Skip to content

Commit a4eb471

Browse files
guitavanoclaudevibe-dexcoderabbitai[bot]
authored
feat(image): add defaultImageQuality config to website mod.ts (#1585)
* feat(image): add defaultImageQuality config to website mod.ts Allow sites to configure a default image quality (low/medium/high/original) from the website app settings, applied as fallback when components don't set an explicit quality prop. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(image): remove "original" from defaultImageQuality and fallback quality from src URL - Add DefaultQualityOptions type excluding "original" for the default config - Extract quality param from originalSrc URL as fallback when not set via prop Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(image): prevent URL crash on relative paths and remove unused FeatureFlags field Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Update website/components/Image.tsx Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update Image.tsx * docs(image): clarify quality types and context purpose Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Dex <fernandofrizzatti@gmail.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1 parent 4e73c28 commit a4eb471

4 files changed

Lines changed: 40 additions & 10 deletions

File tree

website/components/Image.tsx

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { Head, IS_BROWSER } from "$fresh/runtime.ts";
22
import type { JSX } from "preact";
3+
import { createContext } from "preact";
34
import { forwardRef } from "preact/compat";
5+
import { useContext } from "preact/hooks";
46

57
const DEFAULT_CDN_HOST = "https://decoims.com";
68

@@ -72,8 +74,19 @@ const bypassDecoImageOptimization = () =>
7274
? (globalThis as any).DECO?.featureFlags?.bypassDecoImageOptimization
7375
: Deno.env.get("BYPASS_DECO_IMAGE_OPTIMIZATION") === "true";
7476

77+
/** Quality options available per component (includes "original" = 100%). */
7578
export type QualityOptions = "low" | "medium" | "high" | "original"; // 60% - 70% - 80% - 100%
7679

80+
/** Quality options for the site-wide default in mod.ts — "original" is excluded
81+
* because a global 100% quality default would hurt performance. */
82+
export type DefaultQualityOptions = "low" | "medium" | "high";
83+
84+
/** Provides the site-wide default quality to Image/Picture components.
85+
* Typed as QualityOptions because components may override with "original". */
86+
export const DefaultImageQualityContext = createContext<
87+
QualityOptions | undefined
88+
>(undefined);
89+
7790
interface OptimizationOptions {
7891
originalSrc: string;
7992
width: number;
@@ -222,7 +235,9 @@ export const getOptimizedMediaUrl = (opts: OptimizationOptions) => {
222235
params.set("fit", fit);
223236
params.set("width", `${width}`);
224237
height && params.set("height", `${height}`);
225-
quality && params.set("quality", quality);
238+
const srcQuality = quality ||
239+
new URL(originalSrc, "https://a.com").searchParams.get("quality");
240+
srcQuality && params.set("quality", srcQuality);
226241

227242
// Strip known CDN prefixes so the worker can hit GCS directly instead of
228243
// doing an absolute-URL hop. Anything left (path + any query string —
@@ -302,6 +317,8 @@ export const getEarlyHintFromSrcProps = (srcProps: {
302317

303318
const Image = forwardRef<HTMLImageElement, Props>((props, ref) => {
304319
const { preload, loading = "lazy" } = props;
320+
const defaultQuality = useContext(DefaultImageQualityContext);
321+
const quality = props.quality ?? defaultQuality;
305322

306323
const shouldSetEarlyHint = !!props.setEarlyHint && preload;
307324
const srcSet = props.srcSet ??
@@ -311,7 +328,7 @@ const Image = forwardRef<HTMLImageElement, Props>((props, ref) => {
311328
props.height,
312329
props.fit,
313330
shouldSetEarlyHint ? FACTORS.slice(-1) : FACTORS,
314-
props.quality,
331+
quality,
315332
);
316333

317334
const linkProps = srcSet &&
@@ -337,7 +354,7 @@ const Image = forwardRef<HTMLImageElement, Props>((props, ref) => {
337354
height: props.height,
338355
fetchpriority: props.fetchPriority,
339356
src: props.src,
340-
quality: props.quality,
357+
quality,
341358
}),
342359
);
343360
}

website/components/Picture.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { ComponentChildren, createContext, JSX } from "preact";
44
import { Head, IS_BROWSER } from "$fresh/runtime.ts";
55

66
import {
7+
DefaultImageQualityContext,
78
FACTORS,
89
getEarlyHintFromSrcProps,
910
getSrcSet,
@@ -39,6 +40,8 @@ type SourceProps =
3940
export const Source = forwardRef<HTMLSourceElement, SourceProps>(
4041
(props, ref) => {
4142
const { preload } = useContext(Context);
43+
const defaultQuality = useContext(DefaultImageQualityContext);
44+
const quality = props.quality ?? defaultQuality;
4245

4346
const shouldSetEarlyHint = !!props.setEarlyHint && preload;
4447
const srcSet = getSrcSet(
@@ -47,7 +50,7 @@ export const Source = forwardRef<HTMLSourceElement, SourceProps>(
4750
props.height,
4851
undefined,
4952
shouldSetEarlyHint ? FACTORS.slice(-1) : FACTORS,
50-
props.quality,
53+
quality,
5154
);
5255
const linkProps = {
5356
imagesrcset: srcSet,
@@ -68,7 +71,7 @@ export const Source = forwardRef<HTMLSourceElement, SourceProps>(
6871
height: props.height,
6972
fetchpriority: props.fetchPriority,
7073
src: props.src,
71-
quality: props.quality,
74+
quality,
7275
}),
7376
);
7477
}

website/mod.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import "./utils/unhandledRejection.ts";
22
import type { Props as Seo } from "./components/Seo.tsx";
3+
import type { DefaultQualityOptions } from "./components/Image.tsx";
34
import { Routes } from "./flags/audience.ts";
45
import { TextReplace } from "./handlers/proxy.ts";
56
import manifest, { Manifest } from "./manifest.gen.ts";
@@ -143,6 +144,12 @@ export interface Props {
143144
*/
144145
sendToClickHouse?: boolean;
145146

147+
/**
148+
* @title Default Image Quality
149+
* @description The default quality for images when not explicitly set per component
150+
*/
151+
defaultImageQuality?: DefaultQualityOptions;
152+
146153
/**
147154
* @title Disable image/asset proxy for this site
148155
* @description Disable image/asset proxy for this site

website/pages/Page.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
import { logger } from "@deco/deco/o11y";
1717
import { Component, JSX } from "preact";
1818
import ErrorPageComponent from "../../utils/defaultErrorPage.tsx";
19+
import { DefaultImageQualityContext } from "../components/Image.tsx";
1920
import OneDollarStats from "../components/OneDollarStats.tsx";
2021
import Events from "../components/Events.tsx";
2122
import { SEOSection } from "../components/Seo.tsx";
@@ -106,13 +107,14 @@ function Page(
106107
seo,
107108
unindexedDomain,
108109
avoidRedirectingToEditor,
110+
defaultImageQuality,
109111
}: SectionProps<typeof loader>,
110112
): JSX.Element {
111113
const context = Context.active();
112114
const site = { id: context.siteId, name: context.site };
113115
const deco = useDeco();
114116
return (
115-
<>
117+
<DefaultImageQualityContext.Provider value={defaultImageQuality}>
116118
{unindexedDomain && (
117119
<Head>
118120
<meta name="robots" content="noindex, nofollow" />
@@ -148,7 +150,7 @@ function Page(
148150
)}
149151
{sections?.map(renderSection)}
150152
</ErrorBoundary>
151-
</>
153+
</DefaultImageQualityContext.Provider>
152154
);
153155
}
154156

@@ -183,21 +185,22 @@ export const loader = async (
183185
devMode,
184186
unindexedDomain,
185187
avoidRedirectingToEditor: ctx.avoidRedirectingToEditor,
188+
defaultImageQuality: ctx.defaultImageQuality,
186189
};
187190
};
188191
export function Preview(props: SectionProps<typeof loader>) {
189-
const { sections, seo } = props;
192+
const { sections, seo, defaultImageQuality } = props;
190193
const deco = useDeco();
191194
return (
192-
<>
195+
<DefaultImageQualityContext.Provider value={defaultImageQuality}>
193196
<Head>
194197
<meta name="robots" content="noindex, nofollow" />
195198
</Head>
196199

197200
{seo && renderSection(seo)}
198201
<Events deco={deco} />
199202
{sections?.map(renderSection)}
200-
</>
203+
</DefaultImageQualityContext.Provider>
201204
);
202205
}
203206
const PAGE_NOT_FOUND = -1;

0 commit comments

Comments
 (0)