0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-10 00:58:26 -05:00

Refactor page history state and events.

This commit is contained in:
Andrey Antukh 2016-03-29 21:45:42 +03:00
parent e2a22266bd
commit f43faa30b6
7 changed files with 234 additions and 89 deletions

View file

@ -21,6 +21,10 @@
[uxbox.util.datetime :as dt]
[uxbox.util.data :refer (without-keys)]))
(defprotocol IPageUpdate
"A marker protocol for mark events that alters the
page and is subject to perform a backend synchronization.")
;; --- Pages Fetched
(defrecord PagesFetched [pages]
@ -82,20 +86,12 @@
(sc/validate! +create-page-schema+ data)
(map->CreatePage data))
;; --- Update Page
(defrecord UpdatePage [id name width height layout]
rs/UpdateEvent
(-apply-update [_ state]
(letfn [(updater [page]
(merge page
(when width {:width width})
(when height {:height height})
(when name {:name name})))]
(update-in state [:pages-by-id id] updater)))
;; --- Sync Page
(defrecord SyncPage [id]
rs/WatchEvent
(-apply-watch [this state s]
(println "SyncPage")
(letfn [(on-success [{page :payload}]
(rx/of
#(assoc-in % [:pages-by-id id :version] (:version page))
@ -108,31 +104,38 @@
(rx/mapcat on-success)
(rx/catch on-failure))))))
(def ^:const +update-page-schema+
{:name [sc/required sc/string]
:width [sc/required sc/integer]
:height [sc/required sc/integer]
:layout [sc/required sc/string]})
(defn sync-page
[id]
(SyncPage. id))
;; --- Update Page
(declare fetch-page-history)
(declare fetch-pinned-page-history)
(defrecord UpdatePage [id]
rs/WatchEvent
(-apply-watch [this state s]
(println "UpdatePage")
(let [page (get-in state [:pages-by-id id])]
(if (:history page)
(rx/empty)
(rx/of (sync-page id)
(fetch-page-history id)
(fetch-pinned-page-history id))))))
(defn update-page
[data]
(sc/validate! +update-page-schema+ data)
(map->UpdatePage data))
[id]
(UpdatePage. id))
(defn watch-page-changes
[id]
(letfn [(on-page-change [buffer]
(let [page (second buffer)]
(rs/emit! (update-page page))))]
(let [lens (l/getter #(stpr/pack-page % id))]
(as-> (l/focus-atom lens st/state) $
(rx/from-atom $)
(rx/debounce 1000 $)
(rx/scan (fn [acc page]
(if (>= (:version page) (:version acc)) page acc)) $)
(rx/dedupe #(dissoc % :version) $)
(rx/buffer 2 1 $)
(rx/subscribe $ on-page-change #(throw %))))))
(letfn [(on-value []
(rs/emit! (update-page id)))]
(as-> rs/stream $
(rx/filter #(satisfies? IPageUpdate %) $)
(rx/debounce 2000 $)
(rx/on-next $ on-value))))
;; --- Update Page Metadata
@ -161,6 +164,12 @@
(rx/map on-success)
(rx/catch on-failure)))))
(def ^:const +update-page-schema+
{:name [sc/required sc/string]
:width [sc/required sc/integer]
:height [sc/required sc/integer]
:layout [sc/required sc/string]})
(defn update-page-metadata
[data]
(sc/validate! +update-page-schema+ data)
@ -269,8 +278,7 @@
(let [page (get-in state [:pages-by-id id])
page' (assoc page
:history true
:data (:data history)
:version (:version history))]
:data (:data history))]
(-> state
(stpr/unpack-page page')
(assoc-in [:workspace :history :selected] (:id history)))))))
@ -278,3 +286,35 @@
(defn select-page-history
[id history]
(SelectPageHistory. id history))
;; --- Apply selected history
(defrecord ApplySelectedHistory [id]
rs/UpdateEvent
(-apply-update [_ state]
(println "ApplySelectedHistory" id)
(-> state
(update-in [:pages-by-id id] dissoc :history)
(assoc-in [:workspace :history :selected] nil)))
rs/WatchEvent
(-apply-watch [_ state s]
(rx/of (update-page id))))
(defn apply-selected-history
[id]
(ApplySelectedHistory. id))
;; --- Discard Selected History
(defrecord DiscardSelectedHistory [id]
rs/UpdateEvent
(-apply-update [_ state]
(let [packed (get-in state [:pagedata-by-id id])]
(-> state
(stpr/unpack-page packed)
(assoc-in [:workspace :history :selected] nil)))))
(defn discard-selected-history
[id]
(DiscardSelectedHistory. id))

View file

@ -16,6 +16,7 @@
[uxbox.schema :as sc]
[uxbox.xforms :as xf]
[uxbox.shapes :as sh]
[uxbox.data.pages :as udp]
[uxbox.util.geom.point :as gpt]
[uxbox.util.data :refer (index-of)]))
@ -77,6 +78,7 @@
[shape]
(sc/validate! +shape-schema+ shape)
(reify
udp/IPageUpdate
rs/UpdateEvent
(-apply-update [_ state]
(let [page (get-in state [:workspace :page])]
@ -86,6 +88,7 @@
"Remove the shape using its id."
[id]
(reify
udp/IPageUpdate
rs/UpdateEvent
(-apply-update [_ state]
(let [shape (get-in state [:shapes-by-id id])]
@ -96,6 +99,7 @@
[sid delta]
{:pre [(gpt/point? delta)]}
(reify
udp/IPageUpdate
rs/UpdateEvent
(-apply-update [_ state]
(let [shape (get-in state [:shapes-by-id sid])]
@ -105,6 +109,7 @@
[sid {:keys [x1 y1 x2 y2] :as opts}]
(sc/validate! +shape-line-attrs-schema+ opts)
(reify
udp/IPageUpdate
rs/UpdateEvent
(-apply-update [_ state]
(let [shape (get-in state [:shapes-by-id sid])
@ -119,6 +124,7 @@
(>= rotation 0)
(>= 360 rotation)]}
(reify
udp/IPageUpdate
rs/UpdateEvent
(-apply-update [_ state]
(update-in state [:shapes-by-id sid]
@ -134,6 +140,7 @@
[sid {:keys [width height] :as opts}]
(sc/validate! +shape-size-schema+ opts)
(reify
udp/IPageUpdate
rs/UpdateEvent
(-apply-update [_ state]
(let [shape (get-in state [:shapes-by-id sid])
@ -143,6 +150,7 @@
(defn update-vertex-position
[id {:keys [vid delta]}]
(reify
udp/IPageUpdate
rs/UpdateEvent
(-apply-update [_ state]
(update-in state [:shapes-by-id id] sh/move-vertex vid delta))))
@ -161,6 +169,7 @@
[sid {:keys [content]}]
{:pre [(string? content)]}
(reify
udp/IPageUpdate
rs/UpdateEvent
(-apply-update [_ state]
(assoc-in state [:shapes-by-id sid :content] content))))
@ -169,6 +178,7 @@
[sid {:keys [color opacity] :as opts}]
(sc/validate! +shape-fill-attrs-schema+ opts)
(reify
udp/IPageUpdate
rs/UpdateEvent
(-apply-update [_ state]
(update-in state [:shapes-by-id sid]
@ -181,6 +191,7 @@
letter-spacing line-height] :as opts}]
(sc/validate! +shape-font-attrs-schema+ opts)
(reify
udp/IPageUpdate
rs/UpdateEvent
(-apply-update [_ state]
(update-in state [:shapes-by-id sid :font]
@ -197,6 +208,7 @@
[sid {:keys [color opacity type width] :as opts}]
(sc/validate! +shape-stroke-attrs-schema+ opts)
(reify
udp/IPageUpdate
rs/UpdateEvent
(-apply-update [_ state]
(update-in state [:shapes-by-id sid]
@ -210,6 +222,7 @@
[sid {:keys [rx ry] :as opts}]
(sc/validate! +shape-radius-attrs-schema+ opts)
(reify
udp/IPageUpdate
rs/UpdateEvent
(-apply-update [_ state]
(update-in state [:shapes-by-id sid]
@ -220,6 +233,7 @@
(defn hide-shape
[sid]
(reify
udp/IPageUpdate
rs/UpdateEvent
(-apply-update [_ state]
(assoc-in state [:shapes-by-id sid :hidden] true))
@ -235,6 +249,7 @@
(defn show-shape
[sid]
(reify
udp/IPageUpdate
rs/UpdateEvent
(-apply-update [_ state]
(assoc-in state [:shapes-by-id sid :hidden] false))
@ -250,6 +265,7 @@
(defn block-shape
[sid]
(reify
udp/IPageUpdate
rs/UpdateEvent
(-apply-update [_ state]
(assoc-in state [:shapes-by-id sid :blocked] true))
@ -265,6 +281,7 @@
(defn unblock-shape
[sid]
(reify
udp/IPageUpdate
rs/UpdateEvent
(-apply-update [_ state]
(assoc-in state [:shapes-by-id sid :blocked] false))
@ -280,6 +297,7 @@
(defn lock-shape
[sid]
(reify
udp/IPageUpdate
rs/UpdateEvent
(-apply-update [_ state]
(assoc-in state [:shapes-by-id sid :locked] true))
@ -295,6 +313,7 @@
(defn unlock-shape
[sid]
(reify
udp/IPageUpdate
rs/UpdateEvent
(-apply-update [_ state]
(assoc-in state [:shapes-by-id sid :locked] false))
@ -314,6 +333,7 @@
{:pre [(not (nil? tid))
(not (nil? sid))]}
(reify
udp/IPageUpdate
rs/UpdateEvent
(-apply-update [_ state]
(stsh/drop-shape state sid tid loc))))

View file

@ -16,6 +16,7 @@
[uxbox.schema :as sc]
[uxbox.xforms :as xf]
[uxbox.shapes :as sh]
[uxbox.data.pages :as udp]
[uxbox.data.shapes :as uds]
[uxbox.util.geom.point :as gpt]
[uxbox.util.data :refer (index-of)]))
@ -123,6 +124,7 @@
(let [groups (into #{} (map :group shapes))]
(= 1 (count groups))))]
(reify
udp/IPageUpdate
rs/UpdateEvent
(-apply-update [_ state]
(let [shapes-by-id (get state :shapes-by-id)
@ -147,6 +149,7 @@
(defn duplicate-selected
[]
(reify
udp/IPageUpdate
rs/UpdateEvent
(-apply-update [_ state]
(let [selected (get-in state [:workspace :selected])]

View file

@ -37,6 +37,8 @@
"ds.help.circle" "Circle (Ctrl + E)"
"ds.help.line" "Line (Ctrl + L)"
"history.alert-message" "You are seeng version %s"
"errors.auth" "Username or passwords seems to be wrong."
})

View file

@ -9,57 +9,138 @@
[uxbox.util.data :refer (classnames)]
[uxbox.util.dom :as dom]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Api
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; --- Constants
(defonce +message+ (atom nil))
(def ^:const +animation-timeout+ 600)
;; --- Helpers
(defn set-timeout!
[ms callback]
(js/setTimeout callback ms))
(defn abort-timeout!
[v]
(js/clearTimeout v))
(when v
(js/clearTimeout v)))
;; --- Public Api
(defn- clean-prev-msgstate!
[message]
(let [type (namespace (:type message))]
(case type
"notification"
(do
(abort-timeout! (:tsem-main message))
(abort-timeout! (:tsem message)))
"dialog"
(abort-timeout! (:tsem message)))))
(defn error
([message] (error message nil))
([message {:keys [timeout] :or {timeout 30000}}]
(when-let [prev-message @+message+]
(abort-timeout! (:timeout-total prev-message))
(abort-timeout! (:timeout prev-message)))
(let [timeout-total (set-timeout! (+ timeout +animation-timeout+)
#(reset! +message+ nil))
timeout (set-timeout! timeout #(swap! +message+ assoc :state :hide))]
(reset! +message+ {:type :error
([message {:keys [timeout] :or {timeout 6000}}]
(when-let [prev @+message+]
(clean-prev-msgstate! prev))
(let [timeout' (+ timeout +animation-timeout+)
tsem-main (set-timeout! timeout' #(reset! +message+ nil))
tsem (set-timeout! timeout #(swap! +message+ assoc :state :hide))]
(reset! +message+ {:type :notification/error
:state :normal
:timeout-total timeout-total
:timeout timeout
:tsem-main tsem-main
:tsem tsem
:content message}))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Component
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn dialog
[& {:keys [message on-accept on-cancel]
:or {on-cancel (constantly nil)}
:as opts}]
{:pre [(ifn? on-accept)
(string? message)]}
(when-let [prev @+message+]
(clean-prev-msgstate! prev))
(reset! +message+ {:type :dialog/simple
:state :normal
:content message
:on-accept on-accept
:on-cancel on-cancel}))
(defn close
[]
(when @+message+
(let [timeout +animation-timeout+
tsem (set-timeout! timeout #(reset! +message+ nil))]
(swap! +message+ assoc
:state :hide
:tsem tsem))))
(defn messages-render
[own]
(when-let [message (rum/react +message+)]
(let [classes (classnames :error (= (:type message) :error)
:info (= (:type message) :info)
:hide-message (= (:state message) :hide)
:quick true)]
;; --- Notification Component
(defn notification-render
[own message]
(let [msgtype (name (:type message))
classes (classnames :error (= msgtype "error")
:info (= msgtype "info")
:hide-message (= (:state message) :hide)
:quick true)]
(html
[:div.message {:class classes}
[:div.message-body
[:span.close i/close]
[:span (:content message)]]])))
(def ^:private notification-box
(mx/component
{:render notification-render
:name "notification"
:mixins [mx/static]}))
;; --- Dialog Component
(defn dialog-render
[own {:keys [on-accept on-cancel] :as message}]
(let [classes (classnames :info true
:hide-message (= (:state message) :hide))
local (:rum/local own)]
(letfn [(accept [event]
(dom/prevent-default event)
(close)
(on-accept))
(cancel [event]
(dom/prevent-default event)
(close)
(when on-cancel
(on-cancel)))]
(html
[:div.message {:class classes}
[:div.message-body
[:span.close i/close]
[:span (:content message)]
[:div.message-action
[:a.btn-transparent.btn-small "Accept"]
[:a.btn-transparent.btn-small "Cancel"]
]]]))))
[:a.btn-transparent.btn-small
{:on-click accept}
"Accept"]
[:a.btn-transparent.btn-small
{:on-click cancel}
"Cancel"]]]]))))
(def ^:private dialog-box
(mx/component
{:render dialog-render
:name "dialog"
:mixins [mx/static]}))
;; --- Main Component (entry point)
(defn messages-render
[own]
(when-let [message (rum/react +message+)]
(case (namespace (:type message))
"notification" (notification-box message)
"dialog" (dialog-box message)
(throw (ex-info "Invalid message type" message)))))
(def ^:const messages
(mx/component

View file

@ -16,6 +16,7 @@
[uxbox.ui.core :as uuc]
[uxbox.ui.icons :as i]
[uxbox.ui.mixins :as mx]
[uxbox.ui.messages :as uum]
[uxbox.ui.workspace.base :as uuwb]
[uxbox.ui.workspace.shortcuts :as wshortcuts]
[uxbox.ui.workspace.header :refer (header)]
@ -33,7 +34,10 @@
(let [[projectid pageid] (:rum/props own)]
(rs/emit! (dw/initialize projectid pageid)
(dp/fetch-projects)
(udp/fetch-pages projectid))
(udp/fetch-pages projectid)
(udp/fetch-page-history pageid)
(udp/fetch-pinned-page-history pageid))
own))
(defn- workspace-did-mount
@ -119,6 +123,8 @@
[:div
(header)
(colorpalette)
(uum/messages)
[:main.main-content
[:section.workspace-content {:class classes :on-scroll on-scroll}

View file

@ -15,13 +15,14 @@
[uxbox.state :as st]
[uxbox.shapes :as shapes]
[uxbox.library :as library]
[uxbox.util.datetime :as dt]
[uxbox.util.data :refer (read-string)]
[uxbox.data.workspace :as dw]
[uxbox.data.pages :as dpg]
[uxbox.data.pages :as udp]
[uxbox.ui.workspace.base :as wb]
[uxbox.ui.messages :as msg]
[uxbox.ui.icons :as i]
[uxbox.ui.mixins :as mx]
[uxbox.util.datetime :as dt]
[uxbox.util.data :refer (read-string)]
[uxbox.util.dom :as dom]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -38,7 +39,7 @@
(defn history-list-render
[own page history]
(let [select #(rs/emit! (dpg/select-page-history (:id page) %))
(let [select #(rs/emit! (udp/select-page-history (:id page) %))
min-version (apply min (map :version (:items history)))
show-more? (pos? min-version)]
(html
@ -58,9 +59,24 @@
[:li
[:a.btn-primary.btn-small "view more"]])])))
(defn history-list-will-update
[own]
(let [[page history] (:rum/props own)]
(if (:selected history)
(let [selected (->> (:items history)
(filter #(= (:selected history) (:id %)))
(first))]
(msg/dialog
:message (tr "history.alert-message" (:version selected))
:on-accept #(rs/emit! (udp/apply-selected-history (:id page)))
:on-cancel #(rs/emit! (udp/discard-selected-history (:id page)))))
(msg/close))
own))
(def history-list
(mx/component
{:render history-list-render
:will-update history-list-will-update
:name "history-list"
:mixins [mx/static]}))
@ -82,27 +98,6 @@
:name "history-pinned-list"
:mixins [mx/static]}))
(defn- history-toolbox-will-mount
[own]
(let [page @wb/page-l]
(rs/emit! (dpg/fetch-page-history (:id page))
(dpg/fetch-pinned-page-history (:id page)))
(add-watch wb/page-l ::key
(fn [_ _ ov nv]
(when (or (and (> (:version nv) (:version ov))
(not (:history nv)))
(not= (:id ov) (:id nv)))
(rs/emit! (dpg/fetch-page-history (:id nv))
(dpg/fetch-pinned-page-history (:id nv))))))
own))
(defn- history-toolbox-will-unmount
[own]
(rs/emit! (dpg/clean-page-history))
(remove-watch wb/page-l ::key)
own)
(defn history-toolbox-render
[own]
(let [local (:rum/local own)
@ -136,6 +131,4 @@
(mx/component
{:render history-toolbox-render
:name "document-history-toolbox"
:will-mount history-toolbox-will-mount
:will-unmount history-toolbox-will-unmount
:mixins [mx/static rum/reactive (mx/local)]}))