mirror of
https://github.com/civitai/civitai.git
synced 2026-09-20 22:08:18 +08:00
00a1fae7a7
The image detail page served `original=true` to everyone, which is both the largest image on the site and the reason lossless had little left to sell — anyone could see the untouched file for free. It now follows the viewer like every other browsing surface; the download button is what still hands over the stored file. Width is the SOURCE's own size snapped DOWN, not a fixed large number, because the cacher upscales. Measured on an 832x1216 original: `width=1600` returns a real 1600x2338 JPEG of 1,017,924 bytes — interpolated pixels carrying no detail the source had — and even `width=1200` upscales. Snapping down asks for 800: 153,776 bytes optimized against 2,545,486 for the original, at 4% less linear resolution. `snapWidthDownToCommonSize` exists for that; `snapWidthToCommonSize` rounds up, which is right for a layout box and wrong for a source size. The public comic reader is the other gap, and a larger one than it looks: the chapter reader, the overview hero and the chapter thumbnails all build URLs with raw `getEdgeUrl`, which has never resolved the preference, so every panel shipped unoptimized at width=1200 to every viewer. Resolved at component level rather than in the render helper, since `renderPanel` is a plain function and cannot hold a hook. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
90 lines
3.0 KiB
TypeScript
90 lines
3.0 KiB
TypeScript
import { useMemo } from 'react';
|
|
import { useCurrentUser } from '~/hooks/useCurrentUser';
|
|
import { useBrowsingSettings } from '~/providers/BrowserSettingsProvider';
|
|
import {
|
|
getEdgeUrl,
|
|
getEdgeUrlSrcSet,
|
|
getInferredMediaType,
|
|
resolveOptimized,
|
|
type EdgeUrlProps,
|
|
} from '~/client-utils/edge-url';
|
|
import { useMediaQuality } from '~/hooks/useMediaQuality';
|
|
|
|
// The pure URL builder now lives in `~/client-utils/edge-url` (React-free, so server
|
|
// modules can resolve a delivery URL without pulling hooks/providers into their import
|
|
// graph). Re-exported here so every existing consumer of this module is unaffected.
|
|
export {
|
|
COMMON_IMAGE_WIDTHS,
|
|
MAX_EDGE_WIDTH,
|
|
SRCSET_DPR,
|
|
getEdgeUrl,
|
|
getEdgeUrlSrcSet,
|
|
getInferredMediaType,
|
|
resolveOptimized,
|
|
resolvesToOriginal,
|
|
snapWidthDownToCommonSize,
|
|
snapWidthToCommonSize,
|
|
toMediaQuality,
|
|
} from '~/client-utils/edge-url';
|
|
export type { EdgeUrlProps, MediaQuality } from '~/client-utils/edge-url';
|
|
|
|
/** @param hiDpi also emit a variant sized for a 2x display. The format is the viewer's. */
|
|
export function useEdgeUrl(
|
|
src: string,
|
|
options: Omit<EdgeUrlProps, 'src'> | undefined,
|
|
hiDpi?: boolean
|
|
) {
|
|
const currentUser = useCurrentUser();
|
|
const { quality } = useMediaQuality();
|
|
const inferredType = getInferredMediaType(src, options);
|
|
let type = options?.type ?? inferredType;
|
|
|
|
if (!src || src.startsWith('http') || src.startsWith('blob'))
|
|
return { url: src, srcSet: undefined, type: inferredType };
|
|
|
|
let { anim, transcode } = options ?? {};
|
|
|
|
if (inferredType === 'video' && type === 'image') {
|
|
transcode = true;
|
|
anim = false;
|
|
} else if (type === 'video') {
|
|
transcode = true;
|
|
anim = anim ?? true;
|
|
}
|
|
|
|
if (!anim) type = 'image';
|
|
// Decided in `edge-url` so anything that has to reproduce this outside React (the
|
|
// announcement banner health monitor) cannot drift from it.
|
|
const optimized = resolveOptimized({
|
|
optimized: options?.optimized,
|
|
width: options?.width,
|
|
height: options?.height,
|
|
original: options?.original,
|
|
// Video is transcoded to an MP4/WebM for everyone, so lossless has nothing to buy here.
|
|
// Keyed off the SOURCE media, which covers a poster derived from the video itself; a poster
|
|
// built from a separate `thumbnailUrl` is an image uuid and does NOT reach this, so
|
|
// `EdgeVideo` asks for `optimized` explicitly there.
|
|
quality: inferredType === 'video' ? 'compressed' : quality,
|
|
});
|
|
|
|
const resolved = {
|
|
...options,
|
|
anim,
|
|
transcode,
|
|
type,
|
|
optimized: optimized ? true : undefined,
|
|
};
|
|
|
|
return {
|
|
url: getEdgeUrl(src, resolved),
|
|
srcSet: hiDpi ? getEdgeUrlSrcSet(src, resolved) : undefined,
|
|
type,
|
|
};
|
|
}
|
|
|
|
export function useGetEdgeUrl(src?: string | null, options: Omit<EdgeUrlProps, 'src'> = {}) {
|
|
const autoplayGifs = useBrowsingSettings((x) => x.autoplayGifs);
|
|
if (!options.anim && !autoplayGifs) options.anim = false;
|
|
return useMemo(() => (src ? getEdgeUrl(src, options) : undefined), [autoplayGifs, src]);
|
|
}
|