feat(search): add category search support and sync with chips
This commit is contained in:
@@ -11,7 +11,11 @@ import CategoryChips from '#components/category-chips';
|
||||
export default class AppHeaderComponent extends Component {
|
||||
@service storage;
|
||||
@tracked isUserMenuOpen = false;
|
||||
@tracked hasQuery = false;
|
||||
@tracked searchQuery = '';
|
||||
|
||||
get hasQuery() {
|
||||
return !!this.searchQuery;
|
||||
}
|
||||
|
||||
@action
|
||||
toggleUserMenu() {
|
||||
@@ -25,22 +29,21 @@ export default class AppHeaderComponent extends Component {
|
||||
|
||||
@action
|
||||
handleQueryChange(query) {
|
||||
this.hasQuery = !!query;
|
||||
this.searchQuery = query;
|
||||
}
|
||||
|
||||
@action
|
||||
handleChipSelect() {
|
||||
// When a chip is selected, we might want to ensure the search box is cleared visually,
|
||||
// although the route transition will happen.
|
||||
// The SearchBox component manages its own state, so we rely on the route transition.
|
||||
// However, if we want to clear the search box input from here, we'd need to control it.
|
||||
// For now, let's just let the route change happen.
|
||||
handleChipSelect(category) {
|
||||
this.searchQuery = category.label;
|
||||
// The existing logic in CategoryChips triggers the route transition.
|
||||
// This update simply fills the search box.
|
||||
}
|
||||
|
||||
<template>
|
||||
<header class="app-header">
|
||||
<div class="header-left">
|
||||
<SearchBox
|
||||
@query={{this.searchQuery}}
|
||||
@onToggleMenu={{@onToggleMenu}}
|
||||
@onQueryChange={{this.handleQueryChange}}
|
||||
/>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { fn } from '@ember/helper';
|
||||
import { task, timeout } from 'ember-concurrency';
|
||||
import Icon from '#components/icon';
|
||||
import humanizeOsmTag from '../helpers/humanize-osm-tag';
|
||||
import { POI_CATEGORIES } from '../utils/poi-categories';
|
||||
import eq from 'ember-truth-helpers/helpers/eq';
|
||||
|
||||
export default class SearchBoxComponent extends Component {
|
||||
@@ -15,11 +16,19 @@ export default class SearchBoxComponent extends Component {
|
||||
@service mapUi;
|
||||
@service map; // Assuming we might need map context, but mostly we use router
|
||||
|
||||
@tracked query = '';
|
||||
@tracked _internalQuery = '';
|
||||
@tracked results = [];
|
||||
@tracked isFocused = false;
|
||||
@tracked isLoading = false;
|
||||
|
||||
get query() {
|
||||
return this.args.query ?? this._internalQuery;
|
||||
}
|
||||
|
||||
set query(value) {
|
||||
this._internalQuery = value;
|
||||
}
|
||||
|
||||
get showPopover() {
|
||||
return this.isFocused && this.results.length > 0;
|
||||
}
|
||||
@@ -51,8 +60,20 @@ export default class SearchBoxComponent extends Component {
|
||||
if (this.mapUi.currentCenter) {
|
||||
({ lat, lon } = this.mapUi.currentCenter);
|
||||
}
|
||||
|
||||
// Filter categories
|
||||
const q = this.query.toLowerCase();
|
||||
const categoryMatches = POI_CATEGORIES.filter((c) =>
|
||||
c.label.toLowerCase().includes(q)
|
||||
).map((c) => ({
|
||||
source: 'category',
|
||||
title: c.label,
|
||||
id: c.id,
|
||||
icon: 'search',
|
||||
}));
|
||||
|
||||
const results = await this.photon.search(this.query, lat, lon);
|
||||
this.results = results;
|
||||
this.results = [...categoryMatches, ...results];
|
||||
} catch (e) {
|
||||
console.error('Search failed', e);
|
||||
this.results = [];
|
||||
@@ -98,7 +119,29 @@ export default class SearchBoxComponent extends Component {
|
||||
|
||||
@action
|
||||
selectResult(place) {
|
||||
if (place.source === 'category') {
|
||||
this.query = place.title;
|
||||
if (this.args.onQueryChange) {
|
||||
this.args.onQueryChange(place.title);
|
||||
}
|
||||
this.results = [];
|
||||
|
||||
this.router.transitionTo('search', {
|
||||
queryParams: {
|
||||
q: place.title,
|
||||
category: place.id,
|
||||
selected: null,
|
||||
lat: null,
|
||||
lon: null,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.query = place.title;
|
||||
if (this.args.onQueryChange) {
|
||||
this.args.onQueryChange(place.title);
|
||||
}
|
||||
this.results = []; // Hide popover
|
||||
|
||||
// If it has an OSM ID, go to place details
|
||||
@@ -183,7 +226,11 @@ export default class SearchBoxComponent extends Component {
|
||||
{{on "click" (fn this.selectResult result)}}
|
||||
>
|
||||
<div class="result-icon">
|
||||
<Icon @name="map-pin" @size={{16}} @color="#666" />
|
||||
<Icon
|
||||
@name={{if result.icon result.icon "map-pin"}}
|
||||
@size={{16}}
|
||||
@color="#666"
|
||||
/>
|
||||
</div>
|
||||
<div class="result-info">
|
||||
<span class="result-title">{{result.title}}</span>
|
||||
|
||||
File diff suppressed because one or more lines are too long
2
release/assets/main-C4F17h3W.js
Normal file
2
release/assets/main-C4F17h3W.js
Normal file
File diff suppressed because one or more lines are too long
1
release/assets/main-CKp1bFPU.css
Normal file
1
release/assets/main-CKp1bFPU.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
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-gEUnNw-L.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/main-BOfcjRke.css">
|
||||
<script type="module" crossorigin src="/assets/main-C4F17h3W.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/main-CKp1bFPU.css">
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user