Compare commits

..
4 Commits
Author SHA1 Message Date
raucao 9200e6f5c8 1.34.5
CI / Lint (push) Successful in 1m26s
CI / Test (push) Successful in 1m48s
2026-09-25 16:45:46 +02:00
raucao bf09101a24 Move operator info to the bottom of its section
CI / Lint (push) Successful in 1m6s
CI / Test (push) Successful in 1m26s
2026-09-25 16:22:54 +02:00
raucao df720d1b21 Merge pull request 'Save/restore scroll position for bookmark lists' (#112) from bugfix/saved-places_scroll into master
CI / Lint (push) Successful in 1m9s
CI / Test (push) Successful in 1m29s
Reviewed-on: #112
2026-09-25 14:02:30 +00:00
raucao e74c01b1d1 Save/restore scroll position for bookmark lists
CI / Lint (pull_request) Successful in 1m6s
CI / Test (pull_request) Successful in 1m25s
Release Drafter / Update release notes draft (pull_request) Successful in 6s
Keep items loaded, same as for Activity and My Contributions lists
2026-09-25 15:55:47 +02:00
10 changed files with 152 additions and 46 deletions
+39 -39
View File
@@ -554,45 +554,6 @@ export default class PlaceDetails extends Component {
</p>
{{/if}}
{{#if this.showOperator}}
<p class="content-with-icon">
<Icon @name="briefcase" @title="Operator" />
<span>
{{this.operatorNames}}
{{#if this.operatorWebsite}}
<br />
<a
href={{this.operatorWebsite}}
target="_blank"
rel="noopener noreferrer"
>
{{this.operatorWebsiteLabel}}
</a>
{{/if}}
{{#if this.operatorWikidata}}
<br />
<a
href="https://www.wikidata.org/wiki/{{this.operatorWikidata}}"
target="_blank"
rel="noopener noreferrer"
>
Wikidata
</a>
{{/if}}
{{#if this.operatorWikipedia}}
<br />
<a
href={{this.operatorWikipediaUrl}}
target="_blank"
rel="noopener noreferrer"
>
Wikipedia
</a>
{{/if}}
</span>
</p>
{{/if}}
{{#if this.phone}}
<p class="content-with-icon">
<Icon @name="phone" @title="Phone" />
@@ -674,6 +635,45 @@ export default class PlaceDetails extends Component {
</p>
{{/if}}
{{#if this.showOperator}}
<p class="content-with-icon">
<Icon @name="briefcase" @title="Operator" />
<span>
{{this.operatorNames}}
{{#if this.operatorWebsite}}
<br />
<a
href={{this.operatorWebsite}}
target="_blank"
rel="noopener noreferrer"
>
{{this.operatorWebsiteLabel}}
</a>
{{/if}}
{{#if this.operatorWikidata}}
<br />
<a
href="https://www.wikidata.org/wiki/{{this.operatorWikidata}}"
target="_blank"
rel="noopener noreferrer"
>
Wikidata
</a>
{{/if}}
{{#if this.operatorWikipedia}}
<br />
<a
href={{this.operatorWikipediaUrl}}
target="_blank"
rel="noopener noreferrer"
>
Wikipedia
</a>
{{/if}}
</span>
</p>
{{/if}}
</div>
<div class="meta-info">
+16
View File
@@ -23,18 +23,34 @@ export default class ListsListController extends Controller {
@tracked model;
@tracked loadedPlaces = [];
@tracked loadedListId = null;
get listId() {
return this.model?.list_id;
}
get isLoading() {
// Only show the spinner when there is nothing to display yet. On re-entry
// the previously loaded places (plus live storage state) render instantly,
// so we avoid a loading flash and preserve restored scroll position.
return this.loadPlacesTask.isRunning && this.places.length === 0;
}
loadPlacesTask = task({ restartable: true }, async (listId) => {
// Already loaded this list: keep the existing places so returning from
// place details is instant (mirrors the activity service's load guard).
if (this.loadedListId === listId) {
return;
}
this.loadedListId = 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 = [];
this.loadedListId = null; // Allow a retry on next entry
}
});
+10 -1
View File
@@ -5,6 +5,7 @@ import { fn } from '@ember/helper';
import { on } from '@ember/modifier';
import Icon from '#components/icon';
import { htmlSafe } from '@ember/template';
import restoreScroll from '../../modifiers/restore-scroll';
export default class ListsIndexTemplate extends Component {
@service storage;
@@ -26,8 +27,16 @@ export default class ListsIndexTemplate extends Component {
return htmlSafe(`background-color: ${finalColor}`);
}
get scrollTop() {
return this.mapUi.getScrollPosition('lists-index');
}
@action
selectList(listId) {
const sidebarContent = document.querySelector('.sidebar-content');
if (sidebarContent) {
this.mapUi.saveScrollPosition('lists-index', sidebarContent.scrollTop);
}
this.router.transitionTo('lists.list', listId);
}
@@ -59,7 +68,7 @@ export default class ListsIndexTemplate extends Component {
</button>
</div>
<div class="sidebar-content">
<div class="sidebar-content" {{restoreScroll this.scrollTop}}>
<ul class="places-list">
<li>
<button
+1 -1
View File
@@ -7,7 +7,7 @@ import PlacesSidebar from '#components/places-sidebar';
@title={{@controller.listTitle}}
@color={{@controller.listColor}}
@scrollTop={{@controller.scrollTop}}
@isLoading={{@controller.loadPlacesTask.isRunning}}
@isLoading={{@controller.isLoading}}
@onSelect={{@controller.selectPlace}}
@onClose={{@controller.close}}
@onBack={{@controller.backToLists}}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "marco",
"version": "1.34.4",
"version": "1.34.5",
"private": true,
"description": "Unhosted maps app",
"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
+1 -1
View File
@@ -42,7 +42,7 @@
<meta name="msapplication-TileColor" content="#F6E9A6">
<meta name="msapplication-TileImage" content="/icons/icon-144.png">
<script type="module" crossorigin src="/assets/main-C9gFC_z2.js"></script>
<script type="module" crossorigin src="/assets/main-BlRXtNAu.js"></script>
<link rel="stylesheet" crossorigin href="/assets/main-Bv3zmRKA.css">
</head>
<body>
+81
View File
@@ -271,4 +271,85 @@ module('Acceptance | collections navigation', function (hooks) {
'Saved Places are ordered by createdAt in descending order'
);
});
test('returning from place details preserves scroll position without a loading flash', async function (assert) {
const places = Array.from({ length: 40 }, (_, i) => ({
id: `place-${i}`,
title: `Place ${i}`,
geohash: 'u33dc0',
lat: 48.1,
lon: 11.5,
createdAt: new Date(2023, 0, 1, 12, 0, i).toISOString(),
osmTags: { name: `Place ${i}` },
}));
class ManyPlacesStorageService extends Service {
initialSyncDone = true;
savedPlaces = places;
lists = [
{
id: 'to-go',
title: 'Want to go',
color: '#2e9e4f',
placeRefs: places.map((p) => ({ id: p.id, geohash: p.geohash })),
},
];
findPlaceById(id) {
return this.savedPlaces.find((p) => p.id === id) || null;
}
isPlaceSaved() {
return true;
}
loadPlacesInBounds() {
return [];
}
getPlacesInList(listId) {
if (listId === 'to-go') {
return Promise.resolve(this.savedPlaces);
}
return Promise.resolve([]);
}
rs = {
on: () => {},
};
}
this.owner.unregister('service:storage');
this.owner.register('service:storage', ManyPlacesStorageService);
await visit('/lists/to-go');
await waitFor('.places-list');
const sidebarContent = document.querySelector('.sidebar-content');
sidebarContent.scrollTop = 300;
assert.strictEqual(sidebarContent.scrollTop, 300, 'List is scrolled');
// Open place details from an item that is currently visible so the browser
// does not scroll it back into view (which would reset our saved position),
// then return via the back button.
const visibleItem = document.querySelectorAll('.place-item')[5];
await click(visibleItem);
await waitFor('.sidebar-content');
await click('.back-btn');
await waitFor('.places-list');
// Allow the restore-scroll requestAnimationFrame to run
await new Promise((resolve) => requestAnimationFrame(resolve));
await new Promise((resolve) => requestAnimationFrame(resolve));
const restored = document.querySelector('.sidebar-content');
assert.strictEqual(
restored.scrollTop,
300,
'Scroll position is restored on return'
);
assert
.dom('.sidebar-loading')
.doesNotExist('No loading spinner flashes when returning');
});
});