Allow editing of bookmarks/places
This commit is contained in:
parent
e8f7e74e40
commit
0d5a0325f4
@ -5,6 +5,7 @@ import arrowLeft from 'feather-icons/dist/icons/arrow-left.svg?raw';
|
||||
import activity from 'feather-icons/dist/icons/activity.svg?raw';
|
||||
import bookmark from 'feather-icons/dist/icons/bookmark.svg?raw';
|
||||
import clock from 'feather-icons/dist/icons/clock.svg?raw';
|
||||
import edit from 'feather-icons/dist/icons/edit.svg?raw';
|
||||
import globe from 'feather-icons/dist/icons/globe.svg?raw';
|
||||
import home from 'feather-icons/dist/icons/home.svg?raw';
|
||||
import logIn from 'feather-icons/dist/icons/log-in.svg?raw';
|
||||
@ -25,6 +26,7 @@ const ICONS = {
|
||||
activity,
|
||||
bookmark,
|
||||
clock,
|
||||
edit,
|
||||
globe,
|
||||
home,
|
||||
'log-in': logIn,
|
||||
|
||||
@ -4,7 +4,19 @@ import { on } from '@ember/modifier';
|
||||
import capitalize from '../helpers/capitalize';
|
||||
import Icon from '../components/icon';
|
||||
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { action } from '@ember/object';
|
||||
|
||||
export default class PlaceDetails extends Component {
|
||||
@tracked isEditing = false;
|
||||
@tracked editTitle = '';
|
||||
@tracked editDescription = '';
|
||||
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.resetEditFields();
|
||||
}
|
||||
|
||||
get place() {
|
||||
return this.args.place || {};
|
||||
}
|
||||
@ -22,6 +34,47 @@ export default class PlaceDetails extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
@action
|
||||
resetEditFields() {
|
||||
this.editTitle = this.name;
|
||||
this.editDescription = this.place.description || '';
|
||||
}
|
||||
|
||||
@action
|
||||
startEditing() {
|
||||
if (!this.place.createdAt) return; // Only allow editing saved places
|
||||
this.resetEditFields();
|
||||
this.isEditing = true;
|
||||
}
|
||||
|
||||
@action
|
||||
cancelEditing() {
|
||||
this.isEditing = false;
|
||||
}
|
||||
|
||||
@action
|
||||
async saveChanges(event) {
|
||||
event.preventDefault();
|
||||
if (this.args.onSave) {
|
||||
await this.args.onSave({
|
||||
...this.place,
|
||||
title: this.editTitle,
|
||||
description: this.editDescription,
|
||||
});
|
||||
}
|
||||
this.isEditing = false;
|
||||
}
|
||||
|
||||
@action
|
||||
updateTitle(e) {
|
||||
this.editTitle = e.target.value;
|
||||
}
|
||||
|
||||
@action
|
||||
updateDescription(e) {
|
||||
this.editDescription = e.target.value;
|
||||
}
|
||||
|
||||
get type() {
|
||||
return (
|
||||
this.tags.amenity ||
|
||||
@ -117,14 +170,41 @@ export default class PlaceDetails extends Component {
|
||||
|
||||
<template>
|
||||
<div class="place-details">
|
||||
<h3>{{this.name}}</h3>
|
||||
<p class="place-type">
|
||||
{{this.type}}
|
||||
</p>
|
||||
{{#if this.place.description}}
|
||||
<p class="place-description">
|
||||
{{this.place.description}}
|
||||
{{#if this.isEditing}}
|
||||
<form class="edit-form" {{on "submit" this.saveChanges}}>
|
||||
<div class="form-group">
|
||||
<label>Title</label>
|
||||
<input
|
||||
type="text"
|
||||
value={{this.editTitle}}
|
||||
{{on "input" this.updateTitle}}
|
||||
class="form-control"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Description</label>
|
||||
<textarea
|
||||
value={{this.editDescription}}
|
||||
{{on "input" this.updateDescription}}
|
||||
class="form-control"
|
||||
rows="3"
|
||||
></textarea>
|
||||
</div>
|
||||
<div class="edit-actions">
|
||||
<button type="submit" class="btn btn-blue btn-sm">Save</button>
|
||||
<button type="button" class="btn btn-outline btn-sm" {{on "click" this.cancelEditing}}>Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
{{else}}
|
||||
<h3>{{this.name}}</h3>
|
||||
<p class="place-type">
|
||||
{{this.type}}
|
||||
</p>
|
||||
{{#if this.place.description}}
|
||||
<p class="place-description">
|
||||
{{this.place.description}}
|
||||
</p>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
<div class="actions">
|
||||
@ -143,6 +223,18 @@ export default class PlaceDetails extends Component {
|
||||
/>
|
||||
{{if this.place.createdAt "Saved" "Save"}}
|
||||
</button>
|
||||
|
||||
{{#if this.place.createdAt}}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline"
|
||||
title="Edit"
|
||||
{{on "click" this.startEditing}}
|
||||
>
|
||||
<Icon @name="edit" @color="#007bff" />
|
||||
Edit
|
||||
</button>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="meta-info">
|
||||
|
||||
@ -117,6 +117,27 @@ export default class PlacesSidebar extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
async updateBookmark(updatedPlace) {
|
||||
try {
|
||||
const savedPlace = await this.storage.updatePlace(updatedPlace);
|
||||
console.log('Place updated:', savedPlace.title);
|
||||
|
||||
// Notify parent to refresh map/lists
|
||||
if (this.args.onBookmarkChange) {
|
||||
this.args.onBookmarkChange();
|
||||
}
|
||||
|
||||
// Update local view
|
||||
if (this.args.onUpdate) {
|
||||
this.args.onUpdate(savedPlace);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to update place:', e);
|
||||
alert('Failed to update place: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
<template>
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-header">
|
||||
@ -141,6 +162,7 @@ export default class PlacesSidebar extends Component {
|
||||
<PlaceDetails
|
||||
@place={{@selectedPlace}}
|
||||
@onToggleSave={{this.toggleSave}}
|
||||
@onSave={{this.updateBookmark}}
|
||||
/>
|
||||
{{else}}
|
||||
{{#if @places}}
|
||||
|
||||
@ -30,7 +30,6 @@ export default class StorageService extends Service {
|
||||
});
|
||||
|
||||
this.rs.access.claim('places', 'rw');
|
||||
// Caching strategy:
|
||||
this.rs.caching.enable('/places/');
|
||||
|
||||
window.remoteStorage = this.rs;
|
||||
@ -49,11 +48,6 @@ export default class StorageService extends Service {
|
||||
console.debug('Remote storage connected');
|
||||
this.connected = true;
|
||||
this.userAddress = this.rs.remote.userAddress;
|
||||
|
||||
// Close widget after successful connection (respecting autoCloseAfter)
|
||||
setTimeout(() => {
|
||||
this.isWidgetOpen = false;
|
||||
}, 1500);
|
||||
});
|
||||
|
||||
this.rs.on('disconnected', () => {
|
||||
@ -222,7 +216,23 @@ export default class StorageService extends Service {
|
||||
|
||||
async storePlace(placeData) {
|
||||
const savedPlace = await this.places.store(placeData);
|
||||
this.savedPlaces = [...this.savedPlaces, savedPlace];
|
||||
// Only append if not already there (handlePlaceChange might also fire)
|
||||
if (!this.savedPlaces.some((p) => p.id === savedPlace.id)) {
|
||||
this.savedPlaces = [...this.savedPlaces, savedPlace];
|
||||
}
|
||||
return savedPlace;
|
||||
}
|
||||
|
||||
async updatePlace(placeData) {
|
||||
const savedPlace = await this.places.store(placeData);
|
||||
|
||||
// Update local list
|
||||
const index = this.savedPlaces.findIndex((p) => p.id === savedPlace.id);
|
||||
if (index !== -1) {
|
||||
const newPlaces = [...this.savedPlaces];
|
||||
newPlaces[index] = savedPlace;
|
||||
this.savedPlaces = newPlaces;
|
||||
}
|
||||
return savedPlace;
|
||||
}
|
||||
|
||||
|
||||
@ -223,6 +223,64 @@ body {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.edit-form {
|
||||
margin: -1rem;
|
||||
margin-bottom: 1rem;
|
||||
background: #f8f9fa;
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
font-size: 0.85rem;
|
||||
color: #666;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-family: inherit;
|
||||
font-size: 0.95rem;
|
||||
box-sizing: border-box; /* Ensure padding doesn't overflow width */
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
outline: none;
|
||||
border-color: #007bff;
|
||||
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.1);
|
||||
}
|
||||
|
||||
.edit-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.btn-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.75rem;
|
||||
border-radius: 4px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #0069d9;
|
||||
}
|
||||
.back-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
@ -262,13 +320,14 @@ body {
|
||||
}
|
||||
|
||||
.place-details .place-description {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.place-details .actions {
|
||||
padding-bottom: 0.3rem;
|
||||
/* display: flex; */
|
||||
/* flex-direction: row; */
|
||||
/* gap: 1rem; */
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
@ -283,6 +342,11 @@ body {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: 0.4rem 1rem !important;
|
||||
font-size: 0.9rem !important;
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background: transparent;
|
||||
color: #333;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user