0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-23 23:18:48 -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]
@ -1294,7 +1299,7 @@
(dws/prepare-remove-group page-id group objects)]
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}))))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Interactions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

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]
(let [stoper (rx/filter #(= ::finalize %) stream)
notifier (->> stream
(rx/filter (ptk/type? ::dwc/commit-changes))
(rx/debounce 2000)
(rx/merge stoper))]
(rx/merge
(->> stream
(rx/filter (ptk/type? ::dwc/commit-changes))
(rx/map deref)
(rx/tap enable-reload-stoper)
(rx/buffer-until notifier)
(rx/map vec)
(rx/filter (complement empty?))
(rx/map #(persist-changes file-id %))
(rx/take-until (rx/delay 100 stoper)))
(->> stream
(rx/filter (ptk/type? ::changes-persisted))
(rx/tap disable-reload-stoper)
(rx/ignore)
(rx/take-until stoper))))))))
(ptk/reify ::initialize-persistence
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))
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 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
[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)
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)))
(rx/mapcat seq)
(rx/map #(shapes-changes-persisted file-id %)))))))))
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}
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 %))))))
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,25 +88,25 @@
(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
#(modal/show! :confirm-dialog
{:message (t locale "dashboard.grid.add-shared-message" (:name file))
:hint (t locale "dashboard.grid.add-shared-hint")
:accept-text (t locale "dashboard.grid.add-shared-accept")
:not-danger? true
:on-accept add-shared-fn})
{:message (t locale "dashboard.grid.add-shared-message" (:name file))
:hint (t locale "dashboard.grid.add-shared-hint")
:accept-text (t locale "dashboard.grid.add-shared-accept")
:not-danger? true
:on-accept add-shared-fn})
remove-shared-fn #(st/emit! nil (dw/set-file-shared (:id file) false))
on-remove-shared
#(modal/show! :confirm-dialog
{:message (t locale "dashboard.grid.remove-shared-message" (:name file))
:hint (t locale "dashboard.grid.remove-shared-hint")
:accept-text (t locale "dashboard.grid.remove-shared-accept")
:not-danger? false
:on-accept remove-shared-fn})]
{:message (t locale "dashboard.grid.remove-shared-message" (:name file))
:hint (t locale "dashboard.grid.remove-shared-hint")
:accept-text (t locale "dashboard.grid.remove-shared-accept")
:not-danger? false
:on-accept remove-shared-fn})]
[:div.menu-section
[:div.btn-icon-dark.btn-small {:on-click #(reset! show-menu? true)} i/actions]
@ -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))