dotfiles/templates/website/frontend/src/interop.ts
2025-10-01 19:51:55 -05:00

134 lines
4.5 KiB
TypeScript
Executable file

// This returns the flags passed into your Elm application
export const flags = async ({ env }: ElmLand.FlagsArgs) => {
// Get user's preferred font size by creating a temporary element
const tempDiv = document.createElement("div");
tempDiv.style.fontSize = "1rem";
tempDiv.style.position = "absolute";
tempDiv.style.visibility = "hidden";
document.body.appendChild(tempDiv);
const baseFontSize = parseFloat(window.getComputedStyle(tempDiv).fontSize);
document.body.removeChild(tempDiv);
return {
width: window.innerWidth,
height: window.innerHeight,
baseFontSize: baseFontSize,
};
};
// This function is called after your Elm app starts
export const onReady = ({ app, env }: ElmLand.OnReadyArgs) => {
console.log("Elm is ready", app);
// Simple loading screen with minimum display time
let loadingComplete = false;
function completeLoading() {
if (!loadingComplete && app.ports?.assetsLoaded?.send) {
loadingComplete = true;
console.log("Loading complete - starting fade out sequence");
app.ports.assetsLoaded.send(null);
}
}
// Set up the loading completion trigger
if (app.ports?.checkAssetsLoaded?.subscribe) {
app.ports.checkAssetsLoaded.subscribe(() => {
console.log("Elm app ready - starting loading timer");
// Minimum display time for loading screen (so users can see it)
const minDisplayTime = 1200; // 1.2 seconds
// Check if fonts are loaded, then wait for minimum time
if (document.fonts) {
document.fonts.ready.then(() => {
console.log(
"Fonts loaded, waiting for minimum display time of",
minDisplayTime,
"ms",
);
setTimeout(() => {
console.log("Minimum display time elapsed, triggering fade out");
completeLoading();
}, minDisplayTime);
});
} else {
// Fallback if fonts API not supported
console.log("Fonts API not supported, using fallback timing");
setTimeout(completeLoading, minDisplayTime + 300);
}
});
}
// Determine initial theme first
let initialTheme = "dark"; // Match Elm's hardcoded default
const savedTheme = localStorage.getItem("app-theme");
if (savedTheme) {
initialTheme = savedTheme;
} else if (window.matchMedia) {
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
initialTheme = mediaQuery.matches ? "dark" : "light";
}
// Store the determined initial theme
localStorage.setItem("app-theme", initialTheme);
// Handle saving theme to localStorage AND toggle requests
if (app.ports?.saveTheme?.subscribe) {
app.ports.saveTheme.subscribe((themeOrSignal: unknown) => {
if (themeOrSignal === "toggle") {
// Handle toggle request - now we know localStorage has the current theme
const currentTheme = localStorage.getItem("app-theme") || initialTheme;
const newTheme = currentTheme === "light" ? "dark" : "light";
console.log("Toggling theme from", currentTheme, "to", newTheme);
localStorage.setItem("app-theme", newTheme);
// Send new theme back to Elm
if (app.ports?.loadTheme?.send) {
app.ports.loadTheme.send(newTheme);
}
} else {
// Handle normal theme saving (when theme is set directly)
console.log("Saving theme:", themeOrSignal);
localStorage.setItem("app-theme", themeOrSignal as string);
}
});
}
// Load theme from localStorage on startup
if (app.ports?.loadTheme?.send) {
console.log("Loading initial theme:", initialTheme);
app.ports.loadTheme.send(initialTheme);
}
// Optional: Listen for system theme changes
if (window.matchMedia) {
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
mediaQuery.addEventListener("change", (e) => {
// Only apply if user hasn't manually saved a theme preference
if (!savedTheme) {
const systemTheme = e.matches ? "dark" : "light";
console.log("System theme changed:", systemTheme);
localStorage.setItem("app-theme", systemTheme);
if (app.ports?.loadTheme?.send) {
app.ports.loadTheme.send(systemTheme);
}
}
});
}
};
// Type definitions for Elm Land
namespace ElmLand {
export type FlagsArgs = {
env: Record<string, string>;
};
export type OnReadyArgs = {
env: Record<string, string>;
app: { ports?: Record<string, Port> };
};
export type Port = {
send?: (data: unknown) => void;
subscribe?: (callback: (data: unknown) => unknown) => void;
};
}