Compare commits

..

20 Commits

Author SHA1 Message Date
6dfd9765b4 1.15.0
All checks were successful
CI / Lint (push) Successful in 53s
CI / Test (push) Successful in 56s
2026-03-17 15:55:15 +04:00
45eb8f087d Add linting to preversion script 2026-03-17 15:53:58 +04:00
3630fae133 Merge pull request 'Improve app menu sidebar, add separate subsections, more content' (#31) from feature/app_menu into master
All checks were successful
CI / Lint (push) Successful in 48s
CI / Test (push) Successful in 57s
Reviewed-on: #31
2026-03-17 11:52:42 +00:00
1116161e93 Add info/links for preferred contributions
All checks were successful
CI / Lint (pull_request) Successful in 53s
CI / Test (pull_request) Successful in 56s
Release Drafter / Update release notes draft (pull_request) Successful in 18s
2026-03-17 15:48:11 +04:00
88eb0ac0c1 WIP About section
* Add fold-out sections for additional details
* Use table for Open Source info, add licenses
* Move link styles to CSS variables
2026-03-17 15:12:29 +04:00
6da004e199 Merge pull request 'Increase sidebar width, improve auto-panning' (#30) from ui/autopan into feature/app_menu
Reviewed-on: #30
2026-03-17 09:20:46 +00:00
8877a9e1c8 Remove unnecessary whitespace
All checks were successful
CI / Lint (pull_request) Successful in 49s
CI / Test (pull_request) Successful in 56s
Release Drafter / Update release notes draft (pull_request) Successful in 18s
2026-03-17 13:18:17 +04:00
03d6a86569 Increase sidebar width, improve auto-panning
Some checks failed
CI / Lint (pull_request) Failing after 54s
CI / Test (pull_request) Has been cancelled
* Increase sidebar with on desktop to fit more content
* Move sidebar width to CSS variable
* Auto-pan when a selected place would be obstructed by the desktop
  sidebar
* Consider the header obscuring selected places, too
* Only autopan when actually necessary
2026-03-17 13:14:21 +04:00
5baebbb846 Add details elements/sections to App Menu sidebar
Starting with some About details
2026-03-16 18:23:46 +04:00
dca2991754 Style dropdown menu form controls 2026-03-16 17:07:38 +04:00
aee7f9d181 Move Photon API URL to Settings 2026-03-16 17:07:24 +04:00
56a077cceb Change release drafter token config
All checks were successful
CI / Lint (push) Successful in 49s
CI / Test (push) Successful in 57s
Another day, another try
2026-03-16 16:24:40 +04:00
7e5a034cac Merge pull request 'Prevent app icon loading when opening the app menu' (#29) from dev/app-icon into master
All checks were successful
CI / Lint (push) Successful in 47s
CI / Test (push) Successful in 56s
Reviewed-on: #29
2026-03-16 12:06:49 +00:00
5892bd0cda Prevent app icon loading when opening the app menu
Some checks failed
CI / Lint (pull_request) Successful in 50s
CI / Test (pull_request) Successful in 56s
Release Drafter / Update release notes draft (pull_request) Has been cancelled
Compile into app code by importing the icon in JS.
2026-03-16 16:02:57 +04:00
baaf027900 Fix missing token
All checks were successful
CI / Lint (push) Successful in 48s
CI / Test (push) Successful in 57s
Hopefully
2026-03-14 16:57:52 +04:00
beb3d12169 Merge pull request 'Refactor app menu sidebar' (#28) from feature/app_menu into master
All checks were successful
CI / Lint (push) Successful in 48s
CI / Test (push) Successful in 57s
Reviewed-on: #28
2026-03-14 12:40:53 +00:00
a5aa396411 Merge branch 'master' into feature/app_menu
Some checks failed
CI / Test (pull_request) Successful in 58s
Release Drafter / Update release notes draft (pull_request) Failing after 17s
CI / Lint (pull_request) Successful in 48s
2026-03-14 16:31:42 +04:00
21cb8e6cc2 Add release drafter workflow
All checks were successful
CI / Lint (push) Successful in 54s
CI / Test (push) Successful in 1m8s
2026-03-14 16:31:22 +04:00
12ec7fcbbf Add release drafter config
Some checks failed
CI / Lint (push) Successful in 52s
CI / Test (push) Has been cancelled
2026-03-14 16:30:23 +04:00
78d7aeba2c Refactor app menu sidebar
* Rename to "app menu" across code
* Move content to dedicated sections/components, introduce app menu links
* Use CSS variable for hover background color across the app
2026-03-14 16:24:36 +04:00
23 changed files with 740 additions and 211 deletions

View File

@@ -0,0 +1,14 @@
name-template: 'v$RESOLVED_VERSION'
tag-template: 'v$RESOLVED_VERSION'
version-resolver:
major:
labels:
- 'release/major'
minor:
labels:
- 'release/minor'
- 'feature'
patch:
labels:
- 'release/patch'
default: patch

View File

@@ -0,0 +1,13 @@
name: Release Drafter
on:
pull_request:
types: [closed]
jobs:
release_drafter_job:
name: Update release notes draft
runs-on: ubuntu-latest
steps:
- name: Release Drafter
uses: https://github.com/raucao/gitea-release-drafter@dev
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -0,0 +1,152 @@
import { on } from '@ember/modifier';
import Icon from '#components/icon';
<template>
{{! template-lint-disable no-nested-interactive }}
<div class="sidebar-header">
<button type="button" class="back-btn" {{on "click" @onBack}}>
<Icon @name="arrow-left" @size={{20}} @color="#333" />
</button>
<h2>About</h2>
<button type="button" class="close-btn" {{on "click" @onClose}}>
<Icon @name="x" @size={{20}} @color="#333" />
</button>
</div>
<div class="sidebar-content">
<section class="about-section">
<p>
<strong>Marco</strong>
(as in
<a
href="https://en.wikipedia.org/wiki/Marco_Polo"
target="_blank"
rel="noopener"
>Marco Polo</a>) is an unhosted maps application that respects your
privacy and choices.
</p>
<p>
Connect your own
<a
href="https://remotestorage.io/"
target="_blank"
rel="noopener"
>remote storage</a>
to sync place bookmarks across apps and devices.
</p>
<details>
<summary>
<Icon @name="gift" @size={{20}} />
<span>Open Source</span>
</summary>
<div class="details-content">
<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>
<summary>
<Icon @name="heart" @size={{20}} @color="#e5533d" />
<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>
</div>
</template>

View File

@@ -0,0 +1,36 @@
import { on } from '@ember/modifier';
import { fn } from '@ember/helper';
import { htmlSafe } from '@ember/template';
import Icon from '#components/icon';
import iconRounded from '../../icons/icon-rounded.svg?raw';
<template>
<div class="sidebar-header">
<h2>
<span class="app-logo-icon">
{{htmlSafe iconRounded}}
</span>
Marco
</h2>
<button type="button" class="close-btn" {{on "click" @onClose}}>
<Icon @name="x" @size={{20}} @color="#333" />
</button>
</div>
<div class="sidebar-content">
<ul class="app-menu">
<li>
<button type="button" {{on "click" (fn @onNavigate "settings")}}>
<Icon @name="settings" @size={{20}} />
<span>Settings</span>
</button>
</li>
<li>
<button type="button" {{on "click" (fn @onNavigate "about")}}>
<Icon @name="info" @size={{20}} />
<span>About</span>
</button>
</li>
</ul>
</div>
</template>

View File

@@ -0,0 +1,38 @@
import Component from '@glimmer/component';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
import { fn } from '@ember/helper';
import eq from 'ember-truth-helpers/helpers/eq';
import AppMenuHome from './home';
import AppMenuSettings from './settings';
import AppMenuAbout from './about';
export default class AppMenu extends Component {
@tracked currentView = 'menu'; // 'menu', 'settings', 'about'
@action
setView(view) {
this.currentView = view;
}
<template>
<div class="sidebar settings-pane">
{{#if (eq this.currentView "menu")}}
<AppMenuHome @onNavigate={{this.setView}} @onClose={{@onClose}} />
{{else if (eq this.currentView "settings")}}
<AppMenuSettings
@onBack={{fn this.setView "menu"}}
@onClose={{@onClose}}
/>
{{else if (eq this.currentView "about")}}
<AppMenuAbout
@onBack={{fn this.setView "menu"}}
@onClose={{@onClose}}
/>
{{/if}}
</div>
</template>
}

View File

@@ -0,0 +1,100 @@
import Component from '@glimmer/component';
import { on } from '@ember/modifier';
import { service } from '@ember/service';
import { action } from '@ember/object';
import Icon from '#components/icon';
import eq from 'ember-truth-helpers/helpers/eq';
export default class AppMenuSettings extends Component {
@service settings;
@action
updateApi(event) {
this.settings.updateOverpassApi(event.target.value);
}
@action
toggleKinetic(event) {
this.settings.updateMapKinetic(event.target.value === 'true');
}
@action
updatePhotonApi(event) {
this.settings.updatePhotonApi(event.target.value);
}
<template>
<div class="sidebar-header">
<button type="button" class="back-btn" {{on "click" @onBack}}>
<Icon @name="arrow-left" @size={{20}} @color="#333" />
</button>
<h2>Settings</h2>
<button type="button" class="close-btn" {{on "click" @onClose}}>
<Icon @name="x" @size={{20}} @color="#333" />
</button>
</div>
<div class="sidebar-content">
<section class="settings-section">
<div class="form-group">
<label for="map-kinetic">Map Inertia (Kinetic Panning)</label>
<select
id="map-kinetic"
class="form-control"
{{on "change" this.toggleKinetic}}
>
<option
value="true"
selected={{if this.settings.mapKinetic "selected"}}
>
On
</option>
<option
value="false"
selected={{unless this.settings.mapKinetic "selected"}}
>
Off
</option>
</select>
</div>
<div class="form-group">
<label for="overpass-api">Overpass API Provider</label>
<select
id="overpass-api"
class="form-control"
{{on "change" this.updateApi}}
>
{{#each this.settings.overpassApis as |api|}}
<option
value={{api.url}}
selected={{if
(eq api.url this.settings.overpassApi)
"selected"
}}
>
{{api.name}}
</option>
{{/each}}
</select>
</div>
<div class="form-group">
<label for="photon-api">Photon API Provider</label>
<select
id="photon-api"
class="form-control"
{{on "change" this.updatePhotonApi}}
>
{{#each this.settings.photonApis as |api|}}
<option
value={{api.url}}
selected={{if (eq api.url this.settings.photonApi) "selected"}}
>
{{api.name}}
</option>
{{/each}}
</select>
</div>
</section>
</div>
</template>
}

View File

@@ -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
});

View File

@@ -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}}

View File

@@ -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}}

View File

@@ -1,128 +0,0 @@
import Component from '@glimmer/component';
import { on } from '@ember/modifier';
import { service } from '@ember/service';
import { action } from '@ember/object';
import Icon from '#components/icon';
import eq from 'ember-truth-helpers/helpers/eq';
export default class SettingsPane extends Component {
@service settings;
@action
updateApi(event) {
this.settings.updateOverpassApi(event.target.value);
}
@action
toggleKinetic(event) {
this.settings.updateMapKinetic(event.target.value === 'true');
}
<template>
<div class="sidebar settings-pane">
<div class="sidebar-header">
<h2>
<img src="/icons/icon-rounded.svg" alt="" width="32" height="32" />
Marco
</h2>
<button type="button" class="close-btn" {{on "click" @onClose}}>
<Icon @name="x" @size={{20}} @color="#333" />
</button>
</div>
<div class="sidebar-content">
<section class="settings-section">
<h3>Settings</h3>
<div class="form-group">
<label for="map-kinetic">Map Inertia (Kinetic Panning)</label>
<select
id="map-kinetic"
class="form-control"
{{on "change" this.toggleKinetic}}
>
<option
value="true"
selected={{if this.settings.mapKinetic "selected"}}
>
On
</option>
<option
value="false"
selected={{unless this.settings.mapKinetic "selected"}}
>
Off
</option>
</select>
</div>
<div class="form-group">
<label for="overpass-api">Overpass API Provider</label>
<select
id="overpass-api"
class="form-control"
{{on "change" this.updateApi}}
>
{{#each this.settings.overpassApis as |api|}}
<option
value={{api.url}}
selected={{if
(eq api.url this.settings.overpassApi)
"selected"
}}
>
{{api.name}}
</option>
{{/each}}
</select>
</div>
</section>
<section class="settings-section">
<h3>About</h3>
<p>
<strong>Marco</strong>
(as in
<a
href="https://en.wikipedia.org/wiki/Marco_Polo"
target="_blank"
rel="noopener"
>Marco Polo</a>) is an unhosted maps application that respects your
privacy and choices.
</p>
<p>
Connect your own
<a
href="https://remotestorage.io/"
target="_blank"
rel="noopener"
>remote storage</a>
to sync place bookmarks across apps and devices.
</p>
<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>
</section>
</div>
</div>
</template>
}

View File

@@ -0,0 +1,45 @@
<svg
width="1024"
height="1024"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<!-- Background -->
<rect
x="0"
y="0"
width="1024"
height="1024"
rx="220"
fill="#F6E9A6"
/>
<!-- Subtle map grid (kept well outside safe zone) -->
<g stroke="#E6D88A" stroke-width="10" opacity="0.6">
<line x1="256" y1="0" x2="256" y2="1024" />
<line x1="512" y1="0" x2="512" y2="1024" />
<line x1="768" y1="0" x2="768" y2="1024" />
<line x1="0" y1="256" x2="1024" y2="256" />
<line x1="0" y1="512" x2="1024" y2="512" />
<line x1="0" y1="768" x2="1024" y2="768" />
</g>
<!-- Location pin (exact app shape, larger, centered, safe-zone compliant) -->
<!-- Safe zone target: ~680px diameter -->
<g
transform="
translate(512 512)
scale(22)
translate(-12 -12)
"
fill="#ea4335"
stroke="#b31412"
stroke-width="1"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z" />
<circle cx="12" cy="10" r="3" fill="#b31412" stroke="none" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,9 +1,13 @@
import Service from '@ember/service';
import Service, { service } from '@ember/service';
import { getPlaceType } from '../utils/osm';
import { humanizeOsmTag } from '../utils/format-text';
export default class PhotonService extends Service {
baseUrl = 'https://photon.komoot.io/api/';
@service settings;
get baseUrl() {
return this.settings.photonApi;
}
async search(query, lat, lon, limit = 10) {
if (!query || query.length < 2) return [];

View File

@@ -4,6 +4,7 @@ import { tracked } from '@glimmer/tracking';
export default class SettingsService extends Service {
@tracked overpassApi = 'https://overpass-api.de/api/interpreter';
@tracked mapKinetic = true;
@tracked photonApi = 'https://photon.komoot.io/api/';
overpassApis = [
{
@@ -24,6 +25,13 @@ export default class SettingsService extends Service {
// },
];
photonApis = [
{
name: 'photon.komoot.io',
url: 'https://photon.komoot.io/api/',
},
];
constructor() {
super(...arguments);
this.loadSettings();
@@ -59,4 +67,8 @@ export default class SettingsService extends Service {
this.mapKinetic = enabled;
localStorage.setItem('marco:map-kinetic', String(enabled));
}
updatePhotonApi(url) {
this.photonApi = url;
}
}

View File

@@ -2,6 +2,10 @@
:root {
--default-list-color: #fc3;
--hover-bg: #f8f9fa;
--sidebar-width: 360px;
--link-color: #2a7fff;
--link-color-visited: #6a4fbf;
}
html,
@@ -184,7 +188,7 @@ body {
}
.text-primary {
color: #007bff;
color: var(--link-color);
}
.text-danger {
@@ -201,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%);
@@ -251,10 +255,119 @@ body {
overscroll-behavior: contain;
}
.app-menu {
list-style: none;
padding: 0;
margin: 0 -1rem;
}
.app-menu button {
width: 100%;
display: flex;
align-items: center;
gap: 0.8rem;
padding: 1rem;
padding-left: 1.4rem;
background: none;
border: none;
color: #333;
cursor: pointer;
text-align: left;
font-size: 0.95rem;
font-family: inherit;
transition: background-color 0.2s;
}
.app-menu button:hover {
background-color: var(--hover-bg);
}
.app-menu .icon {
color: #666;
width: 20px;
height: 20px;
}
.sidebar-content details {
margin: 0 -1rem; /* Top margin, negative side margins to span full width */
}
.sidebar-content details summary {
list-style: none; /* Hide default triangle */
display: flex;
align-items: center;
gap: 0.8rem;
padding: 1rem;
padding-left: 1.4rem;
cursor: pointer;
font-size: 0.95rem;
color: #333;
transition: background-color 0.2s;
}
.sidebar-content details summary::-webkit-details-marker {
display: none; /* Hide default triangle in WebKit */
}
.sidebar-content details summary:hover {
background-color: var(--hover-bg);
}
.sidebar-content details summary .icon {
width: 20px;
height: 20px;
}
.sidebar-content details summary::after {
content: '';
width: 20px;
height: 20px;
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23666' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='9 18 15 12 9 6'/%3E%3C/svg%3E");
background-size: 20px 20px;
background-repeat: no-repeat;
background-position: center;
margin-left: auto;
transition: transform 0.2s ease;
}
.sidebar-content details[open] summary::after {
transform: rotate(90deg);
}
.sidebar-content details .details-content {
padding: 0 1.4rem 1rem;
animation: details-slide-down 0.2s ease-out;
}
.sidebar-content details .link-list {
padding: 0;
margin: 0;
}
@keyframes details-slide-down {
from {
opacity: 0;
transform: translateY(-5px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.sidebar-content details .link-list li {
margin-bottom: 0.5rem;
}
.sidebar-content details .link-list li:last-child {
margin-bottom: 0;
}
.edit-form {
margin: -1rem;
margin-bottom: 1rem;
background: #f8f9fa;
background: var(--hover-bg);
padding: 1rem;
border-bottom: 1px solid #eee;
}
@@ -276,14 +389,27 @@ body {
border: 1px solid #ddd;
border-radius: 4px;
font-family: inherit;
font-size: 1rem;
font-size: 0.95rem;
box-sizing: border-box; /* Ensure padding doesn't overflow width */
color: #333;
background-color: #fff;
}
.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 {
appearance: none;
background-color: #fff;
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23666' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 0.75rem center;
background-size: 16px 16px;
padding-right: 2.5rem;
cursor: pointer;
}
.edit-actions {
@@ -294,27 +420,28 @@ body {
.settings-section {
margin-bottom: 2rem;
}
.settings-section h3 {
font-size: 1rem;
font-weight: bold;
color: #666;
margin: 0 0 0.5rem;
text-transform: uppercase;
letter-spacing: 0.5px;
font-size: 0.95rem;
}
.settings-section .form-group {
margin-top: 1rem;
}
.settings-section p a {
color: #007bff;
.about-section {
margin-bottom: 2rem;
font-size: 0.95rem;
}
.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;
}
@@ -323,7 +450,7 @@ body {
}
.btn-primary {
background: #007bff;
background: var(--link-color);
color: white;
border: none;
padding: 0.75rem;
@@ -352,7 +479,7 @@ body {
}
.meta-info a {
color: #007bff;
color: var(--link-color);
text-decoration: none;
}
@@ -360,6 +487,37 @@ body {
text-decoration: underline;
}
.sidebar-content table {
width: 100%;
border-collapse: collapse;
font-size: 0.9rem;
}
.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;
@@ -371,7 +529,7 @@ body {
}
.link-list a {
color: #007bff;
color: var(--link-color);
text-decoration: none;
font-size: 0.95rem;
}
@@ -400,7 +558,7 @@ body {
}
.place-item:hover {
background: #eee;
background: var(--hover-bg);
}
.place-name {
@@ -496,7 +654,7 @@ body {
}
.btn-blue {
background: #007bff;
background: var(--link-color);
color: white;
border: none;
}
@@ -606,6 +764,17 @@ body {
/* Icons */
.app-logo-icon {
display: inline-flex;
width: 32px;
height: 32px;
}
.app-logo-icon svg {
width: 100%;
height: 100%;
}
span.icon {
display: inline-block;
}
@@ -730,15 +899,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) {
@@ -946,7 +1114,7 @@ button.create-place {
.search-result-item:hover,
.search-result-item:focus {
background: #f5f5f5;
background: var(--hover-bg);
outline: none;
}
@@ -1011,7 +1179,7 @@ button.create-place {
}
.place-lists-manager .list-item:hover {
background: #f8f9fa;
background: var(--hover-bg);
}
.place-lists-manager label {
@@ -1026,7 +1194,7 @@ button.create-place {
}
.place-lists-manager input[type='checkbox'] {
accent-color: #007bff;
accent-color: var(--link-color);
width: 16px;
height: 16px;
cursor: pointer;

View File

@@ -2,7 +2,7 @@ import Component from '@glimmer/component';
import { pageTitle } from 'ember-page-title';
import Map from '#components/map';
import AppHeader from '#components/app-header';
import SettingsPane from '#components/settings-pane';
import AppMenu from '#components/app-menu/index';
import { service } from '@ember/service';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
@@ -14,7 +14,7 @@ export default class ApplicationComponent extends Component {
@service mapUi;
@service router;
@tracked isSettingsOpen = false;
@tracked isAppMenuOpen = false;
get isSidebarOpen() {
// We consider the sidebar "open" if we are in search or place routes.
@@ -34,19 +34,19 @@ export default class ApplicationComponent extends Component {
}
@action
toggleSettings() {
this.isSettingsOpen = !this.isSettingsOpen;
toggleAppMenu() {
this.isAppMenuOpen = !this.isAppMenuOpen;
}
@action
closeSettings() {
this.isSettingsOpen = false;
closeAppMenu() {
this.isAppMenuOpen = false;
}
@action
handleOutsideClick() {
if (this.isSettingsOpen) {
this.closeSettings();
if (this.isAppMenuOpen) {
this.closeAppMenu();
} else if (this.router.currentRouteName === 'search') {
this.router.transitionTo('index');
} else if (this.router.currentRouteName === 'place') {
@@ -65,7 +65,7 @@ export default class ApplicationComponent extends Component {
<template>
{{pageTitle "Marco"}}
<AppHeader @onToggleMenu={{this.toggleSettings}} />
<AppHeader @onToggleMenu={{this.toggleAppMenu}} />
<div
id="rs-widget-container"
@@ -81,12 +81,12 @@ export default class ApplicationComponent extends Component {
{{/if}}
<Map
@isSidebarOpen={{or this.isSidebarOpen this.isSettingsOpen}}
@isSidebarOpen={{or this.isSidebarOpen this.isAppMenuOpen}}
@onOutsideClick={{this.handleOutsideClick}}
/>
{{#if this.isSettingsOpen}}
<SettingsPane @onClose={{this.closeSettings}} />
{{#if this.isAppMenuOpen}}
<AppMenu @onClose={{this.closeAppMenu}} />
{{/if}}
{{outlet}}

View File

@@ -5,8 +5,11 @@ import checkSquare from 'feather-icons/dist/icons/check-square.svg?raw';
import clock from 'feather-icons/dist/icons/clock.svg?raw';
import edit from 'feather-icons/dist/icons/edit.svg?raw';
import facebook from 'feather-icons/dist/icons/facebook.svg?raw';
import gift from 'feather-icons/dist/icons/gift.svg?raw';
import globe from 'feather-icons/dist/icons/globe.svg?raw';
import heart from 'feather-icons/dist/icons/heart.svg?raw';
import home from 'feather-icons/dist/icons/home.svg?raw';
import info from 'feather-icons/dist/icons/info.svg?raw';
import instagram from 'feather-icons/dist/icons/instagram.svg?raw';
import logIn from 'feather-icons/dist/icons/log-in.svg?raw';
import logOut from 'feather-icons/dist/icons/log-out.svg?raw';
@@ -34,8 +37,11 @@ const ICONS = {
clock,
edit,
facebook,
gift,
globe,
heart,
home,
info,
instagram,
'log-in': logIn,
'log-out': logOut,

View File

@@ -1,6 +1,6 @@
{
"name": "marco",
"version": "1.14.0",
"version": "1.15.0",
"private": true,
"description": "Unhosted maps app",
"repository": {
@@ -21,7 +21,7 @@
},
"scripts": {
"build": "vite build --outDir release/",
"build:icons": "for size in 32 48 144 180 192 512; do if [ \"$size\" -le 64 ]; then magick public/icons/icon.svg -define svg:remove-groups=map-grid -resize ${size}x${size} public/icons/icon-${size}.png; else rsvg-convert -w $size -h $size public/icons/icon.svg -o public/icons/icon-${size}.png; fi; done && rsvg-convert -w 512 -h 512 public/icons/icon.svg -o public/icons/icon-maskable.png",
"build:icons": "cp public/icons/icon-rounded.svg app/icons/icon-rounded.svg && for size in 32 48 144 180 192 512; do if [ \"$size\" -le 64 ]; then magick public/icons/icon.svg -define svg:remove-groups=map-grid -resize ${size}x${size} public/icons/icon-${size}.png; else rsvg-convert -w $size -h $size public/icons/icon.svg -o public/icons/icon-${size}.png; fi; done && rsvg-convert -w 512 -h 512 public/icons/icon.svg -o public/icons/icon-maskable.png",
"format": "prettier . --cache --write",
"lint": "concurrently \"pnpm:lint:*(!fix)\" --names \"lint:\" --prefixColors auto",
"lint:css": "stylelint \"**/*.css\"",
@@ -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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -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-CDrxPJow.js"></script>
<link rel="stylesheet" crossorigin href="/assets/main-D62ihu_u.css">
</head>
<body>
</body>