Skip to content

Commit

Permalink
Explorer: Image Moderation integration (MystenLabs#2647)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Stella Cannefax authored Jul 14, 2022
1 parent 817d799 commit 5dede27
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 30 deletions.
Binary file added explorer/client/public/assets/fallback.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 15 additions & 1 deletion explorer/client/src/components/displaybox/DisplayBox.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
102 changes: 74 additions & 28 deletions explorer/client/src/components/displaybox/DisplayBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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(
Expand All @@ -30,30 +53,53 @@ function DisplayBox({ display }: { display: string }) {
[setHasFailedToLoad]
);

return (
<div className={styles['display-container']}>
{!hasDisplayLoaded && (
<div className={styles.imagebox} id="pleaseWaitImage">
Please wait for display to load
</div>
)}
{hasFailedToLoad ? (
<div className={styles.imagebox} id="noImage">
No Image was Found
</div>
) : (
<img
id="loadedImage"
className={styles.imagebox}
style={imageStyle}
alt="NFT"
src={transformURL(display)}
onLoad={handleImageLoad}
onError={handleImageFail}
/>
)}
</div>
);
const loadedWithoutAllowedState = hasDisplayLoaded && !imgAllowState;

let showAutoModNotice =
!hasFailedToLoad && hasImgBeenChecked && !imgAllowState;

if (loadedWithoutAllowedState && hasImgBeenChecked) {
display = FALLBACK_IMAGE;
showAutoModNotice = true;
}

if (showAutoModNotice) {
return (
<div className={styles['display-container']}>
{showAutoModNotice && (
<div className={styles.automod} id="modnotice">
NFT image hidden
</div>
)}
</div>
);
} else {
return (
<div className={styles['display-container']}>
{!hasDisplayLoaded && (
<div className={styles.imagebox} id="pleaseWaitImage">
image loading...
</div>
)}
{hasFailedToLoad && (
<div className={styles.imagebox} id="noImage">
No Image was Found
</div>
)}
{!hasFailedToLoad && (
<img
id="loadedImage"
className={styles.imagebox}
style={imageStyle}
alt="NFT"
src={transformURL(display)}
onLoad={handleImageLoad}
onError={handleImageFail}
/>
)}
</div>
);
}
}

export default DisplayBox;
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
38 changes: 38 additions & 0 deletions explorer/client/src/utils/imageModeratorClient.ts
Original file line number Diff line number Diff line change
@@ -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<ImageCheckResponse>;
}

export class ImageModClient implements IImageModClient {
private readonly imgEndpoint: string;

constructor() {
this.imgEndpoint = `${HOST}/img`;
}

async checkImage(url: string): Promise<ImageCheckResponse> {
// static and local environments always allow images without checking
if (ENV_STUBS_IMG_CHECK || url === FALLBACK_IMAGE) return { ok: true };

let resp: Promise<boolean> = (
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';

0 comments on commit 5dede27

Please sign in to comment.