Refactor app menu, add place lists
Unify sidebar, make everything route-based
This commit is contained in:
@@ -19,6 +19,12 @@ import iconRounded from '../../icons/icon-rounded.svg?raw';
|
|||||||
|
|
||||||
<div class="sidebar-content">
|
<div class="sidebar-content">
|
||||||
<ul class="app-menu">
|
<ul class="app-menu">
|
||||||
|
<li>
|
||||||
|
<button type="button" {{on "click" @onSavedPlaces}}>
|
||||||
|
<Icon @name="bookmark" @size={{20}} />
|
||||||
|
<span>Saved places</span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<button type="button" {{on "click" (fn @onNavigate "settings")}}>
|
<button type="button" {{on "click" (fn @onNavigate "settings")}}>
|
||||||
<Icon @name="settings" @size={{20}} />
|
<Icon @name="settings" @size={{20}} />
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import Component from '@glimmer/component';
|
|||||||
import { action } from '@ember/object';
|
import { action } from '@ember/object';
|
||||||
import { tracked } from '@glimmer/tracking';
|
import { tracked } from '@glimmer/tracking';
|
||||||
import { fn } from '@ember/helper';
|
import { fn } from '@ember/helper';
|
||||||
|
import { service } from '@ember/service';
|
||||||
import eq from 'ember-truth-helpers/helpers/eq';
|
import eq from 'ember-truth-helpers/helpers/eq';
|
||||||
|
|
||||||
import AppMenuHome from './home';
|
import AppMenuHome from './home';
|
||||||
@@ -9,6 +10,7 @@ import AppMenuSettings from './settings';
|
|||||||
import AppMenuAbout from './about';
|
import AppMenuAbout from './about';
|
||||||
|
|
||||||
export default class AppMenu extends Component {
|
export default class AppMenu extends Component {
|
||||||
|
@service router;
|
||||||
@tracked currentView = 'menu'; // 'menu', 'settings', 'about'
|
@tracked currentView = 'menu'; // 'menu', 'settings', 'about'
|
||||||
|
|
||||||
@action
|
@action
|
||||||
@@ -16,10 +18,19 @@ export default class AppMenu extends Component {
|
|||||||
this.currentView = view;
|
this.currentView = view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
goToSavedPlaces() {
|
||||||
|
this.router.transitionTo('lists.index');
|
||||||
|
}
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="sidebar app-menu-pane">
|
<div class="sidebar app-menu-pane">
|
||||||
{{#if (eq this.currentView "menu")}}
|
{{#if (eq this.currentView "menu")}}
|
||||||
<AppMenuHome @onNavigate={{this.setView}} @onClose={{@onClose}} />
|
<AppMenuHome
|
||||||
|
@onNavigate={{this.setView}}
|
||||||
|
@onClose={{@onClose}}
|
||||||
|
@onSavedPlaces={{this.goToSavedPlaces}}
|
||||||
|
/>
|
||||||
|
|
||||||
{{else if (eq this.currentView "settings")}}
|
{{else if (eq this.currentView "settings")}}
|
||||||
<AppMenuSettings
|
<AppMenuSettings
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ export default class PlaceListsManager extends Component {
|
|||||||
checked={{this.isSaved}}
|
checked={{this.isSaved}}
|
||||||
{{on "change" this.toggleSaved}}
|
{{on "change" this.toggleSaved}}
|
||||||
/>
|
/>
|
||||||
<span class="list-color"></span>
|
<span class="list-color-dot"></span>
|
||||||
<span class="list-name">Saved places</span>
|
<span class="list-name">Saved places</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -122,7 +122,7 @@ export default class PlaceListsManager extends Component {
|
|||||||
/>
|
/>
|
||||||
{{! template-lint-disable no-inline-styles }}
|
{{! template-lint-disable no-inline-styles }}
|
||||||
<span
|
<span
|
||||||
class="list-color"
|
class="list-color-dot"
|
||||||
style={{this.styleFor list.color}}
|
style={{this.styleFor list.color}}
|
||||||
></span>
|
></span>
|
||||||
<span class="list-name">{{list.title}}</span>
|
<span class="list-name">{{list.title}}</span>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import PlaceDetails from './place-details';
|
|||||||
import Icon from './icon';
|
import Icon from './icon';
|
||||||
import humanizeOsmTag from '../helpers/humanize-osm-tag';
|
import humanizeOsmTag from '../helpers/humanize-osm-tag';
|
||||||
import { getLocalizedName, getPlaceType } from '../utils/osm';
|
import { getLocalizedName, getPlaceType } from '../utils/osm';
|
||||||
|
import restoreScroll from '../modifiers/restore-scroll';
|
||||||
|
|
||||||
export default class PlacesSidebar extends Component {
|
export default class PlacesSidebar extends Component {
|
||||||
@service storage;
|
@service storage;
|
||||||
@@ -168,7 +169,17 @@ export default class PlacesSidebar extends Component {
|
|||||||
{{on "click" this.clearSelection}}
|
{{on "click" this.clearSelection}}
|
||||||
><Icon @name="arrow-left" @size={{20}} @color="#333" /></button>
|
><Icon @name="arrow-left" @size={{20}} @color="#333" /></button>
|
||||||
{{else}}
|
{{else}}
|
||||||
{{#if this.isNearbySearch}}
|
{{#if @onBack}}
|
||||||
|
<button type="button" class="back-btn" {{on "click" @onBack}}><Icon
|
||||||
|
@name="arrow-left"
|
||||||
|
@size={{20}}
|
||||||
|
@color="#333"
|
||||||
|
/></button>
|
||||||
|
{{/if}}
|
||||||
|
{{#if @title}}
|
||||||
|
<h2><Icon @name="bookmark" @size={{20}} @color="#333" />
|
||||||
|
{{@title}}</h2>
|
||||||
|
{{else if this.isNearbySearch}}
|
||||||
<h2><Icon @name="target" @size={{20}} @color="#ea4335" />
|
<h2><Icon @name="target" @size={{20}} @color="#ea4335" />
|
||||||
Nearby</h2>
|
Nearby</h2>
|
||||||
{{else}}
|
{{else}}
|
||||||
@@ -182,7 +193,7 @@ export default class PlacesSidebar extends Component {
|
|||||||
/></button>
|
/></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sidebar-content">
|
<div class="sidebar-content" {{restoreScroll @scrollTop}}>
|
||||||
{{#if @selectedPlace}}
|
{{#if @selectedPlace}}
|
||||||
<PlaceDetails
|
<PlaceDetails
|
||||||
@place={{@selectedPlace}}
|
@place={{@selectedPlace}}
|
||||||
|
|||||||
10
app/modifiers/restore-scroll.js
Normal file
10
app/modifiers/restore-scroll.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { modifier } from 'ember-modifier';
|
||||||
|
|
||||||
|
export default modifier((element, [scrollTop]) => {
|
||||||
|
if (element && typeof scrollTop === 'number' && scrollTop > 0) {
|
||||||
|
// Restore inside requestAnimationFrame to guarantee layout rendering is ready
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
element.scrollTop = scrollTop;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -10,6 +10,10 @@ Router.map(function () {
|
|||||||
this.route('place', { path: '/place/:place_id' });
|
this.route('place', { path: '/place/:place_id' });
|
||||||
this.route('place.new', { path: '/place/new' });
|
this.route('place.new', { path: '/place/new' });
|
||||||
this.route('search');
|
this.route('search');
|
||||||
|
this.route('menu');
|
||||||
|
this.route('lists', function () {
|
||||||
|
this.route('list', { path: '/:list_id' });
|
||||||
|
});
|
||||||
this.route('oauth', function () {
|
this.route('oauth', function () {
|
||||||
this.route('osm-callback', { path: '/osm/callback' });
|
this.route('osm-callback', { path: '/osm/callback' });
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,5 +6,6 @@ export default class IndexRoute extends Route {
|
|||||||
|
|
||||||
activate() {
|
activate() {
|
||||||
this.mapUi.clearSearchResults();
|
this.mapUi.clearSearchResults();
|
||||||
|
this.mapUi.hideSidebar();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
10
app/routes/lists.js
Normal file
10
app/routes/lists.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import Route from '@ember/routing/route';
|
||||||
|
import { service } from '@ember/service';
|
||||||
|
|
||||||
|
export default class ListsRoute extends Route {
|
||||||
|
@service mapUi;
|
||||||
|
|
||||||
|
activate() {
|
||||||
|
this.mapUi.showSidebar();
|
||||||
|
}
|
||||||
|
}
|
||||||
3
app/routes/lists/index.js
Normal file
3
app/routes/lists/index.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import Route from '@ember/routing/route';
|
||||||
|
|
||||||
|
export default class ListsIndexRoute extends Route {}
|
||||||
17
app/routes/lists/list.js
Normal file
17
app/routes/lists/list.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import Route from '@ember/routing/route';
|
||||||
|
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: [] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
app/routes/menu.js
Normal file
10
app/routes/menu.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import Route from '@ember/routing/route';
|
||||||
|
import { service } from '@ember/service';
|
||||||
|
|
||||||
|
export default class MenuRoute extends Route {
|
||||||
|
@service mapUi;
|
||||||
|
|
||||||
|
activate() {
|
||||||
|
this.mapUi.showSidebar();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -108,6 +108,7 @@ export default class PlaceRoute extends Route {
|
|||||||
this.mapUi.clearSelection();
|
this.mapUi.clearSelection();
|
||||||
// Reset the "return to search" flag so it doesn't persist to subsequent navigations
|
// Reset the "return to search" flag so it doesn't persist to subsequent navigations
|
||||||
this.mapUi.returnToSearch = false;
|
this.mapUi.returnToSearch = false;
|
||||||
|
this.mapUi.returnToRoute = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadOsmPlace(id, type = null) {
|
async loadOsmPlace(id, type = null) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import Service, { service } from '@ember/service';
|
import Service, { service } from '@ember/service';
|
||||||
import { tracked } from '@glimmer/tracking';
|
import { tracked } from '@glimmer/tracking';
|
||||||
|
import { action } from '@ember/object';
|
||||||
|
|
||||||
export default class MapUiService extends Service {
|
export default class MapUiService extends Service {
|
||||||
@service nostrData;
|
@service nostrData;
|
||||||
@@ -9,6 +10,7 @@ export default class MapUiService extends Service {
|
|||||||
@tracked isCreating = false;
|
@tracked isCreating = false;
|
||||||
@tracked creationCoordinates = null;
|
@tracked creationCoordinates = null;
|
||||||
@tracked returnToSearch = false;
|
@tracked returnToSearch = false;
|
||||||
|
@tracked returnToRoute = null;
|
||||||
@tracked currentCenter = null;
|
@tracked currentCenter = null;
|
||||||
@tracked currentBounds = null;
|
@tracked currentBounds = null;
|
||||||
@tracked currentZoom = null;
|
@tracked currentZoom = null;
|
||||||
@@ -19,13 +21,33 @@ export default class MapUiService extends Service {
|
|||||||
@tracked currentSearch = null;
|
@tracked currentSearch = null;
|
||||||
@tracked loadingState = null;
|
@tracked loadingState = null;
|
||||||
@tracked isSidebarVisible = false;
|
@tracked isSidebarVisible = false;
|
||||||
|
@tracked isSidebarOpening = false;
|
||||||
|
|
||||||
|
scrollPositions = {};
|
||||||
|
|
||||||
|
@action
|
||||||
|
saveScrollPosition(key, value) {
|
||||||
|
this.scrollPositions[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
getScrollPosition(key) {
|
||||||
|
return this.scrollPositions[key] || 0;
|
||||||
|
}
|
||||||
|
|
||||||
showSidebar() {
|
showSidebar() {
|
||||||
this.isSidebarVisible = true;
|
if (!this.isSidebarVisible) {
|
||||||
|
this.isSidebarVisible = true;
|
||||||
|
this.isSidebarOpening = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.isSidebarOpening = false;
|
||||||
|
}, 250);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hideSidebar() {
|
hideSidebar() {
|
||||||
this.isSidebarVisible = false;
|
this.isSidebarVisible = false;
|
||||||
|
this.isSidebarOpening = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
selectPlace(place, options = {}) {
|
selectPlace(place, options = {}) {
|
||||||
|
|||||||
@@ -270,6 +270,11 @@ export default class StorageService extends Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getPlacesInList(listId) {
|
||||||
|
if (!this.places || !this.places.lists) return [];
|
||||||
|
return this.places.lists.getPlaces(listId);
|
||||||
|
}
|
||||||
|
|
||||||
async loadPlacesInBounds(bbox) {
|
async loadPlacesInBounds(bbox) {
|
||||||
// 1. Calculate required prefixes
|
// 1. Calculate required prefixes
|
||||||
const requiredPrefixes = getGeohashPrefixesInBbox(bbox);
|
const requiredPrefixes = getGeohashPrefixesInBbox(bbox);
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
/* Ember supports plain CSS out of the box. More info: https://cli.emberjs.com/release/advanced-use/stylesheets/ */
|
/* Ember supports plain CSS out of the box. More info: https://cli.emberjs.com/release/advanced-use/stylesheets/ */
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--default-list-color: #fc3;
|
--body-text-color: #333;
|
||||||
|
--primary-background-color: #fff;
|
||||||
--hover-bg: #f8f9fa;
|
--hover-bg: #f8f9fa;
|
||||||
|
--divider-color: #eee;
|
||||||
--sidebar-width: 350px;
|
--sidebar-width: 350px;
|
||||||
--link-color: #2a7fff;
|
--link-color: #2a7fff;
|
||||||
--link-color-visited: #6a4fbf;
|
--link-color-visited: #6a4fbf;
|
||||||
@@ -10,6 +12,7 @@
|
|||||||
--marker-color-dark: #b31412;
|
--marker-color-dark: #b31412;
|
||||||
--danger-color: var(--marker-color-primary);
|
--danger-color: var(--marker-color-primary);
|
||||||
--danger-color-dark: var(--marker-color-dark);
|
--danger-color-dark: var(--marker-color-dark);
|
||||||
|
--default-list-color: #fc3;
|
||||||
}
|
}
|
||||||
|
|
||||||
html,
|
html,
|
||||||
@@ -31,7 +34,7 @@ body {
|
|||||||
font-family: 'Noto Sans', sans-serif;
|
font-family: 'Noto Sans', sans-serif;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
color: #333;
|
color: var(--body-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
#root,
|
#root,
|
||||||
@@ -383,7 +386,7 @@ body {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 1rem 0;
|
padding: 1rem 0;
|
||||||
border-bottom: 1px solid #eee;
|
border-bottom: 1px solid var(--divider-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.account-item:last-child {
|
.account-item:last-child {
|
||||||
@@ -461,6 +464,9 @@ body {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden; /* Ensure flex children are contained */
|
overflow: hidden; /* Ensure flex children are contained */
|
||||||
will-change: transform;
|
will-change: transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-opening .sidebar {
|
||||||
animation: sidebar-slide-in-left 0.18s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
animation: sidebar-slide-in-left 0.18s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -481,7 +487,7 @@ body {
|
|||||||
|
|
||||||
.sidebar-header {
|
.sidebar-header {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
border-bottom: 1px solid #eee;
|
border-bottom: 1px solid var(--divider-color);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -528,7 +534,7 @@ body {
|
|||||||
padding-left: 1.4rem;
|
padding-left: 1.4rem;
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
color: #333;
|
color: var(--body-text-color);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
@@ -559,7 +565,7 @@ body {
|
|||||||
padding-left: 1.4rem;
|
padding-left: 1.4rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
color: #333;
|
color: var(--body-text-color);
|
||||||
transition: background-color 0.2s;
|
transition: background-color 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -630,7 +636,7 @@ body {
|
|||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background-color: #fff;
|
background-color: var(--primary-background-color);
|
||||||
border: 1px solid var(--danger-color);
|
border: 1px solid var(--danger-color);
|
||||||
color: var(--danger-color);
|
color: var(--danger-color);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -646,7 +652,7 @@ body {
|
|||||||
.btn-remove-relay:hover,
|
.btn-remove-relay:hover,
|
||||||
.btn-remove-relay:active {
|
.btn-remove-relay:active {
|
||||||
background-color: var(--danger-color);
|
background-color: var(--danger-color);
|
||||||
color: #fff;
|
color: var(--primary-background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-relay-input {
|
.add-relay-input {
|
||||||
@@ -676,7 +682,7 @@ body {
|
|||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
background: var(--hover-bg);
|
background: var(--hover-bg);
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
border-bottom: 1px solid #eee;
|
border-bottom: 1px solid var(--divider-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-group {
|
.form-group {
|
||||||
@@ -698,8 +704,8 @@ body {
|
|||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
box-sizing: border-box; /* Ensure padding doesn't overflow width */
|
box-sizing: border-box; /* Ensure padding doesn't overflow width */
|
||||||
color: #333;
|
color: var(--body-text-color);
|
||||||
background-color: #fff;
|
background-color: var(--primary-background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-control:focus {
|
.form-control:focus {
|
||||||
@@ -710,7 +716,7 @@ body {
|
|||||||
|
|
||||||
select.form-control {
|
select.form-control {
|
||||||
appearance: none;
|
appearance: none;
|
||||||
background-color: #fff;
|
background-color: var(--primary-background-color);
|
||||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23666' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
|
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23666' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: right 0.75rem center;
|
background-position: right 0.75rem center;
|
||||||
@@ -786,7 +792,7 @@ select.form-control {
|
|||||||
.meta-info p:first-child {
|
.meta-info p:first-child {
|
||||||
margin-top: 1.2rem;
|
margin-top: 1.2rem;
|
||||||
padding-top: 1.2rem;
|
padding-top: 1.2rem;
|
||||||
border-top: 1px solid #eee;
|
border-top: 1px solid var(--divider-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.meta-info a,
|
.meta-info a,
|
||||||
@@ -845,9 +851,9 @@ abbr[title] {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
border: none;
|
border: none;
|
||||||
border-bottom: 1px solid #eee;
|
border-bottom: 1px solid var(--divider-color);
|
||||||
background: #fff;
|
background: var(--primary-background-color);
|
||||||
color: #333;
|
color: var(--body-text-color);
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 0.2s;
|
transition: background 0.2s;
|
||||||
@@ -1018,7 +1024,7 @@ abbr[title] {
|
|||||||
.photo-carousel.inline .photo-carousel-track {
|
.photo-carousel.inline .photo-carousel-track {
|
||||||
scroll-snap-type: none;
|
scroll-snap-type: none;
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
background-color: #fff;
|
background-color: var(--primary-background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.photo-carousel.inline .carousel-slide {
|
.photo-carousel.inline .carousel-slide {
|
||||||
@@ -1097,7 +1103,7 @@ abbr[title] {
|
|||||||
|
|
||||||
.btn-outline {
|
.btn-outline {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: #333;
|
color: var(--body-text-color);
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1106,7 +1112,7 @@ abbr[title] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn-secondary {
|
.btn-secondary {
|
||||||
color: #333;
|
color: var(--body-text-color);
|
||||||
border: 1px solid rgb(255 204 51 / 20%);
|
border: 1px solid rgb(255 204 51 / 20%);
|
||||||
background: rgb(255 204 51 / 30%);
|
background: rgb(255 204 51 / 30%);
|
||||||
}
|
}
|
||||||
@@ -1363,7 +1369,7 @@ span.icon {
|
|||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
color: #333;
|
color: var(--body-text-color);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
z-index: 2000;
|
z-index: 2000;
|
||||||
display: none;
|
display: none;
|
||||||
@@ -1499,7 +1505,7 @@ button.create-place {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
color: #333;
|
color: var(--body-text-color);
|
||||||
outline: none;
|
outline: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0 4px;
|
padding: 0 4px;
|
||||||
@@ -1530,7 +1536,7 @@ button.create-place {
|
|||||||
|
|
||||||
.search-submit-btn:hover {
|
.search-submit-btn:hover {
|
||||||
background: rgb(0 0 0 / 5%);
|
background: rgb(0 0 0 / 5%);
|
||||||
color: #333;
|
color: var(--body-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-clear-btn {
|
.search-clear-btn {
|
||||||
@@ -1548,7 +1554,7 @@ button.create-place {
|
|||||||
|
|
||||||
.search-clear-btn:hover {
|
.search-clear-btn:hover {
|
||||||
background: rgb(0 0 0 / 5%);
|
background: rgb(0 0 0 / 5%);
|
||||||
color: #333;
|
color: var(--body-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Search Results Popover */
|
/* Search Results Popover */
|
||||||
@@ -1617,7 +1623,7 @@ button.create-place {
|
|||||||
|
|
||||||
.result-title {
|
.result-title {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #333;
|
color: var(--body-text-color);
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -1669,7 +1675,7 @@ button.create-place {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
color: #333;
|
color: var(--body-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.place-lists-manager input[type='checkbox'] {
|
.place-lists-manager input[type='checkbox'] {
|
||||||
@@ -1679,7 +1685,8 @@ button.create-place {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.place-lists-manager .list-color {
|
/* Shared List Color Dot */
|
||||||
|
.list-color-dot {
|
||||||
width: 12px;
|
width: 12px;
|
||||||
height: 12px;
|
height: 12px;
|
||||||
background-color: var(--default-list-color);
|
background-color: var(--default-list-color);
|
||||||
@@ -1690,7 +1697,7 @@ button.create-place {
|
|||||||
|
|
||||||
.place-lists-manager .divider {
|
.place-lists-manager .divider {
|
||||||
height: 1px;
|
height: 1px;
|
||||||
background: #eee;
|
background: var(--divider-color);
|
||||||
margin: 0.5rem 0;
|
margin: 0.5rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1733,7 +1740,7 @@ button.create-place {
|
|||||||
border: 1px solid #ddd;
|
border: 1px solid #ddd;
|
||||||
border-radius: 16px; /* Pill shape */
|
border-radius: 16px; /* Pill shape */
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
color: #333;
|
color: var(--body-text-color);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
box-shadow: 0 1px 3px rgb(0 0 0 / 10%);
|
box-shadow: 0 1px 3px rgb(0 0 0 / 10%);
|
||||||
@@ -1745,7 +1752,7 @@ button.create-place {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.category-chip:active {
|
.category-chip:active {
|
||||||
background: #eee;
|
background: var(--divider-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.category-chip:disabled {
|
.category-chip:disabled {
|
||||||
@@ -1880,7 +1887,7 @@ button.create-place {
|
|||||||
|
|
||||||
.photo-tag-chip {
|
.photo-tag-chip {
|
||||||
background: #f8f9fa;
|
background: #f8f9fa;
|
||||||
color: #333;
|
color: var(--body-text-color);
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
padding: 6px 12px;
|
padding: 6px 12px;
|
||||||
@@ -1890,7 +1897,7 @@ button.create-place {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.photo-tag-chip:hover {
|
.photo-tag-chip:hover {
|
||||||
background: #eee;
|
background: var(--divider-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.photo-tag-chip.is-selected {
|
.photo-tag-chip.is-selected {
|
||||||
@@ -2121,7 +2128,7 @@ button.create-place {
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
color: #333;
|
color: var(--body-text-color);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2163,3 +2170,47 @@ button.create-place {
|
|||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Lists Index Sidebar Menu */
|
||||||
|
.lists-index-item {
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 1px solid var(--divider-color);
|
||||||
|
background: var(--primary-background-color);
|
||||||
|
color: var(--body-text-color);
|
||||||
|
padding: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
|
font-family: inherit;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lists-index-item:hover {
|
||||||
|
background: var(--hover-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lists-index-item-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lists-index-name {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: normal;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lists-index-count {
|
||||||
|
color: #666;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
margin: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,12 +2,9 @@ import Component from '@glimmer/component';
|
|||||||
import { pageTitle } from 'ember-page-title';
|
import { pageTitle } from 'ember-page-title';
|
||||||
import Map from '#components/map';
|
import Map from '#components/map';
|
||||||
import AppHeader from '#components/app-header';
|
import AppHeader from '#components/app-header';
|
||||||
import AppMenu from '#components/app-menu/index';
|
|
||||||
import Toast from '#components/toast';
|
import Toast from '#components/toast';
|
||||||
import { service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
import { tracked } from '@glimmer/tracking';
|
|
||||||
import { action } from '@ember/object';
|
import { action } from '@ember/object';
|
||||||
import { or } from 'ember-truth-helpers';
|
|
||||||
import { on } from '@ember/modifier';
|
import { on } from '@ember/modifier';
|
||||||
|
|
||||||
export default class ApplicationComponent extends Component {
|
export default class ApplicationComponent extends Component {
|
||||||
@@ -15,16 +12,17 @@ export default class ApplicationComponent extends Component {
|
|||||||
@service mapUi;
|
@service mapUi;
|
||||||
@service router;
|
@service router;
|
||||||
|
|
||||||
@tracked isAppMenuOpen = false;
|
|
||||||
|
|
||||||
get isSidebarOpen() {
|
get isSidebarOpen() {
|
||||||
// We consider the sidebar "open" if we are in search or place routes AND it's visible.
|
// We consider the sidebar "open" if we are in search, menu, lists or place routes AND it's visible.
|
||||||
// This helps the map know if it should shift the center or adjust view.
|
// This helps the map know if it should shift the center or adjust view.
|
||||||
|
const name = this.router.currentRouteName;
|
||||||
return (
|
return (
|
||||||
this.mapUi.isSidebarVisible &&
|
this.mapUi.isSidebarVisible &&
|
||||||
(this.router.currentRouteName === 'place' ||
|
(name === 'place' ||
|
||||||
this.router.currentRouteName === 'place.new' ||
|
name === 'place.new' ||
|
||||||
this.router.currentRouteName === 'search')
|
name === 'search' ||
|
||||||
|
name === 'menu' ||
|
||||||
|
name.startsWith('lists'))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,24 +35,27 @@ export default class ApplicationComponent extends Component {
|
|||||||
|
|
||||||
@action
|
@action
|
||||||
toggleAppMenu() {
|
toggleAppMenu() {
|
||||||
this.isAppMenuOpen = !this.isAppMenuOpen;
|
if (this.router.currentRouteName === 'menu') {
|
||||||
}
|
this.router.transitionTo('index');
|
||||||
|
} else {
|
||||||
@action
|
this.router.transitionTo('menu');
|
||||||
closeAppMenu() {
|
}
|
||||||
this.isAppMenuOpen = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
handleOutsideClick() {
|
handleOutsideClick() {
|
||||||
if (this.isAppMenuOpen) {
|
const name = this.router.currentRouteName;
|
||||||
this.closeAppMenu();
|
if (
|
||||||
} else if (
|
name === 'search' ||
|
||||||
this.router.currentRouteName === 'search' ||
|
name === 'place' ||
|
||||||
this.router.currentRouteName === 'place'
|
name === 'menu' ||
|
||||||
|
name.startsWith('lists')
|
||||||
) {
|
) {
|
||||||
this.mapUi.clearSelection();
|
this.mapUi.clearSelection();
|
||||||
this.mapUi.hideSidebar();
|
this.mapUi.hideSidebar();
|
||||||
|
if (name === 'menu' || name.startsWith('lists')) {
|
||||||
|
this.router.transitionTo('index');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,32 +67,30 @@ export default class ApplicationComponent extends Component {
|
|||||||
<template>
|
<template>
|
||||||
{{pageTitle "Marco"}}
|
{{pageTitle "Marco"}}
|
||||||
|
|
||||||
<AppHeader @onToggleMenu={{this.toggleAppMenu}} />
|
<div class={{if this.mapUi.isSidebarOpening "sidebar-opening"}}>
|
||||||
|
<AppHeader @onToggleMenu={{this.toggleAppMenu}} />
|
||||||
|
|
||||||
<div
|
|
||||||
id="rs-widget-container"
|
|
||||||
class={{if this.storage.isWidgetOpen "visible"}}
|
|
||||||
></div>
|
|
||||||
|
|
||||||
{{#if this.storage.isWidgetOpen}}
|
|
||||||
<div
|
<div
|
||||||
class="rs-backdrop"
|
id="rs-widget-container"
|
||||||
role="button"
|
class={{if this.storage.isWidgetOpen "visible"}}
|
||||||
{{on "click" this.storage.closeWidget}}
|
|
||||||
></div>
|
></div>
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
<Map
|
{{#if this.storage.isWidgetOpen}}
|
||||||
@isSidebarOpen={{or this.isSidebarOpen this.isAppMenuOpen}}
|
<div
|
||||||
@onOutsideClick={{this.handleOutsideClick}}
|
class="rs-backdrop"
|
||||||
/>
|
role="button"
|
||||||
|
{{on "click" this.storage.closeWidget}}
|
||||||
|
></div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
{{#if this.isAppMenuOpen}}
|
<Map
|
||||||
<AppMenu @onClose={{this.closeAppMenu}} />
|
@isSidebarOpen={{this.isSidebarOpen}}
|
||||||
{{/if}}
|
@onOutsideClick={{this.handleOutsideClick}}
|
||||||
|
/>
|
||||||
|
|
||||||
<Toast />
|
<Toast />
|
||||||
|
|
||||||
{{outlet}}
|
{{outlet}}
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
}
|
}
|
||||||
|
|||||||
1
app/templates/lists.gjs
Normal file
1
app/templates/lists.gjs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<template>{{outlet}}</template>
|
||||||
80
app/templates/lists/index.gjs
Normal file
80
app/templates/lists/index.gjs
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import Component from '@glimmer/component';
|
||||||
|
import { service } from '@ember/service';
|
||||||
|
import { action } from '@ember/object';
|
||||||
|
import { fn } from '@ember/helper';
|
||||||
|
import { on } from '@ember/modifier';
|
||||||
|
import Icon from '#components/icon';
|
||||||
|
import { htmlSafe } from '@ember/template';
|
||||||
|
|
||||||
|
export default class ListsIndexTemplate extends Component {
|
||||||
|
@service storage;
|
||||||
|
@service router;
|
||||||
|
@service mapUi;
|
||||||
|
|
||||||
|
styleFor(color) {
|
||||||
|
return htmlSafe(`background-color: ${color}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
selectList(listId) {
|
||||||
|
this.router.transitionTo('lists.list', listId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
close() {
|
||||||
|
this.router.transitionTo('index');
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
backToMenu() {
|
||||||
|
this.router.transitionTo('menu');
|
||||||
|
}
|
||||||
|
|
||||||
|
<template>
|
||||||
|
{{#if this.mapUi.isSidebarVisible}}
|
||||||
|
<div class="sidebar">
|
||||||
|
<div class="sidebar-header">
|
||||||
|
<button type="button" class="back-btn" {{on "click" this.backToMenu}}>
|
||||||
|
<Icon @name="arrow-left" @size={{20}} @color="#333" />
|
||||||
|
</button>
|
||||||
|
<h2><Icon @name="bookmark" @size={{20}} @color="#333" />
|
||||||
|
Saved places</h2>
|
||||||
|
<button type="button" class="close-btn" {{on "click" this.close}}>
|
||||||
|
<Icon @name="x" @size={{20}} @color="#333" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sidebar-content">
|
||||||
|
<ul class="places-list">
|
||||||
|
{{#each this.storage.lists as |list|}}
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="lists-index-item"
|
||||||
|
{{on "click" (fn this.selectList list.id)}}
|
||||||
|
>
|
||||||
|
<div class="lists-index-item-left">
|
||||||
|
{{! template-lint-disable no-inline-styles }}
|
||||||
|
<span
|
||||||
|
class="list-color-dot"
|
||||||
|
style={{this.styleFor list.color}}
|
||||||
|
></span>
|
||||||
|
<div class="lists-index-name">{{list.title}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="lists-index-count">
|
||||||
|
{{#if list.placeRefs.length}}
|
||||||
|
{{list.placeRefs.length}}
|
||||||
|
places
|
||||||
|
{{else}}
|
||||||
|
empty
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</template>
|
||||||
|
}
|
||||||
98
app/templates/lists/list.gjs
Normal file
98
app/templates/lists/list.gjs
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
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 listTitle() {
|
||||||
|
const list = this.storage.lists.find((l) => l.id === this.listId);
|
||||||
|
return list ? list.title : 'Saved places';
|
||||||
|
}
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
|
||||||
|
<template>
|
||||||
|
{{#if this.mapUi.isSidebarVisible}}
|
||||||
|
<PlacesSidebar
|
||||||
|
@places={{this.places}}
|
||||||
|
@title={{this.listTitle}}
|
||||||
|
@scrollTop={{this.scrollTop}}
|
||||||
|
@onSelect={{this.selectPlace}}
|
||||||
|
@onClose={{this.close}}
|
||||||
|
@onBack={{this.backToLists}}
|
||||||
|
/>
|
||||||
|
{{/if}}
|
||||||
|
</template>
|
||||||
|
}
|
||||||
15
app/templates/menu.gjs
Normal file
15
app/templates/menu.gjs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import Component from '@glimmer/component';
|
||||||
|
import AppMenu from '#components/app-menu/index';
|
||||||
|
import { service } from '@ember/service';
|
||||||
|
import { action } from '@ember/object';
|
||||||
|
|
||||||
|
export default class MenuTemplate extends Component {
|
||||||
|
@service router;
|
||||||
|
|
||||||
|
@action
|
||||||
|
close() {
|
||||||
|
this.router.transitionTo('index');
|
||||||
|
}
|
||||||
|
|
||||||
|
<template><AppMenu @onClose={{this.close}} /></template>
|
||||||
|
}
|
||||||
@@ -77,8 +77,14 @@ export default class PlaceTemplate extends Component {
|
|||||||
navigateBack(place) {
|
navigateBack(place) {
|
||||||
// The sidebar calls this with null when "Back" is clicked.
|
// The sidebar calls this with null when "Back" is clicked.
|
||||||
if (place === null) {
|
if (place === null) {
|
||||||
|
// If we have an active route context (e.g. lists), return to it
|
||||||
|
if (this.mapUi.returnToRoute) {
|
||||||
|
this.mapUi.showSidebar();
|
||||||
|
const { name, model } = this.mapUi.returnToRoute;
|
||||||
|
this.router.transitionTo(name, model);
|
||||||
|
}
|
||||||
// If we have an active search context, return to it (UP navigation)
|
// If we have an active search context, return to it (UP navigation)
|
||||||
if (this.mapUi.returnToSearch && this.mapUi.currentSearch) {
|
else if (this.mapUi.returnToSearch && this.mapUi.currentSearch) {
|
||||||
this.mapUi.showSidebar();
|
this.mapUi.showSidebar();
|
||||||
this.router.transitionTo('search', {
|
this.router.transitionTo('search', {
|
||||||
queryParams: this.mapUi.currentSearch,
|
queryParams: this.mapUi.currentSearch,
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ export default class SearchTemplate extends Component {
|
|||||||
@action
|
@action
|
||||||
selectPlace(place) {
|
selectPlace(place) {
|
||||||
if (place) {
|
if (place) {
|
||||||
|
const sidebarContent = document.querySelector('.sidebar-content');
|
||||||
|
if (sidebarContent) {
|
||||||
|
this.mapUi.saveScrollPosition('search', sidebarContent.scrollTop);
|
||||||
|
}
|
||||||
this.mapUi.returnToSearch = true;
|
this.mapUi.returnToSearch = true;
|
||||||
this.mapUi.showSidebar();
|
this.mapUi.showSidebar();
|
||||||
this.mapUi.preventNextZoom = true;
|
this.mapUi.preventNextZoom = true;
|
||||||
@@ -28,6 +32,7 @@ export default class SearchTemplate extends Component {
|
|||||||
{{#if this.mapUi.isSidebarVisible}}
|
{{#if this.mapUi.isSidebarVisible}}
|
||||||
<PlacesSidebar
|
<PlacesSidebar
|
||||||
@places={{this.mapUi.searchResults}}
|
@places={{this.mapUi.searchResults}}
|
||||||
|
@scrollTop={{this.mapUi.getScrollPosition "search"}}
|
||||||
@onSelect={{this.selectPlace}}
|
@onSelect={{this.selectPlace}}
|
||||||
@onClose={{this.close}}
|
@onClose={{this.close}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user