diff --git a/server/src/configs/default.json b/server/src/configs/default.json index 9f7642839..292ea6f7d 100644 --- a/server/src/configs/default.json +++ b/server/src/configs/default.json @@ -47,10 +47,10 @@ "pokemon": true }, "queryUpdateHours": { - "pokemon": 0.25, - "quests": 0.5, - "raids": 0.2, - "nests": 1, + "pokemon": 0.17, + "quests": 0.25, + "raids": 0.05, + "nests": 0.5, "historicalRarity": 6 }, "queryOnSessionInit": { diff --git a/server/src/graphql/resolvers.js b/server/src/graphql/resolvers.js index 60ee0841a..9eee7c54c 100644 --- a/server/src/graphql/resolvers.js +++ b/server/src/graphql/resolvers.js @@ -21,17 +21,6 @@ const resolvers = { Query: { available: (_, _args, { Event, Db, perms }) => { const data = { - pokemon: perms.pokemon ? Event.available.pokemon : [], - gyms: perms.gyms || perms.raids ? Event.available.gyms : [], - nests: perms.nests ? Event.available.nests : [], - pokestops: - perms.pokestops || - perms.invasions || - perms.eventStops || - perms.quests || - perms.lures - ? Event.available.pokestops - : [], questConditions: perms.quests ? Db.questConditions : {}, masterfile: { ...Event.masterfile, invasions: Event.invasions }, filters: buildDefaultFilters(perms, Db), @@ -46,6 +35,48 @@ const resolvers = { } return data }, + availablePokemon: (_, _args, { Event, perms }) => + perms?.pokemon ? Event.available.pokemon : [], + availableGyms: (_, _args, { Event, perms }) => + Event.available.gyms.filter((x) => { + if (x.startsWith('g') || x.startsWith('t')) { + return perms?.gyms + } + if ( + x.startsWith('r') || + x.startsWith('e') || + Number.isInteger(Number(x.charAt(0))) + ) { + return perms?.raids + } + return false + }), + availableNests: (_, _args, { Event, perms }) => + perms?.nests ? Event.available.nests : [], + availablePokestops: (_, _args, { Event, perms }) => + Event.available.pokestops.filter((x) => { + if (x.startsWith('i') || x.startsWith('a')) { + return perms?.invasions + } + if (x.startsWith('d') || x.startsWith('f') || x.startsWith('h')) { + return perms?.lures + } + if ( + x.startsWith('q') || + x.startsWith('m') || + x.startsWith('x') || + x.startsWith('c') || + x.startsWith('d') || + x.startsWith('p') || + Number.isInteger(Number(x.charAt(0))) + ) { + return perms?.quests + } + if (x.startsWith('l')) { + return perms?.lures + } + return perms?.pokestops + }), backup: (_, args, { req, perms, Db }) => { if (perms?.backups && req?.user?.id) { return Db.models.Backup.getOne(args.id, req?.user?.id) diff --git a/server/src/graphql/typeDefs/index.graphql b/server/src/graphql/typeDefs/index.graphql index 90865559c..e0feed932 100644 --- a/server/src/graphql/typeDefs/index.graphql +++ b/server/src/graphql/typeDefs/index.graphql @@ -1,7 +1,11 @@ scalar JSON type Query { - available: Available + available: MapData + availablePokemon: [String] + availablePokestops: [String] + availableGyms: [String] + availableNests: [String] badges: [Badge] backup(id: ID): Backup backups: [Backup] diff --git a/server/src/graphql/typeDefs/map.graphql b/server/src/graphql/typeDefs/map.graphql index db34f45b6..74966f145 100644 --- a/server/src/graphql/typeDefs/map.graphql +++ b/server/src/graphql/typeDefs/map.graphql @@ -1,9 +1,5 @@ -type Available { +type MapData { masterfile: JSON - pokestops: [String] - gyms: [String] - pokemon: [String] - nests: [String] filters: JSON questConditions: JSON icons: JSON diff --git a/server/src/services/EventManager.js b/server/src/services/EventManager.js index 75a65453e..1671e223f 100644 --- a/server/src/services/EventManager.js +++ b/server/src/services/EventManager.js @@ -17,7 +17,14 @@ class EventManager { /** @type {import("@rm/types").Masterfile['invasions'] | {}} */ this.invasions = 'invasions' in this.masterfile ? this.masterfile.invasions : {} - this.available = { gyms: [], pokestops: [], pokemon: [], nests: [] } + + /** @type {{[key in keyof import('@rm/types').Available]: string[] }} */ + this.available = { + gyms: [], + pokestops: [], + pokemon: [], + nests: [], + } this.uicons = [] this.uaudio = [] this.uiconsBackup = {} diff --git a/src/components/layout/dialogs/UserOptions.jsx b/src/components/layout/dialogs/UserOptions.jsx index 61eea7742..679e83547 100644 --- a/src/components/layout/dialogs/UserOptions.jsx +++ b/src/components/layout/dialogs/UserOptions.jsx @@ -67,7 +67,7 @@ const MemoInputType = React.memo( next.localState?.[next.subOption || next.option], ) -export default function UserOptions() { +function UserOptions() { const { t } = useTranslation() const { open, category, type } = useLayoutStore((s) => s.dialog) @@ -201,3 +201,5 @@ export default function UserOptions() { ) } + +export default React.memo(UserOptions) diff --git a/src/components/layout/drawer/SelectorList.jsx b/src/components/layout/drawer/SelectorList.jsx index 1a4c513d2..fe1b15a39 100644 --- a/src/components/layout/drawer/SelectorList.jsx +++ b/src/components/layout/drawer/SelectorList.jsx @@ -21,6 +21,7 @@ import { useTranslateById } from '@hooks/useTranslateById' import { useMemory } from '@hooks/useMemory' import { useLayoutStore } from '@hooks/useLayoutStore' import { useDeepStore, useStorage } from '@hooks/useStorage' +import useGetAvailable from '@hooks/useGetAvailable' import { BoolToggle } from './BoolToggle' import { GenericSearchMemo } from './ItemSearch' @@ -45,10 +46,9 @@ function SelectorList({ category, subCategory, label, height = 400 }) { const searchKey = `${category}${ subCategory ? capitalize(subCategory) : '' }QuickSelect` - + const { available } = useGetAvailable(category) const { t: tId } = useTranslateById() const { t } = useTranslation() - const available = useMemory((s) => s.available[category]) const allFilters = useMemory((s) => s.filters[category]?.filter) const onlyShowAvailable = useStorage((s) => @@ -83,13 +83,13 @@ function SelectorList({ category, subCategory, label, height = 400 }) { case 'rocketPokemon': return key.startsWith('a') case 'pokemon': - return !Number.isNaN(Number(key.charAt(0))) + return Number.isInteger(Number(key.charAt(0))) default: switch (category) { case 'gyms': return key.startsWith('t') default: - return !Number.isNaN(Number(key.charAt(0))) + return Number.isInteger(Number(key.charAt(0))) } } }) diff --git a/src/hooks/useGetAvailable.js b/src/hooks/useGetAvailable.js new file mode 100644 index 000000000..1f29eab46 --- /dev/null +++ b/src/hooks/useGetAvailable.js @@ -0,0 +1,46 @@ +// @ts-check +import { useEffect, useMemo } from 'react' +import { useQuery } from '@apollo/client' +import * as queries from '@services/queries/available' +import { capitalize } from '@mui/material' + +import { useMemory } from './useMemory' + +/** + * @param {keyof import('packages/types/lib').Available} category + * @returns {{available: string[], loading: boolean, error: import('@apollo/client').ApolloError}} + */ +export default function useGetAvailable(category) { + const capitalized = capitalize(category) + const active = useMemory((s) => s.active) + const online = useMemory((s) => s.online) + + /** @type {import('@apollo/client').QueryResult<{ [key: string]: string[] }>} */ + const { data, previousData, loading, error } = useQuery( + queries[`getAvailable${capitalized}`], + { + fetchPolicy: active && online ? 'network-only' : 'cache-and-network', + }, + ) + + useEffect(() => { + if (data?.[`available${capitalized}`]) { + useMemory.setState((prev) => ({ + available: { + ...prev.available, + [category]: data[`available${capitalized}`].some( + (key, i) => key !== prev.available[category][i], + ) + ? data[`available${capitalized}`] + : prev.available[category], + // if it's the same, don't cause re-renders + }, + })) + } + }, [data]) + + return useMemo(() => { + const available = (data || previousData)?.[`available${capitalized}`] || [] + return { available, loading, error } + }, [data, previousData, loading, error]) +} diff --git a/src/hooks/useRefresh.js b/src/hooks/useRefresh.js index d33173f34..5c747a568 100644 --- a/src/hooks/useRefresh.js +++ b/src/hooks/useRefresh.js @@ -1,7 +1,7 @@ // @ts-check import { useEffect } from 'react' import { useQuery } from '@apollo/client' -import getAvailable from '@services/queries/available' +import { getMapData } from '@services/queries/available' import { deepMerge } from '@services/functions/deepMerge' import UAssets from '@services/Icons' @@ -15,7 +15,7 @@ export default function useRefresh() { const hasIcons = useMemory((s) => !!s.Icons) - const { data, stopPolling, startPolling, refetch } = useQuery(getAvailable, { + const { data, stopPolling, startPolling, refetch } = useQuery(getMapData, { fetchPolicy: active && online ? 'network-only' : 'cache-only', }) @@ -34,7 +34,7 @@ export default function useRefresh() { useEffect(() => { if (data?.available) { - const { masterfile, filters, icons, audio, ...rest } = data.available + const { masterfile, filters, icons, audio } = data.available const { icons: userIcons, audio: userAudio } = useStorage.getState() const existing = useMemory.getState() @@ -74,7 +74,6 @@ export default function useRefresh() { ) } useMemory.setState({ - available: rest, masterfile, filters, Icons, diff --git a/src/services/queries/available.js b/src/services/queries/available.js index 1f4aab270..645eceb68 100644 --- a/src/services/queries/available.js +++ b/src/services/queries/available.js @@ -1,13 +1,9 @@ import { gql } from '@apollo/client' -const getAvailable = gql` - query Available { +export const getMapData = gql` + query MapData { available { masterfile - pokestops - gyms - pokemon - nests filters questConditions icons @@ -16,4 +12,26 @@ const getAvailable = gql` } ` -export default getAvailable +export const getAvailablePokemon = gql` + query AvailablePokemon { + availablePokemon + } +` + +export const getAvailablePokestops = gql` + query AvailablePokestops { + availablePokestops + } +` + +export const getAvailableGyms = gql` + query AvailableGyms { + availableGyms + } +` + +export const getAvailableNests = gql` + query AvailableNests { + availableNests + } +`