From 40a5c3c469b142f5cb37ab58dbae1221b6d2ffac Mon Sep 17 00:00:00 2001 From: Derick M <58572875+TurtIeSocks@users.noreply.github.com> Date: Fri, 23 Feb 2024 14:06:32 -0500 Subject: [PATCH] refactor: move distance unit to client --- packages/locales/lib/human/en.json | 5 +- packages/types/lib/server.d.ts | 1 - server/src/configs/default.json | 2 +- server/src/models/Pokemon.js | 8 +- server/src/services/DbCheck.js | 9 +-- server/src/services/config.js | 17 ++++- src/components/Config.jsx | 4 + .../layout/dialogs/search/renderOption.jsx | 8 +- src/components/layout/drawer/Settings.jsx | 76 ++++++++++--------- src/components/popups/Route.jsx | 16 ++-- src/hooks/useFormatDistance.js | 14 ++++ src/hooks/useStorage.js | 1 + src/services/functions/formatDistance.js | 22 ++++++ 13 files changed, 116 insertions(+), 67 deletions(-) create mode 100644 src/hooks/useFormatDistance.js create mode 100644 src/services/functions/formatDistance.js diff --git a/packages/locales/lib/human/en.json b/packages/locales/lib/human/en.json index 01b3a9f39..b0092ba98 100644 --- a/packages/locales/lib/human/en.json +++ b/packages/locales/lib/human/en.json @@ -734,5 +734,8 @@ "gender_filters_all": "Gender Applies to All", "and_caption": "Linked Together", "or_caption": "Filters Independently", - "filter_help": "Filter Help" + "filter_help": "Filter Help", + "kilometers": "Kilometers", + "miles": "Miles", + "distance_unit": "Distance Unit" } diff --git a/packages/types/lib/server.d.ts b/packages/types/lib/server.d.ts index ce9cf387f..923963b47 100644 --- a/packages/types/lib/server.d.ts +++ b/packages/types/lib/server.d.ts @@ -102,7 +102,6 @@ export interface DbCheckClass { historical: Rarity questConditions: { [key: string]: string[] } rarityPercents: RarityPercents - distanceUnit: 'km' | 'mi' reactMapDb: null | number filterContext: { Route: { maxDistance: number; maxDuration: number } diff --git a/server/src/configs/default.json b/server/src/configs/default.json index 26af03924..881d5d1cd 100644 --- a/server/src/configs/default.json +++ b/server/src/configs/default.json @@ -239,7 +239,7 @@ "permImageDir": "images/perms", "permArrayImages": false, "clientTimeoutMinutes": 30, - "distanceUnit": "km", + "distanceUnit": "kilometers", "enablePokemonPopupCoordsSelector": false, "enableGymPopupCoordsSelector": false, "enablePokestopPopupCoordsSelector": false, diff --git a/server/src/models/Pokemon.js b/server/src/models/Pokemon.js index 15c8aad21..f3e37b87f 100644 --- a/server/src/models/Pokemon.js +++ b/server/src/models/Pokemon.js @@ -1,4 +1,3 @@ -/* eslint-disable no-unused-vars */ /* eslint-disable no-restricted-syntax */ const { Model, raw, ref } = require('objection') const i18next = require('i18next') @@ -24,7 +23,6 @@ const { } = require('../services/filters/pokemon/constants') const PkmnFilter = require('../services/filters/pokemon/Backend') -const distanceUnit = config.getSafe('map.misc.distanceUnit') const searchResultsLimit = config.getSafe('api.searchResultsLimit') const queryLimits = config.getSafe('api.queryLimits') const queryDebug = config.getSafe('devOptions.queryDebug') @@ -652,11 +650,7 @@ class Pokemon extends Model { point([poke.lon, poke.lat]), point([args.lon, args.lat]), { - units: - distanceUnit.toLowerCase() === 'km' || - distanceUnit.toLowerCase() === 'kilometers' - ? 'kilometers' - : 'miles', + units: 'meters', }, ).toFixed(2), })) diff --git a/server/src/services/DbCheck.js b/server/src/services/DbCheck.js index 07056118f..8ef2d777b 100644 --- a/server/src/services/DbCheck.js +++ b/server/src/services/DbCheck.js @@ -107,7 +107,6 @@ module.exports = class DbCheck { ) process.exit(0) } - this.distanceUnit = config.getSafe('map.misc.distanceUnit') } /** @@ -115,13 +114,11 @@ module.exports = class DbCheck { * @param {boolean} isMad * @returns {ReturnType} */ - getDistance(args, isMad) { + static getDistance(args, isMad) { const radLat = args.lat * (Math.PI / 180) const radLon = args.lon * (Math.PI / 180) return raw( - `ROUND(( ${ - this.distanceUnit === 'mi' ? '3959' : '6371' - } * acos( cos( ${radLat} ) * cos( radians( ${ + `ROUND(( 6371000 * acos( cos( ${radLat} ) * cos( radians( ${ isMad ? 'latitude' : 'lat' } ) ) * cos( radians( ${ isMad ? 'longitude' : 'lon' @@ -456,7 +453,7 @@ module.exports = class DbCheck { perms, args, source, - this.getDistance(args, source.isMad), + DbCheck.getDistance(args, source.isMad), bbox, ), ), diff --git a/server/src/services/config.js b/server/src/services/config.js index ab902d50b..ed1cf9535 100644 --- a/server/src/services/config.js +++ b/server/src/services/config.js @@ -173,7 +173,22 @@ const mergeMapConfig = (input = {}) => { /** @type {import('@rm/types').Config['map']} */ const merged = config.util.extendDeep({}, base, input) - + if ( + merged.misc.distanceUnit !== 'kilometers' || + merged.misc.distanceUnit !== 'miles' + ) { + log.warn( + HELPERS.config, + `Invalid distanceUnit: ${merged.misc.distanceUnit}, only 'kilometers' OR 'miles' are allowed.`, + ) + if (merged.misc.distance === 'km') { + merged.misc.distanceUnit = 'kilometers' + } else if (merged.misc.distance === 'mi') { + merged.misc.distanceUnit = 'miles' + } else { + merged.misc.distanceUnit = 'kilometers' + } + } merged.general.menuOrder = merged?.general?.menuOrder ? merged.general.menuOrder.filter((x) => allowedMenuItems.includes(x)) : [] diff --git a/src/components/Config.jsx b/src/components/Config.jsx index 6dc15b306..2450e8e45 100644 --- a/src/components/Config.jsx +++ b/src/components/Config.jsx @@ -87,6 +87,10 @@ export default function Config({ children }) { tileServers: Object.fromEntries( data.tileServers.map((item) => [item.name, item]), ), + distanceUnit: { + kilometers: { name: 'kilometers' }, + miles: { name: 'miles' }, + }, } useScannerSessionStorage.setState((prev) => ({ diff --git a/src/components/layout/dialogs/search/renderOption.jsx b/src/components/layout/dialogs/search/renderOption.jsx index ed36968d4..2ad804a95 100644 --- a/src/components/layout/dialogs/search/renderOption.jsx +++ b/src/components/layout/dialogs/search/renderOption.jsx @@ -16,6 +16,7 @@ import { Divider, Typography } from '@mui/material' import Utility from '@services/Utility' import { RawTimeSince } from '@components/popups/common/Timer' import { getGruntReward } from '@services/functions/getGruntReward' +import { formatDistance } from '@services/functions/formatDistance' import { OptionImageMemo } from './OptionImage' @@ -92,8 +93,7 @@ const InvasionSubtitle = ({ /** @type {import('@mui/material').AutocompleteProps['renderOption']} */ export const renderOption = (props, option) => { const { searchTab } = useStorage.getState() - const { distanceUnit, questMessage } = useMemory.getState().config.misc - + const { questMessage } = useMemory.getState().config.misc return ( ({ @@ -146,9 +146,7 @@ export const renderOption = (props, option) => { sx={{ flexGrow: 1, flexShrink: 1 }} /> , + navigationControls: , + tileServers: , + distanceUnit: , +} +const FALLBACK = + +function GeneralSetting({ setting }) { + const { t } = useTranslation() + const staticSettings = useMemory((s) => s.settings) + const value = useStorage((s) => s.settings[setting]) + return ( + { + useStorage.setState((prev) => ({ + settings: { + ...prev.settings, + [target.name]: staticSettings[target.name][target.value].name, + }, + })) + }} + icon={ICON_MAP[setting] || FALLBACK} + > + {Object.keys(staticSettings[setting]).map((option) => ( + + {t( + `${Utility.camelToSnake(setting)}_${option.toLowerCase()}`, + Utility.getProperName(option), + )} + + ))} + + ) } export default function Settings() { const { t } = useTranslation() - const staticSettings = useMemory((s) => s.settings) const separateDrawerActions = useMemory( (s) => s.config.general.separateDrawerActions, ) const holidayEffects = useMemory((s) => s.config.holidayEffects) || [] + const staticSettings = useMemory((s) => s.settings) - const settings = useStorage((s) => s.settings) const darkMode = useStorage((s) => s.darkMode) return ( <> {t('general')} - {Object.keys(staticSettings).map((setting) => { - const Icon = ICON_MAP[setting] || DevicesOtherIcon - return ( - { - useStorage.setState((prev) => ({ - settings: { - ...prev.settings, - [target.name]: staticSettings[target.name][target.value].name, - }, - })) - }} - icon={} - > - {Object.keys(staticSettings[setting]).map((option) => ( - - {t( - `${Utility.camelToSnake(setting)}_${option.toLowerCase()}`, - Utility.getProperName(option), - )} - - ))} - - ) - })} + {Object.keys(staticSettings).map((setting) => ( + + ))} diff --git a/src/components/popups/Route.jsx b/src/components/popups/Route.jsx index 769ce56ae..c45bf477a 100644 --- a/src/components/popups/Route.jsx +++ b/src/components/popups/Route.jsx @@ -25,6 +25,7 @@ import Query from '@services/Query' import formatInterval from '@services/functions/formatInterval' import { useMemory } from '@hooks/useMemory' import { useStorage } from '@hooks/useStorage' +import { useFormatDistance } from '@hooks/useFormatDistance' import Title from './common/Title' import TimeSince from './common/Timer' @@ -131,8 +132,8 @@ function ExpandableWrapper({ disabled = false, children, expandKey, primary }) { */ export default function RoutePopup({ end, ...props }) { const [route, setRoute] = React.useState({ ...props, tags: [] }) - const { i18n } = useTranslation() const { config } = useMemory.getState() + const formatDistance = useFormatDistance() const [getRoute, { data, called }] = useLazyQuery(Query.routes('getOne'), { variables: { id: props.id }, @@ -165,13 +166,6 @@ export default function RoutePopup({ end, ...props }) { return sum }, [!!route.waypoints]) - const numFormatter = new Intl.NumberFormat(i18n.language, { - unitDisplay: 'short', - unit: 'meter', - style: 'unit', - maximumFractionDigits: 1, - }) - const imagesAreEqual = route.image === (end ? route.end_image : route.start_image) @@ -224,7 +218,7 @@ export default function RoutePopup({ end, ...props }) { )} - {`${numFormatter.format(route.distance_meters || 0)}`} + {formatDistance(route.distance_meters)} {`${formatInterval((route.duration_seconds || 0) * 1000).str}`} @@ -237,13 +231,13 @@ export default function RoutePopup({ end, ...props }) { - {numFormatter.format(elevation.up)} + {formatDistance(elevation.up)} - {numFormatter.format(elevation.down)} + {formatDistance(elevation.down)} diff --git a/src/hooks/useFormatDistance.js b/src/hooks/useFormatDistance.js new file mode 100644 index 000000000..34a8d0059 --- /dev/null +++ b/src/hooks/useFormatDistance.js @@ -0,0 +1,14 @@ +// @ts-check +import { useTranslation } from 'react-i18next' + +import { formatDistance } from '@services/functions/formatDistance' + +import { useStorage } from './useStorage' + +/** @returns {(meters: number) => string} */ +export function useFormatDistance() { + const { i18n } = useTranslation() + const unit = useStorage((s) => s.settings.distanceUnit) + + return (meters) => formatDistance(meters, unit, i18n.language) +} diff --git a/src/hooks/useStorage.js b/src/hooks/useStorage.js index 0d8eda4da..852c15647 100644 --- a/src/hooks/useStorage.js +++ b/src/hooks/useStorage.js @@ -18,6 +18,7 @@ import { persist, createJSONStorage } from 'zustand/middleware' * navigationControls: 'react' | 'leaflet' * navigation: string, * tileServers: string + * distanceUnit: 'kilometers' | 'miles' * }, * searches: Record, * tabs: Record, diff --git a/src/services/functions/formatDistance.js b/src/services/functions/formatDistance.js new file mode 100644 index 000000000..7c654eff9 --- /dev/null +++ b/src/services/functions/formatDistance.js @@ -0,0 +1,22 @@ +// @ts-check +import { useStorage } from '@hooks/useStorage' + +const METERS_PER_MILE = 1609.344 + +/** @param {number} meters */ +export function formatDistance( + meters, + unit = useStorage.getState().settings.distanceUnit, + locale = localStorage.getItem('i18nextLng') || 'en', +) { + const distance = unit === 'miles' ? meters / METERS_PER_MILE : meters / 1000 + + const numFormatter = new Intl.NumberFormat(locale, { + unitDisplay: 'short', + unit: unit.replace(/s$/, ''), + style: 'unit', + maximumFractionDigits: 2, + }) + + return numFormatter.format(distance) +}