Skip to content

Commit

Permalink
Night/Light mode
Browse files Browse the repository at this point in the history
  • Loading branch information
sidoh committed Oct 20, 2024
1 parent 7f30b0b commit 644f87d
Show file tree
Hide file tree
Showing 9 changed files with 515 additions and 17 deletions.
30 changes: 23 additions & 7 deletions web2/components/ui/main-nav.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
import * as React from "react";
import { cn } from "@/lib/utils";
import { NavLink, Link } from "@/components/ui/link";
import { Settings } from "lucide-react";
import { Settings, Sun, Moon } from "lucide-react";
import { useSettings } from "@/lib/settings";

function ThemeSwitcher() {
const { theme, toggleTheme } = useSettings();

return (
<button
onClick={toggleTheme}
className="text-slate-500 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-100"
>
{theme === "dark" ? <Sun size={20} /> : <Moon size={20} />}
</button>
);
}

export function MainNav({
className,
...props
Expand Down Expand Up @@ -31,12 +44,15 @@ export function MainNav({
<NavLink href="#/sniffer">Sniffer</NavLink>
</nav>
</div>
<Link
href="#/settings"
className="text-slate-500 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-100"
>
<Settings size={24} />
</Link>
<div className="flex items-center space-x-4">
<ThemeSwitcher />
<Link
href="#/settings"
className="text-slate-500 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-100"
>
<Settings size={24} />
</Link>
</div>
</div>
</div>
);
Expand Down
1 change: 1 addition & 0 deletions web2/dist/versions/1.0.2/bundle.css

Large diffs are not rendered by default.

327 changes: 327 additions & 0 deletions web2/dist/versions/1.0.2/bundle.js

Large diffs are not rendered by default.

133 changes: 133 additions & 0 deletions web2/dist/versions/1.0.2/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MiLight Hub</title>
<link href="data:image/x-icon;base64,AAABAAEAEBAAAAEACABoBQAAFgAAACgAAAAQAAAAIAAAAAEACAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAA/38NABD+CwD/jQsA9pUOABRu/wD/HQwA+gPrAP4MbAAZ8f8ACxb9AP4GkwCW/AoADYj+AP59CQD7FAkAC//nAJYK/QAQ/w4Acgv/AP71CwCSDP8A+AkYAP7rBAAN/+0A/xEcAP1rCwAM/okADf+HAI/7CgAL/5sADP95AA0U/wD4/gsADXr+ABAO/gDlC/8A+v0NAA3/EwB4Ef8A8wr7AP0IeAAN7P4Abf8LAAuJ/wCVDv8A+hcGACH+DgBz/wsA+g7/ABT+ZQAMCv0Aagn/AJT/DQD+EPsADP7sAOf/DAD/Dx0A/g2AAB4N/gB0/gwADZz/ABLu/wAM/BYA+wqMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkYAAAAAAAAAAAAAAUsAAA+NwAAGzIAAAAAAAAAIj0AKhAAHh8AAAAAAAoAAAANAAAAABwAAAA/AAAzIAAAAAAAAAAAAAAmAgAAADsjAAAAAAAAAAASLwAAAAAAAAAAAAAAAAAAAAAAABMnNAAAAAAAAAAAAAArMDwVLREAAAAAAAAAAAAADDUdAAAAAAAAAAAAAAAAAAAAAAAAJDEAAAAAAAAAACU4AAAAKDYAAAAAAAAAAAAAFCEAAAcAAAA6AAAAAAEAAAAXAAAAAABACAAWLgAaAwAAAAAAAAALKQAAOQYAAA4EAAAAAAAAAAAAABkPAAAAAAAAAP5/AADmZwAA8k8AALvdAACf+QAAz/MAAP//AAAf+AAAH/gAAP//AADP8wAAn/kAALvdAADyTwAA5mcAAP5/AAA=" rel="icon" type="image/x-icon" />
<style>
#loading {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 24px;
font-family: Arial, sans-serif;
}
</style>
<script>(function () {
const cdnBase = "https://cdn.jsdelivr.net/gh/sidoh/esp8266_milight_hub@web2/web2/dist/versions/1.0.2/";
const files = [
{
type: "stylesheet",
cdnPath: cdnBase + "bundle.css",
localPath: "dist/bundle.ce967bcb.css",
size: parseInt("8351", 10),
},
{
type: "script",
cdnPath: cdnBase + "bundle.js",
localPath: "dist/bundle.28946f53.js",
size: parseInt("219638", 10),
},
];

function createStylesheetLink(href) {
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = href;
return link;
}

function createScript(src) {
const script = document.createElement("script");
script.src = src;
script.defer = true;
return script;
}

function tryLoadUrl(url, size, retryCount = 0, maxRetries = 5) {
return fetch(url)
.then((response) => {
if (!response.ok) {
throw new Error("Network response was not ok");
}
return Promise.resolve(response);
})
.catch((error) => {
if (retryCount < maxRetries) {
console.warn(`Retrying ${url} (${retryCount + 1}/${maxRetries})`);
return tryLoadUrl(url, size, retryCount + 1, maxRetries);
} else {
throw error;
}
});
}

function tryLoadFile(file) {
const isDevelopment = "production" === "development";
if (isDevelopment) {
return tryLoadUrl(file.localPath, file.size, 0, 0);
} else {
return tryLoadUrl(file.cdnPath, file.size, 0, 0).catch(() =>
tryLoadUrl(file.localPath, file.size, 0, 5)
);
}
}

function loadPage() {
let currentFileIndex = 0;

function loadNextFile() {
if (currentFileIndex >= files.length) {
document.getElementById("loading").style.display = "none";
return;
}

const file = files[currentFileIndex];

tryLoadFile(file)
.then((response) => {
if (!response.ok) {
throw new Error("Failed to load file");
}
return response.blob();
})
.then((blob) => {
const url = URL.createObjectURL(blob);
let element;

if (file.type === "stylesheet") {
element = createStylesheetLink(url);
} else if (file.type === "script") {
element = createScript(url);
}

document.head.appendChild(element);
currentFileIndex++;
loadNextFile();
})
.catch((error) => {
console.error(`Failed to load file: ${file.cdnPath}`, error);
currentFileIndex++;
loadNextFile();
});
}

// Start loading files
loadNextFile();
}

document.addEventListener("DOMContentLoaded", () => {
loadPage();
});
})();
</script>
</head>
<body class="dark">
<div id="page">
<div id="loading">Loading...</div>
</div>
</body>
</html>
2 changes: 1 addition & 1 deletion web2/inline.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const html = `
</style>
<script>${fs.readFileSync("src/pageload.js")}</script>
</head>
<body>
<body class="dark">
<div id="page">
<div id="loading">Loading...</div>
</div>
Expand Down
30 changes: 27 additions & 3 deletions web2/lib/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,53 @@ interface SettingsContextType {
settings: Settings | null;
isLoading: boolean;
updateSettings: (newSettings: Partial<Settings>) => void;
theme: "light" | "dark";
toggleTheme: () => void;
}

const SettingsContext = createContext<SettingsContextType | null>(null);

export const SettingsProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [settings, setSettings] = useState<Settings | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [theme, setTheme] = useState<"light" | "dark">("light");

useEffect(() => {
api.getSettings().then((fetchedSettings) => {
setSettings(fetchedSettings);
setIsLoading(false);
});

// Initialize theme from localStorage or system preference
const savedTheme = localStorage.getItem("theme");
const prefersDarkMode = window.matchMedia("(prefers-color-scheme: dark)").matches;
const initialTheme = savedTheme ? savedTheme : (prefersDarkMode ? "dark" : "light");

setTheme(initialTheme as "light" | "dark");
}, []);

const updateSettings = (newSettings: Partial<Settings>) => {
const updatedSettings = { ...settings, ...newSettings };
setSettings(updatedSettings);
api.putSettings(updatedSettings);
api.putSettings(updatedSettings);
};

const toggleTheme = () => {
const newTheme = theme === "dark" ? "light" : "dark";
setTheme(newTheme);
localStorage.setItem("theme", newTheme);
};

useEffect(() => {
if (theme === "dark") {
document.body.className = "dark";
} else {
document.body.className = "";
}
}, [theme]);

return (
<SettingsContext.Provider value={{ settings, updateSettings, isLoading }}>
<SettingsContext.Provider value={{ settings, updateSettings, isLoading, theme, toggleTheme }}>
{children}
</SettingsContext.Provider>
);
Expand All @@ -43,4 +68,3 @@ export const useSettings = () => {
}
return context;
};

4 changes: 2 additions & 2 deletions web2/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion web2/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "esp8266_milight_hub_ui",
"version": "1.0.1",
"version": "1.0.2",
"description": "",
"main": "index.js",
"scripts": {
Expand Down
3 changes: 0 additions & 3 deletions web2/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@ export default function App() {
);

useEffect(() => {
// Add dark class to body
document.body.classList.add("dark");

const handleHashChange = () => {
const hash = window.location.hash.slice(1);
setCurrentPage(hash as keyof typeof PAGES);
Expand Down

0 comments on commit 644f87d

Please sign in to comment.