|
(ns ui.uploads |
|
(:require-macros [reagent.ratom :refer [reaction]]) |
|
(:require [re-frame.core :as re-frame] |
|
[reagent.ratom :as ratom] |
|
[ui.core :as ui] |
|
[ui.ajax :as ajax])) |
|
|
|
(def interceptors |
|
[(when ^boolean js/goog.DEBUG re-frame/debug) |
|
re-frame/trim-v]) |
|
|
|
(re-frame/reg-event-fx |
|
:upload/start |
|
interceptors |
|
(fn [{db :db} [identifier file]] |
|
(let [progress {:bytes-sent 0 |
|
:bytes-total (.-size file)}] |
|
|
|
{:db (assoc-in db [::progress identifier] progress) |
|
:dispatch (if (some? file) |
|
[::get-upload-url identifier file] |
|
[:upload/done identifier])}))) |
|
|
|
|
|
(re-frame/reg-event-fx |
|
::get-upload-url |
|
interceptors |
|
(fn [_ [identifier file]] |
|
(let [file-name (.-name file) |
|
mime-type (.-type file)] |
|
{:http-xhrio {:method :post |
|
:uri "/sign" |
|
:params {:file-name file-name |
|
:mime-type mime-type} |
|
:format (ajax/transit-request-format) |
|
:response-format (ajax/transit-response-format) |
|
:on-success [::upload-file identifier file] |
|
:on-failure [:upload/failed identifier]}}))) |
|
|
|
|
|
(defn upload-request-body [file form-data] |
|
(let [data (assoc form-data :file file) |
|
fd (new js/FormData)] |
|
(doseq [[k v] data] |
|
(.append fd (name k) v)) |
|
fd)) |
|
|
|
|
|
(re-frame/reg-event-fx |
|
::upload-file |
|
interceptors |
|
(fn [{db :db} [identifier file upload-url-response]] |
|
(let [upload-url (:upload-url upload-url-response) |
|
form-data (:form-data upload-url-response) |
|
storage-key (:key upload-url-response) |
|
request-body (upload-request-body file form-data)] |
|
|
|
;; |
|
;; `storage-key` needs to be available to whoever needs it after upload completes |
|
;; |
|
|
|
{:db (update-in db [::progress identifier] assoc :key storage-key) |
|
:http-xhrio {:method :post |
|
:uri upload-url |
|
:body request-body |
|
:response-format (ajax.core/raw-response-format) |
|
:on-success [:upload/done identifier storage-key] |
|
:on-failure [:upload/failed identifier] |
|
:progress-handler #(re-frame/dispatch [::upload-progress |
|
identifier |
|
%])}}))) |
|
|
|
|
|
(re-frame/reg-event-db |
|
:upload/done |
|
interceptors |
|
(fn [db [identifier]] |
|
(update db ::progress dissoc identifier))) |
|
|
|
|
|
(re-frame/reg-event-db |
|
::upload-progress |
|
interceptors |
|
(fn [db [identifier progress-event]] |
|
(let [progress {:bytes-sent (.-loaded progress-event)}] |
|
(update-in db [::progress identifier] merge progress)))) |
|
|
|
|
|
(re-frame/reg-sub |
|
::upload-progress |
|
(fn [db [_ identifier]] |
|
(get-in db [::progress identifier]))) |
|
|
|
|
|
(defn attachment [{:keys [identifier label file-selected] |
|
:or {:file-selected identity} |
|
:as options}] |
|
(let [input (ui/component "Form" "Input") |
|
button (ui/component "Button") |
|
progress-bar (ui/component "Progress") |
|
|
|
file-id (gensym "attachment-") |
|
filename (ratom/atom "") |
|
progress (re-frame/subscribe [::upload-progress identifier]) |
|
|
|
uploading? (reaction |
|
(some? (:bytes-sent @progress))) |
|
completed? (reaction |
|
(and @uploading? |
|
(= (:bytes-sent @progress) (:bytes-total @progress)))) |
|
percent (reaction |
|
(* (/ (:bytes-sent @progress) (:bytes-total @progress)) |
|
100)) |
|
|
|
choose-file (fn [e d] |
|
(-> (.getElementById js/document file-id) |
|
(.click))) |
|
file-changed (fn [e] |
|
(let [files (-> e .-target .-files) |
|
file (aget files 0) |
|
name' (.-name file)] |
|
(reset! filename name') |
|
(if (fn? file-selected) |
|
(file-selected identifier file))))] |
|
|
|
(fn [options] |
|
(if @uploading? |
|
[:> progress-bar {:indicating true |
|
:percent @percent}] |
|
|
|
[:> input {:action true |
|
:label label} |
|
|
|
[:input {:placeholder "File" |
|
:read-only true |
|
:value @filename |
|
:on-click choose-file}] |
|
[:input {:id file-id |
|
:type "file" |
|
:style {:display "none"} |
|
:on-change file-changed}] |
|
|
|
[:> button {:icon "attach" |
|
:content "Choose" |
|
:type "button" |
|
:on-click choose-file}]])))) |