From 90277f20d3b3267e68c8a7eccb51376820d18554 Mon Sep 17 00:00:00 2001 From: soolar Date: Tue, 5 Dec 2023 19:16:10 -0800 Subject: [PATCH] Add support for viewing snapshot history --- .../greenbox-web/src/components/Header.tsx | 24 ++++++++++++--- .../greenbox-web/src/components/MainPage.tsx | 11 +++++-- .../greenbox-web/src/components/MetaInfo.tsx | 10 ++++++- packages/greenbox-web/src/store/index.ts | 29 +++++++++++++++---- 4 files changed, 62 insertions(+), 12 deletions(-) diff --git a/packages/greenbox-web/src/components/Header.tsx b/packages/greenbox-web/src/components/Header.tsx index f2f8b31..f727c4a 100644 --- a/packages/greenbox-web/src/components/Header.tsx +++ b/packages/greenbox-web/src/components/Header.tsx @@ -10,6 +10,7 @@ import { Stack, Text, Tooltip, + Link, } from "@chakra-ui/react"; import { RawSnapshotData } from "greenbox-data"; import { useCallback } from "react"; @@ -27,6 +28,10 @@ type Props = { loading?: boolean; error?: boolean; errorMessage?: string; + snapshots: { + current: number | null; + total: number | null; + }; }; const forceRefreshInfo = ` @@ -41,6 +46,7 @@ export default function Header({ loading, error, errorMessage, + snapshots, }: Props) { const dispatch = useAppDispatch(); @@ -48,6 +54,9 @@ export default function Header({ dispatch(fetchAll(true)); }, [dispatch]); + const prevSnapshotLink = snapshots.current && snapshots.current !== 1 && `/?u=${meta?.id}&h=${snapshots.current - 1}` + const nextSnapshotLink = snapshots.current && snapshots.total && snapshots.current !== snapshots.total && `/?u=${meta?.id}&h=${snapshots.current + 1}` + return ( @@ -58,7 +67,7 @@ export default function Header({ {meta ? ( - + ) : loading ? ( ) : error ? ( @@ -70,9 +79,16 @@ export default function Header({ - - To get the data from your account, first install the script by running - + + + To get the data from your account, first install the script by running + + + + {prevSnapshotLink && Previous snapshot} + {nextSnapshotLink && Next snapshot} + + git checkout loathers/greenbox release diff --git a/packages/greenbox-web/src/components/MainPage.tsx b/packages/greenbox-web/src/components/MainPage.tsx index 286bcf2..1a1f407 100644 --- a/packages/greenbox-web/src/components/MainPage.tsx +++ b/packages/greenbox-web/src/components/MainPage.tsx @@ -21,14 +21,18 @@ import Header from "./Header"; export default function MainPage() { const [directValue] = useQueryParam("d", StringParam); const [playerId] = useQueryParam("u", NumberParam); + const [snapshotNumber] = useQueryParam("h", NumberParam); const favouritePlayerId = useAppSelector((state) => state.favouritePlayerId); const dispatch = useAppDispatch(); useEffect(() => { const id = playerId || favouritePlayerId; - if (id) dispatch(fetchPlayerData(id)); - }, [playerId, dispatch, favouritePlayerId]); + if (id) dispatch(fetchPlayerData({ + playerId: id, + snapshotNumber: snapshotNumber ?? undefined, + })); + }, [playerId, dispatch, favouritePlayerId, snapshotNumber]); useEffect(() => { if (!directValue) return; @@ -45,6 +49,8 @@ export default function MainPage() { const loading = useAppSelector((state) => state.loading); const error = useAppSelector((state) => state.error); const errorMessage = useAppSelector((state) => state.errorMessage); + const snapshotCurrent = useAppSelector((state) => state.snapshotNumber); + const snapshotsTotal = useAppSelector((state) => state.snapshotsTotal); const id = "clash-toast"; @@ -82,6 +88,7 @@ export default function MainPage() { loading={loading.playerData} error={error.playerData} errorMessage={errorMessage.playerData} + snapshots={{current: snapshotCurrent, total: snapshotsTotal}} /> diff --git a/packages/greenbox-web/src/components/MetaInfo.tsx b/packages/greenbox-web/src/components/MetaInfo.tsx index 808145b..8aca3bb 100644 --- a/packages/greenbox-web/src/components/MetaInfo.tsx +++ b/packages/greenbox-web/src/components/MetaInfo.tsx @@ -2,6 +2,7 @@ import { HStack, Text } from "@chakra-ui/react"; import { formatDistance, intlFormat } from "date-fns"; import { RawSnapshotData, VERSION } from "greenbox-data"; import { useEffect, useMemo, useState } from "react"; +import { useAppSelector } from "../hooks"; import { FavouriteButton } from "./FavouriteButton"; import VersionWarning from "./VersionWarning"; @@ -9,9 +10,13 @@ import VersionWarning from "./VersionWarning"; type Props = { meta: RawSnapshotData["meta"]; direct?: boolean; + snapshots: { + current: number | null; + total: number | null; + }; }; -export default function MetaInfo({ meta, direct }: Props) { +export default function MetaInfo({ meta, direct, snapshots }: Props) { const [now, setNow] = useState(new Date()); const date = useMemo(() => new Date(meta.timestamp), [meta.timestamp]); const humanDate = useMemo( @@ -42,6 +47,9 @@ export default function MetaInfo({ meta, direct }: Props) { {`${ direct ? "Private s" : "S" }napshot`}{" "} + + {snapshots.current} / {snapshots.total} + {" "} by{" "} {meta.name} diff --git a/packages/greenbox-web/src/store/index.ts b/packages/greenbox-web/src/store/index.ts index 690b191..9ad088b 100644 --- a/packages/greenbox-web/src/store/index.ts +++ b/packages/greenbox-web/src/store/index.ts @@ -65,6 +65,8 @@ export interface GreenboxState { loading: Partial<{ [K in keyof GreenboxState]: boolean }>; error: Partial<{ [K in keyof GreenboxState]: boolean }>; errorMessage: Partial<{ [K in keyof GreenboxState]: string }>; + snapshotNumber: number | null; + snapshotsTotal: number | null; } const initialState: GreenboxState = { @@ -106,6 +108,8 @@ const initialState: GreenboxState = { }, error: { wikiClashes: false }, errorMessage: {}, + snapshotNumber: null, + snapshotsTotal: null, }; export const fetchClasses = createAsyncThunk( @@ -161,17 +165,27 @@ export const fetchAll = createAsyncThunk( }, ); +export interface GreenboxSpecifier { + playerId: number + snapshotNumber?: number +} + export const fetchPlayerData = createAsyncThunk( "playerData/fetch", - async (playerId: number) => { + async (specifier: GreenboxSpecifier) => { const response = await fetch( - `https://oaf.loathers.net/api/greenbox/${playerId}`, + specifier.snapshotNumber + ? `https://oaf.loathers.net/api/greenbox/${specifier.playerId}/${specifier.snapshotNumber}` + : `https://oaf.loathers.net/api/greenbox/${specifier.playerId}` ); const json = await response.json(); if (response.status !== 200) { throw new Error(json.error); } - return json.greenboxString || json.data as string; + return { + data: json.greenboxString || json.data as string, + total: json.total, + }; }, ); @@ -306,9 +320,14 @@ export const greenboxSlice = createSlice({ }) .addCase(fetchPlayerData.fulfilled, (state, action) => { // Set current player id - state.playerId = action.meta.arg; + state.playerId = action.meta.arg.playerId; // Parse and load greenbox string - const greenboxString = action.payload; + const greenboxString = action.payload.data; + state.snapshotsTotal = action.payload.total; + if (typeof state.snapshotsTotal !== "number") { + state.snapshotsTotal = state.snapshotsTotal?.greenbox; + } + state.snapshotNumber = action.meta.arg.snapshotNumber ?? state.snapshotsTotal try { state.playerData = api.expand(greenboxString); state.error.playerData = false;