The upload card printed the SDK's toString — 'MatrixError: [413] nope (http://<hs>/_matrix/media/v3/upload?filename=…)'. describeUploadError() maps the common cases to one sentence: 413/M_TOO_LARGE → 'This file is larger than the server allows (limit N)' using m.upload.size when known, 429 → 'Slow down — try again in a moment.', 401/403 → 'The server refused this upload: <server text>', 5xx/transport after the retry loop → 'Couldn't reach the server. Check your connection and retry.', other 4xx → the server's own sentence, URL stripped. Both card renderers use it; the raw error is still console.warn-ed by uploadContent for debugging. Unit-tested; verified headless with routed 413/403/503 responses. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
112 lines
3.5 KiB
TypeScript
112 lines
3.5 KiB
TypeScript
import React, { useEffect } from 'react';
|
|
import { Chip, Icon, IconButton, Icons, Text, color } from 'folds';
|
|
import { UploadCard, UploadCardError, CompactUploadCardProgress } from './UploadCard';
|
|
import { TUploadAtom, UploadStatus, UploadSuccess, useBindUploadAtom } from '../../state/upload';
|
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
|
import { TUploadContent } from '../../utils/matrix';
|
|
import { bytesToSize, getFileTypeIcon } from '../../utils/common';
|
|
import { useMediaConfig } from '../../hooks/useMediaConfig';
|
|
import { describeUploadError } from '../../utils/uploadError';
|
|
|
|
type CompactUploadCardRendererProps = {
|
|
isEncrypted?: boolean;
|
|
uploadAtom: TUploadAtom;
|
|
onRemove: (file: TUploadContent) => void;
|
|
onComplete?: (upload: UploadSuccess) => void;
|
|
};
|
|
export function CompactUploadCardRenderer({
|
|
isEncrypted,
|
|
uploadAtom,
|
|
onRemove,
|
|
onComplete,
|
|
}: CompactUploadCardRendererProps) {
|
|
const mx = useMatrixClient();
|
|
const mediaConfig = useMediaConfig();
|
|
const allowSize = mediaConfig['m.upload.size'] || Infinity;
|
|
|
|
const { upload, startUpload, cancelUpload } = useBindUploadAtom(mx, uploadAtom, isEncrypted);
|
|
const { file } = upload;
|
|
const fileSizeExceeded = file.size >= allowSize;
|
|
|
|
if (upload.status === UploadStatus.Idle && !fileSizeExceeded) {
|
|
startUpload();
|
|
}
|
|
|
|
const removeUpload = () => {
|
|
cancelUpload();
|
|
onRemove(file);
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (upload.status === UploadStatus.Success) {
|
|
onComplete?.(upload);
|
|
}
|
|
}, [upload, onComplete]);
|
|
|
|
return (
|
|
<UploadCard
|
|
compact
|
|
outlined
|
|
radii="300"
|
|
before={<Icon src={getFileTypeIcon(Icons, file.type)} />}
|
|
after={
|
|
<>
|
|
{upload.status === UploadStatus.Error && (
|
|
<Chip
|
|
as="button"
|
|
onClick={startUpload}
|
|
aria-label="Retry Upload"
|
|
variant="Critical"
|
|
radii="Pill"
|
|
outlined
|
|
>
|
|
<Text size="B300">Retry</Text>
|
|
</Chip>
|
|
)}
|
|
<IconButton
|
|
onClick={removeUpload}
|
|
aria-label="Cancel Upload"
|
|
variant="SurfaceVariant"
|
|
radii="Pill"
|
|
size="300"
|
|
>
|
|
<Icon src={Icons.Cross} size="200" />
|
|
</IconButton>
|
|
</>
|
|
}
|
|
>
|
|
{upload.status === UploadStatus.Success ? (
|
|
<>
|
|
<Text size="H6" truncate>
|
|
{(file as File).name}
|
|
</Text>
|
|
<Icon style={{ color: color.Success.Main }} src={Icons.Check} size="100" />
|
|
</>
|
|
) : (
|
|
<>
|
|
{upload.status === UploadStatus.Idle && !fileSizeExceeded && (
|
|
<CompactUploadCardProgress sentBytes={0} totalBytes={file.size} />
|
|
)}
|
|
{upload.status === UploadStatus.Loading && (
|
|
<CompactUploadCardProgress sentBytes={upload.progress.loaded} totalBytes={file.size} />
|
|
)}
|
|
{upload.status === UploadStatus.Error && (
|
|
<UploadCardError>
|
|
<Text size="T200">{describeUploadError(upload.error, allowSize)}</Text>
|
|
</UploadCardError>
|
|
)}
|
|
{upload.status === UploadStatus.Idle && fileSizeExceeded && (
|
|
<UploadCardError>
|
|
<Text size="T200">
|
|
The file size exceeds the limit. Maximum allowed size is{' '}
|
|
<b>{bytesToSize(allowSize)}</b>, but the uploaded file is{' '}
|
|
<b>{bytesToSize(file.size)}</b>.
|
|
</Text>
|
|
</UploadCardError>
|
|
)}
|
|
</>
|
|
)}
|
|
</UploadCard>
|
|
);
|
|
}
|