feat: upload progress on new-project form
Swap the create-project upload from :http-xhrio to a raw XHR effect with upload.onprogress, surface it as a 1-bit progress bar; disable Create while uploading. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
533d14ce49
commit
7e47ec6a2d
5 changed files with 47 additions and 11 deletions
|
|
@ -376,6 +376,12 @@ body { overflow: hidden; background: var(--desktop); background-size: 2px 2px;
|
||||||
.err { color: var(--ink); font-weight: bold; font-size: 12px; margin: 8px 0;
|
.err { color: var(--ink); font-weight: bold; font-size: 12px; margin: 8px 0;
|
||||||
border: 1px solid var(--ink); padding: 4px 8px; background: var(--shade); }
|
border: 1px solid var(--ink); padding: 4px 8px; background: var(--shade); }
|
||||||
|
|
||||||
|
/* 1-bit upload progress: black fill in a bordered white track */
|
||||||
|
.upload-progress { display: flex; align-items: center; gap: 8px; margin: 10px 0; }
|
||||||
|
.upload-track { flex: 1; height: 14px; border: 1px solid var(--ink); background: var(--paper); }
|
||||||
|
.upload-fill { height: 100%; background: var(--ink); transition: width .15s linear; }
|
||||||
|
.upload-pct { font-family: var(--mono); font-size: 11px; min-width: 36px; text-align: right; }
|
||||||
|
|
||||||
/* editor: project bar above the timeline — a Mac window title bar (selected) */
|
/* editor: project bar above the timeline — a Mac window title bar (selected) */
|
||||||
.project-bar { display: flex; gap: 10px; align-items: center; padding: 4px 8px;
|
.project-bar { display: flex; gap: 10px; align-items: center; padding: 4px 8px;
|
||||||
background: var(--ink); color: var(--paper); font-family: var(--chicago);
|
background: var(--ink); color: var(--paper); font-family: var(--chicago);
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
:projects nil ; project list (when signed in)
|
:projects nil ; project list (when signed in)
|
||||||
:project nil ; current project detail {:id :name :clip :otio}
|
:project nil ; current project detail {:id :name :clip :otio}
|
||||||
:create-error nil ; new-project upload failure
|
:create-error nil ; new-project upload failure
|
||||||
|
:create-progress nil ; new-project upload progress (0..1) while uploading
|
||||||
:login-error nil ; sign-in failure
|
:login-error nil ; sign-in failure
|
||||||
:net-error nil ; backend unreachable (fetch-me / logout)
|
:net-error nil ; backend unreachable (fetch-me / logout)
|
||||||
:projects-error nil ; project-list fetch failure
|
:projects-error nil ; project-list fetch failure
|
||||||
|
|
|
||||||
|
|
@ -215,18 +215,39 @@
|
||||||
|
|
||||||
;; --- create project -------------------------------------------------------
|
;; --- create project -------------------------------------------------------
|
||||||
|
|
||||||
|
;; Raw XHR (not :http-xhrio) so we can report upload progress for the big clip.
|
||||||
|
(rf/reg-fx :upload-project
|
||||||
|
(fn [{:keys [name otio clip]}]
|
||||||
|
(let [fd (doto (js/FormData.)
|
||||||
|
(.append "name" name) (.append "otio" otio) (.append "clip" clip))
|
||||||
|
xhr (js/XMLHttpRequest.)]
|
||||||
|
(.open xhr "POST" (str api/base "/api/projects/"))
|
||||||
|
(set! (.-withCredentials xhr) true)
|
||||||
|
(set! (.. xhr -upload -onprogress)
|
||||||
|
(fn [e] (when (.-lengthComputable e)
|
||||||
|
(rf/dispatch [::create-progress (/ (.-loaded e) (.-total e))]))))
|
||||||
|
(set! (.-onload xhr)
|
||||||
|
(fn [_]
|
||||||
|
(let [body (try (js->clj (js/JSON.parse (.-responseText xhr)) :keywordize-keys true)
|
||||||
|
(catch :default _ {}))]
|
||||||
|
(if (<= 200 (.-status xhr) 299)
|
||||||
|
(rf/dispatch [::project-created body])
|
||||||
|
(rf/dispatch [::project-create-error {:status (.-status xhr) :response body}])))))
|
||||||
|
(set! (.-onerror xhr) (fn [_] (rf/dispatch [::project-create-error {:status 0}])))
|
||||||
|
(.send xhr fd))))
|
||||||
|
|
||||||
(rf/reg-event-fx ::create-project
|
(rf/reg-event-fx ::create-project
|
||||||
(fn [{:keys [db]} [_ name otio clip]]
|
(fn [{:keys [db]} [_ name otio clip]]
|
||||||
(let [fd (doto (js/FormData.)
|
{:db (assoc db :create-error nil :create-progress 0)
|
||||||
(.append "name" name) (.append "otio" otio) (.append "clip" clip))]
|
:upload-project {:name name :otio otio :clip clip}}))
|
||||||
{:db (assoc db :create-error nil)
|
(rf/reg-event-db ::create-progress (fn [db [_ p]] (assoc db :create-progress p)))
|
||||||
:http-xhrio (api/upload "/api/projects/" fd
|
(rf/reg-event-fx ::project-created
|
||||||
{:on-success [::project-created]
|
(fn [{:keys [db]} [_ p]]
|
||||||
:on-failure [::project-create-error]})})))
|
{:db (assoc db :create-progress nil) :route [:project/show {:id (:id p)}]}))
|
||||||
(rf/reg-event-fx ::project-created (fn [_ [_ p]] {:route [:project/show {:id (:id p)}]}))
|
|
||||||
(rf/reg-event-db ::project-create-error
|
(rf/reg-event-db ::project-create-error
|
||||||
(fn [db [_ failure]]
|
(fn [db [_ failure]]
|
||||||
(assoc db :create-error (api/error-message failure "Upload failed."))))
|
(assoc db :create-error (api/error-message failure "Upload failed.")
|
||||||
|
:create-progress nil)))
|
||||||
|
|
||||||
;; --- view -----------------------------------------------------------------
|
;; --- view -----------------------------------------------------------------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
(rf/reg-sub ::thumbnails (fn [db] (get-in db [:project :thumbnails])))
|
(rf/reg-sub ::thumbnails (fn [db] (get-in db [:project :thumbnails])))
|
||||||
(rf/reg-sub ::thumbnail-status (fn [db] (get-in db [:project :thumbnail_status])))
|
(rf/reg-sub ::thumbnail-status (fn [db] (get-in db [:project :thumbnail_status])))
|
||||||
(rf/reg-sub ::create-error (fn [db] (:create-error db)))
|
(rf/reg-sub ::create-error (fn [db] (:create-error db)))
|
||||||
|
(rf/reg-sub ::create-progress (fn [db] (:create-progress db)))
|
||||||
(rf/reg-sub ::login-error (fn [db] (:login-error db)))
|
(rf/reg-sub ::login-error (fn [db] (:login-error db)))
|
||||||
(rf/reg-sub ::net-error (fn [db] (:net-error db)))
|
(rf/reg-sub ::net-error (fn [db] (:net-error db)))
|
||||||
(rf/reg-sub ::projects-error (fn [db] (:projects-error db)))
|
(rf/reg-sub ::projects-error (fn [db] (:projects-error db)))
|
||||||
|
|
|
||||||
|
|
@ -986,7 +986,9 @@
|
||||||
(defn create-page []
|
(defn create-page []
|
||||||
(let [pname (r/atom "") otio (atom nil) clip (atom nil)]
|
(let [pname (r/atom "") otio (atom nil) clip (atom nil)]
|
||||||
(fn []
|
(fn []
|
||||||
(let [err @(rf/subscribe [::subs/create-error])]
|
(let [err @(rf/subscribe [::subs/create-error])
|
||||||
|
prog @(rf/subscribe [::subs/create-progress])
|
||||||
|
uploading? (some? prog)]
|
||||||
[:div.home
|
[:div.home
|
||||||
[:div.home-head [:h1 "New project"] [:a {:href (routes/href :projects)} "← back"]]
|
[:div.home-head [:h1 "New project"] [:a {:href (routes/href :projects)} "← back"]]
|
||||||
[:div.form
|
[:div.form
|
||||||
|
|
@ -999,9 +1001,14 @@
|
||||||
[:input {:type "file" :accept "video/*"
|
[:input {:type "file" :accept "video/*"
|
||||||
:on-change #(reset! clip (-> % .-target .-files (aget 0)))}]]
|
:on-change #(reset! clip (-> % .-target .-files (aget 0)))}]]
|
||||||
(when err [:div.err err])
|
(when err [:div.err err])
|
||||||
|
(when uploading?
|
||||||
|
[:div.upload-progress
|
||||||
|
[:div.upload-track [:div.upload-fill {:style {:width (str (* 100 prog) "%")}}]]
|
||||||
|
[:span.upload-pct (str (js/Math.round (* 100 prog)) "%")]])
|
||||||
[:div.form-actions
|
[:div.form-actions
|
||||||
[:button.save {:disabled (or (str/blank? @pname) (not @otio) (not @clip))
|
[:button.save {:disabled (or uploading? (str/blank? @pname) (not @otio) (not @clip))
|
||||||
:on-click #(rf/dispatch [::events/create-project @pname @otio @clip])} "Create"]
|
:on-click #(rf/dispatch [::events/create-project @pname @otio @clip])}
|
||||||
|
(if uploading? "Uploading…" "Create")]
|
||||||
[:a.cancel {:href (routes/href :projects)} "Cancel"]]]]))))
|
[:a.cancel {:href (routes/href :projects)} "Cancel"]]]]))))
|
||||||
|
|
||||||
(defn main-panel []
|
(defn main-panel []
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue