-
Notifications
You must be signed in to change notification settings - Fork 12
feat: added markers styling #393
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
raven-wing
wants to merge
21
commits into
Problematy:next
Choose a base branch
from
raven-wing:different_icons
base: next
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
95bf915
feat: added markers styling
raven-wing c50fa7d
fixes
raven-wing 242cb5f
fix popup
raven-wing c76d8ef
some icon fixes
raven-wing b99e8e5
fix lint
raven-wing e15aa03
fix linting
raven-wing 30088a2
some fixes
raven-wing 6b2bc2c
fixes
raven-wing 049554d
some trims
raven-wing d063439
some fixes
raven-wing 3e19819
added missing marker
raven-wing e289645
cleanup
raven-wing 4ff6c8a
cleanup
raven-wing 0c3ee6a
cleanup comment
raven-wing 3597a66
more human readable icons
raven-wing 3457d33
some cleanup
raven-wing d2096e4
cleanup
raven-wing 121cd64
cleanup comments
raven-wing 56d1b13
cleanup
raven-wing d62f19d
fixes
raven-wing e2a608a
fixes
raven-wing File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| """ | ||
| Marker Styles Tests | ||
|
|
||
| Tests that the map picks pin icon/color per marker_styles (icon_field: | ||
| type_of_place, color_field: speed_limit - see e2e_test_data_initial.json), and | ||
| that a location with both a remark and a marker_styles match keeps its | ||
| type/color styling with an asterisk badge overlay, rather than losing it to a | ||
| plain, unstyled asterisk badge (see getTypedMarkerIcon.jsx/MarkerPopup.jsx). | ||
| """ | ||
|
|
||
| from playwright.sync_api import Page, expect | ||
|
|
||
| from tests.conftest import BASE_URL, MARKER_LOAD_TIMEOUT, open_test_popup | ||
|
|
||
| # "big bridge" and "small bridge" each get their own Phosphor Icons (MIT) type | ||
| # icon - see e2e_test_data_initial.json's marker_styles.icons and | ||
| # getTypedMarkerIcon.jsx (icon URLs are CSS mask-image'd onto the pin, tinted | ||
| # by the matched color, rather than embedded as inline SVG path data). | ||
| BIG_BRIDGE_TYPE_ICON_URL = ( | ||
| "https://cdn.jsdelivr.net/npm/@phosphor-icons/core@2/assets/fill/bridge-fill.svg" | ||
| ) | ||
| SMALL_BRIDGE_TYPE_ICON_URL = ( | ||
| "https://cdn.jsdelivr.net/npm/@phosphor-icons/core@2/assets/fill/footprints-fill.svg" | ||
| ) | ||
|
|
||
|
|
||
| class TestMarkerStyles: | ||
| """Test suite for marker_styles-driven pin icons/colors""" | ||
|
|
||
| def test_fast_bridge_marker_uses_type_icon_and_red_speed_color(self, page: Page): | ||
| """Pokoju (big bridge, speed_limit=50, no remark) is the only seeded bridge | ||
| with all three of lighting+benches+toilets (amenities is an "and" category - | ||
| see test_and_filter_within_category_narrows_results in test_map.py), so | ||
| checking all three isolates its marker without relying on clustering | ||
| distance/zoom assumptions.""" | ||
| page.goto(BASE_URL, wait_until="domcontentloaded") | ||
|
|
||
| # "cars" is checked by default (Pokoju is cars-accessible); narrow further. | ||
| for amenity in ("lighting", "benches", "toilets"): | ||
| page.get_by_role("checkbox", name=amenity, exact=False).click() | ||
|
|
||
| marker = page.locator(".custom-typed-marker-icon") | ||
| expect(marker).to_have_count(1, timeout=MARKER_LOAD_TIMEOUT) | ||
|
|
||
| # The pin shape itself (a masked div, not an inline <path>), filled with | ||
| # speed_limit=50's color. | ||
| pin = marker.locator(".custom-typed-marker-pin") | ||
| expect(pin).to_have_css("background-color", "rgb(198, 40, 40)") # #c62828 | ||
| # The type_of_place icon, configured for "big bridge" - masked onto a div | ||
| # via CSS rather than embedded as an inline <path>. | ||
| type_icon = marker.locator(".custom-typed-marker-type-icon") | ||
| expect(type_icon).to_have_count(1) | ||
| expect(type_icon).to_have_css("mask-image", f'url("{BIG_BRIDGE_TYPE_ICON_URL}")') | ||
| # No remark on Pokoju, so no asterisk badge. | ||
| expect(marker.locator("span")).to_have_count(0) | ||
|
|
||
| def test_slow_bridge_marker_uses_type_icon_and_green_speed_color(self, page: Page): | ||
| """Piaskowy (small bridge, speed_limit=10, no remark, toilets) is the | ||
| only seeded speed<=10 bridge with toilets - the other two speed=10 | ||
| bridges (Zwierzyniecka, Tumski) have lighting/benches but neither has | ||
| toilets, so combining the speed_limit=10 radio with the toilets | ||
| checkbox isolates it without relying on clustering distance/zoom | ||
| assumptions. "cars" is unchecked first since Piaskowy is | ||
| pedestrians-only.""" | ||
| page.goto(BASE_URL, wait_until="domcontentloaded") | ||
|
|
||
| page.get_by_role("checkbox", name="cars", exact=False).click() | ||
| page.get_by_role("radio", name="10 km/h", exact=False).click() | ||
| page.get_by_role("checkbox", name="toilets", exact=False).click() | ||
|
|
||
| marker = page.locator(".custom-typed-marker-icon") | ||
| expect(marker).to_have_count(1, timeout=MARKER_LOAD_TIMEOUT) | ||
|
|
||
| pin = marker.locator(".custom-typed-marker-pin") | ||
| expect(pin).to_have_css("background-color", "rgb(46, 125, 50)") # #2e7d32 (speed_limit=10) | ||
| type_icon = marker.locator(".custom-typed-marker-type-icon") | ||
| expect(type_icon).to_have_count(1) | ||
| expect(type_icon).to_have_css("mask-image", f'url("{SMALL_BRIDGE_TYPE_ICON_URL}")') | ||
| # No remark on Piaskowy, so no asterisk badge. | ||
| expect(marker.locator("span")).to_have_count(0) | ||
|
|
||
| def test_remarked_bridge_keeps_type_and_color_styling_with_asterisk_badge(self, page: Page): | ||
| """Zwierzyniecka has both a remark and marker_styles-matching fields | ||
| (small bridge, speed_limit=10) - it should render its normal typed/colored | ||
| pin plus an asterisk badge, not fall back to our own pin in the plain | ||
| fallback color with no type icon (every type_of_place/speed_limit value | ||
| happens to be covered by marker_styles in this seeded dataset, so that | ||
| fallback-color path isn't exercised here - it's covered at the unit | ||
| level instead, see getTypedMarkerIcon.test.jsx's "returns our own pin in | ||
| the fallback color with just the badge" case). Also guards against ever | ||
| reintroducing the old PNG-based asterisk icon this replaced. | ||
| """ | ||
| page.goto(BASE_URL, wait_until="domcontentloaded") | ||
| open_test_popup(page) | ||
|
|
||
| expect(page.locator('img[alt="Marker-Asterisk"]')).to_have_count(0) | ||
|
|
||
| marker = page.locator(".custom-typed-marker-icon") | ||
| expect(marker).to_have_count(1, timeout=MARKER_LOAD_TIMEOUT) | ||
|
|
||
| pin = marker.locator(".custom-typed-marker-pin") | ||
| expect(pin).to_have_css("background-color", "rgb(46, 125, 50)") # #2e7d32 (speed_limit=10) | ||
| type_icon = marker.locator(".custom-typed-marker-type-icon") | ||
| expect(type_icon).to_have_count(1) | ||
| expect(type_icon).to_have_css("mask-image", f'url("{SMALL_BRIDGE_TYPE_ICON_URL}")') | ||
| expect(marker.locator("span")).to_have_text("*") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
122 changes: 122 additions & 0 deletions
122
frontend/src/components/MarkerPopup/getTypedMarkerIcon.jsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| import React from 'react'; | ||
| import PropTypes from 'prop-types'; | ||
| import { DivIcon } from 'leaflet'; | ||
| import ReactDOMServer from 'react-dom/server'; | ||
| import PIN_SHAPE_URL from '../../res/svg/marker-pin.svg'; | ||
|
|
||
| const PIN_WIDTH = 45; | ||
| const PIN_HEIGHT = 50; | ||
| // The marker's default color (used whenever color_field doesn't match) is | ||
| // always the page's own secondary color, not a separately configurable value. | ||
| const FALLBACK_COLOR = globalThis.SECONDARY_COLOR || 'black'; | ||
|
|
||
| const TYPE_ICON_SIZE = 20; | ||
| const TYPE_ICON_OFFSET_TOP = 8; | ||
| const TYPE_ICON_OFFSET_LEFT = 12; | ||
|
|
||
| const maskStyle = (url, color) => ({ | ||
| backgroundColor: color, | ||
| WebkitMaskImage: `url(${url})`, | ||
| maskImage: `url(${url})`, | ||
| WebkitMaskSize: '100% 100%', | ||
| maskSize: '100% 100%', | ||
| WebkitMaskRepeat: 'no-repeat', | ||
| maskRepeat: 'no-repeat', | ||
| }); | ||
|
|
||
| /** | ||
| * Pin shape masked to `color`, optionally holding a type icon (`typeIconUrl`) | ||
| * inside its head, and an asterisk badge when `hasRemark` is set - so a | ||
| * remarked location keeps its type/color styling (or just its fallback color, | ||
| * if nothing else matched) instead of losing it to an unrelated asterisk icon. | ||
| */ | ||
| const PinIcon = ({ color, typeIconUrl, hasRemark }) => ( | ||
| <div style={{ position: 'relative', width: PIN_WIDTH, height: PIN_HEIGHT }}> | ||
| <div | ||
| className="custom-typed-marker-pin" | ||
| style={{ | ||
| position: 'absolute', | ||
| inset: 0, | ||
| filter: 'drop-shadow(0 0 1px #fff) drop-shadow(0 0 1px #fff)', | ||
| ...maskStyle(PIN_SHAPE_URL, color), | ||
| }} | ||
| /> | ||
| {typeIconUrl !== '' && ( | ||
| <div | ||
| className="custom-typed-marker-type-icon" | ||
| style={{ | ||
| position: 'absolute', | ||
| top: TYPE_ICON_OFFSET_TOP, | ||
| left: TYPE_ICON_OFFSET_LEFT, | ||
| width: TYPE_ICON_SIZE, | ||
| height: TYPE_ICON_SIZE, | ||
| ...maskStyle(typeIconUrl, '#ffffff'), | ||
| }} | ||
| /> | ||
| )} | ||
| {hasRemark && ( | ||
| <span | ||
| style={{ | ||
| position: 'absolute', | ||
| top: 1, | ||
| left: 24, | ||
| fontSize: 21, | ||
| fontWeight: 'bold', | ||
| lineHeight: 1, | ||
| color: '#ffffff', | ||
| textShadow: [-1, 1] | ||
| .flatMap(x => [-1, 1].map(y => `${x}px ${y}px 0 ${color}`)) | ||
| .join(', '), | ||
| }} | ||
| > | ||
| * | ||
| </span> | ||
| )} | ||
| </div> | ||
| ); | ||
|
|
||
| PinIcon.propTypes = { | ||
| color: PropTypes.string.isRequired, | ||
| typeIconUrl: PropTypes.string.isRequired, | ||
| hasRemark: PropTypes.bool.isRequired, | ||
| }; | ||
|
|
||
| /** | ||
| * Builds a Leaflet icon for `place`: colored/typed from the deployment's | ||
| * marker styling lookup table (window.MARKER_STYLES, see goodmap's | ||
| * db.get_marker_styles) when it matches, our own pin in the fallback color | ||
| * with just the asterisk badge when `place.has_remark` is set but nothing | ||
| * matched, or `null` (falls back to Leaflet's default marker) when there's | ||
| * neither a match nor a remark to show. | ||
| * | ||
| * @param {Object} place - Location data, as returned by GET /api/locations | ||
| * @returns {import('leaflet').DivIcon|null} | ||
| */ | ||
| const getTypedMarkerIcon = place => { | ||
| const markerStyles = globalThis.MARKER_STYLES || {}; | ||
| const { icon_field: iconField, color_field: colorField, icons, colors } = markerStyles; | ||
|
|
||
| const typeIconUrl = icons?.[place[iconField]] || ''; | ||
| const matchedColor = colors?.[place[colorField]] || ''; | ||
| const hasRemark = Boolean(place.has_remark); | ||
|
|
||
| if (!typeIconUrl && !matchedColor && !hasRemark) { | ||
| return null; | ||
| } | ||
|
|
||
| return new DivIcon({ | ||
| html: ReactDOMServer.renderToString( | ||
| <PinIcon | ||
| color={matchedColor || FALLBACK_COLOR} | ||
| typeIconUrl={typeIconUrl} | ||
| hasRemark={hasRemark} | ||
| />, | ||
| ), | ||
| className: 'custom-typed-marker-icon', | ||
| iconSize: [PIN_WIDTH, PIN_HEIGHT], | ||
| iconAnchor: [PIN_WIDTH / 2, PIN_HEIGHT], | ||
| popupAnchor: [0, -PIN_HEIGHT], | ||
| }); | ||
| }; | ||
|
|
||
| export default getTypedMarkerIcon; | ||
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.