Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,23 @@
<link rel="mask-icon" href="./mask-icon.svg" color="#FFFFFF" />
<meta name="theme-color" content="#ffffff" />

<!-- Keep storage key ("meshviewer.theme") and class name ("theme_night") in sync with lib/theme.ts -->
<script>
try {
var t = localStorage.getItem("meshviewer.theme");
if (t === "dark" || (t !== "light" && matchMedia("(prefers-color-scheme: dark)").matches)) {
document.documentElement.classList.add("theme_night");
}
} catch (e) {
try {
if (matchMedia("(prefers-color-scheme: dark)").matches) {
document.documentElement.classList.add("theme_night");
}
} catch (_) {
/* matchMedia unavailable; accept a light paint */
}
}
</script>
<script src="lib/index.ts" type="module"></script>
</head>
<body>
Expand Down
16 changes: 16 additions & 0 deletions lib/gui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { FilterGui } from "./filters/filtergui.js";
import { HostnameFilter } from "./filters/hostname.js";
import * as helper from "./utils/helper.js";
import { Language } from "./utils/language.js";
import { cycleTheme, getTheme, initTheme, themeIconSVG } from "./theme.js";

export const Gui = function (language: ReturnType<typeof Language>) {
const self = {
Expand Down Expand Up @@ -78,6 +79,21 @@ export const Gui = function (language: ReturnType<typeof Language>) {

contentDiv.appendChild(buttons);

initTheme();
let buttonTheme = document.createElement("button");
buttonTheme.classList.add("theme-toggle");
function refreshThemeButton() {
let current = getTheme();
buttonTheme.innerHTML = themeIconSVG(current);
buttonTheme.setAttribute("aria-label", _.t("button.theme." + current));
}
buttonTheme.onclick = function onclick() {
cycleTheme();
refreshThemeButton();
};
refreshThemeButton();
buttons.appendChild(buttonTheme);

let buttonToggle = document.createElement("button");
buttonToggle.classList.add("ion-eye");
buttonToggle.setAttribute("aria-label", _.t("button.switchView"));
Expand Down
13 changes: 0 additions & 13 deletions lib/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,19 +160,6 @@ export const Map = function (linkScale: (t: any) => any, sidebar: ReturnType<typ
map.setZoom(maxZoom);
}
}

let html_tag: Element = document.querySelector("html");
let class_list = html_tag.classList;
const mode = selectedLayer?.options?.mode;
class_list.forEach(function (item) {
if (item.startsWith("theme_")) {
class_list.remove(item);
}
});
if (html_tag && mode && mode !== "" && !html_tag.classList.contains(mode)) {
class_list.add("theme_" + mode);
labelLayer.updateLayer();
}
});

map.on("load", function () {
Expand Down
9 changes: 9 additions & 0 deletions lib/map/labellayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,15 @@ export const LabelLayer = L.GridLayer.extend({
if (this.data) {
this.prepareLabels();
}
this._onThemeChange = () => this.updateLayer();
document.documentElement.addEventListener("themechange", this._onThemeChange);
},
onRemove: function (map) {
if (this._onThemeChange) {
document.documentElement.removeEventListener("themechange", this._onThemeChange);
this._onThemeChange = null;
}
L.GridLayer.prototype.onRemove.call(this, map);
},
setData: function (data, map, nodeDict, linkDict, linkScale) {
let config = window.config;
Expand Down
114 changes: 114 additions & 0 deletions lib/theme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
export type Theme = "light" | "dark" | "auto";

// Keep STORAGE_KEY and DARK_CLASS in sync with the pre-paint inline script in index.html.
const STORAGE_KEY = "meshviewer.theme";
const DARK_CLASS = "theme_night";
const THEMES: Theme[] = ["light", "dark", "auto"];

let inMemory: Theme = "auto";

const darkQuery =
typeof window !== "undefined" && window.matchMedia ? window.matchMedia("(prefers-color-scheme: dark)") : null;

function isTheme(value: string | null): value is Theme {
return value === "light" || value === "dark" || value === "auto";
}

function read(): Theme {
try {
let v = localStorage.getItem(STORAGE_KEY);
if (isTheme(v)) {
return v;
}
} catch (e) {
/* localStorage inaccessible (private mode, sandboxed iframe, file://) */
}
return inMemory;
}

function write(value: Theme): void {
inMemory = value;
try {
localStorage.setItem(STORAGE_KEY, value);
} catch (e) {
/* keep in-memory only */
}
}

export function resolveTheme(): "light" | "dark" {
let current = read();
if (current === "dark") {
return "dark";
}
if (current === "auto" && darkQuery && darkQuery.matches) {
return "dark";
}
return "light";
}

function apply(): void {
let dark = resolveTheme() === "dark";
document.documentElement.classList.toggle(DARK_CLASS, dark);
document.documentElement.dispatchEvent(new CustomEvent("themechange"));
}

export function getTheme(): Theme {
return read();
}

export function cycleTheme(): Theme {
let next = THEMES[(THEMES.indexOf(read()) + 1) % THEMES.length];
write(next);
apply();
return next;
}

export function initTheme(): void {
inMemory = read();
apply();
if (darkQuery) {
darkQuery.addEventListener("change", function () {
if (read() === "auto") {
apply();
}
});
}
}

const SVG_OPEN = '<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">';
const SVG_CLOSE = "</svg>";

const SUN_RAYS =
'<rect x="11" y="1" width="2" height="3" rx="1"/>' +
'<rect x="11" y="20" width="2" height="3" rx="1"/>' +
'<rect x="1" y="11" width="3" height="2" rx="1"/>' +
'<rect x="20" y="11" width="3" height="2" rx="1"/>';

const SUN =
SVG_OPEN +
'<circle cx="12" cy="12" r="5"/>' +
"<g>" +
SUN_RAYS +
"</g>" +
'<g transform="rotate(45 12 12)">' +
SUN_RAYS +
"</g>" +
SVG_CLOSE;

const MOON = SVG_OPEN + '<path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"/>' + SVG_CLOSE;

const AUTO =
SVG_OPEN +
'<path d="M12 4a8 8 0 000 16V4z"/>' +
'<circle cx="12" cy="12" r="8" fill="none" stroke="currentColor" stroke-width="1.5"/>' +
SVG_CLOSE;

export function themeIconSVG(theme: Theme): string {
if (theme === "dark") {
return MOON;
}
if (theme === "auto") {
return AUTO;
}
return SUN;
}
7 changes: 6 additions & 1 deletion public/locale/cz.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,12 @@
"button": {
"switchView": "Přepnout zobrazení",
"location": "Vybrat souřadnice",
"tracking": "Lokalizace"
"tracking": "Lokalizace",
"theme": {
"light": "Světlý (další: tmavý)",
"dark": "Tmavý (další: auto)",
"auto": "Auto (další: světlý)"
}
},
"momentjs": {
"calendar": {
Expand Down
7 changes: 6 additions & 1 deletion public/locale/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,12 @@
"fullscreen": "Vollbildmodus wechseln",
"tracking": "Lokalisierung",
"location": "Koordinaten wählen",
"ruler": "Lineal"
"ruler": "Lineal",
"theme": {
"light": "Hell (nächstes: dunkel)",
"dark": "Dunkel (nächstes: auto)",
"auto": "Auto (nächstes: hell)"
}
},
"momentjs": {
"calendar": {
Expand Down
7 changes: 6 additions & 1 deletion public/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,12 @@
"fullscreen": "Toggle fullscreen",
"tracking": "Localisation",
"location": "Pick coordinates",
"ruler": "Ruler"
"ruler": "Ruler",
"theme": {
"light": "Light (next: dark)",
"dark": "Dark (next: auto)",
"auto": "Auto (next: light)"
}
},
"momentjs": {
"calendar": {
Expand Down
7 changes: 6 additions & 1 deletion public/locale/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,12 @@
"fullscreen": "Vollbildmodus wechseln",
"tracking": "Localisation",
"location": "Choisir les coordonnées",
"ruler": "Règle"
"ruler": "Règle",
"theme": {
"light": "Clair (suivant : sombre)",
"dark": "Sombre (suivant : auto)",
"auto": "Auto (suivant : clair)"
}
},
"momentjs": {
"calendar": {
Expand Down
7 changes: 6 additions & 1 deletion public/locale/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,12 @@
"button": {
"switchView": "Переключить вид",
"location": "Взять координаты",
"tracking": "Локализация"
"tracking": "Локализация",
"theme": {
"light": "Светлая (далее: тёмная)",
"dark": "Тёмная (далее: авто)",
"auto": "Авто (далее: светлая)"
}
},
"momentjs": {
"calendar": {
Expand Down
7 changes: 6 additions & 1 deletion public/locale/tr.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,12 @@
"button": {
"switchView": "Görünümü Değiştir",
"location": "Koordinatları seç",
"tracking": "Yerelleştirme"
"tracking": "Yerelleştirme",
"theme": {
"light": "Açık (sonraki: koyu)",
"dark": "Koyu (sonraki: oto)",
"auto": "Oto (sonraki: açık)"
}
},
"momentjs": {
"calendar": {
Expand Down
10 changes: 10 additions & 0 deletions scss/modules/_button.scss
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,16 @@ button {
margin: variables.$button-distance;
width: auto;
}

&.theme-toggle {
text-align: center;

svg {
height: 1em;
vertical-align: middle;
width: 1em;
}
}
}

// Tooltip
Expand Down
Loading