mirror of
https://github.com/lrnselfreliance/wrolpi.git
synced 2026-10-25 18:40:06 -04:00
Creating useMessageDismissal which can be used to dismiss hint messages to the user.
This commit is contained in:
parent
bf36568e13
commit
f7bcf29cce
4 changed files with 130 additions and 47 deletions
|
|
@ -1547,17 +1547,39 @@ export function APIButton({
|
|||
return button;
|
||||
}
|
||||
|
||||
export function InfoMessage({children, size = null}) {
|
||||
return <Message info icon size={size}>
|
||||
<SIcon name='question'/>
|
||||
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 <React.Fragment/>
|
||||
}
|
||||
|
||||
return <Message info icon size={size} onDismiss={storageName ? () => setDismissed(true) : undefined}>
|
||||
<SIcon name={icon}/>
|
||||
<Message.Content>
|
||||
{children}
|
||||
</Message.Content>
|
||||
</Message>
|
||||
}
|
||||
|
||||
export function HandPointMessage({children, size = null}) {
|
||||
return <Message info icon size={size}>
|
||||
export function HandPointMessage({children, size = null, storageName = null}) {
|
||||
const {dismissed, setDismissed} = useMessageDismissal(storageName);
|
||||
|
||||
if (dismissed) {
|
||||
return <React.Fragment/>
|
||||
}
|
||||
|
||||
return <Message info icon size={size} onDismiss={storageName ? () => setDismissed(true) : undefined}>
|
||||
<SIcon name='hand point right'/>
|
||||
<Message.Content>
|
||||
{children}
|
||||
|
|
@ -1565,9 +1587,15 @@ export function HandPointMessage({children, size = null}) {
|
|||
</Message>
|
||||
}
|
||||
|
||||
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 <React.Fragment/>
|
||||
}
|
||||
|
||||
// Use color='yellow' because "warning" does not work.
|
||||
return <Message color='yellow' icon size={size}>
|
||||
return <Message color='yellow' icon size={size} onDismiss={storageName ? () => setDismissed(true) : undefined}>
|
||||
<SIcon name={icon}/>
|
||||
<Message.Content>
|
||||
{children}
|
||||
|
|
@ -1575,10 +1603,18 @@ export function WarningMessage({children, size = null, icon = 'exclamation'}) {
|
|||
</Message>
|
||||
}
|
||||
|
||||
export function ErrorMessage({children, size = null, icon = 'exclamation'}) {
|
||||
return <Message negative icon size={size}>
|
||||
export function ErrorMessage({children, size = null, icon = 'exclamation', storageName = null}) {
|
||||
const {dismissed, setDismissed} = useMessageDismissal(storageName);
|
||||
|
||||
if (dismissed) {
|
||||
return <React.Fragment/>
|
||||
}
|
||||
|
||||
return <Message negative icon size={size} onDismiss={storageName ? () => setDismissed(true) : undefined}>
|
||||
<SIcon name={icon}/>
|
||||
<Message.Content>{children}</Message.Content>
|
||||
<Message.Content>
|
||||
{children}
|
||||
</Message.Content>
|
||||
</Message>
|
||||
}
|
||||
|
||||
|
|
@ -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 <React.Fragment/>
|
||||
}
|
||||
|
||||
return <Message onDismiss={() => setDismissed(true)}>
|
||||
{children}
|
||||
</Message>
|
||||
}
|
||||
|
|
@ -36,46 +36,41 @@ import {MAP_VIEWER_URI} from "./Vars";
|
|||
function DockerMapImportWarning() {
|
||||
const {status} = useContext(StatusContext);
|
||||
if (status['dockerized']) {
|
||||
return <Message negative icon>
|
||||
<SIcon name='hand point right'/>
|
||||
<Message.Content>
|
||||
<Message.Header>
|
||||
Maps are not fully supported in a Docker container!
|
||||
</Message.Header>
|
||||
return <ErrorMessage storageName='hint_map_docker_warning'>
|
||||
<Message.Header>
|
||||
Maps are not fully supported in a Docker container!
|
||||
</Message.Header>
|
||||
|
||||
<p><b>Only one PBF can be imported and displayed in the docker container.</b></p>
|
||||
<p><b>Only one PBF can be imported and displayed in the docker container.</b></p>
|
||||
|
||||
<p>To import a map file, run the following docker-compose commands:</p>
|
||||
<pre> docker-compose stop map</pre>
|
||||
<pre> docker-compose rm map</pre>
|
||||
<pre> docker-compose run --rm -v /absolute/path/to/map.osm.pbf:/data.osm.pbf
|
||||
-v openstreetmap-data:/var/lib/postgresql/12/main map import
|
||||
</pre>
|
||||
<p>To import a map file, run the following docker-compose commands:</p>
|
||||
<pre> docker-compose stop map</pre>
|
||||
<pre> docker-compose rm map</pre>
|
||||
<pre> docker-compose run --rm -v /absolute/path/to/map.osm.pbf:/data.osm.pbf
|
||||
-v openstreetmap-data:/var/lib/postgresql/12/main map import
|
||||
</pre>
|
||||
|
||||
<p>Be sure to change <b>/absolute/path/to/map.osm.pbf</b>!</p>
|
||||
<p>Be sure to change <b>/absolute/path/to/map.osm.pbf</b>!</p>
|
||||
|
||||
<p>After you have imported a new PBF file, you need to clear the rendered tile cache:</p>
|
||||
<pre> docker volume rm openstreetmap-rendered-tiles</pre>
|
||||
<pre> docker volume create openstreetmap-rendered-tiles</pre>
|
||||
<p>After you have imported a new PBF file, you need to clear the rendered tile cache:</p>
|
||||
<pre> docker volume rm openstreetmap-rendered-tiles</pre>
|
||||
<pre> docker volume create openstreetmap-rendered-tiles</pre>
|
||||
|
||||
<p>Start your map container:</p>
|
||||
<pre> docker-compose up -d map</pre>
|
||||
<p>Start your map container:</p>
|
||||
<pre> docker-compose up -d map</pre>
|
||||
|
||||
<Divider/>
|
||||
<Divider/>
|
||||
|
||||
<p>You can merge multiple PBF files using osmium (the merged file can then be imported):</p>
|
||||
<pre> osmium merge file1.osm.pbf file2.osm.pbf -o merged.osm.pbf</pre>
|
||||
</Message.Content>
|
||||
</Message>;
|
||||
<p>You can merge multiple PBF files using osmium (the merged file can then be imported):</p>
|
||||
<pre> osmium merge file1.osm.pbf file2.osm.pbf -o merged.osm.pbf</pre>
|
||||
</ErrorMessage>;
|
||||
}
|
||||
return <></>;
|
||||
}
|
||||
|
||||
function DownloadMessage() {
|
||||
return <InfoMessage>
|
||||
<Message.Header>
|
||||
Where do I get map files?
|
||||
</Message.Header>
|
||||
return <InfoMessage storageName='hint_map_download'>
|
||||
<Message.Header>Where do I get map files?</Message.Header>
|
||||
|
||||
<p>You can download map files from
|
||||
<a href='https://download.geofabrik.de/'>https://download.geofabrik.de/</a>
|
||||
|
|
|
|||
|
|
@ -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() {
|
|||
</Segment>
|
||||
|
||||
{configsSegment}
|
||||
|
||||
<Segment>
|
||||
<Header as='h1'>Browser Settings</Header>
|
||||
<APIButton onClick={clearAll}>Show All Hints</APIButton>
|
||||
</Segment>
|
||||
</Container>;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = <div className="ui fluid labeled input" style={{marginBottom: '0.25em'}}>
|
||||
<div className={`ui label ${aColor}`}>A</div>
|
||||
<input
|
||||
id="a"
|
||||
name="a"
|
||||
type="number"
|
||||
ref={inputARef}
|
||||
value={unitToInputValue(state.a)}
|
||||
onChange={e => 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 &&
|
||||
<Dropdown {...unitDropdownOptions} name='dUnit' value={state.dUnit}/>}
|
||||
</div>;
|
||||
|
||||
React.useEffect(() => {
|
||||
if (inputARef.current) {
|
||||
// Focus on input A on page load.
|
||||
inputARef.current.focus();
|
||||
}
|
||||
}, []);
|
||||
|
||||
return <Form>
|
||||
<Header as='h1'>Ratio</Header>
|
||||
<Header as='h2'>A : B = C : D</Header>
|
||||
|
|
@ -333,6 +366,10 @@ const RatioCalculator = () => {
|
|||
</TableBody>
|
||||
</Table>
|
||||
</Media>
|
||||
<HandPointMessage storageName='hint_ratio_calculator'>
|
||||
<MessageHeader>Tip</MessageHeader>
|
||||
You can change inputs using the <b>a</b>, <b>b</b>, <b>c</b>, and <b>d</b> keys.
|
||||
</HandPointMessage>
|
||||
</Form>
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue