Reihenfolge mit Pfeilen, Zifferntasten entfernt
Sortiert wird im Programm-Editor mit Pfeilen in der Kopfzeile jeder Zeile — das Array ist die Reihenfolge, also gilt sie ueberall zugleich: Streifen, Schublade, Kacheln, Schreibtischsymbole, Startmenue. Neue Apps haengen sich hinten an, statt eine Taste zu verlangen. Die Zifferntasten sind ganz weg (Marcs Entscheidung): ein Pflichtfeld je App, das beim Sortieren nur die Frage aufgeworfen haette, ob es mitwandert. Damit faellt das Feld 'key' aus dem Datenmodell, aus der Kachel, aus der Hilfe und aus der Statuszeile. Alte Eintraege verlieren es beim naechsten Speichern still. Kein A-Z-Knopf — erstmal zu viel, kommt zurueck wenn er fehlt.
This commit is contained in:
1 parent
cc7a50c621
commit
0e1c986f90
6 files changed
+73
-32
No files matched your search
@@ -117,9 +117,15 @@ Zeigerereignis-Variante lohnt erst, wenn sie mehrfach gebraucht wird, und genau
|
||||
das kommt: „Widgets verschieben" braucht sie zwingend. Wird sie dort gebaut,
|
||||
kann die App-Liste sie später erben.
|
||||
|
||||
Dazu ein Knopf **A–Z**: einmal alphabetisch sortieren, danach ist es wieder
|
||||
Handarbeit. Kein dauerhafter Modus, keine Einstellung, keine zwei
|
||||
Sortierzustände im Kopf.
|
||||
**Kein A–Z-Knopf** (Marc, 29.08.2026): erstmal zu viel. Bei einer Handvoll Apps
|
||||
sortiert man einmal von Hand und rührt es nie wieder an — ein zweiter Weg
|
||||
dorthin ist Zierat. Kommt zurück, wenn er fehlt.
|
||||
|
||||
**Die Zifferntasten sind bei der Gelegenheit ganz weggefallen.** Sie waren ein
|
||||
Pflichtfeld je App, das man beim Anlegen ausfüllen musste, und sie hätten beim
|
||||
Sortieren dieselbe Frage aufgeworfen: mitwandern oder stehenbleiben? Beides
|
||||
falsch. Neue Apps hängen sich jetzt einfach hinten an, die Reihenfolge macht man
|
||||
mit den Pfeilen.
|
||||
|
||||
Beim Bauen zu beachten:
|
||||
|
||||
@@ -128,8 +134,6 @@ Beim Bauen zu beachten:
|
||||
- Beim Neuzeichnen müssen offene Zeilen offen bleiben.
|
||||
- Gespeichert wird wie bisher die ganze Liste auf einmal: Man kann probieren,
|
||||
ohne dass sofort alles umspringt.
|
||||
- **Die Zifferntasten wandern nicht mit.** Die stehen je App fest; sonst startet
|
||||
`2` morgen etwas anderes als heute.
|
||||
|
||||
#### Und eine eigene Reihenfolge je Nutzer? — später, aber der Weg bleibt offen
|
||||
|
||||
|
||||
@@ -121,7 +121,10 @@ fragt über die Domaingrenze und bleibt dort leer.
|
||||
| Name, Untertitel | Beschriftung auf Kachel und Symbol |
|
||||
| Adresse | wohin der Start springt |
|
||||
| Symbol | cloud · note · box · chip · folder · gear · globe · disk |
|
||||
| Taste | Zifferntaste im Terminal-Modus |
|
||||
|
||||
**Die Reihenfolge machst du mit den Pfeilen** ▲▼ in der Kopfzeile jeder Zeile — sie gilt
|
||||
überall gleich: Streifen, Schublade, Kacheln, Schreibtischsymbole, Startmenü. Neue Apps
|
||||
hängen sich hinten an.
|
||||
|
||||
Gespeichert wird die Liste als Ganzes; der Server prüft sie noch einmal und antwortet mit
|
||||
dem, was er wirklich behalten hat. Eine Adresse ohne `https://` kommt als Fehlermeldung
|
||||
@@ -178,7 +181,7 @@ und in jeder App, ohne dass eine App neu gebaut wird.
|
||||
Umschalten mit **`M`**, über *Einstellungen → Betriebsart* oder im Startmenü. Gleich bleiben
|
||||
in beiden: Menüleiste, Palette, Einstellungen, App-Liste.
|
||||
|
||||
**Tasten:** `1` `2` App starten · `M` Betriebsart · `S` Einstellungen · `?` Hilfe ·
|
||||
**Tasten:** `M` Betriebsart · `S` Einstellungen · `?` Hilfe ·
|
||||
`Esc` schließt Startmenü, oberstes Fenster oder Dialog — in dieser Reihenfolge.
|
||||
|
||||
## Wetter
|
||||
|
||||
@@ -29,7 +29,6 @@ type App struct {
|
||||
Subtitle string `json:"subtitle,omitempty"`
|
||||
URL string `json:"url"`
|
||||
Glyph string `json:"glyph,omitempty"`
|
||||
Key string `json:"key,omitempty"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
@@ -120,7 +119,6 @@ func (s *Store) SetApps(apps []App) error {
|
||||
a.Subtitle = strings.TrimSpace(a.Subtitle)
|
||||
a.URL = strings.TrimSpace(a.URL)
|
||||
a.Glyph = strings.TrimSpace(a.Glyph)
|
||||
a.Key = strings.TrimSpace(a.Key)
|
||||
|
||||
if !idPattern.MatchString(a.ID) {
|
||||
return fmt.Errorf("App %d: ID muss klein geschrieben sein und mit Buchstabe oder Ziffer beginnen", i+1)
|
||||
@@ -140,9 +138,6 @@ func (s *Store) SetApps(apps []App) error {
|
||||
if a.Glyph != "" && !knownGlyphs[a.Glyph] {
|
||||
a.Glyph = ""
|
||||
}
|
||||
if len([]rune(a.Key)) > 1 {
|
||||
return fmt.Errorf("App %q: Taste ist ein einzelnes Zeichen", a.ID)
|
||||
}
|
||||
for _, f := range []string{a.Name, a.Subtitle, a.URL} {
|
||||
if len(f) > maxFieldLen {
|
||||
return fmt.Errorf("App %q: ein Feld ist länger als %d Zeichen", a.ID, maxFieldLen)
|
||||
|
||||
+17
-9
@@ -372,13 +372,6 @@ body {
|
||||
}
|
||||
.tile:active { transform: none; }
|
||||
|
||||
.tile-key {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 10px;
|
||||
font-size: 11px;
|
||||
color: var(--p-dim);
|
||||
}
|
||||
|
||||
.tile-icon {
|
||||
width: 46px;
|
||||
@@ -464,8 +457,6 @@ body {
|
||||
.tile-name { grid-area: name; }
|
||||
.tile-sub { grid-area: unter; }
|
||||
.tile-state { grid-area: zustand; margin: 0; white-space: nowrap; }
|
||||
/* Die Zifferntaste hilft nur mit Tastatur. */
|
||||
.tile-key { display: none; }
|
||||
}
|
||||
|
||||
/* ── Modal ─────────────────────────────────────────────────────────────── */
|
||||
@@ -954,6 +945,23 @@ body {
|
||||
.approw .ae-url { margin-left: auto; color: var(--p-dim); font-size: 11px; max-width: 40%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.approw .ae-body { padding: 10px; display: flex; flex-direction: column; gap: 8px; }
|
||||
|
||||
/* Sortieren, ohne die Zeile aufzumachen: Die Pfeile sitzen in der Kopfzeile.
|
||||
Die Adresse rückt dafür ein Stück nach links (kein margin-left:auto mehr an
|
||||
ihr allein, sondern hier). */
|
||||
.approw .ae-move { margin-left: 8px; display: flex; gap: 2px; flex: none; }
|
||||
.ae-arrow {
|
||||
background: none;
|
||||
border: 1px solid var(--p-dim);
|
||||
color: var(--p-mid);
|
||||
font: inherit;
|
||||
font-size: 10px;
|
||||
line-height: 1;
|
||||
padding: 4px 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.ae-arrow:hover:not(:disabled) { background: var(--p); color: var(--bg); border-color: var(--p); text-shadow: none; }
|
||||
.ae-arrow:disabled { opacity: 0.3; cursor: default; }
|
||||
|
||||
.ae-field { display: grid; grid-template-columns: 92px 1fr; gap: 8px; align-items: center; }
|
||||
.ae-field > span { font-size: 11px; color: var(--p-dim); letter-spacing: 0.1em; }
|
||||
.ae-field input, .ae-field select {
|
||||
|
||||
+1
-1
@@ -92,7 +92,7 @@
|
||||
</footer>
|
||||
|
||||
<footer class="statusbar" id="statusbar">
|
||||
<span id="hint">[1] [2] app starten · [M] modus · [S] einstellungen · [?] hilfe</span>
|
||||
<span id="hint">[M] modus · [S] einstellungen · [?] hilfe</span>
|
||||
<span class="sb-right" id="sbRight"></span>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
+41
-10
@@ -277,7 +277,6 @@ function renderTiles() {
|
||||
a.target = state.settings.openIn === "tab" ? "_blank" : "_self";
|
||||
a.rel = "noopener";
|
||||
a.innerHTML = `
|
||||
${app.key ? `<span class="tile-key">[${app.key}]</span>` : ""}
|
||||
<span class="tile-icon">${glyphSvg(app.glyph)}</span>
|
||||
<span class="tile-name">${escapeHtml(app.name)}</span>
|
||||
<span class="tile-sub">${escapeHtml(app.subtitle || "")}</span>
|
||||
@@ -707,11 +706,17 @@ function openApps() {
|
||||
const GLYPHS_LIST = Object.keys(GLYPHS).filter((g) => !WETTER_GLYPHEN.includes(g));
|
||||
|
||||
const rowHtml = (a, i) => `
|
||||
<details class="approw" ${a.__neu ? "open" : ""}>
|
||||
<details class="approw" ${a.__offen ? "open" : ""}>
|
||||
<summary>
|
||||
<span class="ae-glyph">${glyphSvg(a.glyph)}</span>
|
||||
<span>${escapeHtml(a.name || a.id || "neue app")}</span>
|
||||
<span class="ae-url">${escapeHtml(a.url || "")}</span>
|
||||
<span class="ae-move">
|
||||
<button class="ae-arrow" data-up="${i}" ${i === 0 ? "disabled" : ""}
|
||||
title="nach oben" aria-label="${escapeAttr(a.name || a.id)} nach oben">▲</button>
|
||||
<button class="ae-arrow" data-down="${i}" ${i === draft.length - 1 ? "disabled" : ""}
|
||||
title="nach unten" aria-label="${escapeAttr(a.name || a.id)} nach unten">▼</button>
|
||||
</span>
|
||||
</summary>
|
||||
<div class="ae-body">
|
||||
<label class="ae-field"><span>ID</span>
|
||||
@@ -727,8 +732,6 @@ function openApps() {
|
||||
<select data-i="${i}" data-f="glyph">
|
||||
${GLYPHS_LIST.map((g) => `<option value="${g}" ${a.glyph === g ? "selected" : ""}>${g}</option>`).join("")}
|
||||
</select></label>
|
||||
<label class="ae-field"><span>Taste</span>
|
||||
<input data-i="${i}" data-f="key" value="${escapeAttr(a.key || "")}" maxlength="1" placeholder="1" /></label>
|
||||
<div class="ae-actions">
|
||||
<button class="btn btn-danger" data-del="${i}">löschen</button>
|
||||
</div>
|
||||
@@ -752,6 +755,35 @@ function openApps() {
|
||||
render(root);
|
||||
})
|
||||
);
|
||||
|
||||
// Merken, was offen ist — das Umsortieren zeichnet die Liste neu, und ohne
|
||||
// das klappte dabei alles zu, was man gerade bearbeitet.
|
||||
list.querySelectorAll(".approw").forEach((d, i) => {
|
||||
d.addEventListener("toggle", () => {
|
||||
if (draft[i]) draft[i].__offen = d.open;
|
||||
});
|
||||
});
|
||||
|
||||
// Die Pfeile sitzen im <summary>: Ohne preventDefault klappt jeder
|
||||
// Sortierklick die Zeile auf oder zu.
|
||||
const tausche = (i, j) => {
|
||||
if (j < 0 || j >= draft.length) return;
|
||||
[draft[i], draft[j]] = [draft[j], draft[i]];
|
||||
render(root);
|
||||
// Zurück auf denselben Knopf, damit man mehrmals hintereinander drücken
|
||||
// kann, ohne die Maus nachzuführen.
|
||||
const wieder = list.querySelector(`[data-${j < i ? "up" : "down"}="${j}"]`);
|
||||
if (wieder && !wieder.disabled) wieder.focus();
|
||||
};
|
||||
|
||||
list.querySelectorAll("[data-up], [data-down]").forEach((b) =>
|
||||
b.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const i = Number(b.dataset.up ?? b.dataset.down);
|
||||
tausche(i, "up" in b.dataset ? i - 1 : i + 1);
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
openPanel(
|
||||
@@ -768,7 +800,8 @@ function openApps() {
|
||||
render(root);
|
||||
|
||||
root.querySelector("#btnAdd").addEventListener("click", () => {
|
||||
draft.push({ id: "", name: "", url: "", glyph: "box", key: String(draft.length + 1), __neu: true });
|
||||
// Immer ans Ende — sortiert wird mit den Pfeilen, nicht beim Anlegen.
|
||||
draft.push({ id: "", name: "", url: "", glyph: "box", __offen: true });
|
||||
render(root);
|
||||
});
|
||||
|
||||
@@ -777,9 +810,10 @@ function openApps() {
|
||||
msg.dataset.kind = "";
|
||||
msg.textContent = "speichere …";
|
||||
try {
|
||||
const clean = draft.map(({ __neu, ...rest }) => rest);
|
||||
const clean = draft.map(({ __offen, ...rest }) => rest);
|
||||
state.cfg.apps = (await api("/api/apps", { method: "PUT", body: JSON.stringify(clean) })).apps;
|
||||
draft = state.cfg.apps.map((a) => ({ ...a }));
|
||||
const offen = new Set(draft.filter((a) => a.__offen).map((a) => a.id));
|
||||
draft = state.cfg.apps.map((a) => ({ ...a, __offen: offen.has(a.id) }));
|
||||
msg.textContent = `${state.cfg.apps.length} gespeichert.`;
|
||||
render(root);
|
||||
renderMenubarApps();
|
||||
@@ -807,7 +841,6 @@ function openAbout() {
|
||||
<code>desktop</code> Symbole und Fenster. Die HoneyBar oben ist in beiden dieselbe —
|
||||
und dieselbe steckt per <code>honeybar/honeybar.js</code> auch in Aether und InkNote.</p>
|
||||
<dl class="keys">
|
||||
${state.cfg.apps.map((a) => `<dt>${a.key || "·"}</dt><dd>${escapeHtml(a.name)} starten</dd>`).join("")}
|
||||
<dt>M</dt><dd>Betriebsart wechseln</dd>
|
||||
<dt>S</dt><dd>Einstellungen</dd>
|
||||
<dt>?</dt><dd>diese Hilfe</dd>
|
||||
@@ -1137,8 +1170,6 @@ async function main() {
|
||||
}
|
||||
if (typing) return;
|
||||
|
||||
const app = state.cfg.apps.find((a) => a.key === e.key);
|
||||
if (app) return launch(app);
|
||||
if (e.key === "m" || e.key === "M") setMode(state.settings.mode === "desktop" ? "terminal" : "desktop");
|
||||
if (e.key === "s" || e.key === "S") openSettings();
|
||||
if (e.key === "?") openAbout();
|
||||
|
||||
Reference in new issue
Block a user