Compare commits
13 Commits
5baebbb846
...
v1.15.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
e3147caa90
|
|||
|
7c11fefdb7
|
|||
|
9af6636971
|
|||
|
8ae7fd1fd8
|
|||
|
cb3fbade39
|
|||
|
6dfd9765b4
|
|||
|
45eb8f087d
|
|||
|
3630fae133
|
|||
|
1116161e93
|
|||
|
88eb0ac0c1
|
|||
|
6da004e199
|
|||
|
8877a9e1c8
|
|||
|
03d6a86569
|
@@ -14,7 +14,7 @@ import Icon from '#components/icon';
|
||||
</div>
|
||||
|
||||
<div class="sidebar-content">
|
||||
<section class="settings-section">
|
||||
<section class="about-section">
|
||||
<p>
|
||||
<strong>Marco</strong>
|
||||
(as in
|
||||
@@ -41,31 +41,78 @@ import Icon from '#components/icon';
|
||||
<span>Open Source</span>
|
||||
</summary>
|
||||
<div class="details-content">
|
||||
<ul class="link-list">
|
||||
<li>
|
||||
<a
|
||||
href="https://gitea.kosmos.org/raucao/marco"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
Source Code
|
||||
</a>
|
||||
(<a
|
||||
href="https://en.wikipedia.org/wiki/GNU_Affero_General_Public_License"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>AGPL</a>)
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://openstreetmap.org/copyright"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
Map Data © OpenStreetMap
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Source</th>
|
||||
<th>License</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a
|
||||
href="https://gitea.kosmos.org/raucao/marco"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
Marco App
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a
|
||||
href="https://en.wikipedia.org/wiki/GNU_Affero_General_Public_License"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<abbr title="GNU Affero General Public License">AGPL</abbr>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a
|
||||
href="https://openstreetmap.org/copyright"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
Map Data
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a
|
||||
href="https://opendatacommons.org/licenses/odbl/"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<abbr
|
||||
title="Open Data Commons Open Database License"
|
||||
>ODbL</abbr>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a
|
||||
href="https://github.com/feathericons/feather"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
Feather Icons
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a
|
||||
href="https://en.wikipedia.org/wiki/MIT_License"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<abbr title="MIT License">MIT</abbr>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</details>
|
||||
<details>
|
||||
@@ -74,6 +121,30 @@ import Icon from '#components/icon';
|
||||
<span>Contribute</span>
|
||||
</summary>
|
||||
<div class="details-content">
|
||||
<p>
|
||||
<strong>Most impactful:</strong>
|
||||
Add and improve data for points of interest in
|
||||
<a
|
||||
href="https://www.openstreetmap.org"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>OpenStreetMap</a>.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Most appreciated:</strong>
|
||||
Use this app as much as you can and
|
||||
<a
|
||||
href="https://community.remotestorage.io/t/marco-an-unhosted-maps-app/941"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>submit feedback</a>
|
||||
about your experience, problems, feature wishes, etc.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Most supportive:</strong>
|
||||
Tell others about this app, on social media, in blog posts,
|
||||
educational videos, etc.
|
||||
</p>
|
||||
</div>
|
||||
</details>
|
||||
</section>
|
||||
|
||||
@@ -499,10 +499,9 @@ export default class MapComponent extends Component {
|
||||
}
|
||||
|
||||
if (options.preventZoom) {
|
||||
// If we are preventing zoom (e.g. user clicked a bookmark), we still need to center
|
||||
// but without changing the zoom level.
|
||||
// We use animateToSmartCenter without a second argument (zoom=null).
|
||||
this.animateToSmartCenter(coords);
|
||||
// If we are preventing zoom (e.g. user clicked a bookmark), we rely on visibility check.
|
||||
// This avoids unnecessary panning if the place is already visible.
|
||||
this.handlePinVisibility(coords, { maintainZoom: true });
|
||||
} else if (selected.bbox) {
|
||||
this.zoomToBbox(selected.bbox);
|
||||
} else {
|
||||
@@ -547,7 +546,10 @@ export default class MapComponent extends Component {
|
||||
}
|
||||
// Desktop: Sidebar covers left side (approx 400px)
|
||||
else if (this.args.isSidebarOpen) {
|
||||
const sidebarWidth = 400;
|
||||
const sidebarWidthVar = getComputedStyle(document.documentElement)
|
||||
.getPropertyValue('--sidebar-width')
|
||||
.trim();
|
||||
const sidebarWidth = parseInt(sidebarWidthVar, 10) || 360;
|
||||
const visibleWidth = size[0] - sidebarWidth;
|
||||
|
||||
// Left padding: Sidebar + 15% of visible width
|
||||
@@ -566,14 +568,15 @@ export default class MapComponent extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
handlePinVisibility(coords) {
|
||||
handlePinVisibility(coords, options = {}) {
|
||||
if (!this.mapInstance) return;
|
||||
|
||||
const view = this.mapInstance.getView();
|
||||
const currentZoom = view.getZoom();
|
||||
|
||||
// If too far out (e.g. world view), zoom in to neighborhood level (16)
|
||||
if (currentZoom < 16) {
|
||||
// UNLESS we want to maintain the current zoom
|
||||
if (!options.maintainZoom && currentZoom < 16) {
|
||||
this.animateToSmartCenter(coords, 16);
|
||||
return;
|
||||
}
|
||||
@@ -590,8 +593,12 @@ export default class MapComponent extends Component {
|
||||
pixel[1] > size[1];
|
||||
|
||||
if (isOffScreen) {
|
||||
this.animateToSmartCenter(coords);
|
||||
// If off-screen, center it smartly (considering sidebar/bottom sheet)
|
||||
// Pass maintainZoom to prevent zoom reset if desired
|
||||
const zoom = options.maintainZoom ? null : 16;
|
||||
this.animateToSmartCenter(coords, zoom);
|
||||
} else {
|
||||
// If on-screen, only pan if obscured by UI
|
||||
this.panIfObscured(coords);
|
||||
}
|
||||
}
|
||||
@@ -627,6 +634,28 @@ export default class MapComponent extends Component {
|
||||
// To move the camera South (Lower Y), we subtract.
|
||||
targetCenter = [coords[0], coords[1] - offsetMapUnits];
|
||||
}
|
||||
// Desktop: Check if sidebar is open
|
||||
else if (this.args.isSidebarOpen) {
|
||||
const sidebarWidthVar = getComputedStyle(document.documentElement)
|
||||
.getPropertyValue('--sidebar-width')
|
||||
.trim();
|
||||
const sidebarWidth = parseInt(sidebarWidthVar, 10) || 360;
|
||||
|
||||
// We want the pin to be in the center of the remaining space.
|
||||
// Remaining space starts at x = sidebarWidth.
|
||||
// Center of remaining space = sidebarWidth + (totalWidth - sidebarWidth) / 2
|
||||
// = sidebarWidth/2 + totalWidth/2
|
||||
// Map Center is totalWidth/2
|
||||
// Offset = sidebarWidth/2 (to the right)
|
||||
|
||||
const offsetPixels = sidebarWidth / 2;
|
||||
const offsetMapUnits = offsetPixels * resolution;
|
||||
|
||||
// We want pin at center + offset.
|
||||
// So map center must be pin - offset.
|
||||
// X increases to the right.
|
||||
targetCenter = [coords[0] - offsetMapUnits, coords[1]];
|
||||
}
|
||||
|
||||
const animationOptions = {
|
||||
center: targetCenter,
|
||||
@@ -645,33 +674,73 @@ export default class MapComponent extends Component {
|
||||
if (!this.mapInstance) return;
|
||||
|
||||
const size = this.mapInstance.getSize();
|
||||
// Check if mobile (width <= 768px matches CSS)
|
||||
if (size[0] > 768) return;
|
||||
|
||||
const pixel = this.mapInstance.getPixelFromCoordinate(coords);
|
||||
if (!pixel) return;
|
||||
|
||||
const height = size[1];
|
||||
const view = this.mapInstance.getView();
|
||||
const center = view.getCenter();
|
||||
const resolution = view.getResolution();
|
||||
|
||||
// Sidebar covers the bottom 50%
|
||||
const splitPoint = height / 2;
|
||||
// Default targets (current position)
|
||||
let targetPixelX = pixel[0];
|
||||
let targetPixelY = pixel[1];
|
||||
let needsPan = false;
|
||||
|
||||
// If the pin is in the bottom half (y > splitPoint), it is obscured
|
||||
if (pixel[1] > splitPoint) {
|
||||
// Target position: Center of top half = height * 0.25
|
||||
const targetY = height * 0.25;
|
||||
const deltaY = pixel[1] - targetY;
|
||||
// 1. Mobile Bottom Sheet Logic (Screen <= 768px)
|
||||
if (size[0] <= 768) {
|
||||
const height = size[1];
|
||||
const splitPoint = height / 2;
|
||||
|
||||
const view = this.mapInstance.getView();
|
||||
const center = view.getCenter();
|
||||
const resolution = view.getResolution();
|
||||
// If in bottom half
|
||||
if (pixel[1] > splitPoint) {
|
||||
targetPixelY = height * 0.25; // Target: Center of top half
|
||||
needsPan = true;
|
||||
}
|
||||
}
|
||||
// 2. Desktop Sidebar Logic (Screen > 768px + Sidebar Open)
|
||||
else if (this.args.isSidebarOpen) {
|
||||
const sidebarWidthVar = getComputedStyle(document.documentElement)
|
||||
.getPropertyValue('--sidebar-width')
|
||||
.trim();
|
||||
const sidebarWidth = parseInt(sidebarWidthVar, 10) || 360;
|
||||
|
||||
// Move the map center SOUTH (decrease Y) to move the pin UP (decrease pixel Y)
|
||||
const deltaMapUnits = deltaY * resolution;
|
||||
const newCenter = [center[0], center[1] - deltaMapUnits];
|
||||
// If under sidebar
|
||||
if (pixel[0] < sidebarWidth) {
|
||||
const visibleWidth = size[0] - sidebarWidth;
|
||||
targetPixelX = sidebarWidth + visibleWidth / 2; // Target: Center of visible area
|
||||
needsPan = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Header Logic (Any screen size)
|
||||
// Check if the (potentially new) target Y is under the header
|
||||
const headerHeight = 60;
|
||||
const minTopDistance = headerHeight + 20; // 80px
|
||||
|
||||
if (targetPixelY < minTopDistance) {
|
||||
targetPixelY = minTopDistance + 30; // Move it to ~110px, clear of header
|
||||
needsPan = true;
|
||||
}
|
||||
|
||||
if (needsPan) {
|
||||
const deltaPixelX = pixel[0] - targetPixelX;
|
||||
const deltaPixelY = pixel[1] - targetPixelY;
|
||||
|
||||
// X: Camera moves same direction as we want the world to move? No.
|
||||
// If we want pin to move RIGHT (pixel increases), Camera must move LEFT (X decreases).
|
||||
// deltaPixelX = current - target. If current < target (want move right), delta is negative.
|
||||
// center + negative = decrease. Correct.
|
||||
const newCenterX = center[0] + deltaPixelX * resolution;
|
||||
|
||||
// Y: Camera moves opposite direction to world relative to pixel coords.
|
||||
// Pixel Y increases DOWN. Map Y increases UP.
|
||||
// If we want pin to move DOWN (pixel increases), Camera must move UP (Y increases).
|
||||
// deltaPixelY = current - target. If current < target (want move down), delta is negative.
|
||||
// center - negative = increase. Correct.
|
||||
const newCenterY = center[1] - deltaPixelY * resolution;
|
||||
|
||||
view.animate({
|
||||
center: newCenter,
|
||||
center: [newCenterX, newCenterY],
|
||||
duration: 500,
|
||||
easing: (t) => t * (2 - t), // Ease-out
|
||||
});
|
||||
|
||||
@@ -286,7 +286,7 @@ export default class PlaceDetails extends Component {
|
||||
>
|
||||
<Icon
|
||||
@name="bookmark"
|
||||
@color={{if this.isSaved "currentColor" "#007bff"}}
|
||||
@color={{if this.isSaved "currentColor" "var(--link-color)"}}
|
||||
/>
|
||||
{{if this.isSaved "Saved" "Save"}}
|
||||
</button>
|
||||
@@ -307,7 +307,7 @@ export default class PlaceDetails extends Component {
|
||||
title="Edit"
|
||||
{{on "click" this.startEditing}}
|
||||
>
|
||||
<Icon @name="edit" @color="#007bff" />
|
||||
<Icon @name="edit" @color="var(--link-color)" />
|
||||
Edit
|
||||
</button>
|
||||
{{/if}}
|
||||
|
||||
@@ -226,7 +226,7 @@ export default class PlacesSidebar extends Component {
|
||||
class="btn btn-outline create-place"
|
||||
{{on "click" this.createNewPlace}}
|
||||
>
|
||||
<Icon @name="plus" @size={{18}} @color="#007bff" />
|
||||
<Icon @name="plus" @size={{18}} @color="var(--link-color)" />
|
||||
Create new place
|
||||
</button>
|
||||
{{/if}}
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
:root {
|
||||
--default-list-color: #fc3;
|
||||
--hover-bg: #f8f9fa;
|
||||
--sidebar-width: 360px;
|
||||
--link-color: #2a7fff;
|
||||
--link-color-visited: #6a4fbf;
|
||||
}
|
||||
|
||||
html,
|
||||
@@ -185,7 +188,7 @@ body {
|
||||
}
|
||||
|
||||
.text-primary {
|
||||
color: #007bff;
|
||||
color: var(--link-color);
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
@@ -202,7 +205,7 @@ body {
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 300px;
|
||||
width: var(--sidebar-width);
|
||||
background: white;
|
||||
z-index: 3100; /* Higher than Header (3000) */
|
||||
box-shadow: 2px 0 5px rgb(0 0 0 / 10%);
|
||||
@@ -243,7 +246,7 @@ body {
|
||||
}
|
||||
|
||||
.sidebar-content {
|
||||
padding: 1rem;
|
||||
padding: 0.5rem 1rem 1rem;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
flex: 1;
|
||||
@@ -332,8 +335,9 @@ body {
|
||||
}
|
||||
|
||||
.sidebar-content details .details-content {
|
||||
padding: 1rem 1.4rem;
|
||||
padding: 0 1.4rem 1rem;
|
||||
animation: details-slide-down 0.2s ease-out;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.sidebar-content details .link-list {
|
||||
@@ -386,7 +390,7 @@ body {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-family: inherit;
|
||||
font-size: 0.95rem;
|
||||
font-size: 1rem;
|
||||
box-sizing: border-box; /* Ensure padding doesn't overflow width */
|
||||
color: #333;
|
||||
background-color: #fff;
|
||||
@@ -394,8 +398,8 @@ body {
|
||||
|
||||
.form-control:focus {
|
||||
outline: none;
|
||||
border-color: #007bff;
|
||||
box-shadow: 0 0 0 2px rgb(0 123 255 / 10%);
|
||||
border-color: var(--link-color);
|
||||
box-shadow: 0 0 0 2px rgb(42 127 255 / 10%);
|
||||
}
|
||||
|
||||
select.form-control {
|
||||
@@ -424,12 +428,20 @@ select.form-control {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.settings-section p a {
|
||||
color: #007bff;
|
||||
.about-section {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.about-section a {
|
||||
color: var(--link-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.settings-section p a:hover {
|
||||
.about-section a:visited {
|
||||
color: var(--link-color-visited);
|
||||
}
|
||||
|
||||
.about-section a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@@ -438,7 +450,7 @@ select.form-control {
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #007bff;
|
||||
background: var(--link-color);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.75rem;
|
||||
@@ -467,7 +479,7 @@ select.form-control {
|
||||
}
|
||||
|
||||
.meta-info a {
|
||||
color: #007bff;
|
||||
color: var(--link-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@@ -475,6 +487,36 @@ select.form-control {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.sidebar-content table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.sidebar-content table th,
|
||||
.sidebar-content table td {
|
||||
padding: 0.5rem 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.sidebar-content table th {
|
||||
font-size: 0.75rem;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
color: #898989;
|
||||
}
|
||||
|
||||
.sidebar-content table td {
|
||||
border-bottom: 1px solid #f9f9f9;
|
||||
}
|
||||
|
||||
.sidebar-content table tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
abbr[title] {
|
||||
text-decoration: underline dotted;
|
||||
}
|
||||
|
||||
.link-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
@@ -486,7 +528,7 @@ select.form-control {
|
||||
}
|
||||
|
||||
.link-list a {
|
||||
color: #007bff;
|
||||
color: var(--link-color);
|
||||
text-decoration: none;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
@@ -611,7 +653,7 @@ select.form-control {
|
||||
}
|
||||
|
||||
.btn-blue {
|
||||
background: #007bff;
|
||||
background: var(--link-color);
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
@@ -856,15 +898,14 @@ span.icon {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Sidebar is open (Desktop: Left 300px) */
|
||||
/* Sidebar is open (Desktop: Left var(--sidebar-width)) */
|
||||
|
||||
/* We want to center in the remaining space (width - 300px) */
|
||||
/* We want to center in the remaining space (width - var(--sidebar-width)) */
|
||||
|
||||
/* Center X = 300 + (width - 300) / 2 = 300 + width/2 - 150 = width/2 + 150 */
|
||||
/* Center X = var(--sidebar-width) + (width - var(--sidebar-width)) / 2 = var(--sidebar-width)/2 + 50% */
|
||||
|
||||
/* So shift left by 150px from center */
|
||||
.map-container.sidebar-open .map-crosshair {
|
||||
left: calc(50% + 150px);
|
||||
left: calc(50% + var(--sidebar-width) / 2);
|
||||
}
|
||||
|
||||
@media (width <= 768px) {
|
||||
@@ -1152,7 +1193,7 @@ button.create-place {
|
||||
}
|
||||
|
||||
.place-lists-manager input[type='checkbox'] {
|
||||
accent-color: #007bff;
|
||||
accent-color: var(--link-color);
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
cursor: pointer;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "marco",
|
||||
"version": "1.14.0",
|
||||
"version": "1.15.2",
|
||||
"private": true,
|
||||
"description": "Unhosted maps app",
|
||||
"repository": {
|
||||
@@ -35,7 +35,7 @@
|
||||
"lint:js:fix": "eslint . --fix",
|
||||
"start": "vite",
|
||||
"test": "vite build --mode development && testem ci --port 0",
|
||||
"preversion": "pnpm test",
|
||||
"preversion": "pnpm lint && pnpm test",
|
||||
"version": "pnpm build && git add release/"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
release/assets/main-_2wecUUD.js
Normal file
2
release/assets/main-_2wecUUD.js
Normal file
File diff suppressed because one or more lines are too long
1
release/assets/main-ib8GDjrs.css
Normal file
1
release/assets/main-ib8GDjrs.css
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -39,8 +39,8 @@
|
||||
<meta name="msapplication-TileColor" content="#F6E9A6">
|
||||
<meta name="msapplication-TileImage" content="/icons/icon-144.png">
|
||||
|
||||
<script type="module" crossorigin src="/assets/main-GynTgP18.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/main-BT0n1kYB.css">
|
||||
<script type="module" crossorigin src="/assets/main-_2wecUUD.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/main-ib8GDjrs.css">
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user