simian 0.2.1

A command-line tool for exploring and implementing Machine Learning algorithms in Rust.
import clsx from 'clsx'
import {
  forwardRef,
  memo,
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState,
} from 'react'
import { createPortal } from 'react-dom'
import { Expand, Loader2, Shrink } from 'lucide-react'

import { Button } from '@/components/ui/button'
import { useEditor } from '@/ui/editor/context'
import { Caption } from '../caption'
import { ImageBaseItemProps } from './types'
import { useImageBlock, useImageBlockElement } from '../context'

function FullscreenImage({ children }: { children: React.ReactNode }) {
  const { editor } = useEditor()

  useEffect(() => {
    const originalStyle = window.getComputedStyle(document.body).overflow
    document.body.style.overflow = 'hidden'
    return () => {
      document.body.style.overflow = originalStyle
    }
  }, [])

  const root =
    typeof window !== 'undefined'
      ? document.getElementById(`image-fullscreen-root-${editor.id}`)
      : null

  if (!root) return null

  return createPortal(children, root)
}

function GridFullscreenImage({ children }: { children: React.ReactNode }) {
  const { blockId } = useImageBlockElement()
  const root =
    typeof window !== 'undefined'
      ? document.getElementById(`image-grid-root-${blockId}`)
      : null

  if (!root) return <>{children}</>

  return createPortal(children, root)
}

export const ImageBaseItem = memo(
  forwardRef<HTMLDivElement, ImageBaseItemProps>(
    (
      {
        isDragging,
        className,
        item,
        onUploadComplete,
        onCaptionChange,
        ...props
      },
      ref,
    ) => {
      const { focus: focusBase, setFocus, itemsLength } = useImageBlockElement()
      const { fileUploadAction } = useImageBlock()
      const fileUpload = fileUploadAction
      const [localUrl, setLocalUrl] = useState<string | null>(null)
      const [isUploading, setIsUploading] = useState(false)

      const wasDraggingRef = useRef(false)

      const focus = focusBase?.id === item.id ? focusBase : null

      const url = useMemo(
        () => localUrl || item.url, // In Simian, url stores the full relative path
        [localUrl, item.url],
      )

      const handleUpload = useCallback(async () => {
        if (!item.upload || item.upload.item) {
          return
        }

        setIsUploading(true)

        try {
          const result = await fileUpload({ file: item.upload.file })

          if (result.data) {
            const dbFile = result.data
            const { upload, ...itemRest } = item

            onUploadComplete({
              item: {
                ...itemRest,
                id: upload.id,
                url: dbFile.url,
                height: 0, // In standard HTML img we might not know it upfront unless we wait for load event
                width: 0,
                mime: dbFile.mime,
              },
              upload,
            })
          }
        } catch (error) {
          console.error(error)
          setLocalUrl(null)
        }

        setIsUploading(false)
      }, [item, onUploadComplete, fileUpload])

      useEffect(() => {
        ;(() => {
          if (item.upload && item.upload.file) {
            const objUrl = URL.createObjectURL(item.upload.file)
            setLocalUrl(objUrl)
          }
        })()
      }, [item])

      useEffect(() => {
        if (isDragging) {
          wasDraggingRef.current = true
        }
      }, [isDragging])

      useEffect(() => {
        // Component mounting logic
        return () => {
          // Unmounting logic
        }
      }, [])

      const imageView = (
        <div
          className={clsx([
            'w-full h-full',
            focus ? '' : 'hidden',
            focus?.mode === 'expand'
              ? 'fixed top-0 left-0 z-[100]'
              : 'absolute inset-0 z-50',
          ])}
          onClick={() => {
            if (focus) {
              setFocus(null)
            }
          }}
        >
          <div
            className={clsx([
              'absolute inset-0 z-2',
              'bg-background/85',
              'backdrop-blur-sm',
              'transition-opacity duration-300',
              'pointer-events-none',
            ])}
          />

          {/* Focused image container */}
          <div className="absolute inset-0 p-4 z-3 w-full h-full flex flex-col">
            <div className="min-h-0 flex items-center justify-center">
              <div
                className="
            relative
            inline-block
            h-full
            max-h-full
            max-w-full
          "
              >
                {/* Expand button INSIDE image bounds */}
                {focus ? (
                  <Button
                    size="icon"
                    className="absolute top-2 left-2 z-40 h-8 w-8"
                    onClick={(e: React.MouseEvent) => {
                      e.stopPropagation()
                      setFocus(
                        focus?.mode === 'expand'
                          ? null // unfocus
                          : { ...focus, mode: 'expand' }, // expand focus
                      )
                    }}
                  >
                    {focus?.mode == 'expand' ? <Shrink /> : <Expand />}
                  </Button>
                ) : null}

                <img
                  alt={item.alt}
                  src={url}
                  className="
                block
                max-h-full
                max-w-full
                object-contain
                rounded-md
              "
                  // className={clsx([
                  //   "max-h-full max-w-full object-contain rounded-md",
                  // ])}
                />
              </div>
            </div>

            <div
              className={clsx([
                'shrink-0 pt-4 pb-2 z-10',
                focus?.mode === 'expand' ? '' : 'hidden',
              ])}
              onClick={(evt) => evt.stopPropagation()}
            >
              <Caption
                itemId={item.id}
                disabled={props.disabled}
                value={item.caption ?? null}
                onValueChange={onCaptionChange}
              />
            </div>
          </div>

          {/* <div className="absolute left-2 top-2 z-4 flex items-center">
        <Button size="icon-sm">
          <Expand />
        </Button>
      </div>

      <img
        alt={item.alt}
        src={url}
        className={clsx([
          "object-contain",
          isFocused && "absolute inset-0 z-3 p-2 flex items-center justify-center w-full h-full",
        ])}
      /> */}
        </div>
      )

      return (
        <>
          <div
            {...props}
            ref={ref}
            className={clsx([
              'relative group',
              'active:cursor-grabbing',
              className,
            ])}
            onPointerDownCapture={() => {
              wasDraggingRef.current = false
            }}
            onDoubleClick={(evt) => {
              evt.preventDefault()
              evt.stopPropagation()
            }}
            onClickCapture={(evt) => {
              if (wasDraggingRef.current) {
                evt.preventDefault()
                evt.stopPropagation()
                wasDraggingRef.current = false
                return
              }
            }}
            onClick={() => {
              // Toggle focus to this item.
              setFocus(focus ? null : { id: item.id })
            }}
          >
            <div
              className={clsx([
                'absolute top-2 left-2 z-1',
                'hidden group-hover:block',
              ])}
            >
              <Button
                variant="default"
                size="icon"
                className="h-8 w-8"
                onClick={(e: React.MouseEvent) => {
                  e.preventDefault()
                  e.stopPropagation()

                  setFocus({ ...(focus ?? { id: item.id }), mode: 'expand' })
                }}
              >
                <Expand />
              </Button>
            </div>

            <img
              className={clsx([
                'w-full h-full transition-all duration-300 object-cover',
              ])}
              width={item.width}
              height={item.height}
              src={url || ''}
              alt={item.alt ?? ''}
              onLoad={() => {
                handleUpload()
              }}
            />

            {/* Loading Skeleton Overlay */}
            {isUploading && (
              <div className="absolute inset-0 bg-black/40 backdrop-blur-[1px] flex flex-col items-center justify-center z-10">
                <Loader2 className="w-8 h-8 text-white animate-spin" />
                <span className="text-xs text-white font-medium mt-2">
                  Uploading...
                </span>
              </div>
            )}
          </div>

          {focus?.mode === 'expand' ? (
            <FullscreenImage>{imageView}</FullscreenImage>
          ) : focus && itemsLength > 1 ? (
            <GridFullscreenImage>{imageView}</GridFullscreenImage>
          ) : null}
        </>
      )
    },
  ),
)

ImageBaseItem.displayName = 'ImageBaseItem'