import { memo, useState } from 'react'; import Button from '../../../../common/components/buttons/Button'; import Input from '../../../../common/components/input/input/Input'; import { getRememberedAspectRatio, rememberAspectRatio } from '../../../../common/utils/imageDimensions'; import style from './EditableImage.module.scss'; interface EditableImageProps { initialValue: string; fieldLabel: string; readOnly?: boolean; updateValue: (newValue: string) => void; } export default memo(EditableImage); /** * An image is either hosted somewhere else * or served by ontime itself (eg. a file placed in the user folder) */ export function isValidImageSource(value: string): boolean { return value.startsWith('http://') || value.startsWith('https://') || value.startsWith('/'); } function EditableImage({ initialValue, fieldLabel, readOnly, updateValue }: EditableImageProps) { const [isRejected, setIsRejected] = useState(false); /** we keep track of the source itself, so that the state follows the value being shown */ const [failedSource, setFailedSource] = useState(null); const [loadedSource, setLoadedSource] = useState(null); const handleUpdate = (newValue: string) => { const value = newValue.trim(); if (value === initialValue) { setIsRejected(false); return; } if (value !== '' && !isValidImageSource(value)) { setIsRejected(true); return; } setIsRejected(false); updateValue(value); }; const openInNewTab = () => { if (initialValue) { window.open(initialValue, '_blank', 'noopener,noreferrer'); } }; if (!initialValue && readOnly) { return null; } if (!initialValue) { return ( <> setIsRejected(false)} onBlur={(event) => handleUpdate(event.currentTarget.value)} onKeyDown={(event) => { if (event.key === 'Enter') { handleUpdate(event.currentTarget.value); } }} /> {isRejected && Use a link (https://...) or a file in ontime (/user/...)} ); } /** * The cuesheet is virtualised: rows are unmounted once they leave the viewport. * When the row comes back, we reserve the space the image took * so that the table does not shift while the browser makes it available. */ const knownAspectRatio = getRememberedAspectRatio(initialValue); const isLoaded = loadedSource === initialValue; return (
{!readOnly && (
)} {failedSource === initialValue ? ( Could not load image ) : ( {fieldLabel} { rememberAspectRatio(initialValue, event.currentTarget); setLoadedSource(initialValue); }} onError={() => setFailedSource(initialValue)} /** until the image is available, we reserve the space it took the last time we saw it */ style={!isLoaded && knownAspectRatio !== null ? { aspectRatio: knownAspectRatio, width: '100%' } : undefined} /> )}
); }