🎉 Add save indicator.

And improve persistence loop error handling.
This commit is contained in:
Andrey Antukh 2020-09-17 17:59:48 +02:00 committed by Alonso Torres
parent 9755516178
commit 1b598e2f6d
8 changed files with 414 additions and 254 deletions

View file

@ -1,3 +1,3 @@
<svg width="500" height="500" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="500" height="500" viewBox="0 0 8 8" xmlns="http://www.w3.org/2000/svg">
<path d="M7.99941 1.62266C8.01413 1.36698 7.74905 1.14313 7.49807 1.21294C7.35967 1.27903 7.02149 1.60601 7.02149 1.60601C7.02149 1.60601 3.82775 4.79865 2.80464 5.76808C2.68664 5.81588 2.6167 5.68421 2.54114 5.62249C1.48872 4.57167 1.68802 4.66986 0.628214 3.62654C0.403393 3.46589 0.0441303 3.61642 0.00630665 3.8924C-0.0278168 4.07137 0.0811424 4.23539 0.212762 4.34466C1.2602 5.45375 1.12484 5.34669 2.1814 6.44725C2.31167 6.56977 2.42611 6.71875 2.58163 6.80902C2.77403 6.89051 2.99255 6.79154 3.11096 6.63235C4.317 5.44398 6.72612 3.05828 7.90933 1.84723C7.96218 1.78479 8.00392 1.70675 7.99941 1.62266Z" fill="black"/> <path d="M7.99941 1.62266C8.01413 1.36698 7.74905 1.14313 7.49807 1.21294C7.35967 1.27903 7.02149 1.60601 7.02149 1.60601C7.02149 1.60601 3.82775 4.79865 2.80464 5.76808C2.68664 5.81588 2.6167 5.68421 2.54114 5.62249C1.48872 4.57167 1.68802 4.66986 0.628214 3.62654C0.403393 3.46589 0.0441303 3.61642 0.00630665 3.8924C-0.0278168 4.07137 0.0811424 4.23539 0.212762 4.34466C1.2602 5.45375 1.12484 5.34669 2.1814 6.44725C2.31167 6.56977 2.42611 6.71875 2.58163 6.80902C2.77403 6.89051 2.99255 6.79154 3.11096 6.63235C4.317 5.44398 6.72612 3.05828 7.90933 1.84723C7.96218 1.78479 8.00392 1.70675 7.99941 1.62266Z" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 731 B

After

Width:  |  Height:  |  Size: 707 B

Before After
Before After

File diff suppressed because it is too large Load diff

View file

@ -2,8 +2,10 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this // License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
// //
// Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz> // This Source Code Form is "Incompatible With Secondary Licenses", as
// Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com> // defined by the Mozilla Public License, v. 2.0.
//
// Copyright (c) 2020 UXBOX Labs SL
.workspace-header { .workspace-header {
align-items: center; align-items: center;
@ -177,3 +179,35 @@
} }
} }
} }
.persistence-status-widget {
display: flex;
margin-right: 10px;
/* border: 1px solid red; */
width: 150px;
justify-content: flex-end;
> div {
display: flex;
&.error {
.label { color: $color-danger; }
.icon svg { fill: $color-danger; }
}
}
.icon {
padding: 0px 10px;
}
.label {
color: $color-gray-30;
font-size: $fs14;
}
svg {
width: 12px;
height: 12px;
fill: $color-gray-30;
}
}

View file

@ -159,7 +159,12 @@
(ptk/reify ::finalize (ptk/reify ::finalize
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(dissoc state :workspace-file :workspace-project :workspace-media-objects :workspace-users)) (dissoc state
:workspace-file
:workspace-project
:workspace-media-objects
:workspace-users
:workspace-persistence))
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]

View file

@ -32,59 +32,120 @@
(declare persist-changes) (declare persist-changes)
(declare shapes-changes-persisted) (declare shapes-changes-persisted)
(declare update-persistence-status)
;; --- Persistence ;; --- Persistence
(defn initialize-file-persistence (defn initialize-file-persistence
[file-id] [file-id]
(letfn [(enable-reload-stoper [] (ptk/reify ::initialize-persistence
(obj/set! js/window "onbeforeunload" (constantly false))) ptk/EffectEvent
(disable-reload-stoper [] (effect [_ state stream]
(obj/set! js/window "onbeforeunload" nil))] (let [stoper (rx/filter #(= ::finalize %) stream)
(ptk/reify ::initialize-persistence notifier (->> stream
ptk/WatchEvent (rx/filter (ptk/type? ::dwc/commit-changes))
(watch [_ state stream] (rx/debounce 2000)
(let [stoper (rx/filter #(= ::finalize %) stream) (rx/merge stoper))
notifier (->> stream
(rx/filter (ptk/type? ::dwc/commit-changes)) on-dirty
(rx/debounce 2000) (fn []
(rx/merge stoper))] ;; Enable reload stoper
(rx/merge (obj/set! js/window "onbeforeunload" (constantly false))
(->> stream (st/emit! (update-persistence-status {:status :pending})))
(rx/filter (ptk/type? ::dwc/commit-changes))
(rx/map deref) on-saving
(rx/tap enable-reload-stoper) (fn []
(rx/buffer-until notifier) (st/emit! (update-persistence-status {:status :saving})))
(rx/map vec)
(rx/filter (complement empty?)) on-saved
(rx/map #(persist-changes file-id %)) (fn []
(rx/take-until (rx/delay 100 stoper))) ;; Disable reload stoper
(->> stream (obj/set! js/window "onbeforeunload" nil)
(rx/filter (ptk/type? ::changes-persisted)) (st/emit! (update-persistence-status {:status :saved})))]
(rx/tap disable-reload-stoper)
(rx/ignore) (->> (rx/merge
(rx/take-until stoper)))))))) (->> stream
(rx/filter (ptk/type? ::dwc/commit-changes))
(rx/map deref)
(rx/tap on-dirty)
(rx/buffer-until notifier)
(rx/map vec)
(rx/filter (complement empty?))
(rx/map #(persist-changes file-id %))
(rx/tap on-saving)
(rx/take-until (rx/delay 100 stoper)))
(->> stream
(rx/filter (ptk/type? ::changes-persisted))
(rx/tap on-saved)
(rx/ignore)
(rx/take-until stoper)))
(rx/subs #(st/emit! %)))))))
(defn persist-changes (defn persist-changes
[file-id changes] [file-id changes]
(ptk/reify ::persist-changes (ptk/reify ::persist-changes
ptk/UpdateEvent
(update [_ state]
(let [conj (fnil conj [])
chng {:id (uuid/next)
:changes changes}]
(update-in state [:workspace-persistence :queue] conj chng)))
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(let [sid (:session-id state) (let [sid (:session-id state)
file (:workspace-file state)] file (:workspace-file state)
(when (= (:id file) file-id) queue (get-in state [:workspace-persistence :queue] [])
(let [changes (into [] (mapcat identity) changes) xf-cat (comp (mapcat :changes)
params {:id (:id file) (mapcat identity))
:revn (:revn file) changes (into [] xf-cat queue)
:session-id sid params {:id (:id file)
:changes changes}] :revn (:revn file)
(->> (rp/mutation :update-file params) :session-id sid
(rx/map (fn [lagged] :changes changes}
(if (= #{sid} (into #{} (map :session-id) lagged))
(map #(assoc % :changes []) lagged) ids (into #{} (map :id) queue)
lagged)))
(rx/mapcat seq) update-persistence-queue
(rx/map #(shapes-changes-persisted file-id %))))))))) (fn [state]
(update-in state [:workspace-persistence :queue]
(fn [items] (into [] (remove #(ids (:id %))) items))))
handle-response
(fn [lagged]
(let [lagged (cond->> lagged
(= #{sid} (into #{} (map :session-id) lagged))
(map #(assoc % :changes [])))]
(rx/concat
(rx/of update-persistence-queue)
(->> (rx/of lagged)
(rx/mapcat seq)
(rx/map #(shapes-changes-persisted file-id %))))))
on-error
(fn [error]
(rx/of (update-persistence-status {:status :error
:reason (:type error)})))]
(when (= file-id (:id file))
(->> (rp/mutation :update-file params)
(rx/mapcat handle-response)
(rx/catch on-error)))))))
(defn update-persistence-status
[{:keys [status reason]}]
(ptk/reify ::update-persistence-status
ptk/UpdateEvent
(update [_ state]
(update state :workspace-persistence
(fn [local]
(assoc local
:reason reason
:status status
:updated-at (dt/now)))))))
(s/def ::shapes-changes-persisted (s/def ::shapes-changes-persisted
(s/keys :req-un [::revn ::cp/changes])) (s/keys :req-un [::revn ::cp/changes]))

View file

@ -20,11 +20,16 @@
(http/success? response) (http/success? response)
(rx/of (:body response)) (rx/of (:body response))
(http/client-error? response) (or (http/client-error? response)
(= 500 (:status response)))
(rx/throw (:body response)) (rx/throw (:body response))
(http/server-error? response) (= 502 (:status response))
(rx/throw (:body response)) (rx/throw {:type :bad-gateway
:body (:body response)})
(= 0 (:status response))
(rx/throw {:type :offline})
:else :else
(rx/throw {:type :unexpected (rx/throw {:type :unexpected

View file

@ -158,7 +158,7 @@
(defmethod ptk/handle-error :validation (defmethod ptk/handle-error :validation
[error] [error]
(js/console.error (if (map? error) (pr-str error) error)) (js/console.error "handle-error(validation):" (if (map? error) (pr-str error) error))
(when-let [explain (:explain error)] (when-let [explain (:explain error)]
(println "============ SERVER RESPONSE ERROR ================") (println "============ SERVER RESPONSE ERROR ================")
(println explain) (println explain)
@ -179,7 +179,8 @@
(if (instance? ExceptionInfo error) (if (instance? ExceptionInfo error)
(ptk/handle-error (ex-data error)) (ptk/handle-error (ex-data error))
(do (do
(js/console.error (if (map? error) (pr-str error) error)) (js/console.error "handle-error(default):"
(if (map? error) (pr-str error) error))
(ts/schedule 100 #(st/emit! (dm/show {:content "Something wrong has happened." (ts/schedule 100 #(st/emit! (dm/show {:content "Something wrong has happened."
:type :error :type :error
:timeout 5000})))))) :timeout 5000}))))))

View file

@ -9,6 +9,7 @@
(ns app.main.ui.workspace.header (ns app.main.ui.workspace.header
(:require (:require
[okulary.core :as l]
[rumext.alpha :as mf] [rumext.alpha :as mf]
[app.main.ui.icons :as i :include-macros true] [app.main.ui.icons :as i :include-macros true]
[app.config :as cfg] [app.config :as cfg]
@ -26,8 +27,37 @@
;; --- Zoom Widget ;; --- Zoom Widget
(def workspace-persistence-ref
(l/derived :workspace-persistence st/state))
(mf/defc persistence-state-widget
{::mf/wrap [mf/memo]}
[{:keys [locale]}]
(let [data (mf/deref workspace-persistence-ref)]
[:div.persistence-status-widget
(cond
(= :pending (:status data))
[:div.pending
[:span.label (t locale "workspace.header.unsaved")]]
(= :saving (:status data))
[:div.saving
[:span.icon i/toggle]
[:span.label (t locale "workspace.header.saving")]]
(= :saved (:status data))
[:div.saved
[:span.icon i/tick]
[:span.label (t locale "workspace.header.saved")]]
(= :error (:status data))
[:div.error {:title "There was an error saving the data. Please refresh if this persists."}
[:span.icon i/msg-warning]
[:span.label (t locale "workspace.header.save-error")]])]))
(mf/defc zoom-widget (mf/defc zoom-widget
{:wrap [mf/memo]} {::mf/wrap [mf/memo]}
[{:keys [zoom [{:keys [zoom
on-increase on-increase
on-decrease on-decrease
@ -58,25 +88,25 @@
(mf/defc menu (mf/defc menu
[{:keys [layout project file] :as props}] [{:keys [layout project file] :as props}]
(let [show-menu? (mf/use-state false) (let [show-menu? (mf/use-state false)
locale (i18n/use-locale) locale (mf/deref i18n/locale)
add-shared-fn #(st/emit! nil (dw/set-file-shared (:id file) true)) add-shared-fn #(st/emit! nil (dw/set-file-shared (:id file) true))
on-add-shared on-add-shared
#(modal/show! :confirm-dialog #(modal/show! :confirm-dialog
{:message (t locale "dashboard.grid.add-shared-message" (:name file)) {:message (t locale "dashboard.grid.add-shared-message" (:name file))
:hint (t locale "dashboard.grid.add-shared-hint") :hint (t locale "dashboard.grid.add-shared-hint")
:accept-text (t locale "dashboard.grid.add-shared-accept") :accept-text (t locale "dashboard.grid.add-shared-accept")
:not-danger? true :not-danger? true
:on-accept add-shared-fn}) :on-accept add-shared-fn})
remove-shared-fn #(st/emit! nil (dw/set-file-shared (:id file) false)) remove-shared-fn #(st/emit! nil (dw/set-file-shared (:id file) false))
on-remove-shared on-remove-shared
#(modal/show! :confirm-dialog #(modal/show! :confirm-dialog
{:message (t locale "dashboard.grid.remove-shared-message" (:name file)) {:message (t locale "dashboard.grid.remove-shared-message" (:name file))
:hint (t locale "dashboard.grid.remove-shared-hint") :hint (t locale "dashboard.grid.remove-shared-hint")
:accept-text (t locale "dashboard.grid.remove-shared-accept") :accept-text (t locale "dashboard.grid.remove-shared-accept")
:not-danger? false :not-danger? false
:on-accept remove-shared-fn})] :on-accept remove-shared-fn})]
[:div.menu-section [:div.menu-section
[:div.btn-icon-dark.btn-small {:on-click #(reset! show-menu? true)} i/actions] [:div.btn-icon-dark.btn-small {:on-click #(reset! show-menu? true)} i/actions]
@ -149,11 +179,10 @@
(mf/defc header (mf/defc header
[{:keys [file layout project page-id] :as props}] [{:keys [file layout project page-id] :as props}]
(let [locale (i18n/use-locale) (let [team-id (:team-id project)
team-id (:team-id project)
go-back #(st/emit! (rt/nav :dashboard-team {:team-id team-id})) go-back #(st/emit! (rt/nav :dashboard-team {:team-id team-id}))
zoom (mf/deref refs/selected-zoom) zoom (mf/deref refs/selected-zoom)
locale (i18n/use-locale) locale (mf/deref i18n/locale)
router (mf/deref refs/router) router (mf/deref refs/router)
view-url (rt/resolve router :viewer {:page-id page-id :file-id (:id file)} {:index 0})] view-url (rt/resolve router :viewer {:page-id page-id :file-id (:id file)} {:index 0})]
[:header.workspace-header [:header.workspace-header
@ -168,6 +197,9 @@
[:& presence/active-sessions]] [:& presence/active-sessions]]
[:div.options-section [:div.options-section
[:& persistence-state-widget
{:locale locale}]
[:& zoom-widget [:& zoom-widget
{:zoom zoom {:zoom zoom
:on-increase #(st/emit! (dw/increase-zoom nil)) :on-increase #(st/emit! (dw/increase-zoom nil))