From 5dede27741569f82560e3fdb9919e3ca4a239d22 Mon Sep 17 00:00:00 2001 From: Stella Cannefax Date: Thu, 14 Jul 2022 04:39:25 -0500 Subject: [PATCH] Explorer: Image Moderation integration (#2647) * initial version of image mod service client * lint changes for image mod client * DisplayBox changes for image mod work * remove excess logging * Update imageModeratorClient.ts * tweak blur effect * remove console logs, lint changes * tweak blur css * lint changes * lower blur to fit smaller pages better * change default devnet imgMod port * initial version of image mod service client lint changes for image mod client DisplayBox changes for image mod work remove excess logging Update imageModeratorClient.ts tweak blur effect remove console logs, lint changes tweak blur css lint changes lower blur to fit smaller pages better change default devnet imgMod port general cleanup / css tweaks lint changes * fix up image moderation service host * lint change * slow down hiding blur animation * revert stray css change * add missing license * add fallback image * add fallback image var * rename blur related vars * show automod notice in middle of image * lint change * remove image display when auto-modded * Update DisplayBox.module.css * don't show image before approval in explorer * tweak automod text notice * remove excess styling in displaybox * make automod notice smaller * update img check response format * lint changes * rearrange logic for automod notice * lint changes --- explorer/client/public/assets/fallback.png | Bin 0 -> 90 bytes .../displaybox/DisplayBox.module.css | 16 ++- .../src/components/displaybox/DisplayBox.tsx | 102 +++++++++++++----- .../object-result/ObjectResult.module.css | 2 +- .../client/src/utils/imageModeratorClient.ts | 38 +++++++ 5 files changed, 128 insertions(+), 30 deletions(-) create mode 100644 explorer/client/public/assets/fallback.png create mode 100644 explorer/client/src/utils/imageModeratorClient.ts diff --git a/explorer/client/public/assets/fallback.png b/explorer/client/public/assets/fallback.png new file mode 100644 index 0000000000000000000000000000000000000000..8d62fdc8b3168b82ca872cd970a984e73bd5c72f GIT binary patch literal 90 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1SBVv2j2ryoCO|{#S9F3${@^GvDCf{D9GdK j;uyklJ^9D~|Mm>5|Cs*WITQIDD9+&N>gTe~DWM4f6$=)o literal 0 HcmV?d00001 diff --git a/explorer/client/src/components/displaybox/DisplayBox.module.css b/explorer/client/src/components/displaybox/DisplayBox.module.css index 78891bc03ee21..29de66f256c5f 100644 --- a/explorer/client/src/components/displaybox/DisplayBox.module.css +++ b/explorer/client/src/components/displaybox/DisplayBox.module.css @@ -7,7 +7,21 @@ img.imagebox { } .codebox { - @apply overflow-hidden + @apply overflow-hidden border-solid border-stone-300 rounded-lg mt-[1vh] w-[88vw] w-[88vw] h-[88vw] lg:w-[35vw] lg:h-[35vw] mb-[10vh]; } + +.automod { + @apply border-0 border-b-2 border-solid border-gray-300 w-1/2; + + background-color: #d8e5ea; + position: relative; + text-align: center; + align-content: center; + vertical-align: middle; + margin: auto; + padding: 1rem !important; + margin-top: 2rem; + word-wrap: normal; +} diff --git a/explorer/client/src/components/displaybox/DisplayBox.tsx b/explorer/client/src/components/displaybox/DisplayBox.tsx index c45222a000af2..bece77731d1f8 100644 --- a/explorer/client/src/components/displaybox/DisplayBox.tsx +++ b/explorer/client/src/components/displaybox/DisplayBox.tsx @@ -3,6 +3,10 @@ import { useState, useCallback, useEffect } from 'react'; +import { + FALLBACK_IMAGE, + ImageModClient, +} from '../../utils/imageModeratorClient'; import { transformURL } from '../../utils/stringUtils'; import styles from './DisplayBox.module.css'; @@ -11,14 +15,33 @@ function DisplayBox({ display }: { display: string }) { const [hasDisplayLoaded, setHasDisplayLoaded] = useState(false); const [hasFailedToLoad, setHasFailedToLoad] = useState(false); + const [hasImgBeenChecked, setHasImgBeenChecked] = useState(false); + const [imgAllowState, setImgAllowState] = useState(false); + const imageStyle = hasDisplayLoaded ? {} : { display: 'none' }; - const handleImageLoad = useCallback( - () => setHasDisplayLoaded(true), - [setHasDisplayLoaded] - ); + const handleImageLoad = useCallback(() => { + setHasDisplayLoaded(true); + setHasFailedToLoad(false); + }, [setHasDisplayLoaded]); useEffect(() => { setHasFailedToLoad(false); + setHasImgBeenChecked(false); + setImgAllowState(false); + + new ImageModClient() + .checkImage(transformURL(display)) + .then(({ ok }) => { + setImgAllowState(ok); + }) + .catch((error) => { + console.warn(error); + // default to allow, so a broken img check service doesn't break NFT display + setImgAllowState(true); + }) + .finally(() => { + setHasImgBeenChecked(true); + }); }, [display]); const handleImageFail = useCallback( @@ -30,30 +53,53 @@ function DisplayBox({ display }: { display: string }) { [setHasFailedToLoad] ); - return ( -
- {!hasDisplayLoaded && ( -
- Please wait for display to load -
- )} - {hasFailedToLoad ? ( -
- No Image was Found -
- ) : ( - NFT - )} -
- ); + const loadedWithoutAllowedState = hasDisplayLoaded && !imgAllowState; + + let showAutoModNotice = + !hasFailedToLoad && hasImgBeenChecked && !imgAllowState; + + if (loadedWithoutAllowedState && hasImgBeenChecked) { + display = FALLBACK_IMAGE; + showAutoModNotice = true; + } + + if (showAutoModNotice) { + return ( +
+ {showAutoModNotice && ( +
+ NFT image hidden +
+ )} +
+ ); + } else { + return ( +
+ {!hasDisplayLoaded && ( +
+ image loading... +
+ )} + {hasFailedToLoad && ( +
+ No Image was Found +
+ )} + {!hasFailedToLoad && ( + NFT + )} +
+ ); + } } export default DisplayBox; diff --git a/explorer/client/src/pages/object-result/ObjectResult.module.css b/explorer/client/src/pages/object-result/ObjectResult.module.css index ee8a019b611be..b64a097371091 100644 --- a/explorer/client/src/pages/object-result/ObjectResult.module.css +++ b/explorer/client/src/pages/object-result/ObjectResult.module.css @@ -10,7 +10,7 @@ h1.title { .display { @apply ml-[5vw] mx-auto mt-[1vh] - w-[88vw] lg:w-[35vw] min-h-[88vw] lg:min-h-[35vw]; + w-[88vw] lg:w-[35vw] min-h-[35vw] lg:min-h-[35vw]; } .display > div > img { diff --git a/explorer/client/src/utils/imageModeratorClient.ts b/explorer/client/src/utils/imageModeratorClient.ts new file mode 100644 index 0000000000000..bd507d675051c --- /dev/null +++ b/explorer/client/src/utils/imageModeratorClient.ts @@ -0,0 +1,38 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import { IS_LOCAL_ENV, IS_STATIC_ENV } from './envUtil'; + +const ENV_STUBS_IMG_CHECK = IS_STATIC_ENV || IS_LOCAL_ENV; +const HOST = 'https://imgmod.sui.io'; + +export type ImageCheckResponse = { ok: boolean }; + +export interface IImageModClient { + checkImage(url: string): Promise; +} + +export class ImageModClient implements IImageModClient { + private readonly imgEndpoint: string; + + constructor() { + this.imgEndpoint = `${HOST}/img`; + } + + async checkImage(url: string): Promise { + // static and local environments always allow images without checking + if (ENV_STUBS_IMG_CHECK || url === FALLBACK_IMAGE) return { ok: true }; + + let resp: Promise = ( + await fetch(this.imgEndpoint, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ url }), + }) + ).json(); + + return { ok: await resp }; + } +} + +export const FALLBACK_IMAGE = 'assets/fallback.png';