Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/locales/lib/human/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@
"exclude_lure": "Exclude Lure",
"timer": "Timer",
"hide": "Hide",
"hidden_for_hour": "Hidden for an hour",
"clean_hidden": "Clean Hidden",
"tier": "Tier",
"slots": "Slots",
"mega": "Mega",
Expand Down
2 changes: 2 additions & 0 deletions packages/locales/lib/human/pl.json
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,8 @@
"has_quest_indicator": "Alternatywny kolor dla Pokéstopów z zadaniami",
"help": "Pomoc",
"hide": "Ukryj",
"hidden_for_hour": "Schowano na godzinę",
"clean_hidden": "Wyczyść schowane",
"hide_editor": "Ukryj edytor",
"historic_rarity": "Rzadkość historyczna",
"hisuian": "Hisuian",
Expand Down
11 changes: 11 additions & 0 deletions src/features/drawer/settings/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import InsightsIcon from '@mui/icons-material/Insights'
import NotificationsActiveIcon from '@mui/icons-material/NotificationsActive'
import NotificationsOffIcon from '@mui/icons-material/NotificationsOff'
import LogoDevIcon from '@mui/icons-material/LogoDev'
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff'
import { useTranslation } from 'react-i18next'

import { useMemory } from '@store/useMemory'
Expand All @@ -22,6 +23,7 @@ import { LocaleSelection } from '@components/inputs/LocaleSelection'
import { DividerWithMargin } from '@components/StyledDivider'
import { BoolToggle } from '@components/inputs/BoolToggle'
import { BasicListButton } from '@components/inputs/BasicListButton'
import { clearHiddenEntities } from '@utils/hiddenEntities'

import { DrawerActions } from '../components/Actions'
import { GeneralSetting } from './General'
Expand Down Expand Up @@ -70,6 +72,15 @@ export function Settings() {
</BasicListButton>
)}
<HolidaySetting />
<BasicListButton
onClick={() => {
clearHiddenEntities()
useMemory.setState({ hideList: new Set() })
}}
label="clean_hidden"
>
<VisibilityOffIcon />
</BasicListButton>
<DividerWithMargin />
<UAssetSetting asset="icons" />
<UAssetSetting asset="audio" />
Expand Down
4 changes: 3 additions & 1 deletion src/features/gym/GymPopup.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { getTimeUntil } from '@utils/getTimeUntil'
import { formatInterval } from '@utils/formatInterval'
import { usePokemonBackgroundVisuals } from '@hooks/usePokemonBackgroundVisuals'
import { getFormDisplay } from '@utils/getFormDisplay'
import { addHiddenEntity, showHideSnackbar } from '@utils/hiddenEntities'

import { useWebhook } from './useWebhook'

Expand Down Expand Up @@ -747,7 +748,8 @@ const DropdownOptions = ({

const handleHide = () => {
handleClose()
useMemory.setState((prev) => ({ hideList: new Set(prev.hideList).add(id) }))
useMemory.setState({ hideList: addHiddenEntity(id) })
showHideSnackbar(t('hidden_for_hour'))
}

const handleExclude = (key) => {
Expand Down
4 changes: 3 additions & 1 deletion src/features/nest/NestPopup.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { setDeepStore } from '@store/useStorage'
import { getTimeUntil } from '@utils/getTimeUntil'
import { useAnalytics } from '@hooks/useAnalytics'
import { Navigation } from '@components/popups/Navigation'
import { addHiddenEntity, showHideSnackbar } from '@utils/hiddenEntities'

/** @param {number} timeSince */
const getColor = (timeSince) => {
Expand Down Expand Up @@ -62,7 +63,8 @@ export function NestPopup({
const handleClose = () => setAnchorEl(null)
const handleHide = () => {
setAnchorEl(null)
useMemory.setState((prev) => ({ hideList: new Set(prev.hideList).add(id) }))
useMemory.setState({ hideList: addHiddenEntity(id) })
showHideSnackbar(t('hidden_for_hour'))
}

const handleExclude = () => {
Expand Down
3 changes: 2 additions & 1 deletion src/features/pokemon/PokemonPopup.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { GET_TAPPABLE_BY_ID } from '@services/queries/tappable'
import { usePokemonBackgroundVisual } from '@hooks/usePokemonBackgroundVisuals'
import { BackgroundCard } from '@components/popups/BackgroundCard'
import { getFormDisplay } from '@utils/getFormDisplay'
import { addHiddenEntity } from '@utils/hiddenEntities'

const rowClass = { width: 30, fontWeight: 'bold' }

Expand Down Expand Up @@ -357,7 +358,7 @@ const Header = ({ pokemon, metaData, iconUrl, userSettings, isTutorial }) => {

const handleHide = () => {
setAnchorEl(null)
useMemory.setState((prev) => ({ hideList: new Set(prev.hideList).add(id) }))
useMemory.setState({ hideList: addHiddenEntity(id) })
}

const handleExclude = () => {
Expand Down
4 changes: 3 additions & 1 deletion src/features/pokestop/PokestopPopup.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { useGetAvailable } from '@hooks/useGetAvailable'
import { parseQuestConditions } from '@utils/parseConditions'
import { Img } from '@components/Img'
import { readableProbability } from '@utils/readableProbability'
import { addHiddenEntity, showHideSnackbar } from '@utils/hiddenEntities'
import {
usePokemonBackgroundVisuals,
usePokemonBackgroundVisual,
Expand Down Expand Up @@ -341,7 +342,8 @@ const MenuActions = ({

const handleHide = () => {
setAnchorEl(null)
useMemory.setState((prev) => ({ hideList: new Set(prev.hideList).add(id) }))
useMemory.setState({ hideList: addHiddenEntity(id) })
showHideSnackbar(t('hidden_for_hour'))
}

/** @param {string} key */
Expand Down
9 changes: 5 additions & 4 deletions src/features/station/StationPopup.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { Img, PokemonImg } from '@components/Img'
import { useFormatStore } from '@store/useFormatStore'
import { useRelativeTimer } from '@hooks/useRelativeTime'
import { useAnalytics } from '@hooks/useAnalytics'
import { addHiddenEntity, showHideSnackbar } from '@utils/hiddenEntities'
import { BackgroundCard } from '@components/popups/BackgroundCard'
import { Title } from '@components/popups/Title'
import {
Expand Down Expand Up @@ -169,10 +170,10 @@ function StationMenu({
() => [
{
name: 'hide',
action: () =>
useMemory.setState((prev) => ({
hideList: new Set(prev.hideList).add(id),
})),
action: () => {
useMemory.setState({ hideList: addHiddenEntity(id) })
showHideSnackbar(t('hidden_for_hour'))
},
},
{
name: 'exclude_battle',
Expand Down
9 changes: 5 additions & 4 deletions src/features/tappable/TappablePopup.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import { StatusIcon } from '@components/StatusIcon'
import { Title } from '@components/popups/Title'

import { getTimeUntil } from '@utils/getTimeUntil'
import { addHiddenEntity, showHideSnackbar } from '@utils/hiddenEntities'

import { getTappableDisplaySettings } from './displayRules'

/**
Expand Down Expand Up @@ -107,10 +109,9 @@ export function TappablePopup({ tappable, rewardIcon }) {
const handleHide = React.useCallback(() => {
setMenuAnchorEl(null)
if (tappable.id === undefined || tappable.id === null) return
useMemory.setState((prev) => ({
hideList: new Set(prev.hideList).add(tappable.id),
}))
}, [tappable.id])
useMemory.setState({ hideList: addHiddenEntity(tappable.id) })
showHideSnackbar(t('hidden_for_hour'))
}, [tappable.id, t])

const handleExclude = React.useCallback(() => {
setMenuAnchorEl(null)
Expand Down
10 changes: 10 additions & 0 deletions src/pages/map/components/Container.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ScanOnDemand } from '@features/scanner'
import { WebhookMarker, WebhookAreaSelection } from '@features/webhooks'
import { ActiveWeather } from '@features/weather'
import { timeCheck } from '@utils/timeCheck'
import { cleanupHiddenEntities } from '@utils/hiddenEntities'

import { Effects } from './Effects'
import { DataView } from './Data'
Expand Down Expand Up @@ -38,6 +39,15 @@ const MAX_BOUNDS = /** @type {[[number, number], [number, number]]} */ ([
export function Container() {
const { location, zoom } = useStorage.getState()

// Cleanup hidden entities every 15 seconds
React.useEffect(() => {
const interval = setInterval(
() => cleanupHiddenEntities(useMemory.setState),
15000,
)
return () => clearInterval(interval)
}, [])

return (
<MapContainer
tap={false}
Expand Down
4 changes: 3 additions & 1 deletion src/store/useMemory.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import { create } from 'zustand'

import { getHiddenEntitySet } from '@utils/hiddenEntities'

/**
* TODO: Finish this
* @typedef {{
Expand Down Expand Up @@ -151,7 +153,7 @@ export const useMemory = create(() => ({
locationCards: {},
routeTypes: {},
},
hideList: new Set(),
hideList: getHiddenEntitySet(),
timerList: [],
timeOfDay: 'day',
extraUserFields: [],
Expand Down
168 changes: 168 additions & 0 deletions src/utils/hiddenEntities.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// @ts-check

const STORAGE_KEY = 'hidden-entities'
const SNACKBAR_COUNT_KEY = 'hidden-entities-snackbar-count'
const MAX_AGE_MS = 60 * 60 * 1000 // 1 hour
const MAX_SNACKBAR_SHOWS = 3

/**
* @typedef {{ id: string | number, ts: number }} HiddenEntry
*/

/**
* Load hidden entries from localStorage
* @returns {HiddenEntry[]}
*/
function loadEntries() {
try {
const raw = localStorage.getItem(STORAGE_KEY)
if (!raw) return []
const parsed = JSON.parse(raw)
return Array.isArray(parsed) ? parsed : []
} catch {
return []
}
}

/**
* Save hidden entries to localStorage
* @param {HiddenEntry[]} entries
*/
function saveEntries(entries) {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(entries))
} catch {
// localStorage may be full or disabled
}
}

/**
* Clean entries older than 1 hour
* @param {HiddenEntry[]} entries
* @returns {HiddenEntry[]}
*/
function cleanOldEntries(entries) {
const now = Date.now()
return entries.filter((e) => now - e.ts < MAX_AGE_MS)
}

/**
* Add an entity ID to the hidden list with timestamp
* @param {string | number} id
* @returns {Set<string | number>} Updated hideList Set
*/
export function addHiddenEntity(id) {
const entries = loadEntries()
if (!entries.some((e) => e.id === id)) {
entries.push({ id, ts: Date.now() })
}
saveEntries(entries)
return new Set(entries.map((e) => e.id))
}

/**
* Get the current hidden entity Set from localStorage
* @returns {Set<string | number>}
*/
export function getHiddenEntitySet() {
const entries = loadEntries()
return new Set(entries.map((e) => e.id))
}

/**
* Clean outdated hidden entries (older than 1 hour) from localStorage
* Updates both localStorage and the in-memory hideList
* @param {(state: { hideList: Set<string | number> }) => void} setState
*/
export function cleanupHiddenEntities(setState) {
const entries = loadEntries()
const cleaned = cleanOldEntries(entries)
if (cleaned.length !== entries.length) {
saveEntries(cleaned)
setState({ hideList: new Set(cleaned.map((e) => e.id)) })
}
}

/** @type {{ current: number | null }} */
const snackbarTimer = { current: null }

/** @type {{ current: HTMLDivElement | null }} */
const snackbarRef = { current: null }

/**
* Get snackbar show count from localStorage
* @returns {number}
*/
function getSnackbarCount() {
try {
return parseInt(localStorage.getItem(SNACKBAR_COUNT_KEY) || '0', 10)
} catch {
return 0
}
}

/**
* Increment snackbar show count in localStorage
*/
function incrementSnackbarCount() {
try {
const count = getSnackbarCount() + 1
localStorage.setItem(SNACKBAR_COUNT_KEY, String(count))
} catch {
// localStorage may be full or disabled
}
}

/**
* Show a temporary snackbar message for 2 seconds (max 3 times total)
* @param {string} message
*/
export function showHideSnackbar(message) {
if (getSnackbarCount() >= MAX_SNACKBAR_SHOWS) {
return
}

if (snackbarTimer.current) {
clearTimeout(snackbarTimer.current)
}
if (snackbarRef.current) {
snackbarRef.current.remove()
}

incrementSnackbarCount()

const snackbar = document.createElement('div')
snackbar.textContent = message
snackbar.style.cssText = `
position: fixed;
bottom: 24px;
left: 50%;
transform: translateX(-50%);
background: rgba(50, 50, 50, 0.95);
color: white;
padding: 8px 16px;
border-radius: 4px;
font-size: 14px;
z-index: 10000;
pointer-events: none;
`
document.body.appendChild(snackbar)
snackbarRef.current = snackbar

snackbarTimer.current = window.setTimeout(() => {
snackbar.remove()
snackbarRef.current = null
snackbarTimer.current = null
}, 2000)
}

/**
* Clear all hidden entities from localStorage
*/
export function clearHiddenEntities() {
try {
localStorage.removeItem(STORAGE_KEY)
} catch {
// localStorage may be disabled
}
}
Loading