mirror of
https://github.com/penpot/penpot.git
synced 2025-08-03 19:38:28 +02:00
♻️ Refactor file persistence layer.
This commit is contained in:
parent
182afedc54
commit
4e694ff194
86 changed files with 3205 additions and 3313 deletions
|
@ -163,6 +163,25 @@
|
|||
:else (recur (rest col1) col2 join-fn
|
||||
(core/concat acc (map (partial join-fn (first col1)) col2))))))
|
||||
|
||||
|
||||
(def sentinel
|
||||
#?(:clj (Object.)
|
||||
:cljs (js/Object.)))
|
||||
|
||||
(defn update-in-when
|
||||
[m key-seq f & args]
|
||||
(let [found (get-in m key-seq sentinel)]
|
||||
(if-not (identical? sentinel found)
|
||||
(assoc-in m key-seq (apply f found args))
|
||||
m)))
|
||||
|
||||
(defn update-when
|
||||
[m key f & args]
|
||||
(let [found (get m key sentinel)]
|
||||
(if-not (identical? sentinel found)
|
||||
(assoc m key (apply f found args))
|
||||
m)))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Data Parsing / Conversion
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
[app.common.uuid :as uuid]))
|
||||
|
||||
(def page-version 5)
|
||||
(def file-version 1)
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Page Transformation Changes
|
||||
|
@ -28,123 +29,139 @@
|
|||
|
||||
;; --- Specs
|
||||
|
||||
(s/def ::frame-id uuid?)
|
||||
(s/def ::id uuid?)
|
||||
(s/def ::shape-id uuid?)
|
||||
(s/def ::session-id uuid?)
|
||||
(s/def ::integer integer?)
|
||||
(s/def ::name string?)
|
||||
(s/def ::page-id uuid?)
|
||||
(s/def ::parent-id uuid?)
|
||||
(s/def ::string string?)
|
||||
(s/def ::type keyword?)
|
||||
(s/def ::uuid uuid?)
|
||||
|
||||
;; Page Options
|
||||
(s/def ::grid-x number?)
|
||||
(s/def ::grid-y number?)
|
||||
(s/def ::grid-color string?)
|
||||
|
||||
(s/def ::options
|
||||
(s/keys :opt-un [::grid-y
|
||||
::grid-x
|
||||
::grid-color]))
|
||||
|
||||
;; TODO: missing specs for :saved-grids
|
||||
|
||||
(s/def :internal.page.options/background string?)
|
||||
|
||||
(s/def :internal.page/options
|
||||
(s/keys :opt-un [:internal.page.options/background]))
|
||||
|
||||
;; Interactions
|
||||
|
||||
(s/def ::event-type #{:click}) ; In the future we will have more options
|
||||
(s/def ::action-type #{:navigate})
|
||||
(s/def ::destination uuid?)
|
||||
(s/def :internal.shape.interaction/event-type #{:click}) ; In the future we will have more options
|
||||
(s/def :internal.shape.interaction/action-type #{:navigate})
|
||||
(s/def :internal.shape.interaction/destination ::uuid)
|
||||
|
||||
(s/def ::interaction
|
||||
(s/keys :req-un [::event-type
|
||||
::action-type
|
||||
::destination]))
|
||||
(s/def :internal.shape/interaction
|
||||
(s/keys :req-un [:internal.shape.interaction/event-type
|
||||
:internal.shape.interaction/action-type
|
||||
:internal.shape.interaction/destination]))
|
||||
|
||||
(s/def ::interactions (s/coll-of ::interaction :kind vector?))
|
||||
(s/def :internal.shape/interactions
|
||||
(s/coll-of :internal.shape/interaction :kind vector?))
|
||||
|
||||
;; Page Data related
|
||||
(s/def ::blocked boolean?)
|
||||
(s/def ::collapsed boolean?)
|
||||
(s/def ::content any?)
|
||||
(s/def ::fill-color string?)
|
||||
(s/def ::fill-opacity number?)
|
||||
(s/def ::font-family string?)
|
||||
(s/def ::font-size number?)
|
||||
(s/def ::font-style string?)
|
||||
(s/def ::font-weight string?)
|
||||
(s/def ::hidden boolean?)
|
||||
(s/def ::letter-spacing number?)
|
||||
(s/def ::line-height number?)
|
||||
(s/def ::locked boolean?)
|
||||
(s/def ::page-id uuid?)
|
||||
(s/def ::proportion number?)
|
||||
(s/def ::proportion-lock boolean?)
|
||||
(s/def ::rx number?)
|
||||
(s/def ::ry number?)
|
||||
(s/def ::stroke-color string?)
|
||||
(s/def ::stroke-opacity number?)
|
||||
(s/def ::stroke-style #{:solid :dotted :dashed :mixed :none})
|
||||
(s/def ::stroke-width number?)
|
||||
(s/def ::stroke-alignment #{:center :inner :outer})
|
||||
(s/def ::text-align #{"left" "right" "center" "justify"})
|
||||
(s/def ::type keyword?)
|
||||
(s/def ::x number?)
|
||||
(s/def ::y number?)
|
||||
(s/def ::cx number?)
|
||||
(s/def ::cy number?)
|
||||
(s/def ::width number?)
|
||||
(s/def ::height number?)
|
||||
(s/def ::index integer?)
|
||||
(s/def ::x1 number?)
|
||||
(s/def ::y1 number?)
|
||||
(s/def ::x2 number?)
|
||||
(s/def ::y2 number?)
|
||||
(s/def :internal.shape/blocked boolean?)
|
||||
(s/def :internal.shape/collapsed boolean?)
|
||||
(s/def :internal.shape/content any?)
|
||||
(s/def :internal.shape/fill-color string?)
|
||||
(s/def :internal.shape/fill-opacity number?)
|
||||
(s/def :internal.shape/font-family string?)
|
||||
(s/def :internal.shape/font-size number?)
|
||||
(s/def :internal.shape/font-style string?)
|
||||
(s/def :internal.shape/font-weight string?)
|
||||
(s/def :internal.shape/hidden boolean?)
|
||||
(s/def :internal.shape/letter-spacing number?)
|
||||
(s/def :internal.shape/line-height number?)
|
||||
(s/def :internal.shape/locked boolean?)
|
||||
(s/def :internal.shape/page-id uuid?)
|
||||
(s/def :internal.shape/proportion number?)
|
||||
(s/def :internal.shape/proportion-lock boolean?)
|
||||
(s/def :internal.shape/rx number?)
|
||||
(s/def :internal.shape/ry number?)
|
||||
(s/def :internal.shape/stroke-color string?)
|
||||
(s/def :internal.shape/stroke-opacity number?)
|
||||
(s/def :internal.shape/stroke-style #{:solid :dotted :dashed :mixed :none})
|
||||
(s/def :internal.shape/stroke-width number?)
|
||||
(s/def :internal.shape/stroke-alignment #{:center :inner :outer})
|
||||
(s/def :internal.shape/text-align #{"left" "right" "center" "justify"})
|
||||
(s/def :internal.shape/x number?)
|
||||
(s/def :internal.shape/y number?)
|
||||
(s/def :internal.shape/cx number?)
|
||||
(s/def :internal.shape/cy number?)
|
||||
(s/def :internal.shape/width number?)
|
||||
(s/def :internal.shape/height number?)
|
||||
(s/def :internal.shape/index integer?)
|
||||
|
||||
(s/def ::suffix string?)
|
||||
(s/def ::scale number?)
|
||||
(s/def ::export
|
||||
(s/keys :req-un [::type ::suffix ::scale]))
|
||||
(s/def :internal.shape/x1 number?)
|
||||
(s/def :internal.shape/y1 number?)
|
||||
(s/def :internal.shape/x2 number?)
|
||||
(s/def :internal.shape/y2 number?)
|
||||
|
||||
(s/def ::exports (s/coll-of ::export :kind vector?))
|
||||
(s/def :internal.shape.export/suffix string?)
|
||||
(s/def :internal.shape.export/scale number?)
|
||||
(s/def :internal.shape/export
|
||||
(s/keys :req-un [::type
|
||||
:internal.shape.export/suffix
|
||||
:internal.shape.export/scale]))
|
||||
|
||||
(s/def :internal.shape/exports
|
||||
(s/coll-of :internal.shape/export :kind vector?))
|
||||
|
||||
|
||||
(s/def ::selrect (s/keys :req-un [::x
|
||||
::y
|
||||
::x1
|
||||
::y1
|
||||
::x2
|
||||
::y2
|
||||
::width
|
||||
::height]))
|
||||
(s/def :internal.shape/selrect
|
||||
(s/keys :req-un [:internal.shape/x
|
||||
:internal.shape/y
|
||||
:internal.shape/x1
|
||||
:internal.shape/y1
|
||||
:internal.shape/x2
|
||||
:internal.shape/y2
|
||||
:internal.shape/width
|
||||
:internal.shape/height]))
|
||||
|
||||
(s/def ::point (s/keys :req-un [::x ::y]))
|
||||
(s/def ::points (s/coll-of ::point :kind vector?))
|
||||
(s/def :internal.shape/point
|
||||
(s/keys :req-un [:internal.shape/x :internal.shape/y]))
|
||||
|
||||
(s/def :internal.shape/points
|
||||
(s/coll-of :internal.shape/point :kind vector?))
|
||||
|
||||
(s/def ::shape-attrs
|
||||
(s/keys :opt-un [::blocked
|
||||
::collapsed
|
||||
::content
|
||||
::fill-color
|
||||
::fill-opacity
|
||||
::font-family
|
||||
::font-size
|
||||
::font-style
|
||||
::font-weight
|
||||
::hidden
|
||||
::letter-spacing
|
||||
::line-height
|
||||
::locked
|
||||
::proportion
|
||||
::proportion-lock
|
||||
::rx ::ry
|
||||
::cx ::cy
|
||||
::x ::y
|
||||
::exports
|
||||
::stroke-color
|
||||
::stroke-opacity
|
||||
::stroke-style
|
||||
::stroke-width
|
||||
::stroke-alignment
|
||||
::text-align
|
||||
::width ::height
|
||||
::interactions
|
||||
::selrect
|
||||
::points]))
|
||||
(s/keys :opt-un [:internal.shape/blocked
|
||||
:internal.shape/collapsed
|
||||
:internal.shape/content
|
||||
:internal.shape/fill-color
|
||||
:internal.shape/fill-opacity
|
||||
:internal.shape/font-family
|
||||
:internal.shape/font-size
|
||||
:internal.shape/font-style
|
||||
:internal.shape/font-weight
|
||||
:internal.shape/hidden
|
||||
:internal.shape/letter-spacing
|
||||
:internal.shape/line-height
|
||||
:internal.shape/locked
|
||||
:internal.shape/proportion
|
||||
:internal.shape/proportion-lock
|
||||
:internal.shape/rx
|
||||
:internal.shape/ry
|
||||
:internal.shape/cx
|
||||
:internal.shape/cy
|
||||
:internal.shape/x
|
||||
:internal.shape/y
|
||||
:internal.shape/exports
|
||||
:internal.shape/stroke-color
|
||||
:internal.shape/stroke-opacity
|
||||
:internal.shape/stroke-style
|
||||
:internal.shape/stroke-width
|
||||
:internal.shape/stroke-alignment
|
||||
:internal.shape/text-align
|
||||
:internal.shape/width
|
||||
:internal.shape/height
|
||||
:internal.shape/interactions
|
||||
:internal.shape/selrect
|
||||
:internal.shape/points]))
|
||||
|
||||
(s/def ::minimal-shape
|
||||
(s/keys :req-un [::type ::name]
|
||||
|
@ -154,73 +171,157 @@
|
|||
(s/and ::minimal-shape ::shape-attrs
|
||||
(s/keys :opt-un [::id])))
|
||||
|
||||
(s/def ::shapes (s/coll-of uuid? :kind vector?))
|
||||
(s/def ::canvas (s/coll-of uuid? :kind vector?))
|
||||
(s/def :internal.page/objects (s/map-of uuid? ::shape))
|
||||
|
||||
(s/def ::objects
|
||||
(s/map-of uuid? ::shape))
|
||||
(s/def ::page
|
||||
(s/keys :req-un [::id
|
||||
::name
|
||||
:internal.page/options
|
||||
:internal.page/objects]))
|
||||
|
||||
(s/def :internal.color/name ::string)
|
||||
(s/def :internal.color/value ::string)
|
||||
|
||||
(s/def ::color
|
||||
(s/keys :req-un [::id
|
||||
:internal.color/name
|
||||
:internal.color/value]))
|
||||
|
||||
(s/def :internal.media-object/name ::string)
|
||||
(s/def :internal.media-object/path ::string)
|
||||
(s/def :internal.media-object/width ::integer)
|
||||
(s/def :internal.media-object/height ::integer)
|
||||
(s/def :internal.media-object/mtype ::string)
|
||||
(s/def :internal.media-object/thumb-path ::string)
|
||||
(s/def :internal.media-object/thumb-width ::integer)
|
||||
(s/def :internal.media-object/thumb-height ::integer)
|
||||
(s/def :internal.media-object/thumb-mtype ::string)
|
||||
|
||||
(s/def ::media-object
|
||||
(s/keys :req-un [::id ::name
|
||||
:internal.media-object/name
|
||||
:internal.media-object/path
|
||||
:internal.media-object/width
|
||||
:internal.media-object/height
|
||||
:internal.media-object/mtype
|
||||
:internal.media-object/thumb-path]))
|
||||
|
||||
|
||||
(s/def :internal.file/colors
|
||||
(s/map-of ::uuid ::color))
|
||||
|
||||
(s/def :internal.file/pages
|
||||
(s/coll-of ::uuid :kind vector?))
|
||||
|
||||
(s/def :internal.file/media
|
||||
(s/map-of ::uuid ::media-object))
|
||||
|
||||
(s/def :internal.file/pages-index
|
||||
(s/map-of ::uuid ::page))
|
||||
|
||||
(s/def ::data
|
||||
(s/keys :req-un [::version
|
||||
::options
|
||||
::objects]))
|
||||
(s/keys :req-un [:internal.file/pages-index
|
||||
:internal.file/pages]
|
||||
:opt-un [:internal.file/colors
|
||||
:internal.file/media]))
|
||||
|
||||
(s/def ::ids (s/coll-of ::us/uuid))
|
||||
(s/def ::attr keyword?)
|
||||
(s/def ::val any?)
|
||||
(s/def ::frame-id uuid?)
|
||||
(defmulti operation-spec :type)
|
||||
|
||||
(defmulti operation-spec-impl :type)
|
||||
(s/def :internal.operations.set/attr keyword?)
|
||||
(s/def :internal.operations.set/val any?)
|
||||
|
||||
(defmethod operation-spec-impl :set [_]
|
||||
(s/keys :req-un [::attr ::val]))
|
||||
(defmethod operation-spec :set [_]
|
||||
(s/keys :req-un [:internal.operations.set/attr
|
||||
:internal.operations.set/val]))
|
||||
|
||||
(s/def ::operation (s/multi-spec operation-spec-impl :type))
|
||||
(s/def ::operations (s/coll-of ::operation))
|
||||
(defmulti change-spec :type)
|
||||
|
||||
(defmulti change-spec-impl :type)
|
||||
(s/def :internal.changes.set-option/option any?)
|
||||
(s/def :internal.changes.set-option/value any?)
|
||||
|
||||
(s/def :set-option/option any? #_(s/or keyword? (s/coll-of keyword?)))
|
||||
(s/def :set-option/value any?)
|
||||
(defmethod change-spec :set-option [_]
|
||||
(s/keys :req-un [:internal.changes.set-option/option
|
||||
:internal.changes.set-option/value]))
|
||||
|
||||
(defmethod change-spec-impl :set-option [_]
|
||||
(s/keys :req-un [:set-option/option :set-option/value]))
|
||||
(s/def :internal.changes.add-obj/obj ::shape)
|
||||
|
||||
(defmethod change-spec-impl :add-obj [_]
|
||||
(s/keys :req-un [::id ::frame-id ::obj]
|
||||
(defmethod change-spec :add-obj [_]
|
||||
(s/keys :req-un [::id ::page-id ::frame-id
|
||||
:internal.changes.add-obj/obj]
|
||||
:opt-un [::parent-id]))
|
||||
|
||||
(defmethod change-spec-impl :mod-obj [_]
|
||||
(s/keys :req-un [::id ::operations]))
|
||||
(s/def ::operation (s/multi-spec operation-spec :type))
|
||||
(s/def ::operations (s/coll-of ::operation))
|
||||
|
||||
(defmethod change-spec-impl :del-obj [_]
|
||||
(s/keys :req-un [::id]))
|
||||
(defmethod change-spec :mod-obj [_]
|
||||
(s/keys :req-un [::id ::page-id ::operations]))
|
||||
|
||||
(defmethod change-spec-impl :reg-objects [_]
|
||||
(s/keys :req-un [::shapes]))
|
||||
(defmethod change-spec :del-obj [_]
|
||||
(s/keys :req-un [::id ::page-id]))
|
||||
|
||||
(defmethod change-spec-impl :mov-objects [_]
|
||||
(s/keys :req-un [::parent-id ::shapes]
|
||||
(s/def :internal.changes.reg-objects/shapes
|
||||
(s/coll-of uuid? :kind vector?))
|
||||
|
||||
(defmethod change-spec :reg-objects [_]
|
||||
(s/keys :req-un [::page-id :internal.changes.reg-objects/shapes]))
|
||||
|
||||
(defmethod change-spec :mov-objects [_]
|
||||
(s/keys :req-un [::page-id ::parent-id ::shapes]
|
||||
:opt-un [::index]))
|
||||
|
||||
(s/def ::change (s/multi-spec change-spec-impl :type))
|
||||
(defmethod change-spec :add-page [_]
|
||||
(s/or :empty (s/keys :req-un [::id ::name])
|
||||
:complete (s/keys :req-un [::page])))
|
||||
|
||||
(defmethod change-spec :mod-page [_]
|
||||
(s/keys :req-un [::id ::name]))
|
||||
|
||||
(defmethod change-spec :del-page [_]
|
||||
(s/keys :req-un [::id]))
|
||||
|
||||
(defmethod change-spec :mov-page [_]
|
||||
(s/keys :req-un [::id ::index]))
|
||||
|
||||
(defmethod change-spec :add-color [_]
|
||||
(s/keys :req-un [::color]))
|
||||
|
||||
(defmethod change-spec :mod-color [_]
|
||||
(s/keys :req-un [::color]))
|
||||
|
||||
(defmethod change-spec :del-color [_]
|
||||
(s/keys :req-un [::id]))
|
||||
|
||||
(s/def :internal.changes.media/object ::media-object)
|
||||
|
||||
(defmethod change-spec :add-media [_]
|
||||
(s/keys :req-un [:internal.changes.media/object]))
|
||||
|
||||
(defmethod change-spec :mod-media [_]
|
||||
(s/keys :req-un [:internal.changes.media/object]))
|
||||
|
||||
(defmethod change-spec :del-media [_]
|
||||
(s/keys :req-un [::id]))
|
||||
|
||||
(s/def ::change (s/multi-spec change-spec :type))
|
||||
(s/def ::changes (s/coll-of ::change))
|
||||
|
||||
(def root uuid/zero)
|
||||
|
||||
(def default-page-data
|
||||
"A reference value of the empty page data."
|
||||
{:version page-version
|
||||
:options {}
|
||||
(def empty-page-data
|
||||
{:options {}
|
||||
:name "Page"
|
||||
:objects
|
||||
{root
|
||||
{:id root
|
||||
:type :frame
|
||||
:name "root"
|
||||
:shapes []}}})
|
||||
:name "Root Frame"}}})
|
||||
|
||||
(def empty-file-data
|
||||
{:version file-version
|
||||
:pages []
|
||||
:pages-index {}})
|
||||
|
||||
(def default-color "#b1b2b5") ;; $color-gray-20
|
||||
|
||||
(def default-shape-attrs
|
||||
{:fill-color default-color
|
||||
:fill-opacity 1})
|
||||
|
@ -297,7 +398,10 @@
|
|||
(defn make-minimal-shape
|
||||
[type]
|
||||
(let [shape (d/seek #(= type (:type %)) minimal-shapes)]
|
||||
(assert shape "unexpected shape type")
|
||||
(when-not shape
|
||||
(ex/raise :type :assertion
|
||||
:code :shape-type-not-implemented
|
||||
:context {:type type}))
|
||||
(assoc shape
|
||||
:id (uuid/next)
|
||||
:x 0
|
||||
|
@ -315,13 +419,21 @@
|
|||
:points []
|
||||
:segments [])))
|
||||
|
||||
(defn make-file-data
|
||||
([] (make-file-data (uuid/next)))
|
||||
([id]
|
||||
(let [
|
||||
pd (assoc empty-page-data
|
||||
:id id
|
||||
:name "Page-1")]
|
||||
(-> empty-file-data
|
||||
(update :pages conj id)
|
||||
(update :pages-index assoc id pd)))))
|
||||
|
||||
;; --- Changes Processing Impl
|
||||
|
||||
(defmulti process-change
|
||||
(fn [data change] (:type change)))
|
||||
|
||||
(defmulti process-operation
|
||||
(fn [_ op] (:type op)))
|
||||
(defmulti process-change (fn [data change] (:type change)))
|
||||
(defmulti process-operation (fn [_ op] (:type op)))
|
||||
|
||||
(defn process-changes
|
||||
[data items]
|
||||
|
@ -332,58 +444,65 @@
|
|||
data)))
|
||||
|
||||
(defmethod process-change :set-option
|
||||
[data {:keys [option value]}]
|
||||
(let [path (if (seqable? option) option [option])]
|
||||
(if value
|
||||
(assoc-in data (into [:options] path) value)
|
||||
(assoc data :options (d/dissoc-in (:options data) path)))))
|
||||
[data {:keys [page-id option value]}]
|
||||
(d/update-in-when data [:pages-index page-id]
|
||||
(fn [data]
|
||||
(let [path (if (seqable? option) option [option])]
|
||||
(if value
|
||||
(assoc-in data (into [:options] path) value)
|
||||
(assoc data :options (d/dissoc-in (:options data) path)))))))
|
||||
|
||||
(defmethod process-change :add-obj
|
||||
[data {:keys [id obj frame-id parent-id index] :as change}]
|
||||
(let [parent-id (or parent-id frame-id)
|
||||
objects (:objects data)]
|
||||
(when (and (contains? objects parent-id)
|
||||
(contains? objects frame-id))
|
||||
(let [obj (assoc obj
|
||||
:frame-id frame-id
|
||||
:parent-id parent-id
|
||||
:id id)]
|
||||
(-> data
|
||||
(update :objects assoc id obj)
|
||||
(update-in [:objects parent-id :shapes]
|
||||
(fn [shapes]
|
||||
(let [shapes (or shapes [])]
|
||||
(cond
|
||||
(some #{id} shapes) shapes
|
||||
(nil? index) (conj shapes id)
|
||||
:else (cph/insert-at-index shapes index [id]))))))))))
|
||||
[data {:keys [id obj page-id frame-id parent-id index] :as change}]
|
||||
(d/update-in-when data [:pages-index page-id]
|
||||
(fn [data]
|
||||
(let [parent-id (or parent-id frame-id)
|
||||
objects (:objects data)]
|
||||
(when (and (contains? objects parent-id)
|
||||
(contains? objects frame-id))
|
||||
(let [obj (assoc obj
|
||||
:frame-id frame-id
|
||||
:parent-id parent-id
|
||||
:id id)]
|
||||
(-> data
|
||||
(update :objects assoc id obj)
|
||||
(update-in [:objects parent-id :shapes]
|
||||
(fn [shapes]
|
||||
(let [shapes (or shapes [])]
|
||||
(cond
|
||||
(some #{id} shapes) shapes
|
||||
(nil? index) (conj shapes id)
|
||||
:else (cph/insert-at-index shapes index [id]))))))))))))
|
||||
|
||||
(defmethod process-change :mod-obj
|
||||
[data {:keys [id operations] :as change}]
|
||||
(update data :objects
|
||||
(fn [objects]
|
||||
(if-let [obj (get objects id)]
|
||||
(assoc objects id (reduce process-operation obj operations))
|
||||
objects))))
|
||||
[data {:keys [id page-id operations] :as change}]
|
||||
(d/update-in-when data [:pages-index page-id :objects]
|
||||
(fn [objects]
|
||||
(if-let [obj (get objects id)]
|
||||
(assoc objects id (reduce process-operation obj operations))
|
||||
objects))))
|
||||
|
||||
(defmethod process-change :del-obj
|
||||
[data {:keys [id] :as change}]
|
||||
(when-let [{:keys [frame-id shapes] :as obj} (get-in data [:objects id])]
|
||||
(let [objects (:objects data)
|
||||
parent-id (cph/get-parent id objects)
|
||||
parent (get objects parent-id)
|
||||
data (update data :objects dissoc id)]
|
||||
(cond-> data
|
||||
(and (not= parent-id frame-id)
|
||||
(= :group (:type parent)))
|
||||
(update-in [:objects parent-id :shapes] (fn [s] (filterv #(not= % id) s)))
|
||||
[data {:keys [page-id id] :as change}]
|
||||
(letfn [(delete-object [objects id]
|
||||
(if-let [target (get objects id)]
|
||||
(let [parent-id (cph/get-parent id objects)
|
||||
frame-id (:frame-id target)
|
||||
parent (get objects parent-id)
|
||||
objects (dissoc objects id)]
|
||||
(cond-> objects
|
||||
(and (not= parent-id frame-id)
|
||||
(= :group (:type parent)))
|
||||
(update-in [parent-id :shapes] (fn [s] (filterv #(not= % id) s)))
|
||||
|
||||
(contains? objects frame-id)
|
||||
(update-in [:objects frame-id :shapes] (fn [s] (filterv #(not= % id) s)))
|
||||
|
||||
(seq shapes) ; Recursive delete all dependend objects
|
||||
(as-> $ (reduce #(or (process-change %1 {:type :del-obj :id %2}) %1) $ shapes))))))
|
||||
(contains? objects frame-id)
|
||||
(update-in [frame-id :shapes] (fn [s] (filterv #(not= % id) s)))
|
||||
|
||||
(seq (:shapes target)) ; Recursive delete all
|
||||
; dependend objects
|
||||
(as-> $ (reduce delete-object $ (:shapes target)))))
|
||||
objects))]
|
||||
(d/update-in-when data [:pages-index page-id :objects] delete-object id)))
|
||||
|
||||
(defn rotation-modifiers
|
||||
[center shape angle]
|
||||
|
@ -395,126 +514,173 @@
|
|||
:displacement displacement}))
|
||||
|
||||
(defmethod process-change :reg-objects
|
||||
[data {:keys [shapes]}]
|
||||
(let [objects (:objects data)
|
||||
xfm (comp
|
||||
(mapcat #(cons % (cph/get-parents % objects)))
|
||||
(map #(get objects %))
|
||||
(filter #(= (:type %) :group))
|
||||
(map :id)
|
||||
(distinct))
|
||||
[data {:keys [page-id shapes]}]
|
||||
(letfn [(reg-objects [objects]
|
||||
(reduce #(update %1 %2 update-group %1) objects
|
||||
(sequence (comp
|
||||
(mapcat #(cons % (cph/get-parents % objects)))
|
||||
(map #(get objects %))
|
||||
(filter #(= (:type %) :group))
|
||||
(map :id)
|
||||
(distinct))
|
||||
shapes)))
|
||||
(update-group [group objects]
|
||||
(let [gcenter (geom/center group)
|
||||
gxfm (comp
|
||||
(map #(get objects %))
|
||||
(map #(-> %
|
||||
(assoc :modifiers
|
||||
(rotation-modifiers gcenter % (- (:rotation group 0))))
|
||||
(geom/transform-shape))))
|
||||
selrect (-> (into [] gxfm (:shapes group))
|
||||
(geom/selection-rect))]
|
||||
|
||||
ids (into [] xfm shapes)
|
||||
;; Rotate the group shape change the data and rotate back again
|
||||
(-> group
|
||||
(assoc-in [:modifiers :rotation] (- (:rotation group)))
|
||||
(geom/transform-shape)
|
||||
(merge (select-keys selrect [:x :y :width :height]))
|
||||
(assoc-in [:modifiers :rotation] (:rotation group))
|
||||
(geom/transform-shape))))]
|
||||
|
||||
update-group
|
||||
(fn [group data]
|
||||
(let [objects (:objects data)
|
||||
gcenter (geom/center group)
|
||||
|
||||
gxfm (comp
|
||||
(map #(get objects %))
|
||||
(map #(-> %
|
||||
(assoc :modifiers
|
||||
(rotation-modifiers gcenter % (- (:rotation group 0))))
|
||||
(geom/transform-shape))))
|
||||
|
||||
selrect (-> (into [] gxfm (:shapes group))
|
||||
(geom/selection-rect))]
|
||||
|
||||
;; Rotate the group shape change the data and rotate back again
|
||||
(-> group
|
||||
(assoc-in [:modifiers :rotation] (- (:rotation group)))
|
||||
(geom/transform-shape)
|
||||
(merge (select-keys selrect [:x :y :width :height]))
|
||||
(assoc-in [:modifiers :rotation] (:rotation group))
|
||||
(geom/transform-shape))))]
|
||||
|
||||
(reduce #(update-in %1 [:objects %2] update-group %1) data ids)))
|
||||
(d/update-in-when data [:pages-index page-id :objects] reg-objects)))
|
||||
|
||||
|
||||
(defmethod process-change :mov-objects
|
||||
[data {:keys [parent-id shapes index] :as change}]
|
||||
(let [
|
||||
;; Check if the move from shape-id -> parent-id is valid
|
||||
[data {:keys [parent-id shapes index page-id] :as change}]
|
||||
(letfn [(is-valid-move? [objects shape-id]
|
||||
(let [invalid-targets (cph/calculate-invalid-targets shape-id objects)]
|
||||
(and (not (invalid-targets parent-id))
|
||||
(cph/valid-frame-target shape-id parent-id objects))))
|
||||
|
||||
is-valid-move
|
||||
(fn [shape-id]
|
||||
(let [invalid-targets (cph/calculate-invalid-targets shape-id (:objects data))]
|
||||
(and (not (invalid-targets parent-id))
|
||||
(cph/valid-frame-target shape-id parent-id (:objects data)))))
|
||||
(insert-items [prev-shapes index shapes]
|
||||
(let [prev-shapes (or prev-shapes [])]
|
||||
(if index
|
||||
(cph/insert-at-index prev-shapes index shapes)
|
||||
(reduce (fn [acc id]
|
||||
(if (some #{id} acc)
|
||||
acc
|
||||
(conj acc id)))
|
||||
prev-shapes
|
||||
shapes))))
|
||||
|
||||
valid? (every? is-valid-move shapes)
|
||||
(strip-id [coll id]
|
||||
(filterv #(not= % id) coll))
|
||||
|
||||
;; Add items into the :shapes property of the target parent-id
|
||||
insert-items
|
||||
(fn [prev-shapes]
|
||||
(let [prev-shapes (or prev-shapes [])]
|
||||
(if index
|
||||
(cph/insert-at-index prev-shapes index shapes)
|
||||
(reduce (fn [acc id]
|
||||
(if (some #{id} acc)
|
||||
acc
|
||||
(conj acc id)))
|
||||
prev-shapes
|
||||
shapes))))
|
||||
|
||||
strip-id
|
||||
(fn [id]
|
||||
(fn [coll] (filterv #(not= % id) coll)))
|
||||
|
||||
cpindex
|
||||
(reduce
|
||||
(fn [index id]
|
||||
(let [obj (get-in data [:objects id])]
|
||||
(assoc index id (:parent-id obj))))
|
||||
{} (keys (:objects data)))
|
||||
|
||||
remove-from-old-parent
|
||||
(fn remove-from-old-parent [data shape-id]
|
||||
(remove-from-old-parent [cpindex objects shape-id]
|
||||
(let [prev-parent-id (get cpindex shape-id)]
|
||||
;; Do nothing if the parent id of the shape is the same as
|
||||
;; the new destination target parent id.
|
||||
(if (= prev-parent-id parent-id)
|
||||
data
|
||||
(loop [sid shape-id
|
||||
pid prev-parent-id
|
||||
data data]
|
||||
(let [obj (get-in data [:objects pid])]
|
||||
objects
|
||||
(loop [sid shape-id
|
||||
pid prev-parent-id
|
||||
objects objects]
|
||||
(let [obj (get objects pid)]
|
||||
(if (and (= 1 (count (:shapes obj)))
|
||||
(= sid (first (:shapes obj)))
|
||||
(= :group (:type obj)))
|
||||
(recur pid
|
||||
(:parent-id obj)
|
||||
(update data :objects dissoc pid))
|
||||
(update-in data [:objects pid :shapes] (strip-id sid))))))))
|
||||
(dissoc objects pid))
|
||||
(update-in objects [pid :shapes] strip-id sid)))))))
|
||||
|
||||
parent (get-in data [:objects parent-id])
|
||||
frame (if (= :frame (:type parent))
|
||||
parent
|
||||
(get-in data [:objects (:frame-id parent)]))
|
||||
|
||||
frame-id (:id frame)
|
||||
(update-parent-id [objects id]
|
||||
(update objects id assoc :parent-id parent-id))
|
||||
|
||||
;; Update parent-id references.
|
||||
update-parent-id
|
||||
(fn [data id]
|
||||
(update-in data [:objects id] assoc :parent-id parent-id))
|
||||
;; Updates the frame-id references that might be outdated
|
||||
(update-frame-ids [frame-id objects id]
|
||||
(let [objects (assoc-in objects [id :frame-id] frame-id)
|
||||
obj (get objects id)]
|
||||
(cond-> objects
|
||||
(not= :frame (:type obj))
|
||||
(as-> $$ (reduce (partial update-frame-ids frame-id) $$ (:shapes obj))))))
|
||||
|
||||
;; Updates the frame-id references that might be outdated
|
||||
update-frame-ids
|
||||
(fn update-frame-ids [data id]
|
||||
(let [data (assoc-in data [:objects id :frame-id] frame-id)
|
||||
obj (get-in data [:objects id])]
|
||||
(cond-> data
|
||||
(not= :frame (:type obj))
|
||||
(as-> $$ (reduce update-frame-ids $$ (:shapes obj))))))]
|
||||
(move-objects [objects]
|
||||
(let [valid? (every? (partial is-valid-move? objects) shapes)
|
||||
cpindex (reduce (fn [index id]
|
||||
(let [obj (get objects id)]
|
||||
(assoc! index id (:parent-id obj))))
|
||||
(transient {})
|
||||
(keys objects))
|
||||
cpindex (persistent! cpindex)
|
||||
|
||||
(when valid?
|
||||
(as-> data $
|
||||
(update-in $ [:objects parent-id :shapes] insert-items)
|
||||
(reduce update-parent-id $ shapes)
|
||||
(reduce remove-from-old-parent $ shapes)
|
||||
(reduce update-frame-ids $ (get-in $ [:objects parent-id :shapes]))))))
|
||||
parent (get-in data [:objects parent-id])
|
||||
parent (get objects parent-id)
|
||||
frame (if (= :frame (:type parent))
|
||||
parent
|
||||
(get objects (:frame-id parent)))
|
||||
|
||||
frm-id (:id frame)]
|
||||
|
||||
(if valid?
|
||||
(as-> objects $
|
||||
(update-in $ [parent-id :shapes] insert-items index shapes)
|
||||
(reduce update-parent-id $ shapes)
|
||||
(reduce (partial remove-from-old-parent cpindex) $ shapes)
|
||||
(reduce (partial update-frame-ids frm-id) $ (get-in $ [parent-id :shapes])))
|
||||
objects)))]
|
||||
|
||||
(d/update-in-when data [:pages-index page-id :objects] move-objects)))
|
||||
|
||||
(defmethod process-change :add-page
|
||||
[data {:keys [id name page]}]
|
||||
(cond
|
||||
(and (string? name) (uuid? id))
|
||||
(let [page (assoc empty-page-data
|
||||
:id id
|
||||
:name name)]
|
||||
(-> data
|
||||
(update :pages conj id)
|
||||
(update :pages-index assoc id page)))
|
||||
|
||||
(map? page)
|
||||
(->> data
|
||||
(update :pages conj (:id page)
|
||||
(update :pages-index assoc (:id page) page)))
|
||||
|
||||
:else
|
||||
(ex/raise :type :conflict
|
||||
:hint "name or page should be provided, never both")))
|
||||
|
||||
(defmethod process-change :mod-page
|
||||
[data {:keys [id name]}]
|
||||
(d/update-in-when data [:pages-index id] assoc :name name))
|
||||
|
||||
(defmethod process-change :del-page
|
||||
[data {:keys [id]}]
|
||||
(-> data
|
||||
(update :pages (fn [pages] (filterv #(not= % id) pages)))
|
||||
(update :pages-index dissoc id)))
|
||||
|
||||
(defmethod process-change :mov-page
|
||||
[data {:keys [id index]}]
|
||||
(update data :pages cph/insert-at-index index [id]))
|
||||
|
||||
(defmethod process-change :add-color
|
||||
[data {:keys [color]}]
|
||||
(update data :colors assoc (:id color) color))
|
||||
|
||||
(defmethod process-change :mod-color
|
||||
[data {:keys [color]}]
|
||||
(d/update-in-when data [:colors (:id color)] merge color))
|
||||
|
||||
(defmethod process-change :del-color
|
||||
[data {:keys [id]}]
|
||||
(update data :colors dissoc id))
|
||||
|
||||
(defmethod process-change :add-media
|
||||
[data {:keys [object]}]
|
||||
(update data :media assoc (:id object) object))
|
||||
|
||||
(defmethod process-change :mod-media
|
||||
[data {:keys [object]}]
|
||||
(d/update-in-when data [:media (:id object)] merge object))
|
||||
|
||||
(defmethod process-change :del-media
|
||||
[data {:keys [id]}]
|
||||
(update data :media dissoc id))
|
||||
|
||||
(defmethod process-operation :set
|
||||
[shape op]
|
||||
|
@ -526,5 +692,6 @@
|
|||
|
||||
(defmethod process-operation :default
|
||||
[shape op]
|
||||
(ex/raise :type :operation-not-implemented
|
||||
(ex/raise :type :not-implemented
|
||||
:code :operation-not-implemented
|
||||
:context {:type (:type op)}))
|
||||
|
|
|
@ -68,8 +68,8 @@
|
|||
(d/index-of (:shapes prt) id)))
|
||||
|
||||
(defn insert-at-index
|
||||
[shapes index ids]
|
||||
(let [[before after] (split-at index shapes)
|
||||
[objects index ids]
|
||||
(let [[before after] (split-at index objects)
|
||||
p? (set ids)]
|
||||
(d/concat []
|
||||
(remove p? before)
|
||||
|
|
|
@ -8,23 +8,27 @@
|
|||
[app.common.uuid :as uuid]
|
||||
[app.common.data :as d]))
|
||||
|
||||
;; TODO: revisit this
|
||||
;; TODO: revisit this and rename to file-migrations
|
||||
|
||||
(defmulti migrate :version)
|
||||
|
||||
(defn migrate-data
|
||||
([data]
|
||||
(if (= (:version data) cp/page-version)
|
||||
(if (= (:version data) cp/file-version)
|
||||
data
|
||||
(reduce #(migrate-data %1 %2 (inc %2))
|
||||
data
|
||||
(range (:version data 0) cp/page-version))))
|
||||
(range (:version data 0) cp/file-version))))
|
||||
|
||||
([data from-version to-version]
|
||||
(-> data
|
||||
(assoc :version to-version)
|
||||
(migrate))))
|
||||
|
||||
(defn migrate-file
|
||||
[file]
|
||||
(update file :data migrate-data))
|
||||
|
||||
;; Default handler, noop
|
||||
(defmethod migrate :default [data] data)
|
||||
|
||||
|
@ -37,49 +41,15 @@
|
|||
(into index (map #(vector % id) (:shapes obj []))))
|
||||
{} objects))
|
||||
|
||||
(defmethod migrate 5
|
||||
[data]
|
||||
(update data :objects
|
||||
(fn [objects]
|
||||
(let [index (generate-child-parent-index objects)]
|
||||
(d/mapm
|
||||
(fn [id obj]
|
||||
(let [parent-id (get index id)]
|
||||
(assoc obj :parent-id parent-id)))
|
||||
objects)))))
|
||||
|
||||
;; We changed the internal model of the shapes so they have their
|
||||
;; selection rect and the vertices
|
||||
|
||||
(defmethod migrate 4
|
||||
[data]
|
||||
|
||||
(letfn [;; Creates a new property `points` that stores the
|
||||
;; transformed points inside the shape this will be used for
|
||||
;; the snaps and the selection rect
|
||||
(calculate-shape-points [objects]
|
||||
(->> objects
|
||||
(d/mapm
|
||||
(fn [id shape]
|
||||
(if (= (:id shape) uuid/zero)
|
||||
shape
|
||||
(assoc shape :points (gsh/shape->points shape)))))))
|
||||
|
||||
;; Creates a new property `selrect` that stores the
|
||||
;; selection rect for the shape
|
||||
(calculate-shape-selrects [objects]
|
||||
(->> objects
|
||||
(d/mapm
|
||||
(fn [id shape]
|
||||
(if (= (:id shape) uuid/zero)
|
||||
shape
|
||||
(assoc shape :selrect (gsh/points->selrect (:points shape))))))))]
|
||||
(-> data
|
||||
;; Adds vertices to shapes
|
||||
(update :objects calculate-shape-points)
|
||||
|
||||
;; Creates selection rects for shapes
|
||||
(update :objects calculate-shape-selrects))))
|
||||
|
||||
;; (defmethod migrate 5
|
||||
;; [data]
|
||||
;; (update data :objects
|
||||
;; (fn [objects]
|
||||
;; (let [index (generate-child-parent-index objects)]
|
||||
;; (d/mapm
|
||||
;; (fn [id obj]
|
||||
;; (let [parent-id (get index id)]
|
||||
;; (assoc obj :parent-id parent-id)))
|
||||
;; objects)))))
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +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) 2016-2019 Andrey Antukh <niwi@niwi.nz>
|
||||
;; 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
|
||||
|
||||
(ns app.common.spec
|
||||
"Data manipulation and query helper functions."
|
||||
|
@ -103,7 +106,11 @@
|
|||
|
||||
(defn spec-assert
|
||||
[spec x]
|
||||
(s/assert* spec x))
|
||||
(if (s/valid? spec x)
|
||||
x
|
||||
(ex/raise :type :assertion
|
||||
:data (s/explain-data spec x)
|
||||
#?@(:cljs [:stack (.-stack (ex-info "assertion" {}))]))))
|
||||
|
||||
(defmacro assert
|
||||
"Development only assertion macro."
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue