analytica-frontend-lib
Version:
Repositório público dos componentes utilizados nas plataformas da Analytica Ensino
1 lines • 10.4 kB
Source Map (JSON)
{"version":3,"sources":["../src/components/RichEditor/components/ImageDialog.tsx"],"sourcesContent":["import { useState, useRef, ChangeEvent } from 'react';\nimport Button from '../../Button/Button';\nimport Input from '../../Input/Input';\nimport ImageUpload from '../../ImageUpload/ImageUpload';\nimport Modal from '../../Modal/Modal';\nimport Text from '../../Text/Text';\nimport { LinkIcon } from '@phosphor-icons/react/dist/csr/Link';\nimport { UploadSimpleIcon } from '@phosphor-icons/react/dist/csr/UploadSimple';\n\n/**\n * Matches the 5MB cap enforced by the backend pre-signed URL schema.\n * Keeping it here avoids the client accepting files the server will reject.\n */\nexport const MAX_IMAGE_SIZE = 5 * 1024 * 1024;\n\ntype InputMode = 'file' | 'url';\n\ninterface ImageDialogProps {\n readonly open: boolean;\n readonly onClose: () => void;\n readonly onInsert: (src: string, alt: string) => void;\n /**\n * Optional callback to upload an image file and get back its public URL.\n * If provided, the file upload tab is enabled; otherwise only URL input is\n * available. The library stays agnostic of how the consumer uploads.\n * @param file - The image file selected by the user\n * @returns Promise resolving to the public URL of the uploaded image\n */\n readonly onUploadImage?: (file: File) => Promise<string>;\n}\n\nexport function ImageDialog({\n open,\n onClose,\n onInsert,\n onUploadImage,\n}: ImageDialogProps) {\n const [inputMode, setInputMode] = useState<InputMode>(\n onUploadImage ? 'file' : 'url'\n );\n const [file, setFile] = useState<File | null>(null);\n const [url, setUrl] = useState('');\n const [alt, setAlt] = useState('');\n const [isUploading, setIsUploading] = useState(false);\n const [error, setError] = useState('');\n /** Bumped on close so a resolving upload can tell it has been superseded. */\n const uploadTokenRef = useRef(0);\n\n const resetState = () => {\n setInputMode(onUploadImage ? 'file' : 'url');\n setFile(null);\n setUrl('');\n setAlt('');\n setIsUploading(false);\n setError('');\n };\n\n const handleClose = () => {\n // Invalidates any upload still in flight. The Modal closes on Escape and on\n // its X button, so the user can dismiss the dialog mid-upload; without this\n // the promise would resolve afterwards and insert an image they cancelled.\n uploadTokenRef.current += 1;\n resetState();\n onClose();\n };\n\n const handleFileSelect = (selected: File) => {\n setFile(selected);\n setError('');\n };\n\n const handleSizeError = () => {\n setFile(null);\n setError('A imagem deve ter no máximo 5MB.');\n };\n\n const handleTypeError = () => {\n setFile(null);\n setError('Selecione um arquivo de imagem válido.');\n };\n\n const switchMode = (mode: InputMode) => {\n setInputMode(mode);\n setError('');\n };\n\n const handleInsert = async () => {\n if (inputMode === 'url') {\n if (!url.trim()) return;\n onInsert(url.trim(), alt.trim());\n resetState();\n return;\n }\n\n if (!file || !onUploadImage) return;\n\n setIsUploading(true);\n setError('');\n const token = uploadTokenRef.current;\n\n try {\n // Upload before inserting so the saved HTML never holds a blob: URL,\n // which would break as soon as the page unloads.\n const uploadedUrl = await onUploadImage(file);\n if (token !== uploadTokenRef.current) return;\n onInsert(uploadedUrl, alt.trim());\n resetState();\n } catch (err) {\n if (token !== uploadTokenRef.current) return;\n setError(err instanceof Error ? err.message : 'Erro ao enviar a imagem.');\n } finally {\n setIsUploading(false);\n }\n };\n\n const canInsert = inputMode === 'url' ? !!url.trim() : !!file;\n\n return (\n <Modal\n isOpen={open}\n onClose={handleClose}\n title=\"Inserir imagem\"\n size=\"md\"\n footer={\n <>\n <Button\n variant=\"outline\"\n onClick={handleClose}\n disabled={isUploading}\n >\n Cancelar\n </Button>\n <Button\n variant=\"solid\"\n action=\"primary\"\n onClick={handleInsert}\n disabled={!canInsert || isUploading}\n >\n {isUploading ? 'Enviando...' : 'Inserir imagem'}\n </Button>\n </>\n }\n >\n <div className=\"flex flex-col gap-4\">\n {onUploadImage && (\n <div className=\"flex gap-2\">\n <Button\n type=\"button\"\n variant={inputMode === 'file' ? 'solid' : 'outline'}\n action=\"primary\"\n size=\"small\"\n onClick={() => switchMode('file')}\n className=\"flex items-center gap-1\"\n >\n <UploadSimpleIcon size={14} />\n Enviar arquivo\n </Button>\n <Button\n type=\"button\"\n variant={inputMode === 'url' ? 'solid' : 'outline'}\n action=\"primary\"\n size=\"small\"\n onClick={() => switchMode('url')}\n className=\"flex items-center gap-1\"\n >\n <LinkIcon size={14} />\n Usar URL\n </Button>\n </div>\n )}\n\n {inputMode === 'file' && onUploadImage ? (\n <div>\n <ImageUpload\n selectedFile={file}\n onFileSelect={handleFileSelect}\n onRemoveFile={() => setFile(null)}\n // Defaults to \"Inserir imagem\", which would collide with the\n // dialog's confirm button — same name, different action.\n buttonText=\"Selecionar arquivo\"\n maxSize={MAX_IMAGE_SIZE}\n onSizeError={handleSizeError}\n onTypeError={handleTypeError}\n disabled={isUploading}\n />\n <Text size=\"xs\" className=\"text-text-400 mt-1\">\n Formatos aceitos: PNG, JPG, GIF, WEBP (máx. 5MB)\n </Text>\n </div>\n ) : (\n <div>\n <Text weight=\"medium\" className=\"text-xs text-text-600 mb-2 block\">\n URL da imagem\n </Text>\n <Input\n type=\"text\"\n value={url}\n onChange={(e: ChangeEvent<HTMLInputElement>) =>\n setUrl(e.target.value)\n }\n placeholder=\"https://exemplo.com/imagem.png\"\n />\n </div>\n )}\n\n <div>\n <Text weight=\"medium\" className=\"text-xs text-text-600 mb-2 block\">\n Texto alternativo (opcional)\n </Text>\n <Input\n type=\"text\"\n value={alt}\n onChange={(e: ChangeEvent<HTMLInputElement>) =>\n setAlt(e.target.value)\n }\n placeholder=\"Descreva a imagem para leitores de tela\"\n disabled={isUploading}\n />\n </div>\n\n {error && (\n <Text size=\"xs\" className=\"text-error-600\">\n {error}\n </Text>\n )}\n </div>\n </Modal>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAAA,SAAS,UAAU,cAA2B;AAM9C,SAAS,gBAAgB;AACzB,SAAS,wBAAwB;AAqHzB,mBACE,KADF;AA/GD,IAAM,iBAAiB,IAAI,OAAO;AAkBlC,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAqB;AACnB,QAAM,CAAC,WAAW,YAAY,IAAI;AAAA,IAChC,gBAAgB,SAAS;AAAA,EAC3B;AACA,QAAM,CAAC,MAAM,OAAO,IAAI,SAAsB,IAAI;AAClD,QAAM,CAAC,KAAK,MAAM,IAAI,SAAS,EAAE;AACjC,QAAM,CAAC,KAAK,MAAM,IAAI,SAAS,EAAE;AACjC,QAAM,CAAC,aAAa,cAAc,IAAI,SAAS,KAAK;AACpD,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,EAAE;AAErC,QAAM,iBAAiB,OAAO,CAAC;AAE/B,QAAM,aAAa,MAAM;AACvB,iBAAa,gBAAgB,SAAS,KAAK;AAC3C,YAAQ,IAAI;AACZ,WAAO,EAAE;AACT,WAAO,EAAE;AACT,mBAAe,KAAK;AACpB,aAAS,EAAE;AAAA,EACb;AAEA,QAAM,cAAc,MAAM;AAIxB,mBAAe,WAAW;AAC1B,eAAW;AACX,YAAQ;AAAA,EACV;AAEA,QAAM,mBAAmB,CAAC,aAAmB;AAC3C,YAAQ,QAAQ;AAChB,aAAS,EAAE;AAAA,EACb;AAEA,QAAM,kBAAkB,MAAM;AAC5B,YAAQ,IAAI;AACZ,aAAS,qCAAkC;AAAA,EAC7C;AAEA,QAAM,kBAAkB,MAAM;AAC5B,YAAQ,IAAI;AACZ,aAAS,2CAAwC;AAAA,EACnD;AAEA,QAAM,aAAa,CAAC,SAAoB;AACtC,iBAAa,IAAI;AACjB,aAAS,EAAE;AAAA,EACb;AAEA,QAAM,eAAe,YAAY;AAC/B,QAAI,cAAc,OAAO;AACvB,UAAI,CAAC,IAAI,KAAK,EAAG;AACjB,eAAS,IAAI,KAAK,GAAG,IAAI,KAAK,CAAC;AAC/B,iBAAW;AACX;AAAA,IACF;AAEA,QAAI,CAAC,QAAQ,CAAC,cAAe;AAE7B,mBAAe,IAAI;AACnB,aAAS,EAAE;AACX,UAAM,QAAQ,eAAe;AAE7B,QAAI;AAGF,YAAM,cAAc,MAAM,cAAc,IAAI;AAC5C,UAAI,UAAU,eAAe,QAAS;AACtC,eAAS,aAAa,IAAI,KAAK,CAAC;AAChC,iBAAW;AAAA,IACb,SAAS,KAAK;AACZ,UAAI,UAAU,eAAe,QAAS;AACtC,eAAS,eAAe,QAAQ,IAAI,UAAU,0BAA0B;AAAA,IAC1E,UAAE;AACA,qBAAe,KAAK;AAAA,IACtB;AAAA,EACF;AAEA,QAAM,YAAY,cAAc,QAAQ,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;AAEzD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,OAAM;AAAA,MACN,MAAK;AAAA,MACL,QACE,iCACE;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,SAAQ;AAAA,YACR,SAAS;AAAA,YACT,UAAU;AAAA,YACX;AAAA;AAAA,QAED;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,SAAQ;AAAA,YACR,QAAO;AAAA,YACP,SAAS;AAAA,YACT,UAAU,CAAC,aAAa;AAAA,YAEvB,wBAAc,gBAAgB;AAAA;AAAA,QACjC;AAAA,SACF;AAAA,MAGF,+BAAC,SAAI,WAAU,uBACZ;AAAA,yBACC,qBAAC,SAAI,WAAU,cACb;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAS,cAAc,SAAS,UAAU;AAAA,cAC1C,QAAO;AAAA,cACP,MAAK;AAAA,cACL,SAAS,MAAM,WAAW,MAAM;AAAA,cAChC,WAAU;AAAA,cAEV;AAAA,oCAAC,oBAAiB,MAAM,IAAI;AAAA,gBAAE;AAAA;AAAA;AAAA,UAEhC;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAS,cAAc,QAAQ,UAAU;AAAA,cACzC,QAAO;AAAA,cACP,MAAK;AAAA,cACL,SAAS,MAAM,WAAW,KAAK;AAAA,cAC/B,WAAU;AAAA,cAEV;AAAA,oCAAC,YAAS,MAAM,IAAI;AAAA,gBAAE;AAAA;AAAA;AAAA,UAExB;AAAA,WACF;AAAA,QAGD,cAAc,UAAU,gBACvB,qBAAC,SACC;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,cAAc;AAAA,cACd,cAAc;AAAA,cACd,cAAc,MAAM,QAAQ,IAAI;AAAA,cAGhC,YAAW;AAAA,cACX,SAAS;AAAA,cACT,aAAa;AAAA,cACb,aAAa;AAAA,cACb,UAAU;AAAA;AAAA,UACZ;AAAA,UACA,oBAAC,gBAAK,MAAK,MAAK,WAAU,sBAAqB,iEAE/C;AAAA,WACF,IAEA,qBAAC,SACC;AAAA,8BAAC,gBAAK,QAAO,UAAS,WAAU,oCAAmC,2BAEnE;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,OAAO;AAAA,cACP,UAAU,CAAC,MACT,OAAO,EAAE,OAAO,KAAK;AAAA,cAEvB,aAAY;AAAA;AAAA,UACd;AAAA,WACF;AAAA,QAGF,qBAAC,SACC;AAAA,8BAAC,gBAAK,QAAO,UAAS,WAAU,oCAAmC,0CAEnE;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,OAAO;AAAA,cACP,UAAU,CAAC,MACT,OAAO,EAAE,OAAO,KAAK;AAAA,cAEvB,aAAY;AAAA,cACZ,UAAU;AAAA;AAAA,UACZ;AAAA,WACF;AAAA,QAEC,SACC,oBAAC,gBAAK,MAAK,MAAK,WAAU,kBACvB,iBACH;AAAA,SAEJ;AAAA;AAAA,EACF;AAEJ;","names":[]}