diff --git a/IDEAS.md b/IDEAS.md index c79ff8f..adea24a 100644 --- a/IDEAS.md +++ b/IDEAS.md @@ -59,39 +59,29 @@ hält — dieselbe Liste, die heute schon die CORS-Freigabe steuert. Das ist ein Sicherheitsentscheidung und keine Kleinigkeit; erst bauen, wenn der Umweg über den Schreibtisch wirklich nervt. -### 1.3 App-Schublade mit Symbolen +### 1.3 App-Schublade mit Symbolen — ✅ halb gebaut (29.08.2026) -**Die Idee:** ein Knopf, der eine Übersicht aller Apps aufklappt — mit Symbol. -Die Liste kommt aus HoneyOS. Eine neue App trägt man einmal ein und sie taucht -überall auf, ohne dass eine App angefasst wird. +**Gebaut: die geteilte Symbolquelle.** Die Raster liegen in `web/glyphs.json`, +der Schreibtisch lädt sie, die HoneyBar bekommt sie mit der App-Liste mit +(siehe [README](README.md)). Damit tragen beide Leisten dieselben Symbole neben +den App-Namen, und die doppelt abgetippten Wettersymbole sind weg. -**Stand:** Die Hälfte steht. `honeybar.js` holt die Liste aus `/api/apps`, bringt -seine Sitzung mit und hat ein Aufklappmenü. Es fehlt die Darstellung als Raster -mit Symbolen — heute sind es Textknöpfe. Bei zwei Apps geht das noch, ab vier -wird es eng, am Handy sofort. +Dazu ein Schalter *Einstellungen → HoneyBar*: Symbole im App-Streifen an oder +aus, für beide Leisten zugleich. In den Aufklappmenüs und auf den Kacheln +stehen sie immer — dort ist Platz, und dort sucht man eine App, statt sie im +Vorbeigehen wiederzuerkennen. -**Der eigentliche Punkt: die Symbole.** Das Pixelraster lebt in `os.js` -(`GLYPHS`), die HoneyBar kennt es nicht. Zwei Wege: +**Offen bleibt die Schublade selbst:** ein Raster aus Symbolen statt einer +Liste, wenn es viele Apps werden. Heute reicht der Streifen samt Menü; das ist +erst ab sechs, sieben Apps eine echte Frage. -- **Pixelglyphen teilen** — `GLYPHS` + `glyphSvg()` in eine eigene Datei, die - Desktop und HoneyBar beide laden. Kein Upload, kein Speicher, passt zum Look. - Nachteil: nur die eingebauten Symbole. -- **Echte Bilder** — pro App eine Bildadresse oder ein Upload in HoneyOS. - Flexibler, aber: Speicher, Größenbegrenzungen, Auslieferung, und der - Retro-Look bekommt bunte Fremdkörper. - -→ **Empfehlung: Pixelglyphen teilen** — als Erstes, weil es sofort trägt. - -**Beschlossen als Planung (25.08.2026): eigene Logos hochladen.** Je App ein -Bild in HoneyOS hinterlegen, statt nur aus den eingebauten Glyphen zu wählen. -Nicht als Ersatz für die Pixelglyphen, sondern daneben: Glyphe als Rückfall, -wenn kein Bild da ist. - -**Das Logo gehört ausdrücklich auch in die HoneyBar**, nicht nur auf den -Schreibtisch — dort ist die Unterscheidung am nötigsten, weil die Knöpfe klein -sind und nebeneinanderstehen. Damit ist die geteilte Symbolquelle aus 1.3 -Voraussetzung: Beide Seiten müssen dasselbe Bild bekommen, sonst pflegt man es -zweimal. +**Ebenfalls offen: eigene Logos hochladen** (beschlossen als Planung, +25.08.2026). Je App ein Bild in HoneyOS, die Pixelglyphe als Rückfall, wenn +keins da ist. Das Logo gehört ausdrücklich auch in die HoneyBar — dort ist die +Unterscheidung am nötigsten, weil die Knöpfe klein sind und nebeneinanderstehen. +Der Weg dorthin ist durch die geteilte Quelle jetzt kurz: Beide Seiten holen +ihre Symbole schon aus derselben Antwort, es müsste nur ein Bild an die Stelle +des Rasters treten können. Was daran zu bauen ist, in dieser Reihenfolge: diff --git a/README.md b/README.md index 564e74b..d2f9ab2 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,31 @@ Gespeichert wird die Liste als Ganzes; der Server prüft sie noch einmal und ant dem, was er wirklich behalten hat. Eine Adresse ohne `https://` kommt als Fehlermeldung zurück, nicht als kaputter Eintrag. +## Ein Satz Symbole für alles + +Die Pixelraster stehen **an genau einer Stelle**: `web/glyphs.json`, 12×12 +Zeichen je Symbol, ein `#` ist ein Pixel. Der Schreibtisch holt die Datei +selbst; die HoneyBar in fremden Apps bekommt die Raster **mit der App-Liste +mitgeliefert** — sie soll eine einzelne Datei ohne zweite Anfrage bleiben, und +`/api/apps` fragt sie ohnehin. Wer dort ein Symbol ergänzt, kann es sofort im +Programm-Editor auswählen; abgetippt wird nichts mehr. + +Dass das nötig war, hatte einen Anlass: Die Wettersymbole standen einmal in +`os.js` **und** in `honeybar.js`. Sie liefen auseinander, und niemand merkte es, +bis das Wetter in der einen Leiste anders aussah als in der anderen. + +**Wo die Symbole stehen:** + +| | Symbol | +|---|---| +| App-Streifen in beiden Leisten | nach Schalter *Einstellungen → HoneyBar* | +| Aufklappmenü beider Leisten | immer | +| Kacheln, Schreibtischsymbole, Startmenü | immer | + +Der Schalter gilt für **beide** Leisten zugleich — es ist dieselbe Leiste, nur +an zwei Orten. Er liegt wie alles andere im Server, wirkt also auf jedem Gerät +und in jeder App, ohne dass eine App neu gebaut wird. + ## Zwei Betriebsarten, ein Datenstand | | Terminal-Modus | Desktop-Modus | @@ -446,8 +471,9 @@ internal/ web/ index.html css/style.css + glyphs.json die Pixelraster — die einzige Quelle, beide Leisten js/core.js Zustand, Server, Einstellungen, Kleinkram - js/glyphs.js die Pixelsymbole + js/glyphs.js lädt die Raster und baut SVGs daraus js/os.js die beiden Ansichten, Fenster, Dialoge js/widgets/ ein Widget je Datei honeybar/ die HoneyBar für fremde Seiten — bewusst eine Datei diff --git a/main.go b/main.go index 615c0e0..1ecbe48 100644 --- a/main.go +++ b/main.go @@ -28,6 +28,26 @@ import ( //go:embed all:web var webFS embed.FS +// glyphen sind die Pixelraster aus web/glyphs.json. Der Schreibtisch holt sich +// dieselbe Datei selbst; die HoneyBar in einer fremden App kann das nicht — sie +// ist bewusst eine einzelne Datei ohne zweite Anfrage. Also reist das Raster +// mit der App-Liste mit. Eine Quelle, zwei Wege, kein Auseinanderlaufen. +var glyphen = ladeGlyphen() + +func ladeGlyphen() map[string][]string { + b, err := webFS.ReadFile("web/glyphs.json") + if err != nil { + log.Printf("glyphs.json fehlt: %v", err) + return nil + } + var g map[string][]string + if err := json.Unmarshal(b, &g); err != nil { + log.Printf("glyphs.json unlesbar: %v", err) + return nil + } + return g +} + // Version ist die einzige Stelle, an der sie steht. build.sh liest sie hier aus // und vergibt denselben Docker-Tag; die Oberfläche holt sie über /api/public. // Vorher stand sie zusätzlich in os.js und lief mit den Image-Tags auseinander. @@ -316,6 +336,7 @@ func (s *Server) handleApps(w http.ResponseWriter, r *http.Request, sub string) writeJSON(w, http.StatusOK, map[string]any{ "apps": s.store.Apps(), "settings": s.store.SettingsFor(sub), + "glyphs": glyphen, }) } diff --git a/web/css/style.css b/web/css/style.css index 29c87b5..3f65a07 100644 --- a/web/css/style.css +++ b/web/css/style.css @@ -231,6 +231,13 @@ body { white-space: nowrap; } .mb-app:hover { color: var(--p); border-color: var(--p-dim); } +/* Symbol und Name in einer Zeile, wie in der HoneyBar. Das Symbol trägt die + Textfarbe, also stimmt es auch im umgekehrten Zustand der aktiven App. */ +.mb-app { display: inline-flex; align-items: center; gap: 6px; } +.mb-ico, +.sm-ico { display: block; width: 12px; height: 12px; flex: 0 0 auto; } +.mb-ico svg, +.sm-ico svg { width: 12px; height: 12px; display: block; } .mb-app[aria-current="true"] { color: var(--bg); background: var(--p); @@ -816,6 +823,10 @@ body { text-align: left; padding: 6px 9px; cursor: pointer; + /* Symbol links, Name daneben — in den Menüs stehen die Symbole immer. */ + display: flex; + align-items: center; + gap: 8px; } .startmenu button:hover { background: var(--p); color: var(--bg); text-shadow: none; } .startmenu hr { border: 0; border-top: 1px solid var(--p-faint); margin: 3px 0; } diff --git a/web/glyphs.json b/web/glyphs.json new file mode 100644 index 0000000..c4dbf29 --- /dev/null +++ b/web/glyphs.json @@ -0,0 +1,212 @@ +{ + "sonne": [ + "............", + ".....##.....", + "..#..##..#..", + "...#....#...", + "....####....", + "..##....##..", + "..##....##..", + "....####....", + "...#....#...", + "..#..##..#..", + ".....##.....", + "............" + ], + "heiter": [ + "............", + "...##.......", + "..####..#...", + "...##..###..", + "......#####.", + "...####...#.", + "..#....##.#.", + ".##........#", + "#..........#", + ".##########.", + "............", + "............" + ], + "wolke": [ + "............", + "....####....", + "...#....#...", + "..#......##.", + ".##........#", + "##.........#", + "#..........#", + "#..........#", + ".##########.", + "............", + "............", + "............" + ], + "nebel": [ + "............", + "....####....", + "..##....##..", + ".##.......#.", + "#..........#", + ".##########.", + "............", + ".#########..", + "............", + "..#########.", + "............", + ".#########.." + ], + "regen": [ + "............", + "....####....", + "...#....#...", + "..#......##.", + ".##........#", + "##.........#", + ".##########.", + "............", + "..#..#..#...", + ".#..#..#....", + "..#..#..#...", + ".#..#..#...." + ], + "schnee": [ + "............", + "....####....", + "...#....#...", + "..#......##.", + ".##........#", + "##.........#", + ".##########.", + "............", + "..#...#...#.", + ".#.#.#.#.#..", + "..#...#...#.", + "............" + ], + "gewitter": [ + "............", + "....####....", + "...#....#...", + "..#......##.", + ".##........#", + "##.........#", + ".##########.", + "......##....", + ".....##.....", + "....####....", + "......##....", + ".....##....." + ], + "cloud": [ + "............", + "....####....", + "...#....#...", + "..#......##.", + ".##........#", + "##.........#", + "#..........#", + "#..........#", + ".##########.", + "............", + "............", + "............" + ], + "note": [ + "..########..", + "..#......#..", + "..#.####.#..", + "..#......#..", + "..#.####.#..", + "..#......#..", + "..#.####.#..", + "..#......#..", + "..#.##...#..", + "..#......#..", + "..########..", + "............" + ], + "box": [ + "............", + ".##########.", + ".#........#.", + ".#........#.", + ".#...##...#.", + ".#..####..#.", + ".#..####..#.", + ".#...##...#.", + ".#........#.", + ".#........#.", + ".##########.", + "............" + ], + "chip": [ + "............", + "..#.#.#.#...", + ".##########.", + ".#........#.", + "##.######.##", + ".#.#....#.#.", + "##.#....#.##", + ".#.######.#.", + ".#........#.", + ".##########.", + "..#.#.#.#...", + "............" + ], + "gear": [ + "....####....", + "..#.####.#..", + "..########..", + ".####..####.", + "###......###", + "##........##", + "##........##", + "###......###", + ".####..####.", + "..########..", + "..#.####.#..", + "....####...." + ], + "globe": [ + "............", + "...######...", + "..#..#...#..", + ".#...#....#.", + "############", + "#....#.....#", + "#....#.....#", + "############", + ".#...#....#.", + "..#..#...#..", + "...######...", + "............" + ], + "disk": [ + "............", + "############", + "#..######..#", + "#..#....#..#", + "#..#....#..#", + "#..######..#", + "#..........#", + "#.########.#", + "#.#......#.#", + "#.#......#.#", + "############", + "............" + ], + "folder": [ + "............", + ".####.......", + "#....#......", + "#.....######", + "#..........#", + "#..........#", + "#..........#", + "#..........#", + "#..........#", + "############", + "............", + "............" + ] +} diff --git a/web/honeybar/honeybar.js b/web/honeybar/honeybar.js index 88749b3..a3090a1 100644 --- a/web/honeybar/honeybar.js +++ b/web/honeybar/honeybar.js @@ -56,6 +56,7 @@ let apps = DEFAULT_APPS; let settings = null; + let glyphs = {}; // Was die Seite oben schon hatte, bevor die HoneyBar sie heruntergeschoben hat. let urspruenglichesPolster = null; @@ -69,22 +70,13 @@ const SAFE = "env(safe-area-inset-top, 0px)"; const HOEHE = "calc(" + BAR_H + "px + " + SAFE + ")"; - /* Wettersymbole als Pixelraster, dieselben Namen wie in weather.go. - Sie stehen auch in os.js — bis die geteilte Symbolquelle (1.3) gebaut ist, - ist das die kleinere Sünde: Die HoneyBar soll eine einzelne Datei bleiben, - die eine fremde App mit einer Zeile einbindet. */ - const WX = { - sonne: "............;.....##.....;..#..##..#..;...#....#...;....####....;..##....##..;..##....##..;....####....;...#....#...;..#..##..#..;.....##.....;............", - heiter: "............;...##.......;..####..#...;...##..###..;......#####.;...####...#.;..#....##.#.;.##........#;#..........#;.##########.;............;............", - wolke: "............;....####....;...#....#...;..#......##.;.##........#;##.........#;#..........#;#..........#;.##########.;............;............;............", - nebel: "............;....####....;..##....##..;.##.......#.;#..........#;.##########.;............;.#########..;............;..#########.;............;.#########..", - regen: "............;....####....;...#....#...;..#......##.;.##........#;##.........#;.##########.;............;..#..#..#...;.#..#..#....;..#..#..#...;.#..#..#....", - schnee: "............;....####....;...#....#...;..#......##.;.##........#;##.........#;.##########.;............;..#...#...#.;.#.#.#.#.#..;..#...#...#.;............", - gewitter: "............;....####....;...#....#...;..#......##.;.##........#;##.........#;.##########.;......##....;.....##.....;....####....;......##....;.....##.....", - }; - - function wxSvg(name) { - const grid = (WX[name] || WX.wolke).split(";"); + /* Alle Pixelraster kommen mit der App-Liste vom Server (web/glyphs.json). + Frueher standen die Wettersymbole hier noch einmal abgetippt — genau das + war schon einmal auseinandergelaufen. Diese Datei bleibt trotzdem eine + einzelne ohne zweite Anfrage: Sie fragt ohnehin nach der App-Liste, und + die bringt die Raster einfach mit. */ + function rasterSvg(grid) { + if (!grid) return ""; let d = ""; grid.forEach((row, y) => { let run = 0; @@ -93,7 +85,7 @@ else if (run) { d += ``; run = 0; } } }); - return ``; + return ``; } const host = document.createElement("honey-bar"); @@ -163,6 +155,11 @@ } .apps::-webkit-scrollbar { display: none; } .app { color: var(--p-mid); letter-spacing: .08em; flex: 0 0 auto; scroll-snap-align: end; } + /* Symbol und Name in einer Zeile. Das Symbol traegt die Textfarbe, also + stimmt es in jeder Roehre und im umgekehrten Zustand von selbst. */ + .app, .menu a[role="menuitem"] { display: inline-flex; align-items: center; gap: 6px; } + .ico { display: block; width: 12px; height: 12px; flex: 0 0 auto; } + .ico svg { width: 12px; height: 12px; display: block; } .app:hover { color: var(--p); border-color: var(--p-dim); } .app[aria-current="page"] { background: var(--p); border-color: var(--p); color: var(--bg); text-shadow: none; @@ -261,20 +258,40 @@ nav.innerHTML = ""; menuApps.innerHTML = ""; + // Der Schalter gilt nur fuer den Streifen. Im Aufklappmenue stehen die + // Symbole immer: Dort ist Platz, und dort sucht man eine App, statt sie + // im Vorbeigehen wiederzuerkennen. + const symboleImStreifen = !settings || settings.barGlyphs !== "off"; + + const symbol = (app) => { + const grid = glyphs[app.glyph] || glyphs.box; + if (!grid) return null; + const span = document.createElement("span"); + span.className = "ico"; + span.innerHTML = rasterSvg(grid); + return span; + }; + apps.forEach((app) => { if (app.id === "honeyos") return; // steckt schon links im Logo const a = document.createElement("a"); a.className = "app"; a.href = app.url; - a.textContent = app.name; + if (symboleImStreifen) { + const ico = symbol(app); + if (ico) a.appendChild(ico); + } + a.appendChild(document.createTextNode(app.name)); if (app.id === current) a.setAttribute("aria-current", "page"); nav.appendChild(a); const m = document.createElement("a"); m.href = app.url; m.setAttribute("role", "menuitem"); - m.textContent = (app.id === current ? "▸ " : "  ") + app.name; + const ico = symbol(app); + if (ico) m.appendChild(ico); + m.appendChild(document.createTextNode((app.id === current ? "\u25b8 " : "") + app.name)); menuApps.appendChild(m); }); } @@ -300,7 +317,7 @@ return; // offline oder HoneyOS aus — die App geht das nichts an } const el = root.getElementById("wx"); - el.innerHTML = wxSvg(w.glyph) + `${Math.round(w.grad)}°`; + el.innerHTML = rasterSvg(glyphs[w.glyph] || glyphs.wolke) + `${Math.round(w.grad)}°`; el.title = `${w.ort}: ${w.text}, ${Math.round(w.grad)}°`; el.hidden = false; } @@ -510,6 +527,7 @@ apps = [{ id: "honeyos", name: "DESKTOP", url: homeUrl }].concat(data.apps); } settings = data.settings || null; + if (data.glyphs) glyphs = data.glyphs; } } catch { /* nicht angemeldet, kein CORS, kein Netz → eingebaute Liste; diff --git a/web/js/core.js b/web/js/core.js index dd74de0..2325c5d 100644 --- a/web/js/core.js +++ b/web/js/core.js @@ -14,6 +14,8 @@ export const DEFAULT_SETTINGS = { flicker: "on", glow: "on", openIn: "tab", // tab | same + // Symbole im App-Streifen beider Leisten. Im Aufklappmenü stehen sie immer. + barGlyphs: "on", // Wetter. Zwei Schalter statt einem: Der Schreibtisch und die HoneyBar sind // zwei verschiedene Orte, und wer das Wetter beim Arbeiten in einer App will, // will es nicht zwingend auch auf einer Seite, die er selten aufruft. diff --git a/web/js/glyphs.js b/web/js/glyphs.js index ab8668f..1ed4811 100644 --- a/web/js/glyphs.js +++ b/web/js/glyphs.js @@ -1,235 +1,43 @@ /* Die Pixelsymbole von HoneyOS. * - * Eigene Datei, weil sie Daten sind und nicht Verhalten: zwölf mal zwölf - * Zeichen je Symbol, mehr passiert hier nicht. Dieselben Raster stehen noch - * einmal in honeybar/honeybar.js — die soll eine einzelne Datei bleiben, die - * eine fremde App mit einer Zeile einbindet (siehe IDEAS 1.3). + * Die Raster selbst stehen in `web/glyphs.json` — **an genau einer Stelle**, + * absichtlich als Daten statt als Quelltext: Der Schreibtisch holt sie hier, + * die HoneyBar in fremden Apps bekommt sie vom Server mit der App-Liste + * mitgeliefert (siehe main.go). Vorher standen sie zweimal da, und die + * Wettersymbole waren schon einmal auseinandergelaufen. + * + * Das Laden geschieht mit `await` auf oberster Ebene: Wer dieses Modul + * einbindet, bekommt fertige Symbole und darf sie überall gerade heraus + * benutzen — sonst müsste jede Zeichenfunktion asynchron werden. */ -/* ── Pixel-Glyphen ───────────────────────────────────────────────────────── - * 12×12-Raster als Zeichenketten. Ein `#` ist ein Pixel. So bleiben die Icons - * im Retro-Raster statt aus einer Icon-Bibliothek zu kommen. - */ -export const GLYPHS = { - // Wettersymbole. Dieselben Namen wie in weather.go (wmo) und in der HoneyBar — - // solange 1.3 (geteilte Symbolquelle) nicht gebaut ist, stehen sie zweimal da. - sonne: [ - "............", - ".....##.....", - "..#..##..#..", - "...#....#...", - "....####....", - "..##....##..", - "..##....##..", - "....####....", - "...#....#...", - "..#..##..#..", - ".....##.....", - "............", - ], - heiter: [ - "............", - "...##.......", - "..####..#...", - "...##..###..", - "......#####.", - "...####...#.", - "..#....##.#.", - ".##........#", - "#..........#", - ".##########.", - "............", - "............", - ], - wolke: [ - "............", - "....####....", - "...#....#...", - "..#......##.", - ".##........#", - "##.........#", - "#..........#", - "#..........#", - ".##########.", - "............", - "............", - "............", - ], - nebel: [ - "............", - "....####....", - "..##....##..", - ".##.......#.", - "#..........#", - ".##########.", - "............", - ".#########..", - "............", - "..#########.", - "............", - ".#########..", - ], - regen: [ - "............", - "....####....", - "...#....#...", - "..#......##.", - ".##........#", - "##.........#", - ".##########.", - "............", - "..#..#..#...", - ".#..#..#....", - "..#..#..#...", - ".#..#..#....", - ], - schnee: [ - "............", - "....####....", - "...#....#...", - "..#......##.", - ".##........#", - "##.........#", - ".##########.", - "............", - "..#...#...#.", - ".#.#.#.#.#..", - "..#...#...#.", - "............", - ], - gewitter: [ - "............", - "....####....", - "...#....#...", - "..#......##.", - ".##........#", - "##.........#", - ".##########.", - "......##....", - ".....##.....", - "....####....", - "......##....", - ".....##.....", - ], - cloud: [ - "............", - "....####....", - "...#....#...", - "..#......##.", - ".##........#", - "##.........#", - "#..........#", - "#..........#", - ".##########.", - "............", - "............", - "............", - ], - note: [ - "..########..", - "..#......#..", - "..#.####.#..", - "..#......#..", - "..#.####.#..", - "..#......#..", - "..#.####.#..", - "..#......#..", - "..#.##...#..", - "..#......#..", - "..########..", - "............", - ], - box: [ - "............", - ".##########.", - ".#........#.", - ".#........#.", - ".#...##...#.", - ".#..####..#.", - ".#..####..#.", - ".#...##...#.", - ".#........#.", - ".#........#.", - ".##########.", - "............", - ], - chip: [ - "............", - "..#.#.#.#...", - ".##########.", - ".#........#.", - "##.######.##", - ".#.#....#.#.", - "##.#....#.##", - ".#.######.#.", - ".#........#.", - ".##########.", - "..#.#.#.#...", - "............", - ], - gear: [ - "....####....", - "..#.####.#..", - "..########..", - ".####..####.", - "###......###", - "##........##", - "##........##", - "###......###", - ".####..####.", - "..########..", - "..#.####.#..", - "....####....", - ], - globe: [ - "............", - "...######...", - "..#..#...#..", - ".#...#....#.", - "############", - "#....#.....#", - "#....#.....#", - "############", - ".#...#....#.", - "..#..#...#..", - "...######...", - "............", - ], - disk: [ - "............", - "############", - "#..######..#", - "#..#....#..#", - "#..#....#..#", - "#..######..#", - "#..........#", - "#.########.#", - "#.#......#.#", - "#.#......#.#", - "############", - "............", - ], - folder: [ - "............", - ".####.......", - "#....#......", - "#.....######", - "#..........#", - "#..........#", - "#..........#", - "#..........#", - "#..........#", - "############", - "............", - "............", - ], -}; +/** 12×12-Raster als Zeichenketten, ein `#` ist ein Pixel. */ +export const GLYPHS = await ladeGlyphen(); + +async function ladeGlyphen() { + try { + const res = await fetch(new URL("../glyphs.json", import.meta.url), { cache: "no-store" }); + if (res.ok) return await res.json(); + } catch { + /* fällt unten auf ein leeres Raster zurück */ + } + // Ohne Symbole ist der Schreibtisch hässlich, aber benutzbar — ein leerer + // Bildschirm wäre schlimmer. Passieren kann es nur, wenn der eigene Server + // gerade nicht antwortet; dann ist ohnehin nichts zu holen. + console.warn("HoneyOS: glyphs.json nicht geladen — Symbole fehlen."); + return {}; +} export function glyphSvg(name) { - const grid = GLYPHS[name] || GLYPHS.box; + return rasterSvg(GLYPHS[name] || GLYPHS.box); +} + +/** Baut aus einem 12×12-Raster ein SVG. Waagerechte Läufe werden + * zusammengefasst: weniger Rechtecke, gleiches Bild. */ +export function rasterSvg(grid) { + if (!grid) return ""; const rects = []; grid.forEach((row, y) => { - // Waagerechte Läufe zusammenfassen: weniger Rechtecke, gleiches Bild. let run = 0; for (let x = 0; x <= row.length; x++) { if (row[x] === "#") { diff --git a/web/js/os.js b/web/js/os.js index 477a888..fa46171 100644 --- a/web/js/os.js +++ b/web/js/os.js @@ -123,10 +123,19 @@ function renderShell() { function renderMenubarApps() { const nav = $("mbApps"); nav.innerHTML = ""; + // Derselbe Schalter wie in der HoneyBar fremder Apps: Wer die Symbole dort + // abstellt, will sie hier auch nicht — es ist dieselbe Leiste. + const mitSymbol = state.settings.barGlyphs !== "off"; state.cfg.apps.forEach((app) => { const b = document.createElement("button"); b.className = "mb-app"; - b.textContent = app.name; + if (mitSymbol) { + const ico = document.createElement("span"); + ico.className = "mb-ico"; + ico.innerHTML = glyphSvg(app.glyph); + b.appendChild(ico); + } + b.appendChild(document.createTextNode(app.name)); b.title = `${app.name} öffnen — ${app.url}`; b.addEventListener("click", () => launch(app)); nav.appendChild(b); @@ -564,6 +573,12 @@ function openSettings() { ${seg("glow", [["on", "glühen an"], ["off", "aus"]], s.glow)} +
+ HoneyBar + ${seg("barGlyphs", [["on", "symbole an"], ["off", "nur namen"]], s.barGlyphs)} +

gilt für den App-Streifen hier und in jeder App

+
+
Apps öffnen ${seg("openIn", [["tab", "neuer tab"], ["same", "gleiches fenster"]], s.openIn)} @@ -619,6 +634,7 @@ function openSettings() { renderStats(); if (key.startsWith("weather")) loadWeather(); if (key === "openIn" && state.settings.mode === "terminal") renderTiles(); + if (key === "barGlyphs") renderMenubarApps(); }); }); @@ -684,7 +700,11 @@ function openSettings() { function openApps() { let draft = state.cfg.apps.map((a) => ({ ...a })); - const GLYPHS_LIST = ["cloud", "note", "box", "chip", "folder", "gear", "globe", "disk"]; + // Die Auswahl kommt aus glyphs.json selbst — wer dort ein Symbol ergänzt, + // kann es sofort wählen. Die Wettersymbole liegen in derselben Datei (die + // HoneyBar braucht beides in einer Antwort), taugen aber nicht als App-Logo. + const WETTER_GLYPHEN = ["sonne", "heiter", "wolke", "nebel", "regen", "schnee", "gewitter"]; + const GLYPHS_LIST = Object.keys(GLYPHS).filter((g) => !WETTER_GLYPHEN.includes(g)); const rowHtml = (a, i) => `
@@ -823,7 +843,14 @@ function buildAppMenu() { menu.innerHTML = `
wechseln zu
` + (state.cfg.apps.length - ? state.cfg.apps.map((a) => ``).join("") + ? state.cfg.apps + .map( + (a) => + // Im Menü stehen die Symbole immer — dort ist Platz, und dort + // sucht man eine App, statt sie im Vorbeigehen zu erkennen. + `` + ) + .join("") : ``); menu.querySelectorAll("[data-app]").forEach((b) => @@ -925,7 +952,9 @@ function buildStartMenu() { closeAppMenu(); menu.innerHTML = `
starten
` + - state.cfg.apps.map((a) => ``).join("") + + state.cfg.apps + .map((a) => ``) + .join("") + `