1 Commits

Author SHA1 Message Date
60f087e813 chore: Selector-UI-Prototyp als Primaerquelle sichern
Drei Varianten des Selector-Bildschirms, umschaltbar per ?variant= und
?state=, gebaut zur Beantwortung von Issue #8. Gewonnen hat B - eine Ansicht,
in der die Workspace-Liste fuehrt und das Hinzufuegen Nebenaktion ist.

Wegwerfcode, absichtlich nicht auf main: A und C haben ihren Zweck mit der
Entscheidung erfuellt, wuerden auf main aber verrotten. Der Prototyp bindet
die echte Projekt-CSS ein, ist also ohne Build im Browser zu oeffnen.
2026-08-28 08:45:08 +02:00

236
prototype-selector-ui.html Normal file
View File

@@ -0,0 +1,236 @@
<!doctype html>
<!--
PROTOTYP — WEGWERFCODE. Nicht in die App übernehmen.
Drei Varianten des Selector-Bildschirms, umschaltbar über ?variant=A|B|C,
dazu die Zustände über ?state=filled|empty|dialog.
Frage, die er beantwortet: Gitea-Issue #8 "Selector-UI: Anlegen und Öffnen".
Doppelklicken zum Öffnen. Bindet die echte CSS des Projekts ein, damit
Tokens und Komponenten nicht auseinanderlaufen.
-->
<html lang="de">
<head>
<meta charset="utf-8">
<title>DioNotes — Selector-Prototyp</title>
<link rel="stylesheet" href="assets/css/tokens.css">
<link rel="stylesheet" href="assets/css/base.css">
<link rel="stylesheet" href="assets/css/layout.css">
<link rel="stylesheet" href="assets/css/components.css">
<style>
/* ---- Nur für den Prototyp. Was hier steht, ist noch kein Projekt-CSS. ---- */
body { margin: 0; background: var(--dn-bg-app); color: var(--dn-fg);
font-family: var(--dn-font-ui); font-size: var(--dn-text-base); }
/* Listeneintrag — in allen Varianten gleich, damit die Varianten sich in der
Struktur unterscheiden und nicht in der Zeilengestaltung. */
.ws-item { display: flex; align-items: center; gap: var(--dn-space-3);
width: 100%; padding: var(--dn-space-3); border: 1px solid transparent;
border-radius: var(--dn-radius); background: transparent; color: inherit;
text-align: left; cursor: pointer; font: inherit;
transition: background-color var(--dn-dur-fast) var(--dn-ease); }
.ws-item:hover { background: var(--dn-bg-raised); }
.ws-item-body { min-width: 0; flex: 1; }
.ws-item-name { color: var(--dn-fg); font-weight: 500; }
.ws-item-path { color: var(--dn-fg-faint); font-family: var(--dn-font-code);
font-size: var(--dn-text-xs); white-space: nowrap; overflow: hidden;
text-overflow: ellipsis; direction: rtl; text-align: left; }
.ws-item.is-broken .ws-item-name { color: var(--dn-fg-muted); }
.ws-badge { flex: none; padding: 2px var(--dn-space-2);
border-radius: 999px; font-size: var(--dn-text-xs); white-space: nowrap; }
.ws-badge.unreachable { background: color-mix(in oklab, var(--dn-warning) 18%, transparent); color: var(--dn-warning); }
.ws-badge.nomarker { background: color-mix(in oklab, var(--dn-fg-faint) 22%, transparent); color: var(--dn-fg-muted); }
.ws-badge.damaged { background: color-mix(in oklab, var(--dn-danger) 18%, transparent); color: var(--dn-danger); }
.ws-list { display: flex; flex-direction: column; gap: 2px; }
/* Variante B */
.vb-shell { max-width: 40rem; margin: 0 auto; padding: var(--dn-space-7) var(--dn-space-5);
display: flex; flex-direction: column; gap: var(--dn-space-5); height: 100%;
box-sizing: border-box; }
.vb-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--dn-space-4); }
.vb-title { font-size: var(--dn-text-xl); font-weight: 600; }
.vb-list-wrap { flex: 1; min-height: 0; overflow-y: auto; }
.vb-foot { border-top: 1px solid var(--dn-border); padding-top: var(--dn-space-4);
display: flex; justify-content: space-between; align-items: center; gap: var(--dn-space-3); }
/* Variante C */
.vc-shell { display: grid; grid-template-columns: 1fr 20rem; height: 100%; }
.vc-main { padding: var(--dn-space-7); display: flex; flex-direction: column;
gap: var(--dn-space-4); min-height: 0; }
.vc-side { background: var(--dn-bg-sunken); border-left: 1px solid var(--dn-border);
padding: var(--dn-space-6) var(--dn-space-5); display: flex; flex-direction: column;
gap: var(--dn-space-5); }
.vc-brand { font-size: var(--dn-text-xl); font-weight: 600; }
.vc-tag { color: var(--dn-fg-faint); font-size: var(--dn-text-xs);
letter-spacing: var(--dn-tracking-caps); text-transform: uppercase; }
.vc-list-wrap { flex: 1; min-height: 0; overflow-y: auto; }
.vc-drop { border: 1px dashed var(--dn-border-strong); border-radius: var(--dn-radius-lg);
padding: var(--dn-space-5); text-align: center; color: var(--dn-fg-muted);
font-size: var(--dn-text-sm); }
@media (max-width: 720px) { .vc-shell { grid-template-columns: 1fr; }
.vc-side { border-left: 0; border-top: 1px solid var(--dn-border); } }
/* Dialog — in allen Varianten identisch, er ist selbst kein Variantenthema */
.pt-scrim { position: fixed; inset: 0; background: rgb(0 0 0 / 0.55);
display: grid; place-items: center; z-index: 50; }
.pt-dialog { width: min(28rem, calc(100vw - 2rem)); background: var(--dn-bg-sunken);
border: 1px solid var(--dn-border-strong); border-radius: var(--dn-radius-lg);
box-shadow: var(--dn-shadow); padding: var(--dn-space-5);
display: flex; flex-direction: column; gap: var(--dn-space-4); }
.pt-dialog h2 { margin: 0; font-size: var(--dn-text-md); }
.pt-dialog p { margin: 0; color: var(--dn-fg-muted); line-height: var(--dn-leading-normal); }
.pt-dialog code { font-family: var(--dn-font-code); font-size: var(--dn-text-sm);
color: var(--dn-fg); }
.pt-actions { display: flex; justify-content: flex-end; gap: var(--dn-space-2); }
/* Umschalter — bewusst hässlich, damit er nicht als Teil des Entwurfs gelesen wird */
.pt-bar { position: fixed; bottom: var(--dn-space-4); left: 50%;
transform: translateX(-50%); z-index: 99; display: flex; align-items: center;
gap: var(--dn-space-1); padding: var(--dn-space-1); border-radius: 999px;
background: #000; border: 1px solid #555; box-shadow: 0 6px 24px rgb(0 0 0 / .5);
font-family: var(--dn-font-code); font-size: var(--dn-text-xs); }
.pt-bar button { border: 0; background: transparent; color: #fff; cursor: pointer;
padding: 6px 10px; border-radius: 999px; font: inherit; }
.pt-bar button:hover { background: #333; }
.pt-bar .pt-label { padding: 0 var(--dn-space-3); color: #fff; white-space: nowrap; }
.pt-bar .pt-sep { width: 1px; align-self: stretch; background: #444; margin: 0 4px; }
.pt-bar button.on { background: #fff; color: #000; }
</style>
</head>
<body>
<div id="main"><div id="stage"></div></div>
<div class="pt-bar">
<button id="prev" title="Vorherige Variante (←)">◀</button>
<span class="pt-label" id="label"></span>
<button id="next" title="Nächste Variante (→)">▶</button>
<span class="pt-sep"></span>
<button data-state="filled">Liste</button>
<button data-state="empty">Erststart</button>
<button data-state="dialog">Rückfrage</button>
</div>
<script>
const WS = [
{ name: "Notizen", path: "C:\\Users\\marvin\\Notizen" },
{ name: "DioNotes-Testdaten", path: "D:\\Projekte\\DioNotes-Testdaten" },
{ name: "Archiv 2024", path: "E:\\Backup\\Archiv 2024", badge: "unreachable", text: "nicht erreichbar" },
{ name: "Ideen", path: "C:\\Users\\marvin\\Ideen", badge: "nomarker", text: "kein Workspace mehr" },
{ name: "Rezepte", path: "C:\\Users\\marvin\\Rezepte",badge: "damaged", text: "beschädigt" },
];
const esc = s => s.replace(/[&<>"]/g, c => ({ "&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;" }[c]));
const item = w => `
<button class="ws-item ${w.badge ? "is-broken" : ""}">
<span class="ws-item-body">
<span class="ws-item-name">${esc(w.name)}</span>
<span class="ws-item-path">${esc(w.path)}</span>
</span>
${w.badge ? `<span class="ws-badge ${w.badge}">${esc(w.text)}</span>` : ""}
</button>`;
const list = () => `<div class="ws-list">${WS.map(item).join("")}</div>`;
const dialog = `
<div class="pt-scrim"><div class="pt-dialog">
<h2>Ordner ist nicht leer</h2>
<p><code>D:\\Notizen-Import</code> enthält bereits 34 Dateien. DioNotes legt darin
nur den Ordner <code>.dionotes/</code> an und verändert nichts Vorhandenes.</p>
<div class="pt-actions">
<button class="btn btn-ghost">Abbrechen</button>
<button class="btn btn-primary">Workspace anlegen</button>
</div>
</div></div>`;
/* ---- Variante A — Reiter, wie der heutige Code es anlegt ---------------- */
const A = state => `
<div class="workspace-selector-layout">
<div class="workspace-selector-links">
<a class="workspace-selector" href="#">Create new Workspace</a>
<a class="workspace-selector active" href="#">Open Workspace</a>
</div>
<div class="workspace-selector-view">
${state === "empty" ? `
<div class="empty-state">
<span class="empty-state-title">Noch kein Workspace</span>
<span>Lege einen an, um loszulegen.</span>
<button class="btn btn-primary">Ordner wählen…</button>
</div>` : list()}
</div>
</div>`;
/* ---- Variante B — eine Ansicht, Liste führt, Anlegen ist Nebenaktion ---- */
const B = state => `
<div class="vb-shell">
<div class="vb-head">
<span class="vb-title">Workspace öffnen</span>
<span class="field-hint">${state === "empty" ? "" : WS.length + " bekannt"}</span>
</div>
<div class="vb-list-wrap">
${state === "empty" ? `
<div class="empty-state">
<span class="empty-state-title">Hier ist noch nichts</span>
<span>Der erste Workspace ist ein Ordner deiner Wahl.</span>
</div>` : list()}
</div>
<div class="vb-foot">
<span class="field-hint">Ein Workspace ist ein Ordner mit Notizen.</span>
<button class="btn ${state === "empty" ? "btn-primary" : ""}">Neuen anlegen…</button>
</div>
</div>`;
/* ---- Variante C — Startbildschirm, zwei Spalten ------------------------- */
const C = state => `
<div class="vc-shell">
<div class="vc-main">
<span class="vc-tag">Zuletzt verwendet</span>
<div class="vc-list-wrap">
${state === "empty" ? `
<div class="vc-drop">Noch keine Workspaces.<br>Rechts einen Ordner wählen.</div>`
: list()}
</div>
</div>
<aside class="vc-side">
<div>
<div class="vc-brand">DioNotes</div>
<span class="field-hint">Markdown-Notizen, lokal.</span>
</div>
<div class="field">
<span class="field-label">Neuer Workspace</span>
<button class="btn btn-primary">Ordner wählen…</button>
<span class="field-hint">Legt <code>.dionotes/</code> im gewählten Ordner an.</span>
</div>
</aside>
</div>`;
const V = { A: [A, "Reiter (heutiger Code)"], B: [B, "Liste führt"], C: [C, "Startbildschirm"] };
const keys = Object.keys(V);
const p = new URLSearchParams(location.search);
let variant = keys.includes(p.get("variant")) ? p.get("variant") : "A";
let state = ["filled","empty","dialog"].includes(p.get("state")) ? p.get("state") : "filled";
function render() {
const [fn, name] = V[variant];
document.getElementById("stage").innerHTML =
fn(state === "dialog" ? "filled" : state) + (state === "dialog" ? dialog : "");
document.getElementById("label").textContent = `${variant} — ${name}`;
document.querySelectorAll("[data-state]").forEach(b =>
b.classList.toggle("on", b.dataset.state === state));
history.replaceState(null, "", `?variant=${variant}&state=${state}`);
}
const cycle = d => { variant = keys[(keys.indexOf(variant) + d + keys.length) % keys.length]; render(); };
document.getElementById("prev").onclick = () => cycle(-1);
document.getElementById("next").onclick = () => cycle(1);
document.querySelectorAll("[data-state]").forEach(b =>
b.onclick = () => { state = b.dataset.state; render(); });
addEventListener("keydown", e => {
if (/^(INPUT|TEXTAREA)$/.test(e.target.tagName) || e.target.isContentEditable) return;
if (e.key === "ArrowLeft") cycle(-1);
if (e.key === "ArrowRight") cycle(1);
});
render();
</script>
</body>
</html>