add svgUrl for image layers
This commit is contained in:
parent
072a95faba
commit
e18aba17c6
8 changed files with 116 additions and 9 deletions
|
@ -6,7 +6,7 @@ const streamPipeline = util.promisify(stream.pipeline);
|
|||
|
||||
const VALID_URL_PATTERNS = [
|
||||
/^http:\/\/images\.neopets\.com\/items\/[a-zA-Z0-9_ -]+\.gif$/,
|
||||
/^http:\/\/pets\.neopets\.com\/cp\/[a-z0-9]+\/[0-9]+\/[0-9]+\.png$/,
|
||||
/^http:\/\/images\.neopets\.com\/cp\/(bio|items)\/data\/[0-9]{3}\/[0-9]{3}\/[0-9]{3}\/[a-f0-9_]+\/[0-9]+\.svg$/,
|
||||
];
|
||||
|
||||
export default async (req, res) => {
|
||||
|
@ -34,6 +34,13 @@ export default async (req, res) => {
|
|||
);
|
||||
|
||||
res.status(proxyRes.status);
|
||||
|
||||
res.setHeader("Content-Length", proxyRes.headers.get("Content-Length"));
|
||||
res.setHeader("Content-Type", proxyRes.headers.get("Content-Type"));
|
||||
|
||||
res.setHeader("Cache-Control", proxyRes.headers.get("Cache-Control"));
|
||||
res.setHeader("ETag", proxyRes.headers.get("ETag"));
|
||||
res.setHeader("Last-Modified", proxyRes.headers.get("Last-Modified"));
|
||||
|
||||
streamPipeline(proxyRes.body, res);
|
||||
};
|
||||
|
|
|
@ -65,7 +65,7 @@ export function OutfitLayers({ loading, visibleLayers, doAnimations = false }) {
|
|||
>
|
||||
<FullScreenCenter>
|
||||
<ImageTag
|
||||
src={layer.imageUrl}
|
||||
src={getBestImageUrlForLayer(layer)}
|
||||
// We manage the fade-in and fade-out separately! The fade-in
|
||||
// happens here, when the <Image> finishes preloading and
|
||||
// applies the src to the underlying <img>.
|
||||
|
@ -132,4 +132,12 @@ function FullScreenCenter({ children }) {
|
|||
);
|
||||
}
|
||||
|
||||
function getBestImageUrlForLayer(layer) {
|
||||
if (layer.svgUrl) {
|
||||
return `/api/assetProxy?url=${encodeURIComponent(layer.svgUrl)}`;
|
||||
} else {
|
||||
return layer.imageUrl;
|
||||
}
|
||||
}
|
||||
|
||||
export default OutfitPreview;
|
||||
|
|
|
@ -93,6 +93,7 @@ export const itemAppearanceFragment = gql`
|
|||
fragment ItemAppearanceForOutfitPreview on ItemAppearance {
|
||||
layers {
|
||||
id
|
||||
svgUrl
|
||||
imageUrl(size: SIZE_600)
|
||||
zone {
|
||||
id
|
||||
|
@ -111,6 +112,7 @@ export const petAppearanceFragment = gql`
|
|||
id
|
||||
layers {
|
||||
id
|
||||
svgUrl
|
||||
imageUrl(size: SIZE_600)
|
||||
zone {
|
||||
id
|
||||
|
|
|
@ -188,8 +188,8 @@ function parseOutfitUrl() {
|
|||
colorId: urlParams.get("color"),
|
||||
emotion: urlParams.get("emotion") || "HAPPY",
|
||||
genderPresentation: urlParams.get("genderPresentation") || "FEMININE",
|
||||
wornItemIds: urlParams.getAll("objects[]"),
|
||||
closetedItemIds: urlParams.getAll("closet[]"),
|
||||
wornItemIds: new Set(urlParams.getAll("objects[]")),
|
||||
closetedItemIds: new Set(urlParams.getAll("closet[]")),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -65,6 +65,14 @@ const typeDefs = gql`
|
|||
id: ID!
|
||||
zone: Zone!
|
||||
imageUrl(size: LayerImageSize): String
|
||||
|
||||
"""
|
||||
This layer as a single SVG, if available.
|
||||
|
||||
This might not be available if the asset isn't converted yet by Neopets,
|
||||
or if it's not as simple as a single SVG (e.g. animated).
|
||||
"""
|
||||
svgUrl: String
|
||||
}
|
||||
|
||||
type Zone {
|
||||
|
@ -210,6 +218,42 @@ const resolvers = {
|
|||
`/${rid1}/${rid2}/${rid3}/${rid}/${sizeNum}x${sizeNum}.png?v2-${time}`
|
||||
);
|
||||
},
|
||||
svgUrl: async (layer) => {
|
||||
const manifest = await neopets.loadAssetManifest(layer.url);
|
||||
if (!manifest) {
|
||||
console.debug("expected manifest to exist, but it did not");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (manifest.assets.length !== 1) {
|
||||
console.debug(
|
||||
"expected 1 asset in manifest, but found %d",
|
||||
manifest.assets.length
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
const asset = manifest.assets[0];
|
||||
if (asset.format !== "vector") {
|
||||
console.debug(
|
||||
'expected asset format "vector", but found %s',
|
||||
asset.format
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (asset.assetData.length !== 1) {
|
||||
console.debug(
|
||||
"expected 1 datum in asset, but found %d",
|
||||
asset.assetData.length
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
const assetDatum = asset.assetData[0];
|
||||
const url = new URL(assetDatum.path, "http://images.neopets.com");
|
||||
return url.toString();
|
||||
},
|
||||
},
|
||||
Zone: {
|
||||
label: async (zone, _, { zoneTranslationLoader }) => {
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
const fetch = require("node-fetch");
|
||||
|
||||
async function loadPetData(petName) {
|
||||
const res = await fetch(
|
||||
const url =
|
||||
`http://www.neopets.com/amfphp/json.php/CustomPetService.getViewerData` +
|
||||
`/${petName}`
|
||||
);
|
||||
`/${petName}`;
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) {
|
||||
throw new Error(`neopets.com returned: ${res.statusText}`);
|
||||
throw new Error(
|
||||
`for pet data, neopets.com returned: ` +
|
||||
`${res.status} ${res.statusText}. (${url})`
|
||||
);
|
||||
}
|
||||
|
||||
const json = await res.json();
|
||||
|
@ -17,4 +20,40 @@ async function loadPetData(petName) {
|
|||
return json;
|
||||
}
|
||||
|
||||
module.exports = { loadPetData };
|
||||
async function loadAssetManifest(swfUrl) {
|
||||
const manifestUrl = convertSwfUrlToManifestUrl(swfUrl);
|
||||
const res = await fetch(manifestUrl);
|
||||
if (res.status === 404) {
|
||||
return null;
|
||||
} else if (!res.ok) {
|
||||
throw new Error(
|
||||
`for asset manifest, images.neopets.com returned: ` +
|
||||
`${res.status} ${res.statusText}. (${manifestUrl})`
|
||||
);
|
||||
}
|
||||
|
||||
const json = await res.json();
|
||||
return {
|
||||
assets: json["cpmanifest"]["assets"].map((asset) => ({
|
||||
format: asset["format"],
|
||||
assetData: asset["asset_data"].map((assetDatum) => ({
|
||||
path: assetDatum["url"],
|
||||
})),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
const SWF_URL_PATTERN = /^http:\/\/images\.neopets\.com\/cp\/(.+?)\/swf\/(.+?)\.swf$/;
|
||||
|
||||
function convertSwfUrlToManifestUrl(swfUrl) {
|
||||
const match = swfUrl.match(SWF_URL_PATTERN);
|
||||
if (!match) {
|
||||
throw new Error(`unexpected SWF URL format: ${JSON.stringify(swfUrl)}`);
|
||||
}
|
||||
|
||||
const [_, type, folders] = match;
|
||||
|
||||
return `http://images.neopets.com/cp/${type}/data/${folders}/manifest.json`;
|
||||
}
|
||||
|
||||
module.exports = { loadPetData, loadAssetManifest };
|
||||
|
|
|
@ -15,6 +15,7 @@ describe("PetAppearance", () => {
|
|||
layers {
|
||||
id
|
||||
imageUrl(size: SIZE_600)
|
||||
svgUrl
|
||||
zone {
|
||||
depth
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ Object {
|
|||
Object {
|
||||
"id": "5995",
|
||||
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/007/7941/600x600.png?v2-0",
|
||||
"svgUrl": "http://images.neopets.com/cp/bio/data/000/000/007/7941_2c4cc4b846/7941.svg",
|
||||
"zone": Object {
|
||||
"depth": 18,
|
||||
},
|
||||
|
@ -14,6 +15,7 @@ Object {
|
|||
Object {
|
||||
"id": "5996",
|
||||
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/007/7942/600x600.png?v2-0",
|
||||
"svgUrl": "http://images.neopets.com/cp/bio/data/000/000/007/7942_2eab06fd7b/7942.svg",
|
||||
"zone": Object {
|
||||
"depth": 7,
|
||||
},
|
||||
|
@ -21,6 +23,7 @@ Object {
|
|||
Object {
|
||||
"id": "6000",
|
||||
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/007/7946/600x600.png?v2-0",
|
||||
"svgUrl": "http://images.neopets.com/cp/bio/data/000/000/007/7946_0348dad587/7946.svg",
|
||||
"zone": Object {
|
||||
"depth": 40,
|
||||
},
|
||||
|
@ -28,6 +31,7 @@ Object {
|
|||
Object {
|
||||
"id": "16467",
|
||||
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/024/24008/600x600.png?v2-0",
|
||||
"svgUrl": "http://images.neopets.com/cp/bio/data/000/000/024/24008_a05fe9876a/24008.svg",
|
||||
"zone": Object {
|
||||
"depth": 34,
|
||||
},
|
||||
|
@ -35,6 +39,7 @@ Object {
|
|||
Object {
|
||||
"id": "19784",
|
||||
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/028/28892/600x600.png?v2-1313418652000",
|
||||
"svgUrl": "http://images.neopets.com/cp/bio/data/000/000/028/28892_a8e3a8b430/28892.svg",
|
||||
"zone": Object {
|
||||
"depth": 37,
|
||||
},
|
||||
|
@ -42,6 +47,7 @@ Object {
|
|||
Object {
|
||||
"id": "178150",
|
||||
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/036/36887/600x600.png?v2-1354240708000",
|
||||
"svgUrl": "http://images.neopets.com/cp/bio/data/000/000/036/36887_a335fbba09/36887.svg",
|
||||
"zone": Object {
|
||||
"depth": 38,
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue