0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-24 07:29:08 -05:00

🎉 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">
<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"/>
<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" />
</svg>

Before

Width:  |  Height:  |  Size: 731 B

After

Width:  |  Height:  |  Size: 707 B

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
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
// Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
// This Source Code Form is "Incompatible With Secondary Licenses", as
// defined by the Mozilla Public License, v. 2.0.
//
// Copyright (c) 2020 UXBOX Labs SL
.workspace-header {
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/UpdateEvent
(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
(watch [_ state stream]

View file

@ -32,59 +32,120 @@
(declare persist-changes)
(declare shapes-changes-persisted)
(declare update-persistence-status)
;; --- Persistence
(defn initialize-file-persistence
[file-id]
(letfn [(enable-reload-stoper []
(obj/set! js/window "onbeforeunload" (constantly false)))
(disable-reload-stoper []
(obj/set! js/window "onbeforeunload" nil))]
(ptk/reify ::initialize-persistence
ptk/WatchEvent
(watch [_ state stream]
ptk/EffectEvent
(effect [_ state stream]
(let [stoper (rx/filter #(= ::finalize %) stream)
notifier (->> stream
(rx/filter (ptk/type? ::dwc/commit-changes))
(rx/debounce 2000)
(rx/merge stoper))]
(rx/merge
(rx/merge stoper))
on-dirty
(fn []
;; Enable reload stoper
(obj/set! js/window "onbeforeunload" (constantly false))
(st/emit! (update-persistence-status {:status :pending})))
on-saving
(fn []
(st/emit! (update-persistence-status {:status :saving})))
on-saved
(fn []
;; Disable reload stoper
(obj/set! js/window "onbeforeunload" nil)
(st/emit! (update-persistence-status {:status :saved})))]
(->> (rx/merge
(->> stream
(rx/filter (ptk/type? ::dwc/commit-changes))
(rx/map deref)
(rx/tap enable-reload-stoper)
(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 disable-reload-stoper)
(rx/tap on-saved)
(rx/ignore)
(rx/take-until stoper))))))))
(rx/take-until stoper)))
(rx/subs #(st/emit! %)))))))
(defn persist-changes
[file-id 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
(watch [_ state stream]
(let [sid (:session-id state)
file (:workspace-file state)]
(when (= (:id file) file-id)
(let [changes (into [] (mapcat identity) changes)
file (:workspace-file state)
queue (get-in state [:workspace-persistence :queue] [])
xf-cat (comp (mapcat :changes)
(mapcat identity))
changes (into [] xf-cat queue)
params {:id (:id file)
:revn (:revn file)
:session-id sid
:changes changes}]
(->> (rp/mutation :update-file params)
(rx/map (fn [lagged]
(if (= #{sid} (into #{} (map :session-id) lagged))
(map #(assoc % :changes []) lagged)
lagged)))
:changes changes}
ids (into #{} (map :id) queue)
update-persistence-queue
(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 %)))))))))
(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/keys :req-un [::revn ::cp/changes]))

View file

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

View file

@ -158,7 +158,7 @@
(defmethod ptk/handle-error :validation
[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)]
(println "============ SERVER RESPONSE ERROR ================")
(println explain)
@ -179,7 +179,8 @@
(if (instance? ExceptionInfo error)
(ptk/handle-error (ex-data error))
(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."
:type :error
:timeout 5000}))))))

View file

@ -9,6 +9,7 @@
(ns app.main.ui.workspace.header
(:require
[okulary.core :as l]
[rumext.alpha :as mf]
[app.main.ui.icons :as i :include-macros true]
[app.config :as cfg]
@ -26,8 +27,37 @@
;; --- 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
{:wrap [mf/memo]}
{::mf/wrap [mf/memo]}
[{:keys [zoom
on-increase
on-decrease
@ -58,7 +88,7 @@
(mf/defc menu
[{:keys [layout project file] :as props}]
(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))
on-add-shared
@ -149,11 +179,10 @@
(mf/defc header
[{:keys [file layout project page-id] :as props}]
(let [locale (i18n/use-locale)
team-id (:team-id project)
(let [team-id (:team-id project)
go-back #(st/emit! (rt/nav :dashboard-team {:team-id team-id}))
zoom (mf/deref refs/selected-zoom)
locale (i18n/use-locale)
locale (mf/deref i18n/locale)
router (mf/deref refs/router)
view-url (rt/resolve router :viewer {:page-id page-id :file-id (:id file)} {:index 0})]
[:header.workspace-header
@ -168,6 +197,9 @@
[:& presence/active-sessions]]
[:div.options-section
[:& persistence-state-widget
{:locale locale}]
[:& zoom-widget
{:zoom zoom
:on-increase #(st/emit! (dw/increase-zoom nil))