Skip to content

Commit

Permalink
Merge pull request #210 from TurtIeSocks/load-stats-on-demand
Browse files Browse the repository at this point in the history
refactor: load stats on demand
  • Loading branch information
TurtIeSocks authored Dec 3, 2023
2 parents 4c9c85c + 4a2df22 commit 44b15dd
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 75 deletions.
2 changes: 1 addition & 1 deletion client/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"private": true,
"name": "koji",
"version": "1.3.11",
"version": "1.3.12",
"description": "Tool to make RDM routes",
"main": "server/dist/index.js",
"author": "TurtIeSocks <[email protected]>",
Expand Down
6 changes: 6 additions & 0 deletions client/src/components/drawer/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export default function Settings() {
<ListItem>
<Select
value={tileServer}
size="small"
fullWidth
onChange={({ target }) => {
usePersist.setState({ tileServer: target.value })
Expand All @@ -81,6 +82,11 @@ export default function Settings() {
sx={{ px: 2 }}
/>
)}
<Divider sx={{ my: 2 }} />
<ListSubheader disableGutters>Max Area to Auto Calc (km²)</ListSubheader>
<NumInput field="pokestopMaxAreaAutoCalc" label="Pokestops" />
<NumInput field="gymMaxAreaAutoCalc" label="Gyms" />
<NumInput field="spawnpointMaxAreaAutoCalc" label="Spawnpoints" />
{process.env.NODE_ENV === 'development' && (
<>
<Divider sx={{ my: 2 }} />
Expand Down
6 changes: 4 additions & 2 deletions client/src/components/drawer/inputs/NumInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,26 @@ export default function NumInput<
>,
>({
field,
label,
endAdornment,
disabled = false,
min = 0,
max = 9999,
}: {
field: T
label?: string
disabled?: boolean
endAdornment?: string
min?: number
max?: number
}) {
const value = usePersist((s) => s[field])

return (
<ListItem disabled={disabled}>
<ListItemText
primary={
field.includes('_') ? fromSnakeCase(field) : fromCamelCase(field)
label ??
(field.includes('_') ? fromSnakeCase(field) : fromCamelCase(field))
}
/>
<TextField
Expand Down
7 changes: 7 additions & 0 deletions client/src/hooks/usePersist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ export interface UsePersist {
simplifyPolygons: boolean
scaleMarkers: boolean

// Settings
pokestopMaxAreaAutoCalc: number
gymMaxAreaAutoCalc: number
spawnpointMaxAreaAutoCalc: number
// Layers
spawnpoint: boolean
gym: boolean
Expand Down Expand Up @@ -146,6 +150,9 @@ export const usePersist = create(
setActiveMode: 'hover',
colorByGeohash: false,
geohashPrecision: 6,
pokestopMaxAreaAutoCalc: 100,
gymMaxAreaAutoCalc: 100,
spawnpointMaxAreaAutoCalc: 100,
setStore: (key, value) => set({ [key]: value }),
}),
{
Expand Down
5 changes: 1 addition & 4 deletions client/src/pages/map/markers/Polygon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ export function KojiPolygon({
}) {
const { setStatic } = useStatic.getState()

const [loadData, setLoadData] = React.useState(false)

const color = getPolygonColor(`${feature.id}`)

return (
Expand All @@ -32,7 +30,6 @@ export function KojiPolygon({
color={color}
eventHandlers={{
click({ latlng }) {
if (!loadData) setLoadData(true)
const { lat, lng } = latlng
setStatic('clickedLocation', [lng, lat])
},
Expand Down Expand Up @@ -145,7 +142,7 @@ export function KojiPolygon({
pane="polygons"
>
<Popup>
<MemoPolyPopup feature={feature} loadData={loadData} dbRef={dbRef} />
<MemoPolyPopup feature={feature} dbRef={dbRef} />
</Popup>
</Polygon>
)
Expand Down
165 changes: 97 additions & 68 deletions client/src/pages/map/popups/Polygon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ import {
Select,
TextField,
Typography,
capitalize,
styled,
} from '@mui/material'
import RefreshIcon from '@mui/icons-material/Refresh'
import useDeepCompareEffect from 'use-deep-compare-effect'
import type { MultiPolygon, Polygon } from 'geojson'
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'
Expand All @@ -22,6 +25,7 @@ import type {
Feature,
DbOption,
KojiModes,
Category,
} from '@assets/types'
import { useShapes } from '@hooks/useShapes'
import { useStatic } from '@hooks/useStatic'
Expand All @@ -36,41 +40,107 @@ import {
removeThisPolygon,
splitMultiPolygons,
} from '@services/utils'
import { shallow } from 'zustand/shallow'
import { useImportExport } from '@hooks/useImportExport'
import { filterPoints, filterPolys } from '@services/geoUtils'
import { usePersist } from '@hooks/usePersist'

const { add, remove, updateProperty } = useShapes.getState().setters
const { setRecord } = useDbCache.getState()

export const LoadingButton = styled(Button, {
shouldForwardProp: (prop) => prop !== 'fetched' && prop !== 'loading',
})<{ fetched?: boolean; loading?: boolean }>(({ fetched, loading }) => ({
minWidth: 0,
'.MuiButton-endIcon': {
display: fetched ? 'inherit' : 'none',
marginRight: '-2px',
marginLeft: '8px',
},
'.MuiButton-startIcon': {
display: loading ? 'inherit' : 'none',
marginRight: '8px',
marginLeft: '-2px',
},
}))
LoadingButton.defaultProps = {
fetched: false,
loading: false,
size: 'small',
disableRipple: true,
endIcon: <RefreshIcon fontSize="small" />,
startIcon: <CircularProgress size={20} />,
}

const MemoStat = React.memo(
({
category,
id,
area,
}: {
category: Category
id: Feature['id']
area: number
}) => {
const [stats, setStats] = React.useState<number | null>(null)
const [loading, setLoading] = React.useState(false)
const tth = usePersist((s) => s.tth)
const raw = usePersist((s) => s.last_seen)
const autoLoad = usePersist((s) => s[`${category}MaxAreaAutoCalc`])
const feature = useShapes((s) => ({ ...s.Polygon, ...s.MultiPolygon }[id]))

const getStats = React.useCallback(() => {
setLoading(true)
setStats((prev) => (prev === null ? 0 : null))
const last_seen = typeof raw === 'string' ? new Date(raw) : raw
fetchWrapper<{ total: number }>(`/internal/data/area_stats/${category}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
area: feature,
last_seen: Math.floor((last_seen?.getTime?.() || 0) / 1000),
tth,
}),
})
.then((data) => setStats(data?.total ?? 0))
.finally(() => setLoading(false))
}, [tth, raw, feature])

React.useEffect(() => {
if ((stats !== null || (area > 0 && area < autoLoad)) && !loading) {
getStats()
}
}, [getStats, area, autoLoad])

return (
<Typography variant="subtitle2">
{capitalize(category)}s: {loading ? '' : stats?.toLocaleString() || ''}
<LoadingButton
size="small"
onClick={getStats}
fetched={stats !== null}
loading={loading}
>
{typeof stats === 'number' || loading ? '' : 'Get'}
</LoadingButton>
</Typography>
)
},
)

export function PolygonPopup({
feature: refFeature,
loadData,
dbRef,
}: {
feature: Feature<Polygon | MultiPolygon>
loadData: boolean
dbRef: DbOption | null
}) {
const feature =
useShapes(
(s) => ({ ...s.Polygon, ...s.MultiPolygon }[refFeature.id]),
shallow,
) || refFeature
const raw = usePersist((s) => s.last_seen)
const tth = usePersist((s) => s.tth)
useShapes((s) => ({ ...s.Polygon, ...s.MultiPolygon }[refFeature.id])) ||
refFeature
const geofence = useDbCache((s) => s.geofence)

const [active, setActive] = React.useState<{
spawnpoint: number | null | string
gym: number | null | string
pokestop: number | null | string
}>({
spawnpoint: null,
gym: null,
pokestop: null,
})
const [name, setName] = React.useState(
dbRef?.name ||
feature.properties?.__name ||
Expand Down Expand Up @@ -103,55 +173,17 @@ export function PolygonPopup({
setDbAnchorEl(null)
}

const getState = (category: keyof typeof active) => {
switch (typeof active[category]) {
case 'number':
return active[category]?.toLocaleString()
case 'string':
return active[category]
case 'object':
return <CircularProgress size={10} />
default:
return 'Loading'
}
}

useDeepCompareEffect(() => {
if (feature.geometry.coordinates.length && loadData) {
if (feature.geometry.coordinates.length) {
fetchWrapper<KojiResponse<{ area: number }>>('/api/v1/calc/area', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ area: feature }),
}).then((res) => res && setArea(res.data.area))
const last_seen = typeof raw === 'string' ? new Date(raw) : raw

Promise.allSettled(
['pokestop', 'gym', 'spawnpoint'].map((category) =>
fetchWrapper<{ total: number }>(
`/internal/data/area_stats/${category}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
area: feature,
last_seen: Math.floor((last_seen?.getTime?.() || 0) / 1000),
tth,
}),
},
).then((data) =>
setActive((prev) => ({
...prev,
[category]: data?.total ?? (data || 0),
})),
),
),
)
}).then((res) => res && setArea(res.data.area / 1000000))
}
}, [feature, loadData, raw, tth])
}, [feature])

const isKoji = feature.id.toString().endsWith('KOJI')
// const isScanner = feature.id.endsWith('SCANNER')
Expand Down Expand Up @@ -224,17 +256,15 @@ export function PolygonPopup({
? `(${feature.geometry.coordinates.length})`
: ''}
</Typography>
<Typography variant="caption">{area.toLocaleString()}</Typography>
<Typography variant="caption">
{+area.toFixed(2).toLocaleString()} km²
</Typography>
</Grid2>
<Divider flexItem sx={{ my: 1, color: 'black', width: '90%' }} />
<Grid2 xs={12}>
<Typography variant="subtitle2">
Pokestops: {getState('pokestop')}
</Typography>
<Typography variant="subtitle2">Gyms: {getState('gym')}</Typography>
<Typography variant="subtitle2">
Spawnpoints: {getState('spawnpoint')}
</Typography>
<MemoStat category="pokestop" id={refFeature.id} area={area} />
<MemoStat category="gym" id={refFeature.id} area={area} />
<MemoStat category="spawnpoint" id={refFeature.id} area={area} />
</Grid2>
<Divider flexItem sx={{ my: 1, color: 'black', width: '90%' }} />
<Grid2 xs={12}>
Expand Down Expand Up @@ -470,7 +500,6 @@ export function PolygonPopup({
export const MemoPolyPopup = React.memo(
PolygonPopup,
(prev, next) =>
prev.loadData === next.loadData &&
prev.feature.geometry.type === next.feature.geometry.type &&
prev.feature.geometry.coordinates.length ===
next.feature.geometry.coordinates.length,
Expand Down

1 comment on commit 44b15dd

@vercel
Copy link

@vercel vercel bot commented on 44b15dd Dec 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

koji – ./

koji.vercel.app
koji-git-main-turtiesocks.vercel.app
koji-turtiesocks.vercel.app

Please sign in to comment.