♻️ Refactor file persistence layer.

This commit is contained in:
Andrey Antukh 2020-09-07 10:56:42 +02:00 committed by Alonso Torres
parent 182afedc54
commit 4e694ff194
86 changed files with 3205 additions and 3313 deletions

View file

@ -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
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View file

@ -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)}))

View file

@ -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)

View file

@ -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)))))

View file

@ -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."