"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 { 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,
useLeafletPublicationPage,
} 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 localSrc = 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-[5px]" : "") : ""}
${isFullBleed ? (isLast ? "-mb-4" : nextIsFullBleed ? "-mb-[9px]" : "") : ""}
`;
return (
{localSrc || image.data.local ? (
) : (
)}
{altText !== undefined && !props.preview ? (
) : null}
{!props.preview ? : null}
);
}
const CoverImageButton = (props: { entityID: string }) => {
let { rep } = useReplicache();
let entity_set = useEntitySetContext();
let { data: pubData } = useLeafletPublicationData();
let publicationPage = useLeafletPublicationPage();
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.
// Publication pages (e.g. an About page) don't have cover images, so skip them.
if (
!isFocused ||
!pubData?.publications ||
!entity_set.permissions.write ||
publicationPage
)
return null;
if (coverImage === props.entityID)
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}
)}
);
};