Symbole in beiden Leisten, aus einer Quelle

Die Pixelraster stehen jetzt in web/glyphs.json — einmal. Der Schreibtisch
laedt die Datei selbst, die HoneyBar bekommt sie mit der App-Liste mit;
sie bleibt damit eine einzelne Datei ohne zweite Anfrage. Die abgetippten
Wettersymbole in honeybar.js sind weg, und die Symbolauswahl im
Programm-Editor kommt jetzt aus derselben Datei statt aus einer Liste im
Quelltext.

Neu: Symbole neben den App-Namen im Streifen beider Leisten, mit einem
Schalter (Einstellungen -> HoneyBar) fuer beide zugleich. In den
Aufklappmenues und auf den Kacheln stehen sie immer.
This commit is contained in:
marc committed 2026-08-29 22:21:53 +02:00
1 parent d29b69a426
commit 53ab62d220
9 files changed
+395 -278

No files matched your search

+11
View File
@@ -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; }
+212
View File
@@ -0,0 +1,212 @@
{
"sonne": [
"............",
".....##.....",
"..#..##..#..",
"...#....#...",
"....####....",
"..##....##..",
"..##....##..",
"....####....",
"...#....#...",
"..#..##..#..",
".....##.....",
"............"
],
"heiter": [
"............",
"...##.......",
"..####..#...",
"...##..###..",
"......#####.",
"...####...#.",
"..#....##.#.",
".##........#",
"#..........#",
".##########.",
"............",
"............"
],
"wolke": [
"............",
"....####....",
"...#....#...",
"..#......##.",
".##........#",
"##.........#",
"#..........#",
"#..........#",
".##########.",
"............",
"............",
"............"
],
"nebel": [
"............",
"....####....",
"..##....##..",
".##.......#.",
"#..........#",
".##########.",
"............",
".#########..",
"............",
"..#########.",
"............",
".#########.."
],
"regen": [
"............",
"....####....",
"...#....#...",
"..#......##.",
".##........#",
"##.........#",
".##########.",
"............",
"..#..#..#...",
".#..#..#....",
"..#..#..#...",
".#..#..#...."
],
"schnee": [
"............",
"....####....",
"...#....#...",
"..#......##.",
".##........#",
"##.........#",
".##########.",
"............",
"..#...#...#.",
".#.#.#.#.#..",
"..#...#...#.",
"............"
],
"gewitter": [
"............",
"....####....",
"...#....#...",
"..#......##.",
".##........#",
"##.........#",
".##########.",
"......##....",
".....##.....",
"....####....",
"......##....",
".....##....."
],
"cloud": [
"............",
"....####....",
"...#....#...",
"..#......##.",
".##........#",
"##.........#",
"#..........#",
"#..........#",
".##########.",
"............",
"............",
"............"
],
"note": [
"..########..",
"..#......#..",
"..#.####.#..",
"..#......#..",
"..#.####.#..",
"..#......#..",
"..#.####.#..",
"..#......#..",
"..#.##...#..",
"..#......#..",
"..########..",
"............"
],
"box": [
"............",
".##########.",
".#........#.",
".#........#.",
".#...##...#.",
".#..####..#.",
".#..####..#.",
".#...##...#.",
".#........#.",
".#........#.",
".##########.",
"............"
],
"chip": [
"............",
"..#.#.#.#...",
".##########.",
".#........#.",
"##.######.##",
".#.#....#.#.",
"##.#....#.##",
".#.######.#.",
".#........#.",
".##########.",
"..#.#.#.#...",
"............"
],
"gear": [
"....####....",
"..#.####.#..",
"..########..",
".####..####.",
"###......###",
"##........##",
"##........##",
"###......###",
".####..####.",
"..########..",
"..#.####.#..",
"....####...."
],
"globe": [
"............",
"...######...",
"..#..#...#..",
".#...#....#.",
"############",
"#....#.....#",
"#....#.....#",
"############",
".#...#....#.",
"..#..#...#..",
"...######...",
"............"
],
"disk": [
"............",
"############",
"#..######..#",
"#..#....#..#",
"#..#....#..#",
"#..######..#",
"#..........#",
"#.########.#",
"#.#......#.#",
"#.#......#.#",
"############",
"............"
],
"folder": [
"............",
".####.......",
"#....#......",
"#.....######",
"#..........#",
"#..........#",
"#..........#",
"#..........#",
"#..........#",
"############",
"............",
"............"
]
}
+38 -20
View File
@@ -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 += `<rect x="${x - run}" y="${y}" width="${run}" height="1"/>`; run = 0; }
}
});
return `<svg viewBox="0 0 12 12" fill="currentColor" aria-hidden="true">${d}</svg>`;
return `<svg viewBox="0 0 12 12" fill="currentColor" shape-rendering="crispEdges" aria-hidden="true">${d}</svg>`;
}
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) + `<span class="grad">${Math.round(w.grad)}°</span>`;
el.innerHTML = rasterSvg(glyphs[w.glyph] || glyphs.wolke) + `<span class="grad">${Math.round(w.grad)}°</span>`;
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;
+2
View File
@@ -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.
+32 -224
View File
@@ -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] === "#") {
+33 -4
View File
@@ -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)}
</div>
<div class="field">
<span class="label">HoneyBar</span>
${seg("barGlyphs", [["on", "symbole an"], ["off", "nur namen"]], s.barGlyphs)}
<p class="ae-hint" style="margin-left:0">gilt für den App-Streifen hier und in jeder App</p>
</div>
<div class="field">
<span class="label">Apps öffnen</span>
${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) => `
<details class="approw" ${a.__neu ? "open" : ""}>
@@ -823,7 +843,14 @@ function buildAppMenu() {
menu.innerHTML =
`<div class="sm-head">wechseln zu</div>` +
(state.cfg.apps.length
? state.cfg.apps.map((a) => `<button data-app="${escapeAttr(a.id)}">${escapeHtml(a.name)}</button>`).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.
`<button data-app="${escapeAttr(a.id)}"><span class="sm-ico">${glyphSvg(a.glyph)}</span>${escapeHtml(a.name)}</button>`
)
.join("")
: `<button data-settings="1">keine app eingetragen …</button>`);
menu.querySelectorAll("[data-app]").forEach((b) =>
@@ -925,7 +952,9 @@ function buildStartMenu() {
closeAppMenu();
menu.innerHTML = `<div class="sm-head">starten</div>` +
state.cfg.apps.map((a) => `<button data-app="${a.id}">${escapeHtml(a.name)}</button>`).join("") +
state.cfg.apps
.map((a) => `<button data-app="${escapeAttr(a.id)}"><span class="sm-ico">${glyphSvg(a.glyph)}</span>${escapeHtml(a.name)}</button>`)
.join("") +
`<hr /><button data-win="programme">Programme</button>
<button data-win="system">System</button>
<button data-win="settings">Einstellungen</button>