Reading and uploading files

Files chosen in a file input come in the on_change_file event, and files dropped onto an element in the on_dropfile event. Both give a DropFileEvent with a list of files, each having a name and the whole content in data:

    use vertigo::{bind, css, dom, DropFileEvent, Value};

    let files = Value::new(Vec::<String>::new());

    // The same handler for both the file input and the drop zone
    let add_files = bind!(files, |event: DropFileEvent| {
        files.change(|files| {
            for file in event.items {
                // file.data contains the whole file content
                files.push(format!("{} ({} bytes)", file.name, file.data.len()));
            }
        })
    });

    let drop_zone_css = css! {"
        padding: 20px;
        border: 2px dashed gray;
    "};

    let list = files.render_value(|files| {
        dom! {
            <ul>{..files.into_iter().map(|file| dom! { <li>{file}</li> })}</ul>
        }
    });

    dom! {
        <div>
            <input type="file" multiple="multiple" on_change_file={add_files.clone()} />
            <div css={drop_zone_css} on_dropfile={add_files}>"...or drop files here"</div>
            {list}
        </div>
    }

Uploading

RequestBuilder sends request bodies as JSON, so the file content needs to be encoded, usually with base64. The ImageUpload struct from vertigo-forms does that for images:

    use vertigo::{bind, dom, get_driver, DropFileEvent, RequestBuilder, Value};
    use vertigo_forms::ImageUpload;

    let status = Value::new(String::new());

    let upload = bind!(status, |event: DropFileEvent| {
        let Some(file) = event.items.into_iter().next() else {
            return;
        };

        status.set(format!("Uploading {}...", file.name));

        get_driver().spawn(bind!(status, async move {
            // Name, mime type and base64-encoded content
            let image = ImageUpload::from(file);

            let response = RequestBuilder::post("/api/avatar")
                .body_json(image)
                .call()
                .await;

            status.set(match response.status() {
                Some(200) => "Uploaded".to_string(),
                _ => "Upload failed".to_string(),
            });
        }));
    });

    dom! {
        <div>
            <input type="file" accept="image/*" on_change_file={upload} />
            <p>{status}</p>
        </div>
    }

For other kinds of files, put the content encoded with the base64 crate into a field of your own struct.