"use client"; import { useEntity, useReplicache } from "src/replicache"; import { BlockProps, BlockLayout } from "./Block"; import { useIsBlockSelected } from "src/useUIState"; import Image from "next/image"; import { useEntitySetContext } from "components/EntitySetProvider"; import { addImage, localImages } from "src/utils/addImage"; import { addBlockBelow } from "src/utils/addBlockBelow"; import { elementId } from "src/utils/elementId"; import { useEffect, useState } from "react"; import { BlockImageSmall } from "components/Icons/BlockImageSmall"; import { ImageAltButton } from "./ImageAltButton"; import { ImageOptions } from "./ImageOptions"; import { ImageGalleryLightbox, EditorLightboxSlide, } from "./ImageGalleryBlock/ImageGalleryLightbox"; import { getPostImageEntities } from "./ImageGalleryBlock/getPostImages"; export function ImageBlock(props: BlockProps & { preview?: boolean }) { let { rep, undoManager } = useReplicache(); let image = useEntity(props.entityID, "block/image"); let entity_set = useEntitySetContext(); let isSelected = useIsBlockSelected(props.entityID); let isFullBleed = useEntity(props.entityID, "image/full-bleed")?.data.value; let maxWidth = useEntity(props.entityID, "image/max-width")?.data.value; // Live width while the settings slider is being dragged, before it commits to // the fact; null hands rendering back to the committed value. let [previewWidth, setPreviewWidth] = useState(null); let isFirst = props.previousBlock === null; let isLast = props.nextBlock === null; let altText = useEntity(props.entityID, "image/alt")?.data.value; // Snapshot of every image in the post, taken the first time the lightbox // opens, so it can page through them all (gallery images included) starting on // the one that was opened. let [lightbox, setLightbox] = useState<{ ids: string[]; index: number; } | null>(null); // Writers select the block first; a second click on the image opens the // lightbox. Readers (no write permission) open it on the first click. let canOpenLightbox = !props.preview && (!entity_set.permissions.write || !!isSelected); let openLightbox = () => { let ids = rep ? getPostImageEntities(rep, props.parent) : []; let index = ids.indexOf(props.entityID); // Fall back to just this image if it isn't in the gathered list (e.g. on the // canvas, or before the write has settled). if (index === -1) { ids = [props.entityID]; index = 0; } setLightbox({ ids, index }); }; let nextIsFullBleed = useEntity( props.nextBlock && props.nextBlock.entityID, "image/full-bleed", )?.data.value; let prevIsFullBleed = useEntity( props.previousBlock && props.previousBlock.entityID, "image/full-bleed", )?.data.value; useEffect(() => { if (props.preview) return; let input = document.getElementById(elementId.block(props.entityID).input); if (isSelected) { input?.focus(); } else { input?.blur(); } }, [isSelected, props.preview, props.entityID]); const handleImageUpload = async (file: File) => { if (!rep) return; await undoManager.withUndoGroup(async () => { let entity = props.entityID; if (!entity) { entity = await addBlockBelow(rep, { parent: props.parent, position: props.position, nextPosition: props.nextPosition, permission_set: entity_set.set, type: "text", }); } await rep.mutate.assertFact({ entity, attribute: "block/type", data: { type: "block-type-union", value: "image" }, }); await addImage(file, rep, { entityID: entity, attribute: "block/image", }); }); }; if (!image) { if (!entity_set.permissions.write) return null; return ( ); } let localSrc = localImages.get(image.data.src); let blockClassName = ` relative group/image border-transparent! p-0! ${ isFullBleed ? "w-[calc(100%+28px)]! sm:w-[calc(100%+36px)]! -mx-[14px] sm:-mx-[18px] rounded-[0px]! sm:outline-offset-[-16px]! -outline-offset[-12px]!" : "w-fit!" } ${isFullBleed ? (isFirst ? "-mt-3 sm:-mt-4" : prevIsFullBleed ? "-mt-[5px]" : "") : ""} ${isFullBleed ? (isLast ? "-mb-4" : nextIsFullBleed ? "-mb-[9px]" : "") : ""} `; let displayWidth = previewWidth ?? maxWidth; let imageStyle = displayWidth && !isFullBleed ? { width: displayWidth, maxWidth: "100%", height: "auto" as const } : undefined; return ( ) : undefined } > {!props.preview && ( { if (i === null) setLightbox(null); }} renderSlide={(i) => lightbox ? : null } /> )} {!props.preview ? ( ) : null} ); }