From 1794859468b5f1da2294dc8e60a41aaad54e1419 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 19 Jun 2024 11:17:04 +0200 Subject: [PATCH] :sparkles: Review input validation for plugins --- common/src/app/common/types/shape/export.cljc | 2 + .../src/app/main/ui/workspace/plugins.cljs | 21 +- frontend/src/app/plugins/file.cljs | 6 +- frontend/src/app/plugins/flex.cljs | 328 ++++++--- frontend/src/app/plugins/fonts.cljs | 84 ++- frontend/src/app/plugins/grid.cljs | 367 +++++++--- frontend/src/app/plugins/library.cljs | 211 ++++-- frontend/src/app/plugins/page.cljs | 27 +- frontend/src/app/plugins/public_utils.cljs | 14 +- frontend/src/app/plugins/shape.cljs | 693 +++++++++++++----- frontend/src/app/plugins/viewport.cljs | 16 +- .../plugins/context_shapes_test.cljs | 260 +++++++ 12 files changed, 1532 insertions(+), 497 deletions(-) create mode 100644 frontend/test/frontend_tests/plugins/context_shapes_test.cljs diff --git a/common/src/app/common/types/shape/export.cljc b/common/src/app/common/types/shape/export.cljc index 6d7953a88..ddbf4263b 100644 --- a/common/src/app/common/types/shape/export.cljc +++ b/common/src/app/common/types/shape/export.cljc @@ -8,6 +8,8 @@ (:require [app.common.schema :as sm])) +(def export-types #{:png :jpeg :svg :pdf}) + (sm/def! ::export [:map {:title "ShapeExport"} [:type :keyword] diff --git a/frontend/src/app/main/ui/workspace/plugins.cljs b/frontend/src/app/main/ui/workspace/plugins.cljs index 7cca4d86c..cf55b802d 100644 --- a/frontend/src/app/main/ui/workspace/plugins.cljs +++ b/frontend/src/app/main/ui/workspace/plugins.cljs @@ -72,15 +72,18 @@ (defn open-plugin! [{:keys [plugin-id name description host code icon permissions]}] - (.ɵloadPlugin - js/window - #js {:pluginId plugin-id - :name name - :description description - :host host - :code code - :icon icon - :permissions (apply array permissions)})) + (try + (.ɵloadPlugin + js/window + #js {:pluginId plugin-id + :name name + :description description + :host host + :code code + :icon icon + :permissions (apply array permissions)}) + (catch :default e + (.error js/console "Error" e)))) (mf/defc plugin-management-dialog {::mf/register modal/components diff --git a/frontend/src/app/plugins/file.cljs b/frontend/src/app/plugins/file.cljs index 9f8ca7380..9dc4d34b4 100644 --- a/frontend/src/app/plugins/file.cljs +++ b/frontend/src/app/plugins/file.cljs @@ -35,7 +35,7 @@ (setPluginData [_ key value] (cond - (not (string? key)) + (or (not (string? key)) (empty? key)) (u/display-not-valid :file-plugin-data-key key) (and (some? value) (not (string? value))) @@ -66,10 +66,10 @@ [_ namespace key value] (cond - (not (string? namespace)) + (or (not (string? namespace)) (empty? namespace)) (u/display-not-valid :file-plugin-data-namespace namespace) - (not (string? key)) + (or (not (string? key)) (empty? key)) (u/display-not-valid :file-plugin-data-key key) (and (some? value) (not (string? value))) diff --git a/frontend/src/app/plugins/flex.cljs b/frontend/src/app/plugins/flex.cljs index 2f7b50bb7..098d11f2e 100644 --- a/frontend/src/app/plugins/flex.cljs +++ b/frontend/src/app/plugins/flex.cljs @@ -13,10 +13,13 @@ [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 [proxy->shape]] + [app.plugins.utils :as u] [app.util.object :as obj] [potok.v2.core :as ptk])) +;; Define in `app.plugins.shape` we do this way to prevent circular dependency +(def shape-proxy? nil) + (deftype FlexLayout [$plugin $file $page $id] Object (remove @@ -25,9 +28,14 @@ (appendChild [_ child] - (let [child-id (obj/get child "$id")] - (st/emit! (dwt/move-shapes-to-frame #{child-id} $id nil nil) - (ptk/data-event :layout/update {:ids [$id]}))))) + (cond + (not (shape-proxy? child)) + (u/display-not-valid :appendChild child) + + :else + (let [child-id (obj/get child "$id")] + (st/emit! (dwt/move-shapes-to-frame #{child-id} $id nil nil) + (ptk/data-event :layout/update {:ids [$id]})))))) (defn flex-layout-proxy? [p] (instance? FlexLayout p)) @@ -42,113 +50,165 @@ {:name "$page" :enumerable false :get (constantly page-id)} {:name "dir" - :get #(-> % proxy->shape :layout-flex-dir d/name) + :get #(-> % u/proxy->shape :layout-flex-dir d/name) :set (fn [self value] - (let [id (obj/get self "$id") - value (keyword value)] - (when (contains? ctl/flex-direction-types value) - (st/emit! (dwsl/update-layout #{id} {:layout-flex-dir value})))))} + (let [value (keyword value)] + (cond + (not (contains? ctl/flex-direction-types value)) + (u/display-not-valid :dir value) + + :else + (let [id (obj/get self "$id")] + (st/emit! (dwsl/update-layout #{id} {:layout-flex-dir value}))))))} {:name "alignItems" - :get #(-> % proxy->shape :layout-align-items d/name) + :get #(-> % u/proxy->shape :layout-align-items d/name) :set (fn [self value] - (let [id (obj/get self "$id") - value (keyword value)] - (when (contains? ctl/align-items-types value) - (st/emit! (dwsl/update-layout #{id} {:layout-align-items value})))))} + (let [value (keyword value)] + (cond + (not (contains? ctl/align-items-types value)) + (u/display-not-valid :alignItems value) + + :else + (let [id (obj/get self "$id")] + (st/emit! (dwsl/update-layout #{id} {:layout-align-items value}))))))} {:name "alignContent" - :get #(-> % proxy->shape :layout-align-content d/name) + :get #(-> % u/proxy->shape :layout-align-content d/name) :set (fn [self value] - (let [id (obj/get self "$id") - value (keyword value)] - (when (contains? ctl/align-content-types value) - (st/emit! (dwsl/update-layout #{id} {:layout-align-content value})))))} + (let [value (keyword value)] + (cond + (not (contains? ctl/align-content-types value)) + (u/display-not-valid :alignContent value) + + :else + (let [id (obj/get self "$id")] + (st/emit! (dwsl/update-layout #{id} {:layout-align-content value}))))))} {:name "justifyItems" - :get #(-> % proxy->shape :layout-justify-items d/name) + :get #(-> % u/proxy->shape :layout-justify-items d/name) :set (fn [self value] - (let [id (obj/get self "$id") - value (keyword value)] - (when (contains? ctl/justify-items-types value) - (st/emit! (dwsl/update-layout #{id} {:layout-justify-items value})))))} + (let [value (keyword value)] + (cond + (not (contains? ctl/justify-items-types value)) + (u/display-not-valid :justifyItems value) + + :else + (let [id (obj/get self "$id")] + (st/emit! (dwsl/update-layout #{id} {:layout-justify-items value}))))))} {:name "justifyContent" - :get #(-> % proxy->shape :layout-justify-content d/name) + :get #(-> % u/proxy->shape :layout-justify-content d/name) :set (fn [self value] - (let [id (obj/get self "$id") - value (keyword value)] - (when (contains? ctl/justify-content-types value) - (st/emit! (dwsl/update-layout #{id} {:layout-justify-content value})))))} + (let [value (keyword value)] + (cond + (not (contains? ctl/justify-content-types value)) + (u/display-not-valid :justifyContent value) + + :else + (let [id (obj/get self "$id")] + (st/emit! (dwsl/update-layout #{id} {:layout-justify-content value}))))))} {:name "rowGap" - :get #(-> % proxy->shape :layout-gap :row-gap) + :get #(-> % u/proxy->shape :layout-gap :row-gap (d/nilv 0)) :set (fn [self value] - (let [id (obj/get self "$id")] - (when (us/safe-int? value) + (cond + (not (us/safe-int? value)) + (u/display-not-valid :rowGap value) + + :else + (let [id (obj/get self "$id")] (st/emit! (dwsl/update-layout #{id} {:layout-gap {:row-gap value}})))))} {:name "columnGap" - :get #(-> % proxy->shape :layout-gap :column-gap) + :get #(-> % u/proxy->shape :layout-gap :column-gap (d/nilv 0)) :set (fn [self value] - (let [id (obj/get self "$id")] - (when (us/safe-int? value) + (cond + (not (us/safe-int? value)) + (u/display-not-valid :columnGap value) + + :else + (let [id (obj/get self "$id")] (st/emit! (dwsl/update-layout #{id} {:layout-gap {:column-gap value}})))))} {:name "verticalPadding" - :get #(-> % proxy->shape :layout-padding :p1) + :get #(-> % u/proxy->shape :layout-padding :p1 (d/nilv 0)) :set (fn [self value] - (let [id (obj/get self "$id")] - (when (us/safe-int? value) + (cond + (not (us/safe-int? value)) + (u/display-not-valid :verticalPadding value) + + :else + (let [id (obj/get self "$id")] (st/emit! (dwsl/update-layout #{id} {:layout-padding {:p1 value :p3 value}})))))} {:name "horizontalPadding" - :get #(-> % proxy->shape :layout-padding :p2) + :get #(-> % u/proxy->shape :layout-padding :p2 (d/nilv 0)) :set (fn [self value] - (let [id (obj/get self "$id")] - (when (us/safe-int? value) + (cond + (not (us/safe-int? value)) + (u/display-not-valid :horizontalPadding value) + + :else + (let [id (obj/get self "$id")] (st/emit! (dwsl/update-layout #{id} {:layout-padding {:p2 value :p4 value}})))))} {:name "topPadding" - :get #(-> % proxy->shape :layout-padding :p1) + :get #(-> % u/proxy->shape :layout-padding :p1 (d/nilv 0)) :set (fn [self value] - (let [id (obj/get self "$id")] - (when (us/safe-int? value) + (cond + (not (us/safe-int? value)) + (u/display-not-valid :topPadding value) + + :else + (let [id (obj/get self "$id")] (st/emit! (dwsl/update-layout #{id} {:layout-padding {:p1 value}})))))} {:name "rightPadding" - :get #(-> % proxy->shape :layout-padding :p2) + :get #(-> % u/proxy->shape :layout-padding :p2 (d/nilv 0)) :set (fn [self value] - (let [id (obj/get self "$id")] - (when (us/safe-int? value) + (cond + (not (us/safe-int? value)) + (u/display-not-valid :rightPadding value) + + :else + (let [id (obj/get self "$id")] (st/emit! (dwsl/update-layout #{id} {:layout-padding {:p2 value}})))))} {:name "bottomPadding" - :get #(-> % proxy->shape :layout-padding :p3) + :get #(-> % u/proxy->shape :layout-padding :p3 (d/nilv 0)) :set (fn [self value] - (let [id (obj/get self "$id")] - (when (us/safe-int? value) + (cond + (not (us/safe-int? value)) + (u/display-not-valid :bottomPadding value) + + :else + (let [id (obj/get self "$id")] (st/emit! (dwsl/update-layout #{id} {:layout-padding {:p3 value}})))))} {:name "leftPadding" - :get #(-> % proxy->shape :layout-padding :p4) + :get #(-> % u/proxy->shape :layout-padding :p4 (d/nilv 0)) :set (fn [self value] - (let [id (obj/get self "$id")] - (when (us/safe-int? value) + (cond + (not (us/safe-int? value)) + (u/display-not-valid :leftPadding value) + + :else + (let [id (obj/get self "$id")] (st/emit! (dwsl/update-layout #{id} {:layout-padding {:p4 value}})))))}))) @@ -167,124 +227,184 @@ {:name "$page" :enumerable false :get (constantly page-id)} {:name "absolute" - :get #(-> % proxy->shape :layout-item-absolute boolean) + :get #(-> % u/proxy->shape :layout-item-absolute boolean) :set (fn [self value] - (let [id (obj/get self "$id")] - (when (boolean? value) + (cond + (not (boolean? value)) + (u/display-not-valid :absolute value) + + :else + (let [id (obj/get self "$id")] (st/emit! (dwsl/update-layout #{id} {:layout-item-absolute value})))))} {:name "zIndex" - :get #(-> % proxy->shape :layout-item-z-index (d/nilv 0)) + :get #(-> % u/proxy->shape :layout-item-z-index (d/nilv 0)) :set (fn [self value] - (let [id (obj/get self "$id")] - (when (us/safe-int? value) + (cond + (us/safe-int? value) + (u/display-not-valid :zIndex value) + + :else + (let [id (obj/get self "$id")] (st/emit! (dwsl/update-layout-child #{id} {:layout-item-z-index value})))))} {:name "horizontalSizing" - :get #(-> % proxy->shape :layout-item-h-sizing (d/nilv :fix) d/name) + :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? ctl/item-h-sizing-types value) - (st/emit! (dwsl/update-layout-child #{id} {:layout-item-h-sizing value})))))} + (let [value (keyword value)] + (cond + (not (contains? ctl/item-h-sizing-types value)) + (u/display-not-valid :horizontalPadding value) + + :else + (let [id (obj/get self "$id")] + (st/emit! (dwsl/update-layout-child #{id} {:layout-item-h-sizing value}))))))} {:name "verticalSizing" - :get #(-> % proxy->shape :layout-item-v-sizing (d/nilv :fix) d/name) + :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? ctl/item-v-sizing-types value) - (st/emit! (dwsl/update-layout-child #{id} {:layout-item-v-sizing value})))))} + (let [value (keyword value)] + (cond + (not (contains? ctl/item-v-sizing-types value)) + (u/display-not-valid :verticalSizing value) + + :else + (let [id (obj/get self "$id")] + (st/emit! (dwsl/update-layout-child #{id} {:layout-item-v-sizing value}))))))} {:name "alignSelf" - :get #(-> % proxy->shape :layout-item-align-self (d/nilv :auto) d/name) + :get #(-> % u/proxy->shape :layout-item-align-self (d/nilv :auto) d/name) :set (fn [self value] - (let [id (obj/get self "$id") - value (keyword value)] - (when (contains? ctl/item-align-self-types value) - (st/emit! (dwsl/update-layout-child #{id} {:layout-item-align-self value})))))} + (let [value (keyword value)] + (cond + (not (contains? ctl/item-align-self-types value)) + (u/display-not-valid :alignSelf value) + + :else + (let [id (obj/get self "$id")] + (st/emit! (dwsl/update-layout-child #{id} {:layout-item-align-self value}))))))} {:name "verticalMargin" - :get #(-> % proxy->shape :layout-item-margin :m1 (d/nilv 0)) + :get #(-> % u/proxy->shape :layout-item-margin :m1 (d/nilv 0)) :set (fn [self value] - (let [id (obj/get self "$id")] - (when (us/safe-number? value) + (cond + (not (us/safe-number? value)) + (u/display-not-valid :verticalMargin value) + + :else + (let [id (obj/get self "$id")] (st/emit! (dwsl/update-layout-child #{id} {:layout-item-margin {:m1 value :m3 value}})))))} {:name "horizontalMargin" - :get #(-> % proxy->shape :layout-item-margin :m2 (d/nilv 0)) + :get #(-> % u/proxy->shape :layout-item-margin :m2 (d/nilv 0)) :set (fn [self value] - (let [id (obj/get self "$id")] - (when (us/safe-number? value) + (cond + (not (us/safe-number? value)) + (u/display-not-valid :horizontalMargin value) + + :else + (let [id (obj/get self "$id")] (st/emit! (dwsl/update-layout-child #{id} {:layout-item-margin {:m2 value :m4 value}})))))} {:name "topMargin" - :get #(-> % proxy->shape :layout-item-margin :m1 (d/nilv 0)) + :get #(-> % u/proxy->shape :layout-item-margin :m1 (d/nilv 0)) :set (fn [self value] - (let [id (obj/get self "$id")] - (when (us/safe-number? value) + (cond + (not (us/safe-number? value)) + (u/display-not-valid :topMargin value) + + :else + (let [id (obj/get self "$id")] (st/emit! (dwsl/update-layout-child #{id} {:layout-item-margin {:m1 value}})))))} {:name "rightMargin" - :get #(-> % proxy->shape :layout-item-margin :m2 (d/nilv 0)) + :get #(-> % u/proxy->shape :layout-item-margin :m2 (d/nilv 0)) :set (fn [self value] - (let [id (obj/get self "$id")] - (when (us/safe-number? value) + (cond + (not (us/safe-number? value)) + (u/display-not-valid :rightMargin value) + + :else + (let [id (obj/get self "$id")] (st/emit! (dwsl/update-layout-child #{id} {:layout-item-margin {:m2 value}})))))} {:name "bottomMargin" - :get #(-> % proxy->shape :layout-item-margin :m3 (d/nilv 0)) + :get #(-> % u/proxy->shape :layout-item-margin :m3 (d/nilv 0)) :set (fn [self value] - (let [id (obj/get self "$id")] - (when (us/safe-number? value) + (cond + (not (us/safe-number? value)) + (u/display-not-valid :bottomMargin value) + + :else + (let [id (obj/get self "$id")] (st/emit! (dwsl/update-layout-child #{id} {:layout-item-margin {:m3 value}})))))} {:name "leftMargin" - :get #(-> % proxy->shape :layout-item-margin :m4 (d/nilv 0)) + :get #(-> % u/proxy->shape :layout-item-margin :m4 (d/nilv 0)) :set (fn [self value] - (let [id (obj/get self "$id")] - (when (us/safe-number? value) + (cond + (not (us/safe-number? value)) + (u/display-not-valid :leftMargin value) + + :else + (let [id (obj/get self "$id")] (st/emit! (dwsl/update-layout-child #{id} {:layout-item-margin {:m4 value}})))))} {:name "maxWidth" - :get #(-> % proxy->shape :layout-item-max-w) + :get #(-> % u/proxy->shape :layout-item-max-w) :set (fn [self value] - (let [id (obj/get self "$id")] - (when (us/safe-number? value) + (cond + (not (us/safe-number? value)) + (u/display-not-valid :maxWidth value) + + :else + (let [id (obj/get self "$id")] (st/emit! (dwsl/update-layout-child #{id} {:layout-item-max-w value})))))} {:name "minWidth" - :get #(-> % proxy->shape :layout-item-min-w) + :get #(-> % u/proxy->shape :layout-item-min-w) :set (fn [self value] - (let [id (obj/get self "$id")] - (when (us/safe-number? value) + (cond + (not (us/safe-number? value)) + (u/display-not-valid :minWidth value) + + :else + (let [id (obj/get self "$id")] (st/emit! (dwsl/update-layout-child #{id} {:layout-item-min-w value})))))} {:name "maxHeight" - :get #(-> % proxy->shape :layout-item-max-h) + :get #(-> % u/proxy->shape :layout-item-max-h) :set (fn [self value] - (let [id (obj/get self "$id")] - (when (us/safe-number? value) + (cond + (not (us/safe-number? value)) + (u/display-not-valid :maxHeight value) + + :else + (let [id (obj/get self "$id")] (st/emit! (dwsl/update-layout-child #{id} {:layout-item-max-h value})))))} {:name "minHeight" - :get #(-> % proxy->shape :layout-item-min-h) + :get #(-> % u/proxy->shape :layout-item-min-h) :set (fn [self value] - (let [id (obj/get self "$id")] - (when (us/safe-number? value) + (cond + (not (us/safe-number? value)) + (u/display-not-valid :minHeight value) + + :else + (let [id (obj/get self "$id")] (st/emit! (dwsl/update-layout-child #{id} {:layout-item-min-h value})))))}))) diff --git a/frontend/src/app/plugins/fonts.cljs b/frontend/src/app/plugins/fonts.cljs index fec42342d..4902277bc 100644 --- a/frontend/src/app/plugins/fonts.cljs +++ b/frontend/src/app/plugins/fonts.cljs @@ -11,6 +11,8 @@ [app.main.data.workspace.texts :as dwt] [app.main.fonts :as fonts] [app.main.store :as st] + [app.plugins.shape :as shape] + [app.plugins.utils :as u] [app.util.object :as obj] [cuerdas.core :as str])) @@ -20,24 +22,38 @@ Object (applyToText [_ text variant] - (let [id (obj/get text "$id") - values {:font-id fontId - :font-family fontFamily - :font-style (d/nilv (obj/get variant "fontStyle") fontStyle) - :font-variant-id (d/nilv (obj/get variant "fontVariantId") fontVariantId) - :font-weight (d/nilv (obj/get variant "fontWeight") fontWeight)}] - (st/emit! (dwt/update-attrs id values)))) + (cond + (not (shape/shape-proxy? text)) + (u/display-not-valid :applyToText text) + + ;; TODO: Check variant inside font variants + + :else + (let [id (obj/get text "$id") + values {:font-id fontId + :font-family fontFamily + :font-style (d/nilv (obj/get variant "fontStyle") fontStyle) + :font-variant-id (d/nilv (obj/get variant "fontVariantId") fontVariantId) + :font-weight (d/nilv (obj/get variant "fontWeight") fontWeight)}] + (st/emit! (dwt/update-attrs id values))))) (applyToRange [_ range variant] - (let [id (obj/get range "$id") - start (obj/get range "start") - end (obj/get range "end") - values {:font-id fontId - :font-family fontFamily - :font-style (d/nilv (obj/get variant "fontStyle") fontStyle) - :font-variant-id (d/nilv (obj/get variant "fontVariantId") fontVariantId) - :font-weight (d/nilv (obj/get variant "fontWeight") fontWeight)}] - (st/emit! (dwt/update-text-range id start end values))))) + (cond + (not (shape/text-range? range)) + (u/display-not-valid :applyToRange range) + + ;; TODO: Check variant inside font variants + + :else + (let [id (obj/get range "$id") + start (obj/get range "start") + end (obj/get range "end") + values {:font-id fontId + :font-family fontFamily + :font-style (d/nilv (obj/get variant "fontStyle") fontStyle) + :font-variant-id (d/nilv (obj/get variant "fontVariantId") fontVariantId) + :font-weight (d/nilv (obj/get variant "fontWeight") fontWeight)}] + (st/emit! (dwt/update-text-range id start end values)))))) (defn font-proxy? [p] (instance? PenpotFont p)) @@ -63,23 +79,43 @@ Object (findById [_ id] - (font-proxy (d/seek #(str/includes? (str/lower (:id %)) (str/lower id)) (vals @fonts/fontsdb)))) + (cond + (not (string? id)) + (u/display-not-valid :findbyId id) + + :else + (font-proxy (d/seek #(str/includes? (str/lower (:id %)) (str/lower id)) (vals @fonts/fontsdb))))) (findByName [_ name] - (font-proxy (d/seek #(str/includes? (str/lower (:name %)) (str/lower name)) (vals @fonts/fontsdb)))) + (cond + (not (string? name)) + (u/display-not-valid :findByName name) + + :else + (font-proxy (d/seek #(str/includes? (str/lower (:name %)) (str/lower name)) (vals @fonts/fontsdb))))) (findAllById [_ id] - (apply array (->> (vals @fonts/fontsdb) - (filter #(str/includes? (str/lower (:id %)) (str/lower id))) - (map font-proxy)))) + (cond + (not (string? id)) + (u/display-not-valid :findAllById name) + + :else + (apply array (->> (vals @fonts/fontsdb) + (filter #(str/includes? (str/lower (:id %)) (str/lower id))) + (map font-proxy))))) (findAllByName [_ name] - (apply array (->> (vals @fonts/fontsdb) - (filter #(str/includes? (str/lower (:name %)) (str/lower name))) - (map font-proxy))))) + (cond + (not (string? name)) + (u/display-not-valid :findAllByName name) + + :else + (apply array (->> (vals @fonts/fontsdb) + (filter #(str/includes? (str/lower (:name %)) (str/lower name))) + (map font-proxy)))))) (defn fonts-subcontext [plugin-id] diff --git a/frontend/src/app/plugins/grid.cljs b/frontend/src/app/plugins/grid.cljs index 1d9d4799f..a715e0c7a 100644 --- a/frontend/src/app/plugins/grid.cljs +++ b/frontend/src/app/plugins/grid.cljs @@ -13,15 +13,18 @@ [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 [proxy->shape locate-shape]] + [app.plugins.utils :as u] [app.util.object :as obj] [potok.v2.core :as ptk])) +;; Define in `app.plugins.shape` we do this way to prevent circular dependency +(def shape-proxy? nil) + (defn- make-tracks [tracks] (.freeze js/Object - (apply array (->> tracks (map utils/to-js))))) + (apply array (->> tracks (map u/to-js))))) (deftype GridLayout [$plugin $file $page $id] Object @@ -29,40 +32,116 @@ (addRow [_ type value] (let [type (keyword type)] - (st/emit! (dwsl/add-layout-track #{$id} :row {:type type :value value})))) + (cond + (not (contains? ctl/grid-track-types type)) + (u/display-not-valid :addRow-type type) + + (and (or (= :percent type) (= :flex type) (= :fixed type)) + (not (us/safe-number? value))) + (u/display-not-valid :addRow-value value) + + :else + (st/emit! (dwsl/add-layout-track #{$id} :row {:type type :value value}))))) (addRowAtIndex [_ index type value] (let [type (keyword type)] - (st/emit! (dwsl/add-layout-track #{$id} :row {:type type :value value} index)))) + (cond + (not (us/safe-int? index)) + (u/display-not-valid :addRowAtIndex-index index) + + (not (contains? ctl/grid-track-types type)) + (u/display-not-valid :addRowAtIndex-type type) + + (and (or (= :percent type) (= :flex type) (= :fixed type)) + (not (us/safe-number? value))) + (u/display-not-valid :addRowAtIndex-value value) + + :else + (st/emit! (dwsl/add-layout-track #{$id} :row {:type type :value value} index))))) (addColumn [_ type value] (let [type (keyword type)] - (st/emit! (dwsl/add-layout-track #{$id} :column {:type type :value value})))) + (cond + (not (contains? ctl/grid-track-types type)) + (u/display-not-valid :addColumn-type type) + + (and (or (= :percent type) (= :flex type) (= :lex type)) + (not (us/safe-number? value))) + (u/display-not-valid :addColumn-value value) + + :else + (st/emit! (dwsl/add-layout-track #{$id} :column {:type type :value value}))))) (addColumnAtIndex [_ index type value] - (let [type (keyword type)] - (st/emit! (dwsl/add-layout-track #{$id} :column {:type type :value value} index)))) + (cond + (not (us/safe-int? index)) + (u/display-not-valid :addColumnAtIndex-index index) + + (not (contains? ctl/grid-track-types type)) + (u/display-not-valid :addColumnAtIndex-type type) + + (and (or (= :percent type) (= :flex type) (= :fixed type)) + (not (us/safe-number? value))) + (u/display-not-valid :addColumnAtIndex-value value) + + :else + (let [type (keyword type)] + (st/emit! (dwsl/add-layout-track #{$id} :column {:type type :value value} index))))) (removeRow [_ index] - (st/emit! (dwsl/remove-layout-track #{$id} :row index))) + (cond + (not (us/safe-int? index)) + (u/display-not-valid :removeRow index) + + :else + (st/emit! (dwsl/remove-layout-track #{$id} :row index)))) (removeColumn [_ index] - (st/emit! (dwsl/remove-layout-track #{$id} :column index))) + (cond + (not (us/safe-int? index)) + (u/display-not-valid :removeColumn index) + + :else + (st/emit! (dwsl/remove-layout-track #{$id} :column index)))) (setColumn [_ index type value] (let [type (keyword type)] - (st/emit! (dwsl/change-layout-track #{$id} :column index (d/without-nils {:type type :value value}))))) + (cond + (not (us/safe-int? index)) + (u/display-not-valid :setColumn-index index) + + (not (contains? ctl/grid-track-types type)) + (u/display-not-valid :setColumn-type type) + + (and (or (= :percent type) (= :flex type) (= :fixed type)) + (not (us/safe-number? value))) + (u/display-not-valid :setColumn-value value) + + :else + (st/emit! (dwsl/change-layout-track #{$id} :column index (d/without-nils {:type type :value value})))))) (setRow [_ index type value] (let [type (keyword type)] - (st/emit! (dwsl/change-layout-track #{$id} :row index (d/without-nils {:type type :value value}))))) + (cond + (not (us/safe-int? index)) + (u/display-not-valid :setRow-index index) + + (not (contains? ctl/grid-track-types type)) + (u/display-not-valid :setRow-type type) + + (and (or (= :percent type) (= :flex type) (= :fixed type)) + (not (us/safe-number? value))) + (u/display-not-valid :setRow-value value) + + :else + (st/emit! (dwsl/change-layout-track #{$id} :row index (d/without-nils {:type type :value value})))))) (remove [_] @@ -70,9 +149,20 @@ (appendChild [_ child row column] - (let [child-id (obj/get child "$id")] - (st/emit! (dwt/move-shapes-to-frame #{child-id} $id nil [row column]) - (ptk/data-event :layout/update {:ids [$id]}))))) + (cond + (not (shape-proxy? child)) + (u/display-not-valid :appendChild-child child) + + (or (< row 0) (not (us/safe-int? row))) + (u/display-not-valid :appendChild-row row) + + (or (< column 0) (not (us/safe-int? column))) + (u/display-not-valid :appendChild-column column) + + :else + (let [child-id (obj/get child "$id")] + (st/emit! (dwt/move-shapes-to-frame #{child-id} $id nil [row column]) + (ptk/data-event :layout/update {:ids [$id]})))))) (defn grid-layout-proxy? [p] (instance? GridLayout p)) @@ -85,119 +175,172 @@ {:name "$id" :enumerable false :get (constantly id)} {:name "$file" :enumerable false :get (constantly file-id)} {:name "$page" :enumerable false :get (constantly page-id)} + {:name "dir" - :get #(-> % proxy->shape :layout-grid-dir d/name) + :get #(-> % u/proxy->shape :layout-grid-dir d/name) :set (fn [self value] - (let [id (obj/get self "$id") - value (keyword value)] - (when (contains? ctl/grid-direction-types value) - (st/emit! (dwsl/update-layout #{id} {:layout-grid-dir value})))))} + (let [value (keyword value)] + (cond + (not (contains? ctl/grid-direction-types value)) + (u/display-not-valid :dir value) + + :else + (let [id (obj/get self "$id")] + (st/emit! (dwsl/update-layout #{id} {:layout-grid-dir value}))))))} {:name "rows" - :get #(-> % proxy->shape :layout-grid-rows make-tracks)} + :get #(-> % u/proxy->shape :layout-grid-rows make-tracks)} {:name "columns" - :get #(-> % proxy->shape :layout-grid-columns make-tracks)} + :get #(-> % u/proxy->shape :layout-grid-columns make-tracks)} {:name "alignItems" - :get #(-> % proxy->shape :layout-align-items d/name) + :get #(-> % u/proxy->shape :layout-align-items d/name) :set (fn [self value] - (let [id (obj/get self "$id") - value (keyword value)] - (when (contains? ctl/align-items-types value) - (st/emit! (dwsl/update-layout #{id} {:layout-align-items value})))))} + (let [value (keyword value)] + (cond + (not (contains? ctl/align-items-types value)) + (u/display-not-valid :alignItems value) + + :else + (let [id (obj/get self "$id")] + (st/emit! (dwsl/update-layout #{id} {:layout-align-items value}))))))} {:name "alignContent" - :get #(-> % proxy->shape :layout-align-content d/name) + :get #(-> % u/proxy->shape :layout-align-content d/name) :set (fn [self value] - (let [id (obj/get self "$id") - value (keyword value)] - (when (contains? ctl/align-content-types value) - (st/emit! (dwsl/update-layout #{id} {:layout-align-content value})))))} + (let [value (keyword value)] + (cond + (not (contains? ctl/align-content-types value)) + (u/display-not-valid :alignContent value) + + :else + (let [id (obj/get self "$id")] + (st/emit! (dwsl/update-layout #{id} {:layout-align-content value}))))))} {:name "justifyItems" - :get #(-> % proxy->shape :layout-justify-items d/name) + :get #(-> % u/proxy->shape :layout-justify-items d/name) :set (fn [self value] - (let [id (obj/get self "$id") - value (keyword value)] - (when (contains? ctl/justify-items-types value) - (st/emit! (dwsl/update-layout #{id} {:layout-justify-items value})))))} + (let [value (keyword value)] + (cond + (not (contains? ctl/justify-items-types value)) + (u/display-not-valid :justifyItems value) + + :else + (let [id (obj/get self "$id")] + (st/emit! (dwsl/update-layout #{id} {:layout-justify-items value}))))))} {:name "justifyContent" - :get #(-> % proxy->shape :layout-justify-content d/name) + :get #(-> % u/proxy->shape :layout-justify-content d/name) :set (fn [self value] - (let [id (obj/get self "$id") - value (keyword value)] - (when (contains? ctl/justify-content-types value) - (st/emit! (dwsl/update-layout #{id} {:layout-justify-content value})))))} + (let [value (keyword value)] + (cond + (not (contains? ctl/justify-content-types value)) + (u/display-not-valid :justifyContent value) + + :else + (let [id (obj/get self "$id")] + (st/emit! (dwsl/update-layout #{id} {:layout-justify-content value}))))))} {:name "rowGap" - :get #(-> % proxy->shape :layout-gap :row-gap) + :get #(-> % u/proxy->shape :layout-gap :row-gap (d/nilv 0)) :set (fn [self value] - (let [id (obj/get self "$id")] - (when (us/safe-int? value) + (cond + (not (us/safe-int? value)) + (u/display-not-valid :rowGap value) + + :else + (let [id (obj/get self "$id")] (st/emit! (dwsl/update-layout #{id} {:layout-gap {:row-gap value}})))))} {:name "columnGap" - :get #(-> % proxy->shape :layout-gap :column-gap) + :get #(-> % u/proxy->shape :layout-gap :column-gap (d/nilv 0)) :set (fn [self value] - (let [id (obj/get self "$id")] - (when (us/safe-int? value) + (cond + (not (us/safe-int? value)) + (u/display-not-valid :columnGap value) + + :else + (let [id (obj/get self "$id")] (st/emit! (dwsl/update-layout #{id} {:layout-gap {:column-gap value}})))))} {:name "verticalPadding" - :get #(-> % proxy->shape :layout-padding :p1) + :get #(-> % u/proxy->shape :layout-padding :p1 (d/nilv 0)) :set (fn [self value] - (let [id (obj/get self "$id")] - (when (us/safe-int? value) + (cond + (not (us/safe-int? value)) + (u/display-not-valid :verticalPadding value) + + :else + (let [id (obj/get self "$id")] (st/emit! (dwsl/update-layout #{id} {:layout-padding {:p1 value :p3 value}})))))} {:name "horizontalPadding" - :get #(-> % proxy->shape :layout-padding :p2) + :get #(-> % u/proxy->shape :layout-padding :p2 (d/nilv 0)) :set (fn [self value] - (let [id (obj/get self "$id")] - (when (us/safe-int? value) + (cond + (not (us/safe-int? value)) + (u/display-not-valid :horizontalPadding value) + + :else + (let [id (obj/get self "$id")] (st/emit! (dwsl/update-layout #{id} {:layout-padding {:p2 value :p4 value}})))))} {:name "topPadding" - :get #(-> % proxy->shape :layout-padding :p1) + :get #(-> % u/proxy->shape :layout-padding :p1 (d/nilv 0)) :set (fn [self value] - (let [id (obj/get self "$id")] - (when (us/safe-int? value) + (cond + (not (us/safe-int? value)) + (u/display-not-valid :topPadding value) + + :else + (let [id (obj/get self "$id")] (st/emit! (dwsl/update-layout #{id} {:layout-padding {:p1 value}})))))} {:name "rightPadding" - :get #(-> % proxy->shape :layout-padding :p2) + :get #(-> % u/proxy->shape :layout-padding :p2 (d/nilv 0)) :set (fn [self value] - (let [id (obj/get self "$id")] - (when (us/safe-int? value) + (cond + (not (us/safe-int? value)) + (u/display-not-valid :rightPadding value) + + :else + (let [id (obj/get self "$id")] (st/emit! (dwsl/update-layout #{id} {:layout-padding {:p2 value}})))))} {:name "bottomPadding" - :get #(-> % proxy->shape :layout-padding :p3) + :get #(-> % u/proxy->shape :layout-padding :p3 (d/nilv 0)) :set (fn [self value] - (let [id (obj/get self "$id")] - (when (us/safe-int? value) + (cond + (not (us/safe-int? value)) + (u/display-not-valid :bottomPadding value) + + :else + (let [id (obj/get self "$id")] (st/emit! (dwsl/update-layout #{id} {:layout-padding {:p3 value}})))))} {:name "leftPadding" - :get #(-> % proxy->shape :layout-padding :p4) + :get #(-> % u/proxy->shape :layout-padding :p4 (d/nilv 0)) :set (fn [self value] - (let [id (obj/get self "$id")] - (when (us/safe-int? value) + (cond + (not (us/safe-int? value)) + (u/display-not-valid :leftPadding value) + + :else + (let [id (obj/get self "$id")] (st/emit! (dwsl/update-layout #{id} {:layout-padding {:p4 value}})))))}))) (deftype GridCellProxy [$plugin $file $page $id]) @@ -208,8 +351,8 @@ (defn layout-cell-proxy [plugin-id file-id page-id id] (letfn [(locate-cell [_] - (let [shape (locate-shape file-id page-id id) - parent (locate-shape file-id page-id (:parent-id shape))] + (let [shape (u/locate-shape file-id page-id id) + parent (u/locate-shape file-id page-id (:parent-id shape))] (ctl/get-cell-by-shape-id parent id)))] (-> (GridCellProxy. plugin-id file-id page-id id) @@ -223,73 +366,129 @@ :get #(-> % locate-cell :row) :set (fn [self value] - (let [shape (proxy->shape self) - cell (locate-cell self)] - (when (us/safe-int? value) + (let [cell (locate-cell self) + shape (u/proxy->shape self)] + (cond + (not (us/safe-int? value)) + (u/display-not-valid :row value) + + (nil? cell) + (u/display-not-valid :cell "cell not found") + + :else (st/emit! (dwsl/update-grid-cell-position (:parent-id shape) (:id cell) {:row value})))))} {:name "rowSpan" :get #(-> % locate-cell :row-span) :set (fn [self value] - (let [shape (proxy->shape self) + (let [shape (u/proxy->shape self) cell (locate-cell self)] - (when (us/safe-int? value) + (cond + (not (us/safe-int? value)) + (u/display-not-valid :rowSpan-value value) + + (nil? cell) + (u/display-not-valid :rowSpan-cell "cell not found") + + :else (st/emit! (dwsl/update-grid-cell-position (:parent-id shape) (:id cell) {:row-span value})))))} {:name "column" :get #(-> % locate-cell :column) :set (fn [self value] - (let [shape (proxy->shape self) + (let [shape (u/proxy->shape self) cell (locate-cell self)] - (when (us/safe-int? value) + (cond + (not (us/safe-int? value)) + (u/display-not-valid :column-value value) + + (nil? cell) + (u/display-not-valid :column-cell "cell not found") + + :else (st/emit! (dwsl/update-grid-cell-position (:parent-id shape) (:id cell) {:column value})))))} {:name "columnSpan" :get #(-> % locate-cell :column-span) :set (fn [self value] - (let [shape (proxy->shape self) + (let [shape (u/proxy->shape self) cell (locate-cell self)] - (when (us/safe-int? value) + (cond + (not (us/safe-int? value)) + (u/display-not-valid :columnSpan-value value) + + (nil? cell) + (u/display-not-valid :columnSpan-cell "cell not found") + + :else (st/emit! (dwsl/update-grid-cell-position (:parent-id shape) (:id cell) {:column-span value})))))} {:name "areaName" :get #(-> % locate-cell :area-name) :set (fn [self value] - (let [shape (proxy->shape self) + (let [shape (u/proxy->shape self) cell (locate-cell self)] - (when (string? value) + (cond + (not (string? value)) + (u/display-not-valid :areaName-value value) + + (nil? cell) + (u/display-not-valid :areaName-cell "cell not found") + + :else (st/emit! (dwsl/update-grid-cells (:parent-id shape) #{(:id cell)} {:area-name value})))))} {:name "position" :get #(-> % locate-cell :position d/name) :set (fn [self value] - (let [shape (proxy->shape self) + (let [shape (u/proxy->shape self) cell (locate-cell self) value (keyword value)] - (when (contains? ctl/grid-position-types value) + (cond + (not (contains? ctl/grid-position-types value)) + (u/display-not-valid :position-value value) + + (nil? cell) + (u/display-not-valid :position-cell "cell not found") + + :else (st/emit! (dwsl/change-cells-mode (:parent-id shape) #{(:id cell)} value)))))} {:name "alignSelf" :get #(-> % locate-cell :align-self d/name) :set (fn [self value] - (let [shape (proxy->shape self) + (let [shape (u/proxy->shape self) value (keyword value) cell (locate-cell self)] - (when (contains? ctl/grid-cell-align-self-types value) + (cond + (not (contains? ctl/grid-cell-align-self-types value)) + (u/display-not-valid :alignSelf-value value) + + (nil? cell) + (u/display-not-valid :alignSelf-cell "cell not found") + + :else (st/emit! (dwsl/update-grid-cells (:parent-id shape) #{(:id cell)} {:align-self value})))))} {:name "justifySelf" :get #(-> % locate-cell :justify-self d/name) :set (fn [self value] - (let [shape (proxy->shape self) + (let [shape (u/proxy->shape self) value (keyword value) cell (locate-cell self)] - (when (contains? ctl/grid-cell-justify-self-types value) + (cond + (not (contains? ctl/grid-cell-justify-self-types value)) + (u/display-not-valid :justifySelf-value value) + + (nil? cell) + (u/display-not-valid :justifySelf-cell "cell not found") + + :else (st/emit! (dwsl/update-grid-cells (:parent-id shape) #{(:id cell)} {:justify-self value})))))})))) diff --git a/frontend/src/app/plugins/library.cljs b/frontend/src/app/plugins/library.cljs index 8107ca375..de163e8b5 100644 --- a/frontend/src/app/plugins/library.cljs +++ b/frontend/src/app/plugins/library.cljs @@ -20,7 +20,7 @@ [app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.texts :as dwt] [app.main.store :as st] - [app.plugins.shape :as shapes] + [app.plugins.shape :as shape] [app.plugins.utils :as u] [app.util.object :as obj])) @@ -157,63 +157,81 @@ :get #(-> % u/proxy->library-color :name) :set (fn [self value] - (if (and (some? value) (string? value)) + (cond + (not (string? value)) + (u/display-not-valid :library-color-name value) + + :else (let [color (u/proxy->library-color self) value (dm/str (d/nilv (:path color) "") " / " value)] - (st/emit! (dwl/rename-color file-id id value))) - (u/display-not-valid :library-color-name value)))} + (st/emit! (dwl/rename-color file-id id value)))))} {:name "path" :get #(-> % u/proxy->library-color :path) :set (fn [self value] - (if (and (some? value) (string? value)) + (cond + (not (string? value)) + (u/display-not-valid :library-color-path value) + + :else (let [color (-> (u/proxy->library-color self) (update :name #(str value " / " %)))] - (st/emit! (dwl/update-color color file-id))) - (u/display-not-valid :library-color-path value)))} + (st/emit! (dwl/update-color color file-id)))))} {:name "color" :get #(-> % u/proxy->library-color :color) :set (fn [self value] - (if (and (some? value) (string? value) (cc/valid-hex-color? value)) + (cond + (or (not (string? value)) (not (cc/valid-hex-color? value))) + (u/display-not-valid :library-color-color value) + + :else (let [color (-> (u/proxy->library-color self) (assoc :color value))] - (st/emit! (dwl/update-color color file-id))) - (u/display-not-valid :library-color-color value)))} + (st/emit! (dwl/update-color color file-id)))))} {:name "opacity" :get #(-> % u/proxy->library-color :opacity) :set (fn [self value] - (if (and (some? value) (number? value) (>= value 0) (<= value 1)) + (cond + (or (not (number? value)) (< value 0) (> value 1)) + (u/display-not-valid :library-color-opacity value) + + :else (let [color (-> (u/proxy->library-color self) (assoc :opacity value))] - (st/emit! (dwl/update-color color file-id))) - (u/display-not-valid :library-color-opacity value)))} + (st/emit! (dwl/update-color color file-id)))))} {:name "gradient" :get #(-> % u/proxy->library-color :gradient u/to-js) :set (fn [self value] (let [value (u/from-js value)] - (if (sm/fast-check! ::ctc/gradient value) + (cond + (not (sm/validate ::ctc/gradient value)) + (u/display-not-valid :library-color-gradient value) + + :else (let [color (-> (u/proxy->library-color self) (assoc :gradient value))] - (st/emit! (dwl/update-color color file-id))) - (u/display-not-valid :library-color-gradient value))))} + (st/emit! (dwl/update-color color file-id))))))} {:name "image" :get #(-> % u/proxy->library-color :image u/to-js) :set (fn [self value] (let [value (u/from-js value)] - (if (sm/fast-check! ::ctc/image-color value) + (cond + (not (sm/validate ::ctc/image-color value)) + (u/display-not-valid :library-color-image value) + + :else (let [color (-> (u/proxy->library-color self) (assoc :image value))] - (st/emit! (dwl/update-color color file-id))) - (u/display-not-valid :library-color-image value))))})) + (st/emit! (dwl/update-color color file-id))))))})) (deftype LibraryTypographyProxy [$plugin $file $id] Object @@ -231,21 +249,31 @@ (applyToText [_ shape] - (let [shape-id (obj/get shape "$id") - typography (u/locate-library-typography $file $id)] - (st/emit! (dwt/apply-typography #{shape-id} typography $file)))) + (cond + (not (shape/shape-proxy? shape)) + (u/display-not-valid :applyToText shape) + + :else + (let [shape-id (obj/get shape "$id") + typography (u/locate-library-typography $file $id)] + (st/emit! (dwt/apply-typography #{shape-id} typography $file))))) (applyToTextRange [self range] - (let [shape-id (obj/get range "$id") - start (obj/get range "start") - end (obj/get range "end") - typography (u/proxy->library-typography self) - attrs (-> typography - (assoc :typography-ref-file $file) - (assoc :typography-ref-id (:id typography)) - (dissoc :id :name))] - (st/emit! (dwt/update-text-range shape-id start end attrs)))) + (cond + (not (shape/text-range? range)) + (u/display-not-valid :applyToText range) + + :else + (let [shape-id (obj/get range "$id") + start (obj/get range "start") + end (obj/get range "end") + typography (u/proxy->library-typography self) + attrs (-> typography + (assoc :typography-ref-file $file) + (assoc :typography-ref-id (:id typography)) + (dissoc :id :name))] + (st/emit! (dwt/update-text-range shape-id start end attrs))))) ;; PLUGIN DATA (getPluginData @@ -322,6 +350,8 @@ (defn lib-typography-proxy? [p] (instance? LibraryTypographyProxy p)) +(set! shape/lib-typography-proxy? lib-typography-proxy?) + (defn lib-typography-proxy [plugin-id file-id id] (assert (uuid? file-id)) @@ -338,111 +368,144 @@ :get #(-> % u/proxy->library-typography :name) :set (fn [self value] - (if (and (some? value) (string? value)) + (cond + (not (string? value)) + (u/display-not-valid :library-typography-name value) + + :else (let [typo (u/proxy->library-typography self) value (dm/str (d/nilv (:path typo) "") " / " value)] - (st/emit! (dwl/rename-typography file-id id value))) - (u/display-not-valid :library-typography-name value)))} + (st/emit! (dwl/rename-typography file-id id value)))))} {:name "path" :get #(-> % u/proxy->library-typography :path) :set (fn [self value] - (if (and (some? value) (string? value)) + (cond + (not (string? value)) + (u/display-not-valid :library-typography-path value) + + :else (let [typo (-> (u/proxy->library-typography self) (update :name #(str value " / " %)))] - (st/emit! (dwl/update-typography typo file-id))) - (u/display-not-valid :library-typography-path value)))} + (st/emit! (dwl/update-typography typo file-id)))))} {:name "fontId" :get #(-> % u/proxy->library-typography :font-id) :set (fn [self value] - (if (and (some? value) (string? value)) + (cond + (not (string? value)) + (u/display-not-valid :library-typography-font-id value) + + :else (let [typo (-> (u/proxy->library-typography self) (assoc :font-id value))] - (st/emit! (dwl/update-typography typo file-id))) - (u/display-not-valid :library-typography-font-id value)))} + (st/emit! (dwl/update-typography typo file-id)))))} {:name "fontFamily" :get #(-> % u/proxy->library-typography :font-family) :set (fn [self value] - (if (and (some? value) (string? value)) + (cond + (not (string? value)) + (u/display-not-valid :library-typography-font-family value) + + :else (let [typo (-> (u/proxy->library-typography self) (assoc :font-family value))] - (st/emit! (dwl/update-typography typo file-id))) - (u/display-not-valid :library-typography-font-family value)))} + (st/emit! (dwl/update-typography typo file-id)))))} {:name "fontVariantId" :get #(-> % u/proxy->library-typography :font-variant-id) :set (fn [self value] - (if (and (some? value) (string? value)) + (cond + (not (string? value)) + (u/display-not-valid :library-typography-font-variant-id value) + + :else (let [typo (-> (u/proxy->library-typography self) (assoc :font-variant-id value))] - (st/emit! (dwl/update-typography typo file-id))) - (u/display-not-valid :library-typography-font-variant-id value)))} + (st/emit! (dwl/update-typography typo file-id)))))} {:name "fontSize" :get #(-> % u/proxy->library-typography :font-size) :set (fn [self value] - (if (and (some? value) (string? value)) + (cond + (not (string? value)) + (u/display-not-valid :library-typography-font-size value) + + :else (let [typo (-> (u/proxy->library-typography self) (assoc :font-size value))] - (st/emit! (dwl/update-typography typo file-id))) - (u/display-not-valid :library-typography-font-size value)))} + (st/emit! (dwl/update-typography typo file-id)))))} {:name "fontWeight" :get #(-> % u/proxy->library-typography :font-weight) :set (fn [self value] - (if (and (some? value) (string? value)) + (cond + (not (string? value)) + (u/display-not-valid :library-typography-font-weight value) + + :else (let [typo (-> (u/proxy->library-typography self) (assoc :font-weight value))] - (st/emit! (dwl/update-typography typo file-id))) - (u/display-not-valid :library-typography-font-weight value)))} + (st/emit! (dwl/update-typography typo file-id)))))} {:name "fontStyle" :get #(-> % u/proxy->library-typography :font-style) :set (fn [self value] - (if (and (some? value) (string? value)) + (cond + (not (string? value)) + (u/display-not-valid :library-typography-font-style value) + + :else (let [typo (-> (u/proxy->library-typography self) (assoc :font-style value))] - (st/emit! (dwl/update-typography typo file-id))) - (u/display-not-valid :library-typography-font-style value)))} + (st/emit! (dwl/update-typography typo file-id)))))} {:name "lineHeight" :get #(-> % u/proxy->library-typography :font-height) :set (fn [self value] - (if (and (some? value) (string? value)) + (cond + (not (string? value)) + (u/display-not-valid :library-typography-font-height value) + + :else (let [typo (-> (u/proxy->library-typography self) (assoc :font-height value))] - (st/emit! (dwl/update-typography typo file-id))) - (u/display-not-valid :library-typography-font-height value)))} + (st/emit! (dwl/update-typography typo file-id)))))} {:name "letterSpacing" :get #(-> % u/proxy->library-typography :letter-spacing) :set (fn [self value] - (if (and (some? value) (string? value)) + (cond + (not (string? value)) + (u/display-not-valid :library-typography-letter-spacing value) + + :else (let [typo (-> (u/proxy->library-typography self) (assoc :letter-spacing value))] - (st/emit! (dwl/update-typography typo file-id))) - (u/display-not-valid :library-typography-letter-spacing value)))} + (st/emit! (dwl/update-typography typo file-id)))))} {:name "textTransform" :get #(-> % u/proxy->library-typography :text-transform) :set (fn [self value] - (if (and (some? value) (string? value)) + (cond + (not (string? value)) + (u/display-not-valid :library-typography-text-transform value) + + :else (let [typo (-> (u/proxy->library-typography self) (assoc :text-transform value))] - (st/emit! (dwl/update-typography typo file-id))) - (u/display-not-valid :library-typography-text-transform value)))})) + (st/emit! (dwl/update-typography typo file-id)))))})) (deftype LibraryComponentProxy [$plugin $file $id] Object @@ -455,7 +518,7 @@ [_] (let [id-ref (atom nil)] (st/emit! (dwl/instantiate-component $file $id (gpt/point 0 0) {:id-ref id-ref})) - (shapes/shape-proxy $plugin @id-ref))) + (shape/shape-proxy $plugin @id-ref))) (getPluginData [self key] @@ -547,21 +610,27 @@ :get #(-> % u/proxy->library-component :name) :set (fn [self value] - (if (and (some? value) (string? value)) + (cond + (not (string? value)) + (u/display-not-valid :library-component-name value) + + :else (let [component (u/proxy->library-component self) value (dm/str (d/nilv (:path component) "") " / " value)] - (st/emit! (dwl/rename-component id value))) - (u/display-not-valid :library-component-name value)))} + (st/emit! (dwl/rename-component id value)))))} {:name "path" :get #(-> % u/proxy->library-component :path) :set (fn [self value] - (if (and (some? value) (string? value)) + (cond + (not (string? value)) + (u/display-not-valid :library-component-path value) + + :else (let [component (u/proxy->library-component self) value (dm/str value " / " (:name component))] - (st/emit! (dwl/rename-component id value))) - (u/display-not-valid :library-component-path value)))})) + (st/emit! (dwl/rename-component id value)))))})) (deftype Library [$plugin $id] Object diff --git a/frontend/src/app/plugins/page.cljs b/frontend/src/app/plugins/page.cljs index f63cfdd9d..91da5bb0c 100644 --- a/frontend/src/app/plugins/page.cljs +++ b/frontend/src/app/plugins/page.cljs @@ -21,8 +21,13 @@ Object (getShapeById [_ shape-id] - (let [shape-id (uuid/uuid shape-id)] - (shape/shape-proxy $plugin $file $id shape-id))) + (cond + (not (string? shape-id)) + (u/display-not-valid :getShapeById shape-id) + + :else + (let [shape-id (uuid/uuid shape-id)] + (shape/shape-proxy $plugin $file $id shape-id)))) (getRoot [_] @@ -125,9 +130,12 @@ :get #(-> % u/proxy->page :name) :set (fn [_ value] - (if (string? value) - (st/emit! (dw/rename-page id value)) - (u/display-not-valid :page-name value)))} + (cond + (not (string? value)) + (u/display-not-valid :page-name value) + + :else + (st/emit! (dw/rename-page id value))))} {:name "root" :enumerable false @@ -138,6 +146,9 @@ :get #(or (-> % u/proxy->page :options :background) cc/canvas) :set (fn [_ value] - (if (and (some? value) (string? value) (cc/valid-hex-color? value)) - (st/emit! (dw/change-canvas-color id {:color value})) - (u/display-not-valid :page-background-color value)))})) + (cond + (or (not (string? value)) (not (cc/valid-hex-color? value))) + (u/display-not-valid :page-background-color value) + + :else + (st/emit! (dw/change-canvas-color id {:color value}))))})) diff --git a/frontend/src/app/plugins/public_utils.cljs b/frontend/src/app/plugins/public_utils.cljs index 6ebe31151..4ea08fa57 100644 --- a/frontend/src/app/plugins/public_utils.cljs +++ b/frontend/src/app/plugins/public_utils.cljs @@ -9,11 +9,17 @@ (:require [app.common.geom.rect :as grc] [app.common.geom.shapes :as gsh] + [app.plugins.shape :as shape] [app.plugins.utils :as u])) (defn ^:export centerShapes [shapes] - (let [shapes (->> shapes (map u/proxy->shape))] - (-> (gsh/shapes->rect shapes) - (grc/rect->center) - (u/to-js)))) + (cond + (not (every? shape/shape-proxy? shapes)) + (u/display-not-valid :centerShapes shapes) + + :else + (let [shapes (->> shapes (map u/proxy->shape))] + (-> (gsh/shapes->rect shapes) + (grc/rect->center) + (u/to-js))))) diff --git a/frontend/src/app/plugins/shape.cljs b/frontend/src/app/plugins/shape.cljs index 5fcf8ce28..44cc2af73 100644 --- a/frontend/src/app/plugins/shape.cljs +++ b/frontend/src/app/plugins/shape.cljs @@ -14,12 +14,18 @@ [app.common.geom.rect :as grc] [app.common.geom.shapes :as gsh] [app.common.record :as crc] + [app.common.schema :as sm] [app.common.spec :as us] [app.common.svg.path.legacy-parser2 :as spp] [app.common.text :as txt] + [app.common.types.grid :as ctg] [app.common.types.shape :as cts] + [app.common.types.shape.blur :as ctsb] + [app.common.types.shape.export :as ctse] [app.common.types.shape.layout :as ctl] + [app.common.types.shape.path :as ctsp] [app.common.types.shape.radius :as ctsr] + [app.common.types.shape.shadow :as ctss] [app.common.uuid :as uuid] [app.main.data.workspace :as dw] [app.main.data.workspace.groups :as dwg] @@ -36,6 +42,7 @@ [app.util.text-editor :as ted] [cuerdas.core :as str])) +(def lib-typography-proxy? nil) (deftype TextRange [$plugin $file $page $id start end] Object @@ -52,7 +59,10 @@ (let [s (set values)] (if (= (count s) 1) (first s) "mixed"))) -;; TODO Validate inputs +(defn text-range? + [range] + (instance? TextRange range)) + (defn text-range [plugin-id file-id page-id id start end] (-> (TextRange. plugin-id file-id page-id id start end) @@ -77,7 +87,12 @@ :set (fn [_ value] - (st/emit! (dwt/update-text-range id start end {:font-id value})))} + (cond + (not (string? value)) + (u/display-not-valid :fontId value) + + :else + (st/emit! (dwt/update-text-range id start end {:font-id value}))))} {:name "fontFamily" :get #(let [range-data @@ -86,7 +101,12 @@ :set (fn [_ value] - (st/emit! (dwt/update-text-range id start end {:font-family value})))} + (cond + (not (string? value)) + (u/display-not-valid :fontFamily value) + + :else + (st/emit! (dwt/update-text-range id start end {:font-family value}))))} {:name "fontVariantId" :get #(let [range-data @@ -94,7 +114,12 @@ (->> range-data (map :font-variant-id) mixed-value)) :set (fn [_ value] - (st/emit! (dwt/update-text-range id start end {:font-variant-id value})))} + (cond + (not (string? value)) + (u/display-not-valid :fontVariantId value) + + :else + (st/emit! (dwt/update-text-range id start end {:font-variant-id value}))))} {:name "fontSize" :get #(let [range-data @@ -102,7 +127,12 @@ (->> range-data (map :font-size) mixed-value)) :set (fn [_ value] - (st/emit! (dwt/update-text-range id start end {:font-size value})))} + (cond + (not (string? value)) + (u/display-not-valid :fontSize value) + + :else + (st/emit! (dwt/update-text-range id start end {:font-size value}))))} {:name "fontWeight" :get #(let [range-data @@ -110,7 +140,12 @@ (->> range-data (map :font-weight) mixed-value)) :set (fn [_ value] - (st/emit! (dwt/update-text-range id start end {:font-weight value})))} + (cond + (not (string? value)) + (u/display-not-valid :fontWeight value) + + :else + (st/emit! (dwt/update-text-range id start end {:font-weight value}))))} {:name "fontStyle" :get #(let [range-data @@ -118,7 +153,12 @@ (->> range-data (map :font-style) mixed-value)) :set (fn [_ value] - (st/emit! (dwt/update-text-range id start end {:font-style value})))} + (cond + (not (string? value)) + (u/display-not-valid :fontStyle value) + + :else + (st/emit! (dwt/update-text-range id start end {:font-style value}))))} {:name "lineHeight" :get #(let [range-data @@ -126,7 +166,12 @@ (->> range-data (map :line-height) mixed-value)) :set (fn [_ value] - (st/emit! (dwt/update-text-range id start end {:line-height value})))} + (cond + (not (string? value)) + (u/display-not-valid :lineHeight value) + + :else + (st/emit! (dwt/update-text-range id start end {:line-height value}))))} {:name "letterSpacing" :get #(let [range-data @@ -134,7 +179,12 @@ (->> range-data (map :letter-spacing) mixed-value)) :set (fn [_ value] - (st/emit! (dwt/update-text-range id start end {:letter-spacing value})))} + (cond + (not (string? value)) + (u/display-not-valid :letterSpacing value) + + :else + (st/emit! (dwt/update-text-range id start end {:letter-spacing value}))))} {:name "textTransform" :get #(let [range-data @@ -142,7 +192,12 @@ (->> range-data (map :text-transform) mixed-value)) :set (fn [_ value] - (st/emit! (dwt/update-text-range id start end {:text-transform value})))} + (cond + (not (string? value)) + (u/display-not-valid :textTransform value) + + :else + (st/emit! (dwt/update-text-range id start end {:text-transform value}))))} {:name "textDecoration" :get #(let [range-data @@ -150,7 +205,12 @@ (->> range-data (map :text-decoration) mixed-value)) :set (fn [_ value] - (st/emit! (dwt/update-text-range id start end {:text-decoration value})))} + (cond + (not (string? value)) + (u/display-not-valid :textDecoration value) + + :else + (st/emit! (dwt/update-text-range id start end {:text-decoration value}))))} {:name "direction" :get #(let [range-data @@ -158,7 +218,12 @@ (->> range-data (map :direction) mixed-value)) :set (fn [_ value] - (st/emit! (dwt/update-text-range id start end {:direction value})))} + (cond + (not (string? value)) + (u/display-not-valid :direction value) + + :else + (st/emit! (dwt/update-text-range id start end {:direction value}))))} {:name "fills" :get #(let [range-data @@ -167,7 +232,12 @@ :set (fn [_ value] (let [value (mapv #(u/from-js %) value)] - (st/emit! (dwt/update-text-range id start end {:fills value}))))}))) + (cond + (not (sm/validate [:vector ::cts/fill] value)) + (u/display-not-valid :fills value) + + :else + (st/emit! (dwt/update-text-range id start end {:fills value})))))}))) (declare shape-proxy) @@ -199,8 +269,16 @@ Object (resize [_ width height] - (st/emit! (dw/update-dimensions [$id] :width width) - (dw/update-dimensions [$id] :height height))) + (cond + (or (not (us/safe-number? width)) (<= width 0)) + (u/display-not-valid :resize width) + + (or (not (us/safe-number? height)) (<= height 0)) + (u/display-not-valid :resize height) + + :else + (st/emit! (dw/update-dimensions [$id] :width width) + (dw/update-dimensions [$id] :height height)))) (rotate [self angle center] @@ -363,17 +441,32 @@ (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))))) + (cond + (not (cfh/text-shape? shape)) + (u/display-not-valid :getRange-shape "shape is not text") + + (or (not (us/safe-int? start)) (< start 0) (> start end)) + (u/display-not-valid :getRange-start start) + + (not (us/safe-int? end)) + (u/display-not-valid :getRange-end end) + + :else + (text-range $plugin $file $page $id start end)))) (applyTypography [_ typography] (let [shape (u/locate-shape $file $page $id)] - (if (cfh/text-shape? shape) + (cond + (not (lib-typography-proxy? typography)) + (u/display-not-valid :applyTypography-typography typography) + + (not (cfh/text-shape? shape)) + (u/display-not-valid :applyTypography-shape (:type shape)) + + :else (let [typography (u/proxy->library-typography typography)] - (st/emit! (dwt/apply-typography #{$id} typography $file))) - (u/display-not-valid :applyTypography (:type shape)))))) + (st/emit! (dwt/apply-typography #{$id} typography $file))))))) (crc/define-properties! ShapeProxy @@ -383,6 +476,10 @@ (defn shape-proxy? [p] (instance? ShapeProxy p)) +;; Prevent circular dependency +(do (set! flex/shape-proxy? shape-proxy?) + (set! grid/shape-proxy? shape-proxy?)) + (defn shape-proxy ([plugin-id id] (shape-proxy plugin-id (:current-file-id @st/state) (:current-page-id @st/state) id)) @@ -411,155 +508,251 @@ {: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))))} + :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)))] + (cond + (not valid?) + (u/display-not-valid :shape-name value) + + :else + (st/emit! (dwsh/update-shapes [id] #(assoc % :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)))))} + :set + (fn [self value] + (cond + (not (boolean? value)) + (u/display-not-valid :blocked value) + + :else + (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)))))} + :set + (fn [self value] + (cond + (not (boolean? value)) + (u/display-not-valid :hidden value) + + :else + (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)))))} + :set + (fn [self value] + (cond + (not (boolean? value)) + (u/display-not-valid :proportionLock value) + + :else + (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))))))} + :set + (fn [self value] + (let [id (obj/get self "$id") + value (keyword value)] + (cond + (not (contains? cts/horizontal-constraint-types value)) + (u/display-not-valid :constraintsHorizontal value) + + :else + (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))))))} + :set + (fn [self value] + (let [id (obj/get self "$id") + value (keyword value)] + (cond + (not (contains? cts/vertical-constraint-types value)) + (u/display-not-valid :constraintsVertical value) + + :else + (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))))))} + :set + (fn [self value] + (let [id (obj/get self "$id") + shape (u/proxy->shape self)] + (cond + (or (not (us/safe-int? value)) (< value 0)) + (u/display-not-valid :borderRadius value) + + (or (not (ctsr/has-radius? shape)) (ctsr/radius-4? shape)) + (st/emit! (dwsh/update-shapes [id] #(-> % + ctsr/switch-to-radius-1 + (ctsr/set-radius-1 value)))) + + :else + (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))))))} + :set + (fn [self value] + (let [id (obj/get self "$id") + shape (u/proxy->shape self)] + (cond + (not (us/safe-int? value)) + (u/display-not-valid :borderRadiusTopLeft value) + + (or (not (ctsr/has-radius? shape)) (not (ctsr/radius-4? shape))) + (st/emit! (dwsh/update-shapes [id] #(-> % + (ctsr/switch-to-radius-4) + (ctsr/set-radius-4 :r1 value)))) + + :else + (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))))))} + :set + (fn [self value] + (let [id (obj/get self "$id") + shape (u/proxy->shape self)] + (cond + (not (us/safe-int? value)) + (u/display-not-valid :borderRadiusTopRight value) + + (or (not (ctsr/has-radius? shape)) (not (ctsr/radius-4? shape))) + (st/emit! (dwsh/update-shapes [id] #(-> % + (ctsr/switch-to-radius-4) + (ctsr/set-radius-4 :r2 value)))) + + :else + (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))))))} + :set + (fn [self value] + (let [id (obj/get self "$id") + shape (u/proxy->shape self)] + (cond + (not (us/safe-int? value)) + (u/display-not-valid :borderRadiusBottomRight value) + + (or (not (ctsr/has-radius? shape)) (not (ctsr/radius-4? shape))) + (st/emit! (dwsh/update-shapes [id] #(-> % + (ctsr/switch-to-radius-4) + (ctsr/set-radius-4 :r3 value)))) + + :else + (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))))))} + :set + (fn [self value] + (let [id (obj/get self "$id") + shape (u/proxy->shape self)] + (cond + (not (us/safe-int? value)) + (u/display-not-valid :borderRadiusBottomLeft value) + + (or (not (ctsr/has-radius? shape)) (not (ctsr/radius-4? shape))) + (st/emit! (dwsh/update-shapes [id] #(-> % + (ctsr/switch-to-radius-4) + (ctsr/set-radius-4 :r4 value)))) + + :else + (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))))))} + :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))))))} + :set + (fn [self value] + (let [id (obj/get self "$id") + value (keyword value)] + (cond + (not (contains? cts/blend-modes value)) + (u/display-not-valid :blendMode value) + + :else + (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)))))} + :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)] + (cond + (not (sm/validate [:vector ::ctss/shadow] value)) + (u/display-not-valid :shadows value) + + :else + (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))))))} + :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))] + (cond + (not (sm/validate ::ctsb/blur value)) + (u/display-not-valid :blur value) + + :else + (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)))))} + :set + (fn [self value] + (let [id (obj/get self "$id") + value (mapv #(u/from-js %) value)] + (cond + (not (sm/validate [:vector ::ctse/export] value)) + (u/display-not-valid :exports value) + + :else + (st/emit! (dwsh/update-shapes [id] #(assoc % :exports value))))))} ;; Geometry properties {:name "x" @@ -567,14 +760,24 @@ :set (fn [self value] (let [id (obj/get self "$id")] - (st/emit! (dw/update-position id {:x value}))))} + (cond + (not (us/safe-number? value)) + (u/display-not-valid :x value) + + :else + (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}))))} + (cond + (not (us/safe-number? value)) + (u/display-not-valid :y value) + + :else + (st/emit! (dw/update-position id {:y value})))))} {:name "parentX" :get (fn [self] @@ -584,11 +787,16 @@ (- (: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)}))))} + (cond + (not (us/safe-number? value)) + (u/display-not-valid :parentX value) + + :else + (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] @@ -599,11 +807,16 @@ (- (: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)}))))} + (cond + (not (us/safe-number? value)) + (u/display-not-valid :parentY value) + + :else + (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] @@ -614,11 +827,16 @@ (- (: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)}))))} + (cond + (not (us/safe-number? value)) + (u/display-not-valid :frameX value) + + :else + (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] @@ -629,11 +847,16 @@ (- (: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)}))))} + (cond + (not (us/safe-number? value)) + (u/display-not-valid :frameY value) + + :else + (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)} @@ -645,49 +868,70 @@ :get #(-> % u/proxy->shape :rotation) :set (fn [self value] - (if (number? value) + (cond + (not (number? value)) + (u/display-not-valid :rotation value) + + :else (let [shape (u/proxy->shape self)] - (st/emit! (dw/increase-rotation #{(:id shape)} value))) - (u/display-not-valid :rotation value)))} + (st/emit! (dw/increase-rotation #{(:id shape)} value)))))} {:name "flipX" :get #(-> % u/proxy->shape :flip-x boolean) :set (fn [self value] - (if (boolean? value) + (cond + (not (boolean? value)) + (u/display-not-valid :flipX value) + + :else (let [id (obj/get self "$id")] - (st/emit! (dw/flip-horizontal-selected #{id}))) - (u/display-not-valid :flipX value)))} + (st/emit! (dw/flip-horizontal-selected #{id})))))} {:name "flipY" :get #(-> % u/proxy->shape :flip-y boolean) :set (fn [self value] - (if (boolean? value) + (cond + (not (boolean? value)) + (u/display-not-valid :flipY value) + + :else (let [id (obj/get self "$id")] - (st/emit! (dw/flip-vertical-selected #{id}))) - (u/display-not-valid :flipY value)))} + (st/emit! (dw/flip-vertical-selected #{id})))))} ;; 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))))))} + :set + (fn [self value] + (let [shape (u/proxy->shape self) + id (:id shape) + value (mapv #(u/from-js %) value)] + (cond + (not (sm/validate [:vector ::cts/fill] value)) + (u/display-not-valid :fills value) + + (cfh/text-shape? shape) + (st/emit! (dwt/update-attrs id {:fills value})) + + :else + (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)))))} + :set + (fn [self value] + (let [id (obj/get self "$id") + value (mapv #(u/from-js % #{:stroke-style :stroke-alignment}) value)] + (cond + (not (sm/validate [:vector ::cts/stroke] value)) + (u/display-not-valid :strokes value) + + :else + (st/emit! (dwsh/update-shapes [id] #(assoc % :strokes value))))))} {:name "layoutChild" :get @@ -742,7 +986,12 @@ :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)))))} + (cond + (not (sm/validate [:vector ::ctg/grid] value)) + (u/display-not-valid :guides value) + + :else + (st/emit! (dwsh/update-shapes [id] #(assoc % :grids value))))))} {:name "horizontalSizing" :get #(-> % u/proxy->shape :layout-item-h-sizing (d/nilv :fix) d/name) @@ -750,7 +999,11 @@ (fn [self value] (let [id (obj/get self "$id") value (keyword value)] - (when (contains? #{:fix :auto} value) + (cond + (not (contains? #{:fix :auto} value)) + (u/display-not-valid :horizontalSizing value) + + :else (st/emit! (dwsl/update-layout #{id} {:layout-item-h-sizing value})))))} {:name "verticalSizing" @@ -759,7 +1012,11 @@ (fn [self value] (let [id (obj/get self "$id") value (keyword value)] - (when (contains? #{:fix :auto} value) + (cond + (not (contains? #{:fix :auto} value)) + (u/display-not-valid :verticalSizing value) + + :else (st/emit! (dwsl/update-layout #{id} {:layout-item-v-sizing value})))))}))) (cond-> (cfh/text-shape? data) @@ -771,7 +1028,11 @@ (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) + (cond + (or (not (string? value)) (empty? value)) + (u/display-not-valid :characters value) + + (contains? (:workspace-editor-state @st/state) id) (let [shape (u/proxy->shape self) editor (-> shape @@ -779,8 +1040,10 @@ :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)))))} + (st/emit! (dwt/update-editor-state shape editor))) + + :else + (st/emit! (dwsh/update-shapes [id] #(txt/change-text % value))))))} {:name "growType" :get #(-> % u/proxy->shape :grow-type d/name) @@ -788,7 +1051,11 @@ (fn [self value] (let [id (obj/get self "$id") value (keyword value)] - (when (contains? #{:auto-width :auto-height :fixed} value) + (cond + (not (contains? #{:auto-width :auto-height :fixed} value)) + (u/display-not-valid :growType value) + + :else (st/emit! (dwsh/update-shapes [id] #(assoc % :grow-type value))))))} {:name "fontId" @@ -796,63 +1063,108 @@ :set (fn [self value] (let [id (obj/get self "$id")] - (st/emit! (dwt/update-attrs id {:font-id value}))))} + (cond + (not (string? value)) + (u/display-not-valid :fontId value) + + :else + (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}))))} + (cond + (not (string? value)) + (u/display-not-valid :fontFamily value) + + :else + (st/emit! (dwt/update-attrs id {:font-family 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}))))} + (cond + (not (string? value)) + (u/display-not-valid :fontVariantId value) + + :else + (st/emit! (dwt/update-attrs id {:font-variant-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}))))} + (cond + (not (string? value)) + (u/display-not-valid :fontSize value) + + :else + (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}))))} + (cond + (not (string? value)) + (u/display-not-valid :fontWeight value) + + :else + (st/emit! (dwt/update-attrs id {:font-weight 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}))))} + (cond + (not (string? value)) + (u/display-not-valid :fontStyle value) + + :else + (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}))))} + (cond + (not (string? value)) + (u/display-not-valid :lineHeight value) + + :else + (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}))))} + (cond + (not (string? value)) + (u/display-not-valid :letterSpacing value) + + :else + (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 + (not (string? value)) + (u/display-not-valid :textTransform value) + + :else + (st/emit! (dwt/update-attrs id {:text-transform value})))))})) (cond-> (or (cfh/path-shape? data) (cfh/bool-shape? data)) (crc/add-properties! @@ -864,7 +1176,12 @@ (->> 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))))))})))))) + (spp/simplify-commands))] + (cond + (not (sm/validate ::ctsp/content content)) + (u/display-not-valid :content value) + + :else + (let [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))))))))})))))) diff --git a/frontend/src/app/plugins/viewport.cljs b/frontend/src/app/plugins/viewport.cljs index 65e4423fb..c42696641 100644 --- a/frontend/src/app/plugins/viewport.cljs +++ b/frontend/src/app/plugins/viewport.cljs @@ -14,6 +14,7 @@ [app.main.data.workspace.viewport :as dwv] [app.main.data.workspace.zoom :as dwz] [app.main.store :as st] + [app.plugins.utils :as u] [app.util.object :as obj])) (deftype ViewportProxy [$plugin] @@ -51,7 +52,14 @@ (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)) + (cond + (not (us/safe-number? new-x)) + (u/display-not-valid :center-x new-x) + + (not (us/safe-number? new-y)) + (u/display-not-valid :center-y new-y) + + :else (let [vb (dm/get-in @st/state [:workspace-local :vbox]) old-x (+ (:x vb) (/ (:width vb) 2)) old-y (+ (:y vb) (/ (:height vb) 2)) @@ -68,7 +76,11 @@ (dm/get-in @st/state [:workspace-local :zoom])) :set (fn [_ value] - (when (us/safe-number? value) + (cond + (not (us/safe-number? value)) + (u/display-not-valid :zoom value) + + :else (let [z (dm/get-in @st/state [:workspace-local :zoom])] (st/emit! (dwz/set-zoom (/ value z))))))} diff --git a/frontend/test/frontend_tests/plugins/context_shapes_test.cljs b/frontend/test/frontend_tests/plugins/context_shapes_test.cljs new file mode 100644 index 000000000..ced9fff4b --- /dev/null +++ b/frontend/test/frontend_tests/plugins/context_shapes_test.cljs @@ -0,0 +1,260 @@ +;; 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 frontend-tests.plugins.context-shapes-test + (:require + [app.common.math :as m] + [app.common.test-helpers.files :as cthf] + [app.common.test-helpers.ids-map :as cthi] + [app.common.test-helpers.shapes :as cths] + [app.common.uuid :as uuid] + [app.main.store :as st] + [app.plugins.api :as api] + [cljs.test :as t :include-macros true] + [frontend-tests.helpers.state :as ths])) + +(t/deftest test-common-shape-properties + (let [;; ==== Setup + store + (ths/setup-store (cthf/sample-file :file1 :page-label :page1)) + + _ (set! st/state store) + + context (api/create-context "tests") + + page (. context -currentPage) + + shape (.createRectangle context) + + get-shape-path + #(vector :workspace-data :pages-index (aget page "$id") :objects (aget shape "$id") %)] + + (t/testing "Basic shape properites" + (t/testing " - name" + (set! (.-name shape) "TEST") + (t/is (= (.-name shape) "TEST")) + (t/is (= (get-in @store (get-shape-path :name)) "TEST"))) + + (t/testing " - x" + (set! (.-x shape) 10) + (t/is (= (.-x shape) 10)) + (t/is (= (get-in @store (get-shape-path :x)) 10)) + + (set! (.-x shape) "fail") + (t/is (= (.-x shape) 10)) + (t/is (= (get-in @store (get-shape-path :x)) 10))) + + (t/testing " - y" + (set! (.-y shape) 50) + (t/is (= (.-y shape) 50)) + (t/is (= (get-in @store (get-shape-path :y)) 50)) + + (set! (.-y shape) "fail") + (t/is (= (.-y shape) 50)) + (t/is (= (get-in @store (get-shape-path :y)) 50))) + + (t/testing " - resize" + (.resize shape 250 300) + (t/is (= (.-width shape) 250)) + (t/is (= (.-height shape) 300)) + (t/is (= (get-in @store (get-shape-path :width)) 250)) + (t/is (= (get-in @store (get-shape-path :height)) 300)) + + (.resize shape 0 0) + (t/is (= (.-width shape) 250)) + (t/is (= (.-height shape) 300)) + (t/is (= (get-in @store (get-shape-path :width)) 250)) + (t/is (= (get-in @store (get-shape-path :height)) 300))) + + (t/testing " - blocked" + (set! (.-blocked shape) true) + (t/is (= (.-blocked shape) true)) + (t/is (= (get-in @store (get-shape-path :blocked)) true)) + + (set! (.-blocked shape) false) + (t/is (= (.-blocked shape) false)) + (t/is (= (get-in @store (get-shape-path :blocked)) false))) + + (t/testing " - hidden" + (set! (.-hidden shape) true) + (t/is (= (.-hidden shape) true)) + (t/is (= (get-in @store (get-shape-path :hidden)) true)) + + (set! (.-hidden shape) false) + (t/is (= (.-hidden shape) false)) + (t/is (= (get-in @store (get-shape-path :hidden)) false))) + + (t/testing " - proportionLock" + (set! (.-proportionLock shape) true) + (t/is (= (.-proportionLock shape) true)) + (t/is (= (get-in @store (get-shape-path :proportion-lock)) true))) + + (t/testing " - constraintsHorizontal" + (set! (.-constraintsHorizontal shape) "fail") + (t/is (not= (.-constraintsHorizontal shape) "fail")) + (t/is (not= (get-in @store (get-shape-path :constraints-h)) "fail")) + + (set! (.-constraintsHorizontal shape) "right") + (t/is (= (.-constraintsHorizontal shape) "right")) + (t/is (= (get-in @store (get-shape-path :constraints-h)) :right))) + + (t/testing " - constraintsVertical" + (set! (.-constraintsVertical shape) "fail") + (t/is (not= (.-constraintsVertical shape) "fail")) + (t/is (not= (get-in @store (get-shape-path :constraints-v)) "fail")) + + (set! (.-constraintsVertical shape) "bottom") + (t/is (= (.-constraintsVertical shape) "bottom")) + (t/is (= (get-in @store (get-shape-path :constraints-v)) :bottom))) + + (t/testing " - borderRadius" + (set! (.-borderRadius shape) 10) + (t/is (= (.-borderRadius shape) 10)) + (t/is (= (get-in @store (get-shape-path :rx)) 10)) + + (set! (.-borderRadiusTopLeft shape) 20) + (t/is (= (.-borderRadiusTopLeft shape) 20)) + (t/is (= (get-in @store (get-shape-path :rx)) nil)) + (t/is (= (get-in @store (get-shape-path :r1)) 20)) + (t/is (= (get-in @store (get-shape-path :r2)) 10)) + (t/is (= (get-in @store (get-shape-path :r3)) 10)) + (t/is (= (get-in @store (get-shape-path :r4)) 10)) + + (set! (.-borderRadiusTopRight shape) 30) + (set! (.-borderRadiusBottomRight shape) 40) + (set! (.-borderRadiusBottomLeft shape) 50) + (t/is (= (.-borderRadiusTopRight shape) 30)) + (t/is (= (.-borderRadiusBottomRight shape) 40)) + (t/is (= (.-borderRadiusBottomLeft shape) 50)) + + (t/is (= (get-in @store (get-shape-path :rx)) nil)) + (t/is (= (get-in @store (get-shape-path :r1)) 20)) + (t/is (= (get-in @store (get-shape-path :r2)) 30)) + (t/is (= (get-in @store (get-shape-path :r3)) 40)) + (t/is (= (get-in @store (get-shape-path :r4)) 50))) + + (t/testing " - opacity" + (set! (.-opacity shape) 0.5) + (t/is (= (.-opacity shape) 0.5)) + (t/is (= (get-in @store (get-shape-path :opacity)) 0.5))) + + (t/testing " - blendMode" + (set! (.-blendMode shape) "multiply") + (t/is (= (.-blendMode shape) "multiply")) + (t/is (= (get-in @store (get-shape-path :blend-mode)) :multiply)) + + (set! (.-blendMode shape) "fail") + (t/is (= (.-blendMode shape) "multiply")) + (t/is (= (get-in @store (get-shape-path :blend-mode)) :multiply))) + + (t/testing " - shadows" + (let [shadow #js {:style "drop-shadow" + :color #js {:color "#FABADA" :opacity 1}}] + (set! (.-shadows shape) #js [shadow]) + (let [shadow-id (uuid/uuid (aget (aget (aget shape "shadows") 0) "id"))] + (t/is (= (-> (. shape -shadows) (aget 0) (aget "style")) "drop-shadow")) + (t/is (= (get-in @store (get-shape-path :shadow)) [{:id shadow-id + :style :drop-shadow + :offset-x 4 + :offset-y 4 + :blur 4 + :spread 0 + :color {:color "#FABADA" :opacity 1} + :hidden false}])))) + + (let [shadow #js {:style "fail"}] + (set! (.-shadows shape) #js [shadow]) + (t/is (= (-> (. shape -shadows) (aget 0) (aget "style")) "drop-shadow")))) + + (t/testing " - blur" + (set! (.-blur shape) #js {:value 10}) + (t/is (= (-> (. shape -blur) (aget "type")) "layer-blur")) + (t/is (= (-> (. shape -blur) (aget "value")) 10)) + (t/is (= (-> (. shape -blur) (aget "hidden")) false)) + (let [id (-> (. shape -blur) (aget "id") uuid/uuid)] + (t/is (= (get-in @store (get-shape-path :blur)) {:id id :type :layer-blur :value 10 :hidden false})))) + + (t/testing " - exports" + (set! (.-exports shape) #js [#js {:type "pdf" :scale 2 :suffix "test"}]) + (t/is (= (-> (. shape -exports) (aget 0) (aget "type")) "pdf")) + (t/is (= (-> (. shape -exports) (aget 0) (aget "scale")) 2)) + (t/is (= (-> (. shape -exports) (aget 0) (aget "suffix")) "test")) + (t/is (= (get-in @store (get-shape-path :exports)) [{:type :pdf :scale 2 :suffix "test"}])) + + (set! (.-exports shape) #js [#js {:type 10 :scale 2 :suffix "test"}]) + (t/is (= (get-in @store (get-shape-path :exports)) [{:type :pdf :scale 2 :suffix "test"}]))) + + (t/testing " - flipX" + (set! (.-flipX shape) true) + (t/is (= (.-flipX shape) true)) + (t/is (= (get-in @store (get-shape-path :flip-x)) true))) + + (t/testing " - flipY" + (set! (.-flipY shape) true) + (t/is (= (.-flipY shape) true)) + (t/is (= (get-in @store (get-shape-path :flip-y)) true))) + + (t/testing " - rotation" + (set! (.-rotation shape) 45) + (t/is (= (.-rotation shape) 45)) + (t/is (= (get-in @store (get-shape-path :rotation)) 45)) + + (set! (.-rotation shape) 0) + (t/is (= (.-rotation shape) 0)) + (t/is (= (get-in @store (get-shape-path :rotation)) 0))) + + (t/testing " - fills" + (set! (.-fills shape) #js [#js {:fillColor 100}]) + (t/is (= (-> (. shape -fills) (aget 0) (aget "fillColor")) "#B1B2B5")) + (t/is (= (get-in @store (get-shape-path :fills)) [{:fill-color "#B1B2B5" :fill-opacity 1}])) + + (set! (.-fills shape) #js [#js {:fillColor "#FABADA" :fillOpacity 1}]) + (t/is (= (-> (. shape -fills) (aget 0) (aget "fillColor")) "#FABADA")) + (t/is (= (-> (. shape -fills) (aget 0) (aget "fillOpacity")) 1)) + (t/is (= (get-in @store (get-shape-path :fills)) [{:fill-color "#FABADA" :fill-opacity 1}]))) + + (t/testing " - strokes" + (set! (.-fills shape) #js [#js {:strokeColor "#FABADA" :strokeOpacity 1 :stroke-width 5}]) + (t/is (= (-> (. shape -fills) (aget 0) (aget "strokeColor")) "#FABADA")) + (t/is (= (-> (. shape -fills) (aget 0) (aget "strokeOpacity")) 1)) + (t/is (= (-> (. shape -fills) (aget 0) (aget "strokeWidth")) 5)) + (t/is (= (get-in @store (get-shape-path :fills)) [{:stroke-color "#FABADA" :stroke-opacity 1 :stroke-width 5}])))) + + (t/testing "Relative properties" + (let [frame (.createFrame context)] + (set! (.-x frame) 100) + (set! (.-y frame) 200) + (t/is (= (.-x frame) 100)) + (t/is (= (.-y frame) 200)) + (.appendChild frame shape) + + (t/testing " - frameX" + (set! (.-frameX shape) 10) + (t/is (m/close? (.-frameX shape) 10)) + (t/is (m/close? (.-x shape) 110)) + (t/is (m/close? (get-in @store (get-shape-path :x)) 110))) + + (t/testing " - frameY" + (set! (.-frameY shape) 20) + (t/is (m/close? (.-frameY shape) 20)) + (t/is (m/close? (.-y shape) 220)) + (t/is (m/close? (get-in @store (get-shape-path :y)) 220))) + + (t/testing " - parentX" + (set! (.-parentX shape) 30) + (t/is (m/close? (.-parentX shape) 30)) + (t/is (m/close? (.-x shape) 130)) + (t/is (m/close? (get-in @store (get-shape-path :x)) 130))) + + (t/testing " - parentY" + (set! (.-parentY shape) 40) + (t/is (m/close? (.-parentY shape) 40)) + (t/is (m/close? (.-y shape) 240)) + (t/is (m/close? (get-in @store (get-shape-path :y)) 240))))) + + (t/testing "Clone") + (t/testing "Remove"))) +