diff --git a/app/components/places-sidebar.gjs b/app/components/places-sidebar.gjs index 9d011b0..0011217 100644 --- a/app/components/places-sidebar.gjs +++ b/app/components/places-sidebar.gjs @@ -218,52 +218,58 @@ export default class PlacesSidebar extends Component { @onSave={{this.updateBookmark}} /> {{else}} - {{#if @places}} -
No places found nearby.
+ {{#if @places}} +No results found.
+ {{#if this.isNearbySearch}} +No places found nearby.
+ {{else}} +No results found.
+ {{/if}} {{/if}} - {{/if}} - + + {{/if}} {{/if}} diff --git a/app/controllers/lists/list.js b/app/controllers/lists/list.js new file mode 100644 index 0000000..9785c88 --- /dev/null +++ b/app/controllers/lists/list.js @@ -0,0 +1,108 @@ +import Controller from '@ember/controller'; +import { service } from '@ember/service'; +import { action } from '@ember/object'; +import { tracked } from '@glimmer/tracking'; +import { task } from 'ember-concurrency'; + +export default class ListsListController extends Controller { + @service router; + @service mapUi; + @service storage; + + @tracked model; + @tracked loadedPlaces = []; + + get listId() { + return this.model?.list_id; + } + + loadPlacesTask = task({ restartable: true }, async (listId) => { + this.loadedPlaces = []; // Clear previous elements immediately to show fresh loader + try { + this.loadedPlaces = await this.storage.getPlacesInList(listId); + } catch (e) { + console.error('Failed to load places in list', listId, e); + this.loadedPlaces = []; + } + }); + + get scrollTop() { + return this.mapUi.getScrollPosition(`list-${this.listId}`); + } + + get listColor() { + const list = this.storage.lists.find((l) => l.id === this.listId); + if (list && list.color) { + return list.color; + } + return getComputedStyle(document.documentElement) + .getPropertyValue('--default-list-color') + .trim(); + } + + get listTitle() { + const list = this.storage.lists.find((l) => l.id === this.listId); + return list ? list.title : 'Collections'; + } + + get places() { + const currentList = this.storage.lists.find((l) => l.id === this.listId); + const placeRefsIds = new Set( + currentList?.placeRefs?.map((ref) => ref.id) || [] + ); + + // Filter live tracked savedPlaces that are in this list + const livePlaces = this.storage.savedPlaces.filter((p) => + placeRefsIds.has(p.id) + ); + + const merged = []; + const seen = new Set(); + + // Process live state first to reflect deletions/edits immediately + livePlaces.forEach((p) => { + merged.push(p); + seen.add(p.id); + }); + + // Supplement with any background-fetched places that are still valid but not in live state yet + this.loadedPlaces.forEach((p) => { + if (placeRefsIds.has(p.id) && !seen.has(p.id)) { + merged.push(p); + seen.add(p.id); + } + }); + + return merged; + } + + @action + selectPlace(place) { + if (place) { + const sidebarContent = document.querySelector('.sidebar-content'); + if (sidebarContent) { + this.mapUi.saveScrollPosition( + `list-${this.listId}`, + sidebarContent.scrollTop + ); + } + this.mapUi.returnToRoute = { + name: 'lists.list', + model: this.listId, + }; + this.mapUi.showSidebar(); + this.mapUi.preventNextZoom = true; + this.router.transitionTo('place', place); + } + } + + @action + close() { + this.router.transitionTo('index'); + } + + @action + backToLists() { + this.router.transitionTo('lists.index'); + } +} diff --git a/app/routes/lists/list.js b/app/routes/lists/list.js index de9f6b6..23138d7 100644 --- a/app/routes/lists/list.js +++ b/app/routes/lists/list.js @@ -4,14 +4,23 @@ import { service } from '@ember/service'; export default class ListsListRoute extends Route { @service storage; - async model(params) { - const listId = params.list_id; - try { - const places = await this.storage.getPlacesInList(listId); - return { listId, places }; - } catch (e) { - console.error('Failed to load places in list', listId, e); - return { listId, places: [] }; + 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 (controller && controller.loadPlacesTask) { + controller.loadPlacesTask.perform(model.list_id); + } else { + console.error('DEBUG: ERROR! controller.loadPlacesTask is undefined!'); } } } diff --git a/app/styles/app.css b/app/styles/app.css index 64f71ac..db56cef 100644 --- a/app/styles/app.css +++ b/app/styles/app.css @@ -2250,3 +2250,11 @@ button.create-place { display: flex; align-items: center; } + +/* Sidebar Loading State */ +.sidebar-loading { + display: flex; + align-items: center; + justify-content: center; + padding: 4rem 1rem; +} diff --git a/app/templates/lists/list.gjs b/app/templates/lists/list.gjs index 14b6070..33cce31 100644 --- a/app/templates/lists/list.gjs +++ b/app/templates/lists/list.gjs @@ -1,109 +1,16 @@ -import Component from '@glimmer/component'; import PlacesSidebar from '#components/places-sidebar'; -import { service } from '@ember/service'; -import { action } from '@ember/object'; -export default class ListsListTemplate extends Component { - @service router; - @service mapUi; - @service storage; - - get listId() { - return this.args.model?.listId; - } - - get scrollTop() { - return this.mapUi.getScrollPosition(`list-${this.listId}`); - } - - get listColor() { - const list = this.storage.lists.find((l) => l.id === this.listId); - if (list && list.color) { - return list.color; - } - return getComputedStyle(document.documentElement) - .getPropertyValue('--default-list-color') - .trim(); - } - - get listTitle() { - const list = this.storage.lists.find((l) => l.id === this.listId); - return list ? list.title : 'Collections'; - } - - get places() { - const modelPlaces = this.args.model?.places || []; - const currentList = this.storage.lists.find((l) => l.id === this.listId); - const placeRefsIds = new Set( - currentList?.placeRefs?.map((ref) => ref.id) || [] - ); - - // Filter live tracked savedPlaces that are in this list - const livePlaces = this.storage.savedPlaces.filter((p) => - placeRefsIds.has(p.id) - ); - - const merged = []; - const seen = new Set(); - - // Process live state first to reflect deletions/edits immediately - livePlaces.forEach((p) => { - merged.push(p); - seen.add(p.id); - }); - - // Supplement with any model-fetched places that are still valid but not in live state yet - modelPlaces.forEach((p) => { - if (placeRefsIds.has(p.id) && !seen.has(p.id)) { - merged.push(p); - seen.add(p.id); - } - }); - - return merged; - } - - @action - selectPlace(place) { - if (place) { - const sidebarContent = document.querySelector('.sidebar-content'); - if (sidebarContent) { - this.mapUi.saveScrollPosition( - `list-${this.listId}`, - sidebarContent.scrollTop - ); - } - this.mapUi.returnToRoute = { - name: 'lists.list', - model: this.listId, - }; - this.mapUi.showSidebar(); - this.mapUi.preventNextZoom = true; - this.router.transitionTo('place', place); - } - } - - @action - close() { - this.router.transitionTo('index'); - } - - @action - backToLists() { - this.router.transitionTo('lists.index'); - } - - - {{#if this.mapUi.isSidebarVisible}} -