Use live outfit URLs in bulk converter
This commit is contained in:
parent
929e6c57a4
commit
684bf5778f
1 changed files with 12 additions and 144 deletions
|
@ -18,7 +18,6 @@ import {
|
||||||
PopoverBody,
|
PopoverBody,
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
Spinner,
|
|
||||||
Tab,
|
Tab,
|
||||||
TabList,
|
TabList,
|
||||||
TabPanel,
|
TabPanel,
|
||||||
|
@ -30,8 +29,7 @@ import {
|
||||||
VStack,
|
VStack,
|
||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
|
|
||||||
import { Delay, ErrorMessage, Heading1, Heading2, usePageTitle } from "./util";
|
import { ErrorMessage, Heading1, Heading2, usePageTitle } from "./util";
|
||||||
import { gql, useQuery } from "@apollo/client";
|
|
||||||
import { CheckIcon, WarningIcon } from "@chakra-ui/icons";
|
import { CheckIcon, WarningIcon } from "@chakra-ui/icons";
|
||||||
|
|
||||||
function OutfitUrlsPage() {
|
function OutfitUrlsPage() {
|
||||||
|
@ -239,45 +237,14 @@ function SingleImageConverter() {
|
||||||
function BulkImageConverter() {
|
function BulkImageConverter() {
|
||||||
const [inputHtml, setInputHtml] = React.useState("");
|
const [inputHtml, setInputHtml] = React.useState("");
|
||||||
|
|
||||||
const parsedUrls = parseManyS3OutfitUrlsFromHtml(inputHtml);
|
const { outputHtml, numReplacements } = React.useMemo(
|
||||||
const outfitIds = parsedUrls.map((pu) => pu.outfitId);
|
|
||||||
|
|
||||||
// TODO: Do this query in batches for large pages?
|
|
||||||
const { loading, error, data } = useQuery(
|
|
||||||
gql`
|
|
||||||
query OutfitUrlsBulkImageConverter($outfitIds: [ID!]!) {
|
|
||||||
outfits(ids: $outfitIds) {
|
|
||||||
id
|
|
||||||
# Rather than send requests for different sizes separately, I'm just
|
|
||||||
# requesting them all and having the client choose, to simplify the
|
|
||||||
# query. gzip should compress it very efficiently!
|
|
||||||
imageUrl600: imageUrl(size: SIZE_600)
|
|
||||||
imageUrl300: imageUrl(size: SIZE_300)
|
|
||||||
imageUrl150: imageUrl(size: SIZE_150)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
{
|
|
||||||
variables: { outfitIds },
|
|
||||||
skip: outfitIds.length === 0,
|
|
||||||
onError: (e) => console.error(e),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const { outputHtml, numReplacements, replacementErrors } = React.useMemo(
|
|
||||||
() =>
|
() =>
|
||||||
inputHtml && data?.outfits
|
inputHtml
|
||||||
? replaceS3OutfitUrlsInHtml(inputHtml, data.outfits)
|
? replaceS3OutfitUrlsInHtml(inputHtml)
|
||||||
: { outputHtml: "", numReplacements: 0, replacementErrors: [] },
|
: { outputHtml: "", numReplacements: 0 },
|
||||||
[inputHtml, data?.outfits]
|
[inputHtml]
|
||||||
);
|
);
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
for (const replacementError of replacementErrors) {
|
|
||||||
console.error("Error replacing outfit URLs in HTML:", replacementError);
|
|
||||||
}
|
|
||||||
}, [replacementErrors]);
|
|
||||||
|
|
||||||
const { onCopy, hasCopied } = useClipboard(outputHtml);
|
const { onCopy, hasCopied } = useClipboard(outputHtml);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -353,20 +320,7 @@ function BulkImageConverter() {
|
||||||
value={outputHtml}
|
value={outputHtml}
|
||||||
/>
|
/>
|
||||||
<Box gridArea="status" textAlign="right" justifySelf="end">
|
<Box gridArea="status" textAlign="right" justifySelf="end">
|
||||||
{loading ? (
|
{outputHtml && numReplacements === 0 ? (
|
||||||
<Delay ms={1000}>
|
|
||||||
<Flex alignItems="center" opacity="0.8">
|
|
||||||
<Spinner size="xs" marginRight="1.5" />
|
|
||||||
<Box fontSize="sm">
|
|
||||||
Found {outfitIds.length} outfit images, converting…
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
|
||||||
</Delay>
|
|
||||||
) : error ? (
|
|
||||||
<ErrorMessage fontSize="sm">
|
|
||||||
Error loading outfits. Try again?
|
|
||||||
</ErrorMessage>
|
|
||||||
) : inputHtml && !outputHtml && outfitIds.length === 0 ? (
|
|
||||||
<Popover trigger="hover">
|
<Popover trigger="hover">
|
||||||
<PopoverTrigger>
|
<PopoverTrigger>
|
||||||
<Flex
|
<Flex
|
||||||
|
@ -403,52 +357,6 @@ function BulkImageConverter() {
|
||||||
</PopoverBody>
|
</PopoverBody>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
) : outputHtml && replacementErrors.length > 0 ? (
|
|
||||||
<Popover trigger="hover">
|
|
||||||
<PopoverTrigger>
|
|
||||||
<Flex
|
|
||||||
as={
|
|
||||||
replacementErrors.length > numReplacements
|
|
||||||
? ErrorMessage
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
alignItems="center"
|
|
||||||
fontSize="sm"
|
|
||||||
tabIndex="0"
|
|
||||||
borderRadius="md"
|
|
||||||
paddingX="2"
|
|
||||||
marginRight="-2"
|
|
||||||
_focus={{ outline: "0", boxShadow: "outline" }}
|
|
||||||
>
|
|
||||||
<WarningIcon marginRight="1.5" />
|
|
||||||
<Box>
|
|
||||||
Converted {numReplacements} outfit images, with{" "}
|
|
||||||
{replacementErrors.length} errors
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent width="50ch">
|
|
||||||
<PopoverArrow />
|
|
||||||
<PopoverBody>
|
|
||||||
<Box fontSize="xs" textAlign="center">
|
|
||||||
Errors are unusual at this point in the process. Sorry
|
|
||||||
about this!
|
|
||||||
<br />
|
|
||||||
Sometimes this means the creator has deleted the outfit
|
|
||||||
from DTI.
|
|
||||||
<br />
|
|
||||||
Email me at{" "}
|
|
||||||
<a href="mailto:matchu@openneo.net">
|
|
||||||
matchu@openneo.net
|
|
||||||
</a>{" "}
|
|
||||||
and I'll try to help!
|
|
||||||
<br />
|
|
||||||
We've left the {replacementErrors.length} erroring images
|
|
||||||
unchanged for now.
|
|
||||||
</Box>
|
|
||||||
</PopoverBody>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
) : outputHtml ? (
|
) : outputHtml ? (
|
||||||
<Flex alignItems="center" fontSize="sm" opacity="0.8">
|
<Flex alignItems="center" fontSize="sm" opacity="0.8">
|
||||||
<CheckIcon marginRight="1.5" />
|
<CheckIcon marginRight="1.5" />
|
||||||
|
@ -503,56 +411,16 @@ function convertS3OutfitUrl(url) {
|
||||||
return `https://impress-outfit-images.openneo.net/outfits/${outfitId}/${size}.png`;
|
return `https://impress-outfit-images.openneo.net/outfits/${outfitId}/${size}.png`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseManyS3OutfitUrlsFromHtml(html) {
|
function replaceS3OutfitUrlsInHtml(html) {
|
||||||
const matches = html.match(S3_OUTFIT_URL_GLOBAL_PATTERN) || [];
|
// Use the `replace` method to scan the HTML for matches, and count the
|
||||||
return matches.map(parseS3OutfitUrl);
|
// replacements as we go!
|
||||||
}
|
|
||||||
|
|
||||||
function replaceS3OutfitUrlsInHtml(html, outfits) {
|
|
||||||
const outfitsById = new Map();
|
|
||||||
for (const outfit of outfits) {
|
|
||||||
if (!outfit) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
outfitsById.set(outfit.id, outfit);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use the `replace` method to scan the HTML for matches, which will run this
|
|
||||||
// function on each match to decide what to replace it with. We'll count
|
|
||||||
// successes and log failures along the way!
|
|
||||||
let numReplacements = 0;
|
let numReplacements = 0;
|
||||||
const replacementErrors = [];
|
|
||||||
const outputHtml = html.replace(S3_OUTFIT_URL_GLOBAL_PATTERN, (match) => {
|
const outputHtml = html.replace(S3_OUTFIT_URL_GLOBAL_PATTERN, (match) => {
|
||||||
let newUrl;
|
|
||||||
try {
|
|
||||||
const { outfitId, size } = parseS3OutfitUrl(match);
|
|
||||||
const outfit = outfitsById.get(outfitId);
|
|
||||||
if (!outfit) {
|
|
||||||
throw new Error(`could not find outfit ${outfitId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const sizeKey = `imageUrl` + size;
|
|
||||||
|
|
||||||
if (!(sizeKey in outfit)) {
|
|
||||||
throw new Error(
|
|
||||||
`outfit ${outfitId} has no image key ${sizeKey}: ${JSON.stringify(
|
|
||||||
outfit
|
|
||||||
)}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
newUrl = outfit[sizeKey];
|
|
||||||
} catch (e) {
|
|
||||||
e.message += ` (${match})`; // help us understand which URL failed!
|
|
||||||
replacementErrors.push(e);
|
|
||||||
return match;
|
|
||||||
}
|
|
||||||
|
|
||||||
numReplacements++;
|
numReplacements++;
|
||||||
return newUrl;
|
return convertS3OutfitUrl(match);
|
||||||
});
|
});
|
||||||
|
|
||||||
return { outputHtml, numReplacements, replacementErrors };
|
return { outputHtml, numReplacements };
|
||||||
}
|
}
|
||||||
|
|
||||||
export default OutfitUrlsPage;
|
export default OutfitUrlsPage;
|
||||||
|
|
Loading…
Reference in a new issue