From f7bcf29ccecc38dad442d8c895326996d99b35ed Mon Sep 17 00:00:00 2001 From: Roland Date: Wed, 22 Jan 2025 18:58:18 -0700 Subject: [PATCH] Creating useMessageDismissal which can be used to dismiss hint messages to the user. --- app/src/components/Common.js | 78 +++++++++++++++---- app/src/components/Map.js | 51 ++++++------ app/src/components/admin/Settings.js | 7 ++ .../components/calculators/RatioCalculator.js | 41 +++++++++- 4 files changed, 130 insertions(+), 47 deletions(-) diff --git a/app/src/components/Common.js b/app/src/components/Common.js index ed5e97e3..b8d31a10 100644 --- a/app/src/components/Common.js +++ b/app/src/components/Common.js @@ -1547,17 +1547,39 @@ export function APIButton({ return button; } -export function InfoMessage({children, size = null}) { - return - +export const useMessageDismissal = (messageName) => { + const [dismissed, setDismissed] = useLocalStorage('dismissed_hints', {}); + + return { + dismissed: dismissed[messageName] || false, // true or false + setDismissed: (value) => setDismissed({...dismissed, [messageName]: value}), + clearAll: () => setDismissed({}), + } +} + +export function InfoMessage({children, size = null, storageName = null, icon = 'question'}) { + const {dismissed, setDismissed} = useMessageDismissal(storageName); + + if (dismissed) { + return + } + + return setDismissed(true) : undefined}> + {children} } -export function HandPointMessage({children, size = null}) { - return +export function HandPointMessage({children, size = null, storageName = null}) { + const {dismissed, setDismissed} = useMessageDismissal(storageName); + + if (dismissed) { + return + } + + return setDismissed(true) : undefined}> {children} @@ -1565,9 +1587,15 @@ export function HandPointMessage({children, size = null}) { } -export function WarningMessage({children, size = null, icon = 'exclamation'}) { +export function WarningMessage({children, size = null, icon = 'exclamation', storageName = null}) { + const {dismissed, setDismissed} = useMessageDismissal(storageName); + + if (dismissed) { + return + } + // Use color='yellow' because "warning" does not work. - return + return setDismissed(true) : undefined}> {children} @@ -1575,10 +1603,18 @@ export function WarningMessage({children, size = null, icon = 'exclamation'}) { } -export function ErrorMessage({children, size = null, icon = 'exclamation'}) { - return +export function ErrorMessage({children, size = null, icon = 'exclamation', storageName = null}) { + const {dismissed, setDismissed} = useMessageDismissal(storageName); + + if (dismissed) { + return + } + + return setDismissed(true) : undefined}> - {children} + + {children} + } @@ -1788,6 +1824,10 @@ export function useLocalStorage(key, initialValue, decode = JSON.parse, encode = // Save to localStorage when the value changes useEffect(() => { + if (key === null || key === undefined) { + // key was not defined, do not add this to storage. + return + } try { decode(value); window.localStorage.setItem(key, value); @@ -1920,10 +1960,14 @@ export function getDistinctColor(hexColors) { return hslToHex(newColor.h, newColor.s, newColor.l); } -export const isReactChildNull = (children) => { - // Returns `true` if a React element returns `null` as it's child. - const nonNullChildren = React.Children.map(children, - (child) => React.isValidElement(child) && child.type(child.props) - ); - return nonNullChildren.length === 0; -} +export const DismissibleMessage = ({storageName, children}) => { + const [dismissed, setDismissed] = useLocalStorage(storageName, false); + + if (dismissed) { + return + } + + return setDismissed(true)}> + {children} + +} \ No newline at end of file diff --git a/app/src/components/Map.js b/app/src/components/Map.js index b40b1d20..d7e8dc74 100644 --- a/app/src/components/Map.js +++ b/app/src/components/Map.js @@ -36,46 +36,41 @@ import {MAP_VIEWER_URI} from "./Vars"; function DockerMapImportWarning() { const {status} = useContext(StatusContext); if (status['dockerized']) { - return - - - - Maps are not fully supported in a Docker container! - + return + + Maps are not fully supported in a Docker container! + -

Only one PBF can be imported and displayed in the docker container.

+

Only one PBF can be imported and displayed in the docker container.

-

To import a map file, run the following docker-compose commands:

-
  docker-compose stop map
-
  docker-compose rm map
-
  docker-compose run --rm -v /absolute/path/to/map.osm.pbf:/data.osm.pbf
-                        -v openstreetmap-data:/var/lib/postgresql/12/main map import
-                    
+

To import a map file, run the following docker-compose commands:

+
  docker-compose stop map
+
  docker-compose rm map
+
  docker-compose run --rm -v /absolute/path/to/map.osm.pbf:/data.osm.pbf
+                    -v openstreetmap-data:/var/lib/postgresql/12/main map import
+                
-

Be sure to change /absolute/path/to/map.osm.pbf!

+

Be sure to change /absolute/path/to/map.osm.pbf!

-

After you have imported a new PBF file, you need to clear the rendered tile cache:

-
  docker volume rm openstreetmap-rendered-tiles
-
  docker volume create openstreetmap-rendered-tiles
+

After you have imported a new PBF file, you need to clear the rendered tile cache:

+
  docker volume rm openstreetmap-rendered-tiles
+
  docker volume create openstreetmap-rendered-tiles
-

Start your map container:

-
  docker-compose up -d map
+

Start your map container:

+
  docker-compose up -d map
- + -

You can merge multiple PBF files using osmium (the merged file can then be imported):

-
  osmium merge file1.osm.pbf file2.osm.pbf -o merged.osm.pbf
-
-
; +

You can merge multiple PBF files using osmium (the merged file can then be imported):

+
  osmium merge file1.osm.pbf file2.osm.pbf -o merged.osm.pbf
+ ; } return <>; } function DownloadMessage() { - return - - Where do I get map files? - + return + Where do I get map files?

You can download map files from  https://download.geofabrik.de/ diff --git a/app/src/components/admin/Settings.js b/app/src/components/admin/Settings.js index 19b3a66a..8da4bafd 100644 --- a/app/src/components/admin/Settings.js +++ b/app/src/components/admin/Settings.js @@ -23,6 +23,7 @@ import { HotspotToggle, ThrottleToggle, Toggle, + useMessageDismissal, WROLModeMessage } from "../Common"; import QRCode from "react-qr-code"; @@ -177,6 +178,7 @@ export function SettingsPage() { const [qrOpen, setQrOpen] = React.useState(false); const [ready, setReady] = React.useState(false); const [pendingSave, setPendingSave] = React.useState(false); + const {clearAll} = useMessageDismissal(); // Get current settings from the API. const {settings, saveSettings, fetchSettings} = React.useContext(SettingsContext); @@ -539,6 +541,11 @@ export function SettingsPage() { {configsSegment} + + +

Browser Settings
+ Show All Hints + ; } diff --git a/app/src/components/calculators/RatioCalculator.js b/app/src/components/calculators/RatioCalculator.js index 767f033d..da9e97e3 100644 --- a/app/src/components/calculators/RatioCalculator.js +++ b/app/src/components/calculators/RatioCalculator.js @@ -1,7 +1,7 @@ -import {Dropdown, TableBody, TableCell, TableRow} from "semantic-ui-react"; +import {Dropdown, MessageHeader, TableBody, TableCell, TableRow} from "semantic-ui-react"; import {Form, Header, Table} from "../Theme"; import React from "react"; -import {roundDigits, useLocalStorage} from "../Common"; +import {HandPointMessage, roundDigits, useLocalStorage} from "../Common"; import {createUnit, multiply, unit} from "mathjs"; import {Media} from "../../contexts/contexts"; @@ -211,6 +211,11 @@ const RatioCalculator = () => { setStorageRecentUnits({...storageRecentUnits, [key]: state.dUnit}); }, [state.dUnit]); + const inputARef = React.useRef(null); + const inputBRef = React.useRef(null); + const inputCRef = React.useRef(null); + const inputDRef = React.useRef(null); + const unitOptions = baseToUnitsMap[state.base]; const unitDropdownOptions = { @@ -234,14 +239,29 @@ const RatioCalculator = () => { style={{marginBottom: '1em'}} />; + const handleInputChange = (e) => { + // Allow the user to switch between inputs using a, b, c, and d keys. + if (e?.key === 'a') { + inputARef.current.focus(); + } else if (e?.key === 'b') { + inputBRef.current.focus(); + } else if (e?.key === 'c') { + inputCRef.current.focus(); + } else if (e?.key === 'd') { + inputDRef.current.focus(); + } + } + const inputA =
A
dispatch(e)} + onKeyPress={handleInputChange} onFocus={e => e.target.select()} /> {state.base && @@ -254,8 +274,10 @@ const RatioCalculator = () => { id="b" name="b" type="number" + ref={inputBRef} value={unitToInputValue(state.b)} onChange={e => dispatch(e)} + onKeyPress={handleInputChange} onFocus={e => e.target.select()} /> {state.base && @@ -268,8 +290,10 @@ const RatioCalculator = () => { id="c" name="c" type="number" + ref={inputCRef} value={unitToInputValue(state.c)} onChange={e => dispatch(e)} + onKeyPress={handleInputChange} onFocus={e => e.target.select()} /> {state.base && @@ -282,14 +306,23 @@ const RatioCalculator = () => { id="d" name="d" type="number" + ref={inputDRef} value={unitToInputValue(state.d)} onChange={e => dispatch(e)} + onKeyPress={handleInputChange} onFocus={e => e.target.select()} /> {state.base && }
; + React.useEffect(() => { + if (inputARef.current) { + // Focus on input A on page load. + inputARef.current.focus(); + } + }, []); + return
Ratio
A : B = C : D
@@ -333,6 +366,10 @@ const RatioCalculator = () => { + + Tip + You can change inputs using the a, b, c, and d keys. +
}