From 87c4f6de55b95583bc8adb43177ab5f3439086a4 Mon Sep 17 00:00:00 2001 From: Peter Date: Mon, 13 Mar 2023 22:21:09 +0100 Subject: [PATCH 01/22] initial version --- src/App.tsx | 28 +++++------- src/SettingsContext.ts | 3 ++ src/ShowDistanceInMilesContext.ts | 2 - src/actions/Actions.ts | 2 + src/layers/PathDetailPopup.tsx | 4 +- src/pathDetails/PathDetails.tsx | 4 +- src/sidebar/MobileSidebar.tsx | 15 ++++--- src/sidebar/RoutingResults.tsx | 4 +- src/sidebar/SettingsBox.module.css | 4 ++ src/sidebar/SettingsBox.tsx | 45 +++++++++++++++++++ src/sidebar/instructions/Instructions.tsx | 4 +- src/sidebar/search/Search.module.css | 6 --- src/sidebar/search/Search.tsx | 11 +---- .../routingProfiles/RoutingProfiles.tsx | 32 ++++++------- src/stores/SettingsStore.ts | 9 +++- 15 files changed, 107 insertions(+), 66 deletions(-) create mode 100644 src/SettingsContext.ts delete mode 100644 src/ShowDistanceInMilesContext.ts create mode 100644 src/sidebar/SettingsBox.module.css create mode 100644 src/sidebar/SettingsBox.tsx diff --git a/src/App.tsx b/src/App.tsx index df6d41af..1357cc12 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react' +import { useContext, useEffect, useState } from 'react' import PathDetails from '@/pathDetails/PathDetails' import styles from './App.module.css' import { @@ -17,7 +17,7 @@ import MobileSidebar from '@/sidebar/MobileSidebar' import { useMediaQuery } from 'react-responsive' import RoutingResults from '@/sidebar/RoutingResults' import PoweredBy from '@/sidebar/PoweredBy' -import { QueryStoreState, RequestState } from '@/stores/QueryStore' +import { QueryStoreState } from '@/stores/QueryStore' import { RouteStoreState } from '@/stores/RouteStore' import { MapOptionsStoreState } from '@/stores/MapOptionsStore' import { ErrorStoreState } from '@/stores/ErrorStore' @@ -30,17 +30,19 @@ import ContextMenu from '@/layers/ContextMenu' import usePathDetailsLayer from '@/layers/UsePathDetailsLayer' import { Map } from 'ol' import { getMap } from '@/map/map' -import CustomModelBox from '@/sidebar/CustomModelBox' import useRoutingGraphLayer from '@/layers/UseRoutingGraphLayer' import useUrbanDensityLayer from '@/layers/UseUrbanDensityLayer' import useMapBorderLayer from '@/layers/UseMapBorderLayer' -import { ShowDistanceInMilesContext } from '@/ShowDistanceInMilesContext' +import { SettingsContext } from '@/SettingsContext' import RoutingProfiles from '@/sidebar/search/routingProfiles/RoutingProfiles' import MapPopups from '@/map/MapPopups' import Menu from '@/sidebar/menu.svg' import Cross from '@/sidebar/times-solid.svg' import PlainButton from '@/PlainButton' import useAreasLayer from '@/layers/UseAreasLayer' +import SettingsBox from '@/sidebar/SettingsBox' +import Dispatcher from '@/stores/Dispatcher' +import { ToggleShowSettings } from '@/actions/Actions' export const POPUP_CONTAINER_ID = 'popup-container' export const SIDEBAR_CONTENT_ID = 'sidebar-content' @@ -107,7 +109,7 @@ export default function App() { usePathDetailsLayer(map, pathDetails) const isSmallScreen = useMediaQuery({ query: '(max-width: 44rem)' }) return ( - +
@@ -131,7 +133,7 @@ export default function App() { /> )}
-
+ ) } @@ -157,15 +159,9 @@ function LargeScreenLayout({ query, route, map, error, mapOptions, encodedValues - Dispatcher.dispatch(new ToggleShowSettings())} /> +
{!error.isDismissed && }
- +
diff --git a/src/SettingsContext.ts b/src/SettingsContext.ts new file mode 100644 index 00000000..b859f770 --- /dev/null +++ b/src/SettingsContext.ts @@ -0,0 +1,3 @@ +import React from 'react' +import { Settings } from '@/stores/SettingsStore' +export const SettingsContext = React.createContext({ showDistanceInMiles: false, showSettings: false }) diff --git a/src/ShowDistanceInMilesContext.ts b/src/ShowDistanceInMilesContext.ts deleted file mode 100644 index 3498d311..00000000 --- a/src/ShowDistanceInMilesContext.ts +++ /dev/null @@ -1,2 +0,0 @@ -import React from 'react' -export const ShowDistanceInMilesContext = React.createContext(false) diff --git a/src/actions/Actions.ts b/src/actions/Actions.ts index 73e3367a..06d83250 100644 --- a/src/actions/Actions.ts +++ b/src/actions/Actions.ts @@ -225,3 +225,5 @@ export class InstructionClicked implements Action { } export class ToggleDistanceUnits implements Action {} + +export class ToggleShowSettings implements Action {} diff --git a/src/layers/PathDetailPopup.tsx b/src/layers/PathDetailPopup.tsx index f07db87f..beacc8a5 100644 --- a/src/layers/PathDetailPopup.tsx +++ b/src/layers/PathDetailPopup.tsx @@ -2,7 +2,7 @@ import { useContext } from 'react' import styles from '@/layers/DefaultMapPopup.module.css' import { PathDetailsStoreState } from '@/stores/PathDetailsStore' import { metersToText } from '@/Converters' -import { ShowDistanceInMilesContext } from '@/ShowDistanceInMilesContext' +import { SettingsContext } from '@/SettingsContext' import MapPopup from '@/layers/MapPopup' import { Map } from 'ol' @@ -15,7 +15,7 @@ interface PathDetailPopupProps { * The popup shown along the selected route when we hover the path detail/elevation graph */ export default function PathDetailPopup({ map, pathDetails }: PathDetailPopupProps) { - const showDistanceInMiles = useContext(ShowDistanceInMilesContext) + const { showDistanceInMiles } = useContext(SettingsContext) return ( // todo: use createMapMarker from heightgraph? // {createMapMarker(point.elevation, point.description, showDistanceInMiles)} diff --git a/src/pathDetails/PathDetails.tsx b/src/pathDetails/PathDetails.tsx index 5cdff25c..9ca7266a 100644 --- a/src/pathDetails/PathDetails.tsx +++ b/src/pathDetails/PathDetails.tsx @@ -8,7 +8,7 @@ import { PathDetailsElevationSelected, PathDetailsHover, PathDetailsRangeSelecte import QueryStore, { Coordinate, QueryPointType } from '@/stores/QueryStore' import { Position } from 'geojson' import { calcDist } from '@/distUtils' -import { ShowDistanceInMilesContext } from '@/ShowDistanceInMilesContext' +import { SettingsContext } from '@/SettingsContext' interface PathDetailsProps { selectedPath: Path @@ -52,7 +52,7 @@ export default function ({ selectedPath }: PathDetailsProps) { graph?.setData(pathDetailsData.data, pathDetailsData.mappings) }, [selectedPath, graph]) - const showDistanceInMiles = useContext(ShowDistanceInMilesContext) + const { showDistanceInMiles } = useContext(SettingsContext) useEffect(() => { graph?.setImperial(showDistanceInMiles) graph?.redraw() diff --git a/src/sidebar/MobileSidebar.tsx b/src/sidebar/MobileSidebar.tsx index 1c683f19..7e378ff1 100644 --- a/src/sidebar/MobileSidebar.tsx +++ b/src/sidebar/MobileSidebar.tsx @@ -1,5 +1,5 @@ -import { useCallback, useEffect, useRef, useState } from 'react' -import { QueryPoint, QueryPointType, QueryStoreState } from '@/stores/QueryStore' +import { useCallback, useContext, useEffect, useRef, useState } from 'react' +import { QueryPoint, QueryPointType, QueryStoreState, RequestState } from '@/stores/QueryStore' import { RouteStoreState } from '@/stores/RouteStore' import { ErrorStoreState } from '@/stores/ErrorStore' import styles from './MobileSidebar.module.css' @@ -10,14 +10,19 @@ import { MarkerComponent } from '@/map/Marker' import RoutingProfiles from '@/sidebar/search/routingProfiles/RoutingProfiles' import OpenInputsIcon from './unfold.svg' import CloseInputsIcon from './unfold_less.svg' +import SettingsBox from '@/sidebar/SettingsBox' +import { SettingsContext } from '@/SettingsContext' +import Dispatcher from '@/stores/Dispatcher' +import { ToggleShowSettings } from '@/actions/Actions' type MobileSidebarProps = { query: QueryStoreState route: RouteStoreState error: ErrorStoreState + encodedValues: object[] } -export default function ({ query, route, error }: MobileSidebarProps) { +export default function ({ query, route, error, encodedValues }: MobileSidebarProps) { // the following three elements control, whether the small search view is displayed const isShortScreen = useMediaQuery({ query: '(max-height: 55rem)' }) const [isSmallSearchView, setIsSmallSearchView] = useState(isShortScreen && hasResult(route)) @@ -55,9 +60,9 @@ export default function ({ query, route, error }: MobileSidebarProps) { Dispatcher.dispatch(new ToggleShowSettings())} /> +
)} diff --git a/src/sidebar/RoutingResults.tsx b/src/sidebar/RoutingResults.tsx index 433cc0bb..59d5b556 100644 --- a/src/sidebar/RoutingResults.tsx +++ b/src/sidebar/RoutingResults.tsx @@ -13,7 +13,7 @@ import { LineString, Position } from 'geojson' import { calcDist } from '@/distUtils' import { useMediaQuery } from 'react-responsive' import { tr } from '@/translation/Translation' -import { ShowDistanceInMilesContext } from '@/ShowDistanceInMilesContext' +import { SettingsContext } from '@/SettingsContext' import { ApiImpl } from '@/api/Api' export interface RoutingResultsProps { @@ -58,7 +58,7 @@ function RoutingResult({ path, isSelected, profile }: { path: Path; isSelected: hasFootways || hasSteepSegments - const showDistanceInMiles = useContext(ShowDistanceInMilesContext) + const { showDistanceInMiles } = useContext(SettingsContext) return (
diff --git a/src/sidebar/SettingsBox.module.css b/src/sidebar/SettingsBox.module.css new file mode 100644 index 00000000..836e34ff --- /dev/null +++ b/src/sidebar/SettingsBox.module.css @@ -0,0 +1,4 @@ +.mileskm { + color: gray; + font-size: 14px; +} diff --git a/src/sidebar/SettingsBox.tsx b/src/sidebar/SettingsBox.tsx new file mode 100644 index 00000000..9af67811 --- /dev/null +++ b/src/sidebar/SettingsBox.tsx @@ -0,0 +1,45 @@ +import { QueryStoreState, RequestState } from '@/stores/QueryStore' +import CustomModelBox from '@/sidebar/CustomModelBox' +import { ClearRoute, DismissLastError, SetCustomModelBoxEnabled, ToggleDistanceUnits } from '@/actions/Actions' +import Dispatcher from '@/stores/Dispatcher' +import styles from '@/sidebar/SettingsBox.module.css' +import { tr } from '@/translation/Translation' +import PlainButton from '@/PlainButton' +import { useContext } from 'react' +import { SettingsContext } from '@/SettingsContext' + +export default function SettingsBox({ query, encodedValues }: { query: QueryStoreState; encodedValues: object[] }) { + const settings = useContext(SettingsContext) + return !settings.showSettings ? ( + <> + ) : ( + <> + Dispatcher.dispatch(new ToggleDistanceUnits())} + > + {tr('distance_unit', [tr(settings.showDistanceInMiles ? 'miles' : 'kilometer')])} + +
+ { + if (query.customModelEnabled) Dispatcher.dispatch(new DismissLastError()) + Dispatcher.dispatch(new ClearRoute()) + Dispatcher.dispatch(new SetCustomModelBoxEnabled(!query.customModelEnabled)) + }} + />{' '} +
custom model
+
+ + + ) +} diff --git a/src/sidebar/instructions/Instructions.tsx b/src/sidebar/instructions/Instructions.tsx index ec4eb293..f105d06e 100644 --- a/src/sidebar/instructions/Instructions.tsx +++ b/src/sidebar/instructions/Instructions.tsx @@ -21,7 +21,7 @@ import { metersToText } from '@/Converters' import { Instruction } from '@/api/graphhopper' import { MarkerComponent } from '@/map/Marker' import QueryStore, { QueryPointType } from '@/stores/QueryStore' -import { ShowDistanceInMilesContext } from '@/ShowDistanceInMilesContext' +import { SettingsContext } from '@/SettingsContext' import Dispatcher from '@/stores/Dispatcher' import { InstructionClicked } from '@/actions/Actions' @@ -36,7 +36,7 @@ export default function (props: { instructions: Instruction[] }) { } const Line = function ({ instruction, index }: { instruction: Instruction; index: number }) { - const showDistanceInMiles = useContext(ShowDistanceInMilesContext) + const { showDistanceInMiles } = useContext(SettingsContext) return (
  • @@ -66,13 +66,6 @@ export default function Search({ points }: { points: QueryPoint[] }) {
    {tr('add_to_route')}
    - Dispatcher.dispatch(new ToggleDistanceUnits())} - > - {showDistanceInMiles ? 'mi' : 'km'} - setShowInfo(!showInfo)}> diff --git a/src/sidebar/search/routingProfiles/RoutingProfiles.tsx b/src/sidebar/search/routingProfiles/RoutingProfiles.tsx index 1e4e1a1f..c1050b99 100644 --- a/src/sidebar/search/routingProfiles/RoutingProfiles.tsx +++ b/src/sidebar/search/routingProfiles/RoutingProfiles.tsx @@ -1,7 +1,7 @@ -import React from 'react' +import React, { useContext } from 'react' import styles from './RoutingProfiles.modules.css' import Dispatcher from '@/stores/Dispatcher' -import { ClearRoute, DismissLastError, SetCustomModelBoxEnabled, SetVehicleProfile } from '@/actions/Actions' +import { SetVehicleProfile } from '@/actions/Actions' import { RoutingProfile } from '@/api/graphhopper' import PlainButton from '@/PlainButton' import BicycleIcon from './bike.svg' @@ -17,33 +17,27 @@ import TruckIcon from './truck.svg' import WheelchairIcon from './wheelchair.svg' import { tr } from '@/translation/Translation' import SettingsSVG from '@/sidebar/settings.svg' +import { SettingsContext } from '@/SettingsContext' export default function ({ routingProfiles, selectedProfile, - customModelAllowed, - customModelEnabled, + openSettingsHandle, }: { routingProfiles: RoutingProfile[] selectedProfile: RoutingProfile - customModelAllowed: boolean - customModelEnabled: boolean + openSettingsHandle: () => void }) { + const { showSettings } = useContext(SettingsContext) return (
    - {customModelAllowed && ( - { - if (customModelEnabled) Dispatcher.dispatch(new DismissLastError()) - Dispatcher.dispatch(new ClearRoute()) - Dispatcher.dispatch(new SetCustomModelBoxEnabled(!customModelEnabled)) - }} - > - - - )} + + +
      {routingProfiles.map(profile => { const className = diff --git a/src/stores/SettingsStore.ts b/src/stores/SettingsStore.ts index 10c74293..58c02a03 100644 --- a/src/stores/SettingsStore.ts +++ b/src/stores/SettingsStore.ts @@ -1,15 +1,17 @@ import Store from '@/stores/Store' import { Action } from '@/stores/Dispatcher' -import { ToggleDistanceUnits } from '@/actions/Actions' +import { ToggleDistanceUnits, ToggleShowSettings } from '@/actions/Actions' export interface Settings { showDistanceInMiles: boolean + showSettings: boolean } export default class SettingsStore extends Store { constructor() { super({ showDistanceInMiles: false, + showSettings: false, }) } @@ -19,6 +21,11 @@ export default class SettingsStore extends Store { ...state, showDistanceInMiles: !state.showDistanceInMiles, } + } else if (action instanceof ToggleShowSettings) { + return { + ...state, + showSettings: !state.showSettings, + } } return state } From a6e43216c4ed1fde4925f7e4df217e51f59babfb Mon Sep 17 00:00:00 2001 From: Peter Date: Wed, 15 Mar 2023 18:19:58 +0100 Subject: [PATCH 02/22] custom model is kept even when switching back and forth between desktop and mobile size --- src/App.tsx | 15 +++-- src/NavBar.ts | 17 +++-- src/SettingsContext.ts | 1 - src/index.tsx | 7 +- src/layers/PathDetailPopup.tsx | 2 +- src/pathDetails/PathDetails.tsx | 2 +- src/sidebar/CustomModelBox.tsx | 32 ++++----- src/sidebar/MobileSidebar.tsx | 10 ++- src/sidebar/RoutingResults.tsx | 2 +- src/sidebar/SettingsBox.module.css | 14 +++- src/sidebar/SettingsBox.tsx | 46 ++++++------- src/sidebar/instructions/Instructions.tsx | 2 +- src/sidebar/search/Search.tsx | 2 - .../routingProfiles/RoutingProfiles.tsx | 7 +- src/sidebar/toggle_off.svg | 1 + src/sidebar/toggle_on.svg | 1 + src/stores/QueryStore.ts | 65 +++++++++---------- src/stores/SettingsStore.ts | 36 +++++++++- test/NavBar.test.ts | 6 +- test/stores/QueryStore.test.ts | 35 ++++++---- test/stores/RouteStore.test.ts | 3 +- 21 files changed, 183 insertions(+), 123 deletions(-) create mode 100644 src/sidebar/toggle_off.svg create mode 100644 src/sidebar/toggle_on.svg diff --git a/src/App.tsx b/src/App.tsx index 1357cc12..0cbad3c6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import { useContext, useEffect, useState } from 'react' +import { useEffect, useState } from 'react' import PathDetails from '@/pathDetails/PathDetails' import styles from './App.module.css' import { @@ -17,7 +17,7 @@ import MobileSidebar from '@/sidebar/MobileSidebar' import { useMediaQuery } from 'react-responsive' import RoutingResults from '@/sidebar/RoutingResults' import PoweredBy from '@/sidebar/PoweredBy' -import { QueryStoreState } from '@/stores/QueryStore' +import { QueryStoreState, RequestState } from '@/stores/QueryStore' import { RouteStoreState } from '@/stores/RouteStore' import { MapOptionsStoreState } from '@/stores/MapOptionsStore' import { ErrorStoreState } from '@/stores/ErrorStore' @@ -33,7 +33,6 @@ import { getMap } from '@/map/map' import useRoutingGraphLayer from '@/layers/UseRoutingGraphLayer' import useUrbanDensityLayer from '@/layers/UseUrbanDensityLayer' import useMapBorderLayer from '@/layers/UseMapBorderLayer' -import { SettingsContext } from '@/SettingsContext' import RoutingProfiles from '@/sidebar/search/routingProfiles/RoutingProfiles' import MapPopups from '@/map/MapPopups' import Menu from '@/sidebar/menu.svg' @@ -43,6 +42,8 @@ import useAreasLayer from '@/layers/UseAreasLayer' import SettingsBox from '@/sidebar/SettingsBox' import Dispatcher from '@/stores/Dispatcher' import { ToggleShowSettings } from '@/actions/Actions' +import CustomModelBox from '@/sidebar/CustomModelBox' +import { SettingsContext } from '@/stores/SettingsStore' export const POPUP_CONTAINER_ID = 'popup-container' export const SIDEBAR_CONTENT_ID = 'sidebar-content' @@ -101,7 +102,7 @@ export default function App() { // our different map layers useBackgroundLayer(map, mapOptions.selectedStyle) useMapBorderLayer(map, info.bbox) - useAreasLayer(map, query.customModelEnabled && query.customModelValid ? query.customModel?.areas! : null) + useAreasLayer(map, settings.customModelEnabled && settings.customModelValid ? query.customModel?.areas! : null) useRoutingGraphLayer(map, mapOptions.routingGraphEnabled) useUrbanDensityLayer(map, mapOptions.urbanDensityEnabled) usePathsLayer(map, route.routingResult.paths, route.selectedPath) @@ -161,7 +162,11 @@ function LargeScreenLayout({ query, route, map, error, mapOptions, encodedValues selectedProfile={query.routingProfile} openSettingsHandle={() => Dispatcher.dispatch(new ToggleShowSettings())} /> - + +
      {!error.isDismissed && }
      await this.updateStateFromUrl()) } @@ -26,7 +29,12 @@ export default class NavBar { this.mapStore.register(() => this.updateUrlFromState()) } - private static createUrl(baseUrl: string, queryStoreState: QueryStoreState, mapState: MapOptionsStoreState) { + private static createUrl( + baseUrl: string, + queryStoreState: QueryStoreState, + mapState: MapOptionsStoreState, + addCustomModel: boolean + ) { const result = new URL(baseUrl) if (queryStoreState.queryPoints.filter(point => point.isInitialized).length > 0) { queryStoreState.queryPoints @@ -36,7 +44,7 @@ export default class NavBar { result.searchParams.append('profile', queryStoreState.routingProfile.name) result.searchParams.append('layer', mapState.selectedStyle.name) - if (queryStoreState.customModelEnabled && queryStoreState.customModel && queryStoreState.customModelValid) + if (addCustomModel && queryStoreState.customModel) result.searchParams.append('custom_model', JSON.stringify(queryStoreState.customModel)) return result @@ -156,7 +164,8 @@ export default class NavBar { return NavBar.createUrl( window.location.origin + window.location.pathname, this.queryStore.state, - this.mapStore.state + this.mapStore.state, + this.settingsStore.state.customModelEnabled && this.settingsStore.state.customModelValid ).toString() } diff --git a/src/SettingsContext.ts b/src/SettingsContext.ts index b859f770..7b3aba6c 100644 --- a/src/SettingsContext.ts +++ b/src/SettingsContext.ts @@ -1,3 +1,2 @@ import React from 'react' import { Settings } from '@/stores/SettingsStore' -export const SettingsContext = React.createContext({ showDistanceInMiles: false, showSettings: false }) diff --git a/src/index.tsx b/src/index.tsx index 49dbc250..4554f387 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -41,11 +41,12 @@ const apiKey = url.searchParams.has('key') ? url.searchParams.get('key') : confi setApi(config.routingApi, config.geocodingApi, apiKey || '') const initialCustomModelStr = url.searchParams.get('custom_model') -const queryStore = new QueryStore(getApi(), initialCustomModelStr) +const settingsStore = new SettingsStore(initialCustomModelStr) +const queryStore = new QueryStore(getApi(), settingsStore) const routeStore = new RouteStore(queryStore) setStores({ - settingsStore: new SettingsStore(), + settingsStore: settingsStore, queryStore: queryStore, routeStore: routeStore, infoStore: new ApiInfoStore(), @@ -72,7 +73,7 @@ const smallScreenMediaQuery = window.matchMedia('(max-width: 44rem)') const mapActionReceiver = new MapActionReceiver(getMap(), routeStore, () => smallScreenMediaQuery.matches) Dispatcher.register(mapActionReceiver) -const navBar = new NavBar(getQueryStore(), getMapOptionsStore()) +const navBar = new NavBar(getQueryStore(), getMapOptionsStore(), getSettingsStore()) // get infos about the api as soon as possible getApi() diff --git a/src/layers/PathDetailPopup.tsx b/src/layers/PathDetailPopup.tsx index beacc8a5..090fd748 100644 --- a/src/layers/PathDetailPopup.tsx +++ b/src/layers/PathDetailPopup.tsx @@ -2,9 +2,9 @@ import { useContext } from 'react' import styles from '@/layers/DefaultMapPopup.module.css' import { PathDetailsStoreState } from '@/stores/PathDetailsStore' import { metersToText } from '@/Converters' -import { SettingsContext } from '@/SettingsContext' import MapPopup from '@/layers/MapPopup' import { Map } from 'ol' +import { SettingsContext } from '@/stores/SettingsStore' interface PathDetailPopupProps { map: Map diff --git a/src/pathDetails/PathDetails.tsx b/src/pathDetails/PathDetails.tsx index 9ca7266a..90b047a4 100644 --- a/src/pathDetails/PathDetails.tsx +++ b/src/pathDetails/PathDetails.tsx @@ -8,7 +8,7 @@ import { PathDetailsElevationSelected, PathDetailsHover, PathDetailsRangeSelecte import QueryStore, { Coordinate, QueryPointType } from '@/stores/QueryStore' import { Position } from 'geojson' import { calcDist } from '@/distUtils' -import { SettingsContext } from '@/SettingsContext' +import { SettingsContext } from '@/stores/SettingsStore' interface PathDetailsProps { selectedPath: Path diff --git a/src/sidebar/CustomModelBox.tsx b/src/sidebar/CustomModelBox.tsx index ab8017c2..21474468 100644 --- a/src/sidebar/CustomModelBox.tsx +++ b/src/sidebar/CustomModelBox.tsx @@ -4,13 +4,14 @@ import 'codemirror/addon/lint/lint.css' // todo: this belongs to this app and we should not take it from the demo... import 'custom-model-editor/demo/style.css' import styles from '@/sidebar/CustomModelBox.module.css' -import { useCallback, useEffect, useRef, useState } from 'react' +import { useCallback, useContext, useEffect, useRef, useState } from 'react' import { create } from 'custom-model-editor/src/index' import Dispatcher from '@/stores/Dispatcher' -import { DismissLastError, SetCustomModel } from '@/actions/Actions' +import { SetCustomModel } from '@/actions/Actions' import { CustomModel } from '@/stores/QueryStore' import { tr } from '@/translation/Translation' import PlainButton from '@/PlainButton' +import { SettingsContext } from '@/stores/SettingsStore' const examples: { [key: string]: CustomModel } = { default_example: { @@ -71,18 +72,12 @@ const examples: { [key: string]: CustomModel } = { } export interface CustomModelBoxProps { - enabled: boolean encodedValues: object[] - initialCustomModelStr: string | null queryOngoing: boolean } -export default function CustomModelBox({ - enabled, - encodedValues, - initialCustomModelStr, - queryOngoing, -}: CustomModelBoxProps) { +export default function CustomModelBox({ encodedValues, queryOngoing }: CustomModelBoxProps) { + let { initialCustomModelStr, customModelEnabled, showSettings, customModel } = useContext(SettingsContext) // todo: add types for custom model editor later const [editor, setEditor] = useState() const [isValid, setIsValid] = useState(false) @@ -94,18 +89,23 @@ export default function CustomModelBox({ setEditor(instance) instance.cm.setSize('100%', '100%') - if (initialCustomModelStr != null) { + if (customModel != null) { + // init from settings in case of entire app recreation like window resizing + initialCustomModelStr = customModel2prettyString(customModel) + } else if (initialCustomModelStr != null) { // prettify the custom model if it can be parsed or leave it as is otherwise try { initialCustomModelStr = customModel2prettyString(JSON.parse(initialCustomModelStr)) } catch (e) {} } + // TODO NOW when changing the size from "desktop" to "mobile" and back we loose the custom model + console.warn("initial custom model string was set? " + initialCustomModelStr != null) instance.value = initialCustomModelStr == null ? customModel2prettyString(examples['default_example']) : initialCustomModelStr - if (enabled) + if (customModelEnabled) // When we got a custom model from the url parameters we send the request right away dispatchCustomModel(instance.value, true, true) @@ -121,8 +121,8 @@ export default function CustomModelBox({ // without this the editor is blank after opening the box and before clicking it or resizing the window? // but having the focus in the box after opening it is nice anyway useEffect(() => { - if (enabled) editor?.cm.focus() - }, [enabled]) + if (customModelEnabled && showSettings) editor?.cm.focus() + }, [customModelEnabled, showSettings]) useEffect(() => { if (!editor) return @@ -160,10 +160,10 @@ export default function CustomModelBox({
      - {enabled && ( + {customModelEnabled && showSettings && (
      { - if (query.customModelEnabled) Dispatcher.dispatch(new DismissLastError()) +
      + {tr('distance_unit', [tr(settings.showDistanceInMiles ? 'mi' : 'km')])} +
      + { + if (settings.customModelEnabled) Dispatcher.dispatch(new DismissLastError()) Dispatcher.dispatch(new ClearRoute()) - Dispatcher.dispatch(new SetCustomModelBoxEnabled(!query.customModelEnabled)) + Dispatcher.dispatch(new SetCustomModelBoxEnabled(!settings.customModelEnabled)) }} - />{' '} -
      custom model
      -
      - - + > + {settings.customModelEnabled ? : } + +
      {tr('custom model enabled')}
      +
      ) } diff --git a/src/sidebar/instructions/Instructions.tsx b/src/sidebar/instructions/Instructions.tsx index f105d06e..e7432620 100644 --- a/src/sidebar/instructions/Instructions.tsx +++ b/src/sidebar/instructions/Instructions.tsx @@ -21,7 +21,7 @@ import { metersToText } from '@/Converters' import { Instruction } from '@/api/graphhopper' import { MarkerComponent } from '@/map/Marker' import QueryStore, { QueryPointType } from '@/stores/QueryStore' -import { SettingsContext } from '@/SettingsContext' +import { SettingsContext } from '@/stores/SettingsStore' import Dispatcher from '@/stores/Dispatcher' import { InstructionClicked } from '@/actions/Actions' diff --git a/src/sidebar/search/Search.tsx b/src/sidebar/search/Search.tsx index 1e7c8918..f22f6401 100644 --- a/src/sidebar/search/Search.tsx +++ b/src/sidebar/search/Search.tsx @@ -20,14 +20,12 @@ import PlainButton from '@/PlainButton' import AddressInput from '@/sidebar/search/AddressInput' import { MarkerComponent } from '@/map/Marker' import { tr } from '@/translation/Translation' -import { SettingsContext } from '@/SettingsContext' export default function Search({ points }: { points: QueryPoint[] }) { const [showInfo, setShowInfo] = useState(false) const [showTargetIcons, setShowTargetIcons] = useState(true) const [moveStartIndex, onMoveStartSelect] = useState(-1) const [dropPreviewIndex, onDropPreviewSelect] = useState(-1) - const { showDistanceInMiles } = useContext(SettingsContext) return (
      diff --git a/src/sidebar/search/routingProfiles/RoutingProfiles.tsx b/src/sidebar/search/routingProfiles/RoutingProfiles.tsx index c1050b99..8a97bc9f 100644 --- a/src/sidebar/search/routingProfiles/RoutingProfiles.tsx +++ b/src/sidebar/search/routingProfiles/RoutingProfiles.tsx @@ -17,7 +17,7 @@ import TruckIcon from './truck.svg' import WheelchairIcon from './wheelchair.svg' import { tr } from '@/translation/Translation' import SettingsSVG from '@/sidebar/settings.svg' -import { SettingsContext } from '@/SettingsContext' +import { SettingsContext } from '@/stores/SettingsStore' export default function ({ routingProfiles, @@ -28,12 +28,13 @@ export default function ({ selectedProfile: RoutingProfile openSettingsHandle: () => void }) { - const { showSettings } = useContext(SettingsContext) + const settings = useContext(SettingsContext) return (
      diff --git a/src/sidebar/toggle_off.svg b/src/sidebar/toggle_off.svg new file mode 100644 index 00000000..a83c2d72 --- /dev/null +++ b/src/sidebar/toggle_off.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/sidebar/toggle_on.svg b/src/sidebar/toggle_on.svg new file mode 100644 index 00000000..17dc978c --- /dev/null +++ b/src/sidebar/toggle_on.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/stores/QueryStore.ts b/src/stores/QueryStore.ts index 988c354d..2b999bb6 100644 --- a/src/stores/QueryStore.ts +++ b/src/stores/QueryStore.ts @@ -21,6 +21,7 @@ import { import { RoutingArgs, RoutingProfile } from '@/api/graphhopper' import { calcDist } from '@/distUtils' import config from 'config' +import SettingsStore from '@/stores/SettingsStore' export interface Coordinate { lat: number @@ -34,13 +35,9 @@ export interface QueryStoreState { readonly currentRequest: CurrentRequest readonly maxAlternativeRoutes: number readonly routingProfile: RoutingProfile - readonly customModelEnabled: boolean - readonly customModelValid: boolean readonly customModel: CustomModel | null // todo: probably this should go somewhere else, see: https://github.com/graphhopper/graphhopper-maps/pull/193 readonly zoom: boolean - // todo: ... and this also - readonly initialCustomModelStr: string | null } export interface QueryPoint { @@ -82,13 +79,15 @@ export interface SubRequest { export default class QueryStore extends Store { private readonly api: Api + private readonly settingsStore: SettingsStore - constructor(api: Api, initialCustomModelStr: string | null = null) { - super(QueryStore.getInitialState(initialCustomModelStr)) + constructor(api: Api, settingsStore: SettingsStore) { + super(QueryStore.getInitialState()) this.api = api + this.settingsStore = settingsStore } - private static getInitialState(initialCustomModelStr: string | null): QueryStoreState { + private static getInitialState(): QueryStoreState { return { profiles: [], queryPoints: [ @@ -103,11 +102,8 @@ export default class QueryStore extends Store { routingProfile: { name: '', }, - customModelEnabled: initialCustomModelStr != null, - customModelValid: false, customModel: null, zoom: true, - initialCustomModelStr: initialCustomModelStr, } } @@ -263,7 +259,6 @@ export default class QueryStore extends Store { const newState: QueryStoreState = { ...state, customModel: action.customModel, - customModelValid: action.valid, } if (action.issueRouteRequest) return this.routeIfReady(newState) @@ -271,11 +266,7 @@ export default class QueryStore extends Store { } else if (action instanceof RouteRequestSuccess || action instanceof RouteRequestFailed) { return QueryStore.handleFinishedRequest(state, action) } else if (action instanceof SetCustomModelBoxEnabled) { - const newState: QueryStoreState = { - ...state, - customModelEnabled: action.enabled, - } - return this.routeIfReady(newState) + return this.routeIfReady(state) } return state } @@ -296,20 +287,25 @@ export default class QueryStore extends Store { } private routeIfReady(state: QueryStoreState): QueryStoreState { - if (QueryStore.isReadyToRoute(state)) { + const cmEnabled = this.settingsStore.state.customModelEnabled + const cmValid = this.settingsStore.state.customModelValid + if (QueryStore.isReadyToRoute(state, cmEnabled, cmValid)) { let requests const maxDistance = getMaxDistance(state.queryPoints) - if (state.customModelEnabled) { + if (cmEnabled) { if (maxDistance < 200_000) { // Use a single request, possibly including alternatives when custom models are enabled. - requests = [QueryStore.buildRouteRequest(state)] + requests = [QueryStore.buildRouteRequest(state, cmEnabled)] } else if (maxDistance < 500_000) { // Force no alternatives for longer custom model routes. requests = [ - QueryStore.buildRouteRequest({ - ...state, - maxAlternativeRoutes: 1, - }), + QueryStore.buildRouteRequest( + { + ...state, + maxAlternativeRoutes: 1, + }, + cmEnabled + ), ] } else { // Custom model requests with large distances take too long, so we just error. @@ -328,10 +324,13 @@ export default class QueryStore extends Store { } else { requests = [ // We first send a fast request without alternatives ... - QueryStore.buildRouteRequest({ - ...state, - maxAlternativeRoutes: 1, - }), + QueryStore.buildRouteRequest( + { + ...state, + maxAlternativeRoutes: 1, + }, + cmEnabled + ), ] // ... and then a second, slower request including alternatives if they are enabled. if ( @@ -339,7 +338,7 @@ export default class QueryStore extends Store { state.maxAlternativeRoutes > 1 && (ApiImpl.isMotorVehicle(state.routingProfile.name) || maxDistance < 500_000) ) - requests.push(QueryStore.buildRouteRequest(state)) + requests.push(QueryStore.buildRouteRequest(state, cmEnabled)) } return { @@ -362,10 +361,10 @@ export default class QueryStore extends Store { return subRequests } - private static isReadyToRoute(state: QueryStoreState) { + private static isReadyToRoute(state: QueryStoreState, cmEnabled: boolean, cmValid: boolean) { // deliberately chose this style of if statements, to make this readable. - if (state.customModelEnabled && !state.customModel) return false - if (state.customModelEnabled && state.customModel && !state.customModelValid) return false + if (cmEnabled && !state.customModel) return false + if (cmEnabled && state.customModel && !cmValid) return false if (state.queryPoints.length <= 1) return false if (!state.queryPoints.every(point => point.isInitialized)) return false if (!state.routingProfile.name) return false @@ -424,7 +423,7 @@ export default class QueryStore extends Store { return QueryPointType.Via } - private static buildRouteRequest(state: QueryStoreState): RoutingArgs { + private static buildRouteRequest(state: QueryStoreState, cmEnabled: boolean): RoutingArgs { const coordinates = state.queryPoints.map(point => [point.coordinate.lng, point.coordinate.lat]) as [ number, number @@ -434,7 +433,7 @@ export default class QueryStore extends Store { points: coordinates, profile: state.routingProfile.name, maxAlternativeRoutes: state.maxAlternativeRoutes, - customModel: state.customModelEnabled ? state.customModel : null, + customModel: cmEnabled ? state.customModel : null, zoom: state.zoom, } } diff --git a/src/stores/SettingsStore.ts b/src/stores/SettingsStore.ts index 58c02a03..7460efe2 100644 --- a/src/stores/SettingsStore.ts +++ b/src/stores/SettingsStore.ts @@ -1,17 +1,36 @@ import Store from '@/stores/Store' -import { Action } from '@/stores/Dispatcher' -import { ToggleDistanceUnits, ToggleShowSettings } from '@/actions/Actions' +import {Action} from '@/stores/Dispatcher' +import {SetCustomModel, SetCustomModelBoxEnabled, ToggleDistanceUnits, ToggleShowSettings} from '@/actions/Actions' +import React from 'react' +import {CustomModel} from "@/stores/QueryStore"; export interface Settings { showDistanceInMiles: boolean showSettings: boolean + customModel: CustomModel | null + customModelValid: boolean + customModelEnabled: boolean + initialCustomModelStr: string | null } +export const SettingsContext = React.createContext({ + showDistanceInMiles: false, + showSettings: false, + customModel: null, + customModelValid: false, + customModelEnabled: false, + initialCustomModelStr: '', +}) + export default class SettingsStore extends Store { - constructor() { + constructor(initialCustomModelStr: string | null = null) { super({ showDistanceInMiles: false, showSettings: false, + customModel: null, + customModelValid: false, + customModelEnabled: initialCustomModelStr != null, + initialCustomModelStr: initialCustomModelStr, }) } @@ -26,6 +45,17 @@ export default class SettingsStore extends Store { ...state, showSettings: !state.showSettings, } + } else if (action instanceof SetCustomModelBoxEnabled) { + return { + ...state, + customModelEnabled: !this.state.customModelEnabled, + } + } else if (action instanceof SetCustomModel) { + return { + ...state, + customModel: action.customModel, + customModelValid: action.valid, + } } return state } diff --git a/test/NavBar.test.ts b/test/NavBar.test.ts index 8487e852..9d9f49c1 100644 --- a/test/NavBar.test.ts +++ b/test/NavBar.test.ts @@ -10,6 +10,7 @@ import { window } from '@/Window' import MapOptionsStore, { StyleOption } from '@/stores/MapOptionsStore' import * as config from 'config' import { RoutingProfile } from '@/api/graphhopper' +import SettingsStore from '@/stores/SettingsStore' jest.mock('@/Window', () => ({ window: { @@ -38,9 +39,10 @@ describe('NavBar', function () { window.addEventListener = jest.fn((type: any, listener: any) => { callbacks.push(listener) }) - queryStore = new QueryStore(new DummyApi()) + const settingsStore = new SettingsStore() + queryStore = new QueryStore(new DummyApi(), settingsStore) mapStore = new MapOptionsStore() - navBar = new NavBar(queryStore, mapStore) + navBar = new NavBar(queryStore, mapStore, settingsStore) navBar.startSyncingUrlWithAppState() Dispatcher.register(queryStore) Dispatcher.register(mapStore) diff --git a/test/stores/QueryStore.test.ts b/test/stores/QueryStore.test.ts index 56f1f2d3..cbaaa135 100644 --- a/test/stores/QueryStore.test.ts +++ b/test/stores/QueryStore.test.ts @@ -12,6 +12,7 @@ import { SetPoint, SetVehicleProfile, } from '@/actions/Actions' +import SettingsStore from '@/stores/SettingsStore' class ApiMock implements Api { private readonly callback: { (args: RoutingArgs): void } @@ -47,7 +48,8 @@ describe('QueryStore', () => { const store = new QueryStore( new ApiMock(() => { throw Error('not expected') - }) + }), + new SettingsStore() ) const point: QueryPoint = { ...store.state.queryPoints[0], @@ -64,7 +66,7 @@ describe('QueryStore', () => { }) it('should only send a route request if all parameters are initialized', () => { let counter = 0 - const store = new QueryStore(new ApiMock(() => counter++)) + const store = new QueryStore(new ApiMock(() => counter++), new SettingsStore()) let state = { ...store.state, maxAlternativeRoutes: 1, @@ -83,7 +85,7 @@ describe('QueryStore', () => { }) it('should send two requests with different parameters when maxAlternativeRoutes is > 1', () => { const requestArgs: RoutingArgs[] = [] - const store = new QueryStore(new ApiMock(args => requestArgs.push(args))) + const store = new QueryStore(new ApiMock(args => requestArgs.push(args)), new SettingsStore()) let state = { ...store.state, @@ -100,7 +102,7 @@ describe('QueryStore', () => { }) it('should send one request when querypoints.length > 2 even though maxAlternativeRoutes > 1', () => { const requestArgs: RoutingArgs[] = [] - const store = new QueryStore(new ApiMock(args => requestArgs.push(args))) + const store = new QueryStore(new ApiMock(args => requestArgs.push(args)), new SettingsStore()) let state = { ...store.state, @@ -118,7 +120,7 @@ describe('QueryStore', () => { }) describe('Invalidate point action', () => { it('should set point with the same id to isInitialized: false', () => { - const store = new QueryStore(new ApiMock(() => {})) + const store = new QueryStore(new ApiMock(() => {}), new SettingsStore()) const initializedPoints = store.state.queryPoints.map(p => ({ ...p, @@ -140,7 +142,7 @@ describe('QueryStore', () => { }) describe('Clear Points action', () => { it('should reset all points', () => { - const store = new QueryStore(new ApiMock(() => {})) + const store = new QueryStore(new ApiMock(() => {}), new SettingsStore()) const initializedPoints = store.state.queryPoints.map((p, i) => ({ ...p, isInitialized: true, @@ -163,7 +165,8 @@ describe('QueryStore', () => { const store = new QueryStore( new ApiMock(() => { counter++ - }) + }), + new SettingsStore() ) const newPointId = store.state.nextQueryPointId const atIndex = 1 @@ -179,7 +182,8 @@ describe('QueryStore', () => { const store = new QueryStore( new ApiMock(() => { counter++ - }) + }), + new SettingsStore() ) const newPointId = store.state.nextQueryPointId const atIndex = 1 @@ -203,7 +207,8 @@ describe('QueryStore', () => { const store = new QueryStore( new ApiMock(() => { counter++ - }) + }), + new SettingsStore() ) const initializedPoints = store.state.queryPoints.map(p => ({ ...p, isInitialized: true })) @@ -233,7 +238,8 @@ describe('QueryStore', () => { const store = new QueryStore( new ApiMock(() => { fail('no routing request when profile was already set.') - }) + }), + new SettingsStore() ) const profile = 'some-profile' @@ -270,7 +276,8 @@ describe('QueryStore', () => { new ApiMock(args => { expect(args.profile).toEqual(expectedProfile.name) routingRequestWasIssued = true - }) + }), + new SettingsStore() ) let state: QueryStoreState = store.state @@ -313,7 +320,7 @@ describe('QueryStore', () => { }) describe('SetVehicleProfile action', () => { it('should set the routing profile (surprise!)', () => { - const store = new QueryStore(new ApiMock(() => {})) + const store = new QueryStore(new ApiMock(() => {}), new SettingsStore()) const state: QueryStoreState = store.state const profile = { name: 'car', @@ -329,7 +336,7 @@ describe('QueryStore', () => { }) describe('RouteRequestSuccess action', () => { it('should mark the correct subrequest as done', () => { - const store = new QueryStore(new ApiMock(() => {})) + const store = new QueryStore(new ApiMock(() => {}), new SettingsStore()) const routingArgs: RoutingArgs = { maxAlternativeRoutes: 1, points: [], @@ -358,7 +365,7 @@ describe('QueryStore', () => { }) describe('RouteRequestFailed action', () => { it('should mark the correct subrequest as done', () => { - const store = new QueryStore(new ApiMock(() => {})) + const store = new QueryStore(new ApiMock(() => {}), new SettingsStore()) const routingArgs: RoutingArgs = { maxAlternativeRoutes: 1, points: [], diff --git a/test/stores/RouteStore.test.ts b/test/stores/RouteStore.test.ts index 866dca78..a3735049 100644 --- a/test/stores/RouteStore.test.ts +++ b/test/stores/RouteStore.test.ts @@ -4,6 +4,7 @@ import Api from '@/api/Api' import { ApiInfo, GeocodingResult, Path, RoutingArgs, RoutingResult } from '@/api/graphhopper' import Dispatcher, { Action } from '@/stores/Dispatcher' import { ClearPoints, ClearRoute, RemovePoint, SetPoint, SetSelectedPath } from '@/actions/Actions' +import SettingsStore from '@/stores/SettingsStore' describe('RouteStore', () => { afterEach(() => { @@ -48,7 +49,7 @@ describe('RouteStore', () => { }) function createStore() { - const store = new RouteStore(new QueryStore(new DummyApi())) + const store = new RouteStore(new QueryStore(new DummyApi(), new SettingsStore())) Dispatcher.register(store) return store } From 61ccbd66118e869b47a395bec1d57bd7c2b9a47e Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 16 Mar 2023 11:16:53 +0100 Subject: [PATCH 03/22] fix bug when mobile view is changed to compact view and no new route is calculated because of an invalid custom model due to missing encoded values (recreation of CustomModelBox without the previously expected /info endpoint being called) --- src/sidebar/CustomModelBox.tsx | 38 ++++++++++++++++++++-------------- src/sidebar/SettingsBox.tsx | 4 ++-- src/stores/SettingsStore.ts | 6 +++--- 3 files changed, 27 insertions(+), 21 deletions(-) diff --git a/src/sidebar/CustomModelBox.tsx b/src/sidebar/CustomModelBox.tsx index 21474468..64b281ad 100644 --- a/src/sidebar/CustomModelBox.tsx +++ b/src/sidebar/CustomModelBox.tsx @@ -71,6 +71,21 @@ const examples: { [key: string]: CustomModel } = { }, } +function convertEV(encodedValues: object[]): any { + const categories: any = {} + Object.keys(encodedValues).forEach((k: any) => { + const v: any = encodedValues[k] + if (v.length === 2 && v[0] === 'true' && v[1] === 'false') { + categories[k] = { type: 'boolean' } + } else if (v.length === 2 && v[0] === '>number' && v[1] === '(null) useEffect(() => { - // we start with empty categories. they will be set later using info - const instance = create({}, (element: Node) => divElement.current?.appendChild(element)) + const instance = create(convertEV(encodedValues), (element: Node) => divElement.current?.appendChild(element)) setEditor(instance) instance.cm.setSize('100%', '100%') @@ -98,8 +112,6 @@ export default function CustomModelBox({ encodedValues, queryOngoing }: CustomMo initialCustomModelStr = customModel2prettyString(JSON.parse(initialCustomModelStr)) } catch (e) {} } - // TODO NOW when changing the size from "desktop" to "mobile" and back we loose the custom model - console.warn("initial custom model string was set? " + initialCustomModelStr != null) instance.value = initialCustomModelStr == null ? customModel2prettyString(examples['default_example']) @@ -128,18 +140,12 @@ export default function CustomModelBox({ encodedValues, queryOngoing }: CustomMo if (!editor) return // todo: maybe do this 'conversion' in Api.ts already and use types from there on - const categories: any = {} - Object.keys(encodedValues).forEach((k: any) => { - const v: any = encodedValues[k] - if (v.length === 2 && v[0] === 'true' && v[1] === 'false') { - categories[k] = { type: 'boolean' } - } else if (v.length === 2 && v[0] === '>number' && v[1] === ' {settings.showDistanceInMiles ? : } -
      +
      {tr('distance_unit', [tr(settings.showDistanceInMiles ? 'mi' : 'km')])}
      {settings.customModelEnabled ? : } -
      {tr('custom model enabled')}
      +
      {tr('custom model enabled')}
      ) } diff --git a/src/stores/SettingsStore.ts b/src/stores/SettingsStore.ts index 7460efe2..11510700 100644 --- a/src/stores/SettingsStore.ts +++ b/src/stores/SettingsStore.ts @@ -1,8 +1,8 @@ import Store from '@/stores/Store' -import {Action} from '@/stores/Dispatcher' -import {SetCustomModel, SetCustomModelBoxEnabled, ToggleDistanceUnits, ToggleShowSettings} from '@/actions/Actions' +import { Action } from '@/stores/Dispatcher' +import { SetCustomModel, SetCustomModelBoxEnabled, ToggleDistanceUnits, ToggleShowSettings } from '@/actions/Actions' import React from 'react' -import {CustomModel} from "@/stores/QueryStore"; +import { CustomModel } from '@/stores/QueryStore' export interface Settings { showDistanceInMiles: boolean From b6977504424a18834393561d48285deeb6ff73bb Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 16 Mar 2023 11:47:42 +0100 Subject: [PATCH 04/22] move CustomModelBox into SettingsBox but outside of settingsTable --- src/App.tsx | 3 +- src/SettingsContext.ts | 2 - src/sidebar/CustomModelBox.tsx | 90 ++++++++++++++++------------------ src/sidebar/MobileSidebar.tsx | 3 +- src/sidebar/SettingsBox.tsx | 62 ++++++++++++++--------- 5 files changed, 83 insertions(+), 77 deletions(-) delete mode 100644 src/SettingsContext.ts diff --git a/src/App.tsx b/src/App.tsx index 0cbad3c6..fba1e594 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -162,8 +162,7 @@ function LargeScreenLayout({ query, route, map, error, mapOptions, encodedValues selectedProfile={query.routingProfile} openSettingsHandle={() => Dispatcher.dispatch(new ToggleShowSettings())} /> - - diff --git a/src/SettingsContext.ts b/src/SettingsContext.ts deleted file mode 100644 index 7b3aba6c..00000000 --- a/src/SettingsContext.ts +++ /dev/null @@ -1,2 +0,0 @@ -import React from 'react' -import { Settings } from '@/stores/SettingsStore' diff --git a/src/sidebar/CustomModelBox.tsx b/src/sidebar/CustomModelBox.tsx index 64b281ad..87a1329d 100644 --- a/src/sidebar/CustomModelBox.tsx +++ b/src/sidebar/CustomModelBox.tsx @@ -162,57 +162,49 @@ export default function CustomModelBox({ encodedValues, queryOngoing }: CustomMo return ( <> - {/*we use 'display: none' instead of conditional rendering to preserve the custom model box's state when it is closed*/} -
      - {customModelEnabled && showSettings && ( -
      - { + editor.value = customModel2prettyString(examples[e.target.value]) + // When selecting an example we request a routing request and act like the model is valid, + // even when it is not according to the editor validation. + dispatchCustomModel(JSON.stringify(examples[e.target.value]), true, true) + }} + > + + + + + + + + + + {tr('help_custom_model')} + +
      + dispatchCustomModel(editor.value, true, true)} > - - - - - - - - - - {tr('help_custom_model')} - -
      - dispatchCustomModel(editor.value, true, true)} - > - {tr('apply_custom_model')} - - {queryOngoing &&
      } -
      + {tr('apply_custom_model')} +
      + {queryOngoing &&
      }
      - )} +
      ) } diff --git a/src/sidebar/MobileSidebar.tsx b/src/sidebar/MobileSidebar.tsx index 5928ed9b..35693084 100644 --- a/src/sidebar/MobileSidebar.tsx +++ b/src/sidebar/MobileSidebar.tsx @@ -62,8 +62,7 @@ export default function ({ query, route, error, encodedValues }: MobileSidebarPr selectedProfile={query.routingProfile} openSettingsHandle={() => Dispatcher.dispatch(new ToggleShowSettings())} /> - - diff --git a/src/sidebar/SettingsBox.tsx b/src/sidebar/SettingsBox.tsx index 47c5f445..80057713 100644 --- a/src/sidebar/SettingsBox.tsx +++ b/src/sidebar/SettingsBox.tsx @@ -7,33 +7,51 @@ import { useContext } from 'react' import OnIcon from '@/sidebar/toggle_on.svg' import OffIcon from '@/sidebar/toggle_off.svg' import { SettingsContext } from '@/stores/SettingsStore' +import CustomModelBox from '@/sidebar/CustomModelBox' +import { RequestState } from '@/stores/QueryStore' -export default function SettingsBox() { +export default function SettingsBox({ + encodedValues, + queryOngoing, +}: { + encodedValues: object[] + queryOngoing: boolean +}) { const settings = useContext(SettingsContext) return !settings.showSettings ? ( <> ) : ( -
      - Dispatcher.dispatch(new ToggleDistanceUnits())} - > - {settings.showDistanceInMiles ? : } - -
      - {tr('distance_unit', [tr(settings.showDistanceInMiles ? 'mi' : 'km')])} + <> +
      + Dispatcher.dispatch(new ToggleDistanceUnits())} + > + {settings.showDistanceInMiles ? : } + +
      + {tr('distance_unit', [tr(settings.showDistanceInMiles ? 'mi' : 'km')])} +
      + { + if (settings.customModelEnabled) Dispatcher.dispatch(new DismissLastError()) + Dispatcher.dispatch(new ClearRoute()) + Dispatcher.dispatch(new SetCustomModelBoxEnabled(!settings.customModelEnabled)) + }} + > + {settings.customModelEnabled ? : } + +
      + {tr('custom model enabled')} +
      +
      + {/*move custom model box outside settingsTable to give it the entire width, i.e. keep this option as last */} +
      - { - if (settings.customModelEnabled) Dispatcher.dispatch(new DismissLastError()) - Dispatcher.dispatch(new ClearRoute()) - Dispatcher.dispatch(new SetCustomModelBoxEnabled(!settings.customModelEnabled)) - }} - > - {settings.customModelEnabled ? : } - -
      {tr('custom model enabled')}
      -
      + {settings.customModelEnabled && ( + + )} + ) } From 7b7839a8e2478c8665adad55dd9d2e9bd9f67c42 Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 16 Mar 2023 13:23:16 +0100 Subject: [PATCH 05/22] minor -> warn --- src/sidebar/CustomModelBox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sidebar/CustomModelBox.tsx b/src/sidebar/CustomModelBox.tsx index 87a1329d..46d37c0f 100644 --- a/src/sidebar/CustomModelBox.tsx +++ b/src/sidebar/CustomModelBox.tsx @@ -142,7 +142,7 @@ export default function CustomModelBox({ encodedValues, queryOngoing }: CustomMo // todo: maybe do this 'conversion' in Api.ts already and use types from there on const categories = convertEV(encodedValues) if (!categories) { - console.log('encoded value: ' + JSON.stringify(encodedValues)) + console.warn('encoded values invalid: ' + JSON.stringify(encodedValues)) } else { editor.categories = categories } From c476d8152187effe3772cbc8fc518b11803ec7c2 Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 17 Mar 2023 13:54:25 +0100 Subject: [PATCH 06/22] minor css fix --- src/sidebar/search/Search.module.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sidebar/search/Search.module.css b/src/sidebar/search/Search.module.css index bc58ea00..1025cbb0 100644 --- a/src/sidebar/search/Search.module.css +++ b/src/sidebar/search/Search.module.css @@ -104,6 +104,8 @@ } .addSearchBox { + color: gray; + font-size: 14px; flex-grow: 1; display: flex; flex-direction: row; From 63ea078b19675802f54e705908dc3767ea94a324 Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 17 Mar 2023 14:08:26 +0100 Subject: [PATCH 07/22] make settings css better fit the rest --- src/sidebar/CustomModelBox.module.css | 1 - src/sidebar/SettingsBox.module.css | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sidebar/CustomModelBox.module.css b/src/sidebar/CustomModelBox.module.css index 814841ed..2af51780 100644 --- a/src/sidebar/CustomModelBox.module.css +++ b/src/sidebar/CustomModelBox.module.css @@ -22,7 +22,6 @@ .examples, .helpLink, .applyButton button { - font-family: Arial, serif; font-size: 14px; } diff --git a/src/sidebar/SettingsBox.module.css b/src/sidebar/SettingsBox.module.css index 757f1a98..29cc5edf 100644 --- a/src/sidebar/SettingsBox.module.css +++ b/src/sidebar/SettingsBox.module.css @@ -1,12 +1,13 @@ .settingsTable { display: grid; + font-size: 16px; grid-template-columns: 15% auto; grid-template-rows: 1.3rem; align-content: center; align-items: center; - margin: 1.5rem 0.5rem 0.5rem 0.5rem; + margin: 1.5rem 0.5rem 0.5rem 0; } .settingsTable svg { - scale: 80%; + scale: 0.7; } From aee208bca11550c7b400f28691b08df80f1c9b3e Mon Sep 17 00:00:00 2001 From: Peter Date: Sat, 18 Mar 2023 11:23:26 +0100 Subject: [PATCH 08/22] 1. URL with custom_model should trigger a routing request with it; 2. avoid route re-calculation if settings box is shown; 3. invalid custom model is reset to custom model before if settings box is closed --- src/App.tsx | 1 - src/custom.d.ts | 1 + src/sidebar/CustomModelBox.tsx | 25 +++++++++++-------------- src/sidebar/MobileSidebar.tsx | 1 - src/sidebar/SettingsBox.tsx | 9 +++++++-- src/stores/QueryStore.ts | 6 +++--- src/stores/SettingsStore.ts | 8 +++++--- 7 files changed, 27 insertions(+), 24 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index fba1e594..04339bfc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -42,7 +42,6 @@ import useAreasLayer from '@/layers/UseAreasLayer' import SettingsBox from '@/sidebar/SettingsBox' import Dispatcher from '@/stores/Dispatcher' import { ToggleShowSettings } from '@/actions/Actions' -import CustomModelBox from '@/sidebar/CustomModelBox' import { SettingsContext } from '@/stores/SettingsStore' export const POPUP_CONTAINER_ID = 'popup-container' diff --git a/src/custom.d.ts b/src/custom.d.ts index c0489278..16a2e446 100644 --- a/src/custom.d.ts +++ b/src/custom.d.ts @@ -3,6 +3,7 @@ declare module '*.svg' declare module '*.png' declare module 'heightgraph/src/heightgraph' declare module 'custom-model-editor/src/index' +declare module 'custom-model-editor/src/validate_json' declare module 'config' { const routingApi: string diff --git a/src/sidebar/CustomModelBox.tsx b/src/sidebar/CustomModelBox.tsx index 46d37c0f..84ef1e8f 100644 --- a/src/sidebar/CustomModelBox.tsx +++ b/src/sidebar/CustomModelBox.tsx @@ -105,27 +105,24 @@ export default function CustomModelBox({ encodedValues, queryOngoing }: CustomMo instance.cm.setSize('100%', '100%') if (customModel != null) { // init from settings in case of entire app recreation like window resizing - initialCustomModelStr = customModel2prettyString(customModel) + instance.value = customModel2prettyString(customModel) } else if (initialCustomModelStr != null) { - // prettify the custom model if it can be parsed or leave it as is otherwise try { - initialCustomModelStr = customModel2prettyString(JSON.parse(initialCustomModelStr)) - } catch (e) {} - } - instance.value = - initialCustomModelStr == null - ? customModel2prettyString(examples['default_example']) - : initialCustomModelStr - - if (customModelEnabled) - // When we got a custom model from the url parameters we send the request right away + instance.value = customModel2prettyString(JSON.parse(initialCustomModelStr)) + } catch (e) { + instance.value = initialCustomModelStr + } + } else { + instance.value = customModel2prettyString(examples['default_example']) dispatchCustomModel(instance.value, true, true) + } instance.validListener = (valid: boolean) => { - // We update the app states' custom model, but we are not requesting a routing query every time the model + // We update the app state's custom model, but we are not requesting a routing query every time the model // becomes valid. Updating the model is still important, because the routing request might be triggered by // moving markers etc. - dispatchCustomModel(instance.value, valid, false) + // TODO NOW shouldn't we better ignore invalid custom models when moving markers and use last valid custom model for this? + // dispatchCustomModel(instance.value, valid, false) setIsValid(valid) } }, []) diff --git a/src/sidebar/MobileSidebar.tsx b/src/sidebar/MobileSidebar.tsx index 35693084..e398f5fc 100644 --- a/src/sidebar/MobileSidebar.tsx +++ b/src/sidebar/MobileSidebar.tsx @@ -13,7 +13,6 @@ import CloseInputsIcon from './unfold_less.svg' import SettingsBox from '@/sidebar/SettingsBox' import Dispatcher from '@/stores/Dispatcher' import { ToggleShowSettings } from '@/actions/Actions' -import CustomModelBox from '@/sidebar/CustomModelBox' type MobileSidebarProps = { query: QueryStoreState diff --git a/src/sidebar/SettingsBox.tsx b/src/sidebar/SettingsBox.tsx index 80057713..a6a21960 100644 --- a/src/sidebar/SettingsBox.tsx +++ b/src/sidebar/SettingsBox.tsx @@ -1,4 +1,10 @@ -import { ClearRoute, DismissLastError, SetCustomModelBoxEnabled, ToggleDistanceUnits } from '@/actions/Actions' +import { + ClearRoute, + DismissLastError, + SetCustomModel, + SetCustomModelBoxEnabled, + ToggleDistanceUnits +} from '@/actions/Actions' import Dispatcher from '@/stores/Dispatcher' import styles from '@/sidebar/SettingsBox.module.css' import { tr } from '@/translation/Translation' @@ -8,7 +14,6 @@ import OnIcon from '@/sidebar/toggle_on.svg' import OffIcon from '@/sidebar/toggle_off.svg' import { SettingsContext } from '@/stores/SettingsStore' import CustomModelBox from '@/sidebar/CustomModelBox' -import { RequestState } from '@/stores/QueryStore' export default function SettingsBox({ encodedValues, diff --git a/src/stores/QueryStore.ts b/src/stores/QueryStore.ts index 2b999bb6..50bf21c7 100644 --- a/src/stores/QueryStore.ts +++ b/src/stores/QueryStore.ts @@ -82,12 +82,12 @@ export default class QueryStore extends Store { private readonly settingsStore: SettingsStore constructor(api: Api, settingsStore: SettingsStore) { - super(QueryStore.getInitialState()) + super(QueryStore.getInitialState(settingsStore.state.customModel)) this.api = api this.settingsStore = settingsStore } - private static getInitialState(): QueryStoreState { + private static getInitialState(cm: CustomModel | null): QueryStoreState { return { profiles: [], queryPoints: [ @@ -102,7 +102,7 @@ export default class QueryStore extends Store { routingProfile: { name: '', }, - customModel: null, + customModel: cm, zoom: true, } } diff --git a/src/stores/SettingsStore.ts b/src/stores/SettingsStore.ts index 11510700..bd06d5bd 100644 --- a/src/stores/SettingsStore.ts +++ b/src/stores/SettingsStore.ts @@ -3,6 +3,7 @@ import { Action } from '@/stores/Dispatcher' import { SetCustomModel, SetCustomModelBoxEnabled, ToggleDistanceUnits, ToggleShowSettings } from '@/actions/Actions' import React from 'react' import { CustomModel } from '@/stores/QueryStore' +import {validateJson} from "custom-model-editor/src/validate_json" export interface Settings { showDistanceInMiles: boolean @@ -24,12 +25,13 @@ export const SettingsContext = React.createContext({ export default class SettingsStore extends Store { constructor(initialCustomModelStr: string | null = null) { + const valid = initialCustomModelStr ? validateJson(initialCustomModelStr).errors.length == 0 : false super({ showDistanceInMiles: false, showSettings: false, - customModel: null, - customModelValid: false, - customModelEnabled: initialCustomModelStr != null, + customModel: initialCustomModelStr && valid ? JSON.parse(initialCustomModelStr) : null, + customModelValid: valid, + customModelEnabled: valid, initialCustomModelStr: initialCustomModelStr, }) } From 96108d7e07af7811bdf908d32b8fc4f6804c3e89 Mon Sep 17 00:00:00 2001 From: Peter Date: Sun, 19 Mar 2023 21:38:37 +0100 Subject: [PATCH 09/22] tricky to fix error 'Cannot use import statement outside a module' in tests: 1. insist on transpiling custom model editor code to avoid ES6 modules for jest and 2. allowJs in tsconfig and 3. explicitly transform js files using ts-jest - why? --- jest.config.js | 4 ++++ src/stores/SettingsStore.ts | 2 +- tsconfig.json | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/jest.config.js b/jest.config.js index 27207b0f..81cbb0c1 100644 --- a/jest.config.js +++ b/jest.config.js @@ -5,4 +5,8 @@ module.exports = { '@/(.*)$': '/src/$1', config: '/config.js', }, + transform: { + "^.+\\.js?$": "ts-jest" + }, + transformIgnorePatterns: [ `/node_modules/(?!custom-model-editor/)` ] } diff --git a/src/stores/SettingsStore.ts b/src/stores/SettingsStore.ts index bd06d5bd..a50dc050 100644 --- a/src/stores/SettingsStore.ts +++ b/src/stores/SettingsStore.ts @@ -3,7 +3,7 @@ import { Action } from '@/stores/Dispatcher' import { SetCustomModel, SetCustomModelBoxEnabled, ToggleDistanceUnits, ToggleShowSettings } from '@/actions/Actions' import React from 'react' import { CustomModel } from '@/stores/QueryStore' -import {validateJson} from "custom-model-editor/src/validate_json" +import { validateJson } from "custom-model-editor/src/validate_json" export interface Settings { showDistanceInMiles: boolean diff --git a/tsconfig.json b/tsconfig.json index 87d02118..fe3971ce 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,7 @@ "sourceMap": true, "noImplicitAny": true, "jsx": "react-jsx", + "allowJs": true, "allowSyntheticDefaultImports": true, "baseUrl": ".", "importHelpers": true, From d1e707a9d91dcbccb61f15553bd12a97a37a6b37 Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 21 Mar 2023 17:05:16 +0100 Subject: [PATCH 10/22] remove CustomModel from QueryStore state --- src/App.tsx | 2 +- src/NavBar.ts | 9 +++++---- src/sidebar/SettingsBox.tsx | 2 +- src/stores/QueryStore.ts | 36 ++++++++++++++---------------------- src/stores/SettingsStore.ts | 2 +- 5 files changed, 22 insertions(+), 29 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 04339bfc..7ec1b3ef 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -101,7 +101,7 @@ export default function App() { // our different map layers useBackgroundLayer(map, mapOptions.selectedStyle) useMapBorderLayer(map, info.bbox) - useAreasLayer(map, settings.customModelEnabled && settings.customModelValid ? query.customModel?.areas! : null) + useAreasLayer(map, settings.customModelEnabled && settings.customModelValid ? settings.customModel?.areas! : null) useRoutingGraphLayer(map, mapOptions.routingGraphEnabled) useUrbanDensityLayer(map, mapOptions.urbanDensityEnabled) usePathsLayer(map, route.routingResult.paths, route.selectedPath) diff --git a/src/NavBar.ts b/src/NavBar.ts index 3315035a..bf6c34c0 100644 --- a/src/NavBar.ts +++ b/src/NavBar.ts @@ -4,7 +4,7 @@ import Dispatcher from '@/stores/Dispatcher' import { ClearPoints, SelectMapLayer, SetInitialBBox, SetQueryPoints, SetVehicleProfile } from '@/actions/Actions' // import the window like this so that it can be mocked during testing import { window } from '@/Window' -import QueryStore, { Coordinate, QueryPoint, QueryPointType, QueryStoreState } from '@/stores/QueryStore' +import QueryStore, { Coordinate, CustomModel, QueryPoint, QueryPointType, QueryStoreState } from '@/stores/QueryStore' import MapOptionsStore, { MapOptionsStoreState } from './stores/MapOptionsStore' import { getApi } from '@/api/Api' import SettingsStore from '@/stores/SettingsStore' @@ -33,7 +33,7 @@ export default class NavBar { baseUrl: string, queryStoreState: QueryStoreState, mapState: MapOptionsStoreState, - addCustomModel: boolean + cm: CustomModel | null ) { const result = new URL(baseUrl) if (queryStoreState.queryPoints.filter(point => point.isInitialized).length > 0) { @@ -44,8 +44,7 @@ export default class NavBar { result.searchParams.append('profile', queryStoreState.routingProfile.name) result.searchParams.append('layer', mapState.selectedStyle.name) - if (addCustomModel && queryStoreState.customModel) - result.searchParams.append('custom_model', JSON.stringify(queryStoreState.customModel)) + if (cm) result.searchParams.append('custom_model', JSON.stringify(cm)) return result } @@ -166,6 +165,8 @@ export default class NavBar { this.queryStore.state, this.mapStore.state, this.settingsStore.state.customModelEnabled && this.settingsStore.state.customModelValid + ? this.settingsStore.state.customModel + : null ).toString() } diff --git a/src/sidebar/SettingsBox.tsx b/src/sidebar/SettingsBox.tsx index a6a21960..4f28ca42 100644 --- a/src/sidebar/SettingsBox.tsx +++ b/src/sidebar/SettingsBox.tsx @@ -3,7 +3,7 @@ import { DismissLastError, SetCustomModel, SetCustomModelBoxEnabled, - ToggleDistanceUnits + ToggleDistanceUnits, } from '@/actions/Actions' import Dispatcher from '@/stores/Dispatcher' import styles from '@/sidebar/SettingsBox.module.css' diff --git a/src/stores/QueryStore.ts b/src/stores/QueryStore.ts index 50bf21c7..8b04d644 100644 --- a/src/stores/QueryStore.ts +++ b/src/stores/QueryStore.ts @@ -35,7 +35,6 @@ export interface QueryStoreState { readonly currentRequest: CurrentRequest readonly maxAlternativeRoutes: number readonly routingProfile: RoutingProfile - readonly customModel: CustomModel | null // todo: probably this should go somewhere else, see: https://github.com/graphhopper/graphhopper-maps/pull/193 readonly zoom: boolean } @@ -82,12 +81,12 @@ export default class QueryStore extends Store { private readonly settingsStore: SettingsStore constructor(api: Api, settingsStore: SettingsStore) { - super(QueryStore.getInitialState(settingsStore.state.customModel)) + super(QueryStore.getInitialState()) this.api = api this.settingsStore = settingsStore } - private static getInitialState(cm: CustomModel | null): QueryStoreState { + private static getInitialState(): QueryStoreState { return { profiles: [], queryPoints: [ @@ -102,7 +101,6 @@ export default class QueryStore extends Store { routingProfile: { name: '', }, - customModel: cm, zoom: true, } } @@ -256,13 +254,7 @@ export default class QueryStore extends Store { return this.routeIfReady(newState) } else if (action instanceof SetCustomModel) { - const newState: QueryStoreState = { - ...state, - customModel: action.customModel, - } - - if (action.issueRouteRequest) return this.routeIfReady(newState) - else return newState + if (action.issueRouteRequest) return this.routeIfReady(state) } else if (action instanceof RouteRequestSuccess || action instanceof RouteRequestFailed) { return QueryStore.handleFinishedRequest(state, action) } else if (action instanceof SetCustomModelBoxEnabled) { @@ -288,14 +280,16 @@ export default class QueryStore extends Store { private routeIfReady(state: QueryStoreState): QueryStoreState { const cmEnabled = this.settingsStore.state.customModelEnabled - const cmValid = this.settingsStore.state.customModelValid - if (QueryStore.isReadyToRoute(state, cmEnabled, cmValid)) { + const cm = this.settingsStore.state.customModelValid && cmEnabled ? this.settingsStore.state.customModel : null + if (cmEnabled && !cm) return state + + if (QueryStore.isReadyToRoute(state)) { let requests const maxDistance = getMaxDistance(state.queryPoints) if (cmEnabled) { if (maxDistance < 200_000) { // Use a single request, possibly including alternatives when custom models are enabled. - requests = [QueryStore.buildRouteRequest(state, cmEnabled)] + requests = [QueryStore.buildRouteRequest(state, cm)] } else if (maxDistance < 500_000) { // Force no alternatives for longer custom model routes. requests = [ @@ -304,7 +298,7 @@ export default class QueryStore extends Store { ...state, maxAlternativeRoutes: 1, }, - cmEnabled + cm ), ] } else { @@ -329,7 +323,7 @@ export default class QueryStore extends Store { ...state, maxAlternativeRoutes: 1, }, - cmEnabled + cm ), ] // ... and then a second, slower request including alternatives if they are enabled. @@ -338,7 +332,7 @@ export default class QueryStore extends Store { state.maxAlternativeRoutes > 1 && (ApiImpl.isMotorVehicle(state.routingProfile.name) || maxDistance < 500_000) ) - requests.push(QueryStore.buildRouteRequest(state, cmEnabled)) + requests.push(QueryStore.buildRouteRequest(state, cm)) } return { @@ -361,10 +355,8 @@ export default class QueryStore extends Store { return subRequests } - private static isReadyToRoute(state: QueryStoreState, cmEnabled: boolean, cmValid: boolean) { + private static isReadyToRoute(state: QueryStoreState) { // deliberately chose this style of if statements, to make this readable. - if (cmEnabled && !state.customModel) return false - if (cmEnabled && state.customModel && !cmValid) return false if (state.queryPoints.length <= 1) return false if (!state.queryPoints.every(point => point.isInitialized)) return false if (!state.routingProfile.name) return false @@ -423,7 +415,7 @@ export default class QueryStore extends Store { return QueryPointType.Via } - private static buildRouteRequest(state: QueryStoreState, cmEnabled: boolean): RoutingArgs { + private static buildRouteRequest(state: QueryStoreState, cm: CustomModel | null): RoutingArgs { const coordinates = state.queryPoints.map(point => [point.coordinate.lng, point.coordinate.lat]) as [ number, number @@ -433,7 +425,7 @@ export default class QueryStore extends Store { points: coordinates, profile: state.routingProfile.name, maxAlternativeRoutes: state.maxAlternativeRoutes, - customModel: cmEnabled ? state.customModel : null, + customModel: cm, zoom: state.zoom, } } diff --git a/src/stores/SettingsStore.ts b/src/stores/SettingsStore.ts index a50dc050..69fcbc80 100644 --- a/src/stores/SettingsStore.ts +++ b/src/stores/SettingsStore.ts @@ -3,7 +3,7 @@ import { Action } from '@/stores/Dispatcher' import { SetCustomModel, SetCustomModelBoxEnabled, ToggleDistanceUnits, ToggleShowSettings } from '@/actions/Actions' import React from 'react' import { CustomModel } from '@/stores/QueryStore' -import { validateJson } from "custom-model-editor/src/validate_json" +import { validateJson } from 'custom-model-editor/src/validate_json' export interface Settings { showDistanceInMiles: boolean From f57ac3e2b9d232f76c2e0fc3dbe50057242da6b5 Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 21 Mar 2023 17:11:50 +0100 Subject: [PATCH 11/22] formatting --- jest.config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jest.config.js b/jest.config.js index 81cbb0c1..d447e30a 100644 --- a/jest.config.js +++ b/jest.config.js @@ -6,7 +6,7 @@ module.exports = { config: '/config.js', }, transform: { - "^.+\\.js?$": "ts-jest" + '^.+\\.js?$': 'ts-jest', }, - transformIgnorePatterns: [ `/node_modules/(?!custom-model-editor/)` ] + transformIgnorePatterns: [`/node_modules/(?!custom-model-editor/)`], } From 34b091d6b09e50324cee309a7cadc01780c293a8 Mon Sep 17 00:00:00 2001 From: Peter Date: Wed, 22 Mar 2023 09:10:47 +0100 Subject: [PATCH 12/22] revert change with SettingsContext to use ShowDistanceInMilesContext --- src/App.tsx | 19 +++++++++++++------ src/ShowDistanceInMilesContext.ts | 2 ++ src/layers/PathDetailPopup.tsx | 4 ++-- src/pathDetails/PathDetails.tsx | 4 ++-- src/sidebar/CustomModelBox.tsx | 7 ++++--- src/sidebar/MobileSidebar.tsx | 7 ++++++- src/sidebar/RoutingResults.tsx | 4 ++-- src/sidebar/SettingsBox.tsx | 9 ++++----- src/sidebar/instructions/Instructions.tsx | 4 ++-- .../routingProfiles/RoutingProfiles.tsx | 10 ++++++---- src/stores/SettingsStore.ts | 10 ---------- 11 files changed, 43 insertions(+), 37 deletions(-) create mode 100644 src/ShowDistanceInMilesContext.ts diff --git a/src/App.tsx b/src/App.tsx index 7ec1b3ef..f1fa1e7e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -33,6 +33,7 @@ import { getMap } from '@/map/map' import useRoutingGraphLayer from '@/layers/UseRoutingGraphLayer' import useUrbanDensityLayer from '@/layers/UseUrbanDensityLayer' import useMapBorderLayer from '@/layers/UseMapBorderLayer' +import { ShowDistanceInMilesContext } from '@/ShowDistanceInMilesContext' import RoutingProfiles from '@/sidebar/search/routingProfiles/RoutingProfiles' import MapPopups from '@/map/MapPopups' import Menu from '@/sidebar/menu.svg' @@ -42,7 +43,7 @@ import useAreasLayer from '@/layers/UseAreasLayer' import SettingsBox from '@/sidebar/SettingsBox' import Dispatcher from '@/stores/Dispatcher' import { ToggleShowSettings } from '@/actions/Actions' -import { SettingsContext } from '@/stores/SettingsStore' +import {Settings} from "@/stores/SettingsStore"; export const POPUP_CONTAINER_ID = 'popup-container' export const SIDEBAR_CONTENT_ID = 'sidebar-content' @@ -109,7 +110,7 @@ export default function App() { usePathDetailsLayer(map, pathDetails) const isSmallScreen = useMediaQuery({ query: '(max-width: 44rem)' }) return ( - +
      @@ -121,6 +122,7 @@ export default function App() { mapOptions={mapOptions} error={error} encodedValues={info.encoded_values} + settings={settings} /> ) : ( )}
      -
      + ) } @@ -144,9 +147,10 @@ interface LayoutProps { mapOptions: MapOptionsStoreState error: ErrorStoreState encodedValues: object[] + settings: Settings } -function LargeScreenLayout({ query, route, map, error, mapOptions, encodedValues }: LayoutProps) { +function LargeScreenLayout({ query, route, map, error, mapOptions, encodedValues, settings }: LayoutProps) { const [showSidebar, setShowSidebar] = useState(true) return ( <> @@ -159,11 +163,14 @@ function LargeScreenLayout({ query, route, map, error, mapOptions, encodedValues Dispatcher.dispatch(new ToggleShowSettings())} />
      {!error.isDismissed && }
      @@ -200,11 +207,11 @@ function LargeScreenLayout({ query, route, map, error, mapOptions, encodedValues ) } -function SmallScreenLayout({ query, route, map, error, mapOptions, encodedValues }: LayoutProps) { +function SmallScreenLayout({ query, route, map, error, mapOptions, encodedValues, settings }: LayoutProps) { return ( <>
      - +
      diff --git a/src/ShowDistanceInMilesContext.ts b/src/ShowDistanceInMilesContext.ts new file mode 100644 index 00000000..1d55d3c7 --- /dev/null +++ b/src/ShowDistanceInMilesContext.ts @@ -0,0 +1,2 @@ +import React from 'react' +export const ShowDistanceInMilesContext = React.createContext(false) \ No newline at end of file diff --git a/src/layers/PathDetailPopup.tsx b/src/layers/PathDetailPopup.tsx index 090fd748..c15e3b82 100644 --- a/src/layers/PathDetailPopup.tsx +++ b/src/layers/PathDetailPopup.tsx @@ -4,7 +4,7 @@ import { PathDetailsStoreState } from '@/stores/PathDetailsStore' import { metersToText } from '@/Converters' import MapPopup from '@/layers/MapPopup' import { Map } from 'ol' -import { SettingsContext } from '@/stores/SettingsStore' +import {ShowDistanceInMilesContext} from "@/ShowDistanceInMilesContext"; interface PathDetailPopupProps { map: Map @@ -15,7 +15,7 @@ interface PathDetailPopupProps { * The popup shown along the selected route when we hover the path detail/elevation graph */ export default function PathDetailPopup({ map, pathDetails }: PathDetailPopupProps) { - const { showDistanceInMiles } = useContext(SettingsContext) + const showDistanceInMiles = useContext(ShowDistanceInMilesContext) return ( // todo: use createMapMarker from heightgraph? // {createMapMarker(point.elevation, point.description, showDistanceInMiles)} diff --git a/src/pathDetails/PathDetails.tsx b/src/pathDetails/PathDetails.tsx index 90b047a4..3d847533 100644 --- a/src/pathDetails/PathDetails.tsx +++ b/src/pathDetails/PathDetails.tsx @@ -8,7 +8,7 @@ import { PathDetailsElevationSelected, PathDetailsHover, PathDetailsRangeSelecte import QueryStore, { Coordinate, QueryPointType } from '@/stores/QueryStore' import { Position } from 'geojson' import { calcDist } from '@/distUtils' -import { SettingsContext } from '@/stores/SettingsStore' +import {ShowDistanceInMilesContext} from "@/ShowDistanceInMilesContext"; interface PathDetailsProps { selectedPath: Path @@ -52,7 +52,7 @@ export default function ({ selectedPath }: PathDetailsProps) { graph?.setData(pathDetailsData.data, pathDetailsData.mappings) }, [selectedPath, graph]) - const { showDistanceInMiles } = useContext(SettingsContext) + const showDistanceInMiles = useContext(ShowDistanceInMilesContext) useEffect(() => { graph?.setImperial(showDistanceInMiles) graph?.redraw() diff --git a/src/sidebar/CustomModelBox.tsx b/src/sidebar/CustomModelBox.tsx index 1c269eb3..50f206dd 100644 --- a/src/sidebar/CustomModelBox.tsx +++ b/src/sidebar/CustomModelBox.tsx @@ -10,7 +10,7 @@ import { SetCustomModel } from '@/actions/Actions' import { CustomModel } from '@/stores/QueryStore' import { tr } from '@/translation/Translation' import PlainButton from '@/PlainButton' -import { SettingsContext } from '@/stores/SettingsStore' +import {Settings} from "@/stores/SettingsStore"; const examples: { [key: string]: CustomModel } = { default_example: { @@ -88,10 +88,11 @@ function convertEV(encodedValues: object[]): any { export interface CustomModelBoxProps { encodedValues: object[] queryOngoing: boolean + settings: Settings } -export default function CustomModelBox({ encodedValues, queryOngoing }: CustomModelBoxProps) { - let { initialCustomModelStr, customModelEnabled, showSettings, customModel } = useContext(SettingsContext) +export default function CustomModelBox({ encodedValues, queryOngoing, settings }: CustomModelBoxProps) { + const { initialCustomModelStr, customModelEnabled, showSettings, customModel } = settings // todo: add types for custom model editor later const [editor, setEditor] = useState() const [isValid, setIsValid] = useState(false) diff --git a/src/sidebar/MobileSidebar.tsx b/src/sidebar/MobileSidebar.tsx index e398f5fc..b94f3ee4 100644 --- a/src/sidebar/MobileSidebar.tsx +++ b/src/sidebar/MobileSidebar.tsx @@ -13,15 +13,17 @@ import CloseInputsIcon from './unfold_less.svg' import SettingsBox from '@/sidebar/SettingsBox' import Dispatcher from '@/stores/Dispatcher' import { ToggleShowSettings } from '@/actions/Actions' +import {Settings} from "@/stores/SettingsStore"; type MobileSidebarProps = { query: QueryStoreState route: RouteStoreState error: ErrorStoreState encodedValues: object[] + settings: Settings } -export default function ({ query, route, error, encodedValues }: MobileSidebarProps) { +export default function ({ query, route, error, encodedValues, settings }: MobileSidebarProps) { // the following three elements control, whether the small search view is displayed const isShortScreen = useMediaQuery({ query: '(max-height: 55rem)' }) const [isSmallSearchView, setIsSmallSearchView] = useState(isShortScreen && hasResult(route)) @@ -59,11 +61,14 @@ export default function ({ query, route, error, encodedValues }: MobileSidebarPr Dispatcher.dispatch(new ToggleShowSettings())} />
      diff --git a/src/sidebar/RoutingResults.tsx b/src/sidebar/RoutingResults.tsx index a2dad086..1bbbe025 100644 --- a/src/sidebar/RoutingResults.tsx +++ b/src/sidebar/RoutingResults.tsx @@ -13,8 +13,8 @@ import { LineString, Position } from 'geojson' import { calcDist } from '@/distUtils' import { useMediaQuery } from 'react-responsive' import { tr } from '@/translation/Translation' -import { SettingsContext } from '@/stores/SettingsStore' import { ApiImpl } from '@/api/Api' +import {ShowDistanceInMilesContext} from "@/ShowDistanceInMilesContext"; export interface RoutingResultsProps { paths: Path[] @@ -58,7 +58,7 @@ function RoutingResult({ path, isSelected, profile }: { path: Path; isSelected: hasFootways || hasSteepSegments - const { showDistanceInMiles } = useContext(SettingsContext) + const showDistanceInMiles = useContext(ShowDistanceInMilesContext) return (
      diff --git a/src/sidebar/SettingsBox.tsx b/src/sidebar/SettingsBox.tsx index 4f28ca42..90aee8eb 100644 --- a/src/sidebar/SettingsBox.tsx +++ b/src/sidebar/SettingsBox.tsx @@ -1,7 +1,6 @@ import { ClearRoute, DismissLastError, - SetCustomModel, SetCustomModelBoxEnabled, ToggleDistanceUnits, } from '@/actions/Actions' @@ -9,20 +8,20 @@ import Dispatcher from '@/stores/Dispatcher' import styles from '@/sidebar/SettingsBox.module.css' import { tr } from '@/translation/Translation' import PlainButton from '@/PlainButton' -import { useContext } from 'react' import OnIcon from '@/sidebar/toggle_on.svg' import OffIcon from '@/sidebar/toggle_off.svg' -import { SettingsContext } from '@/stores/SettingsStore' import CustomModelBox from '@/sidebar/CustomModelBox' +import {Settings} from "@/stores/SettingsStore"; export default function SettingsBox({ encodedValues, queryOngoing, + settings, }: { encodedValues: object[] queryOngoing: boolean + settings: Settings }) { - const settings = useContext(SettingsContext) return !settings.showSettings ? ( <> ) : ( @@ -55,7 +54,7 @@ export default function SettingsBox({
      {settings.customModelEnabled && ( - + )} ) diff --git a/src/sidebar/instructions/Instructions.tsx b/src/sidebar/instructions/Instructions.tsx index e7432620..05379f23 100644 --- a/src/sidebar/instructions/Instructions.tsx +++ b/src/sidebar/instructions/Instructions.tsx @@ -21,9 +21,9 @@ import { metersToText } from '@/Converters' import { Instruction } from '@/api/graphhopper' import { MarkerComponent } from '@/map/Marker' import QueryStore, { QueryPointType } from '@/stores/QueryStore' -import { SettingsContext } from '@/stores/SettingsStore' import Dispatcher from '@/stores/Dispatcher' import { InstructionClicked } from '@/actions/Actions' +import {ShowDistanceInMilesContext} from "@/ShowDistanceInMilesContext"; export default function (props: { instructions: Instruction[] }) { return ( @@ -36,7 +36,7 @@ export default function (props: { instructions: Instruction[] }) { } const Line = function ({ instruction, index }: { instruction: Instruction; index: number }) { - const { showDistanceInMiles } = useContext(SettingsContext) + const showDistanceInMiles = useContext(ShowDistanceInMilesContext) return (
    • void }) { - const settings = useContext(SettingsContext) return (
      diff --git a/src/stores/SettingsStore.ts b/src/stores/SettingsStore.ts index 69fcbc80..044654e5 100644 --- a/src/stores/SettingsStore.ts +++ b/src/stores/SettingsStore.ts @@ -1,7 +1,6 @@ import Store from '@/stores/Store' import { Action } from '@/stores/Dispatcher' import { SetCustomModel, SetCustomModelBoxEnabled, ToggleDistanceUnits, ToggleShowSettings } from '@/actions/Actions' -import React from 'react' import { CustomModel } from '@/stores/QueryStore' import { validateJson } from 'custom-model-editor/src/validate_json' @@ -14,15 +13,6 @@ export interface Settings { initialCustomModelStr: string | null } -export const SettingsContext = React.createContext({ - showDistanceInMiles: false, - showSettings: false, - customModel: null, - customModelValid: false, - customModelEnabled: false, - initialCustomModelStr: '', -}) - export default class SettingsStore extends Store { constructor(initialCustomModelStr: string | null = null) { const valid = initialCustomModelStr ? validateJson(initialCustomModelStr).errors.length == 0 : false From 16579256d01de539e70c3d61b276c5f165406ad8 Mon Sep 17 00:00:00 2001 From: easbar Date: Wed, 22 Mar 2023 09:16:23 +0100 Subject: [PATCH 13/22] format and clean up imports --- src/App.tsx | 10 ++++++++-- src/ShowDistanceInMilesContext.ts | 2 +- src/layers/PathDetailPopup.tsx | 2 +- src/pathDetails/PathDetails.tsx | 2 +- src/sidebar/CustomModelBox.tsx | 4 ++-- src/sidebar/MobileSidebar.tsx | 2 +- src/sidebar/RoutingResults.tsx | 2 +- src/sidebar/SettingsBox.tsx | 9 ++------- src/sidebar/instructions/Instructions.tsx | 2 +- src/sidebar/search/routingProfiles/RoutingProfiles.tsx | 6 +++--- 10 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index f1fa1e7e..9dc324cd 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -43,7 +43,7 @@ import useAreasLayer from '@/layers/UseAreasLayer' import SettingsBox from '@/sidebar/SettingsBox' import Dispatcher from '@/stores/Dispatcher' import { ToggleShowSettings } from '@/actions/Actions' -import {Settings} from "@/stores/SettingsStore"; +import { Settings } from '@/stores/SettingsStore' export const POPUP_CONTAINER_ID = 'popup-container' export const SIDEBAR_CONTENT_ID = 'sidebar-content' @@ -211,7 +211,13 @@ function SmallScreenLayout({ query, route, map, error, mapOptions, encodedValues return ( <>
      - +
      diff --git a/src/ShowDistanceInMilesContext.ts b/src/ShowDistanceInMilesContext.ts index 1d55d3c7..3498d311 100644 --- a/src/ShowDistanceInMilesContext.ts +++ b/src/ShowDistanceInMilesContext.ts @@ -1,2 +1,2 @@ import React from 'react' -export const ShowDistanceInMilesContext = React.createContext(false) \ No newline at end of file +export const ShowDistanceInMilesContext = React.createContext(false) diff --git a/src/layers/PathDetailPopup.tsx b/src/layers/PathDetailPopup.tsx index c15e3b82..f07db87f 100644 --- a/src/layers/PathDetailPopup.tsx +++ b/src/layers/PathDetailPopup.tsx @@ -2,9 +2,9 @@ import { useContext } from 'react' import styles from '@/layers/DefaultMapPopup.module.css' import { PathDetailsStoreState } from '@/stores/PathDetailsStore' import { metersToText } from '@/Converters' +import { ShowDistanceInMilesContext } from '@/ShowDistanceInMilesContext' import MapPopup from '@/layers/MapPopup' import { Map } from 'ol' -import {ShowDistanceInMilesContext} from "@/ShowDistanceInMilesContext"; interface PathDetailPopupProps { map: Map diff --git a/src/pathDetails/PathDetails.tsx b/src/pathDetails/PathDetails.tsx index 3d847533..5cdff25c 100644 --- a/src/pathDetails/PathDetails.tsx +++ b/src/pathDetails/PathDetails.tsx @@ -8,7 +8,7 @@ import { PathDetailsElevationSelected, PathDetailsHover, PathDetailsRangeSelecte import QueryStore, { Coordinate, QueryPointType } from '@/stores/QueryStore' import { Position } from 'geojson' import { calcDist } from '@/distUtils' -import {ShowDistanceInMilesContext} from "@/ShowDistanceInMilesContext"; +import { ShowDistanceInMilesContext } from '@/ShowDistanceInMilesContext' interface PathDetailsProps { selectedPath: Path diff --git a/src/sidebar/CustomModelBox.tsx b/src/sidebar/CustomModelBox.tsx index 50f206dd..b2a7f1ed 100644 --- a/src/sidebar/CustomModelBox.tsx +++ b/src/sidebar/CustomModelBox.tsx @@ -3,14 +3,14 @@ import 'codemirror/addon/hint/show-hint.css' import 'codemirror/addon/lint/lint.css' import '@/sidebar/CustomModelBox.css' import styles from '@/sidebar/CustomModelBox.module.css' -import { useCallback, useContext, useEffect, useRef, useState } from 'react' +import { useCallback, useEffect, useRef, useState } from 'react' import { create } from 'custom-model-editor/src/index' import Dispatcher from '@/stores/Dispatcher' import { SetCustomModel } from '@/actions/Actions' import { CustomModel } from '@/stores/QueryStore' import { tr } from '@/translation/Translation' import PlainButton from '@/PlainButton' -import {Settings} from "@/stores/SettingsStore"; +import { Settings } from '@/stores/SettingsStore' const examples: { [key: string]: CustomModel } = { default_example: { diff --git a/src/sidebar/MobileSidebar.tsx b/src/sidebar/MobileSidebar.tsx index b94f3ee4..41d0ae01 100644 --- a/src/sidebar/MobileSidebar.tsx +++ b/src/sidebar/MobileSidebar.tsx @@ -13,7 +13,7 @@ import CloseInputsIcon from './unfold_less.svg' import SettingsBox from '@/sidebar/SettingsBox' import Dispatcher from '@/stores/Dispatcher' import { ToggleShowSettings } from '@/actions/Actions' -import {Settings} from "@/stores/SettingsStore"; +import { Settings } from '@/stores/SettingsStore' type MobileSidebarProps = { query: QueryStoreState diff --git a/src/sidebar/RoutingResults.tsx b/src/sidebar/RoutingResults.tsx index 1bbbe025..433cc0bb 100644 --- a/src/sidebar/RoutingResults.tsx +++ b/src/sidebar/RoutingResults.tsx @@ -13,8 +13,8 @@ import { LineString, Position } from 'geojson' import { calcDist } from '@/distUtils' import { useMediaQuery } from 'react-responsive' import { tr } from '@/translation/Translation' +import { ShowDistanceInMilesContext } from '@/ShowDistanceInMilesContext' import { ApiImpl } from '@/api/Api' -import {ShowDistanceInMilesContext} from "@/ShowDistanceInMilesContext"; export interface RoutingResultsProps { paths: Path[] diff --git a/src/sidebar/SettingsBox.tsx b/src/sidebar/SettingsBox.tsx index 90aee8eb..96745acc 100644 --- a/src/sidebar/SettingsBox.tsx +++ b/src/sidebar/SettingsBox.tsx @@ -1,9 +1,4 @@ -import { - ClearRoute, - DismissLastError, - SetCustomModelBoxEnabled, - ToggleDistanceUnits, -} from '@/actions/Actions' +import { ClearRoute, DismissLastError, SetCustomModelBoxEnabled, ToggleDistanceUnits } from '@/actions/Actions' import Dispatcher from '@/stores/Dispatcher' import styles from '@/sidebar/SettingsBox.module.css' import { tr } from '@/translation/Translation' @@ -11,7 +6,7 @@ import PlainButton from '@/PlainButton' import OnIcon from '@/sidebar/toggle_on.svg' import OffIcon from '@/sidebar/toggle_off.svg' import CustomModelBox from '@/sidebar/CustomModelBox' -import {Settings} from "@/stores/SettingsStore"; +import { Settings } from '@/stores/SettingsStore' export default function SettingsBox({ encodedValues, diff --git a/src/sidebar/instructions/Instructions.tsx b/src/sidebar/instructions/Instructions.tsx index 05379f23..ec4eb293 100644 --- a/src/sidebar/instructions/Instructions.tsx +++ b/src/sidebar/instructions/Instructions.tsx @@ -21,9 +21,9 @@ import { metersToText } from '@/Converters' import { Instruction } from '@/api/graphhopper' import { MarkerComponent } from '@/map/Marker' import QueryStore, { QueryPointType } from '@/stores/QueryStore' +import { ShowDistanceInMilesContext } from '@/ShowDistanceInMilesContext' import Dispatcher from '@/stores/Dispatcher' import { InstructionClicked } from '@/actions/Actions' -import {ShowDistanceInMilesContext} from "@/ShowDistanceInMilesContext"; export default function (props: { instructions: Instruction[] }) { return ( diff --git a/src/sidebar/search/routingProfiles/RoutingProfiles.tsx b/src/sidebar/search/routingProfiles/RoutingProfiles.tsx index 7510834b..cda620ff 100644 --- a/src/sidebar/search/routingProfiles/RoutingProfiles.tsx +++ b/src/sidebar/search/routingProfiles/RoutingProfiles.tsx @@ -1,4 +1,4 @@ -import React, { useContext } from 'react' +import React from 'react' import styles from './RoutingProfiles.module.css' import Dispatcher from '@/stores/Dispatcher' import { SetVehicleProfile } from '@/actions/Actions' @@ -21,8 +21,8 @@ import SettingsSVG from '@/sidebar/settings.svg' export default function ({ routingProfiles, selectedProfile, - customModelEnabled, - showSettings, + customModelEnabled, + showSettings, openSettingsHandle, }: { routingProfiles: RoutingProfile[] From 78b25ca7575d95977f9c925c5f09c2146ac02a5e Mon Sep 17 00:00:00 2001 From: Peter Date: Wed, 22 Mar 2023 10:45:54 +0100 Subject: [PATCH 14/22] show error message if invalid custom model (not possible at Settings construction time) --- src/index.tsx | 17 +++++++++++++++-- src/stores/SettingsStore.ts | 8 +++----- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index 4554f387..4cb1823b 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,6 +1,6 @@ import React from 'react' import { createRoot } from 'react-dom/client' - +import { validateJson } from 'custom-model-editor/src/validate_json' import { setTranslation } from '@/translation/Translation' import App from '@/App' import { @@ -28,7 +28,7 @@ import MapActionReceiver from '@/stores/MapActionReceiver' import { createMap, getMap, setMap } from '@/map/map' import MapFeatureStore from '@/stores/MapFeatureStore' import SettingsStore from '@/stores/SettingsStore' -import { ErrorAction, InfoReceived } from '@/actions/Actions' +import { ErrorAction, InfoReceived, SetCustomModel } from '@/actions/Actions' console.log(`Source code: https://github.com/graphhopper/graphhopper-maps/tree/${GIT_SHA}`) @@ -73,6 +73,19 @@ const smallScreenMediaQuery = window.matchMedia('(max-width: 44rem)') const mapActionReceiver = new MapActionReceiver(getMap(), routeStore, () => smallScreenMediaQuery.matches) Dispatcher.register(mapActionReceiver) +if (initialCustomModelStr) { + try { + const rsp = validateJson(initialCustomModelStr) + if(rsp.errors.length == 0 && rsp.jsonErrors.length == 0) + Dispatcher.dispatch(new SetCustomModel(JSON.parse(initialCustomModelStr), true)) + else + Dispatcher.dispatch(new ErrorAction('Open settings to fix error in custom model')) + } catch (e: any) { + Dispatcher.dispatch(new ErrorAction('Open settings to fix error in custom model: ' + e.toString())) + console.error('invalid custom model ' + initialCustomModelStr) + } +} + const navBar = new NavBar(getQueryStore(), getMapOptionsStore(), getSettingsStore()) // get infos about the api as soon as possible diff --git a/src/stores/SettingsStore.ts b/src/stores/SettingsStore.ts index 044654e5..70632a84 100644 --- a/src/stores/SettingsStore.ts +++ b/src/stores/SettingsStore.ts @@ -2,7 +2,6 @@ import Store from '@/stores/Store' import { Action } from '@/stores/Dispatcher' import { SetCustomModel, SetCustomModelBoxEnabled, ToggleDistanceUnits, ToggleShowSettings } from '@/actions/Actions' import { CustomModel } from '@/stores/QueryStore' -import { validateJson } from 'custom-model-editor/src/validate_json' export interface Settings { showDistanceInMiles: boolean @@ -15,13 +14,12 @@ export interface Settings { export default class SettingsStore extends Store { constructor(initialCustomModelStr: string | null = null) { - const valid = initialCustomModelStr ? validateJson(initialCustomModelStr).errors.length == 0 : false super({ showDistanceInMiles: false, showSettings: false, - customModel: initialCustomModelStr && valid ? JSON.parse(initialCustomModelStr) : null, - customModelValid: valid, - customModelEnabled: valid, + customModel: null, // initialCustomModelStr will be parsed later. We cannot report errors that early. + customModelValid: false, + customModelEnabled: !!initialCustomModelStr, initialCustomModelStr: initialCustomModelStr, }) } From 662591153669b8d2ef53bd49af86eef15641d4e9 Mon Sep 17 00:00:00 2001 From: Peter Date: Wed, 22 Mar 2023 10:47:17 +0100 Subject: [PATCH 15/22] not many benefits to use validateJson as e.g. encoded values not yet known --- src/custom.d.ts | 1 - src/index.tsx | 7 +------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/custom.d.ts b/src/custom.d.ts index 16a2e446..c0489278 100644 --- a/src/custom.d.ts +++ b/src/custom.d.ts @@ -3,7 +3,6 @@ declare module '*.svg' declare module '*.png' declare module 'heightgraph/src/heightgraph' declare module 'custom-model-editor/src/index' -declare module 'custom-model-editor/src/validate_json' declare module 'config' { const routingApi: string diff --git a/src/index.tsx b/src/index.tsx index 4cb1823b..52f6e0f9 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,6 +1,5 @@ import React from 'react' import { createRoot } from 'react-dom/client' -import { validateJson } from 'custom-model-editor/src/validate_json' import { setTranslation } from '@/translation/Translation' import App from '@/App' import { @@ -75,11 +74,7 @@ Dispatcher.register(mapActionReceiver) if (initialCustomModelStr) { try { - const rsp = validateJson(initialCustomModelStr) - if(rsp.errors.length == 0 && rsp.jsonErrors.length == 0) - Dispatcher.dispatch(new SetCustomModel(JSON.parse(initialCustomModelStr), true)) - else - Dispatcher.dispatch(new ErrorAction('Open settings to fix error in custom model')) + Dispatcher.dispatch(new SetCustomModel(JSON.parse(initialCustomModelStr), true)) } catch (e: any) { Dispatcher.dispatch(new ErrorAction('Open settings to fix error in custom model: ' + e.toString())) console.error('invalid custom model ' + initialCustomModelStr) From fe88161d4eede2aa7f0e5e2c2d4e54e7e82767a0 Mon Sep 17 00:00:00 2001 From: Peter Date: Wed, 22 Mar 2023 10:48:56 +0100 Subject: [PATCH 16/22] revert commit 96108d7e07af7811bdf908d32b8fc4f6804c3e89 --- jest.config.js | 4 ---- tsconfig.json | 1 - 2 files changed, 5 deletions(-) diff --git a/jest.config.js b/jest.config.js index d447e30a..27207b0f 100644 --- a/jest.config.js +++ b/jest.config.js @@ -5,8 +5,4 @@ module.exports = { '@/(.*)$': '/src/$1', config: '/config.js', }, - transform: { - '^.+\\.js?$': 'ts-jest', - }, - transformIgnorePatterns: [`/node_modules/(?!custom-model-editor/)`], } diff --git a/tsconfig.json b/tsconfig.json index fe3971ce..87d02118 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,6 @@ "sourceMap": true, "noImplicitAny": true, "jsx": "react-jsx", - "allowJs": true, "allowSyntheticDefaultImports": true, "baseUrl": ".", "importHelpers": true, From e3ca24ddc162f73eb12c65496dcb8dcfcf6f9cda Mon Sep 17 00:00:00 2001 From: Peter Date: Wed, 22 Mar 2023 11:19:08 +0100 Subject: [PATCH 17/22] remove TODO NOW --- src/sidebar/CustomModelBox.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/sidebar/CustomModelBox.tsx b/src/sidebar/CustomModelBox.tsx index b2a7f1ed..5edca7c8 100644 --- a/src/sidebar/CustomModelBox.tsx +++ b/src/sidebar/CustomModelBox.tsx @@ -121,8 +121,7 @@ export default function CustomModelBox({ encodedValues, queryOngoing, settings } // We update the app state's custom model, but we are not requesting a routing query every time the model // becomes valid. Updating the model is still important, because the routing request might be triggered by // moving markers etc. - // TODO NOW shouldn't we better ignore invalid custom models when moving markers and use last valid custom model for this? - // dispatchCustomModel(instance.value, valid, false) + dispatchCustomModel(instance.value, valid, false) setIsValid(valid) } }, []) From cc658e33730e92bb1710960f66830219ededc60f Mon Sep 17 00:00:00 2001 From: Andi Date: Thu, 23 Mar 2023 08:40:07 +0100 Subject: [PATCH 18/22] Use string to represent custom model state (#320) * Move custom model examples to separate file * Minor rename * Extract custom model area function * Remove openSettingsHandle * Rename custom model examples file * Remove warning that can never occur * Move state from settings to query store, query store even depended on settings.. * minor * move custom model box out of settings * oops, made a mistake * Use string to represent custom model state! * much better * add todos * Rename action --- src/App.tsx | 31 ++-- src/NavBar.ts | 22 +-- src/actions/Actions.ts | 16 +- src/index.tsx | 19 +-- src/sidebar/CustomModelBox.tsx | 145 ++++-------------- src/sidebar/CustomModelExamples.ts | 63 ++++++++ src/sidebar/MobileSidebar.tsx | 20 +-- src/sidebar/SettingsBox.tsx | 49 +++--- .../routingProfiles/RoutingProfiles.tsx | 8 +- src/stores/QueryStore.ts | 88 +++++++---- src/stores/SettingsStore.ts | 24 +-- test/NavBar.test.ts | 6 +- test/stores/QueryStore.test.ts | 35 ++--- test/stores/RouteStore.test.ts | 3 +- 14 files changed, 236 insertions(+), 293 deletions(-) create mode 100644 src/sidebar/CustomModelExamples.ts diff --git a/src/App.tsx b/src/App.tsx index 9dc324cd..4056a10d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -30,6 +30,7 @@ import ContextMenu from '@/layers/ContextMenu' import usePathDetailsLayer from '@/layers/UsePathDetailsLayer' import { Map } from 'ol' import { getMap } from '@/map/map' +import CustomModelBox from '@/sidebar/CustomModelBox' import useRoutingGraphLayer from '@/layers/UseRoutingGraphLayer' import useUrbanDensityLayer from '@/layers/UseUrbanDensityLayer' import useMapBorderLayer from '@/layers/UseMapBorderLayer' @@ -41,8 +42,6 @@ import Cross from '@/sidebar/times-solid.svg' import PlainButton from '@/PlainButton' import useAreasLayer from '@/layers/UseAreasLayer' import SettingsBox from '@/sidebar/SettingsBox' -import Dispatcher from '@/stores/Dispatcher' -import { ToggleShowSettings } from '@/actions/Actions' import { Settings } from '@/stores/SettingsStore' export const POPUP_CONTAINER_ID = 'popup-container' @@ -102,7 +101,7 @@ export default function App() { // our different map layers useBackgroundLayer(map, mapOptions.selectedStyle) useMapBorderLayer(map, info.bbox) - useAreasLayer(map, settings.customModelEnabled && settings.customModelValid ? settings.customModel?.areas! : null) + useAreasLayer(map, getCustomModelAreas(query)) useRoutingGraphLayer(map, mapOptions.routingGraphEnabled) useUrbanDensityLayer(map, mapOptions.urbanDensityEnabled) usePathsLayer(map, route.routingResult.paths, route.selectedPath) @@ -163,15 +162,18 @@ function LargeScreenLayout({ query, route, map, error, mapOptions, encodedValues Dispatcher.dispatch(new ToggleShowSettings())} - /> - + {settings.showSettings && } + {settings.showSettings && query.customModelEnabled && ( + + )}
      {!error.isDismissed && }
      ) } + +function getCustomModelAreas(queryStoreState: QueryStoreState): object | null { + if (!queryStoreState.customModelEnabled) return null + try { + return JSON.parse(queryStoreState.customModelStr)['areas'] + } catch { + return null + } +} diff --git a/src/NavBar.ts b/src/NavBar.ts index bf6c34c0..74c89525 100644 --- a/src/NavBar.ts +++ b/src/NavBar.ts @@ -4,21 +4,18 @@ import Dispatcher from '@/stores/Dispatcher' import { ClearPoints, SelectMapLayer, SetInitialBBox, SetQueryPoints, SetVehicleProfile } from '@/actions/Actions' // import the window like this so that it can be mocked during testing import { window } from '@/Window' -import QueryStore, { Coordinate, CustomModel, QueryPoint, QueryPointType, QueryStoreState } from '@/stores/QueryStore' +import QueryStore, { Coordinate, QueryPoint, QueryPointType, QueryStoreState } from '@/stores/QueryStore' import MapOptionsStore, { MapOptionsStoreState } from './stores/MapOptionsStore' import { getApi } from '@/api/Api' -import SettingsStore from '@/stores/SettingsStore' export default class NavBar { private readonly queryStore: QueryStore private readonly mapStore: MapOptionsStore - private readonly settingsStore: SettingsStore private ignoreStateUpdates = false - constructor(queryStore: QueryStore, mapStore: MapOptionsStore, settingsStore: SettingsStore) { + constructor(queryStore: QueryStore, mapStore: MapOptionsStore) { this.queryStore = queryStore this.mapStore = mapStore - this.settingsStore = settingsStore window.addEventListener('popstate', async () => await this.updateStateFromUrl()) } @@ -29,12 +26,7 @@ export default class NavBar { this.mapStore.register(() => this.updateUrlFromState()) } - private static createUrl( - baseUrl: string, - queryStoreState: QueryStoreState, - mapState: MapOptionsStoreState, - cm: CustomModel | null - ) { + private static createUrl(baseUrl: string, queryStoreState: QueryStoreState, mapState: MapOptionsStoreState) { const result = new URL(baseUrl) if (queryStoreState.queryPoints.filter(point => point.isInitialized).length > 0) { queryStoreState.queryPoints @@ -44,7 +36,8 @@ export default class NavBar { result.searchParams.append('profile', queryStoreState.routingProfile.name) result.searchParams.append('layer', mapState.selectedStyle.name) - if (cm) result.searchParams.append('custom_model', JSON.stringify(cm)) + if (queryStoreState.customModelEnabled) + result.searchParams.append('custom_model', queryStoreState.customModelStr.replace(/\s+/g, '')) return result } @@ -163,10 +156,7 @@ export default class NavBar { return NavBar.createUrl( window.location.origin + window.location.pathname, this.queryStore.state, - this.mapStore.state, - this.settingsStore.state.customModelEnabled && this.settingsStore.state.customModelValid - ? this.settingsStore.state.customModel - : null + this.mapStore.state ).toString() } diff --git a/src/actions/Actions.ts b/src/actions/Actions.ts index 06d83250..127a4cc7 100644 --- a/src/actions/Actions.ts +++ b/src/actions/Actions.ts @@ -77,7 +77,7 @@ export class InvalidatePoint implements Action { } } -export class SetCustomModelBoxEnabled implements Action { +export class SetCustomModelEnabled implements Action { readonly enabled: boolean constructor(enabled: boolean) { @@ -86,14 +86,12 @@ export class SetCustomModelBoxEnabled implements Action { } export class SetCustomModel implements Action { - readonly customModel: CustomModel | null - readonly valid: boolean - readonly issueRouteRequest - - constructor(customModel: CustomModel | null, valid: boolean, issueRouteRequest = false) { - this.customModel = customModel - this.valid = valid - this.issueRouteRequest = issueRouteRequest + readonly customModelStr: string + readonly issueRoutingRequest: boolean + + constructor(customModelStr: string, issueRoutingRequest: boolean) { + this.customModelStr = customModelStr + this.issueRoutingRequest = issueRoutingRequest } } diff --git a/src/index.tsx b/src/index.tsx index 52f6e0f9..49dbc250 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,5 +1,6 @@ import React from 'react' import { createRoot } from 'react-dom/client' + import { setTranslation } from '@/translation/Translation' import App from '@/App' import { @@ -27,7 +28,7 @@ import MapActionReceiver from '@/stores/MapActionReceiver' import { createMap, getMap, setMap } from '@/map/map' import MapFeatureStore from '@/stores/MapFeatureStore' import SettingsStore from '@/stores/SettingsStore' -import { ErrorAction, InfoReceived, SetCustomModel } from '@/actions/Actions' +import { ErrorAction, InfoReceived } from '@/actions/Actions' console.log(`Source code: https://github.com/graphhopper/graphhopper-maps/tree/${GIT_SHA}`) @@ -40,12 +41,11 @@ const apiKey = url.searchParams.has('key') ? url.searchParams.get('key') : confi setApi(config.routingApi, config.geocodingApi, apiKey || '') const initialCustomModelStr = url.searchParams.get('custom_model') -const settingsStore = new SettingsStore(initialCustomModelStr) -const queryStore = new QueryStore(getApi(), settingsStore) +const queryStore = new QueryStore(getApi(), initialCustomModelStr) const routeStore = new RouteStore(queryStore) setStores({ - settingsStore: settingsStore, + settingsStore: new SettingsStore(), queryStore: queryStore, routeStore: routeStore, infoStore: new ApiInfoStore(), @@ -72,16 +72,7 @@ const smallScreenMediaQuery = window.matchMedia('(max-width: 44rem)') const mapActionReceiver = new MapActionReceiver(getMap(), routeStore, () => smallScreenMediaQuery.matches) Dispatcher.register(mapActionReceiver) -if (initialCustomModelStr) { - try { - Dispatcher.dispatch(new SetCustomModel(JSON.parse(initialCustomModelStr), true)) - } catch (e: any) { - Dispatcher.dispatch(new ErrorAction('Open settings to fix error in custom model: ' + e.toString())) - console.error('invalid custom model ' + initialCustomModelStr) - } -} - -const navBar = new NavBar(getQueryStore(), getMapOptionsStore(), getSettingsStore()) +const navBar = new NavBar(getQueryStore(), getMapOptionsStore()) // get infos about the api as soon as possible getApi() diff --git a/src/sidebar/CustomModelBox.tsx b/src/sidebar/CustomModelBox.tsx index 5edca7c8..ec8fa84e 100644 --- a/src/sidebar/CustomModelBox.tsx +++ b/src/sidebar/CustomModelBox.tsx @@ -7,70 +7,12 @@ import { useCallback, useEffect, useRef, useState } from 'react' import { create } from 'custom-model-editor/src/index' import Dispatcher from '@/stores/Dispatcher' import { SetCustomModel } from '@/actions/Actions' -import { CustomModel } from '@/stores/QueryStore' import { tr } from '@/translation/Translation' import PlainButton from '@/PlainButton' -import { Settings } from '@/stores/SettingsStore' +import { customModel2prettyString, customModelExamples } from '@/sidebar/CustomModelExamples' -const examples: { [key: string]: CustomModel } = { - default_example: { - distance_influence: 15, - priority: [{ if: 'road_environment == FERRY', multiply_by: '0.9' }], - speed: [], - areas: { - type: 'FeatureCollection', - features: [], - }, - }, - exclude_motorway: { - priority: [{ if: 'road_class == MOTORWAY', multiply_by: '0.0' }], - }, - limit_speed: { - speed: [ - { if: 'true', limit_to: '100' }, - { if: 'road_class == TERTIARY', limit_to: '80' }, - ], - }, - exclude_area: { - priority: [{ if: 'in_berlin_bbox', multiply_by: '0' }], - areas: { - type: 'FeatureCollection', - features: [ - { - type: 'Feature', - id: 'berlin_bbox', - properties: {}, - geometry: { - type: 'Polygon', - coordinates: [ - [ - [13.253, 52.608], - [13.228, 52.437], - [13.579, 52.447], - [13.563, 52.609], - [13.253, 52.608], - ], - ], - }, - }, - ], - }, - }, - cargo_bike: { - speed: [{ if: 'road_class == TRACK', limit_to: '2' }], - priority: [{ if: 'max_width < 1.5 || road_class == STEPS', multiply_by: '0' }], - }, - combined: { - distance_influence: 100, - speed: [{ if: 'road_class == TRACK || road_environment == FERRY || surface == DIRT', limit_to: '10' }], - priority: [ - { if: 'road_environment == TUNNEL || toll == ALL', multiply_by: '0.5' }, - { if: 'max_weight < 3 || max_height < 2.5', multiply_by: '0.0' }, - ], - }, -} - -function convertEV(encodedValues: object[]): any { +function convertEncodedValuesForEditor(encodedValues: object[]): any { + // todo: maybe do this 'conversion' in Api.ts already and use types from there on const categories: any = {} Object.keys(encodedValues).forEach((k: any) => { const v: any = encodedValues[k] @@ -86,71 +28,49 @@ function convertEV(encodedValues: object[]): any { } export interface CustomModelBoxProps { + customModelEnabled: boolean encodedValues: object[] + customModelStr: string queryOngoing: boolean - settings: Settings } -export default function CustomModelBox({ encodedValues, queryOngoing, settings }: CustomModelBoxProps) { - const { initialCustomModelStr, customModelEnabled, showSettings, customModel } = settings +export default function CustomModelBox({ + customModelEnabled, + encodedValues, + customModelStr, + queryOngoing, +}: CustomModelBoxProps) { // todo: add types for custom model editor later const [editor, setEditor] = useState() const [isValid, setIsValid] = useState(false) const divElement = useRef(null) useEffect(() => { - const instance = create(convertEV(encodedValues), (element: Node) => divElement.current?.appendChild(element)) + // we start with the encoded values we already have, but they might be empty still + const instance = create(convertEncodedValuesForEditor(encodedValues), (element: Node) => + divElement.current?.appendChild(element) + ) setEditor(instance) instance.cm.setSize('100%', '100%') - if (customModel != null) { - // init from settings in case of entire app recreation like window resizing - instance.value = customModel2prettyString(customModel) - } else if (initialCustomModelStr != null) { - try { - instance.value = customModel2prettyString(JSON.parse(initialCustomModelStr)) - } catch (e) { - instance.value = initialCustomModelStr - } - } else { - instance.value = customModel2prettyString(examples['default_example']) - dispatchCustomModel(instance.value, true, true) - } - - instance.validListener = (valid: boolean) => { - // We update the app state's custom model, but we are not requesting a routing query every time the model - // becomes valid. Updating the model is still important, because the routing request might be triggered by - // moving markers etc. - dispatchCustomModel(instance.value, valid, false) - setIsValid(valid) - } + instance.cm.on('change', () => Dispatcher.dispatch(new SetCustomModel(instance.value, false))) + instance.validListener = (valid: boolean) => setIsValid(valid) }, []) - // without this the editor is blank after opening the box and before clicking it or resizing the window? - // but having the focus in the box after opening it is nice anyway - useEffect(() => { - if (customModelEnabled && showSettings) editor?.cm.focus() - }, [customModelEnabled, showSettings]) - useEffect(() => { if (!editor) return - - // todo: maybe do this 'conversion' in Api.ts already and use types from there on - const categories = convertEV(encodedValues) - if (!categories) { - console.warn('encoded values invalid: ' + JSON.stringify(encodedValues)) - } else { - editor.categories = categories - } - }, [encodedValues]) + editor.categories = convertEncodedValuesForEditor(encodedValues) + // focus the box when it is opened + if (customModelEnabled) editor.cm.focus() + if (editor.value !== customModelStr) editor.value = customModelStr + }, [editor, encodedValues, customModelEnabled, customModelStr]) const triggerRouting = useCallback( (event: React.KeyboardEvent) => { if (event.ctrlKey && event.key === 'Enter') { // Using this keyboard shortcut we can skip the custom model validation and directly request a routing // query. - const isValid = true - dispatchCustomModel(editor.value, isValid, true) + Dispatcher.dispatch(new SetCustomModel(editor.value, true)) } }, [editor, isValid] @@ -163,10 +83,11 @@ export default function CustomModelBox({ encodedValues, queryOngoing, settings } Dispatcher.dispatch(new SetCustomModel(editor.value, true))} + onClick={() => { + if (!customModelEnabled) Dispatcher.dispatch(new SetCustomModelEnabled(true)) + Dispatcher.dispatch(new SetCustomModel(editor.value, true)) + }} > {tr('apply_custom_model')} @@ -125,4 +143,3 @@ export default function CustomModelBox({ ) } - diff --git a/src/sidebar/MobileSidebar.tsx b/src/sidebar/MobileSidebar.tsx index 890e19f2..c7807619 100644 --- a/src/sidebar/MobileSidebar.tsx +++ b/src/sidebar/MobileSidebar.tsx @@ -10,7 +10,6 @@ import { MarkerComponent } from '@/map/Marker' import RoutingProfiles from '@/sidebar/search/routingProfiles/RoutingProfiles' import OpenInputsIcon from './unfold.svg' import CloseInputsIcon from './unfold_less.svg' -import SettingsBox from '@/sidebar/SettingsBox' import CustomModelBox from '@/sidebar/CustomModelBox' import { Settings } from '@/stores/SettingsStore' @@ -23,6 +22,7 @@ type MobileSidebarProps = { } export default function ({ query, route, error, encodedValues, settings }: MobileSidebarProps) { + const [showCustomModelBox, setShowCustomModelBox] = useState(false) // the following three elements control, whether the small search view is displayed const isShortScreen = useMediaQuery({ query: '(max-height: 55rem)' }) const [isSmallSearchView, setIsSmallSearchView] = useState(isShortScreen && hasResult(route)) @@ -60,10 +60,11 @@ export default function ({ query, route, error, encodedValues, settings }: Mobil setShowCustomModelBox(!showCustomModelBox)} + customModelBoxEnabled={query.customModelEnabled} /> - {settings.showSettings && } - {settings.showSettings && query.customModelEnabled && ( + {showCustomModelBox && ( )} - +
      )} {!error.isDismissed && } diff --git a/src/sidebar/SettingsBox.module.css b/src/sidebar/SettingsBox.module.css index 29cc5edf..660a2e4d 100644 --- a/src/sidebar/SettingsBox.module.css +++ b/src/sidebar/SettingsBox.module.css @@ -1,13 +1,50 @@ +.parent { + margin-top: 15px; + padding: 15px 5px 10px 5px; + border-top: 1px lightgray solid; + border-bottom: 1px lightgray solid; +} + .settingsTable { display: grid; - font-size: 16px; - grid-template-columns: 15% auto; + grid-template-columns: 50px auto; grid-template-rows: 1.3rem; - align-content: center; + align-content: flex-start; align-items: center; - margin: 1.5rem 0.5rem 0.5rem 0; + margin: 1rem 0.5rem 1.5rem 0; } .settingsTable svg { scale: 0.7; + margin-left: calc(-8px); /* scaling svg creates white space around it */ +} + +.settingsTable button { +} + +.infoLine { + padding: 10px 2px 0 0; + display: flex; + flex-direction: row; + justify-content: space-between; +} + +.infoLine a { + color: gray; + text-decoration: none; +} + +.infoLine a:hover { + color: black; +} + +.title { + font-weight: bold; +} + +.title, +.infoLine, +.settingsTable { + font-size: 14px; + color: gray; } diff --git a/src/sidebar/SettingsBox.tsx b/src/sidebar/SettingsBox.tsx index 96c12ec7..3fb86398 100644 --- a/src/sidebar/SettingsBox.tsx +++ b/src/sidebar/SettingsBox.tsx @@ -1,45 +1,39 @@ -import { ClearRoute, DismissLastError, SetCustomModelEnabled, ToggleDistanceUnits } from '@/actions/Actions' +import {ToggleDistanceUnits} from '@/actions/Actions' import Dispatcher from '@/stores/Dispatcher' import styles from '@/sidebar/SettingsBox.module.css' -import { tr } from '@/translation/Translation' +import {tr} from '@/translation/Translation' import PlainButton from '@/PlainButton' import OnIcon from '@/sidebar/toggle_on.svg' import OffIcon from '@/sidebar/toggle_off.svg' -import { useContext } from 'react' -import { ShowDistanceInMilesContext } from '@/ShowDistanceInMilesContext' -import { QueryStoreState } from '@/stores/QueryStore' +import {useContext} from 'react' +import {ShowDistanceInMilesContext} from '@/ShowDistanceInMilesContext' -export default function SettingsBox({ queryStoreState }: { queryStoreState: QueryStoreState }) { +export default function SettingsBox() { const showDistanceInMiles = useContext(ShowDistanceInMilesContext) return ( - <> + +
      +
      + {tr('settings')} +
      Dispatcher.dispatch(new ToggleDistanceUnits())} > - {showDistanceInMiles ? : } + {showDistanceInMiles ? : } - {/*todonow: move to css?*/} -
      +
      {tr('distance_unit', [tr(showDistanceInMiles ? 'mi' : 'km')])}
      - { - if (queryStoreState.customModelEnabled) Dispatcher.dispatch(new DismissLastError()) - Dispatcher.dispatch(new ClearRoute()) - Dispatcher.dispatch(new SetCustomModelEnabled(!queryStoreState.customModelEnabled)) - }} - > - {queryStoreState.customModelEnabled ? : } - - {/* todonow: move to css? */} -
      - {tr('custom_model_enabled')} -
      - + +
      ) } diff --git a/src/sidebar/settings.svg b/src/sidebar/open_custom_model.svg similarity index 99% rename from src/sidebar/settings.svg rename to src/sidebar/open_custom_model.svg index 8e649588..8486b0cf 100644 --- a/src/sidebar/settings.svg +++ b/src/sidebar/open_custom_model.svg @@ -40,6 +40,7 @@ id="layer1" inkscape:highlight-color="#729fcf">
      {tr('add_to_route')}
      - {customModelEnabled && Dispatcher.dispatch(new ToggleShowSettings())}> - {tr('custom_model_enabled')} - } - setShowInfo(!showInfo)}> - + setShowSettings(!showSettings)}> + {showSettings ? tr('settings_close') : tr('settings')}
      - {showInfo && ( - - )} + {showSettings && }
      ) } diff --git a/src/sidebar/search/routingProfiles/RoutingProfiles.module.css b/src/sidebar/search/routingProfiles/RoutingProfiles.module.css index 1da6b9ee..3b583108 100644 --- a/src/sidebar/search/routingProfiles/RoutingProfiles.module.css +++ b/src/sidebar/search/routingProfiles/RoutingProfiles.module.css @@ -28,50 +28,58 @@ transform: scale(1.1); } -.enabledSettings, -.settings { +.asIndicator { + position: absolute; + left: -21px; + top: -40px; + z-index: 1; + scale: 0.1; +} + +.enabledCMBox, +.cmBox { border-radius: 50%; } -.enabledSettings svg path, -.settings svg path { +.enabledCMBox svg path, +.cmBox svg path { fill: #a8a8a8; stroke: #a8a8a8; } -.enabledSettings:active svg path, -.settings:active svg path { +.enabledCMBox:active svg path, +.cmBox:active svg path { fill: lightgray; stroke: lightgray; } -.settings:hover svg path, -.enabledSettings:hover svg path { +.cmBox:hover svg path, +.enabledCMBox:hover svg path { fill: #000000; stroke: #000000; } -.settings:active svg path, -.enabledSettings:active svg path { +.cmBox:active svg path, +.enabledCMBox:active svg path { fill: lightgray; stroke: lightgray; } -.enabledSettings svg, -.settings svg { +.enabledCMBox svg, +.cmBox svg { margin: 0; padding: 8px; width: 18px; height: 18px; } -.settings svg { +.cmBox svg { transform: rotate(0); animation-name: reverse_rotate; animation-duration: 0.2s; } -.enabledSettings svg { +.enabledCMBox svg { transform: rotate(180deg); animation-name: setting_rotate; animation-duration: 0.2s; diff --git a/src/sidebar/search/routingProfiles/RoutingProfiles.tsx b/src/sidebar/search/routingProfiles/RoutingProfiles.tsx index 3e23748d..afce4067 100644 --- a/src/sidebar/search/routingProfiles/RoutingProfiles.tsx +++ b/src/sidebar/search/routingProfiles/RoutingProfiles.tsx @@ -1,7 +1,7 @@ import React from 'react' import styles from './RoutingProfiles.module.css' import Dispatcher from '@/stores/Dispatcher' -import { SetVehicleProfile, ToggleShowSettings } from '@/actions/Actions' +import { SetVehicleProfile } from '@/actions/Actions' import { RoutingProfile } from '@/api/graphhopper' import PlainButton from '@/PlainButton' import BicycleIcon from './bike.svg' @@ -16,25 +16,29 @@ import SmallTruckIcon from './small_truck.svg' import TruckIcon from './truck.svg' import WheelchairIcon from './wheelchair.svg' import { tr } from '@/translation/Translation' -import SettingsSVG from '@/sidebar/settings.svg' +import CustomModelBoxSVG from '@/sidebar/open_custom_model.svg' export default function ({ routingProfiles, selectedProfile, - showSettings, + showCustomModelBox, + toggleCustomModelBox, + customModelBoxEnabled, }: { routingProfiles: RoutingProfile[] selectedProfile: RoutingProfile - showSettings: boolean + showCustomModelBox: boolean + toggleCustomModelBox: () => void + customModelBoxEnabled: boolean }) { return (
      Dispatcher.dispatch(new ToggleShowSettings())} + title={tr('open_custom_model_box')} + className={showCustomModelBox ? styles.enabledCMBox : styles.cmBox} + onClick={toggleCustomModelBox} > - +
        {routingProfiles.map(profile => { @@ -49,6 +53,9 @@ export default function ({ onClick={() => Dispatcher.dispatch(new SetVehicleProfile(profile))} className={className} > + {customModelBoxEnabled && profile.name === selectedProfile.name && ( + + )} {getIcon(profile)} diff --git a/src/stores/SettingsStore.ts b/src/stores/SettingsStore.ts index 58c02a03..10c74293 100644 --- a/src/stores/SettingsStore.ts +++ b/src/stores/SettingsStore.ts @@ -1,17 +1,15 @@ import Store from '@/stores/Store' import { Action } from '@/stores/Dispatcher' -import { ToggleDistanceUnits, ToggleShowSettings } from '@/actions/Actions' +import { ToggleDistanceUnits } from '@/actions/Actions' export interface Settings { showDistanceInMiles: boolean - showSettings: boolean } export default class SettingsStore extends Store { constructor() { super({ showDistanceInMiles: false, - showSettings: false, }) } @@ -21,11 +19,6 @@ export default class SettingsStore extends Store { ...state, showDistanceInMiles: !state.showDistanceInMiles, } - } else if (action instanceof ToggleShowSettings) { - return { - ...state, - showSettings: !state.showSettings, - } } return state } diff --git a/src/translation/tr.json b/src/translation/tr.json index 74cffc98..83875dee 100644 --- a/src/translation/tr.json +++ b/src/translation/tr.json @@ -27,6 +27,8 @@ "help_custom_model":"Help", "apply_custom_model":"Apply", "custom_model_enabled":"Custom Model Active", +"settings":"Settings", +"settings_close":"Close", "exclude_motorway_example":"Exclude Motorway", "limit_speed_example":"Limit Speed", "cargo_bike_example":"Cargo Bike", @@ -104,6 +106,8 @@ "help_custom_model":"", "apply_custom_model":"", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"", "limit_speed_example":"", "cargo_bike_example":"", @@ -181,6 +185,8 @@ "help_custom_model":"", "apply_custom_model":"", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"", "limit_speed_example":"", "cargo_bike_example":"", @@ -258,6 +264,8 @@ "help_custom_model":"", "apply_custom_model":"", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"", "limit_speed_example":"", "cargo_bike_example":"", @@ -335,6 +343,8 @@ "help_custom_model":"", "apply_custom_model":"", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"", "limit_speed_example":"", "cargo_bike_example":"", @@ -412,6 +422,8 @@ "help_custom_model":"সাহায্য", "apply_custom_model":"", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"", "limit_speed_example":"", "cargo_bike_example":"", @@ -489,6 +501,8 @@ "help_custom_model":"", "apply_custom_model":"", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"", "limit_speed_example":"", "cargo_bike_example":"", @@ -566,6 +580,8 @@ "help_custom_model":"", "apply_custom_model":"", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"Vynechat dálnice", "limit_speed_example":"Rychlostní omezení", "cargo_bike_example":"", @@ -643,6 +659,8 @@ "help_custom_model":"", "apply_custom_model":"", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"", "limit_speed_example":"", "cargo_bike_example":"", @@ -720,6 +738,8 @@ "help_custom_model":"Hilfe", "apply_custom_model":"Anwenden", "custom_model_enabled":"Custom Model aktiv", +"settings":"Optionen", +"settings_close":"Schließen", "exclude_motorway_example":"Autobahn vermeiden", "limit_speed_example":"Max. Geschwindigkeit", "cargo_bike_example":"Lastenfahrrad", @@ -797,6 +817,8 @@ "help_custom_model":"", "apply_custom_model":"", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"", "limit_speed_example":"", "cargo_bike_example":"", @@ -874,6 +896,8 @@ "help_custom_model":"", "apply_custom_model":"", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"", "limit_speed_example":"", "cargo_bike_example":"", @@ -951,6 +975,8 @@ "help_custom_model":"", "apply_custom_model":"", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"", "limit_speed_example":"", "cargo_bike_example":"", @@ -1028,6 +1054,8 @@ "help_custom_model":"", "apply_custom_model":"", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"", "limit_speed_example":"", "cargo_bike_example":"", @@ -1105,6 +1133,8 @@ "help_custom_model":"", "apply_custom_model":"", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"", "limit_speed_example":"", "cargo_bike_example":"", @@ -1182,6 +1212,8 @@ "help_custom_model":"", "apply_custom_model":"", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"", "limit_speed_example":"", "cargo_bike_example":"", @@ -1259,6 +1291,8 @@ "help_custom_model":"Aide", "apply_custom_model":"Appliquer", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"Eviter les Autoroutes", "limit_speed_example":"Vitesse Limite", "cargo_bike_example":"Vélo cargo", @@ -1336,6 +1370,8 @@ "help_custom_model":"Aide", "apply_custom_model":"", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"", "limit_speed_example":"", "cargo_bike_example":"", @@ -1413,6 +1449,8 @@ "help_custom_model":"", "apply_custom_model":"", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"", "limit_speed_example":"", "cargo_bike_example":"", @@ -1490,6 +1528,8 @@ "help_custom_model":"", "apply_custom_model":"", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"", "limit_speed_example":"", "cargo_bike_example":"", @@ -1567,6 +1607,8 @@ "help_custom_model":"", "apply_custom_model":"", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"", "limit_speed_example":"", "cargo_bike_example":"", @@ -1644,6 +1686,8 @@ "help_custom_model":"", "apply_custom_model":"", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"", "limit_speed_example":"", "cargo_bike_example":"", @@ -1721,6 +1765,8 @@ "help_custom_model":"Súgó", "apply_custom_model":"Alkalmazás", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"Autópálya nélkül", "limit_speed_example":"Sebességkorlátozás", "cargo_bike_example":"", @@ -1798,6 +1844,8 @@ "help_custom_model":"", "apply_custom_model":"", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"", "limit_speed_example":"", "cargo_bike_example":"", @@ -1875,6 +1923,8 @@ "help_custom_model":"Aiuto", "apply_custom_model":"Applica", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"Escludi autostrada", "limit_speed_example":"Limita velocità", "cargo_bike_example":"", @@ -1952,6 +2002,8 @@ "help_custom_model":"", "apply_custom_model":"", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"", "limit_speed_example":"", "cargo_bike_example":"", @@ -2029,6 +2081,8 @@ "help_custom_model":"көмек", "apply_custom_model":"қолдану", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"Автомогистральді алып тастау", "limit_speed_example":"шектеулі жылдамдық", "cargo_bike_example":"жүк велосипеді", @@ -2106,6 +2160,8 @@ "help_custom_model":"", "apply_custom_model":"", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"", "limit_speed_example":"", "cargo_bike_example":"", @@ -2183,6 +2239,8 @@ "help_custom_model":"", "apply_custom_model":"", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"", "limit_speed_example":"", "cargo_bike_example":"", @@ -2260,6 +2318,8 @@ "help_custom_model":"Hjelp", "apply_custom_model":"Bekreft", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"Unngå motorvei", "limit_speed_example":"Begrens hastighet", "cargo_bike_example":"", @@ -2337,6 +2397,8 @@ "help_custom_model":"", "apply_custom_model":"", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"", "limit_speed_example":"", "cargo_bike_example":"", @@ -2414,6 +2476,8 @@ "help_custom_model":"", "apply_custom_model":"", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"", "limit_speed_example":"", "cargo_bike_example":"", @@ -2491,6 +2555,8 @@ "help_custom_model":"Pomoc", "apply_custom_model":"Zastosuj", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"Pomijaj autostrady", "limit_speed_example":"Ogranicz prędkość", "cargo_bike_example":"Rower towarowy", @@ -2568,6 +2634,8 @@ "help_custom_model":"", "apply_custom_model":"", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"", "limit_speed_example":"", "cargo_bike_example":"", @@ -2645,6 +2713,8 @@ "help_custom_model":"", "apply_custom_model":"", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"", "limit_speed_example":"", "cargo_bike_example":"", @@ -2722,6 +2792,8 @@ "help_custom_model":"", "apply_custom_model":"", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"", "limit_speed_example":"", "cargo_bike_example":"", @@ -2799,6 +2871,8 @@ "help_custom_model":"", "apply_custom_model":"", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"", "limit_speed_example":"", "cargo_bike_example":"", @@ -2876,6 +2950,8 @@ "help_custom_model":"", "apply_custom_model":"", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"", "limit_speed_example":"", "cargo_bike_example":"", @@ -2953,6 +3029,8 @@ "help_custom_model":"", "apply_custom_model":"", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"", "limit_speed_example":"", "cargo_bike_example":"", @@ -3030,6 +3108,8 @@ "help_custom_model":"", "apply_custom_model":"", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"", "limit_speed_example":"", "cargo_bike_example":"", @@ -3107,6 +3187,8 @@ "help_custom_model":"Hjälp", "apply_custom_model":"Applicera", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"", "limit_speed_example":"", "cargo_bike_example":"", @@ -3184,6 +3266,8 @@ "help_custom_model":"Yardım", "apply_custom_model":"Uygula", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"", "limit_speed_example":"Hız limiti", "cargo_bike_example":"", @@ -3261,6 +3345,8 @@ "help_custom_model":"", "apply_custom_model":"", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"", "limit_speed_example":"", "cargo_bike_example":"", @@ -3338,6 +3424,8 @@ "help_custom_model":"", "apply_custom_model":"", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"", "limit_speed_example":"", "cargo_bike_example":"", @@ -3415,6 +3503,8 @@ "help_custom_model":"帮助", "apply_custom_model":"应用", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"排除高速公路", "limit_speed_example":"限速", "cargo_bike_example":"", @@ -3492,6 +3582,8 @@ "help_custom_model":"", "apply_custom_model":"", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"", "limit_speed_example":"", "cargo_bike_example":"", @@ -3569,6 +3661,8 @@ "help_custom_model":"", "apply_custom_model":"", "custom_model_enabled":"", +"settings":"", +"settings_close":"", "exclude_motorway_example":"", "limit_speed_example":"", "cargo_bike_example":"", From f04c3be7b44074c1386744a409e7ff8c69271ff7 Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 23 Mar 2023 15:01:20 +0100 Subject: [PATCH 22/22] format --- src/sidebar/SettingsBox.tsx | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/sidebar/SettingsBox.tsx b/src/sidebar/SettingsBox.tsx index 3fb86398..d1a08f06 100644 --- a/src/sidebar/SettingsBox.tsx +++ b/src/sidebar/SettingsBox.tsx @@ -1,29 +1,26 @@ -import {ToggleDistanceUnits} from '@/actions/Actions' +import { ToggleDistanceUnits } from '@/actions/Actions' import Dispatcher from '@/stores/Dispatcher' import styles from '@/sidebar/SettingsBox.module.css' -import {tr} from '@/translation/Translation' +import { tr } from '@/translation/Translation' import PlainButton from '@/PlainButton' import OnIcon from '@/sidebar/toggle_on.svg' import OffIcon from '@/sidebar/toggle_off.svg' -import {useContext} from 'react' -import {ShowDistanceInMilesContext} from '@/ShowDistanceInMilesContext' +import { useContext } from 'react' +import { ShowDistanceInMilesContext } from '@/ShowDistanceInMilesContext' export default function SettingsBox() { const showDistanceInMiles = useContext(ShowDistanceInMilesContext) return ( -
        -
        - {tr('settings')} -
        +
        {tr('settings')}
        Dispatcher.dispatch(new ToggleDistanceUnits())} > - {showDistanceInMiles ? : } + {showDistanceInMiles ? : } -
        +
        {tr('distance_unit', [tr(showDistanceInMiles ? 'mi' : 'km')])}