mobile
This commit is contained in:
parent
8446303399
commit
03c20ebfd8
3 changed files with 315 additions and 5 deletions
|
|
@ -34,6 +34,25 @@ main {
|
|||
height: 100vh !important;
|
||||
width: 100vw !important;
|
||||
z-index: 1 !important;
|
||||
transition: right 0.3s ease-in-out !important;
|
||||
}
|
||||
|
||||
/* Desktop slideover behavior for map */
|
||||
@media (min-width: 1024px) {
|
||||
#aprs-map.slideover-open {
|
||||
right: 352px !important;
|
||||
}
|
||||
|
||||
#aprs-map.slideover-closed {
|
||||
right: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile slideover behavior for map */
|
||||
@media (max-width: 1023px) {
|
||||
#aprs-map {
|
||||
right: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Ensure the map container div has proper dimensions */
|
||||
|
|
@ -273,3 +292,113 @@ body.home-page main > div {
|
|||
.historical-dot-marker {
|
||||
z-index: 15 !important;
|
||||
}
|
||||
|
||||
/* Slideover toggle button styles */
|
||||
.slideover-toggle {
|
||||
position: fixed !important;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
z-index: 1001;
|
||||
background: white;
|
||||
border: 2px solid rgba(0, 0, 0, 0.2);
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.slideover-toggle:hover {
|
||||
background: #f8fafc;
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.slideover-toggle:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
/* Hide toggle on desktop when slideover is open */
|
||||
@media (min-width: 1024px) {
|
||||
.slideover-toggle.slideover-open {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile toggle adjustments */
|
||||
@media (max-width: 1023px) {
|
||||
.slideover-toggle {
|
||||
right: 15px;
|
||||
top: 15px;
|
||||
padding: 12px;
|
||||
border-radius: 50%;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Slideover panel responsive styles */
|
||||
.slideover-panel {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: 100vh;
|
||||
width: 352px;
|
||||
background: white;
|
||||
box-shadow: -4px 0 24px rgba(0, 0, 0, 0.15);
|
||||
z-index: 1000;
|
||||
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Desktop slideover behavior */
|
||||
@media (min-width: 1024px) {
|
||||
.slideover-panel.slideover-open {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.slideover-panel.slideover-closed {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile slideover behavior */
|
||||
@media (max-width: 1023px) {
|
||||
.slideover-panel {
|
||||
width: 100vw;
|
||||
max-width: 400px;
|
||||
box-shadow: -8px 0 32px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.slideover-panel.slideover-open {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.slideover-panel.slideover-closed {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile panel improvements */
|
||||
@media (max-width: 1023px) {
|
||||
.slideover-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
/* Improve scrolling on mobile */
|
||||
@media (max-width: 1023px) {
|
||||
.slideover-panel {
|
||||
-webkit-overflow-scrolling: touch;
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
|
||||
.slideover-panel .flex-1 {
|
||||
min-height: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,9 +27,52 @@ let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("
|
|||
// Import minimal APRS map hook
|
||||
import MapAPRSMap from "./map";
|
||||
|
||||
// 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);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// APRS Map Hook
|
||||
let Hooks = {};
|
||||
Hooks.APRSMap = MapAPRSMap;
|
||||
Hooks.ResponsiveSlideoverHook = ResponsiveSlideoverHook;
|
||||
|
||||
let liveSocket = new LiveSocket("/live", Socket, {
|
||||
longPollFallbackMs: 2500,
|
||||
|
|
|
|||
|
|
@ -56,7 +56,9 @@ defmodule AprsWeb.MapLive.Index do
|
|||
# Overlay controls
|
||||
overlay_callsign: "",
|
||||
trail_duration: "1",
|
||||
historical_hours: "1"
|
||||
historical_hours: "1",
|
||||
# Slideover state - will be set based on screen size
|
||||
slideover_open: true
|
||||
)
|
||||
end
|
||||
|
||||
|
|
@ -248,6 +250,16 @@ defmodule AprsWeb.MapLive.Index do
|
|||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("toggle_slideover", _params, socket) do
|
||||
{:noreply, assign(socket, slideover_open: !socket.assigns.slideover_open)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("set_slideover_state", %{"open" => open}, socket) do
|
||||
{:noreply, assign(socket, slideover_open: open)}
|
||||
end
|
||||
|
||||
@spec handle_bounds_update(map(), Socket.t()) :: {:noreply, Socket.t()}
|
||||
defp handle_bounds_update(bounds, socket) do
|
||||
# Update the map bounds from the client
|
||||
|
|
@ -474,10 +486,28 @@ defmodule AprsWeb.MapLive.Index do
|
|||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 352px;
|
||||
bottom: 0;
|
||||
height: 100vh;
|
||||
z-index: 1;
|
||||
transition: right 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
/* Desktop styles */
|
||||
@media (min-width: 1024px) {
|
||||
#aprs-map.slideover-open {
|
||||
right: 352px;
|
||||
}
|
||||
|
||||
#aprs-map.slideover-closed {
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile styles */
|
||||
@media (max-width: 1023px) {
|
||||
#aprs-map {
|
||||
right: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.locate-button {
|
||||
|
|
@ -550,6 +580,7 @@ defmodule AprsWeb.MapLive.Index do
|
|||
|
||||
<div
|
||||
id="aprs-map"
|
||||
class={if @slideover_open, do: "slideover-open", else: "slideover-closed"}
|
||||
phx-hook="APRSMap"
|
||||
phx-update="ignore"
|
||||
data-center={Jason.encode!(@map_center)}
|
||||
|
|
@ -575,10 +606,63 @@ defmodule AprsWeb.MapLive.Index do
|
|||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Slideover Toggle Button -->
|
||||
<button
|
||||
class={["slideover-toggle", if(@slideover_open, do: "slideover-open", else: "slideover-closed")]}
|
||||
phx-click="toggle_slideover"
|
||||
title={if @slideover_open, do: "Hide controls", else: "Show controls"}
|
||||
>
|
||||
<%= if @slideover_open do %>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="m9 18 6-6-6-6" />
|
||||
</svg>
|
||||
<% else %>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="m15 18-6-6 6-6" />
|
||||
</svg>
|
||||
<% end %>
|
||||
</button>
|
||||
|
||||
<!-- Mobile Backdrop -->
|
||||
<%= if @slideover_open do %>
|
||||
<div
|
||||
class="fixed inset-0 bg-black bg-opacity-50 z-[999] lg:hidden backdrop-blur-sm"
|
||||
phx-click="toggle_slideover"
|
||||
>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<!-- Control Panel Overlay -->
|
||||
<div class="fixed top-0 right-0 h-full w-88 bg-white shadow-2xl z-[1000] backdrop-blur-sm">
|
||||
<div
|
||||
id="slideover-panel"
|
||||
class={[
|
||||
"slideover-panel",
|
||||
if(@slideover_open, do: "slideover-open", else: "slideover-closed")
|
||||
]}
|
||||
phx-hook="ResponsiveSlideoverHook"
|
||||
>
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-center p-6 border-b border-slate-200 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 bg-gradient-to-r from-indigo-600 to-purple-600 text-white">
|
||||
<div class="flex items-center space-x-2">
|
||||
<svg
|
||||
class="w-6 h-6"
|
||||
|
|
@ -596,10 +680,32 @@ defmodule AprsWeb.MapLive.Index do
|
|||
</svg>
|
||||
<h2 class="text-xl font-bold">APRS.me</h2>
|
||||
</div>
|
||||
|
||||
<!-- Close button for mobile -->
|
||||
<button
|
||||
class="lg:hidden text-white hover:text-slate-200 transition-colors"
|
||||
phx-click="toggle_slideover"
|
||||
title="Close controls"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="m18 6-12 12" />
|
||||
<path d="m6 6 12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="p-6 space-y-6 bg-slate-50 h-full overflow-y-auto">
|
||||
<div class="p-6 space-y-6 bg-slate-50 flex-1 overflow-y-auto">
|
||||
<!-- Callsign Search -->
|
||||
<div class="space-y-4">
|
||||
<label class="block text-sm font-semibold text-slate-700 flex items-center space-x-2">
|
||||
|
|
@ -742,6 +848,38 @@ defmodule AprsWeb.MapLive.Index do
|
|||
<span>How many hours of historical packets to load</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Navigation Links (Mobile) -->
|
||||
<div class="lg:hidden pt-4 border-t border-slate-200 space-y-3">
|
||||
<.link
|
||||
navigate={~p"/packets"}
|
||||
class="flex items-center space-x-2 text-sm text-blue-600 hover:text-blue-800 transition-colors"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"
|
||||
/>
|
||||
</svg>
|
||||
<span>View All Packets</span>
|
||||
</.link>
|
||||
<.link
|
||||
navigate={~p"/badpackets"}
|
||||
class="flex items-center space-x-2 text-sm text-blue-600 hover:text-blue-800 transition-colors"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.082 16.5c-.77.833.192 2.5 1.732 2.5z"
|
||||
/>
|
||||
</svg>
|
||||
<span>View Bad Packets</span>
|
||||
</.link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue