fix: harden frontend browser compatibility
This commit is contained in:
parent
6e47d3b15c
commit
d2fd0d181f
5 changed files with 73 additions and 42 deletions
|
|
@ -217,6 +217,10 @@ const applyTheme = (theme: string | null) => {
|
|||
// Apply initial theme from localStorage
|
||||
applyTheme(localStorage.getItem("theme") || "auto");
|
||||
|
||||
const colorSchemeQuery = window.matchMedia
|
||||
? window.matchMedia("(prefers-color-scheme: dark)")
|
||||
: null;
|
||||
|
||||
// Handle theme changes dispatched from LiveView via JS.dispatch
|
||||
window.addEventListener("phx:set-theme", ((e: CustomEvent<{ theme: string }>) => {
|
||||
const theme = e.detail.theme;
|
||||
|
|
@ -226,14 +230,20 @@ window.addEventListener("phx:set-theme", ((e: CustomEvent<{ theme: string }>) =>
|
|||
}) as EventListener);
|
||||
|
||||
// Listen for system theme changes when auto is selected
|
||||
window
|
||||
.matchMedia("(prefers-color-scheme: dark)")
|
||||
.addEventListener("change", () => {
|
||||
if (localStorage.getItem("theme") === "auto") {
|
||||
applyTheme("auto");
|
||||
window.dispatchEvent(new CustomEvent("themeChanged"));
|
||||
}
|
||||
});
|
||||
const handleSystemThemeChange = () => {
|
||||
if (localStorage.getItem("theme") === "auto") {
|
||||
applyTheme("auto");
|
||||
window.dispatchEvent(new CustomEvent("themeChanged"));
|
||||
}
|
||||
};
|
||||
|
||||
if (colorSchemeQuery) {
|
||||
if (typeof colorSchemeQuery.addEventListener === "function") {
|
||||
colorSchemeQuery.addEventListener("change", handleSystemThemeChange);
|
||||
} else if (typeof (colorSchemeQuery as MediaQueryList).addListener === "function") {
|
||||
(colorSchemeQuery as MediaQueryList).addListener(handleSystemThemeChange);
|
||||
}
|
||||
}
|
||||
|
||||
const liveSocket = new LiveSocket("/live", Socket, {
|
||||
longPollFallbackMs: 5000,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
interface TimeAgoHookContext {
|
||||
el: HTMLElement;
|
||||
timer: ReturnType<typeof setInterval> | null;
|
||||
timer: ReturnType<typeof setTimeout> | null;
|
||||
}
|
||||
|
||||
const TimeAgoHook = {
|
||||
|
|
@ -22,14 +22,19 @@ const TimeAgoHook = {
|
|||
};
|
||||
|
||||
function startTimer(this: TimeAgoHookContext) {
|
||||
const updateTimeAgo = () => {
|
||||
const timestampStr = this.el.dataset.timestamp;
|
||||
if (!timestampStr) return;
|
||||
const timestampStr = this.el.dataset.timestamp;
|
||||
if (!timestampStr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const timestamp = new Date(timestampStr);
|
||||
const now = new Date();
|
||||
const diffMs = now.getTime() - timestamp.getTime();
|
||||
const diffSeconds = Math.floor(diffMs / 1000);
|
||||
const timestampMs = new Date(timestampStr).getTime();
|
||||
if (Number.isNaN(timestampMs)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updateTimeAgo = () => {
|
||||
const nowMs = Date.now();
|
||||
const diffSeconds = Math.max(0, Math.floor((nowMs - timestampMs) / 1000));
|
||||
|
||||
if (diffSeconds < 60) {
|
||||
this.el.textContent = `${diffSeconds} second${diffSeconds !== 1 ? "s" : ""} ago`;
|
||||
|
|
@ -43,33 +48,35 @@ function startTimer(this: TimeAgoHookContext) {
|
|||
const days = Math.floor(diffSeconds / 86400);
|
||||
this.el.textContent = `${days} day${days !== 1 ? "s" : ""} ago`;
|
||||
}
|
||||
|
||||
const nextDelay = getNextUpdateDelay(diffSeconds);
|
||||
this.timer = setTimeout(updateTimeAgo, nextDelay);
|
||||
};
|
||||
|
||||
updateTimeAgo();
|
||||
|
||||
const timestampStr = this.el.dataset.timestamp;
|
||||
if (timestampStr) {
|
||||
const timestamp = new Date(timestampStr);
|
||||
const age = Date.now() - timestamp.getTime();
|
||||
|
||||
let interval: number;
|
||||
if (age < 60000) {
|
||||
interval = 1000;
|
||||
} else if (age < 3600000) {
|
||||
interval = 60000;
|
||||
} else {
|
||||
interval = 300000;
|
||||
}
|
||||
|
||||
this.timer = setInterval(updateTimeAgo, interval);
|
||||
}
|
||||
}
|
||||
|
||||
function stopTimer(this: TimeAgoHookContext) {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer);
|
||||
clearTimeout(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function getNextUpdateDelay(diffSeconds: number): number {
|
||||
if (diffSeconds < 60) {
|
||||
return 1000;
|
||||
}
|
||||
|
||||
if (diffSeconds < 3600) {
|
||||
return (60 - (diffSeconds % 60)) * 1000;
|
||||
}
|
||||
|
||||
if (diffSeconds < 86400) {
|
||||
return (3600 - (diffSeconds % 3600)) * 1000;
|
||||
}
|
||||
|
||||
return (86400 - (diffSeconds % 86400)) * 1000;
|
||||
}
|
||||
|
||||
export default TimeAgoHook;
|
||||
|
|
|
|||
|
|
@ -847,11 +847,15 @@ let MapAPRSMap = {
|
|||
// Use Phoenix LiveView's built-in navigation
|
||||
const liveSocket = getLiveSocket();
|
||||
if (liveSocket) {
|
||||
liveSocket.pushHistoryPatch(navLink.href, "push", navLink);
|
||||
} else {
|
||||
// Fallback to regular navigation if LiveView socket not available
|
||||
window.location.href = navLink.href;
|
||||
try {
|
||||
liveSocket.pushHistoryPatch(navLink.href, "push", navLink);
|
||||
return;
|
||||
} catch (error) {
|
||||
console.warn("LiveView navigation failed, falling back to full navigation", error);
|
||||
}
|
||||
}
|
||||
// Fallback to regular navigation if LiveView socket is not available or patching fails
|
||||
window.location.href = navLink.href;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -127,7 +127,16 @@ export function safePushEvent<T extends BaseEventPayload = BaseEventPayload>(pus
|
|||
* Check if LiveView socket is available
|
||||
*/
|
||||
export function isLiveViewConnected(): boolean {
|
||||
return typeof window !== 'undefined' && !!window.liveSocket;
|
||||
const liveSocket = getLiveSocket();
|
||||
if (!liveSocket) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof liveSocket.isConnected === "function") {
|
||||
return liveSocket.isConnected();
|
||||
}
|
||||
|
||||
return liveSocket.connected !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -135,4 +144,4 @@ export function isLiveViewConnected(): boolean {
|
|||
*/
|
||||
export function getLiveSocket(): LiveSocket | null {
|
||||
return typeof window !== 'undefined' ? window.liveSocket || null : null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
5
assets/js/types/map.d.ts
vendored
5
assets/js/types/map.d.ts
vendored
|
|
@ -110,7 +110,8 @@ export interface MapState {
|
|||
}
|
||||
|
||||
export interface LiveSocket {
|
||||
connected: boolean;
|
||||
connected?: boolean;
|
||||
isConnected?: () => boolean;
|
||||
pushHistoryPatch: (href: string, state: string, target: HTMLElement) => void;
|
||||
}
|
||||
|
||||
|
|
@ -118,4 +119,4 @@ declare global {
|
|||
interface Window {
|
||||
liveSocket?: LiveSocket;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue