diff options
| author | Nathan Reiner <nathan@nathanreiner.xyz> | 2025-11-15 15:12:19 +0100 |
|---|---|---|
| committer | Nathan Reiner <nathan@nathanreiner.xyz> | 2025-11-15 15:12:19 +0100 |
| commit | 0016aaa197697ec5ff38dfb3f63ac8b6f74b48e0 (patch) | |
| tree | 688930dcc978bcadfc01dac4ff09908452d802a4 /static/api | |
| parent | 69488029a8eafc55d42ae7e65ba8ddeebcfb8454 (diff) | |
backend: add image upload
Diffstat (limited to 'static/api')
| -rw-r--r-- | static/api/images.js | 43 |
1 files changed, 43 insertions, 0 deletions
diff --git a/static/api/images.js b/static/api/images.js index 915aae6..4a41a3c 100644 --- a/static/api/images.js +++ b/static/api/images.js @@ -1,6 +1,43 @@ import * as sfw from 'sfw'; const { Input } = sfw.element.native; +class FileUploader { + constructor(url) { + this.onprogress = () => {} + this.ondone = () => {} + this.url = url; + this.sessions = []; + } + + send(...files) { + let count = 0; + this.sessions = this.sessions.concat(files.map( + file => new Promise((resolve) => { + const xhr = new XMLHttpRequest(); + xhr.upload.addEventListener("progress", (event) => { + if (event.lengthComputable) { + this.onprogress(file, event.loaded, event.total) + } + }); + + xhr.addEventListener("loadend", () => { + count += 1; + + resolve(xhr.readyState === 4 && xhr.status === 200); + + if (count == files.length) { + this.ondone(); + } + }); + + xhr.open("POST", this.url, true); + xhr.setRequestHeader("Content-Type", "application/octet-stream"); + xhr.send(file.slice()); + }) + )); + } +} + export async function upload_to_timeline() { const input = Input.new({ type: 'file', @@ -8,6 +45,12 @@ export async function upload_to_timeline() { accept: 'image/jpeg', }) input.click(); + + const uploader = new FileUploader('/api/image/upload'); + + input.onchange = async () => { + uploader.send(...input.files); + } } export async function upload_to_profile() { |