dawarich/app/views/maps_v2/_settings_panel.html.erb
2025-11-26 21:07:12 +01:00

726 lines
24 KiB
Text

<div class="map-control-panel" data-maps-v2-target="settingsPanel" data-controller="map-panel">
<!-- Vertical Icon Tabs (Left Side) -->
<div class="panel-tabs">
<button class="tab-btn active"
data-action="click->map-panel#switchTab"
data-tab="search"
data-map-panel-target="tabButton"
title="Search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tab-icon">
<circle cx="11" cy="11" r="8"></circle>
<path d="m21 21-4.35-4.35"></path>
</svg>
</button>
<button class="tab-btn"
data-action="click->map-panel#switchTab"
data-tab="layers"
data-map-panel-target="tabButton"
title="Map Layers">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tab-icon">
<polygon points="12 2 2 7 12 12 22 7 12 2"></polygon>
<polyline points="2 17 12 22 22 17"></polyline>
<polyline points="2 12 12 17 22 12"></polyline>
</svg>
</button>
<button class="tab-btn"
data-action="click->map-panel#switchTab"
data-tab="settings"
data-map-panel-target="tabButton"
title="Settings">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tab-icon">
<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"></path>
<circle cx="12" cy="12" r="3"></circle>
</svg>
</button>
</div>
<!-- Panel Content -->
<div class="panel-content">
<!-- Panel Header -->
<div class="panel-header">
<h3 class="panel-title" data-map-panel-target="title">Search</h3>
<button class="btn btn-ghost btn-sm btn-circle"
data-action="click->maps-v2#toggleSettings"
title="Close panel">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-5 h-5">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<!-- Panel Body -->
<div class="panel-body">
<!-- Search Tab -->
<div class="tab-content active" data-tab-content="search" data-map-panel-target="tabContent">
<div class="form-control w-full">
<label class="label">
<span class="label-text">Search for a place</span>
</label>
<div class="relative">
<input type="text"
placeholder="Enter name of a place"
class="input input-bordered w-full"
data-maps-v2-target="searchInput"
autocomplete="off" />
<!-- Search Results -->
<div class="absolute z-50 w-full mt-1 bg-base-100 rounded-lg shadow-lg border border-base-300 hidden max-h-96 overflow-y-auto"
data-maps-v2-target="searchResults">
<!-- Results will be populated by SearchManager -->
</div>
</div>
<p class="text-xs text-base-content/60 mt-2">
Search for a location to navigate on the map
</p>
</div>
</div>
<!-- Layers Tab -->
<div class="tab-content" data-tab-content="layers" data-map-panel-target="tabContent">
<div class="space-y-4">
<!-- Points Layer -->
<div class="form-control">
<label class="label cursor-pointer justify-start gap-3">
<input type="checkbox"
class="toggle toggle-primary"
data-maps-v2-target="pointsToggle"
data-action="change->maps-v2#togglePoints" />
<span class="label-text font-medium">Points</span>
</label>
<p class="text-sm text-base-content/60 ml-14">Show individual location points</p>
</div>
<div class="divider my-2"></div>
<!-- Routes Layer -->
<div class="form-control">
<label class="label cursor-pointer justify-start gap-3">
<input type="checkbox"
class="toggle toggle-primary"
data-maps-v2-target="routesToggle"
data-action="change->maps-v2#toggleRoutes" />
<span class="label-text font-medium">Routes</span>
</label>
<p class="text-sm text-base-content/60 ml-14">Show connected route lines</p>
</div>
<div class="divider my-2"></div>
<!-- Heatmap Layer -->
<div class="form-control">
<label class="label cursor-pointer justify-start gap-3">
<input type="checkbox"
class="toggle toggle-primary"
data-maps-v2-target="heatmapToggle"
data-action="change->maps-v2#toggleHeatmap" />
<span class="label-text font-medium">Heatmap</span>
</label>
<p class="text-sm text-base-content/60 ml-14">Show density heatmap</p>
</div>
<div class="divider my-2"></div>
<!-- Visits Layer -->
<div class="form-control">
<label class="label cursor-pointer justify-start gap-3">
<input type="checkbox"
class="toggle toggle-primary"
data-maps-v2-target="visitsToggle"
data-action="change->maps-v2#toggleVisits" />
<span class="label-text font-medium">Visits</span>
</label>
<p class="text-sm text-base-content/60 ml-14">Show detected area visits</p>
</div>
<!-- Visits Search (conditionally shown) -->
<div class="ml-14 space-y-2" data-maps-v2-target="visitsSearch" style="display: none;">
<input type="text"
id="visits-search"
placeholder="Filter by name..."
class="input input-sm input-bordered w-full"
data-action="input->maps-v2#searchVisits" />
<select class="select select-bordered w-full"
data-action="change->maps-v2#filterVisits">
<option value="all">All Visits</option>
<option value="confirmed">Confirmed Only</option>
<option value="suggested">Suggested Only</option>
</select>
</div>
<div class="divider my-2"></div>
<!-- Photos Layer -->
<div class="form-control">
<label class="label cursor-pointer justify-start gap-3">
<input type="checkbox"
class="toggle toggle-primary"
data-maps-v2-target="photosToggle"
data-action="change->maps-v2#togglePhotos" />
<span class="label-text font-medium">Photos</span>
</label>
<p class="text-sm text-base-content/60 ml-14">Show geotagged photos</p>
</div>
<div class="divider my-2"></div>
<!-- Areas Layer -->
<div class="form-control">
<label class="label cursor-pointer justify-start gap-3">
<input type="checkbox"
class="toggle toggle-primary"
data-maps-v2-target="areasToggle"
data-action="change->maps-v2#toggleAreas" />
<span class="label-text font-medium">Areas</span>
</label>
<p class="text-sm text-base-content/60 ml-14">Show defined areas</p>
</div>
<div class="divider my-2"></div>
<!-- Tracks Layer -->
<%# <div class="form-control">
<label class="label cursor-pointer justify-start gap-3">
<input type="checkbox"
class="toggle toggle-primary"
data-maps-v2-target="tracksToggle"
data-action="change->maps-v2#toggleTracks" />
<span class="label-text font-medium">Tracks</span>
</label>
<p class="text-sm text-base-content/60 ml-14">Show saved tracks</p>
</div> %>
<%# <div class="divider my-2"></div> %>
<!-- Fog of War Layer -->
<div class="form-control">
<label class="label cursor-pointer justify-start gap-3">
<input type="checkbox"
class="toggle toggle-primary"
data-maps-v2-target="fogToggle"
data-action="change->maps-v2#toggleFog" />
<span class="label-text font-medium">Fog of War</span>
</label>
<p class="text-sm text-base-content/60 ml-14">Show explored areas</p>
</div>
<div class="divider my-2"></div>
<!-- Scratch Map Layer -->
<div class="form-control">
<label class="label cursor-pointer justify-start gap-3">
<input type="checkbox"
class="toggle toggle-primary"
data-maps-v2-target="scratchToggle"
data-action="change->maps-v2#toggleScratch" />
<span class="label-text font-medium">Scratch Map</span>
</label>
<p class="text-sm text-base-content/60 ml-14">Show scratched countries</p>
</div>
</div>
</div>
<!-- Settings Tab -->
<div class="tab-content" data-tab-content="settings" data-map-panel-target="tabContent">
<form data-action="submit->maps-v2#updateAdvancedSettings" class="space-y-4">
<!-- Map Style -->
<div class="form-control w-full">
<label class="label">
<span class="label-text font-medium">Map Style</span>
</label>
<select class="select select-bordered w-full"
name="mapStyle"
data-action="change->maps-v2#updateMapStyle">
<option value="light" selected>Light</option>
<option value="dark">Dark</option>
<option value="white">White</option>
<option value="black">Black</option>
<option value="grayscale">Grayscale</option>
</select>
</div>
<div class="divider my-2"></div>
<!-- Route Opacity -->
<div class="form-control w-full">
<label class="label">
<span class="label-text font-medium">Route Opacity</span>
<span class="label-text-alt">%</span>
</label>
<input type="range"
name="routeOpacity"
min="10"
max="100"
step="10"
value="100"
class="range range-primary range-sm"
data-maps-v2-target="routeOpacityRange"
data-action="input->maps-v2#updateRouteOpacity" />
<div class="w-full flex justify-between text-xs px-2 mt-1">
<span>10%</span>
<span>50%</span>
<span>100%</span>
</div>
</div>
<div class="divider my-2"></div>
<!-- Fog of War Settings -->
<div class="form-control w-full">
<label class="label">
<span class="label-text font-medium">Fog of War Radius</span>
<span class="label-text-alt" data-maps-v2-target="fogRadiusValue">1000m</span>
</label>
<input type="range"
name="fogOfWarRadius"
min="5"
max="2000"
step="5"
value="1000"
class="range range-primary range-sm"
data-action="input->maps-v2#updateFogRadiusDisplay" />
<div class="w-full flex justify-between text-xs px-2 mt-1">
<span>5m</span>
<span>1000m</span>
<span>2000m</span>
</div>
<p class="text-xs text-base-content/60 mt-1">Clear radius around visited points</p>
</div>
<div class="form-control w-full">
<label class="label">
<span class="label-text font-medium">Fog of War Threshold</span>
<span class="label-text-alt" data-maps-v2-target="fogThresholdValue">1</span>
</label>
<input type="range"
name="fogOfWarThreshold"
min="1"
max="10"
step="1"
value="1"
class="range range-primary range-sm"
data-action="input->maps-v2#updateFogThresholdDisplay" />
<div class="w-full flex justify-between text-xs px-2 mt-1">
<span>1</span>
<span>5</span>
<span>10</span>
</div>
<p class="text-xs text-base-content/60 mt-1">Minimum points to clear fog</p>
</div>
<div class="divider my-2"></div>
<!-- Route Generation Settings -->
<div class="form-control w-full">
<label class="label">
<span class="label-text font-medium">Meters Between Routes</span>
<span class="label-text-alt" data-maps-v2-target="metersBetweenValue">500m</span>
</label>
<input type="range"
name="metersBetweenRoutes"
min="100"
max="5000"
step="100"
value="500"
class="range range-primary range-sm"
data-action="input->maps-v2#updateMetersBetweenDisplay" />
<div class="w-full flex justify-between text-xs px-2 mt-1">
<span>100m</span>
<span>2500m</span>
<span>5000m</span>
</div>
<p class="text-xs text-base-content/60 mt-1">Distance threshold for route splitting</p>
</div>
<div class="form-control w-full">
<label class="label">
<span class="label-text font-medium">Minutes Between Routes</span>
<span class="label-text-alt" data-maps-v2-target="minutesBetweenValue">60min</span>
</label>
<input type="range"
name="minutesBetweenRoutes"
min="1"
max="180"
step="1"
value="60"
class="range range-primary range-sm"
data-action="input->maps-v2#updateMinutesBetweenDisplay" />
<div class="w-full flex justify-between text-xs px-2 mt-1">
<span>1min</span>
<span>90min</span>
<span>180min</span>
</div>
<p class="text-xs text-base-content/60 mt-1">Time threshold for route splitting</p>
</div>
<div class="divider my-2"></div>
<!-- Points Rendering Mode -->
<div class="form-control w-full">
<label class="label">
<span class="label-text font-medium">Points Rendering Mode</span>
</label>
<div class="flex flex-col gap-2">
<label class="label cursor-pointer justify-start gap-3 py-1">
<input type="radio"
name="pointsRenderingMode"
value="raw"
class="radio radio-primary radio-sm"
checked />
<span class="label-text">Raw (all points)</span>
</label>
<label class="label cursor-pointer justify-start gap-3 py-1">
<input type="radio"
name="pointsRenderingMode"
value="simplified"
class="radio radio-primary radio-sm" />
<span class="label-text">Simplified (reduced points)</span>
</label>
</div>
</div>
<div class="divider my-2"></div>
<!-- Speed-Colored Routes -->
<div class="form-control">
<label class="label cursor-pointer justify-start gap-3">
<input type="checkbox"
name="speedColoredRoutes"
class="toggle toggle-primary" />
<span class="label-text font-medium">Speed-Colored Routes</span>
</label>
<p class="text-sm text-base-content/60 mt-1">Color routes by speed</p>
</div>
<div class="divider my-2"></div>
<!-- Live Mode -->
<div class="form-control">
<label class="label cursor-pointer justify-start gap-3">
<input type="checkbox"
class="toggle toggle-primary"
data-action="change->maps-v2-realtime#toggleLiveMode"
data-maps-v2-realtime-target="liveModeToggle" />
<span class="label-text font-medium">Live Mode</span>
</label>
<p class="text-sm text-base-content/60 mt-1">Show new points in real-time</p>
</div>
<div class="divider my-2"></div>
<!-- Update Button -->
<button type="submit" class="btn btn-primary btn-block">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-5 h-5">
<path d="M21 12a9 9 0 1 1-6.219-8.56"></path>
<polyline points="12 2 12 12 14.5 14.5"></polyline>
</svg>
Apply Settings
</button>
<!-- Reset Settings -->
<button type="button"
class="btn btn-outline btn-block"
data-action="click->maps-v2#resetSettings">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-5 h-5">
<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"></path>
<path d="M21 3v5h-5"></path>
<path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"></path>
<path d="M3 21v-5h5"></path>
</svg>
Reset to Defaults
</button>
</form>
</div>
</div>
</div>
</div>
<style>
.map-control-panel {
position: absolute;
top: 0;
right: -480px; /* Hidden by default */
width: 480px;
height: 100%;
background: oklch(var(--b1));
box-shadow: -4px 0 24px rgba(0, 0, 0, 0.15);
z-index: 9999;
transition: right 0.3s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
overflow: hidden;
}
.map-control-panel.open {
right: 0;
}
/* Vertical Tab Bar */
.panel-tabs {
width: 64px;
background: oklch(var(--b2));
border-right: 1px solid oklch(var(--bc) / 0.1);
display: flex;
flex-direction: column;
align-items: center;
padding: 16px 0;
gap: 8px;
flex-shrink: 0;
}
.tab-btn {
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8px;
border: none;
background: transparent;
cursor: pointer;
transition: all 0.2s;
position: relative;
color: oklch(var(--bc) / 0.6);
}
.tab-btn:hover {
background: oklch(var(--b3));
color: oklch(var(--bc));
}
.tab-btn.active {
background: oklch(var(--p));
color: oklch(var(--pc));
}
.tab-btn.active::after {
content: '';
position: absolute;
right: -1px;
top: 50%;
transform: translateY(-50%);
width: 3px;
height: 24px;
background: oklch(var(--p));
border-radius: 2px 0 0 2px;
}
.tab-icon {
width: 24px;
height: 24px;
}
/* Panel Content */
.panel-content {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
min-width: 0;
}
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 24px;
border-bottom: 1px solid oklch(var(--bc) / 0.1);
background: oklch(var(--b1));
flex-shrink: 0;
}
.panel-title {
font-size: 1.25rem;
font-weight: 600;
margin: 0;
color: oklch(var(--bc));
}
.panel-body {
flex: 1;
overflow-y: auto;
padding: 24px;
}
/* Tab Content */
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
/* Custom Scrollbar */
.panel-body::-webkit-scrollbar {
width: 8px;
}
.panel-body::-webkit-scrollbar-track {
background: transparent;
}
.panel-body::-webkit-scrollbar-thumb {
background: oklch(var(--bc) / 0.2);
border-radius: 4px;
}
.panel-body::-webkit-scrollbar-thumb:hover {
background: oklch(var(--bc) / 0.3);
}
/* Toggle Focus State - Remove all focus indicators */
.toggle:focus,
.toggle:focus-visible,
.toggle:focus-within {
outline: none !important;
box-shadow: none !important;
border-color: inherit !important;
}
/* Override DaisyUI toggle focus styles */
.toggle:focus-visible:checked,
.toggle:checked:focus,
.toggle:checked:focus-visible {
outline: none !important;
box-shadow: none !important;
}
/* Ensure no outline on the toggle container */
.form-control .toggle:focus {
outline: none !important;
}
/* Prevent indeterminate visual state on toggles */
.toggle:indeterminate {
opacity: 1;
}
/* Ensure smooth toggle transitions without intermediate states */
.toggle {
transition: background-color 0.2s ease, border-color 0.2s ease;
}
.toggle:checked {
transition: background-color 0.2s ease, border-color 0.2s ease;
}
/* Remove any active/pressed state that might cause intermediate appearance */
.toggle:active,
.toggle:active:focus {
outline: none !important;
box-shadow: none !important;
}
/* Responsive Breakpoints */
/* Large tablets and smaller desktops (1024px - 1280px) */
@media (max-width: 1280px) {
.map-control-panel {
width: 420px;
right: -420px;
}
}
/* Tablets (768px - 1024px) */
@media (max-width: 1024px) {
.map-control-panel {
width: 380px;
right: -380px;
}
.panel-body {
padding: 20px;
}
}
/* Small tablets and large phones (640px - 768px) */
@media (max-width: 768px) {
.map-control-panel {
width: 90%;
right: -90%;
max-width: 400px;
}
.panel-header {
padding: 16px 20px;
}
.panel-title {
font-size: 1.125rem;
}
.panel-body {
padding: 16px 20px;
}
}
/* Mobile phones (< 640px) */
@media (max-width: 640px) {
.map-control-panel {
width: 100%;
right: -100%;
max-width: none;
}
.panel-tabs {
width: 56px;
padding: 12px 0;
gap: 6px;
}
.tab-btn {
width: 44px;
height: 44px;
}
.tab-icon {
width: 20px;
height: 20px;
}
.panel-header {
padding: 14px 16px;
}
.panel-title {
font-size: 1rem;
}
.panel-body {
padding: 16px;
}
/* Reduce spacing on mobile */
.space-y-4 > * + * {
margin-top: 0.75rem;
}
.space-y-6 > * + * {
margin-top: 1rem;
}
}
/* Very small phones (< 375px) */
@media (max-width: 375px) {
.panel-tabs {
width: 52px;
padding: 10px 0;
}
.tab-btn {
width: 40px;
height: 40px;
}
.panel-header {
padding: 12px;
}
.panel-body {
padding: 12px;
}
}
</style>