Shape, on a photograph block
Choose 16:9 and the band gets shorter than the picture — the ratio becomes a slot
shape and the site serves a rendition cut to it, rather than a whole photograph
scaled down by the browser.
`setContent` writes the whole content object, because the API replaces it rather
than merging: a PATCH with `{ ratio }` alone would drop the url.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -62,7 +62,7 @@ export type CanvasComponent = {
|
|||||||
would then need a cast, and the point of naming them is that the compiler knows `free-text` holds
|
would then need a cast, and the point of naming them is that the compiler knows `free-text` holds
|
||||||
`html` and `gallery` holds `galleryId`.
|
`html` and `gallery` holds `galleryId`.
|
||||||
*/
|
*/
|
||||||
content?: { html?: string; galleryId?: number | null; url?: string };
|
content?: { html?: string; galleryId?: number | null; url?: string; ratio?: number; crop?: string };
|
||||||
padding?: Record<string, { x?: number; y?: number }>;
|
padding?: Record<string, { x?: number; y?: number }>;
|
||||||
};
|
};
|
||||||
export type CanvasSection = {
|
export type CanvasSection = {
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ export const PageBuilderPanel = () => {
|
|||||||
remove: (id: number) => void;
|
remove: (id: number) => void;
|
||||||
addComponent: (section: CanvasSection, column: number) => void;
|
addComponent: (section: CanvasSection, column: number) => void;
|
||||||
editComponent: (component: CanvasComponent) => void;
|
editComponent: (component: CanvasComponent) => void;
|
||||||
|
setContent: (component: CanvasComponent, content: Record<string, unknown>) => Promise<void>;
|
||||||
removeComponent: (component: CanvasComponent) => void;
|
removeComponent: (component: CanvasComponent) => void;
|
||||||
spaceSection: (section: CanvasSection) => void;
|
spaceSection: (section: CanvasSection) => void;
|
||||||
spaceComponent: (component: CanvasComponent) => void;
|
spaceComponent: (component: CanvasComponent) => void;
|
||||||
@@ -102,6 +103,7 @@ export const PageBuilderPanel = () => {
|
|||||||
remove: () => {},
|
remove: () => {},
|
||||||
addComponent: () => {},
|
addComponent: () => {},
|
||||||
editComponent: () => {},
|
editComponent: () => {},
|
||||||
|
setContent: async () => {},
|
||||||
removeComponent: () => {},
|
removeComponent: () => {},
|
||||||
spaceSection: () => {},
|
spaceSection: () => {},
|
||||||
spaceComponent: () => {},
|
spaceComponent: () => {},
|
||||||
@@ -471,6 +473,28 @@ export const PageBuilderPanel = () => {
|
|||||||
/* Held so the effect that reads the URL can call it without depending on its identity. */
|
/* Held so the effect that reads the URL can call it without depending on its identity. */
|
||||||
const editComponentRef = useRef<(component: CanvasComponent) => void>(() => {});
|
const editComponentRef = useRef<(component: CanvasComponent) => void>(() => {});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a block's whole `content`.
|
||||||
|
*
|
||||||
|
* Whole, because the API replaces it rather than merging — so every caller sends what is already
|
||||||
|
* there alongside what it is changing. That is the contract for a jsonb column somebody can put
|
||||||
|
* anything in: a merge would need the server to know which keys are meaningful, and it does not.
|
||||||
|
*/
|
||||||
|
const setContent = async (component: CanvasComponent, content: Record<string, unknown>) => {
|
||||||
|
const owner = sections.find((section) => section.components.some((one) => one.id === component.id));
|
||||||
|
if (!page || !owner) return;
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
await clientRef.current.patch(
|
||||||
|
`/fofos-admin/pages/${page.id}/sections/${owner.id}/components/${component.id}`,
|
||||||
|
{ content },
|
||||||
|
);
|
||||||
|
setRendered((n) => n + 1);
|
||||||
|
} catch (failure) {
|
||||||
|
setError(readableError(failure));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const editComponent = (component: CanvasComponent) => {
|
const editComponent = (component: CanvasComponent) => {
|
||||||
const owner = sections.find((section) => section.components.some((one) => one.id === component.id));
|
const owner = sections.find((section) => section.components.some((one) => one.id === component.id));
|
||||||
if (!owner) return;
|
if (!owner) return;
|
||||||
@@ -728,6 +752,44 @@ export const PageBuilderPanel = () => {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/*
|
||||||
|
The shape of the slot, for a PHOTOGRAPH.
|
||||||
|
|
||||||
|
It is how you make a band shorter than the picture in it: the ratio becomes a slot
|
||||||
|
shape, and the site asks av-club for a rendition cut to that — so the bytes served
|
||||||
|
match the space rather than being scaled down by the browser.
|
||||||
|
|
||||||
|
Not offered for a video. There is nothing to crop and a ratio would letterbox it.
|
||||||
|
|
||||||
|
`content` is replaced wholesale by the API, so this sends what is already there
|
||||||
|
alongside the new value — a PATCH with `{ ratio }` alone would drop the url.
|
||||||
|
*/}
|
||||||
|
{block.content?.url && !/\.(mp4|mov|webm|m4v)$/i.test(block.content.url) && (
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<label className="text-xs">Shape</label>
|
||||||
|
<select
|
||||||
|
value={block.content.ratio ? String(block.content.ratio) : ''}
|
||||||
|
onChange={(event) => {
|
||||||
|
const value = event.currentTarget.value;
|
||||||
|
void handlersRef.current.setContent(block, {
|
||||||
|
...block.content,
|
||||||
|
ratio: value ? Number(value) : undefined,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
className="h-8 rounded-md border border-input bg-transparent px-2 text-xs"
|
||||||
|
>
|
||||||
|
<option value="">The picture's own shape</option>
|
||||||
|
<option value={String(21 / 9)}>21:9 — a letterbox band</option>
|
||||||
|
<option value={String(16 / 9)}>16:9</option>
|
||||||
|
<option value={String(3 / 2)}>3:2</option>
|
||||||
|
<option value={String(4 / 3)}>4:3</option>
|
||||||
|
<option value="1">1:1 — square</option>
|
||||||
|
<option value={String(3 / 4)}>3:4 — upright</option>
|
||||||
|
<option value={String(2 / 3)}>2:3 — upright</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/*
|
{/*
|
||||||
What is actually in the block, at the width of the column.
|
What is actually in the block, at the width of the column.
|
||||||
|
|
||||||
@@ -805,6 +867,7 @@ export const PageBuilderPanel = () => {
|
|||||||
remove: (sectionId: number) => void deleteSection(sectionId),
|
remove: (sectionId: number) => void deleteSection(sectionId),
|
||||||
addComponent,
|
addComponent,
|
||||||
editComponent,
|
editComponent,
|
||||||
|
setContent,
|
||||||
removeComponent: (component) => void deleteComponent(component),
|
removeComponent: (component) => void deleteComponent(component),
|
||||||
spaceSection,
|
spaceSection,
|
||||||
spaceComponent,
|
spaceComponent,
|
||||||
|
|||||||
Reference in New Issue
Block a user