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
|
// Apply initial theme from localStorage
|
||||||
applyTheme(localStorage.getItem("theme") || "auto");
|
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
|
// Handle theme changes dispatched from LiveView via JS.dispatch
|
||||||
window.addEventListener("phx:set-theme", ((e: CustomEvent<{ theme: string }>) => {
|
window.addEventListener("phx:set-theme", ((e: CustomEvent<{ theme: string }>) => {
|
||||||
const theme = e.detail.theme;
|
const theme = e.detail.theme;
|
||||||
|
|
@ -226,14 +230,20 @@ window.addEventListener("phx:set-theme", ((e: CustomEvent<{ theme: string }>) =>
|
||||||
}) as EventListener);
|
}) as EventListener);
|
||||||
|
|
||||||
// Listen for system theme changes when auto is selected
|
// Listen for system theme changes when auto is selected
|
||||||
window
|
const handleSystemThemeChange = () => {
|
||||||
.matchMedia("(prefers-color-scheme: dark)")
|
if (localStorage.getItem("theme") === "auto") {
|
||||||
.addEventListener("change", () => {
|
applyTheme("auto");
|
||||||
if (localStorage.getItem("theme") === "auto") {
|
window.dispatchEvent(new CustomEvent("themeChanged"));
|
||||||
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, {
|
const liveSocket = new LiveSocket("/live", Socket, {
|
||||||
longPollFallbackMs: 5000,
|
longPollFallbackMs: 5000,
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
interface TimeAgoHookContext {
|
interface TimeAgoHookContext {
|
||||||
el: HTMLElement;
|
el: HTMLElement;
|
||||||
timer: ReturnType<typeof setInterval> | null;
|
timer: ReturnType<typeof setTimeout> | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TimeAgoHook = {
|
const TimeAgoHook = {
|
||||||
|
|
@ -22,14 +22,19 @@ const TimeAgoHook = {
|
||||||
};
|
};
|
||||||
|
|
||||||
function startTimer(this: TimeAgoHookContext) {
|
function startTimer(this: TimeAgoHookContext) {
|
||||||
const updateTimeAgo = () => {
|
const timestampStr = this.el.dataset.timestamp;
|
||||||
const timestampStr = this.el.dataset.timestamp;
|
if (!timestampStr) {
|
||||||
if (!timestampStr) return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const timestamp = new Date(timestampStr);
|
const timestampMs = new Date(timestampStr).getTime();
|
||||||
const now = new Date();
|
if (Number.isNaN(timestampMs)) {
|
||||||
const diffMs = now.getTime() - timestamp.getTime();
|
return;
|
||||||
const diffSeconds = Math.floor(diffMs / 1000);
|
}
|
||||||
|
|
||||||
|
const updateTimeAgo = () => {
|
||||||
|
const nowMs = Date.now();
|
||||||
|
const diffSeconds = Math.max(0, Math.floor((nowMs - timestampMs) / 1000));
|
||||||
|
|
||||||
if (diffSeconds < 60) {
|
if (diffSeconds < 60) {
|
||||||
this.el.textContent = `${diffSeconds} second${diffSeconds !== 1 ? "s" : ""} ago`;
|
this.el.textContent = `${diffSeconds} second${diffSeconds !== 1 ? "s" : ""} ago`;
|
||||||
|
|
@ -43,33 +48,35 @@ function startTimer(this: TimeAgoHookContext) {
|
||||||
const days = Math.floor(diffSeconds / 86400);
|
const days = Math.floor(diffSeconds / 86400);
|
||||||
this.el.textContent = `${days} day${days !== 1 ? "s" : ""} ago`;
|
this.el.textContent = `${days} day${days !== 1 ? "s" : ""} ago`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const nextDelay = getNextUpdateDelay(diffSeconds);
|
||||||
|
this.timer = setTimeout(updateTimeAgo, nextDelay);
|
||||||
};
|
};
|
||||||
|
|
||||||
updateTimeAgo();
|
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) {
|
function stopTimer(this: TimeAgoHookContext) {
|
||||||
if (this.timer) {
|
if (this.timer) {
|
||||||
clearInterval(this.timer);
|
clearTimeout(this.timer);
|
||||||
this.timer = null;
|
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;
|
export default TimeAgoHook;
|
||||||
|
|
|
||||||
|
|
@ -847,11 +847,15 @@ let MapAPRSMap = {
|
||||||
// Use Phoenix LiveView's built-in navigation
|
// Use Phoenix LiveView's built-in navigation
|
||||||
const liveSocket = getLiveSocket();
|
const liveSocket = getLiveSocket();
|
||||||
if (liveSocket) {
|
if (liveSocket) {
|
||||||
liveSocket.pushHistoryPatch(navLink.href, "push", navLink);
|
try {
|
||||||
} else {
|
liveSocket.pushHistoryPatch(navLink.href, "push", navLink);
|
||||||
// Fallback to regular navigation if LiveView socket not available
|
return;
|
||||||
window.location.href = navLink.href;
|
} 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
|
* Check if LiveView socket is available
|
||||||
*/
|
*/
|
||||||
export function isLiveViewConnected(): boolean {
|
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 {
|
export function getLiveSocket(): LiveSocket | null {
|
||||||
return typeof window !== 'undefined' ? window.liveSocket || null : 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 {
|
export interface LiveSocket {
|
||||||
connected: boolean;
|
connected?: boolean;
|
||||||
|
isConnected?: () => boolean;
|
||||||
pushHistoryPatch: (href: string, state: string, target: HTMLElement) => void;
|
pushHistoryPatch: (href: string, state: string, target: HTMLElement) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -118,4 +119,4 @@ declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
liveSocket?: LiveSocket;
|
liveSocket?: LiveSocket;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue