wrolpi/app/src/components/MapSearchView.js

132 lines
4.5 KiB
JavaScript
Raw Normal View History

import React, {useEffect, useState} from "react";
import {Link, useNavigate} from "react-router";
import {getMapSearchStatus, searchMap} from "../api";
ui: thirty-five files off Semantic, leaving fifteen Phase 2, part 4. Baseline 50 -> 15. Migrated: all eight calculators, the rest of inventory and collections, the Map/Zim/Docs cluster, Videos, Files, Search, Archive, DashboardPage, DatesSelector, HelpModal, Events, Tags, Flasher's neighbours, admin Settings and Status, Download, and App. Also the three shared modules everything imports: api.js and customHooks.js swap the Semantic toast library for the library's shim (same call shape), and useForm.js renders the library's Message. useForm accepts both the old `{content, header, positive|negative}` shape and the new `{kind, title}`, because the pages building those objects migrate separately. Two library gaps closed, both found by call sites needing them: ActionInput is a forwardRef now. Its ref reaches the input rather than the wrapper, because callers use it to focus or select the field -- a ref to the surrounding div would do neither, which is why one call site had resorted to hand-copying the component's markup. Header takes a `color` token name. Three separate call sites had reached for an inline style to colour a heading that carries meaning ("3 items to add" against "2 to remove"), which is how a hex ends up in markup. Two files needed repair after being interrupted mid-edit: MapViewer had a Segment left in its error branch, and VideoPlayer had lost the useContext import its own code still used. Neither jest nor tsc noticed; only `npm run build` did, which is the whole reason that check exists. Fifteen files remain: Archive, BulkTagModal, Channels, Docs, FileBrowser, FilePreview, Flasher, Nav, Playlists, Theme.tsx, Upload, VideoPlayer, Zim, ControllerPage, admin/Downloads. Theme.tsx is deliberately last -- the unmigrated files still import its Semantic-backed wrappers, so it can only go once they have.
2026-07-30 12:21:30 -06:00
import {Card, CardGroup, Header, Loading, Panel} from "./ui";
import {Paginator} from "./Common";
import {useLatestRequest, usePages} from "../hooks/customHooks";
ui: thirty-five files off Semantic, leaving fifteen Phase 2, part 4. Baseline 50 -> 15. Migrated: all eight calculators, the rest of inventory and collections, the Map/Zim/Docs cluster, Videos, Files, Search, Archive, DashboardPage, DatesSelector, HelpModal, Events, Tags, Flasher's neighbours, admin Settings and Status, Download, and App. Also the three shared modules everything imports: api.js and customHooks.js swap the Semantic toast library for the library's shim (same call shape), and useForm.js renders the library's Message. useForm accepts both the old `{content, header, positive|negative}` shape and the new `{kind, title}`, because the pages building those objects migrate separately. Two library gaps closed, both found by call sites needing them: ActionInput is a forwardRef now. Its ref reaches the input rather than the wrapper, because callers use it to focus or select the field -- a ref to the surrounding div would do neither, which is why one call site had resorted to hand-copying the component's markup. Header takes a `color` token name. Three separate call sites had reached for an inline style to colour a heading that carries meaning ("3 items to add" against "2 to remove"), which is how a hex ends up in markup. Two files needed repair after being interrupted mid-edit: MapViewer had a Segment left in its error branch, and VideoPlayer had lost the useContext import its own code still used. Neither jest nor tsc noticed; only `npm run build` did, which is the whole reason that check exists. Fifteen files remain: Archive, BulkTagModal, Channels, Docs, FileBrowser, FilePreview, Flasher, Nav, Playlists, Theme.tsx, Upload, VideoPlayer, Zim, ControllerPage, admin/Downloads. Theme.tsx is deliberately last -- the unmigrated files still import its Semantic-backed wrappers, so it can only go once they have.
2026-07-30 12:21:30 -06:00
import {QueryContext} from "../contexts/contexts";
import {MAP_VIEWER_URI} from "./Vars";
function formatPopulation(pop) {
if (!pop) return null;
if (pop >= 1_000_000) return `${(pop / 1_000_000).toFixed(1)}M`;
if (pop >= 1_000) return `${Math.round(pop / 1_000)}K`;
return `${pop}`;
}
function MapPlaceCard({place, index}) {
const navigate = useNavigate();
const [iframeSrc, setIframeSrc] = useState(null);
const zoom = place.min_zoom ? Math.max(8, Math.round(13 - place.min_zoom / 3)) : 11;
const embedSrc = `${MAP_VIEWER_URI}/embed.html#${place.lat},${place.lon},${zoom}`;
// Stagger iframe loading to avoid overwhelming the map viewer with simultaneous requests.
useEffect(() => {
const timer = setTimeout(() => setIframeSrc(embedSrc), 100 + index * 200);
return () => clearTimeout(timer);
}, [embedSrc, index]);
const handleClick = () => {
navigate(`/map?lat=${place.lat}&lon=${place.lon}&z=${zoom}`);
};
const subtitle = [];
if (place.region) subtitle.push(place.region);
const detail = place.kind_detail || place.kind || "";
if (detail && !place.region) subtitle.push(detail.replace(/_/g, " "));
const pop = formatPopulation(place.population);
if (pop) subtitle.push(`pop. ${pop}`);
ui: thirty-five files off Semantic, leaving fifteen Phase 2, part 4. Baseline 50 -> 15. Migrated: all eight calculators, the rest of inventory and collections, the Map/Zim/Docs cluster, Videos, Files, Search, Archive, DashboardPage, DatesSelector, HelpModal, Events, Tags, Flasher's neighbours, admin Settings and Status, Download, and App. Also the three shared modules everything imports: api.js and customHooks.js swap the Semantic toast library for the library's shim (same call shape), and useForm.js renders the library's Message. useForm accepts both the old `{content, header, positive|negative}` shape and the new `{kind, title}`, because the pages building those objects migrate separately. Two library gaps closed, both found by call sites needing them: ActionInput is a forwardRef now. Its ref reaches the input rather than the wrapper, because callers use it to focus or select the field -- a ref to the surrounding div would do neither, which is why one call site had resorted to hand-copying the component's markup. Header takes a `color` token name. Three separate call sites had reached for an inline style to colour a heading that carries meaning ("3 items to add" against "2 to remove"), which is how a hex ends up in markup. Two files needed repair after being interrupted mid-edit: MapViewer had a Segment left in its error branch, and VideoPlayer had lost the useContext import its own code still used. Neither jest nor tsc noticed; only `npm run build` did, which is the whole reason that check exists. Fifteen files remain: Archive, BulkTagModal, Channels, Docs, FileBrowser, FilePreview, Flasher, Nav, Playlists, Theme.tsx, Upload, VideoPlayer, Zim, ControllerPage, admin/Downloads. Theme.tsx is deliberately last -- the unmigrated files still import its Semantic-backed wrappers, so it can only go once they have.
2026-07-30 12:21:30 -06:00
const media = <div style={{height: 150, overflow: "hidden", position: "relative", background: "var(--head)"}}>
{iframeSrc && <iframe
src={iframeSrc}
title={place.name}
loading="lazy"
style={{
width: "100%",
height: "100%",
border: "none",
pointerEvents: "none",
}}
/>}
</div>;
return <Card
onClick={handleClick}
media={media}
title={place.name}
meta={subtitle.length > 0 ? subtitle.join(" · ") : undefined}
>
<div>{place.lat.toFixed(4)}, {place.lon.toFixed(4)}</div>
</Card>;
}
export function MapSearchView() {
const {searchParams} = React.useContext(QueryContext);
const searchStr = searchParams.get("q") || "";
const {offset, limit, activePage, setPage, totalPages, setTotal} = usePages(12);
// useLatestRequest discards stale responses and catches errors, so rapid pagination
// can't show out-of-order results and a network failure can't leave the spinner stuck.
const {data, sendRequest, loading} = useLatestRequest(300);
const [hasMaps, setHasMaps] = useState(null);
useEffect(() => {
let cancelled = false;
(async () => {
const status = await getMapSearchStatus();
if (!cancelled) {
const installed = (status.indexed || []).length + (status.missing || []).length;
setHasMaps(installed > 0);
}
})();
return () => {
cancelled = true;
};
}, []);
useEffect(() => {
if (!searchStr || searchStr.length < 2) {
setTotal(0);
return;
}
sendRequest(async () => await searchMap(searchStr, limit, offset));
}, [searchStr, offset, limit, sendRequest]);
const results = (data && data.results) || [];
useEffect(() => {
setTotal((data && data.total) || 0);
}, [data, setTotal]);
if (hasMaps === false) {
ui: thirty-five files off Semantic, leaving fifteen Phase 2, part 4. Baseline 50 -> 15. Migrated: all eight calculators, the rest of inventory and collections, the Map/Zim/Docs cluster, Videos, Files, Search, Archive, DashboardPage, DatesSelector, HelpModal, Events, Tags, Flasher's neighbours, admin Settings and Status, Download, and App. Also the three shared modules everything imports: api.js and customHooks.js swap the Semantic toast library for the library's shim (same call shape), and useForm.js renders the library's Message. useForm accepts both the old `{content, header, positive|negative}` shape and the new `{kind, title}`, because the pages building those objects migrate separately. Two library gaps closed, both found by call sites needing them: ActionInput is a forwardRef now. Its ref reaches the input rather than the wrapper, because callers use it to focus or select the field -- a ref to the surrounding div would do neither, which is why one call site had resorted to hand-copying the component's markup. Header takes a `color` token name. Three separate call sites had reached for an inline style to colour a heading that carries meaning ("3 items to add" against "2 to remove"), which is how a hex ends up in markup. Two files needed repair after being interrupted mid-edit: MapViewer had a Segment left in its error branch, and VideoPlayer had lost the useContext import its own code still used. Neither jest nor tsc noticed; only `npm run build` did, which is the whole reason that check exists. Fifteen files remain: Archive, BulkTagModal, Channels, Docs, FileBrowser, FilePreview, Flasher, Nav, Playlists, Theme.tsx, Upload, VideoPlayer, Zim, ControllerPage, admin/Downloads. Theme.tsx is deliberately last -- the unmigrated files still import its Semantic-backed wrappers, so it can only go once they have.
2026-07-30 12:21:30 -06:00
return <Panel>
<Header as="h4">No maps are installed.</Header>
<p>Subscribe to a map region to search for places.</p>
<Link to="/map/manage">Go to Map Manager</Link>
ui: thirty-five files off Semantic, leaving fifteen Phase 2, part 4. Baseline 50 -> 15. Migrated: all eight calculators, the rest of inventory and collections, the Map/Zim/Docs cluster, Videos, Files, Search, Archive, DashboardPage, DatesSelector, HelpModal, Events, Tags, Flasher's neighbours, admin Settings and Status, Download, and App. Also the three shared modules everything imports: api.js and customHooks.js swap the Semantic toast library for the library's shim (same call shape), and useForm.js renders the library's Message. useForm accepts both the old `{content, header, positive|negative}` shape and the new `{kind, title}`, because the pages building those objects migrate separately. Two library gaps closed, both found by call sites needing them: ActionInput is a forwardRef now. Its ref reaches the input rather than the wrapper, because callers use it to focus or select the field -- a ref to the surrounding div would do neither, which is why one call site had resorted to hand-copying the component's markup. Header takes a `color` token name. Three separate call sites had reached for an inline style to colour a heading that carries meaning ("3 items to add" against "2 to remove"), which is how a hex ends up in markup. Two files needed repair after being interrupted mid-edit: MapViewer had a Segment left in its error branch, and VideoPlayer had lost the useContext import its own code still used. Neither jest nor tsc noticed; only `npm run build` did, which is the whole reason that check exists. Fifteen files remain: Archive, BulkTagModal, Channels, Docs, FileBrowser, FilePreview, Flasher, Nav, Playlists, Theme.tsx, Upload, VideoPlayer, Zim, ControllerPage, admin/Downloads. Theme.tsx is deliberately last -- the unmigrated files still import its Semantic-backed wrappers, so it can only go once they have.
2026-07-30 12:21:30 -06:00
</Panel>;
}
if (loading) {
ui: thirty-five files off Semantic, leaving fifteen Phase 2, part 4. Baseline 50 -> 15. Migrated: all eight calculators, the rest of inventory and collections, the Map/Zim/Docs cluster, Videos, Files, Search, Archive, DashboardPage, DatesSelector, HelpModal, Events, Tags, Flasher's neighbours, admin Settings and Status, Download, and App. Also the three shared modules everything imports: api.js and customHooks.js swap the Semantic toast library for the library's shim (same call shape), and useForm.js renders the library's Message. useForm accepts both the old `{content, header, positive|negative}` shape and the new `{kind, title}`, because the pages building those objects migrate separately. Two library gaps closed, both found by call sites needing them: ActionInput is a forwardRef now. Its ref reaches the input rather than the wrapper, because callers use it to focus or select the field -- a ref to the surrounding div would do neither, which is why one call site had resorted to hand-copying the component's markup. Header takes a `color` token name. Three separate call sites had reached for an inline style to colour a heading that carries meaning ("3 items to add" against "2 to remove"), which is how a hex ends up in markup. Two files needed repair after being interrupted mid-edit: MapViewer had a Segment left in its error branch, and VideoPlayer had lost the useContext import its own code still used. Neither jest nor tsc noticed; only `npm run build` did, which is the whole reason that check exists. Fifteen files remain: Archive, BulkTagModal, Channels, Docs, FileBrowser, FilePreview, Flasher, Nav, Playlists, Theme.tsx, Upload, VideoPlayer, Zim, ControllerPage, admin/Downloads. Theme.tsx is deliberately last -- the unmigrated files still import its Semantic-backed wrappers, so it can only go once they have.
2026-07-30 12:21:30 -06:00
return <Panel><Loading/></Panel>;
}
if (!searchStr) {
ui: thirty-five files off Semantic, leaving fifteen Phase 2, part 4. Baseline 50 -> 15. Migrated: all eight calculators, the rest of inventory and collections, the Map/Zim/Docs cluster, Videos, Files, Search, Archive, DashboardPage, DatesSelector, HelpModal, Events, Tags, Flasher's neighbours, admin Settings and Status, Download, and App. Also the three shared modules everything imports: api.js and customHooks.js swap the Semantic toast library for the library's shim (same call shape), and useForm.js renders the library's Message. useForm accepts both the old `{content, header, positive|negative}` shape and the new `{kind, title}`, because the pages building those objects migrate separately. Two library gaps closed, both found by call sites needing them: ActionInput is a forwardRef now. Its ref reaches the input rather than the wrapper, because callers use it to focus or select the field -- a ref to the surrounding div would do neither, which is why one call site had resorted to hand-copying the component's markup. Header takes a `color` token name. Three separate call sites had reached for an inline style to colour a heading that carries meaning ("3 items to add" against "2 to remove"), which is how a hex ends up in markup. Two files needed repair after being interrupted mid-edit: MapViewer had a Segment left in its error branch, and VideoPlayer had lost the useContext import its own code still used. Neither jest nor tsc noticed; only `npm run build` did, which is the whole reason that check exists. Fifteen files remain: Archive, BulkTagModal, Channels, Docs, FileBrowser, FilePreview, Flasher, Nav, Playlists, Theme.tsx, Upload, VideoPlayer, Zim, ControllerPage, admin/Downloads. Theme.tsx is deliberately last -- the unmigrated files still import its Semantic-backed wrappers, so it can only go once they have.
2026-07-30 12:21:30 -06:00
return <Panel><Header as="h4">Enter a search query to find map locations.</Header></Panel>;
}
if (results.length === 0) {
ui: thirty-five files off Semantic, leaving fifteen Phase 2, part 4. Baseline 50 -> 15. Migrated: all eight calculators, the rest of inventory and collections, the Map/Zim/Docs cluster, Videos, Files, Search, Archive, DashboardPage, DatesSelector, HelpModal, Events, Tags, Flasher's neighbours, admin Settings and Status, Download, and App. Also the three shared modules everything imports: api.js and customHooks.js swap the Semantic toast library for the library's shim (same call shape), and useForm.js renders the library's Message. useForm accepts both the old `{content, header, positive|negative}` shape and the new `{kind, title}`, because the pages building those objects migrate separately. Two library gaps closed, both found by call sites needing them: ActionInput is a forwardRef now. Its ref reaches the input rather than the wrapper, because callers use it to focus or select the field -- a ref to the surrounding div would do neither, which is why one call site had resorted to hand-copying the component's markup. Header takes a `color` token name. Three separate call sites had reached for an inline style to colour a heading that carries meaning ("3 items to add" against "2 to remove"), which is how a hex ends up in markup. Two files needed repair after being interrupted mid-edit: MapViewer had a Segment left in its error branch, and VideoPlayer had lost the useContext import its own code still used. Neither jest nor tsc noticed; only `npm run build` did, which is the whole reason that check exists. Fifteen files remain: Archive, BulkTagModal, Channels, Docs, FileBrowser, FilePreview, Flasher, Nav, Playlists, Theme.tsx, Upload, VideoPlayer, Zim, ControllerPage, admin/Downloads. Theme.tsx is deliberately last -- the unmigrated files still import its Semantic-backed wrappers, so it can only go once they have.
2026-07-30 12:21:30 -06:00
return <Panel><Header as="h4">No map locations found for "{searchStr}".</Header></Panel>;
}
return <React.Fragment>
<CardGroup minWidth={275}>
{results.map((place, i) =>
<MapPlaceCard key={`${place.name}-${place.lat}-${place.lon}-${i}`} place={place} index={i}/>
)}
</CardGroup>
ui: centre the pagination strip It sat hard against the left edge of its container despite three call sites wrapping it in `<center>` or `text-align: center`. Mantine builds a Pagination as a BLOCK root -- full width, so those wrappers had nothing to centre -- holding a flex Group that carries the controls and lays them out at `flex-start`. So `justify-content` on the root does nothing at all; it has to reach the Group. Setting `--group-justify` on the root does not work either, which cost a detour: Group defaults that prop and writes the variable INLINE on its own element, where an inherited value cannot reach it. The property itself is fair game, since Mantine only ever reads the variable from a hashed class, so `.wrolpi-pagination .mantine-Group-root { justify-content: center }` at (0,2,0) is what lands. The three wrappers are now plain divs keeping their margin. `<center>` is a deprecated element that was doing nothing here. Two tests: the gap either side is equal -- measured, not read off `justify-content` -- with an assertion that the container really is wider than the strip, since otherwise the equality holds trivially at 0 and 0. And an inverse: at 240px nothing hangs off the LEFT edge. That one is the risk centring introduces -- overflow in a flex-start row spills right, where it can be scrolled to, but centred it spills both ways and the first page becomes unreachable. It passed before this change and must keep passing after. Verified red first (0px left against 230px right), and the wrong-element fix -- centring the root rather than the Group -- reddens it again. 1080 jest, 160 in ui-layout.cy.js, clean build. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 13:00:56 -06:00
<div style={{marginTop: '2em'}}>
<Paginator activePage={activePage} totalPages={totalPages} onPageChange={setPage}/>
ui: thirty-five files off Semantic, leaving fifteen Phase 2, part 4. Baseline 50 -> 15. Migrated: all eight calculators, the rest of inventory and collections, the Map/Zim/Docs cluster, Videos, Files, Search, Archive, DashboardPage, DatesSelector, HelpModal, Events, Tags, Flasher's neighbours, admin Settings and Status, Download, and App. Also the three shared modules everything imports: api.js and customHooks.js swap the Semantic toast library for the library's shim (same call shape), and useForm.js renders the library's Message. useForm accepts both the old `{content, header, positive|negative}` shape and the new `{kind, title}`, because the pages building those objects migrate separately. Two library gaps closed, both found by call sites needing them: ActionInput is a forwardRef now. Its ref reaches the input rather than the wrapper, because callers use it to focus or select the field -- a ref to the surrounding div would do neither, which is why one call site had resorted to hand-copying the component's markup. Header takes a `color` token name. Three separate call sites had reached for an inline style to colour a heading that carries meaning ("3 items to add" against "2 to remove"), which is how a hex ends up in markup. Two files needed repair after being interrupted mid-edit: MapViewer had a Segment left in its error branch, and VideoPlayer had lost the useContext import its own code still used. Neither jest nor tsc noticed; only `npm run build` did, which is the whole reason that check exists. Fifteen files remain: Archive, BulkTagModal, Channels, Docs, FileBrowser, FilePreview, Flasher, Nav, Playlists, Theme.tsx, Upload, VideoPlayer, Zim, ControllerPage, admin/Downloads. Theme.tsx is deliberately last -- the unmigrated files still import its Semantic-backed wrappers, so it can only go once they have.
2026-07-30 12:21:30 -06:00
</div>
</React.Fragment>;
}