رافع الملفات: تعديل سجل
هذه الصفحة غير مترجمة بعد، وهي معروضة بالإنجليزية.
To edit a record that already has files, pass their stored URLs to the file uploader as defaultValue. Each becomes a finished item, with a preview for images, that can be removed like any other. onUrlsChange gives back the URLs to save: the ones left plus the new uploads.
Saves images: /samples/avatar.jpg
import { useState } from "react";
import { FileUploader } from "slotsmith/file-uploader";
import { fakeUpload } from "../shared/fake-upload";
/** The record being edited, as the server returned it: images are stored URLs. */
const product = { id: 42, images: ["/samples/avatar.jpg"] };
export default function EditingARecord() {
// What the form would save: the URLs that are left, plus the new uploads'.
const [images, setImages] = useState<string[]>(product.images);
return (
<div style={{ display: "grid", gap: "0.75rem" }}>
<FileUploader
multiple
accept="image/*"
maxFiles={4}
// The stored URLs become finished items, with previews for images.
defaultValue={product.images}
upload={fakeUpload}
onUrlsChange={setImages}
/>
<p>Saves images: {images.length ? images.join(", ") : "none"}</p>
</div>
);
}/** What the uploader hands an `upload` function, as far as this stand-in reads it. */
export interface FakeUploadContext {
signal: AbortSignal;
onProgress(percent: number): void;
}
/** Five steps of 200 ms: the whole upload takes one second. */
const STEPS = 5;
const STEP_MS = 200;
/** Waits `ms`, and rejects with an `AbortError` as soon as the signal aborts. */
function wait(ms: number, signal: AbortSignal): Promise<void> {
return new Promise((resolve, reject) => {
let timer: ReturnType<typeof setTimeout> | undefined;
const abort = () => {
clearTimeout(timer);
reject(new DOMException("The upload was cancelled.", "AbortError"));
};
if (signal.aborted) return abort();
timer = setTimeout(() => {
signal.removeEventListener("abort", abort);
resolve();
}, ms);
signal.addEventListener("abort", abort, { once: true });
});
}
/**
* A stand-in for an upload endpoint. Nothing leaves the browser: it reports
* progress in five steps over one second, stops as soon as `signal` aborts,
* and resolves with `/samples/uploaded/<name>`. With `fail`, it fails at 60%
* the way a server error would, so the retry path can be tried.
*/
export async function fakeUpload(
file: File,
{ signal, onProgress }: FakeUploadContext,
{ fail = false }: { fail?: boolean } = {},
): Promise<{ url: string }> {
for (let step = 1; step <= STEPS; step += 1) {
await wait(STEP_MS, signal);
if (fail && step === 3) throw new Error("The server answered 503. Try again.");
onProgress((step / STEPS) * 100);
}
return { url: `/samples/uploaded/${encodeURIComponent(file.name)}` };
}The record starts with one stored image. Remove it, or add another: the line under the uploader shows what the form would save.
From URLs to items
defaultValue takes stored URLs, File objects or full items. A URL becomes an item that is already done:
- Its name is the last part of the URL path, decoded.
- Its type is guessed from the extension, so a
.jpg,.png,.webpor other image extension shows a preview. Anything else shows the file glyph. - Its size is unknown, so none is shown. It has no file, so it cannot be retried.
defaultValue is read once, when the uploader mounts. To change the items later, control them with value and onValueChange.
Saving
onUrlsChange is called after every change with the URLs of the stored items and of the finished uploads, in list order. A file still uploading has no URL yet, so it joins the list when it finishes. That array is what a record usually keeps.