mirror of
https://github.com/penpot/penpot.git
synced 2025-05-24 11:56:11 +02:00
867 lines
34 KiB
Clojure
867 lines
34 KiB
Clojure
;; 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.shape
|
|
"RPC for plugins runtime."
|
|
(:require
|
|
[app.common.colors :as clr]
|
|
[app.common.data :as d]
|
|
[app.common.data.macros :as dm]
|
|
[app.common.files.helpers :as cfh]
|
|
[app.common.geom.rect :as grc]
|
|
[app.common.geom.shapes :as gsh]
|
|
[app.common.record :as crc]
|
|
[app.common.spec :as us]
|
|
[app.common.svg.path.legacy-parser2 :as spp]
|
|
[app.common.text :as txt]
|
|
[app.common.types.shape :as cts]
|
|
[app.common.types.shape.layout :as ctl]
|
|
[app.common.types.shape.radius :as ctsr]
|
|
[app.common.uuid :as uuid]
|
|
[app.main.data.workspace :as dw]
|
|
[app.main.data.workspace.groups :as dwg]
|
|
[app.main.data.workspace.selection :as dws]
|
|
[app.main.data.workspace.shape-layout :as dwsl]
|
|
[app.main.data.workspace.shapes :as dwsh]
|
|
[app.main.data.workspace.texts :as dwt]
|
|
[app.main.store :as st]
|
|
[app.plugins.flex :as flex]
|
|
[app.plugins.grid :as grid]
|
|
[app.plugins.utils :as u]
|
|
[app.util.object :as obj]
|
|
[app.util.path.format :as upf]
|
|
[app.util.text-editor :as ted]
|
|
[cuerdas.core :as str]))
|
|
|
|
|
|
(deftype TextRange [$plugin $file $page $id start end]
|
|
Object
|
|
(applyTypography [_ typography]
|
|
(let [typography (u/proxy->library-typography typography)
|
|
attrs (-> typography
|
|
(assoc :typography-ref-file $file)
|
|
(assoc :typography-ref-id (:id typography))
|
|
(dissoc :id :name))]
|
|
(st/emit! (dwt/update-text-range $id start end attrs)))))
|
|
|
|
(defn mixed-value
|
|
[values]
|
|
(let [s (set values)]
|
|
(if (= (count s) 1) (first s) "mixed")))
|
|
|
|
;; TODO Validate inputs
|
|
(defn text-range
|
|
[plugin-id file-id page-id id start end]
|
|
(-> (TextRange. plugin-id file-id page-id id start end)
|
|
(crc/add-properties!
|
|
{:name "$plugin" :enumerable false :get (constantly plugin-id)}
|
|
{:name "$id" :enumerable false :get (constantly id)}
|
|
{:name "$file" :enumerable false :get (constantly file-id)}
|
|
{:name "$page" :enumerable false :get (constantly page-id)}
|
|
|
|
{:name "shape"
|
|
:get #(-> % u/proxy->shape)}
|
|
|
|
{:name "characters"
|
|
:get #(let [range-data
|
|
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
|
|
(->> range-data (map :text) (str/join "")))}
|
|
|
|
{:name "fontId"
|
|
:get #(let [range-data
|
|
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
|
|
(->> range-data (map :font-id) mixed-value))
|
|
|
|
:set
|
|
(fn [_ value]
|
|
(st/emit! (dwt/update-text-range id start end {:font-id value})))}
|
|
|
|
{:name "fontFamily"
|
|
:get #(let [range-data
|
|
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
|
|
(->> range-data (map :font-family) mixed-value))
|
|
|
|
:set
|
|
(fn [_ value]
|
|
(st/emit! (dwt/update-text-range id start end {:font-family value})))}
|
|
|
|
{:name "fontVariantId"
|
|
:get #(let [range-data
|
|
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
|
|
(->> range-data (map :font-variant-id) mixed-value))
|
|
:set
|
|
(fn [_ value]
|
|
(st/emit! (dwt/update-text-range id start end {:font-variant-id value})))}
|
|
|
|
{:name "fontSize"
|
|
:get #(let [range-data
|
|
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
|
|
(->> range-data (map :font-size) mixed-value))
|
|
:set
|
|
(fn [_ value]
|
|
(st/emit! (dwt/update-text-range id start end {:font-size value})))}
|
|
|
|
{:name "fontWeight"
|
|
:get #(let [range-data
|
|
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
|
|
(->> range-data (map :font-weight) mixed-value))
|
|
:set
|
|
(fn [_ value]
|
|
(st/emit! (dwt/update-text-range id start end {:font-weight value})))}
|
|
|
|
{:name "fontStyle"
|
|
:get #(let [range-data
|
|
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
|
|
(->> range-data (map :font-style) mixed-value))
|
|
:set
|
|
(fn [_ value]
|
|
(st/emit! (dwt/update-text-range id start end {:font-style value})))}
|
|
|
|
{:name "lineHeight"
|
|
:get #(let [range-data
|
|
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
|
|
(->> range-data (map :line-height) mixed-value))
|
|
:set
|
|
(fn [_ value]
|
|
(st/emit! (dwt/update-text-range id start end {:line-height value})))}
|
|
|
|
{:name "letterSpacing"
|
|
:get #(let [range-data
|
|
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
|
|
(->> range-data (map :letter-spacing) mixed-value))
|
|
:set
|
|
(fn [_ value]
|
|
(st/emit! (dwt/update-text-range id start end {:letter-spacing value})))}
|
|
|
|
{:name "textTransform"
|
|
:get #(let [range-data
|
|
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
|
|
(->> range-data (map :text-transform) mixed-value))
|
|
:set
|
|
(fn [_ value]
|
|
(st/emit! (dwt/update-text-range id start end {:text-transform value})))}
|
|
|
|
{:name "textDecoration"
|
|
:get #(let [range-data
|
|
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
|
|
(->> range-data (map :text-decoration) mixed-value))
|
|
:set
|
|
(fn [_ value]
|
|
(st/emit! (dwt/update-text-range id start end {:text-decoration value})))}
|
|
|
|
{:name "direction"
|
|
:get #(let [range-data
|
|
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
|
|
(->> range-data (map :direction) mixed-value))
|
|
:set
|
|
(fn [_ value]
|
|
(st/emit! (dwt/update-text-range id start end {:direction value})))}
|
|
|
|
{:name "fills"
|
|
:get #(let [range-data
|
|
(-> % u/proxy->shape :content (txt/content-range->text+styles start end))]
|
|
(->> range-data (map :fills) mixed-value u/array-to-js))
|
|
:set
|
|
(fn [_ value]
|
|
(let [value (mapv #(u/from-js %) value)]
|
|
(st/emit! (dwt/update-text-range id start end {:fills value}))))})))
|
|
|
|
(declare shape-proxy)
|
|
|
|
(defn parse-command
|
|
[entry]
|
|
(update entry
|
|
:command
|
|
#(case %
|
|
"M" :move-to
|
|
"Z" :close-path
|
|
"L" :line-to
|
|
"H" :line-to-horizontal
|
|
"V" :line-to-vertical
|
|
"C" :curve-to
|
|
"S" :smooth-curve-to
|
|
"Q" :quadratic-bezier-curve-to
|
|
"T" :smooth-quadratic-bezier-curve-to
|
|
"A" :elliptical-arc
|
|
(keyword %))))
|
|
|
|
(defn text-props
|
|
[shape]
|
|
(d/merge
|
|
(dwt/current-root-values {:shape shape :attrs txt/root-attrs})
|
|
(dwt/current-paragraph-values {:shape shape :attrs txt/paragraph-attrs})
|
|
(dwt/current-text-values {:shape shape :attrs txt/text-node-attrs})))
|
|
|
|
(deftype ShapeProxy [$plugin $file $page $id]
|
|
Object
|
|
(resize
|
|
[_ width height]
|
|
(st/emit! (dw/update-dimensions [$id] :width width)
|
|
(dw/update-dimensions [$id] :height height)))
|
|
|
|
(rotate
|
|
[self angle center]
|
|
(let [center (when center {:x (obj/get center "x") :y (obj/get center "y")})]
|
|
(cond
|
|
(not (number? angle))
|
|
(u/display-not-valid :rotate-angle angle)
|
|
|
|
(and (some? center) (or (not (number? (:x center))) (not (number? (:y center)))))
|
|
(u/display-not-valid :rotate-center center)
|
|
|
|
:else
|
|
(let [id (obj/get self "$id")]
|
|
(st/emit! (dw/increase-rotation [id] angle {:center center :delta? true}))))))
|
|
|
|
(clone
|
|
[_]
|
|
(let [ret-v (atom nil)]
|
|
(st/emit! (dws/duplicate-shapes #{$id} :change-selection? false :return-ref ret-v))
|
|
(shape-proxy $plugin (deref ret-v))))
|
|
|
|
(remove
|
|
[_]
|
|
(st/emit! (dwsh/delete-shapes #{$id})))
|
|
|
|
;; Plugin data
|
|
(getPluginData
|
|
[self key]
|
|
(cond
|
|
(not (string? key))
|
|
(u/display-not-valid :shape-plugin-data-key key)
|
|
|
|
:else
|
|
(let [shape (u/proxy->shape self)]
|
|
(dm/get-in shape [:plugin-data (keyword "plugin" (str $plugin)) key]))))
|
|
|
|
(setPluginData
|
|
[_ key value]
|
|
(cond
|
|
(not (string? key))
|
|
(u/display-not-valid :shape-plugin-data-key key)
|
|
|
|
(and (some? value) (not (string? value)))
|
|
(u/display-not-valid :shape-plugin-data value)
|
|
|
|
:else
|
|
(st/emit! (dw/set-plugin-data $file :shape $id $page (keyword "plugin" (str $plugin)) key value))))
|
|
|
|
(getPluginDataKeys
|
|
[self]
|
|
(let [shape (u/proxy->shape self)]
|
|
(apply array (keys (dm/get-in shape [:plugin-data (keyword "plugin" (str $plugin))])))))
|
|
|
|
(getSharedPluginData
|
|
[self namespace key]
|
|
(cond
|
|
(not (string? namespace))
|
|
(u/display-not-valid :shape-plugin-data-namespace namespace)
|
|
|
|
(not (string? key))
|
|
(u/display-not-valid :shape-plugin-data-key key)
|
|
|
|
:else
|
|
(let [shape (u/proxy->shape self)]
|
|
(dm/get-in shape [:plugin-data (keyword "shared" namespace) key]))))
|
|
|
|
(setSharedPluginData
|
|
[_ namespace key value]
|
|
|
|
(cond
|
|
(not (string? namespace))
|
|
(u/display-not-valid :shape-plugin-data-namespace namespace)
|
|
|
|
(not (string? key))
|
|
(u/display-not-valid :shape-plugin-data-key key)
|
|
|
|
(and (some? value) (not (string? value)))
|
|
(u/display-not-valid :shape-plugin-data value)
|
|
|
|
:else
|
|
(st/emit! (dw/set-plugin-data $file :shape $id $page (keyword "shared" namespace) key value))))
|
|
|
|
(getSharedPluginDataKeys
|
|
[self namespace]
|
|
(cond
|
|
(not (string? namespace))
|
|
(u/display-not-valid :shape-plugin-data-namespace namespace)
|
|
|
|
:else
|
|
(let [shape (u/proxy->shape self)]
|
|
(apply array (keys (dm/get-in shape [:plugin-data (keyword "shared" namespace)]))))))
|
|
|
|
;; Only for frames + groups + booleans
|
|
(getChildren
|
|
[_]
|
|
(let [shape (u/locate-shape $file $page $id)]
|
|
(if (or (cfh/frame-shape? shape) (cfh/group-shape? shape) (cfh/svg-raw-shape? shape) (cfh/bool-shape? shape))
|
|
(apply array (->> (u/locate-shape $file $page $id)
|
|
:shapes
|
|
(map #(shape-proxy $plugin $file $page %))))
|
|
(u/display-not-valid :getChildren (:type shape)))))
|
|
|
|
(appendChild
|
|
[_ child]
|
|
(let [shape (u/locate-shape $file $page $id)]
|
|
(if (or (cfh/frame-shape? shape) (cfh/group-shape? shape) (cfh/svg-raw-shape? shape) (cfh/bool-shape? shape))
|
|
(let [child-id (obj/get child "$id")]
|
|
(st/emit! (dw/relocate-shapes #{child-id} $id 0)))
|
|
(u/display-not-valid :appendChild (:type shape)))))
|
|
|
|
(insertChild
|
|
[_ index child]
|
|
(let [shape (u/locate-shape $file $page $id)]
|
|
(if (or (cfh/frame-shape? shape) (cfh/group-shape? shape) (cfh/svg-raw-shape? shape) (cfh/bool-shape? shape))
|
|
(let [child-id (obj/get child "$id")]
|
|
(st/emit! (dw/relocate-shapes #{child-id} $id index)))
|
|
(u/display-not-valid :insertChild (:type shape)))))
|
|
|
|
;; Only for frames
|
|
(addFlexLayout
|
|
[_]
|
|
(let [shape (u/locate-shape $file $page $id)]
|
|
(if (cfh/frame-shape? shape)
|
|
(do (st/emit! (dwsl/create-layout-from-id $id :flex :from-frame? true :calculate-params? false))
|
|
(grid/grid-layout-proxy $plugin $file $page $id))
|
|
(u/display-not-valid :addFlexLayout (:type shape)))))
|
|
|
|
(addGridLayout
|
|
[_]
|
|
(let [shape (u/locate-shape $file $page $id)]
|
|
(if (cfh/frame-shape? shape)
|
|
(do (st/emit! (dwsl/create-layout-from-id $id :grid :from-frame? true :calculate-params? false))
|
|
(grid/grid-layout-proxy $plugin $file $page $id))
|
|
(u/display-not-valid :addGridLayout (:type shape)))))
|
|
|
|
;; Make masks for groups
|
|
(makeMask
|
|
[_]
|
|
(let [shape (u/locate-shape $file $page $id)]
|
|
(if (cfh/group-shape? shape)
|
|
(st/emit! (dwg/mask-group #{$id}))
|
|
(u/display-not-valid :makeMask (:type shape)))))
|
|
|
|
(removeMask
|
|
[_]
|
|
(let [shape (u/locate-shape $file $page $id)]
|
|
(if (cfh/mask-shape? shape)
|
|
(st/emit! (dwg/unmask-group #{$id}))
|
|
(u/display-not-valid :removeMask (:type shape)))))
|
|
|
|
;; Only for path and bool shapes
|
|
(toD
|
|
[_]
|
|
(let [shape (u/locate-shape $file $page $id)]
|
|
(if (cfh/path-shape? shape)
|
|
(upf/format-path (:content shape))
|
|
(u/display-not-valid :makeMask (:type shape)))))
|
|
|
|
;; Text shapes
|
|
(getRange
|
|
[_ start end]
|
|
(let [shape (u/locate-shape $file $page $id)]
|
|
(if (cfh/text-shape? shape)
|
|
(text-range $plugin $file $page $id start end)
|
|
(u/display-not-valid :makeMask (:type shape)))))
|
|
|
|
(applyTypography
|
|
[_ typography]
|
|
(let [shape (u/locate-shape $file $page $id)]
|
|
(if (cfh/text-shape? shape)
|
|
(let [typography (u/proxy->library-typography typography)]
|
|
(st/emit! (dwt/apply-typography #{$id} typography $file)))
|
|
(u/display-not-valid :applyTypography (:type shape))))))
|
|
|
|
(crc/define-properties!
|
|
ShapeProxy
|
|
{:name js/Symbol.toStringTag
|
|
:get (fn [] (str "ShapeProxy"))})
|
|
|
|
(defn shape-proxy
|
|
([plugin-id id]
|
|
(shape-proxy plugin-id (:current-file-id @st/state) (:current-page-id @st/state) id))
|
|
|
|
([plugin-id page-id id]
|
|
(shape-proxy plugin-id (:current-file-id @st/state) page-id id))
|
|
|
|
([plugin-id file-id page-id id]
|
|
(assert (uuid? file-id))
|
|
(assert (uuid? page-id))
|
|
(assert (uuid? id))
|
|
|
|
(let [data (u/locate-shape file-id page-id id)]
|
|
(-> (ShapeProxy. plugin-id file-id page-id id)
|
|
(crc/add-properties!
|
|
{:name "$plugin" :enumerable false :get (constantly plugin-id)}
|
|
{:name "$id" :enumerable false :get (constantly id)}
|
|
{:name "$file" :enumerable false :get (constantly file-id)}
|
|
{:name "$page" :enumerable false :get (constantly page-id)}
|
|
|
|
{:name "id"
|
|
:get #(-> % u/proxy->shape :id str)}
|
|
|
|
{:name "type"
|
|
:get #(-> % u/proxy->shape :type name)}
|
|
|
|
{:name "name"
|
|
:get #(-> % u/proxy->shape :name)
|
|
:set (fn [self value]
|
|
(let [id (obj/get self "$id")
|
|
value (when (string? value) (-> value str/trim cfh/clean-path))
|
|
valid? (and (some? value)
|
|
(not (str/ends-with? value "/"))
|
|
(not (str/blank? value)))]
|
|
(if valid?
|
|
(st/emit! (dwsh/update-shapes [id] #(assoc % :name value)))
|
|
(u/display-not-valid :shape-name value))))}
|
|
|
|
{:name "blocked"
|
|
:get #(-> % u/proxy->shape :blocked boolean)
|
|
:set (fn [self value]
|
|
(let [id (obj/get self "$id")]
|
|
(st/emit! (dwsh/update-shapes [id] #(assoc % :blocked value)))))}
|
|
|
|
{:name "hidden"
|
|
:get #(-> % u/proxy->shape :hidden boolean)
|
|
:set (fn [self value]
|
|
(let [id (obj/get self "$id")]
|
|
(st/emit! (dwsh/update-shapes [id] #(assoc % :hidden value)))))}
|
|
|
|
{:name "proportionLock"
|
|
:get #(-> % u/proxy->shape :proportion-lock boolean)
|
|
:set (fn [self value]
|
|
(let [id (obj/get self "$id")]
|
|
(st/emit! (dwsh/update-shapes [id] #(assoc % :proportion-lock value)))))}
|
|
|
|
{:name "constraintsHorizontal"
|
|
:get #(-> % u/proxy->shape :constraints-h d/name)
|
|
:set (fn [self value]
|
|
(let [id (obj/get self "$id")
|
|
value (keyword value)]
|
|
(when (contains? cts/horizontal-constraint-types value)
|
|
(st/emit! (dwsh/update-shapes [id] #(assoc % :constraints-h value))))))}
|
|
|
|
{:name "constraintsVertical"
|
|
:get #(-> % u/proxy->shape :constraints-v d/name)
|
|
:set (fn [self value]
|
|
(let [id (obj/get self "$id")
|
|
value (keyword value)]
|
|
(when (contains? cts/vertical-constraint-types value)
|
|
(st/emit! (dwsh/update-shapes [id] #(assoc % :constraints-v value))))))}
|
|
|
|
{:name "borderRadius"
|
|
:get #(-> % u/proxy->shape :rx)
|
|
:set (fn [self value]
|
|
(let [id (obj/get self "$id")
|
|
shape (u/proxy->shape self)]
|
|
(when (us/safe-int? value)
|
|
(when (or (not (ctsr/has-radius? shape)) (ctsr/radius-4? shape))
|
|
(st/emit! (dwsh/update-shapes [id] ctsr/switch-to-radius-1)))
|
|
(st/emit! (dwsh/update-shapes [id] #(ctsr/set-radius-1 % value))))))}
|
|
|
|
{:name "borderRadiusTopLeft"
|
|
:get #(-> % u/proxy->shape :r1)
|
|
:set (fn [self value]
|
|
(let [id (obj/get self "$id")
|
|
shape (u/proxy->shape self)]
|
|
(when (us/safe-int? value)
|
|
(when (or (not (ctsr/has-radius? shape)) (not (ctsr/radius-4? shape)))
|
|
(st/emit! (dwsh/update-shapes [id] ctsr/switch-to-radius-4)))
|
|
(st/emit! (dwsh/update-shapes [id] #(ctsr/set-radius-4 % :r1 value))))))}
|
|
|
|
{:name "borderRadiusTopRight"
|
|
:get #(-> % u/proxy->shape :r2)
|
|
:set (fn [self value]
|
|
(let [id (obj/get self "$id")
|
|
shape (u/proxy->shape self)]
|
|
(when (us/safe-int? value)
|
|
(when (or (not (ctsr/has-radius? shape)) (not (ctsr/radius-4? shape)))
|
|
(st/emit! (dwsh/update-shapes [id] ctsr/switch-to-radius-4)))
|
|
(st/emit! (dwsh/update-shapes [id] #(ctsr/set-radius-4 % :r2 value))))))}
|
|
|
|
{:name "borderRadiusBottomRight"
|
|
:get #(-> % u/proxy->shape :r3)
|
|
:set (fn [self value]
|
|
(let [id (obj/get self "$id")
|
|
shape (u/proxy->shape self)]
|
|
(when (us/safe-int? value)
|
|
(when (or (not (ctsr/has-radius? shape)) (not (ctsr/radius-4? shape)))
|
|
(st/emit! (dwsh/update-shapes [id] ctsr/switch-to-radius-4)))
|
|
(st/emit! (dwsh/update-shapes [id] #(ctsr/set-radius-4 % :r3 value))))))}
|
|
|
|
{:name "borderRadiusBottomLeft"
|
|
:get #(-> % u/proxy->shape :r4)
|
|
:set (fn [self value]
|
|
(let [id (obj/get self "$id")
|
|
shape (u/proxy->shape self)]
|
|
(when (us/safe-int? value)
|
|
(when (or (not (ctsr/has-radius? shape)) (not (ctsr/radius-4? shape)))
|
|
(st/emit! (dwsh/update-shapes [id] ctsr/switch-to-radius-4)))
|
|
(st/emit! (dwsh/update-shapes [id] #(ctsr/set-radius-4 % :r4 value))))))}
|
|
|
|
{:name "opacity"
|
|
:get #(-> % u/proxy->shape :opacity)
|
|
:set (fn [self value]
|
|
(let [id (obj/get self "$id")]
|
|
(when (and (us/safe-number? value) (>= value 0) (<= value 1))
|
|
(st/emit! (dwsh/update-shapes [id] #(assoc % :opacity value))))))}
|
|
|
|
{:name "blendMode"
|
|
:get #(-> % u/proxy->shape :blend-mode (d/nilv :normal) d/name)
|
|
:set (fn [self value]
|
|
(let [id (obj/get self "$id")
|
|
value (keyword value)]
|
|
(when (contains? cts/blend-modes value)
|
|
(st/emit! (dwsh/update-shapes [id] #(assoc % :blend-mode value))))))}
|
|
|
|
{:name "shadows"
|
|
:get #(-> % u/proxy->shape :shadow u/array-to-js)
|
|
:set (fn [self value]
|
|
(let [id (obj/get self "$id")
|
|
value (mapv (fn [val]
|
|
;; Merge default shadow properties
|
|
(d/patch-object
|
|
{:id (uuid/next)
|
|
:style :drop-shadow
|
|
:color {:color clr/black :opacity 0.2}
|
|
:offset-x 4
|
|
:offset-y 4
|
|
:blur 4
|
|
:spread 0
|
|
:hidden false}
|
|
(u/from-js val #{:style :type})))
|
|
value)]
|
|
(st/emit! (dwsh/update-shapes [id] #(assoc % :shadow value)))))}
|
|
|
|
{:name "blur"
|
|
:get #(-> % u/proxy->shape :blur u/to-js)
|
|
:set (fn [self value]
|
|
(if (nil? value)
|
|
(st/emit! (dwsh/update-shapes [id] #(dissoc % :blur)))
|
|
(let [id (obj/get self "$id")
|
|
value
|
|
(d/patch-object
|
|
{:id (uuid/next)
|
|
:type :layer-blur
|
|
:value 4
|
|
:hidden false}
|
|
(u/from-js value))]
|
|
(st/emit! (dwsh/update-shapes [id] #(assoc % :blur value))))))}
|
|
|
|
{:name "exports"
|
|
:get #(-> % u/proxy->shape :exports u/array-to-js)
|
|
:set (fn [self value]
|
|
(let [id (obj/get self "$id")
|
|
value (mapv #(u/from-js %) value)]
|
|
(st/emit! (dwsh/update-shapes [id] #(assoc % :exports value)))))}
|
|
|
|
;; Geometry properties
|
|
{:name "x"
|
|
:get #(-> % u/proxy->shape :x)
|
|
:set
|
|
(fn [self value]
|
|
(let [id (obj/get self "$id")]
|
|
(st/emit! (dw/update-position id {:x value}))))}
|
|
|
|
{:name "y"
|
|
:get #(-> % u/proxy->shape :y)
|
|
:set
|
|
(fn [self value]
|
|
(let [id (obj/get self "$id")]
|
|
(st/emit! (dw/update-position id {:y value}))))}
|
|
|
|
{:name "parentX"
|
|
:get (fn [self]
|
|
(let [shape (u/proxy->shape self)
|
|
parent-id (:parent-id shape)
|
|
parent (u/locate-shape (obj/get self "$file") (obj/get self "$page") parent-id)]
|
|
(- (:x shape) (:x parent))))
|
|
:set
|
|
(fn [self value]
|
|
(let [id (obj/get self "$id")
|
|
parent-id (-> self u/proxy->shape :parent-id)
|
|
parent (u/locate-shape (obj/get self "$file") (obj/get self "$page") parent-id)
|
|
parent-x (:x parent)]
|
|
(st/emit! (dw/update-position id {:x (+ parent-x value)}))))}
|
|
|
|
{:name "parentY"
|
|
:get (fn [self]
|
|
(let [shape (u/proxy->shape self)
|
|
parent-id (:parent-id shape)
|
|
parent (u/locate-shape (obj/get self "$file") (obj/get self "$page") parent-id)
|
|
parent-y (:y parent)]
|
|
(- (:y shape) parent-y)))
|
|
:set
|
|
(fn [self value]
|
|
(let [id (obj/get self "$id")
|
|
parent-id (-> self u/proxy->shape :parent-id)
|
|
parent (u/locate-shape (obj/get self "$file") (obj/get self "$page") parent-id)
|
|
parent-y (:y parent)]
|
|
(st/emit! (dw/update-position id {:y (+ parent-y value)}))))}
|
|
|
|
{:name "frameX"
|
|
:get (fn [self]
|
|
(let [shape (u/proxy->shape self)
|
|
frame-id (:parent-id shape)
|
|
frame (u/locate-shape (obj/get self "$file") (obj/get self "$page") frame-id)
|
|
frame-x (:x frame)]
|
|
(- (:x shape) frame-x)))
|
|
:set
|
|
(fn [self value]
|
|
(let [id (obj/get self "$id")
|
|
frame-id (-> self u/proxy->shape :frame-id)
|
|
frame (u/locate-shape (obj/get self "$file") (obj/get self "$page") frame-id)
|
|
frame-x (:x frame)]
|
|
(st/emit! (dw/update-position id {:x (+ frame-x value)}))))}
|
|
|
|
{:name "frameY"
|
|
:get (fn [self]
|
|
(let [shape (u/proxy->shape self)
|
|
frame-id (:parent-id shape)
|
|
frame (u/locate-shape (obj/get self "$file") (obj/get self "$page") frame-id)
|
|
frame-y (:y frame)]
|
|
(- (:y shape) frame-y)))
|
|
:set
|
|
(fn [self value]
|
|
(let [id (obj/get self "$id")
|
|
frame-id (-> self u/proxy->shape :frame-id)
|
|
frame (u/locate-shape (obj/get self "$file") (obj/get self "$page") frame-id)
|
|
frame-y (:y frame)]
|
|
(st/emit! (dw/update-position id {:y (+ frame-y value)}))))}
|
|
|
|
{:name "width"
|
|
:get #(-> % u/proxy->shape :width)}
|
|
|
|
{:name "height"
|
|
:get #(-> % u/proxy->shape :height)}
|
|
|
|
{:name "rotation"
|
|
:get #(-> % u/proxy->shape :rotation)
|
|
:set
|
|
(fn [self value]
|
|
(if (number? value)
|
|
(let [shape (u/proxy->shape self)]
|
|
(st/emit! (dw/increase-rotation #{(:id shape)} value)))
|
|
(u/display-not-valid :rotation value)))}
|
|
|
|
{:name "flipX"
|
|
:get #(-> % u/proxy->shape :flip-x boolean)
|
|
:set
|
|
(fn [self value]
|
|
(if (boolean? value)
|
|
(let [id (obj/get self "$id")]
|
|
(st/emit! (dw/flip-horizontal-selected #{id})))
|
|
(u/display-not-valid :flipX value)))}
|
|
|
|
{:name "flipY"
|
|
:get #(-> % u/proxy->shape :flip-y boolean)
|
|
:set
|
|
(fn [self value]
|
|
(if (boolean? value)
|
|
(let [id (obj/get self "$id")]
|
|
(st/emit! (dw/flip-vertical-selected #{id})))
|
|
(u/display-not-valid :flipY value)))}
|
|
|
|
;; Strokes and fills
|
|
;; TODO: Validate fills input
|
|
{:name "fills"
|
|
:get #(if (cfh/text-shape? data)
|
|
(-> % u/proxy->shape text-props :fills u/array-to-js)
|
|
(-> % u/proxy->shape :fills u/array-to-js))
|
|
:set (fn [self value]
|
|
(let [shape (u/proxy->shape self)
|
|
id (:id shape)
|
|
value (mapv #(u/from-js %) value)]
|
|
(if (cfh/text-shape? shape)
|
|
(st/emit! (dwt/update-attrs id {:fills value}))
|
|
(st/emit! (dwsh/update-shapes [id] #(assoc % :fills value))))))}
|
|
|
|
{:name "strokes"
|
|
:get #(-> % u/proxy->shape :strokes u/array-to-js)
|
|
:set (fn [self value]
|
|
(let [id (obj/get self "$id")
|
|
value (mapv #(u/from-js % #{:stroke-style :stroke-alignment}) value)]
|
|
(st/emit! (dwsh/update-shapes [id] #(assoc % :strokes value)))))}
|
|
|
|
{:name "layoutChild"
|
|
:get
|
|
(fn [self]
|
|
(let [file-id (obj/get self "$file")
|
|
page-id (obj/get self "$page")
|
|
id (obj/get self "$id")
|
|
objects (u/locate-objects file-id page-id)]
|
|
(when (ctl/any-layout-immediate-child-id? objects id)
|
|
(flex/layout-child-proxy plugin-id file-id page-id id))))}
|
|
|
|
{:name "layoutCell"
|
|
:get
|
|
(fn [self]
|
|
(let [file-id (obj/get self "$file")
|
|
page-id (obj/get self "$page")
|
|
id (obj/get self "$id")
|
|
objects (u/locate-objects file-id page-id)]
|
|
(when (ctl/grid-layout-immediate-child-id? objects id)
|
|
(grid/layout-cell-proxy plugin-id file-id page-id id))))})
|
|
|
|
(cond-> (or (cfh/frame-shape? data) (cfh/group-shape? data) (cfh/svg-raw-shape? data) (cfh/bool-shape? data))
|
|
(crc/add-properties!
|
|
{:name "children"
|
|
:enumerable false
|
|
:get #(.getChildren ^js %)}))
|
|
|
|
(cond-> (cfh/frame-shape? data)
|
|
(-> (crc/add-properties!
|
|
{:name "grid"
|
|
:get
|
|
(fn [self]
|
|
(let [layout (-> self u/proxy->shape :layout)
|
|
file-id (obj/get self "$file")
|
|
page-id (obj/get self "$page")
|
|
id (obj/get self "$id")]
|
|
(when (= :grid layout)
|
|
(grid/grid-layout-proxy plugin-id file-id page-id id))))}
|
|
|
|
{:name "flex"
|
|
:get
|
|
(fn [self]
|
|
(let [layout (-> self u/proxy->shape :layout)
|
|
file-id (obj/get self "$file")
|
|
page-id (obj/get self "$page")
|
|
id (obj/get self "$id")]
|
|
(when (= :flex layout)
|
|
(flex/flex-layout-proxy plugin-id file-id page-id id))))}
|
|
|
|
{:name "guides"
|
|
:get #(-> % u/proxy->shape :grids u/array-to-js)
|
|
:set (fn [self value]
|
|
(let [id (obj/get self "$id")
|
|
value (mapv #(u/from-js %) value)]
|
|
(st/emit! (dwsh/update-shapes [id] #(assoc % :grids value)))))}
|
|
|
|
{:name "horizontalSizing"
|
|
:get #(-> % u/proxy->shape :layout-item-h-sizing (d/nilv :fix) d/name)
|
|
:set
|
|
(fn [self value]
|
|
(let [id (obj/get self "$id")
|
|
value (keyword value)]
|
|
(when (contains? #{:fix :auto} value)
|
|
(st/emit! (dwsl/update-layout #{id} {:layout-item-h-sizing value})))))}
|
|
|
|
{:name "verticalSizing"
|
|
:get #(-> % u/proxy->shape :layout-item-v-sizing (d/nilv :fix) d/name)
|
|
:set
|
|
(fn [self value]
|
|
(let [id (obj/get self "$id")
|
|
value (keyword value)]
|
|
(when (contains? #{:fix :auto} value)
|
|
(st/emit! (dwsl/update-layout #{id} {:layout-item-v-sizing value})))))})))
|
|
|
|
(cond-> (cfh/text-shape? data)
|
|
(crc/add-properties!
|
|
{:name "characters"
|
|
:get #(-> % u/proxy->shape :content txt/content->text)
|
|
:set
|
|
(fn [self value]
|
|
(let [id (obj/get self "$id")]
|
|
;; The user is currently editing the text. We need to update the
|
|
;; editor as well
|
|
(when (contains? (:workspace-editor-state @st/state) id)
|
|
(let [shape (u/proxy->shape self)
|
|
editor
|
|
(-> shape
|
|
(txt/change-text value)
|
|
:content
|
|
ted/import-content
|
|
ted/create-editor-state)]
|
|
(st/emit! (dwt/update-editor-state shape editor))))
|
|
(st/emit! (dwsh/update-shapes [id] #(txt/change-text % value)))))}
|
|
|
|
{:name "growType"
|
|
:get #(-> % u/proxy->shape :grow-type d/name)
|
|
:set
|
|
(fn [self value]
|
|
(let [id (obj/get self "$id")
|
|
value (keyword value)]
|
|
(when (contains? #{:auto-width :auto-height :fixed} value)
|
|
(st/emit! (dwsh/update-shapes [id] #(assoc % :grow-type value))))))}
|
|
|
|
{:name "fontId"
|
|
:get #(-> % u/proxy->shape text-props :font-id)
|
|
:set
|
|
(fn [self value]
|
|
(let [id (obj/get self "$id")]
|
|
(st/emit! (dwt/update-attrs id {:font-id value}))))}
|
|
|
|
{:name "fontFamily"
|
|
:get #(-> % u/proxy->shape text-props :font-family)
|
|
:set
|
|
(fn [self value]
|
|
(let [id (obj/get self "$id")]
|
|
(st/emit! (dwt/update-attrs id {:font-id value}))))}
|
|
|
|
{:name "fontVariantId"
|
|
:get #(-> % u/proxy->shape text-props :font-variant-id)
|
|
:set
|
|
(fn [self value]
|
|
(let [id (obj/get self "$id")]
|
|
(st/emit! (dwt/update-attrs id {:font-id value}))))}
|
|
|
|
{:name "fontSize"
|
|
:get #(-> % u/proxy->shape text-props :font-size)
|
|
:set
|
|
(fn [self value]
|
|
(let [id (obj/get self "$id")]
|
|
(st/emit! (dwt/update-attrs id {:font-size value}))))}
|
|
|
|
{:name "fontWeight"
|
|
:get #(-> % u/proxy->shape text-props :font-weight)
|
|
:set
|
|
(fn [self value]
|
|
(let [id (obj/get self "$id")]
|
|
(st/emit! (dwt/update-attrs id {:font-id value}))))}
|
|
|
|
{:name "fontStyle"
|
|
:get #(-> % u/proxy->shape text-props :font-style)
|
|
:set
|
|
(fn [self value]
|
|
(let [id (obj/get self "$id")]
|
|
(st/emit! (dwt/update-attrs id {:font-style value}))))}
|
|
|
|
{:name "lineHeight"
|
|
:get #(-> % u/proxy->shape text-props :line-height)
|
|
:set
|
|
(fn [self value]
|
|
(let [id (obj/get self "$id")]
|
|
(st/emit! (dwt/update-attrs id {:line-height value}))))}
|
|
|
|
{:name "letterSpacing"
|
|
:get #(-> % u/proxy->shape text-props :letter-spacing)
|
|
:set
|
|
(fn [self value]
|
|
(let [id (obj/get self "$id")]
|
|
(st/emit! (dwt/update-attrs id {:letter-spacing value}))))}
|
|
|
|
{:name "textTransform"
|
|
:get #(-> % u/proxy->shape text-props :text-transform)
|
|
:set
|
|
(fn [self value]
|
|
(let [id (obj/get self "$id")]
|
|
(st/emit! (dwt/update-attrs id {:text-transform value}))))}))
|
|
|
|
(cond-> (or (cfh/path-shape? data) (cfh/bool-shape? data))
|
|
(crc/add-properties!
|
|
{:name "content"
|
|
:get #(-> % u/proxy->shape :content u/array-to-js)
|
|
:set
|
|
(fn [_ value]
|
|
(let [content
|
|
(->> value
|
|
(map u/from-js)
|
|
(mapv parse-command)
|
|
(spp/simplify-commands))
|
|
selrect (gsh/content->selrect content)
|
|
points (grc/rect->points selrect)]
|
|
(st/emit! (dwsh/update-shapes [id] (fn [shape] (assoc shape :content content :selrect selrect :points points))))))}))))))
|