feat: add collapsible map controls
This commit is contained in:
@@ -9,6 +9,7 @@ COPY server.js ./
|
||||
COPY index.html ./
|
||||
COPY app.js ./
|
||||
COPY styles.css ./
|
||||
COPY assets ./assets
|
||||
|
||||
ENV HOST=0.0.0.0
|
||||
ENV PORT=4173
|
||||
|
||||
@@ -4,6 +4,10 @@ const API_WMS = "/api/wms";
|
||||
const PARCEL_LAYER_NAME = "xpro:parcelas";
|
||||
|
||||
const els = {
|
||||
appShell: document.querySelector(".app-shell"),
|
||||
sidebar: document.getElementById("sidebar"),
|
||||
sidebarOpen: document.getElementById("sidebar-open"),
|
||||
sidebarClose: document.getElementById("sidebar-close"),
|
||||
status: document.getElementById("status"),
|
||||
layerList: document.getElementById("layer-list"),
|
||||
layerCount: document.getElementById("layer-count"),
|
||||
@@ -77,9 +81,26 @@ let selectedLayers = [];
|
||||
|
||||
els.parcelSearchForm.addEventListener("submit", searchParcela);
|
||||
els.backgroundSelect.addEventListener("change", syncBackground);
|
||||
els.sidebarOpen.addEventListener("click", () => setSidebarCollapsed(false));
|
||||
els.sidebarClose.addEventListener("click", () => setSidebarCollapsed(true));
|
||||
document.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Escape" && !els.appShell.classList.contains("sidebar-is-collapsed")) {
|
||||
setSidebarCollapsed(true);
|
||||
}
|
||||
});
|
||||
|
||||
loadLayers();
|
||||
|
||||
function setSidebarCollapsed(collapsed) {
|
||||
els.appShell.classList.toggle("sidebar-is-collapsed", collapsed);
|
||||
els.sidebar.inert = collapsed;
|
||||
els.sidebarOpen.setAttribute("aria-expanded", String(!collapsed));
|
||||
|
||||
if (collapsed) {
|
||||
els.sidebarOpen.focus();
|
||||
}
|
||||
}
|
||||
|
||||
async function searchParcela(event) {
|
||||
event.preventDefault();
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 66.295753 33.036644">
|
||||
<g transform="translate(-15.909682 -167.46353)">
|
||||
<path fill="#1574c3" d="m 51.662106,186.67656 h 1.857378 v -3.80966 h 0.59653 c 1.423537,0 2.074297,-0.10846 2.630155,-0.4474 0.81345,-0.51518 1.315077,-1.51844 1.315077,-2.67082 0,-1.15239 -0.48807,-2.10142 -1.35575,-2.63016 -0.569415,-0.33894 -1.24729,-0.46095 -2.562367,-0.46095 h -2.481023 z m 1.857378,-5.58569 v -2.65727 h 0.623645 c 0.610087,0 0.894795,0.0271 1.179502,0.0949 0.5423,0.14913 0.86768,0.62365 0.86768,1.24729 0,0.48807 -0.21692,0.86768 -0.610087,1.0846 -0.284708,0.14913 -0.786335,0.23048 -1.51844,0.23048 z m 5.500524,5.58569 h 1.803148 v -4.17571 c -0.04067,-1.11172 0.528742,-1.73536 1.613342,-1.77603 v -1.73536 h -0.135575 c -0.772777,0 -1.152387,0.21692 -1.6269,0.90835 v -0.73211 h -1.654015 z m 7.683272,-7.6871 c -2.182758,0 -3.972348,1.77603 -3.972348,3.93167 0,2.1692 1.78959,3.93168 3.985905,3.93168 2.182758,0 3.999463,-1.76248 3.999463,-3.87745 0,-2.23699 -1.748918,-3.9859 -4.01302,-3.9859 z m 0.01356,1.65401 c 1.206618,0 2.182758,1.01681 2.182758,2.27766 0,1.26085 -0.97614,2.27766 -2.1692,2.27766 -1.220175,0 -2.182758,-1.01681 -2.182758,-2.30477 0,-1.23374 0.97614,-2.25055 2.1692,-2.25055 z" transform="matrix(1.729104 0 0 1.729104 -47.763707 -130.17409)"/>
|
||||
<path fill="#f8a621" d="m 41.157585,186.71507 h 2.209873 l 2.33189,-3.45716 2.399677,3.45716 h 2.155643 l -3.47072,-4.98916 3.47072,-5.02983 h -2.155643 l -2.399677,3.49784 -2.33189,-3.49784 h -2.209873 l 3.443605,5.02983 z" transform="matrix(1.729104 0 0 1.729104 -47.763707 -130.17409)"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
+21
-2
@@ -9,9 +9,28 @@
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-shell">
|
||||
<aside class="sidebar">
|
||||
<button
|
||||
id="sidebar-open"
|
||||
class="sidebar-toggle sidebar-open"
|
||||
type="button"
|
||||
aria-controls="sidebar"
|
||||
aria-expanded="true"
|
||||
>
|
||||
<span aria-hidden="true">☰</span>
|
||||
<span class="visually-hidden">Open map controls</span>
|
||||
</button>
|
||||
|
||||
<aside id="sidebar" class="sidebar" aria-label="Map controls">
|
||||
<button id="sidebar-close" class="sidebar-toggle sidebar-close" type="button">
|
||||
<span aria-hidden="true">×</span>
|
||||
<span class="visually-hidden">Close map controls</span>
|
||||
</button>
|
||||
<div class="brand">
|
||||
<h1>XPro Map Visualizer</h1>
|
||||
<h1>
|
||||
<img class="brand-logo" src="./assets/brand/xpro-logo-simple.svg" alt="XPro" />
|
||||
<span class="visually-hidden">XPro</span>
|
||||
</h1>
|
||||
<p class="brand-subtitle">Map Visualizer</p>
|
||||
</div>
|
||||
|
||||
<section class="panel">
|
||||
|
||||
+122
-8
@@ -1,7 +1,7 @@
|
||||
:root {
|
||||
color-scheme: light;
|
||||
--bg: #f3efe3;
|
||||
--panel: rgba(253, 249, 240, 0.92);
|
||||
--panel: rgba(253, 249, 240, 0.75);
|
||||
--panel-border: rgba(92, 73, 49, 0.18);
|
||||
--ink: #1f2a2e;
|
||||
--muted: #5f6b6f;
|
||||
@@ -30,10 +30,8 @@ body {
|
||||
}
|
||||
|
||||
.app-shell {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(320px, 380px) 1fr;
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
gap: 24px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
@@ -43,9 +41,70 @@ body {
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
z-index: 10;
|
||||
top: 24px;
|
||||
bottom: 24px;
|
||||
left: 24px;
|
||||
width: min(266px, calc(100vw - 48px));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
overflow-y: auto;
|
||||
padding: 8px;
|
||||
transition: transform 240ms ease;
|
||||
backdrop-filter: blur(3px);
|
||||
}
|
||||
|
||||
.sidebar-toggle {
|
||||
position: fixed;
|
||||
z-index: 11;
|
||||
display: grid;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
padding: 0;
|
||||
place-items: center;
|
||||
border: 1px solid var(--panel-border);
|
||||
border-radius: 14px;
|
||||
background: var(--panel);
|
||||
color: var(--ink);
|
||||
font-size: 1.65rem;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.sidebar-toggle:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.sidebar-open {
|
||||
top: 32px;
|
||||
left: 32px;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
transition: opacity 180ms ease;
|
||||
}
|
||||
|
||||
.sidebar-close {
|
||||
position: absolute;
|
||||
top: 18px;
|
||||
right: 18px;
|
||||
}
|
||||
|
||||
.sidebar-close span[aria-hidden="true"] {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.app-shell.sidebar-is-collapsed .sidebar {
|
||||
transform: translateX(calc(-100% - 24px));
|
||||
}
|
||||
|
||||
.app-shell.sidebar-is-collapsed .sidebar-open {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.brand,
|
||||
@@ -61,6 +120,32 @@ body {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.brand-logo {
|
||||
display: block;
|
||||
width: min(100%, 178px);
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.brand-subtitle {
|
||||
margin-top: 8px;
|
||||
color: var(--muted);
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.visually-hidden {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
p {
|
||||
@@ -210,11 +295,12 @@ button:disabled {
|
||||
}
|
||||
|
||||
.map-wrap {
|
||||
position: relative;
|
||||
position: fixed;
|
||||
z-index: 0;
|
||||
inset: 24px;
|
||||
overflow: hidden;
|
||||
border-radius: 32px;
|
||||
box-shadow: var(--shadow);
|
||||
min-height: 70vh;
|
||||
}
|
||||
|
||||
#map {
|
||||
@@ -222,12 +308,40 @@ button:disabled {
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.map-wrap .ol-zoom {
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
left: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 920px) {
|
||||
.app-shell {
|
||||
grid-template-columns: 1fr;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.map-wrap {
|
||||
min-height: 62vh;
|
||||
inset: 16px;
|
||||
border-radius: 24px;
|
||||
}
|
||||
|
||||
.map-wrap .ol-zoom {
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
top: 16px;
|
||||
bottom: 16px;
|
||||
left: 16px;
|
||||
width: min(266px, calc(100vw - 32px));
|
||||
}
|
||||
|
||||
.sidebar-open {
|
||||
top: 24px;
|
||||
left: 24px;
|
||||
}
|
||||
|
||||
.app-shell.sidebar-is-collapsed .sidebar {
|
||||
transform: translateX(calc(-100% - 16px));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user