Rename Collections, add special list for all saved #99

Merged
raucao merged 3 commits from feature/saved_places into master 2026-09-03 19:49:07 +00:00
5 changed files with 124 additions and 25 deletions
+1 -1
View File
@@ -22,7 +22,7 @@ import iconRounded from '../../icons/icon-rounded.svg?raw';
<li>
<button type="button" {{on "click" @onSavedPlaces}}>
<Icon @name="bookmark" @size={{20}} />
<span>Collections</span>
<span>Saved Places</span>
</button>
</li>
<li>
+18
View File
@@ -14,6 +14,8 @@ function getPlaceTime(place) {
return isNaN(parsed) ? 0 : parsed;
}
const SAVED_LIST_ID = 'saved';
export default class ListsListController extends Controller {
@service router;
@service mapUi;
@@ -41,6 +43,12 @@ export default class ListsListController extends Controller {
}
get listColor() {
if (this.listId === SAVED_LIST_ID) {
return getComputedStyle(document.documentElement)
.getPropertyValue('--default-list-color')
.trim();
}
const list = this.storage.lists.find((l) => l.id === this.listId);
if (list && list.color) {
return list.color;
@@ -51,11 +59,21 @@ export default class ListsListController extends Controller {
}
get listTitle() {
if (this.listId === SAVED_LIST_ID) {
return 'Saved Places';
}
const list = this.storage.lists.find((l) => l.id === this.listId);
return list ? list.title : 'Collections';
}
get places() {
if (this.listId === SAVED_LIST_ID) {
return [...this.storage.savedPlaces].sort(
(a, b) => getPlaceTime(b) - getPlaceTime(a)
);
}
const currentList = this.storage.lists.find((l) => l.id === this.listId);
const placeRefsIds = new Set(
currentList?.placeRefs?.map((ref) => ref.id) || []
+5 -9
View File
@@ -1,26 +1,22 @@
import Route from '@ember/routing/route';
import { service } from '@ember/service';
export default class ListsListRoute extends Route {
@service storage;
model(params) {
// Resolve instantly so transition happens in 0ms!
return { list_id: params.list_id };
}
setupController(controller, model) {
console.debug('DEBUG: setupController controller is:', controller);
console.debug(
'DEBUG: controller.loadPlacesTask is:',
controller?.loadPlacesTask
);
controller.model = model;
super.setupController(controller, model);
if (model.list_id === 'saved') {
return;
}
if (controller && controller.loadPlacesTask) {
controller.loadPlacesTask.perform(model.list_id);
} else {
console.error('DEBUG: ERROR! controller.loadPlacesTask is undefined!');
console.error('controller.loadPlacesTask is undefined');
}
}
}
+31 -1
View File
@@ -11,6 +11,12 @@ export default class ListsIndexTemplate extends Component {
@service router;
@service mapUi;
savedList = {
id: 'saved',
title: 'Saved',
color: 'var(--default-list-color)',
};
styleFor(color) {
const finalColor =
color ||
@@ -46,7 +52,7 @@ export default class ListsIndexTemplate extends Component {
<span class="sidebar-header-icon-wrapper">
<Icon @name="bookmark" @size={{20}} @color="#898989" />
</span>
Collections
Saved Places
</h2>
<button type="button" class="close-btn" {{on "click" this.close}}>
<Icon @name="x" @size={{20}} @color="#333" />
@@ -55,6 +61,30 @@ export default class ListsIndexTemplate extends Component {
<div class="sidebar-content">
<ul class="places-list">
<li>
<button
type="button"
class="lists-index-item"
{{on "click" (fn this.selectList this.savedList.id)}}
>
<div class="lists-index-item-left">
{{! template-lint-disable no-inline-styles }}
<span
class="list-color-dot"
style={{this.styleFor this.savedList.color}}
></span>
<div class="lists-index-name">{{this.savedList.title}}</div>
</div>
<div class="lists-index-count">
{{#if this.storage.savedPlaces.length}}
{{this.storage.savedPlaces.length}}
places
{{else}}
empty
{{/if}}
</div>
</button>
</li>
{{#each this.storage.lists as |list|}}
<li>
<button
+69 -14
View File
@@ -16,8 +16,16 @@ class MockStorageService extends Service {
id: 'place-123',
title: 'Mountain Trail',
geohash: 'u33dc0',
createdAt: '2023-01-02T12:00:00.000Z',
osmTags: { name: 'Mountain Trail' },
},
{
id: 'place-456',
title: 'Beach View',
geohash: 'u33dc1',
createdAt: '2023-01-03T12:00:00.000Z',
osmTags: { name: 'Beach View' },
},
];
lists = [
{
@@ -74,27 +82,56 @@ module('Acceptance | collections navigation', function (hooks) {
assert.dom('.sidebar.app-menu-pane').exists('App menu sidebar is open');
assert
.dom('.app-menu')
.includesText('Collections', 'Menu contains Collections link');
.includesText('Saved Places', 'Menu contains Saved Places link');
// 3. Transition to Collections Index (List of lists)
await click(document.querySelectorAll('.app-menu button')[0]); // Click "Collections"
// 3. Transition to Saved Places index (list of lists)
await click(document.querySelectorAll('.app-menu button')[0]); // Click "Saved Places"
assert.strictEqual(currentURL(), '/lists', 'Transitions to /lists index');
assert
.dom('.sidebar-header-text-centered')
.includesText('Collections', 'Header is centered and titled Collections');
.includesText(
'Saved Places',
'Header is centered and titled Saved Places'
);
assert
.dom('.lists-index-item')
.exists({ count: 2 }, 'Renders our 2 mocked list items');
.exists({ count: 3 }, 'Renders Saved plus our 2 mocked list items');
assert
.dom(document.querySelectorAll('.lists-index-item')[0])
.includesText('Saved', 'Saved appears as the first list item');
assert
.dom(document.querySelectorAll('.lists-index-item')[0])
.includesText('2 places', 'Saved shows the total saved places count');
// 4. Transition to a specific list (Want to go)
await click(document.querySelectorAll('.lists-index-item')[0]); // Click "Want to go"
// 4. Transition to the synthetic Saved list
await click(document.querySelectorAll('.lists-index-item')[0]);
assert.strictEqual(
currentURL(),
'/lists/saved',
'Transitions to /lists/saved'
);
await waitFor('.places-list');
assert
.dom(document.querySelectorAll('.places-list .place-name')[0])
.hasText('Beach View', 'Saved Places shows newest saved place first');
assert
.dom(document.querySelectorAll('.places-list .place-name')[1])
.hasText('Mountain Trail', 'Saved Places includes all saved places');
// 5. Go back to the Saved Places index
await click('.sidebar-header .back-btn');
assert.strictEqual(currentURL(), '/lists', 'Goes back to /lists index');
// 6. Transition to a specific real list (Want to go)
await click(document.querySelectorAll('.lists-index-item')[1]); // Click "Want to go"
assert.strictEqual(
currentURL(),
'/lists/to-go',
'Transitions instantly to /lists/to-go'
);
// 5. Verify background loading spinner shows up, then results populate
// 7. Verify background loading spinner shows up, then results populate
await waitFor('.places-list');
assert
.dom('.places-list .place-name')
@@ -103,19 +140,37 @@ module('Acceptance | collections navigation', function (hooks) {
.dom('.places-list .place-type')
.hasText('Saved place', 'Place type displays Saved place correctly');
// 6. Click back button in collection list header
// 8. Click back button in collection list header
await click('.sidebar-header .back-btn');
assert.strictEqual(currentURL(), '/lists', 'Goes back to /lists index');
// 7. Click back button in collections index header
// 9. Click back button in collections index header
await click('.sidebar-header .back-btn');
assert.strictEqual(currentURL(), '/menu', 'Goes back to main menu route');
// 8. Close sidebar
// 10. Close sidebar
await click('.sidebar-header .close-btn');
assert.strictEqual(currentURL(), '/', 'Sidebar closed and returned home');
});
test('clicking a place inside Saved Places sets returnToRoute and returns gracefully on back click', async function (assert) {
await visit('/lists/saved');
await waitFor('.places-list');
await click('.place-item');
assert.ok(
currentURL().includes('/place/place-456'),
'Transitions to the newest saved place details route'
);
await click('.back-btn');
assert.strictEqual(
currentURL(),
'/lists/saved',
'Returns gracefully back to the Saved Places view'
);
});
test('clicking a place inside a collection sets returnToRoute and returns gracefully on back click', async function (assert) {
await visit('/lists/to-go');
await waitFor('.places-list');
@@ -136,7 +191,7 @@ module('Acceptance | collections navigation', function (hooks) {
);
});
test('places inside a collection are sorted by createdAt descending', async function (assert) {
test('places inside Saved Places are sorted by createdAt descending', async function (assert) {
class SortedMockStorageService extends Service {
initialSyncDone = true;
savedPlaces = [
@@ -203,7 +258,7 @@ module('Acceptance | collections navigation', function (hooks) {
this.owner.unregister('service:storage');
this.owner.register('service:storage', SortedMockStorageService);
await visit('/lists/to-go');
await visit('/lists/saved');
await waitFor('.places-list');
const placeNames = Array.from(
@@ -213,7 +268,7 @@ module('Acceptance | collections navigation', function (hooks) {
assert.deepEqual(
placeNames,
['Newest Place', 'Middle Place', 'Oldest Place'],
'Places are ordered by createdAt in descending order'
'Saved Places are ordered by createdAt in descending order'
);
});
});