Compare commits

..

1 Commits

Author SHA1 Message Date
c362268567 Use latest pnpm
Some checks failed
CI / Lint (pull_request) Failing after 8s
CI / Test (pull_request) Failing after 6s
2026-03-14 12:37:31 +04:00
23 changed files with 213 additions and 741 deletions

View File

@@ -1,14 +0,0 @@
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

@@ -1,13 +0,0 @@
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

@@ -1,152 +0,0 @@
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

@@ -1,36 +0,0 @@
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

@@ -1,38 +0,0 @@
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

@@ -1,100 +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 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,9 +499,10 @@ export default class MapComponent extends Component {
}
if (options.preventZoom) {
// 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 });
// 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);
} else if (selected.bbox) {
this.zoomToBbox(selected.bbox);
} else {
@@ -546,10 +547,7 @@ export default class MapComponent extends Component {
}
// Desktop: Sidebar covers left side (approx 400px)
else if (this.args.isSidebarOpen) {
const sidebarWidthVar = getComputedStyle(document.documentElement)
.getPropertyValue('--sidebar-width')
.trim();
const sidebarWidth = parseInt(sidebarWidthVar, 10) || 360;
const sidebarWidth = 400;
const visibleWidth = size[0] - sidebarWidth;
// Left padding: Sidebar + 15% of visible width
@@ -568,15 +566,14 @@ export default class MapComponent extends Component {
});
}
handlePinVisibility(coords, options = {}) {
handlePinVisibility(coords) {
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)
// UNLESS we want to maintain the current zoom
if (!options.maintainZoom && currentZoom < 16) {
if (currentZoom < 16) {
this.animateToSmartCenter(coords, 16);
return;
}
@@ -593,12 +590,8 @@ export default class MapComponent extends Component {
pixel[1] > size[1];
if (isOffScreen) {
// 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);
this.animateToSmartCenter(coords);
} else {
// If on-screen, only pan if obscured by UI
this.panIfObscured(coords);
}
}
@@ -634,28 +627,6 @@ 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,
@@ -674,73 +645,33 @@ 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 view = this.mapInstance.getView();
const center = view.getCenter();
const resolution = view.getResolution();
const height = size[1];
// Default targets (current position)
let targetPixelX = pixel[0];
let targetPixelY = pixel[1];
let needsPan = false;
// Sidebar covers the bottom 50%
const splitPoint = height / 2;
// 1. Mobile Bottom Sheet Logic (Screen <= 768px)
if (size[0] <= 768) {
const height = size[1];
const splitPoint = height / 2;
// 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;
// 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;
const view = this.mapInstance.getView();
const center = view.getCenter();
const resolution = view.getResolution();
// 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;
// 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];
view.animate({
center: [newCenterX, newCenterY],
center: newCenter,
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" "var(--link-color)"}}
@color={{if this.isSaved "currentColor" "#007bff"}}
/>
{{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="var(--link-color)" />
<Icon @name="edit" @color="#007bff" />
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="var(--link-color)" />
<Icon @name="plus" @size={{18}} @color="#007bff" />
Create new place
</button>
{{/if}}

View File

@@ -0,0 +1,128 @@
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

@@ -1,45 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

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

View File

@@ -4,7 +4,6 @@ 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 = [
{
@@ -25,13 +24,6 @@ export default class SettingsService extends Service {
// },
];
photonApis = [
{
name: 'photon.komoot.io',
url: 'https://photon.komoot.io/api/',
},
];
constructor() {
super(...arguments);
this.loadSettings();
@@ -67,8 +59,4 @@ export default class SettingsService extends Service {
this.mapKinetic = enabled;
localStorage.setItem('marco:map-kinetic', String(enabled));
}
updatePhotonApi(url) {
this.photonApi = url;
}
}

View File

@@ -2,10 +2,6 @@
:root {
--default-list-color: #fc3;
--hover-bg: #f8f9fa;
--sidebar-width: 360px;
--link-color: #2a7fff;
--link-color-visited: #6a4fbf;
}
html,
@@ -188,7 +184,7 @@ body {
}
.text-primary {
color: var(--link-color);
color: #007bff;
}
.text-danger {
@@ -205,7 +201,7 @@ body {
top: 0;
left: 0;
bottom: 0;
width: var(--sidebar-width);
width: 300px;
background: white;
z-index: 3100; /* Higher than Header (3000) */
box-shadow: 2px 0 5px rgb(0 0 0 / 10%);
@@ -255,119 +251,10 @@ 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: var(--hover-bg);
background: #f8f9fa;
padding: 1rem;
border-bottom: 1px solid #eee;
}
@@ -389,27 +276,14 @@ 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;
}
.form-control:focus {
outline: none;
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;
border-color: #007bff;
box-shadow: 0 0 0 2px rgb(0 123 255 / 10%);
}
.edit-actions {
@@ -420,28 +294,27 @@ select.form-control {
.settings-section {
margin-bottom: 2rem;
font-size: 0.95rem;
}
.settings-section h3 {
font-size: 1rem;
font-weight: bold;
color: #666;
margin: 0 0 0.5rem;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.settings-section .form-group {
margin-top: 1rem;
}
.about-section {
margin-bottom: 2rem;
font-size: 0.95rem;
}
.about-section a {
color: var(--link-color);
.settings-section p a {
color: #007bff;
text-decoration: none;
}
.about-section a:visited {
color: var(--link-color-visited);
}
.about-section a:hover {
.settings-section p a:hover {
text-decoration: underline;
}
@@ -450,7 +323,7 @@ select.form-control {
}
.btn-primary {
background: var(--link-color);
background: #007bff;
color: white;
border: none;
padding: 0.75rem;
@@ -479,7 +352,7 @@ select.form-control {
}
.meta-info a {
color: var(--link-color);
color: #007bff;
text-decoration: none;
}
@@ -487,37 +360,6 @@ select.form-control {
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;
@@ -529,7 +371,7 @@ abbr[title] {
}
.link-list a {
color: var(--link-color);
color: #007bff;
text-decoration: none;
font-size: 0.95rem;
}
@@ -558,7 +400,7 @@ abbr[title] {
}
.place-item:hover {
background: var(--hover-bg);
background: #eee;
}
.place-name {
@@ -654,7 +496,7 @@ abbr[title] {
}
.btn-blue {
background: var(--link-color);
background: #007bff;
color: white;
border: none;
}
@@ -764,17 +606,6 @@ abbr[title] {
/* Icons */
.app-logo-icon {
display: inline-flex;
width: 32px;
height: 32px;
}
.app-logo-icon svg {
width: 100%;
height: 100%;
}
span.icon {
display: inline-block;
}
@@ -899,14 +730,15 @@ span.icon {
display: block;
}
/* Sidebar is open (Desktop: Left var(--sidebar-width)) */
/* Sidebar is open (Desktop: Left 300px) */
/* We want to center in the remaining space (width - var(--sidebar-width)) */
/* We want to center in the remaining space (width - 300px) */
/* Center X = var(--sidebar-width) + (width - var(--sidebar-width)) / 2 = var(--sidebar-width)/2 + 50% */
/* Center X = 300 + (width - 300) / 2 = 300 + width/2 - 150 = width/2 + 150 */
/* So shift left by 150px from center */
.map-container.sidebar-open .map-crosshair {
left: calc(50% + var(--sidebar-width) / 2);
left: calc(50% + 150px);
}
@media (width <= 768px) {
@@ -1114,7 +946,7 @@ button.create-place {
.search-result-item:hover,
.search-result-item:focus {
background: var(--hover-bg);
background: #f5f5f5;
outline: none;
}
@@ -1179,7 +1011,7 @@ button.create-place {
}
.place-lists-manager .list-item:hover {
background: var(--hover-bg);
background: #f8f9fa;
}
.place-lists-manager label {
@@ -1194,7 +1026,7 @@ button.create-place {
}
.place-lists-manager input[type='checkbox'] {
accent-color: var(--link-color);
accent-color: #007bff;
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 AppMenu from '#components/app-menu/index';
import SettingsPane from '#components/settings-pane';
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 isAppMenuOpen = false;
@tracked isSettingsOpen = 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
toggleAppMenu() {
this.isAppMenuOpen = !this.isAppMenuOpen;
toggleSettings() {
this.isSettingsOpen = !this.isSettingsOpen;
}
@action
closeAppMenu() {
this.isAppMenuOpen = false;
closeSettings() {
this.isSettingsOpen = false;
}
@action
handleOutsideClick() {
if (this.isAppMenuOpen) {
this.closeAppMenu();
if (this.isSettingsOpen) {
this.closeSettings();
} 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.toggleAppMenu}} />
<AppHeader @onToggleMenu={{this.toggleSettings}} />
<div
id="rs-widget-container"
@@ -81,12 +81,12 @@ export default class ApplicationComponent extends Component {
{{/if}}
<Map
@isSidebarOpen={{or this.isSidebarOpen this.isAppMenuOpen}}
@isSidebarOpen={{or this.isSidebarOpen this.isSettingsOpen}}
@onOutsideClick={{this.handleOutsideClick}}
/>
{{#if this.isAppMenuOpen}}
<AppMenu @onClose={{this.closeAppMenu}} />
{{#if this.isSettingsOpen}}
<SettingsPane @onClose={{this.closeSettings}} />
{{/if}}
{{outlet}}

View File

@@ -5,11 +5,8 @@ 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';
@@ -37,11 +34,8 @@ 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.15.0",
"version": "1.13.3",
"private": true,
"description": "Unhosted maps app",
"repository": {
@@ -21,7 +21,7 @@
},
"scripts": {
"build": "vite build --outDir release/",
"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",
"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",
"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 lint && pnpm test",
"preversion": "pnpm test",
"version": "pnpm build && git add release/"
},
"devDependencies": {
@@ -104,5 +104,6 @@
"dependencies": {
"ember-concurrency": "^5.2.0",
"ember-lifeline": "^7.0.0"
}
},
"packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be"
}

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