import React, { useState } from 'react'; export type ImageSrcResolver = (path: string, base?: string) => string; /** * Default image URL resolver — Plannotator's local server behavior, verbatim. * Remote URLs pass through; local paths resolve through `/api/image`. */ const defaultImageSrcResolver: ImageSrcResolver = (path, base) => { if (path.startsWith('http://') || path.startsWith('https://')) { return path; // Remote URL, use directly } let url = `/api/image?path=${encodeURIComponent(path)}`; if (base && !path.startsWith('/')) { url += `&base=${encodeURIComponent(base)}`; } return url; }; // Module-level resolver, stable identity. Defaults to Plannotator's behavior so // callers and consumers are unchanged. A host (e.g. Workspaces) calls // `setImageSrcResolver` once at startup to resolve images via its own backend. let imageSrcResolver: ImageSrcResolver = defaultImageSrcResolver; /** Override how image paths resolve to URLs. Call once at app startup. */ export const setImageSrcResolver = (resolver: ImageSrcResolver): void => { imageSrcResolver = resolver; }; /** Reset to the default (Plannotator local) resolver. Mainly for tests. */ export const resetImageSrcResolver = (): void => { imageSrcResolver = defaultImageSrcResolver; }; /** * Get the display URL for an image path or URL. * Delegates to the active resolver (default = Plannotator `/api/image`). */ export const getImageSrc = (path: string, base?: string): string => imageSrcResolver(path, base); interface ImageThumbnailProps { path: string; size?: 'sm' | 'md' | 'lg'; onClick?: () => void; onRemove?: () => void; showRemove?: boolean; className?: string; } const SIZES = { sm: 'w-6 h-6', md: 'w-10 h-10', lg: 'w-16 h-16', }; export const ImageThumbnail: React.FC = ({ path, size = 'md', onClick, onRemove, showRemove = true, className = '', }) => { const [error, setError] = useState(false); const [loading, setLoading] = useState(true); const sizeClass = SIZES[size]; return (
{loading && !error && (
)} {error ? (
) : ( Attachment setLoading(false)} onError={() => { setError(true); setLoading(false); }} className={`${sizeClass} rounded object-cover border border-border ${onClick ? 'cursor-pointer hover:opacity-80' : ''}`} /> )} {/* Remove button */} {showRemove && onRemove && ( )}
); };