diff --git a/components/upload-modal.html b/components/upload-modal.html
index cea44bdf..830d5118 100644
--- a/components/upload-modal.html
+++ b/components/upload-modal.html
@@ -1383,6 +1383,33 @@
Large files are supported.
+
+
+
+
+
+ Generating torrent...
+ 0%
+
+
+
+
+
+
+
+
+
+
+
@@ -1782,6 +1809,7 @@
type="submit"
id="cloudflareUploadButton"
class="btn w-full text-sm"
+ disabled
>
Upload to R2 & publish
diff --git a/js/services/r2Service.js b/js/services/r2Service.js
index 2cbb3162..c37819bb 100644
--- a/js/services/r2Service.js
+++ b/js/services/r2Service.js
@@ -831,6 +831,37 @@ class R2Service {
async uploadVideo(params = {}) {
return this.handleCloudflareUploadSubmit(params);
}
+
+ async uploadFile({
+ file,
+ accountId,
+ accessKeyId,
+ secretAccessKey,
+ bucket,
+ key,
+ onProgress,
+ } = {}) {
+ if (!file || !bucket || !key || !accountId || !accessKeyId || !secretAccessKey) {
+ throw new Error("Missing required parameters for uploadFile");
+ }
+
+ const s3 = makeR2Client({ accountId, accessKeyId, secretAccessKey });
+
+ await multipartUpload({
+ s3,
+ bucket,
+ key,
+ file,
+ contentType: file.type || "application/octet-stream",
+ onProgress: (fraction) => {
+ if (typeof onProgress === "function") {
+ onProgress(fraction);
+ }
+ },
+ });
+
+ return { bucket, key };
+ }
}
const r2Service = new R2Service();
diff --git a/verification/verify_ui.py b/verification/verify_ui.py
new file mode 100644
index 00000000..a4d30f47
--- /dev/null
+++ b/verification/verify_ui.py
@@ -0,0 +1,54 @@
+
+from playwright.sync_api import sync_playwright
+import os
+
+def run():
+ # Construct absolute path to the local file
+ file_path = os.path.abspath('index.html')
+ url = f'file://{file_path}'
+
+ with sync_playwright() as p:
+ browser = p.chromium.launch(headless=True)
+ context = browser.new_context()
+ page = context.new_page()
+
+ # Navigate to the local index.html
+ print(f'Navigating to {url}')
+ page.goto(url)
+
+ # Wait for the upload modal to be part of the DOM (it's in the static HTML but hidden)
+ # We need to trigger it to open.
+ # The upload button has id='uploadButton'.
+ # However, checking ApplicationBootstrap logic, it might need JS initialization.
+ # Since we are running against static file://, module scripts might fail CORS or path resolution
+ # unless served via http.
+ # Let's try to serve it first.
+
+ # Actually, simpler: just check if the new elements exist in the DOM,
+ # even if the modal isn't visible, we can force it visible via JS for the screenshot.
+
+ # Wait for the modal container
+ page.wait_for_selector('#uploadModal', state='attached')
+
+ # Force modal visible for screenshot
+ page.evaluate("document.getElementById('uploadModal').classList.remove('hidden')")
+
+ # Switch to Cloudflare tab to see new UI
+ # The tab buttons have data-upload-mode='cloudflare'
+ # We can click it or just unhide the section manually if JS isn't running fully.
+ # Let's assume JS might fail on file://, so we do manual DOM manipulation to reveal the section.
+
+ # Hide custom section
+ page.evaluate("document.getElementById('customUploadSection').classList.add('hidden')")
+
+ # Show Cloudflare section
+ page.evaluate("document.getElementById('cloudflareUploadSection').classList.remove('hidden')")
+
+ # Take screenshot of the Cloudflare section
+ element = page.locator('#cloudflareUploadSection')
+ element.screenshot(path='verification/upload_modal_cloudflare.png')
+
+ browser.close()
+
+if __name__ == '__main__':
+ run()