"use client"; import { useEntity, useReplicache } from "src/replicache"; import { BlockProps, BlockLayout } from "./Block"; import { useUIState } from "src/useUIState"; import Image from "next/image"; import { v7 } from "uuid"; import { useEntitySetContext } from "components/EntitySetProvider"; import { generateKeyBetween } from "fractional-indexing"; import { addImage, localImages } from "src/utils/addImage"; import { elementId } from "src/utils/elementId"; import { createContext, useContext, useEffect, useState } from "react"; import { BlockImageSmall } from "components/Icons/BlockImageSmall"; import { Popover } from "components/Popover"; import { theme } from "tailwind.config"; import { EditTiny } from "components/Icons/EditTiny"; import { AsyncValueAutosizeTextarea } from "components/utils/AutosizeTextarea"; import { set } from "colorjs.io/fn"; import { ImageAltSmall } from "components/Icons/ImageAlt"; import { useLeafletPublicationData } from "components/PageSWRDataProvider"; import { useSubscribe } from "src/replicache/useSubscribe"; import { ImageCoverImage, ImageCoverImageRemove, } from "components/Icons/ImageCoverImage"; import { ButtonPrimary, ButtonSecondary, ButtonTertiary, } from "components/Buttons"; import { CheckTiny } from "components/Icons/CheckTiny"; export function ImageBlock(props: BlockProps & { preview?: boolean }) { let { rep } = useReplicache(); let image = useEntity(props.value, "block/image"); let entity_set = useEntitySetContext(); let isSelected = useUIState((s) => s.selectedBlocks.find((b) => b.value === props.value), ); let isFullBleed = useEntity(props.value, "image/full-bleed")?.data.value; let isFirst = props.previousBlock === null; let isLast = props.nextBlock === null; let altText = useEntity(props.value, "image/alt")?.data.value; let nextIsFullBleed = useEntity( props.nextBlock && props.nextBlock.value, "image/full-bleed", )?.data.value; let prevIsFullBleed = useEntity( props.previousBlock && props.previousBlock.value, "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; let entity = props.entityID; if (!entity) { entity = v7(); await rep?.mutate.addBlock({ parent: props.parent, factID: v7(), permission_set: entity_set.set, type: "text", position: generateKeyBetween(props.position, props.nextPosition), newEntityID: entity, }); } 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 isLocalUpload = localImages.get(image.data.src); let blockClassName = ` relative group/image border-transparent! p-0! w-fit! ${isFullBleed && "-mx-[14px] sm:-mx-[18px] rounded-[0px]! sm:outline-offset-[-16px]! -outline-offset[-12px]!"} ${isFullBleed ? (isFirst ? "-mt-3 sm:-mt-4" : prevIsFullBleed ? "-mt-1" : "") : ""} ${isFullBleed ? (isLast ? "-mb-4" : nextIsFullBleed ? "-mb-2" : "") : ""} `; return ( {isLocalUpload || image.data.local ? ( {altText} ) : ( {altText )} {altText !== undefined && !props.preview ? ( ) : null} {!props.preview ? : null} ); } export const FullBleedSelectionIndicator = () => { return (
); }; export const ImageBlockContext = createContext({ altEditorOpen: false, setAltEditorOpen: (s: boolean) => {}, }); const CoverImageButton = (props: { entityID: string }) => { let { rep } = useReplicache(); let entity_set = useEntitySetContext(); let { data: pubData } = useLeafletPublicationData(); let coverImage = useSubscribe(rep, (tx) => tx.get("publication_cover_image"), ); let isFocused = useUIState( (s) => s.focusedEntity?.entityID === props.entityID, ); // Only show if focused, in a publication, has write permissions, and no cover image is set if (!isFocused || !pubData?.publications || !entity_set.permissions.write) return null; if (coverImage) return ( { e.preventDefault(); e.stopPropagation(); await rep?.mutate.updatePublicationDraft({ cover_image: null, }); }} > Remove Cover Image ); return ( { e.preventDefault(); e.stopPropagation(); await rep?.mutate.updatePublicationDraft({ cover_image: props.entityID, }); }} > Use as Cover Image ); }; const ImageAlt = (props: { entityID: string }) => { let { rep } = useReplicache(); let altText = useEntity(props.entityID, "image/alt")?.data.value; let entity_set = useEntitySetContext(); let setAltEditorOpen = useUIState((s) => s.setOpenPopover); let altEditorOpen = useUIState((s) => s.openPopover === props.entityID); if (!entity_set.permissions.write && altText === "") return null; return (
setAltEditorOpen(altEditorOpen ? null : props.entityID) } > } > {entity_set.permissions.write ? ( { e.currentTarget.setSelectionRange( e.currentTarget.value.length, e.currentTarget.value.length, ); }} onChange={async (e) => { await rep?.mutate.assertFact({ entity: props.entityID, attribute: "image/alt", data: { type: "string", value: e.currentTarget.value }, }); }} placeholder="add alt text..." /> ) : (
{altText}
)}
); };