Compare commits
8 Commits
b2220b8310
...
v1.14.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
3b71531de2
|
|||
|
6ef7549ea9
|
|||
|
9097c63a55
|
|||
|
ec0d5a30f9
|
|||
|
f1779131e8
|
|||
|
37cf47b3dd
|
|||
|
ff68b5addc
|
|||
|
990f3afa88
|
@@ -1,65 +1,10 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { htmlSafe } from '@ember/template';
|
||||
|
||||
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 facebook from 'feather-icons/dist/icons/facebook.svg?raw';
|
||||
import globe from 'feather-icons/dist/icons/globe.svg?raw';
|
||||
import home from 'feather-icons/dist/icons/home.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';
|
||||
import mail from 'feather-icons/dist/icons/mail.svg?raw';
|
||||
import map from 'feather-icons/dist/icons/map.svg?raw';
|
||||
import mapPin from 'feather-icons/dist/icons/map-pin.svg?raw';
|
||||
import menu from 'feather-icons/dist/icons/menu.svg?raw';
|
||||
import navigation from 'feather-icons/dist/icons/navigation.svg?raw';
|
||||
import phone from 'feather-icons/dist/icons/phone.svg?raw';
|
||||
import plus from 'feather-icons/dist/icons/plus.svg?raw';
|
||||
import server from 'feather-icons/dist/icons/server.svg?raw';
|
||||
import search from 'feather-icons/dist/icons/search.svg?raw';
|
||||
import settings from 'feather-icons/dist/icons/settings.svg?raw';
|
||||
import target from 'feather-icons/dist/icons/target.svg?raw';
|
||||
import user from 'feather-icons/dist/icons/user.svg?raw';
|
||||
import x from 'feather-icons/dist/icons/x.svg?raw';
|
||||
import zap from 'feather-icons/dist/icons/zap.svg?raw';
|
||||
import wikipedia from '../icons/wikipedia.svg?raw';
|
||||
|
||||
const ICONS = {
|
||||
'arrow-left': arrowLeft,
|
||||
activity,
|
||||
bookmark,
|
||||
clock,
|
||||
edit,
|
||||
facebook,
|
||||
globe,
|
||||
home,
|
||||
instagram,
|
||||
'log-in': logIn,
|
||||
'log-out': logOut,
|
||||
mail,
|
||||
map,
|
||||
'map-pin': mapPin,
|
||||
menu,
|
||||
navigation,
|
||||
phone,
|
||||
plus,
|
||||
server,
|
||||
search,
|
||||
settings,
|
||||
target,
|
||||
user,
|
||||
wikipedia,
|
||||
x,
|
||||
zap,
|
||||
};
|
||||
import { getIcon } from '../utils/icons';
|
||||
|
||||
export default class IconComponent extends Component {
|
||||
get svg() {
|
||||
return ICONS[this.args.name];
|
||||
return getIcon(this.args.name);
|
||||
}
|
||||
|
||||
get size() {
|
||||
|
||||
@@ -60,10 +60,13 @@ export default class MapComponent extends Component {
|
||||
|
||||
// Create a vector source and layer for bookmarks
|
||||
this.bookmarkSource = new VectorSource();
|
||||
|
||||
|
||||
const bookmarkStyleFunction = (feature) => {
|
||||
const originalPlace = feature.get('originalPlace');
|
||||
let color = '#ffcc33'; // Default Yellow
|
||||
let color =
|
||||
getComputedStyle(document.documentElement)
|
||||
.getPropertyValue('--default-list-color')
|
||||
.trim() || '#000000'; // Fallback to black if variable is missing to make error obvious
|
||||
|
||||
if (
|
||||
originalPlace &&
|
||||
@@ -71,7 +74,7 @@ export default class MapComponent extends Component {
|
||||
originalPlace._listIds.length > 0
|
||||
) {
|
||||
// Find the first list color
|
||||
// We need access to storage.lists.
|
||||
// We need access to storage.lists.
|
||||
// Since this is inside setupMap, 'this' refers to the component instance.
|
||||
const firstListId = originalPlace._listIds[0];
|
||||
const list = this.storage.lists.find((l) => l.id === firstListId);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { fn } from '@ember/helper';
|
||||
import { service } from '@ember/service';
|
||||
import { on } from '@ember/modifier';
|
||||
import { htmlSafe } from '@ember/template';
|
||||
import { humanizeOsmTag } from '../utils/format-text';
|
||||
@@ -14,9 +14,14 @@ import { tracked } from '@glimmer/tracking';
|
||||
import { action } from '@ember/object';
|
||||
|
||||
export default class PlaceDetails extends Component {
|
||||
@service storage;
|
||||
@tracked isEditing = false;
|
||||
@tracked showLists = false;
|
||||
|
||||
get isSaved() {
|
||||
return this.storage.isPlaceSaved(this.place.id || this.place.osmId);
|
||||
}
|
||||
|
||||
get place() {
|
||||
return this.args.place || {};
|
||||
}
|
||||
@@ -39,7 +44,7 @@ export default class PlaceDetails extends Component {
|
||||
|
||||
@action
|
||||
startEditing() {
|
||||
if (!this.place.createdAt) return; // Only allow editing saved places
|
||||
if (!this.isSaved) return; // Only allow editing saved places
|
||||
this.isEditing = true;
|
||||
}
|
||||
|
||||
@@ -276,29 +281,26 @@ export default class PlaceDetails extends Component {
|
||||
<div class="save-button-wrapper">
|
||||
<button
|
||||
type="button"
|
||||
class={{if
|
||||
this.place.createdAt
|
||||
"btn btn-secondary"
|
||||
"btn btn-outline"
|
||||
}}
|
||||
class={{if this.isSaved "btn btn-secondary" "btn btn-outline"}}
|
||||
{{on "click" this.toggleLists}}
|
||||
>
|
||||
<Icon
|
||||
@name="bookmark"
|
||||
@color={{if this.place.createdAt "currentColor" "#007bff"}}
|
||||
@color={{if this.isSaved "currentColor" "#007bff"}}
|
||||
/>
|
||||
{{if this.place.createdAt "Saved" "Save"}}
|
||||
{{if this.isSaved "Saved" "Save"}}
|
||||
</button>
|
||||
|
||||
{{#if this.showLists}}
|
||||
<PlaceListsManager
|
||||
@place={{this.saveablePlace}}
|
||||
@onClose={{this.closeLists}}
|
||||
@isSaved={{this.isSaved}}
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#if this.place.createdAt}}
|
||||
{{#if this.isSaved}}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline"
|
||||
|
||||
@@ -1,22 +1,30 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { on } from '@ember/modifier';
|
||||
import { fn } from '@ember/helper';
|
||||
import Icon from './icon';
|
||||
import { htmlSafe } from '@ember/template';
|
||||
import onClickOutside from '../modifiers/on-click-outside';
|
||||
|
||||
export default class PlaceListsManager extends Component {
|
||||
@service storage;
|
||||
@service router;
|
||||
@tracked _forceClear = false;
|
||||
|
||||
get isSaved() {
|
||||
return !!this.args.place.createdAt;
|
||||
return this.args.isSaved;
|
||||
}
|
||||
|
||||
get placeListIds() {
|
||||
if (this._forceClear) return [];
|
||||
return this.args.place._listIds || [];
|
||||
}
|
||||
|
||||
styleFor(color) {
|
||||
return htmlSafe(`background-color: ${color}`);
|
||||
}
|
||||
|
||||
@action
|
||||
isInList(list) {
|
||||
if (!this.placeListIds) return false;
|
||||
@@ -26,7 +34,35 @@ export default class PlaceListsManager extends Component {
|
||||
@action
|
||||
async toggleSaved() {
|
||||
if (this.isSaved) {
|
||||
const { osmId, osmType } = this.args.place;
|
||||
|
||||
await this.storage.removePlace(this.args.place);
|
||||
|
||||
// Clean up the local object reference immediately to prevent UI flicker
|
||||
// or stale state if the transition is delayed/cancelled.
|
||||
if (this.args.place) {
|
||||
this.args.place.id = null;
|
||||
this.args.place.createdAt = null;
|
||||
this.args.place._listIds = [];
|
||||
this._forceClear = true;
|
||||
}
|
||||
|
||||
// Transition immediately to the canonical state
|
||||
if (osmId && osmType) {
|
||||
// Create a transient copy that looks like a fresh OSM result
|
||||
const rawPlace = { ...this.args.place };
|
||||
delete rawPlace.id;
|
||||
delete rawPlace.createdAt;
|
||||
delete rawPlace._listIds;
|
||||
|
||||
// Transition to the place route using the raw object
|
||||
// This updates the URL to 'osm:...' and renders immediately
|
||||
this.router.transitionTo('place', rawPlace);
|
||||
} else {
|
||||
// Custom place deleted -> go home
|
||||
this.router.transitionTo('index');
|
||||
}
|
||||
|
||||
if (this.args.onClose) this.args.onClose();
|
||||
} else {
|
||||
await this.storage.storePlace(this.args.place);
|
||||
@@ -59,10 +95,7 @@ export default class PlaceListsManager extends Component {
|
||||
}
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="place-lists-manager"
|
||||
{{onClickOutside @onClose}}
|
||||
>
|
||||
<div class="place-lists-manager" {{onClickOutside @onClose}}>
|
||||
<div class="list-item master-toggle">
|
||||
<label>
|
||||
<input
|
||||
@@ -70,7 +103,8 @@ export default class PlaceListsManager extends Component {
|
||||
checked={{this.isSaved}}
|
||||
{{on "change" this.toggleSaved}}
|
||||
/>
|
||||
<span class="list-name">Saved</span>
|
||||
<span class="list-color"></span>
|
||||
<span class="list-name">Saved places</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -86,7 +120,11 @@ export default class PlaceListsManager extends Component {
|
||||
{{on "change" (fn this.toggleList list)}}
|
||||
disabled={{unless this.isSaved true}}
|
||||
/>
|
||||
<span class="list-color" style="background-color: {{list.color}}"></span>
|
||||
{{! template-lint-disable no-inline-styles }}
|
||||
<span
|
||||
class="list-color"
|
||||
style={{this.styleFor list.color}}
|
||||
></span>
|
||||
<span class="list-name">{{list.title}}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -50,6 +50,10 @@ export default class StorageService extends Service {
|
||||
this.loadLists();
|
||||
});
|
||||
|
||||
this.rs.on('not-connected', () => {
|
||||
this.loadLists();
|
||||
});
|
||||
|
||||
this.rs.on('disconnected', () => {
|
||||
this.connected = false;
|
||||
this.userAddress = null;
|
||||
@@ -137,6 +141,16 @@ export default class StorageService extends Service {
|
||||
|
||||
const lists = await this.places.lists.getAll();
|
||||
this.lists = lists || [];
|
||||
|
||||
// Decorate with hardcoded icons for default lists (in-memory only)
|
||||
this.lists.forEach((list) => {
|
||||
if (list.id === 'to-go') {
|
||||
list.icon = 'bookmark';
|
||||
} else if (list.id === 'to-do') {
|
||||
list.icon = 'check-square';
|
||||
}
|
||||
});
|
||||
|
||||
this.refreshPlaceListAssociations();
|
||||
} catch (e) {
|
||||
console.error('Failed to load lists:', e);
|
||||
@@ -282,11 +296,22 @@ export default class StorageService extends Service {
|
||||
let place = this.savedPlaces.find((p) => p.id && String(p.id) === strId);
|
||||
if (place) return place;
|
||||
|
||||
// Check placesInView as fallback
|
||||
place = this.placesInView.find((p) => p.id && String(p.id) === strId);
|
||||
if (place) return place;
|
||||
|
||||
// Then search by OSM ID
|
||||
place = this.savedPlaces.find((p) => p.osmId && String(p.osmId) === strId);
|
||||
if (place) return place;
|
||||
|
||||
place = this.placesInView.find((p) => p.osmId && String(p.osmId) === strId);
|
||||
return place;
|
||||
}
|
||||
|
||||
isPlaceSaved(id) {
|
||||
return !!this.findPlaceById(id);
|
||||
}
|
||||
|
||||
async storePlace(placeData) {
|
||||
const savedPlace = await this.places.store(placeData);
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
/* Ember supports plain CSS out of the box. More info: https://cli.emberjs.com/release/advanced-use/stylesheets/ */
|
||||
|
||||
:root {
|
||||
--default-list-color: #fc3;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
@@ -1021,7 +1025,7 @@ button.create-place {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.place-lists-manager input[type="checkbox"] {
|
||||
.place-lists-manager input[type='checkbox'] {
|
||||
accent-color: #007bff;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
@@ -1031,9 +1035,10 @@ button.create-place {
|
||||
.place-lists-manager .list-color {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background-color: var(--default-list-color);
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
border: 1px solid rgba(0,0,0,0.1);
|
||||
border: 1px solid rgb(0 0 0 / 10%);
|
||||
}
|
||||
|
||||
.place-lists-manager .divider {
|
||||
|
||||
61
app/utils/icons.js
Normal file
61
app/utils/icons.js
Normal file
@@ -0,0 +1,61 @@
|
||||
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 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 globe from 'feather-icons/dist/icons/globe.svg?raw';
|
||||
import home from 'feather-icons/dist/icons/home.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';
|
||||
import mail from 'feather-icons/dist/icons/mail.svg?raw';
|
||||
import map from 'feather-icons/dist/icons/map.svg?raw';
|
||||
import mapPin from 'feather-icons/dist/icons/map-pin.svg?raw';
|
||||
import menu from 'feather-icons/dist/icons/menu.svg?raw';
|
||||
import navigation from 'feather-icons/dist/icons/navigation.svg?raw';
|
||||
import phone from 'feather-icons/dist/icons/phone.svg?raw';
|
||||
import plus from 'feather-icons/dist/icons/plus.svg?raw';
|
||||
import server from 'feather-icons/dist/icons/server.svg?raw';
|
||||
import search from 'feather-icons/dist/icons/search.svg?raw';
|
||||
import settings from 'feather-icons/dist/icons/settings.svg?raw';
|
||||
import target from 'feather-icons/dist/icons/target.svg?raw';
|
||||
import user from 'feather-icons/dist/icons/user.svg?raw';
|
||||
import x from 'feather-icons/dist/icons/x.svg?raw';
|
||||
import zap from 'feather-icons/dist/icons/zap.svg?raw';
|
||||
import wikipedia from '../icons/wikipedia.svg?raw';
|
||||
|
||||
const ICONS = {
|
||||
'arrow-left': arrowLeft,
|
||||
activity,
|
||||
bookmark,
|
||||
'check-square': checkSquare,
|
||||
clock,
|
||||
edit,
|
||||
facebook,
|
||||
globe,
|
||||
home,
|
||||
instagram,
|
||||
'log-in': logIn,
|
||||
'log-out': logOut,
|
||||
mail,
|
||||
map,
|
||||
'map-pin': mapPin,
|
||||
menu,
|
||||
navigation,
|
||||
phone,
|
||||
plus,
|
||||
server,
|
||||
search,
|
||||
settings,
|
||||
target,
|
||||
user,
|
||||
wikipedia,
|
||||
x,
|
||||
zap,
|
||||
};
|
||||
|
||||
export function getIcon(name) {
|
||||
return ICONS[name];
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "marco",
|
||||
"version": "1.13.3",
|
||||
"version": "1.14.0",
|
||||
"private": true,
|
||||
"description": "Unhosted maps app",
|
||||
"repository": {
|
||||
@@ -52,7 +52,7 @@
|
||||
"@embroider/vite": "^1.5.0",
|
||||
"@eslint/js": "^9.39.2",
|
||||
"@glimmer/component": "^2.0.0",
|
||||
"@remotestorage/module-places": "1.x",
|
||||
"@remotestorage/module-places": "~1.2.1",
|
||||
"@rollup/plugin-babel": "^6.1.0",
|
||||
"@warp-drive/core": "~5.8.0",
|
||||
"@warp-drive/ember": "~5.8.0",
|
||||
|
||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
@@ -55,8 +55,8 @@ importers:
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0
|
||||
'@remotestorage/module-places':
|
||||
specifier: 1.x
|
||||
version: 1.0.0
|
||||
specifier: ~1.2.1
|
||||
version: 1.2.1
|
||||
'@rollup/plugin-babel':
|
||||
specifier: ^6.1.0
|
||||
version: 6.1.0(@babel/core@7.28.6)(rollup@4.55.1)
|
||||
@@ -1380,8 +1380,8 @@ packages:
|
||||
resolution: {integrity: sha512-4rdu8GPY9TeQwsYp5D2My74dC3dSVS3tghAvisG80ybK4lqa0gvlrglaSTBxogJbxqHRw/NjI/liEtb3+SD+Bw==}
|
||||
engines: {node: '>=18.12'}
|
||||
|
||||
'@remotestorage/module-places@1.0.0':
|
||||
resolution: {integrity: sha512-vaqJeTw658gjPyLz70Mq2AbGfDZ66O2mpDFME+gtaGFYl2+UvrvRLCrXWHYuyTE21f3TJdegeXM6C5nZMxLv9A==}
|
||||
'@remotestorage/module-places@1.2.1':
|
||||
resolution: {integrity: sha512-hNRuhGoG8RS+cieVvDVzXWBEuNPfyeFirhgNH3z1WoKw9ngHdPY6V0sT0vKbsxB8xaODReZfo2ZKHLTmdFunlw==}
|
||||
|
||||
'@rollup/plugin-babel@6.1.0':
|
||||
resolution: {integrity: sha512-dFZNuFD2YRcoomP4oYf+DvQNSUA9ih+A3vUqopQx5EdtPGo3WBnQcI/S8pwpz91UsGfL0HsMSOlaMld8HrbubA==}
|
||||
@@ -7002,7 +7002,7 @@ snapshots:
|
||||
'@pnpm/error': 1000.0.5
|
||||
find-up: 5.0.0
|
||||
|
||||
'@remotestorage/module-places@1.0.0':
|
||||
'@remotestorage/module-places@1.2.1':
|
||||
dependencies:
|
||||
latlon-geohash: 2.0.0
|
||||
ulid: 3.0.2
|
||||
|
||||
1
release/assets/main-BT0n1kYB.css
Normal file
1
release/assets/main-BT0n1kYB.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
2
release/assets/main-GynTgP18.js
Normal file
2
release/assets/main-GynTgP18.js
Normal file
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
@@ -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-gjk9d6Ld.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/main-DAo4Q0R2.css">
|
||||
<script type="module" crossorigin src="/assets/main-GynTgP18.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/main-BT0n1kYB.css">
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
|
||||
@@ -42,6 +42,9 @@ class MockStorageService extends Service {
|
||||
findPlaceById() {
|
||||
return null;
|
||||
}
|
||||
isPlaceSaved() {
|
||||
return false;
|
||||
}
|
||||
loadPlacesInBounds() {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -41,6 +41,9 @@ module('Acceptance | search', function (hooks) {
|
||||
findPlaceById() {
|
||||
return null;
|
||||
}
|
||||
isPlaceSaved() {
|
||||
return false;
|
||||
}
|
||||
rs = {
|
||||
on: () => {},
|
||||
};
|
||||
@@ -85,6 +88,9 @@ module('Acceptance | search', function (hooks) {
|
||||
findPlaceById() {
|
||||
return null;
|
||||
}
|
||||
isPlaceSaved() {
|
||||
return false;
|
||||
}
|
||||
rs = {
|
||||
on: () => {},
|
||||
};
|
||||
@@ -130,6 +136,9 @@ module('Acceptance | search', function (hooks) {
|
||||
if (id === '999') return this.savedPlaces[0];
|
||||
return null;
|
||||
}
|
||||
isPlaceSaved(id) {
|
||||
return !!this.findPlaceById(id);
|
||||
}
|
||||
rs = {
|
||||
on: () => {},
|
||||
};
|
||||
|
||||
@@ -9,10 +9,18 @@ module('Integration | Component | place-details', function (hooks) {
|
||||
|
||||
class StorageService extends Service {
|
||||
lists = [
|
||||
{ id: 'to-go', title: 'Want to go', color: '#ff00ff' },
|
||||
{ id: 'to-do', title: 'To do', color: '#008000' },
|
||||
{ id: 'to-go', title: 'Want to go', color: '#2e9e4f' },
|
||||
{ id: 'to-do', title: 'To do', color: '#2a7fff' },
|
||||
];
|
||||
|
||||
isPlaceSaved() {
|
||||
return false;
|
||||
}
|
||||
|
||||
findPlaceById() {
|
||||
return null;
|
||||
}
|
||||
|
||||
async storePlace(place) {
|
||||
return { ...place, id: '123', createdAt: new Date().toISOString() };
|
||||
}
|
||||
@@ -28,6 +36,12 @@ module('Integration | Component | place-details', function (hooks) {
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.owner.register('service:storage', StorageService);
|
||||
|
||||
// Mock Router for all tests
|
||||
class MockRouter extends Service {
|
||||
transitionTo() {}
|
||||
}
|
||||
this.owner.register('service:router', MockRouter);
|
||||
});
|
||||
|
||||
test('it formats coordinates correctly', async function (assert) {
|
||||
@@ -95,6 +109,12 @@ module('Integration | Component | place-details', function (hooks) {
|
||||
storedPlace = place;
|
||||
return { ...place, id: 'new-id', createdAt: new Date().toISOString() };
|
||||
}
|
||||
isPlaceSaved() {
|
||||
return false;
|
||||
}
|
||||
findPlaceById() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
this.owner.register('service:storage', MockStorage);
|
||||
|
||||
@@ -126,12 +146,15 @@ module('Integration | Component | place-details', function (hooks) {
|
||||
});
|
||||
|
||||
test('it handles removing a saved place via master toggle', async function (assert) {
|
||||
let removedPlace = null;
|
||||
let removedPlaceId = null;
|
||||
|
||||
class MockStorage extends Service {
|
||||
lists = [];
|
||||
async removePlace(place) {
|
||||
removedPlace = place;
|
||||
removedPlaceId = place.id;
|
||||
}
|
||||
isPlaceSaved() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
this.owner.register('service:storage', MockStorage);
|
||||
@@ -160,7 +183,9 @@ module('Integration | Component | place-details', function (hooks) {
|
||||
// Click it to remove
|
||||
await click(masterToggle);
|
||||
|
||||
assert.strictEqual(removedPlace.id, 'saved-id', 'removePlace was called');
|
||||
assert.strictEqual(removedPlaceId, 'saved-id', 'removePlace was called');
|
||||
|
||||
assert.deepEqual(place._listIds, [], '_listIds was cleared on the object');
|
||||
});
|
||||
|
||||
test('it adds place to a list', async function (assert) {
|
||||
@@ -175,6 +200,9 @@ module('Integration | Component | place-details', function (hooks) {
|
||||
listId = id;
|
||||
shouldAdd = add;
|
||||
}
|
||||
isPlaceSaved() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
this.owner.register('service:storage', MockStorage);
|
||||
|
||||
@@ -202,4 +230,29 @@ module('Integration | Component | place-details', function (hooks) {
|
||||
assert.strictEqual(placeArg.id, 'p1');
|
||||
assert.true(shouldAdd);
|
||||
});
|
||||
|
||||
test('it respects storage service state over stale place object', async function (assert) {
|
||||
class MockStorage extends Service {
|
||||
lists = [];
|
||||
isPlaceSaved() {
|
||||
return false;
|
||||
}
|
||||
findPlaceById() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
this.owner.register('service:storage', MockStorage);
|
||||
|
||||
const place = {
|
||||
id: 'stale-id',
|
||||
title: 'Stale Place',
|
||||
createdAt: '2023-01-01', // Looks saved
|
||||
};
|
||||
|
||||
await render(<template><PlaceDetails @place={{place}} /></template>);
|
||||
|
||||
// Button should say "Save", not "Saved" because isPlaceSaved returns false
|
||||
assert.dom('.actions button').hasText('Save');
|
||||
assert.dom('.actions button').doesNotHaveClass('btn-secondary');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user