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
10 changes: 10 additions & 0 deletions config/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"polling": {
"devices": 10,
"gyms": 10,
"hyperlocal": 20,
"nests": 300,
"pokemon": 20,
"pokestops": 300,
Expand Down Expand Up @@ -159,6 +160,7 @@
"pokestops",
"stations",
"pokemon",
"hyperlocal",
"routes",
"wayfarer",
"s2cells",
Expand Down Expand Up @@ -489,6 +491,9 @@
"gymBadges": false,
"baseGymSlotAmounts": [1, 2, 3, 4, 5, 6]
},
"hyperlocal": {
"enabled": true
},
"nests": {
"enabled": false,
"polygons": false,
Expand Down Expand Up @@ -764,6 +769,11 @@
"trialPeriodEligible": false,
"roles": []
},
"hyperlocal": {
"enabled": true,
"trialPeriodEligible": false,
"roles": []
},
"raids": {
"enabled": true,
"trialPeriodEligible": false,
Expand Down
4 changes: 3 additions & 1 deletion packages/locales/lib/human/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -820,5 +820,7 @@
"filters": "Filters",
"active": "Active",
"inactive": "Inactive",
"bread_time_window": "You can take on Max Battles between 6AM and 9PM."
"bread_time_window": "You can take on Max Battles between 6AM and 9PM.",
"hyperlocal": "Bonus Regions",
"radius": "Radius"
}
10 changes: 10 additions & 0 deletions server/src/filters/builder/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,16 @@ function buildDefaultFilters(perms) {
},
}
: undefined,
hyperlocal:
perms.hyperlocal && state.db.models.Hyperlocal
? {
enabled: defaultFilters.hyperlocal.enabled,
standard: new BaseFilter(),
filter: {
global: new BaseFilter(),
},
}
: undefined,
portals:
perms.portals && state.db.models.Portal
? {
Expand Down
6 changes: 6 additions & 0 deletions server/src/graphql/resolvers.js
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,12 @@ const resolvers = {
}
return []
},
hyperlocal: (_, args, { perms, Db }) => {
if (perms?.hyperlocal) {
return Db.query('Hyperlocal', 'getAll', perms, args)
}
return []
},
s2cells: (_, args, { perms }) => {
if (perms?.s2cells) {
const { onlyCells } = args.filters
Expand Down
7 changes: 7 additions & 0 deletions server/src/graphql/typeDefs/index.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,13 @@ type Query {
maxLon: Float
filters: JSON
): [Route]
hyperlocal(
minLat: Float
maxLat: Float
minLon: Float
maxLon: Float
filters: JSON
): [Hyperlocal]
webhook(category: String, status: String): Poracle
webhookAreas: [WebhookAreaGroups]
webhookGeojson: JSON
Expand Down
12 changes: 12 additions & 0 deletions server/src/graphql/typeDefs/scanner.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,18 @@ type Route {
waypoints: [Waypoint]
}

type Hyperlocal {
id: String
experiment_id: Int
start_ms: Float
end_ms: Float
lat: Float
lon: Float
radius_m: Float
challenge_bonus_key: String
updated_ms: Float
}

type Station {
id: ID
name: String
Expand Down
53 changes: 53 additions & 0 deletions server/src/models/Hyperlocal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// @ts-check
const { Model } = require('objection')

class Hyperlocal extends Model {
static get tableName() {
return 'hyperlocal'
}

static get idColumn() {
return ['experiment_id', 'lat', 'lon']
}

/**
* Returns all hyperlocal records within bounds
* @param {import('@rm/types').Permissions} perms
* @param {object} args
* @returns {Promise<import('@rm/types').Hyperlocal[]>}
*/
static async getAll(perms, { minLat, maxLat, minLon, maxLon }) {
const query = this.query()
.whereBetween('lat', [minLat, maxLat])
.whereBetween('lon', [minLon, maxLon])

// Only show active bonus regions (not expired)
const now = Date.now()
query.where('end_ms', '>', now)

const results = await query

// Add unique id for React keys by combining the composite primary key
return results.map((hyperlocal) => ({
...hyperlocal,
id: `${hyperlocal.experiment_id}_${hyperlocal.lat}_${hyperlocal.lon}`,
}))
}

/**
* Returns a single hyperlocal record
* @param {number} experimentId
* @param {number} lat
* @param {number} lon
* @returns {Promise<import('@rm/types').Hyperlocal>}
*/
static async getOne(experimentId, lat, lon) {
return this.query()
.where('experiment_id', experimentId)
.where('lat', lat)
.where('lon', lon)
.first()
}
}

module.exports = { Hyperlocal }
2 changes: 2 additions & 0 deletions server/src/models/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const { Backup } = require('./Backup')
const { Badge } = require('./Badge')
const { Device } = require('./Device')
const { Gym } = require('./Gym')
const { Hyperlocal } = require('./Hyperlocal')
const { Nest } = require('./Nest')
const { NestSubmission } = require('./NestSubmission')
const { Pokestop } = require('./Pokestop')
Expand All @@ -28,6 +29,7 @@ const rmModels = {
const scannerModels = {
Device,
Gym,
Hyperlocal,
Nest,
Pokestop,
Pokemon,
Expand Down
1 change: 1 addition & 0 deletions server/src/services/DbManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class DbManager extends Logger {
static validModels = /** @type {const} */ ([
'Device',
'Gym',
'Hyperlocal',
'Nest',
'Pokestop',
'Pokemon',
Expand Down
4 changes: 4 additions & 0 deletions server/src/ui/drawer.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,10 @@ function drawer(req, perms) {
},
}
: BLOCKED,
hyperlocal:
perms.hyperlocal && state.db.models.Hyperlocal
? { enabled: true }
: BLOCKED,
routes: perms.routes && state.db.models.Route ? { enabled: true } : BLOCKED,
wayfarer:
perms.portals || perms.submissionCells
Expand Down
3 changes: 3 additions & 0 deletions src/features/drawer/Extras.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { PokestopDrawer } from './pokestops'
import { GymDrawer } from './gyms'
import { NestsDrawer } from './nests'
import { RoutesDrawer } from './Routes'
import { HyperlocalDrawer } from './Hyperlocal'
import { WayfarerDrawer } from './Wayfarer'
import { S2CellsDrawer } from './S2Cells'
import { AdminDrawer } from './Admin'
Expand All @@ -24,6 +25,8 @@ function ExtrasComponent({ category, subItem }) {
return <WayfarerDrawer subItem={subItem} />
case 'routes':
return <RoutesDrawer subItem={subItem} />
case 'hyperlocal':
return <HyperlocalDrawer subItem={subItem} />
case 'admin':
return <AdminDrawer subItem={subItem} />
case 'stations':
Expand Down
13 changes: 13 additions & 0 deletions src/features/drawer/Hyperlocal.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// @ts-check
import * as React from 'react'

import { CollapsibleItem } from './components/CollapsibleItem'

function BaseHyperlocalDrawer({ subItem }) {
return subItem === 'enabled' ? <CollapsibleItem /> : null
}

export const HyperlocalDrawer = React.memo(
BaseHyperlocalDrawer,
(prev, next) => prev.subItem === next.subItem,
)
79 changes: 79 additions & 0 deletions src/features/hyperlocal/HyperlocalPopup.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// @ts-check
import * as React from 'react'
import Typography from '@mui/material/Typography'
import { useTranslation } from 'react-i18next'

import { Timer } from '@components/popups/Timer'
import { formatInterval } from '@utils/formatInterval'

/**
* @param {{ hyperlocal: import('@rm/types').Hyperlocal, ts?: number }} props
*/
export function HyperlocalPopup({
hyperlocal,
ts = Math.floor(Date.now() / 1000),
}) {
const { t, i18n } = useTranslation()

// Format times in h:m:s AM/PM format
const formatTime = React.useCallback(
(timestamp) => {
if (!timestamp) return null
const formatter = new Intl.DateTimeFormat(i18n.language, {
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
})
return formatter.format(timestamp * 1000)
},
[i18n.language],
)

// Calculate time ago for last seen
const timeAgo = React.useMemo(() => {
if (!hyperlocal.updated_ms) return null
const updatedSeconds = Math.floor(hyperlocal.updated_ms / 1000)
const diff = ts - updatedSeconds
const { str } = formatInterval(diff * 1000)
return str
}, [hyperlocal.updated_ms, ts])

const startTime = hyperlocal.start_ms
? Math.floor(hyperlocal.start_ms / 1000)
: null
const endTime = hyperlocal.end_ms
? Math.floor(hyperlocal.end_ms / 1000)
: null
const lastSeenTime = hyperlocal.updated_ms
? Math.floor(hyperlocal.updated_ms / 1000)
: null

return (
<div style={{ textAlign: 'center', minWidth: 200 }}>
<Typography variant="h6" gutterBottom>
{`${t(hyperlocal.challenge_bonus_key)} (${hyperlocal.experiment_id})`}
</Typography>

<Typography variant="subtitle1" gutterBottom>
{t('starts')}: {formatTime(startTime)}
</Typography>

<Typography variant="subtitle1" gutterBottom>
{t('ends')}: {formatTime(endTime)}
</Typography>

<Typography variant="h6" gutterBottom>
<Timer expireTime={endTime} />
</Typography>

<Typography variant="subtitle1" gutterBottom>
Radius: {hyperlocal.radius_m}m
</Typography>

<Typography variant="body2" gutterBottom>
{t('last_seen')}: {formatTime(lastSeenTime)}{' '}
{timeAgo && `(${timeAgo} ago)`}
</Typography>
</div>
)
}
31 changes: 31 additions & 0 deletions src/features/hyperlocal/HyperlocalTile.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// @ts-check
import * as React from 'react'
import { Circle, Popup } from 'react-leaflet'

import { HyperlocalPopup } from './HyperlocalPopup'
import { hyperlocalMarker } from './hyperlocalMarker'

/**
* @param {import('@rm/types').Hyperlocal & { lat: number, lon: number }} props
*/
const BaseHyperlocalTile = (props) => {
const markerProps = hyperlocalMarker(props)

return (
<Circle
center={markerProps.center}
radius={markerProps.radius}
pathOptions={markerProps.pathOptions}
className={markerProps.className}
>
<Popup>
<HyperlocalPopup hyperlocal={props} />
</Popup>
</Circle>
)
}

export const HyperlocalTile = React.memo(
BaseHyperlocalTile,
(prev, next) => prev.updated_ms === next.updated_ms,
)
23 changes: 23 additions & 0 deletions src/features/hyperlocal/hyperlocalMarker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// @ts-check

/**
* Creates a circle marker for hyperlocal bonus regions
* @param {object} hyperlocal - The hyperlocal data object
* @returns {object} Circle marker configuration
*/
export function hyperlocalMarker(hyperlocal) {
const { lat, lon, radius_m } = hyperlocal

return {
center: [lat, lon],
radius: radius_m,
pathOptions: {
color: '#FFD700', // Gold color for bonus regions
fillColor: '#FFD700',
fillOpacity: 0.3,
weight: 2,
opacity: 0.7,
},
className: 'hyperlocal-circle',
}
}
5 changes: 5 additions & 0 deletions src/features/hyperlocal/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// @ts-check

export * from './hyperlocalMarker'
export * from './HyperlocalPopup'
export * from './HyperlocalTile'
Loading
Loading