@@ -1083,51 +1088,42 @@ export default function MemeLabPageComponent({
Arweave Links
-
-
- {nft.metadata.image_details.format}
-
- {nft.metadata.image}
-
-
-
-
- {(nft.metadata.animation ||
- nft.metadata.animation_url) && (
+ {imageHref && (
+
+
+ {imageFormat && {imageFormat}}
+
+ {imageHref}
+
+
+
+
+ )}
+ {animationHref && (
- {nft.metadata.animation_details.format}
+ {animationFormat && {animationFormat}}
- {nft.metadata.animation
- ? nft.metadata.animation
- : nft.metadata.animation_url}
+ {animationHref}
@@ -1178,14 +1174,18 @@ export default function MemeLabPageComponent({
Mint Date |
{printMintDate(nft.mint_date)} |
-
- | File Type |
- {getFileTypeFromMetadata(nft.metadata)} |
-
-
- | Dimensions |
- {getDimensionsFromMetadata(nft.metadata)} |
-
+ {fileType && (
+
+ | File Type |
+ {fileType} |
+
+ )}
+ {dimensions && (
+
+ | Dimensions |
+ {dimensions} |
+
+ )}
diff --git a/components/nft-image/NFTModel.tsx b/components/nft-image/NFTModel.tsx
index 611c5aeedc..64d38af947 100644
--- a/components/nft-image/NFTModel.tsx
+++ b/components/nft-image/NFTModel.tsx
@@ -1,5 +1,6 @@
import "@google/model-viewer";
import type { BaseNFT } from "@/entities/INFT";
+import { getResolvedAnimationSrc } from "./utils/animation-source";
export default function NFTModel(
props: Readonly<{ nft: BaseNFT; id?: string | undefined }>
@@ -8,12 +9,13 @@ export default function NFTModel(
// @ts-ignore
+ poster={props.nft.scaled}
+ >
);
}
diff --git a/components/nft-image/renderers/NFTHTMLRenderer.tsx b/components/nft-image/renderers/NFTHTMLRenderer.tsx
index da9ecf58e1..f01ece3bd6 100644
--- a/components/nft-image/renderers/NFTHTMLRenderer.tsx
+++ b/components/nft-image/renderers/NFTHTMLRenderer.tsx
@@ -4,26 +4,19 @@ import { Col } from "react-bootstrap";
import styles from "../NFTImage.module.scss";
import NFTImageBalance from "../NFTImageBalance";
import type { BaseRendererProps } from "../types/renderer-props";
+import { getResolvedAnimationSrc } from "../utils/animation-source";
function getSrc(nft: BaseRendererProps["nft"]): string | undefined {
- const hasMetadata = "metadata" in nft;
- const hasAnimation = hasMetadata && nft.metadata.animation;
-
- if (hasAnimation) {
- return nft.metadata.animation;
- } else if (hasMetadata) {
- return nft.metadata.animation_url;
- }
-
- return undefined;
+ return getResolvedAnimationSrc(nft);
}
export default function NFTHTMLRenderer(props: Readonly
) {
const src = getSrc(props.nft);
+ const animationClassName = styles["nftAnimation"] ?? "";
return (
{props.showBalance && (
) {
return null;
}
+ const animationClassName = styles["nftAnimation"] ?? "";
+
return (
+ className={`${animationClassName} ${props.imageStyle} ${props.bgStyle} d-flex justify-content-center align-items-center`}
+ >
{props.showBalance && (
) {
+ const animationSrc = getResolvedAnimationSrc(props.nft);
+ const animationClassName = styles["nftAnimation"] ?? "";
+ const compressedAnimationSrc =
+ "metadata" in props.nft ? props.nft.compressed_animation : undefined;
+ const normalizedCompressedAnimationSrc = (() => {
+ if (compressedAnimationSrc == null || compressedAnimationSrc === "") {
+ return undefined;
+ }
+
+ try {
+ const baseUrl = globalScope.window.location.href;
+ return new URL(compressedAnimationSrc, baseUrl).href;
+ } catch {
+ return undefined;
+ }
+ })();
+
return (
+ className={`${animationClassName} ${props.heightStyle} ${props.bgStyle} d-flex justify-content-center align-items-center`}
+ >
{props.showBalance && (
) {
"metadata" in props.nft &&
props.nft.compressed_animation
? props.nft.compressed_animation
- : props.nft.animation
+ : animationSrc
}
className={props.imageStyle}
onError={withArweaveFallback(({ currentTarget }) => {
if (
"metadata" in props.nft &&
- currentTarget.src === props.nft.compressed_animation
+ currentTarget.src === normalizedCompressedAnimationSrc &&
+ animationSrc
) {
- currentTarget.src = props.nft.animation;
- } else if ("metadata" in props.nft) {
- currentTarget.src = props.nft.metadata.animation;
+ currentTarget.src = animationSrc;
}
- })}>
+ })}
+ >
);
}
diff --git a/components/nft-image/utils/animation-source.ts b/components/nft-image/utils/animation-source.ts
new file mode 100644
index 0000000000..5886171e64
--- /dev/null
+++ b/components/nft-image/utils/animation-source.ts
@@ -0,0 +1,43 @@
+import type { BaseNFT, NFTLite } from "@/entities/INFT";
+
+const normalizeNonEmptyString = (value: unknown): string | undefined => {
+ if (typeof value !== "string") {
+ return undefined;
+ }
+
+ const trimmed = value.trim();
+ return trimmed.length > 0 ? trimmed : undefined;
+};
+
+export function getResolvedAnimationSrc(
+ nft: BaseNFT | NFTLite | undefined
+): string | undefined {
+ if (!nft) {
+ return undefined;
+ }
+
+ const metadata =
+ "metadata" in nft &&
+ nft.metadata !== null &&
+ typeof nft.metadata === "object"
+ ? (nft.metadata as {
+ readonly animation?: unknown;
+ readonly animation_url?: unknown;
+ })
+ : undefined;
+
+ const candidates = [
+ nft.animation,
+ metadata?.animation,
+ metadata?.animation_url,
+ ];
+
+ for (const candidate of candidates) {
+ const resolved = normalizeNonEmptyString(candidate);
+ if (resolved) {
+ return resolved;
+ }
+ }
+
+ return undefined;
+}
diff --git a/components/nft-image/utils/image-source.ts b/components/nft-image/utils/image-source.ts
new file mode 100644
index 0000000000..026370d6e7
--- /dev/null
+++ b/components/nft-image/utils/image-source.ts
@@ -0,0 +1,38 @@
+import type { BaseNFT, NFTLite } from "@/entities/INFT";
+
+const normalizeNonEmptyString = (value: unknown): string | undefined => {
+ if (typeof value !== "string") {
+ return undefined;
+ }
+
+ const trimmed = value.trim();
+ return trimmed.length > 0 ? trimmed : undefined;
+};
+
+export function getResolvedImageSrc(
+ nft: BaseNFT | NFTLite | undefined
+): string | undefined {
+ if (!nft) {
+ return undefined;
+ }
+
+ const metadata =
+ "metadata" in nft &&
+ nft.metadata !== null &&
+ typeof nft.metadata === "object"
+ ? (nft.metadata as {
+ readonly image?: unknown;
+ })
+ : undefined;
+
+ const candidates = [metadata?.image, nft.image];
+
+ for (const candidate of candidates) {
+ const resolved = normalizeNonEmptyString(candidate);
+ if (resolved) {
+ return resolved;
+ }
+ }
+
+ return undefined;
+}
diff --git a/components/nft-image/utils/media-type.ts b/components/nft-image/utils/media-type.ts
index 26c5b5aba7..cc7dd8a6bc 100644
--- a/components/nft-image/utils/media-type.ts
+++ b/components/nft-image/utils/media-type.ts
@@ -1,27 +1,31 @@
import type { BaseNFT, NFTLite } from "@/entities/INFT";
+import { getResolvedAnimationSrc } from "./animation-source";
-type MediaType = 'html' | 'glb' | 'video' | 'image';
+type MediaType = "html" | "glb" | "video" | "image";
-export function getMediaType(nft: BaseNFT | NFTLite, animation: boolean): MediaType {
- if (!animation || !nft.animation) {
- return 'image';
+export function getMediaType(
+ nft: BaseNFT | NFTLite,
+ animation: boolean
+): MediaType {
+ if (!animation || !getResolvedAnimationSrc(nft)) {
+ return "image";
}
if (!("metadata" in nft) || !nft.metadata?.animation_details?.format) {
- return 'image';
+ return "image";
}
const format = nft.metadata.animation_details.format.toLowerCase();
-
+
switch (format) {
- case 'html':
- return 'html';
- case 'glb':
- return 'glb';
- case 'mp4':
- case 'mov':
- return 'video';
+ case "html":
+ return "html";
+ case "glb":
+ return "glb";
+ case "mp4":
+ case "mov":
+ return "video";
default:
- return 'image';
+ return "image";
}
}
diff --git a/components/the-memes/MemePageArt.tsx b/components/the-memes/MemePageArt.tsx
index d1cf4ec0d4..2d5fa3abd0 100644
--- a/components/the-memes/MemePageArt.tsx
+++ b/components/the-memes/MemePageArt.tsx
@@ -3,6 +3,8 @@
import Download from "@/components/download/Download";
import NFTAttributes from "@/components/nft-attributes/NFTAttributes";
import NFTImage from "@/components/nft-image/NFTImage";
+import { getResolvedAnimationSrc } from "@/components/nft-image/utils/animation-source";
+import { getResolvedImageSrc } from "@/components/nft-image/utils/image-source";
import type { MemesExtendedData, NFT } from "@/entities/INFT";
import {
enterArtFullScreen,
@@ -12,8 +14,10 @@ import {
printMintDate,
} from "@/helpers/Helpers";
import {
- getDimensionsFromMetadata,
- getFileTypeFromMetadata,
+ getAnimationDimensionsFromMetadata,
+ getAnimationFileTypeFromMetadata,
+ getImageDimensionsFromMetadata,
+ getImageFileTypeFromMetadata,
} from "@/helpers/nft.helpers";
import { faExpandAlt } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@@ -32,11 +36,25 @@ export function MemePageArt(props: {
const [currentSlide, setCurrentSlide] = useState(0);
- const hasAnimation = props.nft?.animation || props.nft?.metadata?.animation;
- const fullscreenElementId =
- hasAnimation && currentSlide === 0
- ? "the-art-fullscreen-animation"
- : "the-art-fullscreen-img";
+ const animationHref = getResolvedAnimationSrc(props.nft);
+ const hasAnimation = Boolean(animationHref);
+ const imageFormat = getImageFileTypeFromMetadata(props.nft?.metadata);
+ const animationFormat = getAnimationFileTypeFromMetadata(props.nft?.metadata);
+ const imageDimensions = getImageDimensionsFromMetadata(props.nft?.metadata);
+ const animationDimensions = getAnimationDimensionsFromMetadata(
+ props.nft?.metadata
+ );
+ const imageHref = getResolvedImageSrc(props.nft);
+ const hasImage = Boolean(imageHref);
+ const isShowingAnimation = hasAnimation && (currentSlide === 0 || !imageHref);
+ let fullscreenElementId = "";
+ if (isShowingAnimation) {
+ fullscreenElementId = "the-art-fullscreen-animation";
+ } else if (hasImage) {
+ fullscreenElementId = "the-art-fullscreen-img";
+ }
+ const fileType = isShowingAnimation ? animationFormat : imageFormat;
+ const dimensions = isShowingAnimation ? animationDimensions : imageDimensions;
const distributionPlanLink = (() => {
const id = props.nft?.id;
@@ -59,27 +77,20 @@ export function MemePageArt(props: {
setCurrentSlide(event);
}
- let currentFormat: string | undefined;
- if (props.nft?.animation || props.nft?.metadata.animation) {
- if (currentSlide === 0) {
- currentFormat = props.nft.metadata.animation_details.format;
- } else {
- currentFormat = props.nft.metadata.image_details.format;
- }
- } else {
- currentFormat = props.nft?.metadata.image_details.format;
- }
+ const currentFormat = fileType ?? "";
if (props.show && props.nft && props.nftMeta) {
return (
<>
- {props.nft.animation || props.nft.metadata.animation ? (
+ {hasAnimation ? (
<>
-
{currentFormat}
+
+ {currentFormat}
+
{isFullScreenSupported && (
+ onSlide={carouselHandlerSlide}
+ >
-
-
-
+ {hasImage && (
+
+
+
+ )}
>
) : (
<>
-
{currentFormat}
+
+ {currentFormat}
+
{isFullScreenSupported && (
-
+ {hasImage && (
+
+ )}
>
)}
@@ -163,53 +181,42 @@ export function MemePageArt(props: {
Arweave Links
-
-
- {props.nft.metadata.image_details.format}
-
- {props.nft.metadata.image}
-
-
-
-
- {(props.nft.metadata.animation ||
- props.nft.metadata.animation_url) && (
+ {imageHref && (
+
+
+ {imageFormat && {imageFormat}}
+
+ {imageHref}
+
+
+
+
+ )}
+ {animationHref && (
-
- {props.nft.metadata.animation_details.format}
-
+ {animationFormat && {animationFormat}}
- {props.nft.metadata.animation
- ? props.nft.metadata.animation
- : props.nft.metadata.animation_url}
+ rel="noopener noreferrer"
+ >
+ {animationHref}
@@ -226,7 +233,8 @@ export function MemePageArt(props: {
xs={{ span: 12 }}
sm={{ span: 6 }}
md={{ span: 6 }}
- lg={{ span: 6 }}>
+ lg={{ span: 6 }}
+ >
@@ -267,16 +275,18 @@ export function MemePageArt(props: {
| Mint Date |
{printMintDate(props.nft.mint_date)} |
-
- | File Type |
- {getFileTypeFromMetadata(props.nft.metadata)} |
-
-
- | Dimensions |
-
- {getDimensionsFromMetadata(props.nft.metadata)}
- |
-
+ {fileType && (
+
+ | File Type |
+ {fileType} |
+
+ )}
+ {dimensions && (
+
+ | Dimensions |
+ {dimensions} |
+
+ )}
@@ -287,7 +297,8 @@ export function MemePageArt(props: {
xs={{ span: 12 }}
sm={{ span: 6 }}
md={{ span: 6 }}
- lg={{ span: 6 }}>
+ lg={{ span: 6 }}
+ >
@@ -305,7 +316,8 @@ export function MemePageArt(props: {
? undefined
: "noopener noreferrer"
}
- className={styles["distributionPlanLink"]}>
+ className={styles["distributionPlanLink"]}
+ >
Distribution Plan
@@ -338,7 +350,8 @@ export function MemePageArt(props: {
+ }}
+ >
@@ -376,7 +389,8 @@ export function MemePageArt(props: {
xs={{ span: 12 }}
sm={{ span: 6 }}
md={{ span: 6 }}
- lg={{ span: 6 }}>
+ lg={{ span: 6 }}
+ >
@@ -388,7 +402,8 @@ export function MemePageArt(props: {
xs={{ span: 12 }}
sm={{ span: 10 }}
md={{ span: 8 }}
- lg={{ span: 6 }}>
+ lg={{ span: 6 }}
+ >
@@ -431,7 +446,8 @@ export function MemePageArt(props: {
xs={{ span: 12 }}
sm={{ span: 6 }}
md={{ span: 6 }}
- lg={{ span: 6 }}>
+ lg={{ span: 6 }}
+ >
@@ -443,7 +459,8 @@ export function MemePageArt(props: {
xs={{ span: 12 }}
sm={{ span: 10 }}
md={{ span: 8 }}
- lg={{ span: 6 }}>
+ lg={{ span: 6 }}
+ >
{props.nft.metadata.attributes
diff --git a/contexts/SeizeSettingsContext.tsx b/contexts/SeizeSettingsContext.tsx
index 60c7c95950..74ad095020 100644
--- a/contexts/SeizeSettingsContext.tsx
+++ b/contexts/SeizeSettingsContext.tsx
@@ -93,7 +93,9 @@ export const SeizeSettingsProvider = ({
);
useEffect(() => {
- loadSeizeSettings();
+ // Initial load failures are exposed through `loadError`; do not leak
+ // them as unhandled promise rejections from the mount effect.
+ loadSeizeSettings().catch(() => undefined);
return () => {
isMountedRef.current = false;
diff --git a/helpers/nft.helpers.ts b/helpers/nft.helpers.ts
index 55c6a1e776..6a4d7c52ad 100644
--- a/helpers/nft.helpers.ts
+++ b/helpers/nft.helpers.ts
@@ -6,16 +6,87 @@ import {
} from "@/constants/constants";
import { numberWithCommas } from "./Helpers";
-export function getFileTypeFromMetadata(metadata: any) {
- return metadata.animation_details?.format ?? metadata.image_details.format;
+interface NFTMediaDetails {
+ readonly format?: string | null | undefined;
+ readonly width?: number | null | undefined;
+ readonly height?: number | null | undefined;
}
-export function getDimensionsFromMetadata(metadata: any) {
- return `${numberWithCommas(
- metadata.animation_details?.width ?? metadata.image_details.width
- )} x ${numberWithCommas(
- metadata.animation_details?.height ?? metadata.image_details.height
- )}`;
+type NFTMediaMetadata =
+ | {
+ readonly animation_details?: NFTMediaDetails | null | undefined;
+ readonly image_details?: NFTMediaDetails | null | undefined;
+ }
+ | null
+ | undefined;
+
+const getMediaFormat = (
+ details: NFTMediaDetails | null | undefined
+): string | null => {
+ const rawFormat = details?.format;
+ if (typeof rawFormat !== "string") {
+ return null;
+ }
+
+ const format = rawFormat.trim();
+ return format.length > 0 ? format : null;
+};
+
+const isValidDimension = (
+ value: number | null | undefined
+): value is number => {
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
+};
+
+const getMediaDimensions = (
+ details: NFTMediaDetails | null | undefined
+): string | null => {
+ const width = details?.width;
+ const height = details?.height;
+
+ if (!isValidDimension(width) || !isValidDimension(height)) {
+ return null;
+ }
+
+ return `${numberWithCommas(width)} x ${numberWithCommas(height)}`;
+};
+
+export function getAnimationFileTypeFromMetadata(
+ metadata: NFTMediaMetadata
+): string | null {
+ return getMediaFormat(metadata?.animation_details);
+}
+
+export function getImageFileTypeFromMetadata(
+ metadata: NFTMediaMetadata
+): string | null {
+ return getMediaFormat(metadata?.image_details);
+}
+
+export function getAnimationDimensionsFromMetadata(
+ metadata: NFTMediaMetadata
+): string | null {
+ return getMediaDimensions(metadata?.animation_details);
+}
+
+export function getImageDimensionsFromMetadata(
+ metadata: NFTMediaMetadata
+): string | null {
+ return getMediaDimensions(metadata?.image_details);
+}
+
+export function getFileTypeFromMetadata(metadata: NFTMediaMetadata) {
+ return (
+ getAnimationFileTypeFromMetadata(metadata) ??
+ getImageFileTypeFromMetadata(metadata)
+ );
+}
+
+export function getDimensionsFromMetadata(metadata: NFTMediaMetadata) {
+ return (
+ getAnimationDimensionsFromMetadata(metadata) ??
+ getImageDimensionsFromMetadata(metadata)
+ );
}
export const isMemesEcosystemContract = (contract: string): boolean => {
diff --git a/openapi.yaml b/openapi.yaml
index 9068848dad..44f2db148d 100644
--- a/openapi.yaml
+++ b/openapi.yaml
@@ -3412,7 +3412,7 @@ paths:
tags:
- Ratings
summary: >-
- Change REP rating of multiple targets and categories in one go. Targets
+ Change REP rating of multiple targets and categories in one go. Targets
final REP value will be the value you supply here. If you supply
multiple addresses for one consolidation group then those amounts will
be summed together.
@@ -7236,7 +7236,7 @@ components:
$ref: "#/components/schemas/ApiWaveParticipationRequirement"
required_metadata:
description: |
- The metadata that must be provided by the participant.
+ The metadata that must be provided by the participant.
Empty array if nothing is required.
type: array
items:
@@ -10107,13 +10107,6 @@ components:
$ref: "#/components/schemas/ApiCreateNewWaveChatConfig"
wave:
$ref: "#/components/schemas/ApiCreateWaveConfig"
- ApiSetPinnedDropRequest:
- type: object
- required:
- - drop_id
- properties:
- drop_id:
- type: string
ApiUploadItem:
type: object
required:
@@ -10319,7 +10312,7 @@ components:
type: string
ApiWaveCreditScope:
description: |
- The scope of the credit.
+ The scope of the credit.
* WAVE - Credit is spendable across all drops in wave.
enum:
- WAVE
@@ -10739,7 +10732,7 @@ components:
nullable: true
required_metadata:
description: |
- The metadata that must be provided by the participant.
+ The metadata that must be provided by the participant.
Empty array if nothing is required.
type: array
items: