mirror of
https://github.com/penpot/penpot.git
synced 2025-03-31 00:51:19 -05:00
Merge pull request #4536 from penpot/alotor-plugins-api
New plugins APIs
This commit is contained in:
commit
5436633104
15 changed files with 855 additions and 256 deletions
|
@ -22,6 +22,7 @@
|
|||
[app.common.svg :as csvg]
|
||||
[app.common.svg.path :as path]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.uuid :as uuid]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
(def default-rect
|
||||
|
@ -78,67 +79,68 @@
|
|||
(declare parse-svg-element)
|
||||
|
||||
(defn create-svg-shapes
|
||||
[svg-data {:keys [x y]} objects frame-id parent-id selected center?]
|
||||
(let [[vb-x vb-y vb-width vb-height] (svg-dimensions svg-data)
|
||||
([svg-data pos objects frame-id parent-id selected center?]
|
||||
(create-svg-shapes (uuid/next) svg-data pos objects frame-id parent-id selected center?))
|
||||
([id svg-data {:keys [x y]} objects frame-id parent-id selected center?]
|
||||
(let [[vb-x vb-y vb-width vb-height] (svg-dimensions svg-data)
|
||||
|
||||
unames (cfh/get-used-names objects)
|
||||
svg-name (str/replace (:name svg-data) ".svg" "")
|
||||
|
||||
unames (cfh/get-used-names objects)
|
||||
svg-name (str/replace (:name svg-data) ".svg" "")
|
||||
svg-data (-> svg-data
|
||||
(assoc :x (mth/round
|
||||
(if center?
|
||||
(- x vb-x (/ vb-width 2))
|
||||
x)))
|
||||
(assoc :y (mth/round
|
||||
(if center?
|
||||
(- y vb-y (/ vb-height 2))
|
||||
y)))
|
||||
(assoc :offset-x vb-x)
|
||||
(assoc :offset-y vb-y)
|
||||
(assoc :width vb-width)
|
||||
(assoc :height vb-height)
|
||||
(assoc :name svg-name))
|
||||
|
||||
svg-data (-> svg-data
|
||||
(assoc :x (mth/round
|
||||
(if center?
|
||||
(- x vb-x (/ vb-width 2))
|
||||
x)))
|
||||
(assoc :y (mth/round
|
||||
(if center?
|
||||
(- y vb-y (/ vb-height 2))
|
||||
y)))
|
||||
(assoc :offset-x vb-x)
|
||||
(assoc :offset-y vb-y)
|
||||
(assoc :width vb-width)
|
||||
(assoc :height vb-height)
|
||||
(assoc :name svg-name))
|
||||
[def-nodes svg-data]
|
||||
(-> svg-data
|
||||
(csvg/fix-default-values)
|
||||
(csvg/fix-percents)
|
||||
(csvg/extract-defs))
|
||||
|
||||
[def-nodes svg-data]
|
||||
(-> svg-data
|
||||
(csvg/fix-default-values)
|
||||
(csvg/fix-percents)
|
||||
(csvg/extract-defs))
|
||||
;; In penpot groups have the size of their children. To
|
||||
;; respect the imported svg size and empty space let's create
|
||||
;; a transparent shape as background to respect the imported
|
||||
;; size
|
||||
background
|
||||
{:tag :rect
|
||||
:attrs {:x (dm/str vb-x)
|
||||
:y (dm/str vb-y)
|
||||
:width (dm/str vb-width)
|
||||
:height (dm/str vb-height)
|
||||
:fill "none"
|
||||
:id "base-background"}
|
||||
:hidden true
|
||||
:content []}
|
||||
|
||||
;; In penpot groups have the size of their children. To
|
||||
;; respect the imported svg size and empty space let's create
|
||||
;; a transparent shape as background to respect the imported
|
||||
;; size
|
||||
background
|
||||
{:tag :rect
|
||||
:attrs {:x (dm/str vb-x)
|
||||
:y (dm/str vb-y)
|
||||
:width (dm/str vb-width)
|
||||
:height (dm/str vb-height)
|
||||
:fill "none"
|
||||
:id "base-background"}
|
||||
:hidden true
|
||||
:content []}
|
||||
svg-data (-> svg-data
|
||||
(assoc :defs def-nodes)
|
||||
(assoc :content (into [background] (:content svg-data))))
|
||||
|
||||
svg-data (-> svg-data
|
||||
(assoc :defs def-nodes)
|
||||
(assoc :content (into [background] (:content svg-data))))
|
||||
root-shape (create-svg-root id frame-id parent-id svg-data)
|
||||
root-id (:id root-shape)
|
||||
|
||||
root-shape (create-svg-root frame-id parent-id svg-data)
|
||||
root-id (:id root-shape)
|
||||
;; Create the root shape
|
||||
root-attrs (-> (:attrs svg-data)
|
||||
(csvg/format-styles))
|
||||
|
||||
;; Create the root shape
|
||||
root-attrs (-> (:attrs svg-data)
|
||||
(csvg/format-styles))
|
||||
[_ children]
|
||||
(reduce (partial create-svg-children objects selected frame-id root-id svg-data)
|
||||
[unames []]
|
||||
(d/enumerate (->> (:content svg-data)
|
||||
(mapv #(csvg/inherit-attributes root-attrs %)))))]
|
||||
|
||||
[_ children]
|
||||
(reduce (partial create-svg-children objects selected frame-id root-id svg-data)
|
||||
[unames []]
|
||||
(d/enumerate (->> (:content svg-data)
|
||||
(mapv #(csvg/inherit-attributes root-attrs %)))))]
|
||||
|
||||
[root-shape children]))
|
||||
[root-shape children])))
|
||||
|
||||
(defn create-raw-svg
|
||||
[name frame-id {:keys [x y width height offset-x offset-y]} {:keys [attrs] :as data}]
|
||||
|
@ -157,12 +159,13 @@
|
|||
:svg-viewbox vbox})))
|
||||
|
||||
(defn create-svg-root
|
||||
[frame-id parent-id {:keys [name x y width height offset-x offset-y attrs]}]
|
||||
[id frame-id parent-id {:keys [name x y width height offset-x offset-y attrs]}]
|
||||
(let [props (-> (dissoc attrs :viewBox :view-box :xmlns)
|
||||
(d/without-keys csvg/inheritable-props)
|
||||
(csvg/attrs->props))]
|
||||
(cts/setup-shape
|
||||
{:type :group
|
||||
{:id id
|
||||
:type :group
|
||||
:name name
|
||||
:frame-id frame-id
|
||||
:parent-id parent-id
|
||||
|
|
|
@ -1281,6 +1281,21 @@
|
|||
(let [cells+index (d/enumerate cells)]
|
||||
(d/seek #(in-cell? (second %) row column) cells+index)))
|
||||
|
||||
(defn free-cell-shapes
|
||||
"Removes the shape-ids from the cells previously assigned."
|
||||
[parent shape-ids]
|
||||
(let [shape-ids (set shape-ids)]
|
||||
(letfn [(free-cells
|
||||
[cells]
|
||||
(reduce-kv
|
||||
(fn [m k v]
|
||||
(if (some shape-ids (:shapes v))
|
||||
(assoc-in m [k :shapes] [])
|
||||
m))
|
||||
cells
|
||||
cells))]
|
||||
(update parent :layout-grid-cells free-cells))))
|
||||
|
||||
(defn push-into-cell
|
||||
"Push the shapes into the row/column cell and moves the rest"
|
||||
[parent shape-ids row column]
|
||||
|
@ -1295,16 +1310,17 @@
|
|||
;; Move shift the `shapes` attribute between cells
|
||||
(->> (range start-index (inc to-index))
|
||||
(map vector shape-ids)
|
||||
(reduce (fn [[parent cells] [shape-id idx]]
|
||||
;; If the shape to put in a cell is the same that is already in the cell we do nothing
|
||||
(if (= shape-id (get-in parent [:layout-grid-cells (get-in cells [idx :id]) :shapes 0]))
|
||||
[parent cells]
|
||||
(let [[parent cells] (free-cell-push parent cells idx)]
|
||||
[(update-in parent [:layout-grid-cells (get-in cells [idx :id])]
|
||||
assoc :position :manual
|
||||
:shapes [shape-id])
|
||||
cells])))
|
||||
[parent cells])
|
||||
(reduce
|
||||
(fn [[parent cells] [shape-id idx]]
|
||||
;; If the shape to put in a cell is the same that is already in the cell we do nothing
|
||||
(if (= shape-id (get-in parent [:layout-grid-cells (get-in cells [idx :id]) :shapes 0]))
|
||||
[parent cells]
|
||||
(let [[parent cells] (free-cell-push parent cells idx)]
|
||||
[(update-in parent [:layout-grid-cells (get-in cells [idx :id])]
|
||||
assoc :position :manual
|
||||
:shapes [shape-id])
|
||||
cells])))
|
||||
[parent cells])
|
||||
(first)))
|
||||
parent)))
|
||||
|
||||
|
|
|
@ -790,6 +790,7 @@
|
|||
(defn relocate-shapes
|
||||
[ids parent-id to-index & [ignore-parents?]]
|
||||
(dm/assert! (every? uuid? ids))
|
||||
(dm/assert! (set? ids))
|
||||
(dm/assert! (uuid? parent-id))
|
||||
(dm/assert! (number? to-index))
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
[app.common.types.container :as ctn]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace.changes :as dch]
|
||||
[app.main.data.workspace.selection :as dws]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
|
@ -68,7 +69,7 @@
|
|||
result)))))))
|
||||
|
||||
(defn prepare-create-group
|
||||
[changes objects page-id shapes base-name keep-name?]
|
||||
[changes id objects page-id shapes base-name keep-name?]
|
||||
(let [frame-id (:frame-id (first shapes))
|
||||
parent-id (:parent-id (first shapes))
|
||||
gname (if (and keep-name?
|
||||
|
@ -84,7 +85,8 @@
|
|||
(cfh/get-position-on-parent objects)
|
||||
inc)
|
||||
|
||||
group (cts/setup-shape {:type :group
|
||||
group (cts/setup-shape {:id id
|
||||
:type :group
|
||||
:name gname
|
||||
:shapes (mapv :id shapes)
|
||||
:selrect selrect
|
||||
|
@ -173,30 +175,43 @@
|
|||
;; GROUPS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(def group-selected
|
||||
(ptk/reify ::group-selected
|
||||
(defn group-shapes
|
||||
[id ids & {:keys [change-selection?] :or {change-selection? false}}]
|
||||
(ptk/reify ::group-shapes
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(let [page-id (:current-page-id state)
|
||||
(let [id (d/nilv id (uuid/next))
|
||||
page-id (:current-page-id state)
|
||||
objects (wsh/lookup-page-objects state page-id)
|
||||
selected (->> (wsh/lookup-selected state)
|
||||
(cfh/clean-loops objects)
|
||||
(remove #(ctn/has-any-copy-parent? objects (get objects %))))
|
||||
shapes (shapes-for-grouping objects selected)
|
||||
|
||||
shapes
|
||||
(->> ids
|
||||
(cfh/clean-loops objects)
|
||||
(remove #(ctn/has-any-copy-parent? objects (get objects %)))
|
||||
(shapes-for-grouping objects))
|
||||
parents (into #{} (map :parent-id) shapes)]
|
||||
(when-not (empty? shapes)
|
||||
(let [[group changes]
|
||||
(prepare-create-group (pcb/empty-changes it) objects page-id shapes "Group" false)]
|
||||
(prepare-create-group (pcb/empty-changes it) id objects page-id shapes "Group" false)]
|
||||
(rx/of (dch/commit-changes changes)
|
||||
(dws/select-shapes (d/ordered-set (:id group)))
|
||||
(when change-selection?
|
||||
(dws/select-shapes (d/ordered-set (:id group))))
|
||||
(ptk/data-event :layout/update {:ids parents}))))))))
|
||||
|
||||
(def ungroup-selected
|
||||
(ptk/reify ::ungroup-selected
|
||||
(def group-selected
|
||||
(ptk/reify ::group-selected
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [selected (wsh/lookup-selected state)]
|
||||
(rx/of (group-shapes nil selected))))))
|
||||
|
||||
(defn ungroup-shapes
|
||||
[ids & {:keys [change-selection?] :or {change-selection? false}}]
|
||||
(ptk/reify ::ungroup-shapes
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (wsh/lookup-page-objects state page-id)
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (wsh/lookup-page-objects state page-id)
|
||||
|
||||
prepare
|
||||
(fn [shape-id]
|
||||
|
@ -213,35 +228,42 @@
|
|||
(ctl/grid-layout? objects (:parent-id shape))
|
||||
(pcb/update-shapes [(:parent-id shape)] ctl/assign-cells {:with-objects? true}))))
|
||||
|
||||
selected (->> (wsh/lookup-selected state)
|
||||
(remove #(ctn/has-any-copy-parent? objects (get objects %)))
|
||||
;; components can't be ungrouped
|
||||
(remove #(ctk/instance-head? (get objects %))))
|
||||
changes-list (sequence
|
||||
(keep prepare)
|
||||
selected)
|
||||
ids (->> ids
|
||||
(remove #(ctn/has-any-copy-parent? objects (get objects %)))
|
||||
;; components can't be ungrouped
|
||||
(remove #(ctk/instance-head? (get objects %))))
|
||||
|
||||
changes-list (sequence (keep prepare) ids)
|
||||
|
||||
parents (into #{}
|
||||
(comp (map #(cfh/get-parent objects %))
|
||||
(keep :id))
|
||||
selected)
|
||||
ids)
|
||||
|
||||
child-ids
|
||||
(into (d/ordered-set)
|
||||
(mapcat #(dm/get-in objects [% :shapes]))
|
||||
selected)
|
||||
ids)
|
||||
|
||||
changes {:redo-changes (vec (mapcat :redo-changes changes-list))
|
||||
:undo-changes (vec (mapcat :undo-changes changes-list))
|
||||
:origin it}
|
||||
undo-id (js/Symbol)]
|
||||
|
||||
(when-not (empty? selected)
|
||||
(when-not (empty? ids)
|
||||
(rx/of (dwu/start-undo-transaction undo-id)
|
||||
(dch/commit-changes changes)
|
||||
(ptk/data-event :layout/update {:ids parents})
|
||||
(dwu/commit-undo-transaction undo-id)
|
||||
(dws/select-shapes child-ids)))))))
|
||||
(when change-selection?
|
||||
(dws/select-shapes child-ids))))))))
|
||||
|
||||
(def ungroup-selected
|
||||
(ptk/reify ::ungroup-selected
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [selected (wsh/lookup-selected state)]
|
||||
(rx/of (ungroup-shapes selected :change-selection? true))))))
|
||||
|
||||
(def mask-group
|
||||
(ptk/reify ::mask-group
|
||||
|
@ -262,7 +284,7 @@
|
|||
(= (:type (first shapes)) :group))
|
||||
[first-shape (-> (pcb/empty-changes it page-id)
|
||||
(pcb/with-objects objects))]
|
||||
(prepare-create-group (pcb/empty-changes it) objects page-id shapes "Mask" true))
|
||||
(prepare-create-group (pcb/empty-changes it) (uuid/next) objects page-id shapes "Mask" true))
|
||||
|
||||
changes (-> changes
|
||||
(pcb/update-shapes (:shapes group)
|
||||
|
|
|
@ -87,7 +87,17 @@
|
|||
(->> (svg/upload-images svg-data file-id)
|
||||
(rx/map #(svg/add-svg-shapes (assoc svg-data :image-data %) position))))))
|
||||
|
||||
(defn- process-uris
|
||||
|
||||
(defn upload-media-url
|
||||
[name file-id url]
|
||||
(rp/cmd!
|
||||
:create-file-media-object-from-url
|
||||
{:name name
|
||||
:file-id file-id
|
||||
:url url
|
||||
:is-local true}))
|
||||
|
||||
(defn process-uris
|
||||
[{:keys [file-id local? name uris mtype on-image on-svg]}]
|
||||
(letfn [(svg-url? [url]
|
||||
(or (and mtype (= mtype "image/svg+xml"))
|
||||
|
@ -449,3 +459,12 @@
|
|||
(rx/tap on-success)
|
||||
(rx/catch on-error)
|
||||
(rx/finalize #(st/emit! (msg/hide-tag :media-loading)))))))))
|
||||
|
||||
(defn create-svg-shape
|
||||
[id name svg-string position]
|
||||
(ptk/reify ::create-svg-shape
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(->> (svg->clj [name svg-string])
|
||||
(rx/take 1)
|
||||
(rx/map #(svg/add-svg-shapes id % position {:change-selection? false}))))))
|
||||
|
|
|
@ -723,62 +723,76 @@
|
|||
|
||||
(gpt/subtract new-pos pt-obj)))))
|
||||
|
||||
(defn duplicate-shapes
|
||||
[ids & {:keys [move-delta? alt-duplication? change-selection? return-ref]
|
||||
:or {move-delta? false alt-duplication? false change-selection? true return-ref nil}}]
|
||||
(ptk/reify ::duplicate-shapes
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(let [page (wsh/lookup-page state)
|
||||
objects (:objects page)
|
||||
ids (into #{}
|
||||
(comp (map (d/getf objects))
|
||||
(filter #(ctk/allow-duplicate? objects %))
|
||||
(map :id))
|
||||
ids)]
|
||||
(when (seq ids)
|
||||
(let [obj (get objects (first ids))
|
||||
delta (if move-delta?
|
||||
(calc-duplicate-delta obj state objects)
|
||||
(gpt/point 0 0))
|
||||
|
||||
file-id (:current-file-id state)
|
||||
libraries (wsh/get-libraries state)
|
||||
library-data (wsh/get-file state file-id)
|
||||
|
||||
changes (->> (prepare-duplicate-changes objects page ids delta it libraries library-data file-id)
|
||||
(duplicate-changes-update-indices objects ids))
|
||||
|
||||
tags (or (:tags changes) #{})
|
||||
|
||||
changes (cond-> changes alt-duplication? (assoc :tags (conj tags :alt-duplication)))
|
||||
|
||||
id-original (first ids)
|
||||
|
||||
new-ids (->> changes
|
||||
:redo-changes
|
||||
(filter #(= (:type %) :add-obj))
|
||||
(filter #(ids (:old-id %)))
|
||||
(map #(get-in % [:obj :id]))
|
||||
(into (d/ordered-set)))
|
||||
|
||||
id-duplicated (first new-ids)
|
||||
|
||||
frames (into #{}
|
||||
(map #(get-in objects [% :frame-id]))
|
||||
ids)
|
||||
undo-id (js/Symbol)]
|
||||
|
||||
;; Warning: This order is important for the focus mode.
|
||||
(->> (rx/of
|
||||
(dwu/start-undo-transaction undo-id)
|
||||
(dch/commit-changes changes)
|
||||
(when change-selection?
|
||||
(select-shapes new-ids))
|
||||
(ptk/data-event :layout/update {:ids frames})
|
||||
(memorize-duplicated id-original id-duplicated)
|
||||
(dwu/commit-undo-transaction undo-id))
|
||||
(rx/tap #(when (some? return-ref)
|
||||
(reset! return-ref id-duplicated))))))))))
|
||||
|
||||
(defn duplicate-selected
|
||||
([move-delta?]
|
||||
(duplicate-selected move-delta? false))
|
||||
([move-delta? alt-duplication?]
|
||||
(ptk/reify ::duplicate-selected
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(watch [_ state _]
|
||||
(when (or (not move-delta?) (nil? (get-in state [:workspace-local :transform])))
|
||||
(let [page (wsh/lookup-page state)
|
||||
objects (:objects page)
|
||||
selected (->> (wsh/lookup-selected state)
|
||||
(map (d/getf objects))
|
||||
(filter #(ctk/allow-duplicate? objects %))
|
||||
(map :id)
|
||||
set)]
|
||||
(when (seq selected)
|
||||
(let [obj (get objects (first selected))
|
||||
delta (if move-delta?
|
||||
(calc-duplicate-delta obj state objects)
|
||||
(gpt/point 0 0))
|
||||
|
||||
file-id (:current-file-id state)
|
||||
libraries (wsh/get-libraries state)
|
||||
library-data (wsh/get-file state file-id)
|
||||
|
||||
changes (->> (prepare-duplicate-changes objects page selected delta it libraries library-data file-id)
|
||||
(duplicate-changes-update-indices objects selected))
|
||||
|
||||
tags (or (:tags changes) #{})
|
||||
|
||||
changes (cond-> changes alt-duplication? (assoc :tags (conj tags :alt-duplication)))
|
||||
|
||||
id-original (first selected)
|
||||
|
||||
new-selected (->> changes
|
||||
:redo-changes
|
||||
(filter #(= (:type %) :add-obj))
|
||||
(filter #(selected (:old-id %)))
|
||||
(map #(get-in % [:obj :id]))
|
||||
(into (d/ordered-set)))
|
||||
|
||||
id-duplicated (first new-selected)
|
||||
|
||||
frames (into #{}
|
||||
(map #(get-in objects [% :frame-id]))
|
||||
selected)
|
||||
undo-id (js/Symbol)]
|
||||
|
||||
;; Warning: This order is important for the focus mode.
|
||||
(rx/of
|
||||
(dwu/start-undo-transaction undo-id)
|
||||
(dch/commit-changes changes)
|
||||
(select-shapes new-selected)
|
||||
(ptk/data-event :layout/update {:ids frames})
|
||||
(memorize-duplicated id-original id-duplicated)
|
||||
(dwu/commit-undo-transaction undo-id))))))))))
|
||||
(let [selected (wsh/lookup-selected state)]
|
||||
(rx/of (duplicate-shapes selected
|
||||
:move-delta? move-delta?
|
||||
:alt-duplication? alt-duplication?))))))))
|
||||
|
||||
(defn change-hover-state
|
||||
[id value]
|
||||
|
|
|
@ -72,7 +72,7 @@
|
|||
:layout-grid-columns []})
|
||||
|
||||
(defn get-layout-initializer
|
||||
[type from-frame?]
|
||||
[type from-frame? calculate-params?]
|
||||
(let [[initial-layout-data calculate-params]
|
||||
(case type
|
||||
:flex [initial-flex-layout flex/calculate-params]
|
||||
|
@ -87,9 +87,11 @@
|
|||
(cond-> (not from-frame?)
|
||||
(assoc :show-content true :hide-in-viewer true)))
|
||||
|
||||
params (calculate-params objects (cfh/get-immediate-children objects (:id shape)) shape)]
|
||||
params (when calculate-params?
|
||||
(calculate-params objects (cfh/get-immediate-children objects (:id shape)) shape))]
|
||||
(cond-> (merge shape params)
|
||||
(= type :grid) (-> (ctl/assign-cells objects) ctl/reorder-grid-children))))))
|
||||
(= type :grid)
|
||||
(-> (ctl/assign-cells objects) ctl/reorder-grid-children))))))
|
||||
|
||||
;; Never call this directly but through the data-event `:layout/update`
|
||||
;; Otherwise a lot of cycle dependencies could be generated
|
||||
|
@ -124,7 +126,7 @@
|
|||
(ptk/reify ::finalize))
|
||||
|
||||
(defn create-layout-from-id
|
||||
[id type from-frame?]
|
||||
[id type & {:keys [from-frame? calculate-params?] :or {from-frame? false calculate-params? true}}]
|
||||
(dm/assert!
|
||||
"expected uuid for `id`"
|
||||
(uuid? id))
|
||||
|
@ -135,7 +137,7 @@
|
|||
(let [objects (wsh/lookup-page-objects state)
|
||||
parent (get objects id)
|
||||
undo-id (js/Symbol)
|
||||
layout-initializer (get-layout-initializer type from-frame?)]
|
||||
layout-initializer (get-layout-initializer type from-frame? calculate-params?)]
|
||||
|
||||
(rx/of (dwu/start-undo-transaction undo-id)
|
||||
(dch/update-shapes [id] layout-initializer {:with-objects? true})
|
||||
|
@ -177,7 +179,7 @@
|
|||
(dwse/select-shapes ordered-ids)
|
||||
(dwsh/create-artboard-from-selection new-shape-id parent-id group-index (:name (first selected-shapes)))
|
||||
(cl/remove-all-fills [new-shape-id] {:color clr/black :opacity 1})
|
||||
(create-layout-from-id new-shape-id type false)
|
||||
(create-layout-from-id new-shape-id type)
|
||||
(dch/update-shapes [new-shape-id] #(assoc % :layout-item-h-sizing :auto :layout-item-v-sizing :auto))
|
||||
(dch/update-shapes selected #(assoc % :layout-item-h-sizing :fix :layout-item-v-sizing :fix))
|
||||
(dwsh/delete-shapes page-id selected)
|
||||
|
@ -188,7 +190,7 @@
|
|||
(rx/of
|
||||
(dwsh/create-artboard-from-selection new-shape-id)
|
||||
(cl/remove-all-fills [new-shape-id] {:color clr/black :opacity 1})
|
||||
(create-layout-from-id new-shape-id type false)
|
||||
(create-layout-from-id new-shape-id type)
|
||||
(dch/update-shapes [new-shape-id] #(assoc % :layout-item-h-sizing :auto :layout-item-v-sizing :auto))
|
||||
(dch/update-shapes selected #(assoc % :layout-item-h-sizing :fix :layout-item-v-sizing :fix))))
|
||||
|
||||
|
@ -227,7 +229,7 @@
|
|||
(rx/of
|
||||
(dwu/start-undo-transaction undo-id)
|
||||
(if (and single? is-frame?)
|
||||
(create-layout-from-id (first selected) type true)
|
||||
(create-layout-from-id (first selected) type :from-frame? true)
|
||||
(create-layout-from-selection type))
|
||||
(dwu/commit-undo-transaction undo-id))))))
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
[app.common.svg :as csvg]
|
||||
[app.common.svg.shapes-builder :as csvg.shapes-builder]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace.changes :as dch]
|
||||
[app.main.data.workspace.selection :as dws]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
|
@ -60,52 +61,57 @@
|
|||
(rx/reduce conj {})))
|
||||
|
||||
(defn add-svg-shapes
|
||||
[svg-data position]
|
||||
(ptk/reify ::add-svg-shapes
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(try
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (wsh/lookup-page-objects state page-id)
|
||||
frame-id (ctst/top-nested-frame objects position)
|
||||
selected (wsh/lookup-selected state)
|
||||
base (cfh/get-base-shape objects selected)
|
||||
([svg-data position]
|
||||
(add-svg-shapes nil svg-data position nil))
|
||||
|
||||
selected-id (first selected)
|
||||
selected-frame? (and (= 1 (count selected))
|
||||
(= :frame (dm/get-in objects [selected-id :type])))
|
||||
([id svg-data position {:keys [change-selection?] :or {change-selection? false}}]
|
||||
(ptk/reify ::add-svg-shapes
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(try
|
||||
(let [id (d/nilv id (uuid/next))
|
||||
page-id (:current-page-id state)
|
||||
objects (wsh/lookup-page-objects state page-id)
|
||||
frame-id (ctst/top-nested-frame objects position)
|
||||
selected (wsh/lookup-selected state)
|
||||
base (cfh/get-base-shape objects selected)
|
||||
|
||||
parent-id (if (or selected-frame? (empty? selected))
|
||||
frame-id
|
||||
(:parent-id base))
|
||||
selected-id (first selected)
|
||||
selected-frame? (and (= 1 (count selected))
|
||||
(= :frame (dm/get-in objects [selected-id :type])))
|
||||
|
||||
[new-shape new-children]
|
||||
(csvg.shapes-builder/create-svg-shapes svg-data position objects frame-id parent-id selected true)
|
||||
parent-id (if (or selected-frame? (empty? selected))
|
||||
frame-id
|
||||
(:parent-id base))
|
||||
|
||||
changes (-> (pcb/empty-changes it page-id)
|
||||
(pcb/with-objects objects)
|
||||
(pcb/add-object new-shape))
|
||||
[new-shape new-children]
|
||||
(csvg.shapes-builder/create-svg-shapes id svg-data position objects frame-id parent-id selected true)
|
||||
|
||||
changes (reduce (fn [changes new-child]
|
||||
(pcb/add-object changes new-child))
|
||||
changes
|
||||
new-children)
|
||||
changes (-> (pcb/empty-changes it page-id)
|
||||
(pcb/with-objects objects)
|
||||
(pcb/add-object new-shape))
|
||||
|
||||
changes (pcb/resize-parents changes
|
||||
(->> (:redo-changes changes)
|
||||
(filter #(= :add-obj (:type %)))
|
||||
(map :id)
|
||||
(reverse)
|
||||
(vec)))
|
||||
undo-id (js/Symbol)]
|
||||
changes (reduce (fn [changes new-child]
|
||||
(pcb/add-object changes new-child))
|
||||
changes
|
||||
new-children)
|
||||
|
||||
(rx/of (dwu/start-undo-transaction undo-id)
|
||||
(dch/commit-changes changes)
|
||||
(dws/select-shapes (d/ordered-set (:id new-shape)))
|
||||
(ptk/data-event :layout/update {:ids [(:id new-shape)]})
|
||||
(dwu/commit-undo-transaction undo-id)))
|
||||
changes (pcb/resize-parents changes
|
||||
(->> (:redo-changes changes)
|
||||
(filter #(= :add-obj (:type %)))
|
||||
(map :id)
|
||||
(reverse)
|
||||
(vec)))
|
||||
undo-id (js/Symbol)]
|
||||
|
||||
(rx/of (dwu/start-undo-transaction undo-id)
|
||||
(dch/commit-changes changes)
|
||||
(when change-selection?
|
||||
(dws/select-shapes (d/ordered-set (:id new-shape))))
|
||||
(ptk/data-event :layout/update {:ids [(:id new-shape)]})
|
||||
(dwu/commit-undo-transaction undo-id)))
|
||||
|
||||
(catch :default cause
|
||||
(rx/throw {:type :svg-parser
|
||||
:data cause})))))))
|
||||
|
||||
(catch :default cause
|
||||
(js/console.log (.-stack cause))
|
||||
(rx/throw {:type :svg-parser
|
||||
:data cause}))))))
|
||||
|
|
|
@ -831,7 +831,7 @@
|
|||
:ignore-constraints false
|
||||
:ignore-snap-pixel true}))))))
|
||||
|
||||
(defn- move-shapes-to-frame
|
||||
(defn move-shapes-to-frame
|
||||
[ids frame-id drop-index [row column :as cell]]
|
||||
(ptk/reify ::move-shapes-to-frame
|
||||
ptk/WatchEvent
|
||||
|
@ -923,24 +923,32 @@
|
|||
changes
|
||||
(-> (pcb/empty-changes it page-id)
|
||||
(pcb/with-objects objects)
|
||||
|
||||
;; Remove layout-item properties when moving a shape outside a layout
|
||||
(cond-> (not (ctl/any-layout? objects frame-id))
|
||||
(pcb/update-shapes moving-shapes-ids ctl/remove-layout-item-data))
|
||||
|
||||
;; Remove the swap slots if it is moving to a different component
|
||||
(pcb/update-shapes child-heads
|
||||
(fn [shape]
|
||||
(cond-> shape
|
||||
(not= component-main-frame (ctn/find-component-main objects shape false))
|
||||
(ctk/remove-swap-slot))))
|
||||
(pcb/update-shapes
|
||||
child-heads
|
||||
(fn [shape]
|
||||
(cond-> shape
|
||||
(not= component-main-frame (ctn/find-component-main objects shape false))
|
||||
(ctk/remove-swap-slot))))
|
||||
|
||||
;; Remove component-root property when moving a shape inside a component
|
||||
(cond-> (ctn/get-instance-root objects frame)
|
||||
(pcb/update-shapes moving-shapes-children-ids #(dissoc % :component-root)))
|
||||
|
||||
;; Add component-root property when moving a component outside a component
|
||||
(cond-> (not (ctn/get-instance-root objects frame))
|
||||
(pcb/update-shapes child-heads #(assoc % :component-root true)))
|
||||
|
||||
(pcb/update-shapes moving-shapes-ids #(cond-> % (cfh/frame-shape? %) (assoc :hide-in-viewer true)))
|
||||
(pcb/update-shapes shape-ids-to-detach ctk/detach-shape)
|
||||
(pcb/change-parent frame-id moving-shapes drop-index)
|
||||
|
||||
;; Change the grid cell in a grid layout
|
||||
(cond-> (ctl/grid-layout? objects frame-id)
|
||||
(-> (pcb/update-shapes
|
||||
[frame-id]
|
||||
|
@ -948,7 +956,8 @@
|
|||
(-> frame
|
||||
;; Assign the cell when pushing into a specific grid cell
|
||||
(cond-> (some? cell)
|
||||
(-> (ctl/push-into-cell moving-shapes-ids row column)
|
||||
(-> (ctl/free-cell-shapes moving-shapes-ids)
|
||||
(ctl/push-into-cell moving-shapes-ids row column)
|
||||
(ctl/assign-cells objects)))
|
||||
(ctl/assign-cell-positions objects)))
|
||||
{:with-objects? true})
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
(ns app.main.data.workspace.zoom
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.geom.align :as gal]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
|
@ -54,14 +56,20 @@
|
|||
#(impl-update-zoom % center (fn [z] (max (/ z 1.3) 0.01)))))))))
|
||||
|
||||
(defn set-zoom
|
||||
[center scale]
|
||||
(ptk/reify ::set-zoom
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :workspace-local
|
||||
#(impl-update-zoom % center (fn [z] (-> (* z scale)
|
||||
(max 0.01)
|
||||
(min 200))))))))
|
||||
([scale]
|
||||
(set-zoom nil scale))
|
||||
([center scale]
|
||||
(ptk/reify ::set-zoom
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [vp (dm/get-in state [:workspace-local :vbox])
|
||||
x (+ (:x vp) (/ (:width vp) 2))
|
||||
y (+ (:y vp) (/ (:height vp) 2))
|
||||
center (d/nilv center (gpt/point x y))]
|
||||
(update state :workspace-local
|
||||
#(impl-update-zoom % center (fn [z] (-> (* z scale)
|
||||
(max 0.01)
|
||||
(min 200))))))))))
|
||||
|
||||
(def reset-zoom
|
||||
(ptk/reify ::reset-zoom
|
||||
|
@ -110,6 +118,31 @@
|
|||
(assoc :zoom-inverse (/ 1 zoom))
|
||||
(update :vbox merge srect)))))))))))
|
||||
|
||||
(defn fit-to-shapes
|
||||
[ids]
|
||||
(ptk/reify ::fit-to-shapes
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(if (empty? ids)
|
||||
state
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (wsh/lookup-page-objects state page-id)
|
||||
srect (->> ids
|
||||
(map #(get objects %))
|
||||
(gsh/shapes->rect))]
|
||||
|
||||
(update state :workspace-local
|
||||
(fn [{:keys [vport] :as local}]
|
||||
(let [srect (gal/adjust-to-viewport
|
||||
vport srect
|
||||
{:padding 40})
|
||||
zoom (/ (:width vport)
|
||||
(:width srect))]
|
||||
(-> local
|
||||
(assoc :zoom zoom)
|
||||
(assoc :zoom-inverse (/ 1 zoom))
|
||||
(update :vbox merge srect))))))))))
|
||||
|
||||
(defn start-zooming [pt]
|
||||
(ptk/reify ::start-zooming
|
||||
ptk/WatchEvent
|
||||
|
|
|
@ -9,15 +9,23 @@
|
|||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.files.changes-builder :as cb]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.record :as cr]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace.changes :as ch]
|
||||
[app.main.data.workspace.groups :as dwg]
|
||||
[app.main.data.workspace.media :as dwm]
|
||||
[app.main.store :as st]
|
||||
[app.plugins.events :as events]
|
||||
[app.plugins.file :as file]
|
||||
[app.plugins.page :as page]
|
||||
[app.plugins.shape :as shape]))
|
||||
[app.plugins.shape :as shape]
|
||||
[app.plugins.utils :as utils]
|
||||
[app.plugins.viewport :as viewport]
|
||||
[app.util.object :as obj]
|
||||
[beicon.v2.core :as rx]
|
||||
[promesa.core :as p]))
|
||||
|
||||
;;
|
||||
;; PLUGINS PUBLIC API - The plugins will able to access this functions
|
||||
|
@ -28,12 +36,30 @@
|
|||
(map val)
|
||||
(map shape/data->shape-proxy)))
|
||||
|
||||
(defn create-shape
|
||||
[type]
|
||||
(let [page-id (:current-page-id @st/state)
|
||||
page (dm/get-in @st/state [:workspace-data :pages-index page-id])
|
||||
shape (cts/setup-shape {:type type
|
||||
:x 0 :y 0 :width 100 :height 100})
|
||||
changes
|
||||
(-> (cb/empty-changes)
|
||||
(cb/with-page page)
|
||||
(cb/with-objects (:objects page))
|
||||
(cb/add-object shape))]
|
||||
(st/emit! (ch/commit-changes changes))
|
||||
(shape/data->shape-proxy shape)))
|
||||
|
||||
(deftype PenpotContext []
|
||||
Object
|
||||
(addListener
|
||||
[_ type callback]
|
||||
(events/add-listener type callback))
|
||||
|
||||
(getViewport
|
||||
[_]
|
||||
(viewport/create-proxy))
|
||||
|
||||
(getFile
|
||||
[_]
|
||||
(file/data->file-proxy (:workspace-file @st/state) (:workspace-data @st/state)))
|
||||
|
@ -70,19 +96,46 @@
|
|||
"dark"
|
||||
(get-in @st/state [:profile :theme]))))
|
||||
|
||||
(uploadMediaUrl
|
||||
[_ name url]
|
||||
(let [file-id (get-in @st/state [:workspace-file :id])]
|
||||
(p/create
|
||||
(fn [resolve reject]
|
||||
(->> (dwm/upload-media-url name file-id url)
|
||||
(rx/map utils/to-js)
|
||||
(rx/take 1)
|
||||
(rx/subs! resolve reject))))))
|
||||
|
||||
(group
|
||||
[_ shapes]
|
||||
(let [page-id (:current-page-id @st/state)
|
||||
id (uuid/next)
|
||||
ids (into #{} (map #(get (obj/get % "_data") :id)) shapes)]
|
||||
(st/emit! (dwg/group-shapes id ids))
|
||||
(shape/data->shape-proxy
|
||||
(dm/get-in @st/state [:workspace-data :pages-index page-id :objects id]))))
|
||||
|
||||
(ungroup
|
||||
[_ group & rest]
|
||||
(let [shapes (concat [group] rest)
|
||||
ids (into #{} (map #(get (obj/get % "_data") :id)) shapes)]
|
||||
(st/emit! (dwg/ungroup-shapes ids))))
|
||||
|
||||
(createFrame
|
||||
[_]
|
||||
(create-shape :frame))
|
||||
|
||||
(createRectangle
|
||||
[_]
|
||||
(let [page-id (:current-page-id @st/state)
|
||||
page (dm/get-in @st/state [:workspace-data :pages-index page-id])
|
||||
shape (cts/setup-shape {:type :rect
|
||||
:x 0 :y 0 :width 100 :height 100})
|
||||
changes
|
||||
(-> (cb/empty-changes)
|
||||
(cb/with-page page)
|
||||
(cb/with-objects (:objects page))
|
||||
(cb/add-object shape))]
|
||||
(st/emit! (ch/commit-changes changes))
|
||||
(shape/data->shape-proxy shape))))
|
||||
(create-shape :rect))
|
||||
|
||||
(createShapeFromSvg
|
||||
[_ svg-string]
|
||||
(let [id (uuid/next)
|
||||
page-id (:current-page-id @st/state)]
|
||||
(st/emit! (dwm/create-svg-shape id "svg" svg-string (gpt/point 0 0)))
|
||||
(shape/data->shape-proxy
|
||||
(dm/get-in @st/state [:workspace-data :pages-index page-id :objects id])))))
|
||||
|
||||
(defn create-context
|
||||
[]
|
||||
|
@ -90,4 +143,5 @@
|
|||
(PenpotContext.)
|
||||
{:name "root" :get #(.getRoot ^js %)}
|
||||
{:name "currentPage" :get #(.getPage ^js %)}
|
||||
{:name "selection" :get #(.getSelectedShapes ^js %)}))
|
||||
{:name "selection" :get #(.getSelectedShapes ^js %)}
|
||||
{:name "viewport" :get #(.getViewport ^js %)}))
|
||||
|
|
173
frontend/src/app/plugins/grid.cljs
Normal file
173
frontend/src/app/plugins/grid.cljs
Normal file
|
@ -0,0 +1,173 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; 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) KALEIDOS INC
|
||||
|
||||
(ns app.plugins.grid
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.record :as crc]
|
||||
[app.common.spec :as us]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace.shape-layout :as dwsl]
|
||||
[app.main.data.workspace.transforms :as dwt]
|
||||
[app.main.store :as st]
|
||||
[app.plugins.utils :as utils :refer [get-data get-state]]
|
||||
[app.util.object :as obj]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
(defn- make-tracks
|
||||
[tracks]
|
||||
(.freeze
|
||||
js/Object
|
||||
(apply array (->> tracks (map utils/to-js)))))
|
||||
|
||||
(deftype GridLayout [_data]
|
||||
Object
|
||||
|
||||
(addRow
|
||||
[self type value]
|
||||
(let [id (get-data self :id)
|
||||
type (keyword type)]
|
||||
(st/emit! (dwsl/add-layout-track #{id} :row {:type type :value value}))))
|
||||
|
||||
(addRowAtIndex
|
||||
[self type value index]
|
||||
(let [id (get-data self :id)
|
||||
type (keyword type)]
|
||||
(st/emit! (dwsl/add-layout-track #{id} :row {:type type :value value} index))))
|
||||
|
||||
(addColumn
|
||||
[self type value]
|
||||
(let [id (get-data self :id)
|
||||
type (keyword type)]
|
||||
(st/emit! (dwsl/add-layout-track #{id} :column {:type type :value value}))))
|
||||
|
||||
(addColumnAtIndex
|
||||
[self type value index]
|
||||
(let [id (get-data self :id)
|
||||
type (keyword type)]
|
||||
(st/emit! (dwsl/add-layout-track #{id} :column {:type type :value value} index))))
|
||||
|
||||
(removeRow
|
||||
[self index]
|
||||
(let [id (get-data self :id)]
|
||||
(st/emit! (dwsl/remove-layout-track #{id} :row index))))
|
||||
|
||||
(removeColumn
|
||||
[self index]
|
||||
(let [id (get-data self :id)]
|
||||
(st/emit! (dwsl/remove-layout-track #{id} :column index))))
|
||||
|
||||
(setColumn
|
||||
[self index type value]
|
||||
(let [id (get-data self :id)
|
||||
type (keyword type)]
|
||||
(st/emit! (dwsl/change-layout-track #{id} :column index (d/without-nils {:type type :value value})))))
|
||||
|
||||
(setRow
|
||||
[self index type value]
|
||||
(let [id (get-data self :id)
|
||||
type (keyword type)]
|
||||
(st/emit! (dwsl/change-layout-track #{id} :row index (d/without-nils {:type type :value value})))))
|
||||
|
||||
(remove
|
||||
[self]
|
||||
(let [id (get-data self :id)]
|
||||
(st/emit! (dwsl/remove-layout #{id}))))
|
||||
|
||||
(appendChild
|
||||
[self child row column]
|
||||
(let [parent-id (get-data self :id)
|
||||
child-id (uuid/uuid (obj/get child "id"))]
|
||||
(st/emit! (dwt/move-shapes-to-frame #{child-id} parent-id nil [row column])
|
||||
(ptk/data-event :layout/update {:ids [parent-id]})))))
|
||||
|
||||
(defn grid-layout-proxy
|
||||
[data]
|
||||
(-> (GridLayout. data)
|
||||
(crc/add-properties!
|
||||
{:name "dir"
|
||||
:get #(get-state % :layout-grid-dir d/name)
|
||||
:set
|
||||
(fn [self value]
|
||||
(let [id (get-data self :id)
|
||||
value (keyword value)]
|
||||
(when (contains? ctl/grid-direction-types value)
|
||||
(st/emit! (dwsl/update-layout #{id} {:layout-grid-dir value})))))}
|
||||
|
||||
{:name "rows"
|
||||
:get #(get-state % :layout-grid-rows make-tracks)}
|
||||
|
||||
{:name "columns"
|
||||
:get #(get-state % :layout-grid-columns make-tracks)}
|
||||
|
||||
{:name "alignItems"
|
||||
:get #(get-state % :layout-align-items d/name)
|
||||
:set
|
||||
(fn [self value]
|
||||
(let [id (get-data self :id)
|
||||
value (keyword value)]
|
||||
(when (contains? ctl/align-items-types value)
|
||||
(st/emit! (dwsl/update-layout #{id} {:layout-align-items value})))))}
|
||||
|
||||
{:name "alignContent"
|
||||
:get #(get-state % :layout-align-content d/name)
|
||||
:set
|
||||
(fn [self value]
|
||||
(let [id (get-data self :id)
|
||||
value (keyword value)]
|
||||
(when (contains? ctl/align-content-types value)
|
||||
(st/emit! (dwsl/update-layout #{id} {:layout-align-content value})))))}
|
||||
|
||||
{:name "justifyItems"
|
||||
:get #(get-state % :layout-justify-items d/name)
|
||||
:set
|
||||
(fn [self value]
|
||||
(let [id (get-data self :id)
|
||||
value (keyword value)]
|
||||
(when (contains? ctl/justify-items-types value)
|
||||
(st/emit! (dwsl/update-layout #{id} {:layout-justify-items value})))))}
|
||||
|
||||
{:name "justifyContent"
|
||||
:get #(get-state % :layout-justify-content d/name)
|
||||
:set
|
||||
(fn [self value]
|
||||
(let [id (get-data self :id)
|
||||
value (keyword value)]
|
||||
(when (contains? ctl/justify-content-types value)
|
||||
(st/emit! (dwsl/update-layout #{id} {:layout-justify-content value})))))}
|
||||
|
||||
{:name "rowGap"
|
||||
:get #(:row-gap (get-state % :layout-gap))
|
||||
:set
|
||||
(fn [self value]
|
||||
(let [id (get-data self :id)]
|
||||
(when (us/safe-int? value)
|
||||
(st/emit! (dwsl/update-layout #{id} {:layout-gap {:row-gap value}})))))}
|
||||
|
||||
{:name "columnGap"
|
||||
:get #(:column-gap (get-state % :layout-gap))
|
||||
:set
|
||||
(fn [self value]
|
||||
(let [id (get-data self :id)]
|
||||
(when (us/safe-int? value)
|
||||
(st/emit! (dwsl/update-layout #{id} {:layout-gap {:column-gap value}})))))}
|
||||
|
||||
{:name "verticalPadding"
|
||||
:get #(:p1 (get-state % :layout-padding))
|
||||
:set
|
||||
(fn [self value]
|
||||
(let [id (get-data self :id)]
|
||||
(when (us/safe-int? value)
|
||||
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p1 value :p3 value}})))))}
|
||||
|
||||
{:name "horizontalPadding"
|
||||
:get #(:p2 (get-state % :layout-padding))
|
||||
:set
|
||||
(fn [self value]
|
||||
(let [id (get-data self :id)]
|
||||
(when (us/safe-int? value)
|
||||
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p2 value :p4 value}})))))})))
|
|
@ -7,16 +7,20 @@
|
|||
(ns app.plugins.shape
|
||||
"RPC for plugins runtime."
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.record :as crc]
|
||||
[app.common.text :as txt]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace :as udw]
|
||||
[app.main.data.workspace.changes :as dwc]
|
||||
[app.main.data.workspace.selection :as dws]
|
||||
[app.main.data.workspace.shape-layout :as dwsl]
|
||||
[app.main.data.workspace.shapes :as dwsh]
|
||||
[app.main.store :as st]
|
||||
[app.plugins.utils :refer [get-data get-data-fn]]
|
||||
[cuerdas.core :as str]))
|
||||
[app.plugins.grid :as grid]
|
||||
[app.plugins.utils :as utils :refer [get-data get-data-fn get-state]]
|
||||
[app.util.object :as obj]))
|
||||
|
||||
(declare data->shape-proxy)
|
||||
|
||||
|
@ -24,52 +28,65 @@
|
|||
[fills]
|
||||
(.freeze
|
||||
js/Object
|
||||
(apply array
|
||||
(->> fills
|
||||
;; TODO: Transform explicitly instead of cljs->js?
|
||||
(map #(clj->js % {:keyword-fn (fn [k] (str/camel (name k)))}))))))
|
||||
(apply array (->> fills (map utils/to-js)))))
|
||||
|
||||
(defn- make-strokes
|
||||
[strokes]
|
||||
(.freeze
|
||||
js/Object
|
||||
(apply array
|
||||
(->> strokes
|
||||
;; TODO: Transform explicitly instead of cljs->js?
|
||||
(map #(clj->js % {:keyword-fn (fn [k] (str/camel (name k)))}))))))
|
||||
(apply array (->> strokes (map utils/to-js)))))
|
||||
|
||||
(defn- locate-shape
|
||||
[shape-id]
|
||||
(let [page-id (:current-page-id @st/state)]
|
||||
(dm/get-in @st/state [:workspace-data :pages-index page-id :objects shape-id])))
|
||||
|
||||
(defn- get-state
|
||||
([self attr]
|
||||
(let [id (get-data self :id)
|
||||
page-id (d/nilv (get-data self :page-id) (:current-page-id @st/state))]
|
||||
(dm/get-in @st/state [:workspace-data :pages-index page-id :objects id attr])))
|
||||
([self attr mapfn]
|
||||
(-> (get-state self attr)
|
||||
(mapfn))))
|
||||
|
||||
(deftype ShapeProxy [^:mutable #_:clj-kondo/ignore _data]
|
||||
(deftype ShapeProxy [#_:clj-kondo/ignore _data]
|
||||
Object
|
||||
(resize
|
||||
[self width height]
|
||||
(let [id (get-data self :id)]
|
||||
(st/emit! (udw/update-dimensions [id] :width width)
|
||||
(udw/update-dimensions [id] :height height))))
|
||||
|
||||
(clone [self]
|
||||
(let [id (get-data self :id)
|
||||
page-id (:current-page-id @st/state)
|
||||
ret-v (atom nil)]
|
||||
(st/emit! (dws/duplicate-shapes #{id} :change-selection? false :return-ref ret-v))
|
||||
(let [new-id (deref ret-v)
|
||||
shape (dm/get-in @st/state [:workspace-data :pages-index page-id :objects new-id])]
|
||||
(data->shape-proxy shape))))
|
||||
|
||||
(remove [self]
|
||||
(let [id (get-data self :id)]
|
||||
(st/emit! (dwsh/delete-shapes #{id}))))
|
||||
|
||||
;; Only for frames + groups + booleans
|
||||
(getChildren
|
||||
[self]
|
||||
(apply array (->> (get-state self :shapes)
|
||||
(map locate-shape)
|
||||
(map data->shape-proxy))))
|
||||
|
||||
(resize
|
||||
[self width height]
|
||||
(appendChild [self child]
|
||||
(let [parent-id (get-data self :id)
|
||||
child-id (uuid/uuid (obj/get child "id"))]
|
||||
(st/emit! (udw/relocate-shapes #{child-id} parent-id 0))))
|
||||
|
||||
(insertChild [self index child]
|
||||
(let [parent-id (get-data self :id)
|
||||
child-id (uuid/uuid (obj/get child "id"))]
|
||||
(st/emit! (udw/relocate-shapes #{child-id} parent-id index))))
|
||||
|
||||
;; Only for frames
|
||||
(addFlexLayout [self]
|
||||
(let [id (get-data self :id)]
|
||||
(st/emit! (udw/update-dimensions [id] :width width)
|
||||
(udw/update-dimensions [id] :height height))))
|
||||
(st/emit! (dwsl/create-layout-from-id id :flex :from-frame? true :calculate-params? false))))
|
||||
|
||||
(clone [_] (.log js/console (clj->js _data)))
|
||||
(delete [_] (.log js/console (clj->js _data)))
|
||||
(appendChild [_] (.log js/console (clj->js _data))))
|
||||
(addGridLayout [self]
|
||||
(let [id (get-data self :id)]
|
||||
(st/emit! (dwsl/create-layout-from-id id :grid :from-frame? true :calculate-params? false)))))
|
||||
|
||||
(crc/define-properties!
|
||||
ShapeProxy
|
||||
|
@ -88,7 +105,7 @@
|
|||
:get (get-data-fn :id str)}
|
||||
|
||||
{:name "type"
|
||||
:get (get-data-fn :type)}
|
||||
:get (get-data-fn :type name)}
|
||||
|
||||
{:name "x"
|
||||
:get #(get-state % :x)
|
||||
|
@ -104,6 +121,62 @@
|
|||
(let [id (get-data self :id)]
|
||||
(st/emit! (udw/update-position id {:y value}))))}
|
||||
|
||||
{:name "parentX"
|
||||
:get (fn [self]
|
||||
(let [page-id (:current-page-id @st/state)
|
||||
parent-id (get-state self :parent-id)
|
||||
parent-x (dm/get-in @st/state [:workspace-data :pages-index page-id :objects parent-id :x])]
|
||||
(- (get-state self :x) parent-x)))
|
||||
:set
|
||||
(fn [self value]
|
||||
(let [page-id (:current-page-id @st/state)
|
||||
id (get-data self :id)
|
||||
parent-id (get-state self :parent-id)
|
||||
parent-x (dm/get-in @st/state [:workspace-data :pages-index page-id :objects parent-id :x])]
|
||||
(st/emit! (udw/update-position id {:x (+ parent-x value)}))))}
|
||||
|
||||
{:name "parentY"
|
||||
:get (fn [self]
|
||||
(let [page-id (:current-page-id @st/state)
|
||||
parent-id (get-state self :parent-id)
|
||||
parent-y (dm/get-in @st/state [:workspace-data :pages-index page-id :objects parent-id :y])]
|
||||
(- (get-state self :y) parent-y)))
|
||||
:set
|
||||
(fn [self value]
|
||||
(let [page-id (:current-page-id @st/state)
|
||||
id (get-data self :id)
|
||||
parent-id (get-state self :parent-id)
|
||||
parent-y (dm/get-in @st/state [:workspace-data :pages-index page-id :objects parent-id :y])]
|
||||
(st/emit! (udw/update-position id {:y (+ parent-y value)}))))}
|
||||
|
||||
{:name "frameX"
|
||||
:get (fn [self]
|
||||
(let [page-id (:current-page-id @st/state)
|
||||
frame-id (get-state self :frame-id)
|
||||
frame-x (dm/get-in @st/state [:workspace-data :pages-index page-id :objects frame-id :x])]
|
||||
(- (get-state self :x) frame-x)))
|
||||
:set
|
||||
(fn [self value]
|
||||
(let [page-id (:current-page-id @st/state)
|
||||
id (get-data self :id)
|
||||
frame-id (get-state self :frame-id)
|
||||
frame-x (dm/get-in @st/state [:workspace-data :pages-index page-id :objects frame-id :x])]
|
||||
(st/emit! (udw/update-position id {:x (+ frame-x value)}))))}
|
||||
|
||||
{:name "frameY"
|
||||
:get (fn [self]
|
||||
(let [page-id (:current-page-id @st/state)
|
||||
frame-id (get-state self :frame-id)
|
||||
frame-y (dm/get-in @st/state [:workspace-data :pages-index page-id :objects frame-id :y])]
|
||||
(- (get-state self :y) frame-y)))
|
||||
:set
|
||||
(fn [self value]
|
||||
(let [page-id (:current-page-id @st/state)
|
||||
id (get-data self :id)
|
||||
frame-id (get-state self :frame-id)
|
||||
frame-y (dm/get-in @st/state [:workspace-data :pages-index page-id :objects frame-id :y])]
|
||||
(st/emit! (udw/update-position id {:y (+ frame-y value)}))))}
|
||||
|
||||
{:name "width"
|
||||
:get #(get-state % :width)}
|
||||
|
||||
|
@ -116,18 +189,50 @@
|
|||
(let [id (get-data self :id)]
|
||||
(st/emit! (dwc/update-shapes [id] #(assoc % :name value)))))}
|
||||
|
||||
{:name "children"
|
||||
:get #(.getChildren ^js %)}
|
||||
|
||||
{:name "fills"
|
||||
:get #(get-state % :fills make-fills)
|
||||
;;:set (fn [self value] (.log js/console self value))
|
||||
}
|
||||
:set (fn [self value]
|
||||
(let [id (get-data self :id)
|
||||
value (mapv #(utils/from-js %) value)]
|
||||
(st/emit! (dwc/update-shapes [id] #(assoc % :fills value)))))}
|
||||
|
||||
{:name "strokes"
|
||||
:get #(get-state % :strokes make-strokes)
|
||||
;;:set (fn [self value] (.log js/console self value))
|
||||
})
|
||||
:set (fn [self value]
|
||||
(let [id (get-data self :id)
|
||||
value (mapv #(utils/from-js %) value)]
|
||||
(st/emit! (dwc/update-shapes [id] #(assoc % :strokes value)))))})
|
||||
|
||||
(cond-> (or (cfh/frame-shape? data) (cfh/group-shape? data) (cfh/svg-raw-shape? data) (cfh/bool-shape? data))
|
||||
(crc/add-properties!
|
||||
{:name "children"
|
||||
:get #(.getChildren ^js %)}))
|
||||
|
||||
(cond-> (not (or (cfh/frame-shape? data) (cfh/group-shape? data) (cfh/svg-raw-shape? data) (cfh/bool-shape? data)))
|
||||
(-> (obj/unset! "appendChild")
|
||||
(obj/unset! "insertChild")
|
||||
(obj/unset! "getChildren")))
|
||||
|
||||
(cond-> (cfh/frame-shape? data)
|
||||
(-> (crc/add-properties!
|
||||
{:name "grid"
|
||||
:get
|
||||
(fn [self]
|
||||
(let [layout (get-state self :layout)]
|
||||
(when (= :grid layout)
|
||||
(grid/grid-layout-proxy data))))})
|
||||
|
||||
#_(crc/add-properties!
|
||||
{:name "flex"
|
||||
:get
|
||||
(fn [self]
|
||||
(let [layout (get-state self :layout)]
|
||||
(when (= :flex layout)
|
||||
(flex-layout-proxy data))))})))
|
||||
|
||||
(cond-> (not (cfh/frame-shape? data))
|
||||
(-> (obj/unset! "addGridLayout")
|
||||
(obj/unset! "addFlexLayout")))
|
||||
|
||||
(cond-> (cfh/text-shape? data)
|
||||
(crc/add-properties!
|
||||
|
@ -136,4 +241,3 @@
|
|||
:set (fn [self value]
|
||||
(let [id (get-data self :id)]
|
||||
(st/emit! (dwc/update-shapes [id] #(txt/change-text % value)))))}))))
|
||||
|
||||
|
|
|
@ -7,7 +7,14 @@
|
|||
(ns app.plugins.utils
|
||||
"RPC for plugins runtime."
|
||||
(:require
|
||||
[app.util.object :as obj]))
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.spec :as us]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.store :as st]
|
||||
[app.util.object :as obj]
|
||||
[cuerdas.core :as str]
|
||||
[promesa.core :as p]))
|
||||
|
||||
(defn get-data
|
||||
([self attr]
|
||||
|
@ -27,4 +34,62 @@
|
|||
(fn [self]
|
||||
(get-data self attr transform-fn))))
|
||||
|
||||
(defn get-state
|
||||
([self attr]
|
||||
(let [id (get-data self :id)
|
||||
page-id (d/nilv (get-data self :page-id) (:current-page-id @st/state))]
|
||||
(dm/get-in @st/state [:workspace-data :pages-index page-id :objects id attr])))
|
||||
([self attr mapfn]
|
||||
(-> (get-state self attr)
|
||||
(mapfn))))
|
||||
|
||||
(defn from-js
|
||||
"Converts the object back to js"
|
||||
([obj]
|
||||
(from-js obj identity))
|
||||
([obj vfn]
|
||||
(let [ret (js->clj obj {:keyword-fn (fn [k] (str/camel (name k)))})]
|
||||
(reduce-kv
|
||||
(fn [m k v]
|
||||
(let [k (keyword (str/kebab k))
|
||||
v (cond (map? v)
|
||||
(from-js v)
|
||||
|
||||
(and (string? v) (re-matches us/uuid-rx v))
|
||||
(uuid/uuid v)
|
||||
|
||||
:else (vfn k v))]
|
||||
(assoc m k v)))
|
||||
{}
|
||||
ret))))
|
||||
|
||||
(defn to-js
|
||||
"Converts to javascript an camelize the keys"
|
||||
[obj]
|
||||
(let [result
|
||||
(reduce-kv
|
||||
(fn [m k v]
|
||||
(let [v (cond (object? v) (to-js v)
|
||||
(uuid? v) (dm/str v)
|
||||
:else v)]
|
||||
(assoc m (str/camel (name k)) v)))
|
||||
{}
|
||||
obj)]
|
||||
(clj->js result)))
|
||||
|
||||
(defn result-p
|
||||
"Creates a pair of atom+promise. The promise will be resolved when the atom gets a value.
|
||||
We use this to return the promise to the library clients and resolve its value when a value is passed
|
||||
to the atom"
|
||||
[]
|
||||
(let [ret-v (atom nil)
|
||||
ret-p
|
||||
(p/create
|
||||
(fn [resolve _]
|
||||
(add-watch
|
||||
ret-v
|
||||
::watcher
|
||||
(fn [_ _ _ value]
|
||||
(remove-watch ret-v ::watcher)
|
||||
(resolve value)))))]
|
||||
[ret-v ret-p]))
|
||||
|
|
78
frontend/src/app/plugins/viewport.cljs
Normal file
78
frontend/src/app/plugins/viewport.cljs
Normal file
|
@ -0,0 +1,78 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; 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) KALEIDOS INC
|
||||
|
||||
(ns app.plugins.viewport
|
||||
"RPC for plugins runtime."
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.record :as crc]
|
||||
[app.common.spec :as us]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace.viewport :as dwv]
|
||||
[app.main.data.workspace.zoom :as dwz]
|
||||
[app.main.store :as st]
|
||||
[app.util.object :as obj]))
|
||||
|
||||
(deftype ViewportProxy []
|
||||
Object
|
||||
(zoomIntoView [_ shapes]
|
||||
(let [ids
|
||||
(->> shapes
|
||||
(map (fn [v]
|
||||
(if (string? v)
|
||||
(uuid/uuid v)
|
||||
(uuid/uuid (obj/get v "x"))))))]
|
||||
(st/emit! (dwz/fit-to-shapes ids)))))
|
||||
|
||||
(crc/define-properties!
|
||||
ViewportProxy
|
||||
{:name js/Symbol.toStringTag
|
||||
:get (fn [] (str "ViewportProxy"))})
|
||||
|
||||
(defn create-proxy
|
||||
[]
|
||||
(crc/add-properties!
|
||||
(ViewportProxy.)
|
||||
{:name "center"
|
||||
:get
|
||||
(fn [_]
|
||||
(let [vp (dm/get-in @st/state [:workspace-local :vbox])
|
||||
x (+ (:x vp) (/ (:width vp) 2))
|
||||
y (+ (:y vp) (/ (:height vp) 2))]
|
||||
(.freeze js/Object #js {:x x :y y})))
|
||||
|
||||
:set
|
||||
(fn [_ value]
|
||||
(let [new-x (obj/get value "x")
|
||||
new-y (obj/get value "y")]
|
||||
(when (and (us/safe-number? new-x) (us/safe-number? new-y))
|
||||
(let [vb (dm/get-in @st/state [:workspace-local :vbox])
|
||||
old-x (+ (:x vb) (/ (:width vb) 2))
|
||||
old-y (+ (:y vb) (/ (:height vb) 2))
|
||||
delta-x (- new-x old-x)
|
||||
delta-y (- new-y old-y)
|
||||
to-position
|
||||
{:x #(+ % delta-x)
|
||||
:y #(+ % delta-y)}]
|
||||
(st/emit! (dwv/update-viewport-position to-position))))))}
|
||||
|
||||
{:name "zoom"
|
||||
:get
|
||||
(fn [_]
|
||||
(dm/get-in @st/state [:workspace-local :zoom]))
|
||||
:set
|
||||
(fn [_ value]
|
||||
(when (us/safe-number? value)
|
||||
(let [z (dm/get-in @st/state [:workspace-local :zoom])]
|
||||
(st/emit! (dwz/set-zoom (/ value z))))))}
|
||||
|
||||
{:name "bounds"
|
||||
:get
|
||||
(fn [_]
|
||||
(let [vport (dm/get-in @st/state [:workspace-local :vport])]
|
||||
(.freeze js/Object (clj->js vport))))}))
|
||||
|
||||
|
Loading…
Add table
Reference in a new issue