Upload/delete/move/create-folder now require logging in via Gitea OAuth2 (only MAPTOOL_ALLOWED_GITEA_USER is granted write access); reading stays public. Deploy secrets (session secret, OAuth2 client credentials, allowed user) are written to .env from Gitea Actions repo secrets on every deploy instead of living on the server by hand. Also switch the container's bind address to "[::]" (dual-stack) so the app is reachable over IPv6, not just IPv4.
433 lines
6.8 KiB
CSS
433 lines
6.8 KiB
CSS
html, body {
|
|
height: 100%;
|
|
margin: 0;
|
|
font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
}
|
|
|
|
#app {
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.topbar {
|
|
flex: 0 0 auto;
|
|
display: flex;
|
|
align-items: center;
|
|
height: 48px;
|
|
padding: 0 16px;
|
|
background: #fff;
|
|
border-bottom: 1px solid #ddd;
|
|
}
|
|
|
|
.topbar__brand {
|
|
font-size: 15px;
|
|
font-weight: 700;
|
|
color: #333;
|
|
}
|
|
|
|
.topbar__auth {
|
|
margin-left: auto;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
font-size: 13px;
|
|
color: #555;
|
|
}
|
|
|
|
.app-body {
|
|
flex: 1;
|
|
display: flex;
|
|
min-height: 0;
|
|
}
|
|
|
|
.sidebar {
|
|
flex: 0 0 52px;
|
|
position: relative;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 4px;
|
|
padding: 8px 0;
|
|
background: #fff;
|
|
border-right: 1px solid #ddd;
|
|
/* No overflow-y:auto here: setting only one axis to a clipping value
|
|
forces the browser to compute the other axis as "auto" too (CSS
|
|
Overflow spec), which would clip the flyouts — they intentionally
|
|
poke out past the sidebar's own width via left:44px. */
|
|
}
|
|
|
|
.sidebar-item {
|
|
position: relative;
|
|
}
|
|
|
|
.sidebar-icon {
|
|
width: 36px;
|
|
height: 36px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border: none;
|
|
border-radius: 6px;
|
|
background: none;
|
|
color: #444;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.sidebar-icon svg {
|
|
width: 20px;
|
|
height: 20px;
|
|
}
|
|
|
|
.sidebar-icon:hover {
|
|
background: #f0f0f0;
|
|
}
|
|
|
|
.sidebar-icon.active {
|
|
background: #dbe9ff;
|
|
color: #1a5fb4;
|
|
}
|
|
|
|
.sidebar-flyout {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 44px;
|
|
z-index: 20;
|
|
min-width: max-content;
|
|
background: #fff;
|
|
border: 1px solid #ddd;
|
|
border-radius: 6px;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
padding: 6px;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.sidebar-flyout[hidden] {
|
|
display: none;
|
|
}
|
|
|
|
.sidebar-flyout__option {
|
|
display: block;
|
|
width: 100%;
|
|
padding: 6px 10px;
|
|
border: none;
|
|
border-radius: 4px;
|
|
background: none;
|
|
font: inherit;
|
|
font-size: 13px;
|
|
text-align: left;
|
|
white-space: nowrap;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.sidebar-flyout__option:hover {
|
|
background: #f0f0f0;
|
|
}
|
|
|
|
.sidebar-flyout__option.active {
|
|
background: #dbe9ff;
|
|
font-weight: 600;
|
|
}
|
|
|
|
#map {
|
|
flex: 1;
|
|
min-width: 0;
|
|
position: relative;
|
|
}
|
|
|
|
.radar-control__playback {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
|
|
.radar-control__play {
|
|
border: none;
|
|
background: none;
|
|
padding: 2px 4px;
|
|
font-size: 13px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.radar-control__scrub {
|
|
width: 110px;
|
|
}
|
|
|
|
.radar-control__time {
|
|
font-variant-numeric: tabular-nums;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.basemap-panel {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 52px;
|
|
width: 260px;
|
|
max-width: 90vw;
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: #fff;
|
|
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.15);
|
|
font-size: 13px;
|
|
z-index: 10;
|
|
}
|
|
|
|
.basemap-panel[hidden] {
|
|
display: none;
|
|
}
|
|
|
|
.basemap-panel__header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 10px 12px;
|
|
border-bottom: 1px solid #eee;
|
|
}
|
|
|
|
.basemap-panel__close {
|
|
border: none;
|
|
background: none;
|
|
font-size: 18px;
|
|
line-height: 1;
|
|
padding: 0 4px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.basemap-panel__list {
|
|
padding: 6px;
|
|
}
|
|
|
|
.basemap-panel__option {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
width: 100%;
|
|
padding: 10px;
|
|
border: none;
|
|
border-radius: 4px;
|
|
background: none;
|
|
font: inherit;
|
|
font-size: 14px;
|
|
text-align: left;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.basemap-panel__option:hover {
|
|
background: #f0f0f0;
|
|
}
|
|
|
|
.basemap-panel__option.active {
|
|
background: #dbe9ff;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.basemap-panel__check {
|
|
color: #1a5fb4;
|
|
visibility: hidden;
|
|
}
|
|
|
|
.basemap-panel__option.active .basemap-panel__check {
|
|
visibility: visible;
|
|
}
|
|
|
|
.gpx-panel {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 52px;
|
|
width: 300px;
|
|
max-width: 90vw;
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: #fff;
|
|
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.15);
|
|
font-size: 13px;
|
|
z-index: 10;
|
|
}
|
|
|
|
.gpx-panel[hidden] {
|
|
display: none;
|
|
}
|
|
|
|
.gpx-panel__header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 10px 12px;
|
|
border-bottom: 1px solid #eee;
|
|
}
|
|
|
|
.gpx-panel__actions {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 2px;
|
|
}
|
|
|
|
.gpx-panel__action {
|
|
width: 30px;
|
|
height: 30px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border: none;
|
|
border-radius: 6px;
|
|
background: none;
|
|
color: #444;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.gpx-panel__action svg {
|
|
width: 18px;
|
|
height: 18px;
|
|
}
|
|
|
|
.gpx-panel__action:hover {
|
|
background: #f0f0f0;
|
|
}
|
|
|
|
.gpx-panel__close {
|
|
border: none;
|
|
background: none;
|
|
font-size: 18px;
|
|
line-height: 1;
|
|
padding: 0 4px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.gpx-upload__status {
|
|
margin: 0 12px 10px;
|
|
color: #666;
|
|
}
|
|
|
|
.gpx-tree {
|
|
/* flex:1 (not just overflow-y) so the tree fills the rest of the panel
|
|
even when short/empty — otherwise the blank space below it wouldn't
|
|
be part of the drop target and dropping a file there would silently
|
|
do nothing. */
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 8px 12px;
|
|
}
|
|
|
|
.gpx-tree ul {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding-left: 16px;
|
|
}
|
|
|
|
.gpx-tree > ul {
|
|
padding-left: 0;
|
|
}
|
|
|
|
.gpx-tree summary {
|
|
cursor: pointer;
|
|
font-weight: 600;
|
|
border-radius: 3px;
|
|
}
|
|
|
|
.gpx-tree__folder-row {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 4px;
|
|
width: calc(100% - 18px);
|
|
}
|
|
|
|
.gpx-tree summary.gpx-drop-hover {
|
|
background: #eef5fd;
|
|
outline: 1px dashed #4a90d9;
|
|
}
|
|
|
|
.gpx-tree__file {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
padding: 2px 0;
|
|
cursor: grab;
|
|
}
|
|
|
|
.gpx-tree__file label {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
flex: 1;
|
|
min-width: 0;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.gpx-tree__file-name {
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.gpx-tree__kind-icon {
|
|
flex-shrink: 0;
|
|
display: flex;
|
|
color: #999;
|
|
}
|
|
|
|
.gpx-tree__kind-icon svg {
|
|
width: 14px;
|
|
height: 14px;
|
|
}
|
|
|
|
.gpx-tree__delete {
|
|
flex-shrink: 0;
|
|
border: none;
|
|
background: none;
|
|
color: #999;
|
|
font-size: 14px;
|
|
line-height: 1;
|
|
padding: 0 4px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.gpx-tree__delete:hover {
|
|
color: #e63946;
|
|
}
|
|
|
|
.gpx-tree__empty {
|
|
color: #888;
|
|
font-style: italic;
|
|
}
|
|
|
|
.gpx-tree.gpx-drop-hover {
|
|
outline: 1px dashed #4a90d9;
|
|
outline-offset: -2px;
|
|
}
|
|
|
|
#map.gpx-drop-hover::after {
|
|
content: "";
|
|
position: absolute;
|
|
inset: 0;
|
|
outline: 3px dashed #4a90d9;
|
|
outline-offset: -3px;
|
|
background: rgba(74, 144, 217, 0.08);
|
|
pointer-events: none;
|
|
}
|
|
|
|
.maplibregl-popup-content p {
|
|
margin: 4px 0 0;
|
|
}
|
|
|
|
.gpx-popup__time {
|
|
color: #888;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.gnss-control__status {
|
|
color: #888;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.gnss-marker {
|
|
width: 14px;
|
|
height: 14px;
|
|
border: 2px solid #fff;
|
|
border-radius: 50%;
|
|
background: #2a9d8f;
|
|
box-shadow: 0 0 0 2px rgba(42, 157, 143, 0.4);
|
|
}
|