diff --git a/CHANGELOG.md b/CHANGELOG.md index 147e5ffe5..e310e6167 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,60 @@ +# [1.41.0-develop.5](https://github.com/WatWowMap/ReactMap/compare/v1.41.0-develop.4...v1.41.0-develop.5) (2026-01-08) + + +### Bug Fixes + +* escape gpx xml ([#1186](https://github.com/WatWowMap/ReactMap/issues/1186)) ([7bd5ce1](https://github.com/WatWowMap/ReactMap/commit/7bd5ce10d3e8aa6f64faac8d3d9773c589d6a28e)) + +# [1.41.0-develop.4](https://github.com/WatWowMap/ReactMap/compare/v1.41.0-develop.3...v1.41.0-develop.4) (2025-12-31) + + +### Bug Fixes + +* area reload ([cea3141](https://github.com/WatWowMap/ReactMap/commit/cea3141386d804f502e83e89b9bf92da93102a5d)) + +# [1.41.0-develop.3](https://github.com/WatWowMap/ReactMap/compare/v1.41.0-develop.2...v1.41.0-develop.3) (2025-12-16) + + +### Bug Fixes + +* logic bug in loading non-clustered elements ([#1161](https://github.com/WatWowMap/ReactMap/issues/1161)) ([a2c7f17](https://github.com/WatWowMap/ReactMap/commit/a2c7f17e02ff7cd5467d4bf5ccd8b13a444ba725)) +* more robust discord handling ([#1181](https://github.com/WatWowMap/ReactMap/issues/1181)) ([b579ded](https://github.com/WatWowMap/ReactMap/commit/b579dedeb196fb4d8b1da025e325049bfd7833e8)) + + +### Features + +* default clientPrompt to none to skip permission reapproval ([d3ea283](https://github.com/WatWowMap/ReactMap/commit/d3ea283b5eacda18b9f4725e50e5353a955a363d)) + +# [1.41.0-develop.2](https://github.com/WatWowMap/ReactMap/compare/v1.41.0-develop.1...v1.41.0-develop.2) (2025-12-15) + + +### Bug Fixes + +* remove extra Forms ([0f157ed](https://github.com/WatWowMap/ReactMap/commit/0f157edba37eb727fdb9f4a5b96bdf417298cdfc)) + +# [1.41.0-develop.1](https://github.com/WatWowMap/ReactMap/compare/v1.40.2-develop.1...v1.41.0-develop.1) (2025-12-11) + + +### Bug Fixes + +* battle/raid icon max height ([497f350](https://github.com/WatWowMap/ReactMap/commit/497f3504a4ea6000184c9df30c5a3c3e76d65d80)) +* no scrollbars plz ([907ca33](https://github.com/WatWowMap/ReactMap/commit/907ca331beb1fe81dd671732d5ebe1980d8d6faa)) + + +### Features + +* dynamic placed Pokemon dropdown height ([8a0447c](https://github.com/WatWowMap/ReactMap/commit/8a0447c3e9d7df9c4aa9add657a234a1bc59b412)) +* support stationed pokemon extras ([4f5aace](https://github.com/WatWowMap/ReactMap/commit/4f5aace285285d55e3a946fb2f465b7609a9caea)) + +## [1.40.2-develop.1](https://github.com/WatWowMap/ReactMap/compare/v1.40.1...v1.40.2-develop.1) (2025-12-10) + + +### Bug Fixes + +* battle/raid filtering race condition ([2a9cbbf](https://github.com/WatWowMap/ReactMap/commit/2a9cbbf180313064830a524f86eae62f41b6b87b)) +* ground raid/battle icons ([cd01a92](https://github.com/WatWowMap/ReactMap/commit/cd01a92bda81f786ca2c14f10df8cc4489163fd6)) +* weather icon in pokemon background popup ([21b50f5](https://github.com/WatWowMap/ReactMap/commit/21b50f51b81bf6f7723753a4f77289ead99c51e7)) + ## [1.40.1](https://github.com/WatWowMap/ReactMap/compare/v1.40.0...v1.40.1) (2025-12-09) diff --git a/config/default.json b/config/default.json index db377b97f..1742b860d 100644 --- a/config/default.json +++ b/config/default.json @@ -719,7 +719,7 @@ "allowedGuilds": [], "blockedGuilds": [], "allowedUsers": [], - "clientPrompt": "consent", + "clientPrompt": "none", "thumbnailUrl": "https://user-images.githubusercontent.com/58572875/167069223-745a139d-f485-45e3-a25c-93ec4d09779c.png", "trialPeriod": { "start": { diff --git a/package.json b/package.json index d4152e1be..10e04a679 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "reactmap", - "version": "1.40.1", + "version": "1.41.0-develop.5", "private": true, "description": "React based frontend map.", "license": "MIT", diff --git a/packages/config/.configref b/packages/config/.configref index ad8102533..c2dbeed58 100644 --- a/packages/config/.configref +++ b/packages/config/.configref @@ -1 +1 @@ -26702 \ No newline at end of file +26699 \ No newline at end of file diff --git a/packages/types/lib/scanner.d.ts b/packages/types/lib/scanner.d.ts index 003c7c9aa..6cce54a32 100644 --- a/packages/types/lib/scanner.d.ts +++ b/packages/types/lib/scanner.d.ts @@ -372,11 +372,8 @@ export interface Route { export type FullRoute = FullModel -export interface StationPokemon { +export interface StationPokemon extends PokemonDisplay { pokemon_id: number - form: number - costume: number - gender: number bread_mode: number } diff --git a/server/src/graphql/typeDefs/scanner.graphql b/server/src/graphql/typeDefs/scanner.graphql index 4296affec..12bc0efa2 100644 --- a/server/src/graphql/typeDefs/scanner.graphql +++ b/server/src/graphql/typeDefs/scanner.graphql @@ -323,5 +323,11 @@ type StationPokemon { form: Int costume: Int gender: Int + shiny: Boolean + temp_evolution: Int + temp_evolution_finish_ms: Float + alignment: Int + badge: Int + background: Int bread_mode: Int } diff --git a/server/src/services/DiscordClient.js b/server/src/services/DiscordClient.js index 4591cda1a..6a191e9ae 100644 --- a/server/src/services/DiscordClient.js +++ b/server/src/services/DiscordClient.js @@ -87,23 +87,36 @@ class DiscordClient extends AuthClient { this.client.login(this.strategy.botToken) } - /** @param {string} guildId @param {string} userId */ + /** + * @param {string} guildId + * @param {string} userId + * @returns {Promise} + */ async getUserRoles(guildId, userId) { try { - const members = await this.client.guilds.cache - .get(guildId) - ?.members.fetch() - if (members) { - const member = members.get(userId) - return member?.roles.cache.map((role) => role.id) || [] - } - return [] + const guild = + this.client.guilds.cache.get(guildId) || + (await this.client.guilds.fetch(guildId)) + const member = await guild?.members.fetch(userId) + return member?.roles.cache.map((role) => role.id) || [] } catch (e) { + const code = + e && typeof e === 'object' && 'code' in e ? Number(e.code) : null + if (code === 10007) { + this.log.debug( + 'Discord member not found in guild', + guildId, + 'for user', + userId, + ) + return [] + } this.log.error( 'Failed to get roles in guild', guildId, 'for user', userId, + e, ) } return [] diff --git a/server/src/utils/reloadConfig.js b/server/src/utils/reloadConfig.js index d82adaff1..ef7afd985 100644 --- a/server/src/utils/reloadConfig.js +++ b/server/src/utils/reloadConfig.js @@ -5,7 +5,7 @@ const { log, TAGS } = require('@rm/logger') const { state } = require('../services/state') const { bindConnections } = require('../models') -const { loadLatestAreas } = require('../services/areas') +const { loadCachedAreas, loadLatestAreas } = require('../services/areas') const { loadAuthStrategies } = require('../routes/authRouter') const { deepCompare } = require('./deepCompare') @@ -52,6 +52,10 @@ async function reloadConfig() { const newConfig = require('@rm/config') const { areas, ...oldWithoutAreas } = oldConfig + const primedAreas = areas || loadCachedAreas() + // Prime areas early so config.getSafe('areas') never fails during reload. + newConfig.setAreas(primedAreas) + oldWithoutAreas.areas = primedAreas const { report, areEqual, changed } = deepCompare( oldWithoutAreas, @@ -69,8 +73,6 @@ async function reloadConfig() { !report.manualAreas.areEqual ) { newConfig.setAreas(await loadLatestAreas()) - } else { - newConfig.setAreas(areas) } if (areEqual) { diff --git a/src/assets/css/main.css b/src/assets/css/main.css index f6d4a5c95..9c806d452 100644 --- a/src/assets/css/main.css +++ b/src/assets/css/main.css @@ -249,6 +249,34 @@ body { position: absolute; } +.marker-overlay-icon { + position: absolute; + display: flex; + align-items: flex-end; + justify-content: center; + transform: translateX(-50%); + width: var(--marker-overlay-size, 30px); + height: var(--marker-overlay-size, 30px); +} + +.marker-overlay-icon img, +.leaflet-container + .leaflet-marker-pane + .pokestop-marker__stack-item + img:not(.pokestop-marker__decoration) { + width: var(--marker-overlay-size, var(--marker-size, 30px)) !important; + height: auto !important; + max-width: var(--marker-overlay-size, var(--marker-size, 30px)) !important; + max-height: var(--marker-overlay-size, var(--marker-size, 30px)) !important; + object-fit: contain; +} + +/* Override Leaflet's marker-pane img reset for overlay icons */ +.leaflet-container .leaflet-marker-pane .marker-overlay-icon img { + max-width: var(--marker-overlay-size, var(--marker-size, 30px)) !important; + max-height: var(--marker-overlay-size, var(--marker-size, 30px)) !important; +} + .invasion-reward { display: inline-block; width: 30px; @@ -356,17 +384,6 @@ img { transform: translateX(-50%); } -.leaflet-container - .leaflet-marker-pane - .pokestop-marker__stack-item - img:not(.pokestop-marker__decoration) { - width: var(--marker-size, 30px) !important; - height: auto !important; - max-width: var(--marker-size, 30px) !important; - max-height: var(--marker-size, 30px) !important; - object-fit: contain; -} - .leaflet-container .leaflet-marker-pane .pokestop-marker__stack-item[data-reward-type='12'] diff --git a/src/components/filters/Advanced.jsx b/src/components/filters/Advanced.jsx index 90060974e..381b32d80 100644 --- a/src/components/filters/Advanced.jsx +++ b/src/components/filters/Advanced.jsx @@ -126,7 +126,9 @@ export function AdvancedFilter() { category === 'pokemon' || (!id.startsWith('l') && !id.startsWith('i')) ? t('advanced') : t('set_size') - } - ${tId(id)}`} + } - ${tId(id, { + omitFormSuffix: true, + })}`} action={() => toggleClose(false)} /> diff --git a/src/components/virtual/SelectorItem.jsx b/src/components/virtual/SelectorItem.jsx index 4ae49be73..ae80245f9 100644 --- a/src/components/virtual/SelectorItem.jsx +++ b/src/components/virtual/SelectorItem.jsx @@ -48,6 +48,7 @@ export function SelectorItem({ alt: true, newLine: true, quest: category === 'pokestops', + omitFormSuffix: true, }) const title = t(id) const url = useMemory((s) => s.Icons.getIconById(id)) diff --git a/src/components/virtual/VirtualGrid.jsx b/src/components/virtual/VirtualGrid.jsx index 599dc0bd0..275544909 100644 --- a/src/components/virtual/VirtualGrid.jsx +++ b/src/components/virtual/VirtualGrid.jsx @@ -40,7 +40,8 @@ const List = React.forwardRef(({ context, ...props }, ref) => ( * children: import('react-virtuoso').VirtuosoGridProps['itemContent'], * Header?: React.ComponentType, * Footer?: React.ComponentType, - * useWindowScroll?: boolean + * useWindowScroll?: boolean, + * scrollerRef?: import('react-virtuoso').VirtuosoGridProps['scrollerRef'] * }} props */ export function VirtualGrid({ @@ -55,6 +56,7 @@ export function VirtualGrid({ Header, Footer, useWindowScroll, + scrollerRef, }) { const fullContext = React.useMemo( () => ({ ...context, xs, sm, md, lg, xl }), @@ -74,6 +76,7 @@ export function VirtualGrid({ components={components} itemContent={children} useWindowScroll={useWindowScroll} + scrollerRef={scrollerRef} /> ) } diff --git a/src/features/drawer/Stations.jsx b/src/features/drawer/Stations.jsx index d2c99ddf2..de1c4f5eb 100644 --- a/src/features/drawer/Stations.jsx +++ b/src/features/drawer/Stations.jsx @@ -6,8 +6,8 @@ import ListItemText from '@mui/material/ListItemText' import MenuItem from '@mui/material/MenuItem' import { useTranslation } from 'react-i18next' -import { useMemory } from '@store/useMemory' import { useDeepStore, useStorage } from '@store/useStorage' +import { useGetAvailable } from '@hooks/useGetAvailable' import { FCSelect } from '@components/inputs/FCSelect' import { CollapsibleItem } from './components/CollapsibleItem' @@ -15,7 +15,7 @@ import { SelectorListMemo } from './components/SelectorList' function StationLevels() { const { t } = useTranslation() - const available = useMemory((s) => s.available.stations) + const { available } = useGetAvailable('stations') const enabled = useStorage( (s) => !!s.filters?.stations?.maxBattles && !s.filters?.stations?.allStations, @@ -24,6 +24,20 @@ function StationLevels() { 'filters.stations.battleTier', 'all', ) + + const battleTiers = React.useMemo(() => { + const availableTiers = available + .filter((x) => x.startsWith('j')) + .map((y) => +y.slice(1)) + if (filters === 'all') return availableTiers + + const storedTier = Number(filters) + if (Number.isNaN(storedTier) || availableTiers.includes(storedTier)) { + return availableTiers + } + return [storedTier, ...availableTiers] + }, [available, filters]) + return ( - {[ - 'all', - ...available - .filter((x) => x.startsWith('j')) - .map((y) => +y.slice(1)), - ].map((tier, i) => ( + {['all', ...battleTiers].map((tier, i) => ( {t(i ? `max_battle_${tier}_plural` : 'disabled')} diff --git a/src/features/drawer/components/SelectorList.jsx b/src/features/drawer/components/SelectorList.jsx index 1787ab510..1b799c8f5 100644 --- a/src/features/drawer/components/SelectorList.jsx +++ b/src/features/drawer/components/SelectorList.jsx @@ -45,7 +45,10 @@ function SelectorList({ category, subCategory, label, height = 400 }) { subCategory ? capitalize(subCategory) : '' }QuickSelect` const { available } = useGetAvailable(category) - const { t: tId } = useTranslateById({ quest: subCategory === 'pokemon' }) + const { t: tId } = useTranslateById({ + quest: subCategory === 'pokemon', + omitFormSuffix: true, + }) const { t } = useTranslation() const allFilters = useMemory((s) => s.filters[category]?.filter) diff --git a/src/features/drawer/gyms/Raids.jsx b/src/features/drawer/gyms/Raids.jsx index 2bc7621f7..a5b30f8de 100644 --- a/src/features/drawer/gyms/Raids.jsx +++ b/src/features/drawer/gyms/Raids.jsx @@ -5,8 +5,8 @@ import ListItemText from '@mui/material/ListItemText' import MenuItem from '@mui/material/MenuItem' import { useTranslation } from 'react-i18next' -import { useMemory } from '@store/useMemory' import { useStorage, useDeepStore } from '@store/useStorage' +import { useGetAvailable } from '@hooks/useGetAvailable' import { FCSelect } from '@components/inputs/FCSelect' import { CollapsibleItem } from '../components/CollapsibleItem' @@ -14,9 +14,23 @@ import { MultiSelectorList, SelectorListMemo } from '../components/SelectorList' const RaidOverride = () => { const { t } = useTranslation() - const available = useMemory((s) => s.available.gyms) + const { available } = useGetAvailable('gyms') const enabled = useStorage((s) => !!s.filters?.gyms?.raids) const [filters, setFilters] = useDeepStore('filters.gyms.raidTier', 'all') + + const raidTiers = React.useMemo(() => { + const availableTiers = available + .filter((x) => x.startsWith('r')) + .map((y) => +y.slice(1)) + if (filters === 'all') return availableTiers + + const storedTier = Number(filters) + if (Number.isNaN(storedTier) || availableTiers.includes(storedTier)) { + return availableTiers + } + return [storedTier, ...availableTiers] + }, [available, filters]) + return ( { setFilters(e.target.value === 'all' ? 'all' : e.target.value) } > - {[ - 'all', - ...available - .filter((x) => x.startsWith('r')) - .map((y) => +y.slice(1)), - ].map((tier, i) => ( + {['all', ...raidTiers].map((tier, i) => ( {t(i ? `raid_${tier}_plural` : 'disabled')} diff --git a/src/features/gym/gymMarker.js b/src/features/gym/gymMarker.js index afa9f6ed6..3f5f7b6f4 100644 --- a/src/features/gym/gymMarker.js +++ b/src/features/gym/gymMarker.js @@ -1,6 +1,7 @@ // @ts-check import { divIcon } from 'leaflet' import { useMemory } from '@store/useMemory' +import { renderOverlayIcon } from '@utils/renderOverlayIcon' /** @param {number} raidLevel */ const getBadgeColor = (raidLevel) => { @@ -165,21 +166,13 @@ export function gymMarker({ } ${ raidIconUrl - ? /* html */ - `${raidIconUrl}` + ? /* html */ renderOverlayIcon({ + url: raidIconUrl, + size: raidIconSize, + opacity, + bottom: gymIconSize * 0.4 + slotModifier * raidMod.offsetY, + left: raidMod.offsetX * 50, + }) : '' } ${ diff --git a/src/features/pokemon/PokemonPopup.jsx b/src/features/pokemon/PokemonPopup.jsx index da91124f6..f88937713 100644 --- a/src/features/pokemon/PokemonPopup.jsx +++ b/src/features/pokemon/PokemonPopup.jsx @@ -286,6 +286,7 @@ export function PokemonPopup({ pokemon, iconUrl, isTutorial = false }) { metaData={metaData} perms={pokePerms} timeOfDay={timeOfDay} + backgroundVisuals={backgroundVisuals} />