Skip to content

Automic dark mode#346

Open
grische wants to merge 5 commits intofreifunk:mainfrom
freifunkMUC:auto-dark-mode
Open

Automic dark mode#346
grische wants to merge 5 commits intofreifunk:mainfrom
freifunkMUC:auto-dark-mode

Conversation

@grische
Copy link
Copy Markdown
Contributor

@grische grische commented Apr 18, 2026

Description

This changes the current behaviour and decouples dark mode from the map layer.
The user can now select either dark, light or automatic by using the button on the top right.
The automatic theme switches depending on the system theme (without a reload).

Motivation and Context

As the map layer is not saved, it bothered my how my browser switched between dark and bright mode somewhat random.
This mode behaves similar to other apps (e.g. Github) and should be a lot more intuitive.

How Has This Been Tested?

Tested locally in Chrome with all variants of Themes.

Screenshots/links:

See it live here: https://map.ffmuc.net/

image or image

Checklist:

  • My code follows the code style of this project. (CI will test it anyway and also needs approval)
  • My change requires a change to the documentation.
    • I have updated the documentation accordingly.

grische and others added 3 commits April 18, 2026 22:59
Adds button.theme.{light,dark,auto} across en/de/fr/cz/ru/tr. Each
label names the current theme and, in parentheses, the theme the
next click will switch to ("Light (next: dark)"), giving both
sighted and screen-reader users the same state-plus-action signal
in a single short string. Polyglot is configured with
allowMissing: true, so all locales need the keys to avoid rendering
raw dotted strings to non-English users.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New standalone module lib/theme.ts owns theme state, storage, DOM
class mutation, and matchMedia subscription. Exposes initTheme,
getTheme, resolveTheme, cycleTheme, and themeIconSVG.

Persists the user's choice in localStorage under meshviewer.theme
(greenfield key — no prior storage usage in repo). All localStorage
access is wrapped in try/catch so sandboxed iframes, Safari private
mode, and file:// contexts fall back to in-memory state.

On apply, toggles the existing theme_night class on <html> and
dispatches a "themechange" CustomEvent on document.documentElement
so other modules (the map's label canvas) can react without
creating a direct dependency on this module.

Auto mode subscribes to prefers-color-scheme changes so the UI
flips live without a reload. Icons are inline SVG rather than
font glyphs because the bundled meshviewer.woff2 is a 24-glyph
subset of Ionicons and does not contain sun/moon/auto symbols.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes the theme_* class manipulation from the baselayerchange
handler. The theme is now a user preference owned by lib/theme.ts
and no longer implicitly tied to tile-layer config.

The label canvas renders colors derived from getComputedStyle on
the body (lib/map/labellayer.js), so it still needs to refresh
when the theme changes. LabelLayer subscribes to the themechange
CustomEvent in its own onAdd / onRemove Leaflet lifecycle hooks,
which ties the listener's lifetime to the layer and guarantees
teardown when the layer is removed — map.ts keeps no theme-related
code at all.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread lib/theme.ts
grische and others added 2 commits April 19, 2026 23:44
Adds a button that cycles light -> dark -> auto. The button icon
shows the current theme (sun / moon / half-circle) and the
aria-label carries the short "Light (next: dark)" string from the
locale file, so screen-reader users get the same state-plus-action
signal the icon conveys visually.

The same aria-label is the source for the button's hover tooltip
via the existing CSS rule (::after { content: attr(aria-label) }
on .content button), so no title attribute is set — that would
produce a duplicate native browser tooltip on top of the styled
one.

Calls initTheme() just before the button is rendered so the
matchMedia listener and storage state are active before any
click.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reads the stored theme preference synchronously in the document
head and sets theme_night on <html> before any CSS resolves, so
dark-mode users don't see a light-theme flash while Vite loads
the main bundle. Read-only and wrapped in try/catch; the full
theme module still owns writes and listeners.

If localStorage access throws (sandboxed iframes, storage disabled,
some file:// contexts), a nested try/catch still applies
theme_night based on prefers-color-scheme alone, so OS-dark users
also get the correct first paint without a late flip when
lib/theme.ts boots.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@maurerle
Copy link
Copy Markdown
Member

maurerle commented May 6, 2026

I merged main into this to use the svg icons folder as well:
https://github.com/freifunk/meshviewer/compare/auto-dark-mode

Can you pleas update this PR to this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants