Add secret HTML5 conversion page at /conversion
A lil page for us to keep track of Neopets's HTML5 conversion progress!
This commit is contained in:
parent
6da2ddb453
commit
275d1d62ab
5 changed files with 202 additions and 2 deletions
|
@ -30,6 +30,7 @@ const tryLoadable = (load, options) =>
|
|||
options
|
||||
);
|
||||
|
||||
const ConversionPage = tryLoadable(() => import("./ConversionPage"));
|
||||
const HomePage = tryLoadable(() => import("./HomePage"));
|
||||
const ItemSearchPage = tryLoadable(() => import("./ItemSearchPage"));
|
||||
const ItemPage = tryLoadable(() => import("./ItemPage"));
|
||||
|
@ -162,6 +163,11 @@ function App() {
|
|||
<PrivacyPolicyPage />
|
||||
</PageLayout>
|
||||
</Route>
|
||||
<Route path="/conversion">
|
||||
<PageLayout>
|
||||
<ConversionPage />
|
||||
</PageLayout>
|
||||
</Route>
|
||||
<Route path="/">
|
||||
<PageLayout hideHomeLink>
|
||||
<HomePage />
|
||||
|
|
107
src/app/ConversionPage.js
Normal file
107
src/app/ConversionPage.js
Normal file
|
@ -0,0 +1,107 @@
|
|||
import React from "react";
|
||||
import {
|
||||
Box,
|
||||
CircularProgress,
|
||||
CircularProgressLabel,
|
||||
Flex,
|
||||
Stack,
|
||||
} from "@chakra-ui/react";
|
||||
import gql from "graphql-tag";
|
||||
import { useQuery } from "@apollo/client";
|
||||
|
||||
import { ErrorMessage, Heading1 } from "./util";
|
||||
|
||||
function ConversionPage() {
|
||||
const { loading, error, data } = useQuery(
|
||||
gql`
|
||||
query ConversionPage_NoAuthRequired {
|
||||
numAppearanceLayersConverted
|
||||
numAppearanceLayersTotal
|
||||
|
||||
numPetLayersConverted: numAppearanceLayersConverted(type: PET_LAYER)
|
||||
numPetLayersTotal: numAppearanceLayersTotal(type: PET_LAYER)
|
||||
|
||||
numItemLayersConverted: numAppearanceLayersConverted(type: ITEM_LAYER)
|
||||
numItemLayersTotal: numAppearanceLayersTotal(type: ITEM_LAYER)
|
||||
}
|
||||
`,
|
||||
{ onError: (e) => console.error(e) }
|
||||
);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Heading1>HTML5 Conversion Hub</Heading1>
|
||||
<Box height="6" />
|
||||
<Stack direction="row" spacing="12" align="center">
|
||||
<ConversionProgress
|
||||
label="All layers"
|
||||
color="green.500"
|
||||
size="150px"
|
||||
numConverted={data?.numAppearanceLayersConverted}
|
||||
numTotal={data?.numAppearanceLayersTotal}
|
||||
isLoading={loading}
|
||||
/>
|
||||
<ConversionProgress
|
||||
label="Pet layers"
|
||||
color="blue.500"
|
||||
size="125px"
|
||||
numConverted={data?.numPetLayersConverted}
|
||||
numTotal={data?.numPetLayersTotal}
|
||||
isLoading={loading}
|
||||
/>
|
||||
<ConversionProgress
|
||||
label="Item layers"
|
||||
color="blue.500"
|
||||
size="125px"
|
||||
numConverted={data?.numItemLayersConverted}
|
||||
numTotal={data?.numItemLayersTotal}
|
||||
isLoading={loading}
|
||||
/>
|
||||
</Stack>
|
||||
{error && (
|
||||
<ErrorMessage marginTop="2">
|
||||
Oops, we couldn't load the latest data: {error.message}
|
||||
</ErrorMessage>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ConversionProgress({
|
||||
label,
|
||||
color,
|
||||
size,
|
||||
numConverted,
|
||||
numTotal,
|
||||
isLoading,
|
||||
}) {
|
||||
const convertedPercent = (numConverted / numTotal) * 100;
|
||||
|
||||
return (
|
||||
<Flex direction="column" align="center">
|
||||
<CircularProgress
|
||||
color={color}
|
||||
size={size}
|
||||
value={convertedPercent || 0}
|
||||
isIndeterminate={isLoading}
|
||||
>
|
||||
{numConverted != null && numTotal != null && (
|
||||
<CircularProgressLabel>
|
||||
{Math.floor(convertedPercent)}%
|
||||
</CircularProgressLabel>
|
||||
)}
|
||||
</CircularProgress>
|
||||
<Box height="1" />
|
||||
<Box textAlign="center">
|
||||
<Box fontSize="xl">{label}</Box>
|
||||
{numConverted != null && numTotal != null && (
|
||||
<Box fontSize="xs">
|
||||
{numConverted.toLocaleString()} of {numTotal.toLocaleString()}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
export default ConversionPage;
|
|
@ -84,8 +84,12 @@ export function Heading3({ children, ...props }) {
|
|||
/**
|
||||
* ErrorMessage is a simple error message for simple errors!
|
||||
*/
|
||||
export function ErrorMessage({ children }) {
|
||||
return <Box color="red.400">{children}</Box>;
|
||||
export function ErrorMessage({ children, ...props }) {
|
||||
return (
|
||||
<Box color="red.400" {...props}>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export function useCommonStyles() {
|
||||
|
|
|
@ -709,6 +709,40 @@ const buildSwfAssetLoader = (db) =>
|
|||
);
|
||||
});
|
||||
|
||||
const buildSwfAssetCountLoader = (db) =>
|
||||
new DataLoader(
|
||||
async (requests) => {
|
||||
const [rows, _] = await db.execute(
|
||||
`
|
||||
SELECT count(*) AS count, type,
|
||||
(manifest IS NOT NULL AND manifest != "") AS is_converted
|
||||
FROM swf_assets
|
||||
GROUP BY type, is_converted;
|
||||
`
|
||||
);
|
||||
const entities = rows.map(normalizeRow);
|
||||
|
||||
return requests.map(({ type, isConverted }) => {
|
||||
// Find the returned rows that match this count request.
|
||||
let matchingEntities = entities;
|
||||
if (type != null) {
|
||||
matchingEntities = matchingEntities.filter((e) => e.type === type);
|
||||
}
|
||||
if (isConverted != null) {
|
||||
matchingEntities = matchingEntities.filter(
|
||||
(e) => Boolean(e.isConverted) === isConverted
|
||||
);
|
||||
}
|
||||
|
||||
// Add their counts together, and return the total.
|
||||
return matchingEntities.map((e) => e.count).reduce((a, b) => a + b, 0);
|
||||
});
|
||||
},
|
||||
{
|
||||
cacheKeyFn: ({ type, isConverted }) => `${type},${isConverted}`,
|
||||
}
|
||||
);
|
||||
|
||||
const buildSwfAssetByRemoteIdLoader = (db) =>
|
||||
new DataLoader(
|
||||
async (typeAndRemoteIdPairs) => {
|
||||
|
@ -1190,6 +1224,7 @@ function buildLoaders(db) {
|
|||
loaders
|
||||
);
|
||||
loaders.swfAssetLoader = buildSwfAssetLoader(db);
|
||||
loaders.swfAssetCountLoader = buildSwfAssetCountLoader(db);
|
||||
loaders.swfAssetByRemoteIdLoader = buildSwfAssetByRemoteIdLoader(db);
|
||||
loaders.itemSwfAssetLoader = buildItemSwfAssetLoader(db, loaders);
|
||||
loaders.petSwfAssetLoader = buildPetSwfAssetLoader(db, loaders);
|
||||
|
|
|
@ -8,6 +8,11 @@ const typeDefs = gql`
|
|||
SIZE_150
|
||||
}
|
||||
|
||||
enum LayerType {
|
||||
PET_LAYER
|
||||
ITEM_LAYER
|
||||
}
|
||||
|
||||
# Cache for 1 week (unlikely to change)
|
||||
type AppearanceLayer @cacheControl(maxAge: 604800) {
|
||||
# The DTI ID. Guaranteed unique across all layers of all types.
|
||||
|
@ -67,6 +72,18 @@ const typeDefs = gql`
|
|||
"""
|
||||
restrictedZones: [Zone!]!
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
# Return the number of layers that have been converted to HTML5, optionally
|
||||
# filtered by type. Cache for 30 minutes (we re-sync with Neopets every
|
||||
# hour).
|
||||
numAppearanceLayersConverted(type: LayerType): Int!
|
||||
@cacheControl(maxAge: 1800)
|
||||
|
||||
# Return the total number of layers, optionally filtered by type. Cache for
|
||||
# 30 minutes (we re-sync with Neopets every hour).
|
||||
numAppearanceLayersTotal(type: LayerType): Int! @cacheControl(maxAge: 1800)
|
||||
}
|
||||
`;
|
||||
|
||||
const resolvers = {
|
||||
|
@ -200,8 +217,39 @@ const resolvers = {
|
|||
return { id: String(rows[0].parent_id) };
|
||||
},
|
||||
},
|
||||
|
||||
Query: {
|
||||
numAppearanceLayersConverted: async (
|
||||
_,
|
||||
{ type },
|
||||
{ swfAssetCountLoader }
|
||||
) => {
|
||||
const count = await swfAssetCountLoader.load({
|
||||
type: convertLayerTypeToSwfAssetType(type),
|
||||
isConverted: true,
|
||||
});
|
||||
return count;
|
||||
},
|
||||
numAppearanceLayersTotal: async (_, { type }, { swfAssetCountLoader }) => {
|
||||
const count = await swfAssetCountLoader.load({
|
||||
type: convertLayerTypeToSwfAssetType(type),
|
||||
});
|
||||
return count;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function convertLayerTypeToSwfAssetType(layerType) {
|
||||
switch (layerType) {
|
||||
case "PET_LAYER":
|
||||
return "biology";
|
||||
case "ITEM_LAYER":
|
||||
return "object";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadAndCacheAssetManifest(db, layer) {
|
||||
let manifest;
|
||||
try {
|
||||
|
|
Loading…
Reference in a new issue