Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: load stats on demand #210

Merged
merged 2 commits into from
Dec 3, 2023
Merged
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: 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()} m²</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