-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add [Distance, Duration, Speed]UnitFormatter react components to auto…
…matically choose the best unit for a value They also allow clicking on the values to cycle between the different possible units. Example usage is shown in transit routing output for the walking mode, and route statistics for speed. Fixes #775
- Loading branch information
1 parent
726c117
commit d12a31e
Showing
9 changed files
with
268 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
61 changes: 61 additions & 0 deletions
61
packages/chaire-lib-frontend/src/components/pageParts/DistanceUnitFormatter.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
/* | ||
* Copyright 2023, Polytechnique Montreal and contributors | ||
* | ||
* This file is licensed under the MIT License. | ||
* License text available at https://opensource.org/licenses/MIT | ||
*/ | ||
import React, { useState, useEffect } from 'react'; | ||
import { withTranslation, WithTranslation } from 'react-i18next'; | ||
import { roundToDecimals } from 'chaire-lib-common/lib/utils/MathUtils'; | ||
import { metersToMiles, metersToFeet } from 'chaire-lib-common/lib/utils/PhysicsUtils'; | ||
|
||
const destinationUnitOptions = ['kilometers', 'meters', 'miles', 'feet'] as const; | ||
type destinationUnitOptionsType = typeof destinationUnitOptions[number]; | ||
|
||
export interface DistanceUnitFormatterProps extends WithTranslation { | ||
value: number; | ||
sourceUnit: 'kilometers' | 'meters'; | ||
destinationUnit?: destinationUnitOptionsType; | ||
} | ||
|
||
const DistanceUnitFormatter: React.FunctionComponent<DistanceUnitFormatterProps> = ( | ||
props: DistanceUnitFormatterProps | ||
) => { | ||
const [destinationUnit, setDestinationUnit] = useState<string | undefined>(props.destinationUnit); | ||
|
||
const valueInMeters = props.sourceUnit === 'meters' ? props.value : props.value / 1000; | ||
|
||
useEffect(() => { | ||
// If the destination unit was not specified, we choose the best one based on the magnitude of the value. | ||
if (destinationUnit === undefined) { | ||
if (valueInMeters < 1000) { | ||
setDestinationUnit('meters'); | ||
} else { | ||
setDestinationUnit('kilometers'); | ||
} | ||
} | ||
}, [valueInMeters]); | ||
|
||
const unitFormatters: Record<destinationUnitOptionsType, (value: number) => string> = { | ||
meters: (value) => `${roundToDecimals(value, 0)} ${props.t('main:meterAbbr')}`, | ||
kilometers: (value) => `${roundToDecimals(value / 1000, 2)} ${props.t('main:kilometerAbbr')}`, | ||
miles: (value) => `${roundToDecimals(metersToMiles(value), 2)} ${props.t('main:mileAbbr')}`, | ||
feet: (value) => `${roundToDecimals(metersToFeet(value), 0)} ${props.t('main:feetAbbr')}` | ||
}; | ||
|
||
const formattedValue = destinationUnit ? unitFormatters[destinationUnit](valueInMeters) : ''; | ||
|
||
const cycleThroughDestinationUnits = () => { | ||
// Infer the next unit based on the currently displayed unit. | ||
setDestinationUnit((prevUnit) => { | ||
return destinationUnitOptions[ | ||
(destinationUnitOptions.indexOf(prevUnit as destinationUnitOptionsType) + 1) % | ||
destinationUnitOptions.length | ||
]; | ||
}); | ||
}; | ||
|
||
return <span onClick={cycleThroughDestinationUnits}>{formattedValue}</span>; | ||
}; | ||
|
||
export default withTranslation([])(DistanceUnitFormatter); |
66 changes: 66 additions & 0 deletions
66
packages/chaire-lib-frontend/src/components/pageParts/DurationUnitFormatter.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
/* | ||
* Copyright 2023, Polytechnique Montreal and contributors | ||
* | ||
* This file is licensed under the MIT License. | ||
* License text available at https://opensource.org/licenses/MIT | ||
*/ | ||
import React, { useState, useEffect } from 'react'; | ||
import { withTranslation, WithTranslation } from 'react-i18next'; | ||
import { toXXhrYYminZZsec } from 'chaire-lib-common/lib/utils/DateTimeUtils'; | ||
import { roundToDecimals } from 'chaire-lib-common/lib/utils/MathUtils'; | ||
|
||
const destinationUnitOptions = ['hrMinSec', 'seconds', 'minutes', 'hours'] as const; | ||
type destinationUnitOptionsType = typeof destinationUnitOptions[number]; | ||
|
||
export interface DurationUnitFormatterProps extends WithTranslation { | ||
value: number; | ||
sourceUnit: 'seconds' | 'minutes' | 'hours'; | ||
destinationUnit?: destinationUnitOptionsType; | ||
} | ||
|
||
const DurationUnitFormatter: React.FunctionComponent<DurationUnitFormatterProps> = ( | ||
props: DurationUnitFormatterProps | ||
) => { | ||
const [destinationUnit, setDestinationUnit] = useState<string | undefined>(props.destinationUnit); | ||
|
||
const valueInSeconds = | ||
props.sourceUnit === 'seconds' | ||
? props.value | ||
: props.sourceUnit === 'minutes' | ||
? props.value * 60 | ||
: props.sourceUnit === 'hours' | ||
? props.value * 60 * 60 | ||
: props.value; | ||
|
||
useEffect(() => { | ||
// If the destination unit was not specified, we choose a default. | ||
if (destinationUnit === undefined) { | ||
setDestinationUnit('hrMinSec'); | ||
} | ||
}, [valueInSeconds]); | ||
|
||
const unitFormatters: Record<destinationUnitOptionsType, (value: number) => string> = { | ||
hrMinSec: (value) => | ||
toXXhrYYminZZsec(value, props.t('main:hourAbbr'), props.t('main:minuteAbbr'), props.t('main:secondAbbr')) || | ||
`${value.toString()} ${props.t('main:secondAbbr')}`, | ||
seconds: (value) => `${roundToDecimals(value, 2)} ${props.t('main:secondAbbr')}`, | ||
minutes: (value) => `${roundToDecimals(value / 60, 2)} ${props.t('main:minuteAbbr')}`, | ||
hours: (value) => `${roundToDecimals(value / 3600, 2)} ${props.t('main:hourAbbr')}` | ||
}; | ||
|
||
const formattedValue = destinationUnit ? unitFormatters[destinationUnit](valueInSeconds) : ''; | ||
|
||
const cycleThroughDestinationUnits = () => { | ||
// Infer the next unit based on the currently displayed unit. | ||
setDestinationUnit((prevUnit) => { | ||
return destinationUnitOptions[ | ||
(destinationUnitOptions.indexOf(prevUnit as destinationUnitOptionsType) + 1) % | ||
destinationUnitOptions.length | ||
]; | ||
}); | ||
}; | ||
|
||
return <span onClick={cycleThroughDestinationUnits}>{formattedValue}</span>; | ||
}; | ||
|
||
export default withTranslation([])(DurationUnitFormatter); |
57 changes: 57 additions & 0 deletions
57
packages/chaire-lib-frontend/src/components/pageParts/SpeedUnitFormatter.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
/* | ||
* Copyright 2023, Polytechnique Montreal and contributors | ||
* | ||
* This file is licensed under the MIT License. | ||
* License text available at https://opensource.org/licenses/MIT | ||
*/ | ||
import React, { useState, useEffect } from 'react'; | ||
import { withTranslation, WithTranslation } from 'react-i18next'; | ||
import { roundToDecimals } from 'chaire-lib-common/lib/utils/MathUtils'; | ||
import { mpsToKph, mpsToMph, mpsToFtps } from 'chaire-lib-common/lib/utils/PhysicsUtils'; | ||
|
||
const destinationUnitOptions = ['km/h', 'm/s', 'mph', 'ft/s'] as const; | ||
type destinationUnitOptionsType = typeof destinationUnitOptions[number]; | ||
|
||
export interface SpeedUnitFormatterProps extends WithTranslation { | ||
value: number; | ||
sourceUnit: 'm/s' | 'km/h'; | ||
destinationUnit?: destinationUnitOptionsType; | ||
} | ||
|
||
const SpeedUnitFormatter: React.FunctionComponent<SpeedUnitFormatterProps> = ( | ||
props: SpeedUnitFormatterProps | ||
) => { | ||
const [destinationUnit, setDestinationUnit] = useState<string | undefined>(props.destinationUnit); | ||
|
||
const valueInMetersPerSecond = props.sourceUnit === 'm/s' ? props.value : mpsToKph(props.value); | ||
|
||
useEffect(() => { | ||
// If the destination unit was not specified, we choose a default one. | ||
if (destinationUnit === undefined) { | ||
setDestinationUnit('km/h'); | ||
} | ||
}, [valueInMetersPerSecond]); | ||
|
||
const unitFormatters: Record<destinationUnitOptionsType, (value: number) => string> = { | ||
'm/s': (value) => `${roundToDecimals(value, 0)} ${props.t('main:mpsAbbr')}`, | ||
'km/h': (value) => `${roundToDecimals(mpsToKph(value), 1)} ${props.t('main:kphAbbr')}`, | ||
'mph': (value) => `${roundToDecimals(mpsToMph(value), 1)} ${props.t('main:mphAbbr')}`, | ||
'ft/s': (value) => `${roundToDecimals(mpsToFtps(value), 0)} ${props.t('main:ftpsAbbr')}` | ||
}; | ||
|
||
const formattedValue = destinationUnit ? unitFormatters[destinationUnit](valueInMetersPerSecond) : ''; | ||
|
||
const cycleThroughDestinationUnits = () => { | ||
// Infer the next unit based on the currently displayed unit. | ||
setDestinationUnit((prevUnit) => { | ||
return destinationUnitOptions[ | ||
(destinationUnitOptions.indexOf(prevUnit as destinationUnitOptionsType) + 1) % | ||
destinationUnitOptions.length | ||
]; | ||
}); | ||
}; | ||
|
||
return <span onClick={cycleThroughDestinationUnits}>{formattedValue}</span>; | ||
}; | ||
|
||
export default withTranslation([])(SpeedUnitFormatter); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters