5 Commits

Author SHA1 Message Date
026d1c4712 1.4.2 2026-01-23 12:59:37 +07:00
6bd55843bb Switch to bkero's API (for now) 2026-01-23 12:59:11 +07:00
33a6469a19 Various layout and style improvements for place details 2026-01-23 12:41:27 +07:00
6d7bea411a 1.4.1 2026-01-23 10:21:25 +07:00
7b01bb1118 Fix place store/remove behavior 2026-01-23 10:21:02 +07:00
13 changed files with 124 additions and 98 deletions

View File

@@ -67,6 +67,11 @@ export default class PlaceDetails extends Component {
return this.place.url || this.tags.website || this.tags['contact:website'];
}
get websiteDomain() {
const url = new URL(this.website);
return url.hostname;
}
get openingHours() {
return this.tags.opening_hours;
}
@@ -106,6 +111,10 @@ export default class PlaceDetails extends Component {
return `https://www.openstreetmap.org/${type}/${id}`;
}
get gmapsUrl() {
return `https://www.google.com/maps/search/?api=1&query=${this.name}&query=${this.place.lat},${this.place.lon}`;
}
<template>
<div class="place-details">
<h3>{{this.name}}</h3>
@@ -129,6 +138,7 @@ export default class PlaceDetails extends Component {
</div>
<div class="meta-info">
{{#if this.cuisine}}
<p>
<strong>Cuisine:</strong>
@@ -153,7 +163,7 @@ export default class PlaceDetails extends Component {
{{#if this.website}}
<p class="content-with-icon">
<Icon @name="globe" @title="Website" />
<span><a href={{this.website}} target="_blank" rel="noopener noreferrer">Website</a></span>
<span><a href={{this.website}} target="_blank" rel="noopener noreferrer">{{this.websiteDomain}}</a></span>
</p>
{{/if}}
@@ -164,7 +174,8 @@ export default class PlaceDetails extends Component {
</p>
{{/if}}
<hr class="meta-divider">
</div>
<div class="meta-info">
{{#if this.address}}
<p class="content-with-icon">
@@ -192,6 +203,16 @@ export default class PlaceDetails extends Component {
</span>
</p>
{{/if}}
<p class="content-with-icon">
<Icon @name="map" @title="OSM ID" />
<span>
<a href={{this.gmapsUrl}} target="_blank" rel="noopener noreferrer">
Google Maps
</a>
</span>
</p>
</div>
</div>
</template>

View File

@@ -31,70 +31,46 @@ export default class PlacesSidebar extends Component {
}
}
get geoLink() {
if (!this.args.selectedPlace) return '#';
const p = this.args.selectedPlace;
// geo:lat,lon?q=lat,lon(Label)
const label = encodeURIComponent(
p.title ||
p.tags?.name ||
p.tags?.['name:en'] ||
'Location'
);
return `geo:${p.lat},${p.lon}?q=${p.lat},${p.lon}(${label})`;
}
get visibleGeoLink() {
if (!this.args.selectedPlace) return '';
const p = this.args.selectedPlace;
return `geo:${p.lat},${p.lon}`;
}
@action
async toggleSave(place) {
if (!place) return;
if (place.createdAt) {
// It's a saved bookmark -> Delete it
if (confirm(`Delete "${place.title}"?`)) {
try {
if (place.id && place.geohash) {
await this.storage.places.remove(place.id, place.geohash);
console.log('Place deleted:', place.title);
await this.storage.removePlace(place);
console.log('Place deleted:', place.title);
// Notify parent to refresh map bookmarks
if (this.args.onBookmarkChange) {
this.args.onBookmarkChange();
}
// Notify parent to refresh map bookmarks
if (this.args.onBookmarkChange) {
this.args.onBookmarkChange();
}
// Update selection to the new saved place object
// This updates the local UI state immediately without a route refresh
if (this.args.onUpdate) {
// When deleting, we revert to a "fresh" object or just close.
// Since we close the sidebar below, we might not strictly need to update local state,
// but it's good practice.
// Reconstruct the "original" place without ID/Geohash/CreatedAt
const freshPlace = {
...place,
id: undefined,
geohash: undefined,
createdAt: undefined
};
this.args.onUpdate(freshPlace);
}
// Update selection to the new saved place object
// This updates the local UI state immediately without a route refresh
if (this.args.onUpdate) {
// When deleting, we revert to a "fresh" object or just close.
// Since we close the sidebar below, we might not strictly need to update local state,
// but it's good practice.
// Reconstruct the "original" place without ID/Geohash/CreatedAt
const freshPlace = {
...place,
id: undefined,
geohash: undefined,
createdAt: undefined
};
this.args.onUpdate(freshPlace);
}
// Also fire onSelect if it exists (for list view)
if (this.args.onSelect) {
// Similar logic for select if needed, but we usually close.
this.args.onSelect(null);
}
// Also fire onSelect if it exists (for list view)
if (this.args.onSelect) {
// Similar logic for select if needed, but we usually close.
this.args.onSelect(null);
}
// Close sidebar after delete
if (this.args.onClose) {
this.args.onClose();
}
} else {
alert('Cannot delete: Missing ID or Geohash');
// Close sidebar after delete
if (this.args.onClose) {
this.args.onClose();
}
} catch (e) {
console.error('Failed to delete:', e);
@@ -115,7 +91,7 @@ export default class PlacesSidebar extends Component {
};
try {
const savedPlace = await this.storage.places.store(placeData);
const savedPlace = await this.storage.storePlace(placeData);
console.log('Place saved:', placeData.title);
// Notify parent to refresh map bookmarks
@@ -148,7 +124,6 @@ export default class PlacesSidebar extends Component {
class="back-btn"
{{on "click" this.clearSelection}}
>←</button>
<h2>Details</h2>
{{else}}
<h2>Nearby Places</h2>
{{/if}}
@@ -161,9 +136,9 @@ export default class PlacesSidebar extends Component {
<div class="sidebar-content">
{{#if @selectedPlace}}
<PlaceDetails
@place={{@selectedPlace}}
@onToggleSave={{this.toggleSave}}
<PlaceDetails
@place={{@selectedPlace}}
@onToggleSave={{this.toggleSave}}
/>
{{else}}
{{#if @places}}

View File

@@ -9,17 +9,14 @@ export default class PlaceRoute extends Route {
async model(params) {
const id = params.place_id;
// Check for explicit OSM prefixes
if (id.startsWith('osm:node:') || id.startsWith('osm:way:')) {
const [, type, osmId] = id.split(':');
console.log(`Fetching explicit OSM ${type}:`, osmId);
return this.loadOsmPlace(osmId, type);
}
// Wait for storage sync before checking bookmarks
await this.waitForSync();
// 1. Try to find in local bookmarks
let bookmark = this.storage.findPlaceById(id);
if (bookmark) {
@@ -27,9 +24,8 @@ export default class PlaceRoute extends Route {
return bookmark;
}
// 2. Fallback: Fetch from OSM (assuming generic ID or old format)
console.log('Not in bookmarks, fetching from OSM:', id);
return this.loadOsmPlace(id);
console.warn('Not in bookmarks:', id);
return null;
}
async waitForSync() {

View File

@@ -23,7 +23,8 @@ export default class OsmService extends Service {
out center;
`.trim();
const url = `https://overpass-api.de/api/interpreter?data=${encodeURIComponent(
const url = `https://overpass.bke.ro/api/interpreter?data=${encodeURIComponent(
// const url = `https://overpass-api.de/api/interpreter?data=${encodeURIComponent(
query
)}`;

View File

@@ -178,12 +178,26 @@ export default class StorageService extends Service {
}
findPlaceById(id) {
// Search by internal ID first
let place = this.savedPlaces.find((p) => p.id === id);
if (!id) return undefined;
const strId = String(id);
// Search by internal ID first (loose comparison via string cast)
let place = this.savedPlaces.find((p) => p.id && String(p.id) === strId);
if (place) return place;
// Then search by OSM ID
place = this.savedPlaces.find((p) => p.osmId === id);
place = this.savedPlaces.find((p) => p.osmId && String(p.osmId) === strId);
return place;
}
async storePlace(placeData) {
const savedPlace = await this.places.store(placeData);
this.savedPlaces = [...this.savedPlaces, savedPlace];
return savedPlace;
}
async removePlace(place) {
await this.places.remove(place.id, place.geohash);
this.savedPlaces = this.savedPlaces.filter(p => p.id !== place.id);
}
}

View File

@@ -97,6 +97,10 @@ body {
.place-details .place-description {
}
.place-details .actions {
padding-bottom: 0.3rem;
}
.btn-primary {
background: #007bff;
color: white;
@@ -149,13 +153,16 @@ body {
}
.meta-info {
margin-top: 1.5rem;
padding-top: 1rem;
border-top: 1px solid #eee;
font-size: 0.9rem;
text-align: left;
}
.meta-info p:first-child {
margin-top: 1.2rem;
padding-top: 1.2rem;
border-top: 1px solid #eee;
}
.meta-info p {
margin: 0.75rem 0;
line-height: 1.4;
@@ -169,18 +176,13 @@ body {
.meta-info a {
color: #007bff;
text-decoration: none;
padding-bottom: 4rem;
}
.meta-info a:hover {
text-decoration: underline;
}
.meta-divider {
border: 0;
border-top: 1px dashed #ddd;
margin: 1rem 0;
}
/* Map Search Pulse Animation */
.search-pulse {
border-radius: 50%;

View File

@@ -23,20 +23,37 @@ export default class PlaceTemplate extends Component {
// Let's use a modifier or just sync it.
get place() {
// If we have a manually updated place (from save), use it.
// Otherwise use the route model.
// We need to ensure we reset `localPlace` when navigating to a NEW place.
// Comparing IDs is a safe bet.
// 1. Resolve the ID from the model (OSM ID or internal ID)
const model = this.args.model;
if (
this.localPlace &&
(this.localPlace.id === model.id || this.localPlace.osmId === model.osmId)
) {
// If the local place is "richer" (has createdAt), prefer it.
if (this.localPlace.createdAt && !model.createdAt) return this.localPlace;
// If we deleted it (local has no createdAt, model might?) - wait, if we delete, we close sidebar.
const id = model.osmId || model.id;
// 2. Check the storage service for a LIVE version of this bookmark
// This is the most critical fix: Storage is the source of truth.
// Since `this.storage.savedPlaces` is @tracked, this getter will re-compute
// whenever a bookmark is added or removed.
const saved = this.storage.findPlaceById(id);
if (saved) {
return saved;
}
// 3. If not saved, check our local "optimistic" state (from handleUpdate)
// This handles the "unsaved" state immediately after deletion before any other sync
if (this.localPlace && (this.localPlace.osmId === id || this.localPlace.id === id)) {
return this.localPlace;
}
// 4. Fallback to the route model (which might be the stale "saved" object from when the route loaded)
// If the model *has* a createdAt but we didn't find it in step 2 (storage),
// it means it was deleted. We must return a sanitized version.
if (model.createdAt) {
return {
...model,
id: undefined,
createdAt: undefined,
geohash: undefined
};
}
return model;
}

View File

@@ -1,6 +1,6 @@
{
"name": "marco",
"version": "1.4.0",
"version": "1.4.2",
"private": true,
"description": "Small description for marco goes here",
"repository": "",

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

@@ -6,8 +6,8 @@
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script type="module" crossorigin src="/assets/main-IB7GaxzK.js"></script>
<link rel="stylesheet" crossorigin href="/assets/main-B-vHK2y6.css">
<script type="module" crossorigin src="/assets/main-C6x36ClG.js"></script>
<link rel="stylesheet" crossorigin href="/assets/main-_X0dk-zm.css">
</head>
<body>
</body>