Replace JS hooks with LiveView patterns for map page styling, slideover, and theme switching
- Remove BodyClassHook: use CSS :has() selectors on data-map-page attribute
instead of JS toggling body.map-page class
- Remove ResponsiveSlideoverHook: pass viewport_width via LiveSocket connect
params and determine initial slideover state server-side
- Convert theme selector from data-set-theme attributes with DOMContentLoaded
listeners to JS.dispatch("phx:set-theme") with a single event handler
- Fix buggy reRenderAllCharts that incorrectly used `new MapAPRSMap()` as a
Map constructor; charts now re-render via themeChanged event directly
This commit is contained in:
parent
f1b363a2e2
commit
aca0571a3e
5 changed files with 65 additions and 183 deletions
152
assets/js/app.js
152
assets/js/app.js
|
|
@ -40,73 +40,6 @@ import { InfoMap } from "./hooks/info_map";
|
||||||
// Import time ago hook
|
// Import time ago hook
|
||||||
import TimeAgoHook from "./hooks/time_ago_hook";
|
import TimeAgoHook from "./hooks/time_ago_hook";
|
||||||
|
|
||||||
// Responsive Slideover Hook
|
|
||||||
let ResponsiveSlideoverHook = {
|
|
||||||
mounted() {
|
|
||||||
this.isInitialized = false;
|
|
||||||
|
|
||||||
this.handleResize = () => {
|
|
||||||
const isDesktop = window.innerWidth >= 1024;
|
|
||||||
const isMobile = window.innerWidth < 1024;
|
|
||||||
|
|
||||||
// Set initial state based on screen size
|
|
||||||
if (!this.isInitialized) {
|
|
||||||
this.isInitialized = true;
|
|
||||||
if (isDesktop) {
|
|
||||||
this.pushEvent("set_slideover_state", { open: true });
|
|
||||||
} else {
|
|
||||||
this.pushEvent("set_slideover_state", { open: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Initial check after a brief delay to ensure LiveView is ready
|
|
||||||
setTimeout(() => {
|
|
||||||
this.handleResize();
|
|
||||||
}, 100);
|
|
||||||
|
|
||||||
// Listen for resize events with debouncing
|
|
||||||
let resizeTimer;
|
|
||||||
this.debouncedResize = () => {
|
|
||||||
clearTimeout(resizeTimer);
|
|
||||||
resizeTimer = setTimeout(this.handleResize, 150);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener("resize", this.debouncedResize);
|
|
||||||
},
|
|
||||||
|
|
||||||
destroyed() {
|
|
||||||
if (this.debouncedResize) {
|
|
||||||
window.removeEventListener("resize", this.debouncedResize);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Body Class Hook - Updates body class based on map_page assign
|
|
||||||
let BodyClassHook = {
|
|
||||||
mounted() {
|
|
||||||
this.updateBodyClass();
|
|
||||||
},
|
|
||||||
|
|
||||||
updated() {
|
|
||||||
this.updateBodyClass();
|
|
||||||
},
|
|
||||||
|
|
||||||
updateBodyClass() {
|
|
||||||
// Get the map_page value from the element's data attribute
|
|
||||||
const mapPage = this.el?.dataset?.mapPage === "true";
|
|
||||||
|
|
||||||
// Update body class based on map_page value
|
|
||||||
if (document.body && document.body.classList) {
|
|
||||||
if (mapPage) {
|
|
||||||
document.body.classList.add("map-page");
|
|
||||||
} else {
|
|
||||||
document.body.classList.remove("map-page");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// APRS MapAPRSMap Hook
|
// APRS MapAPRSMap Hook
|
||||||
let Hooks = {};
|
let Hooks = {};
|
||||||
|
|
||||||
|
|
@ -206,29 +139,10 @@ Object.keys(WeatherChartHooks).forEach(hookName => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Core hooks - no bundle loading needed
|
// Core hooks - no bundle loading needed
|
||||||
Hooks.ResponsiveSlideoverHook = ResponsiveSlideoverHook;
|
|
||||||
Hooks.BodyClassHook = BodyClassHook;
|
|
||||||
Hooks.ErrorBoundary = ErrorBoundary;
|
Hooks.ErrorBoundary = ErrorBoundary;
|
||||||
Hooks.TimeAgoHook = TimeAgoHook;
|
Hooks.TimeAgoHook = TimeAgoHook;
|
||||||
|
|
||||||
// Helper function to get theme-aware colors
|
// Theme management
|
||||||
const getThemeColors = () => {
|
|
||||||
const isDark = document.documentElement.getAttribute("data-theme") === "dark";
|
|
||||||
return {
|
|
||||||
text: isDark ? "#e5e7eb" : "#111827",
|
|
||||||
grid: isDark ? "#374151" : "#9ca3af",
|
|
||||||
background: isDark ? "rgba(0, 0, 0, 0.1)" : "rgba(255, 255, 255, 0.1)",
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Theme switching functionality
|
|
||||||
const theme = (() => {
|
|
||||||
if (typeof localStorage !== "undefined" && localStorage.getItem("theme")) {
|
|
||||||
return localStorage.getItem("theme");
|
|
||||||
}
|
|
||||||
return "auto";
|
|
||||||
})();
|
|
||||||
|
|
||||||
const applyTheme = (theme) => {
|
const applyTheme = (theme) => {
|
||||||
const element = document.documentElement;
|
const element = document.documentElement;
|
||||||
if (!element) return;
|
if (!element) return;
|
||||||
|
|
@ -244,65 +158,29 @@ const applyTheme = (theme) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Apply initial theme
|
// Apply initial theme from localStorage
|
||||||
applyTheme(theme);
|
applyTheme(localStorage.getItem("theme") || "auto");
|
||||||
window.localStorage.setItem("theme", theme);
|
|
||||||
|
|
||||||
// Global function to re-render all charts
|
// Handle theme changes dispatched from LiveView via JS.dispatch
|
||||||
window.reRenderAllCharts = () => {
|
window.addEventListener("phx:set-theme", (e) => {
|
||||||
// Store all chart instances globally so we can access them
|
const theme = e.detail.theme;
|
||||||
if (!window.chartInstances) {
|
applyTheme(theme);
|
||||||
window.chartInstances = new MapAPRSMap();
|
localStorage.setItem("theme", theme);
|
||||||
}
|
|
||||||
|
|
||||||
// Re-render all stored chart instances
|
|
||||||
window.chartInstances.forEach((chartInstance, elementId) => {
|
|
||||||
if (chartInstance && typeof chartInstance.renderChart === "function") {
|
|
||||||
chartInstance.renderChart();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Also dispatch a custom event that charts can listen to
|
|
||||||
window.dispatchEvent(new CustomEvent("themeChanged"));
|
window.dispatchEvent(new CustomEvent("themeChanged"));
|
||||||
};
|
|
||||||
|
|
||||||
const handleThemeClick = (selectedTheme) => {
|
|
||||||
applyTheme(selectedTheme);
|
|
||||||
localStorage.setItem("theme", selectedTheme);
|
|
||||||
|
|
||||||
// Re-render all charts with new theme colors
|
|
||||||
setTimeout(() => {
|
|
||||||
window.reRenderAllCharts();
|
|
||||||
}, 100);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Listen for system theme changes when auto is selected
|
|
||||||
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", (e) => {
|
|
||||||
if (localStorage.getItem("theme") === "auto") {
|
|
||||||
applyTheme("auto");
|
|
||||||
|
|
||||||
// Re-render all charts with new theme colors
|
|
||||||
setTimeout(() => {
|
|
||||||
window.reRenderAllCharts();
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add event listeners for theme switching
|
// Listen for system theme changes when auto is selected
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", () => {
|
||||||
const themeButtons = document.querySelectorAll("[data-set-theme]");
|
if (localStorage.getItem("theme") === "auto") {
|
||||||
themeButtons.forEach((button) => {
|
applyTheme("auto");
|
||||||
button.addEventListener("click", () => {
|
window.dispatchEvent(new CustomEvent("themeChanged"));
|
||||||
const theme = button.getAttribute("data-set-theme");
|
}
|
||||||
handleThemeClick(theme);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("Creating LiveSocket with hooks:", Object.keys(Hooks));
|
console.log("Creating LiveSocket with hooks:", Object.keys(Hooks));
|
||||||
let liveSocket = new LiveSocket("/live", Socket, {
|
let liveSocket = new LiveSocket("/live", Socket, {
|
||||||
longPollFallbackMs: 2500,
|
longPollFallbackMs: 2500,
|
||||||
params: { _csrf_token: csrfToken },
|
params: { _csrf_token: csrfToken, viewport_width: window.innerWidth },
|
||||||
hooks: Hooks,
|
hooks: Hooks,
|
||||||
timeout: 60000, // 60 second timeout for slow initial loads
|
timeout: 60000, // 60 second timeout for slow initial loads
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -475,7 +475,7 @@ defmodule AprsmeWeb.CoreComponents do
|
||||||
</div>
|
</div>
|
||||||
<ul tabindex="0" class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-52">
|
<ul tabindex="0" class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-52">
|
||||||
<li>
|
<li>
|
||||||
<button data-set-theme="light" data-act-class="ACTIVECLASS" class="flex items-center gap-2">
|
<button phx-click={JS.dispatch("phx:set-theme", detail: %{theme: "light"})} class="flex items-center gap-2">
|
||||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
||||||
<path
|
<path
|
||||||
fill-rule="evenodd"
|
fill-rule="evenodd"
|
||||||
|
|
@ -487,7 +487,7 @@ defmodule AprsmeWeb.CoreComponents do
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<button data-set-theme="dark" data-act-class="ACTIVECLASS" class="flex items-center gap-2">
|
<button phx-click={JS.dispatch("phx:set-theme", detail: %{theme: "dark"})} class="flex items-center gap-2">
|
||||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
||||||
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" />
|
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|
@ -495,7 +495,7 @@ defmodule AprsmeWeb.CoreComponents do
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<button data-set-theme="auto" data-act-class="ACTIVECLASS" class="flex items-center gap-2">
|
<button phx-click={JS.dispatch("phx:set-theme", detail: %{theme: "auto"})} class="flex items-center gap-2">
|
||||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
||||||
<path
|
<path
|
||||||
fill-rule="evenodd"
|
fill-rule="evenodd"
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,7 @@ defmodule AprsmeWeb.Layouts do
|
||||||
|
|
||||||
embed_templates "layouts/*"
|
embed_templates "layouts/*"
|
||||||
|
|
||||||
def body_class(assigns) do
|
def body_class(_assigns) do
|
||||||
base_classes = ["bg-base-100 antialiased"]
|
["bg-base-100 antialiased"]
|
||||||
|
|
||||||
if Map.get(assigns, :map_page, false) do
|
|
||||||
base_classes ++ ["map-page"]
|
|
||||||
else
|
|
||||||
base_classes
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Reset constraints for non-map pages */
|
/* Reset constraints for non-map pages */
|
||||||
body:not(.map-page) main,
|
body:not(:has(#main-content[data-map-page="true"])) main,
|
||||||
body:not(.map-page) .phx-main,
|
body:not(:has(#main-content[data-map-page="true"])) .phx-main,
|
||||||
body:not(.map-page) div[data-phx-main="true"] {
|
body:not(:has(#main-content[data-map-page="true"])) div[data-phx-main="true"] {
|
||||||
overflow: visible !important;
|
overflow: visible !important;
|
||||||
height: auto !important;
|
height: auto !important;
|
||||||
max-height: none !important;
|
max-height: none !important;
|
||||||
|
|
@ -18,48 +18,47 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ensure proper scrolling for non-map pages */
|
/* Ensure proper scrolling for non-map pages */
|
||||||
body:not(.map-page) {
|
body:not(:has(#main-content[data-map-page="true"])) {
|
||||||
overflow-y: auto !important;
|
overflow-y: auto !important;
|
||||||
height: auto !important;
|
height: auto !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Only apply map constraints to map pages */
|
/* Only apply map constraints to map pages */
|
||||||
body.map-page,
|
body:has(#main-content[data-map-page="true"]) {
|
||||||
body.map-page html {
|
|
||||||
overflow: hidden !important;
|
overflow: hidden !important;
|
||||||
height: 100vh !important;
|
height: 100vh !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Map page specific styles */
|
/* Map page specific styles - hide navbar header */
|
||||||
body.map-page header {
|
body:has(#main-content[data-map-page="true"]) .navbar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.map-page main {
|
body:has(#main-content[data-map-page="true"]) main {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
max-width: none;
|
max-width: none;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.map-page main > div {
|
body:has(#main-content[data-map-page="true"]) main > div {
|
||||||
max-width: none;
|
max-width: none;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.map-page .phx-main {
|
body:has(#main-content[data-map-page="true"]) .phx-main {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.map-page div[data-phx-main="true"] {
|
body:has(#main-content[data-map-page="true"]) div[data-phx-main="true"] {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Full page map for APRS home page and callsign pages */
|
/* Full page map for APRS home page and callsign pages */
|
||||||
body.map-page #aprs-map {
|
body:has(#main-content[data-map-page="true"]) #aprs-map {
|
||||||
position: fixed !important;
|
position: fixed !important;
|
||||||
top: 0 !important;
|
top: 0 !important;
|
||||||
left: 0 !important;
|
left: 0 !important;
|
||||||
|
|
@ -72,24 +71,24 @@
|
||||||
|
|
||||||
/* Desktop slideover behavior for map */
|
/* Desktop slideover behavior for map */
|
||||||
@media (min-width: 1024px) {
|
@media (min-width: 1024px) {
|
||||||
body.map-page #aprs-map.slideover-open {
|
body:has(#main-content[data-map-page="true"]) #aprs-map.slideover-open {
|
||||||
right: 352px !important;
|
right: 352px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.map-page #aprs-map.slideover-closed {
|
body:has(#main-content[data-map-page="true"]) #aprs-map.slideover-closed {
|
||||||
right: 0 !important;
|
right: 0 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mobile slideover behavior for map */
|
/* Mobile slideover behavior for map */
|
||||||
@media (max-width: 1023px) {
|
@media (max-width: 1023px) {
|
||||||
body.map-page #aprs-map {
|
body:has(#main-content[data-map-page="true"]) #aprs-map {
|
||||||
right: 0 !important;
|
right: 0 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Slideover panel responsive styles */
|
/* Slideover panel responsive styles */
|
||||||
body.map-page .slideover-panel {
|
body:has(#main-content[data-map-page="true"]) .slideover-panel {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
|
@ -104,35 +103,35 @@
|
||||||
|
|
||||||
/* Desktop slideover behavior */
|
/* Desktop slideover behavior */
|
||||||
@media (min-width: 1024px) {
|
@media (min-width: 1024px) {
|
||||||
body.map-page .slideover-panel.slideover-open {
|
body:has(#main-content[data-map-page="true"]) .slideover-panel.slideover-open {
|
||||||
transform: translateX(0);
|
transform: translateX(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
body.map-page .slideover-panel.slideover-closed {
|
body:has(#main-content[data-map-page="true"]) .slideover-panel.slideover-closed {
|
||||||
transform: translateX(100%);
|
transform: translateX(100%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mobile slideover behavior */
|
/* Mobile slideover behavior */
|
||||||
@media (max-width: 1023px) {
|
@media (max-width: 1023px) {
|
||||||
body.map-page .slideover-panel {
|
body:has(#main-content[data-map-page="true"]) .slideover-panel {
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
box-shadow: -8px 0 32px rgba(0, 0, 0, 0.2);
|
box-shadow: -8px 0 32px rgba(0, 0, 0, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
body.map-page .slideover-panel.slideover-open {
|
body:has(#main-content[data-map-page="true"]) .slideover-panel.slideover-open {
|
||||||
transform: translateX(0);
|
transform: translateX(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
body.map-page .slideover-panel.slideover-closed {
|
body:has(#main-content[data-map-page="true"]) .slideover-panel.slideover-closed {
|
||||||
transform: translateX(100%);
|
transform: translateX(100%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mobile panel improvements */
|
/* Mobile panel improvements */
|
||||||
@media (max-width: 1023px) {
|
@media (max-width: 1023px) {
|
||||||
body.map-page .slideover-panel {
|
body:has(#main-content[data-map-page="true"]) .slideover-panel {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
@ -140,12 +139,12 @@
|
||||||
|
|
||||||
/* Improve scrolling on mobile */
|
/* Improve scrolling on mobile */
|
||||||
@media (max-width: 1023px) {
|
@media (max-width: 1023px) {
|
||||||
body.map-page .slideover-panel {
|
body:has(#main-content[data-map-page="true"]) .slideover-panel {
|
||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
overscroll-behavior: contain;
|
overscroll-behavior: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.map-page .slideover-panel .flex-1 {
|
body:has(#main-content[data-map-page="true"]) .slideover-panel .flex-1 {
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -155,7 +154,6 @@
|
||||||
<main
|
<main
|
||||||
id="main-content"
|
id="main-content"
|
||||||
class={if assigns[:map_page], do: "min-h-screen bg-base-100", else: "bg-base-100"}
|
class={if assigns[:map_page], do: "min-h-screen bg-base-100", else: "bg-base-100"}
|
||||||
phx-hook="BodyClassHook"
|
|
||||||
data-map-page={to_string(assigns[:map_page] || false)}
|
data-map-page={to_string(assigns[:map_page] || false)}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,9 @@ defmodule AprsmeWeb.MapLive.Index do
|
||||||
import AprsmeWeb.Live.Shared.PacketUtils, only: [get_callsign_key: 1]
|
import AprsmeWeb.Live.Shared.PacketUtils, only: [get_callsign_key: 1]
|
||||||
import AprsmeWeb.MapLive.Components
|
import AprsmeWeb.MapLive.Components
|
||||||
import AprsmeWeb.TimeHelpers, only: [time_ago_in_words: 1]
|
import AprsmeWeb.TimeHelpers, only: [time_ago_in_words: 1]
|
||||||
import Phoenix.LiveView, only: [connected?: 1, push_event: 3, push_patch: 2, put_flash: 3]
|
|
||||||
|
import Phoenix.LiveView,
|
||||||
|
only: [connected?: 1, get_connect_params: 1, push_event: 3, push_patch: 2, put_flash: 3]
|
||||||
|
|
||||||
# Import the new components module
|
# Import the new components module
|
||||||
alias Aprsme.Packets
|
alias Aprsme.Packets
|
||||||
|
|
@ -198,6 +200,9 @@ defmodule AprsmeWeb.MapLive.Index do
|
||||||
# Start packet batcher for efficient updates
|
# Start packet batcher for efficient updates
|
||||||
{:ok, batcher_pid} = PacketBatcher.start_link(self())
|
{:ok, batcher_pid} = PacketBatcher.start_link(self())
|
||||||
|
|
||||||
|
# Determine initial slideover state from client viewport width
|
||||||
|
slideover_open = initial_slideover_open?(socket)
|
||||||
|
|
||||||
assign(socket,
|
assign(socket,
|
||||||
map_ready: false,
|
map_ready: false,
|
||||||
map_bounds: initial_bounds,
|
map_bounds: initial_bounds,
|
||||||
|
|
@ -212,7 +217,7 @@ defmodule AprsmeWeb.MapLive.Index do
|
||||||
trail_duration: trail_duration,
|
trail_duration: trail_duration,
|
||||||
historical_hours: historical_hours,
|
historical_hours: historical_hours,
|
||||||
packet_age_threshold: packet_age_threshold,
|
packet_age_threshold: packet_age_threshold,
|
||||||
slideover_open: true,
|
slideover_open: slideover_open,
|
||||||
deployed_at: deployed_at,
|
deployed_at: deployed_at,
|
||||||
map_page: true,
|
map_page: true,
|
||||||
packet_buffer: [],
|
packet_buffer: [],
|
||||||
|
|
@ -239,6 +244,15 @@ defmodule AprsmeWeb.MapLive.Index do
|
||||||
if connection_disabled, do: "disconnected", else: "connected"
|
if connection_disabled, do: "disconnected", else: "connected"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp initial_slideover_open?(socket) do
|
||||||
|
if connected?(socket) do
|
||||||
|
viewport_width = get_connect_params(socket)["viewport_width"] || 1024
|
||||||
|
viewport_width >= 1024
|
||||||
|
else
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@spec assign_defaults(Socket.t(), DateTime.t()) :: Socket.t()
|
@spec assign_defaults(Socket.t(), DateTime.t()) :: Socket.t()
|
||||||
defp assign_defaults(socket, one_hour_ago) do
|
defp assign_defaults(socket, one_hour_ago) do
|
||||||
assign(socket,
|
assign(socket,
|
||||||
|
|
@ -269,7 +283,6 @@ defmodule AprsmeWeb.MapLive.Index do
|
||||||
pending_batch_tasks: [],
|
pending_batch_tasks: [],
|
||||||
# Overlay controls
|
# Overlay controls
|
||||||
overlay_callsign: "",
|
overlay_callsign: "",
|
||||||
# Slideover state - will be set based on screen size
|
|
||||||
slideover_open: true,
|
slideover_open: true,
|
||||||
# Track when last update occurred for real-time display in map sidebar
|
# Track when last update occurred for real-time display in map sidebar
|
||||||
# Updated when packets are processed or map bounds change
|
# Updated when packets are processed or map bounds change
|
||||||
|
|
@ -1431,7 +1444,6 @@ defmodule AprsmeWeb.MapLive.Index do
|
||||||
"slideover-panel",
|
"slideover-panel",
|
||||||
if(@slideover_open, do: "slideover-open", else: "slideover-closed")
|
if(@slideover_open, do: "slideover-open", else: "slideover-closed")
|
||||||
]}
|
]}
|
||||||
phx-hook="ResponsiveSlideoverHook"
|
|
||||||
>
|
>
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="flex items-center justify-between p-6 border-b border-slate-200 dark:border-slate-700 bg-gradient-to-r from-indigo-600 to-purple-600 text-white">
|
<div class="flex items-center justify-between p-6 border-b border-slate-200 dark:border-slate-700 bg-gradient-to-r from-indigo-600 to-purple-600 text-white">
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue