diff --git a/.gitignore b/.gitignore index 416950e13..7e3654e7d 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ /*.zip /.clj-kondo/.cache /_dump +/notes /backend/*.md /backend/*.sql /backend/*.txt diff --git a/backend/deps.edn b/backend/deps.edn index 047f2aaaa..0026f259a 100644 --- a/backend/deps.edn +++ b/backend/deps.edn @@ -19,6 +19,7 @@ io.lettuce/lettuce-core {:mvn/version "6.6.0.RELEASE"} java-http-clj/java-http-clj {:mvn/version "0.4.3"} + com.google.guava/guava {:mvn/version "33.4.8-jre"} funcool/yetti {:git/tag "v11.4" diff --git a/backend/src/app/binfile/cleaner.clj b/backend/src/app/binfile/cleaner.clj index 74c394e2f..9f07f73b7 100644 --- a/backend/src/app/binfile/cleaner.clj +++ b/backend/src/app/binfile/cleaner.clj @@ -16,16 +16,40 @@ ;; PRE DECODE ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defn- pre-clean-bool-content + [shape] + (if-let [content (get shape :bool-content)] + (-> shape + (assoc :content content) + (dissoc :bool-content)) + shape)) + +(defn- pre-clean-shadow-color + [shape] + (d/update-when shape :shadow + (fn [shadows] + (mapv (fn [shadow] + (update shadow :color + (fn [color] + (let [ref-id (get color :id) + ref-file (get color :file-id)] + (-> (d/without-qualified color) + (select-keys [:opacity :color :gradient :image :ref-id :ref-file]) + (cond-> ref-id + (assoc :ref-id ref-id)) + (cond-> ref-file + (assoc :ref-file ref-file))))))) + shadows)))) + (defn clean-shape-pre-decode "Applies a pre-decode phase migration to the shape" [shape] - (if (= "bool" (:type shape)) - (if-let [content (get shape :bool-content)] - (-> shape - (assoc :content content) - (dissoc :bool-content)) - shape) - shape)) + (cond-> shape + (= "bool" (:type shape)) + (pre-clean-bool-content) + + (contains? shape :shadow) + (pre-clean-shadow-color))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; POST DECODE diff --git a/backend/src/app/binfile/v3.clj b/backend/src/app/binfile/v3.clj index 4763e1230..70a9960e6 100644 --- a/backend/src/app/binfile/v3.clj +++ b/backend/src/app/binfile/v3.clj @@ -113,7 +113,7 @@ (sm/encoder ::ctc/component sm/json-transformer)) (def encode-color - (sm/encoder ::ctcl/color sm/json-transformer)) + (sm/encoder ctcl/schema:library-color sm/json-transformer)) (def encode-typography (sm/encoder ::cty/typography sm/json-transformer)) @@ -142,7 +142,7 @@ (sm/decoder ::ctc/component sm/json-transformer)) (def decode-color - (sm/decoder ::ctcl/color sm/json-transformer)) + (sm/decoder ctcl/schema:library-color sm/json-transformer)) (def decode-file (sm/decoder schema:file sm/json-transformer)) @@ -157,7 +157,7 @@ (sm/decoder ::cty/typography sm/json-transformer)) (def decode-tokens-lib - (sm/decoder ::cto/tokens-lib sm/json-transformer)) + (sm/decoder cto/schema:tokens-lib sm/json-transformer)) (def decode-plugin-data (sm/decoder ::ctpg/plugin-data sm/json-transformer)) @@ -186,7 +186,7 @@ (sm/check-fn ::ctf/media)) (def validate-color - (sm/check-fn ::ctcl/color)) + (sm/check-fn ctcl/schema:library-color)) (def validate-component (sm/check-fn ::ctc/component)) @@ -617,8 +617,7 @@ (let [object (->> (read-entry input entry) (clean-component-pre-decode) (decode-component) - (clean-component-post-decode) - (validate-component))] + (clean-component-post-decode))] (if (= id (:id object)) (assoc result id object) result))) @@ -652,8 +651,7 @@ (let [object (->> (read-entry input entry) (bfl/clean-shape-pre-decode) (decode-shape) - (bfl/clean-shape-post-decode) - (validate-shape))] + (bfl/clean-shape-post-decode))] (if (= id (:id object)) (assoc result id object) result))) @@ -699,7 +697,6 @@ components (read-file-components cfg) plugin-data (read-file-plugin-data cfg) pages (read-file-pages cfg)] - {:pages (-> pages keys vec) :pages-index (into {} pages) :colors colors @@ -756,7 +753,8 @@ (assoc :project-id project-id) (dissoc :options)) - file (bfc/process-file cfg file)] + file (bfc/process-file cfg file) + file (ctf/check-file file)] (bfm/register-pending-migrations! cfg file) (bfc/save-file! cfg file ::db/return-keys false) diff --git a/backend/src/app/media.clj b/backend/src/app/media.clj index 5df82a266..710275a54 100644 --- a/backend/src/app/media.clj +++ b/backend/src/app/media.clj @@ -55,7 +55,7 @@ (sm/check-fn schema:input)) (defn validate-media-type! - ([upload] (validate-media-type! upload cm/valid-image-types)) + ([upload] (validate-media-type! upload cm/image-types)) ([upload allowed] (when-not (contains? allowed (:mtype upload)) (ex/raise :type :validation diff --git a/backend/test/backend_tests/rpc_file_test.clj b/backend/test/backend_tests/rpc_file_test.clj index 538cc82f0..9a46efd37 100644 --- a/backend/test/backend_tests/rpc_file_test.clj +++ b/backend/test/backend_tests/rpc_file_test.clj @@ -343,8 +343,9 @@ :name "image" :frame-id uuid/zero :parent-id uuid/zero - :type :image - :metadata {:id (:id fmo1) :width 100 :height 100 :mtype "image/jpeg"}})}]) + :type :rect + :fills [{:fill-opacity 1 + :fill-image {:id (:id fmo1) :width 100 :height 100 :mtype "image/jpeg"}}]})}]) ;; Check that reference storage objects on filemediaobjects ;; are the same because of deduplication feature. @@ -462,7 +463,8 @@ fmo3 (add-file-media-object :profile-id (:id profile) :file-id (:id file)) fmo4 (add-file-media-object :profile-id (:id profile) :file-id (:id file)) fmo5 (add-file-media-object :profile-id (:id profile) :file-id (:id file)) - s-shid (uuid/random) + s1-shid (uuid/random) + s2-shid (uuid/random) t-shid (uuid/random) page-id (first (get-in file [:data :pages]))] @@ -481,19 +483,31 @@ :changes [{:type :add-obj :page-id page-id - :id s-shid + :id s1-shid :parent-id uuid/zero :frame-id uuid/zero :components-v2 true :obj (cts/setup-shape - {:id s-shid + {:id s1-shid :name "image" :frame-id uuid/zero :parent-id uuid/zero - :type :image - :metadata {:id (:id fmo1) :width 100 :height 100 :mtype "image/jpeg"} - :fills [{:opacity 1 :fill-image {:id (:id fmo2) :width 100 :height 100 :mtype "image/jpeg"}}] - :strokes [{:opacity 1 :stroke-image {:id (:id fmo3) :width 100 :height 100 :mtype "image/jpeg"}}]})} + :type :rect + :fills [{:fill-opacity 1 :fill-image {:id (:id fmo2) :width 101 :height 100 :mtype "image/jpeg"}}] + :strokes [{:stroke-opacity 1 :stroke-image {:id (:id fmo3) :width 102 :height 100 :mtype "image/jpeg"}}]})} + {:type :add-obj + :page-id page-id + :id s2-shid + :parent-id uuid/zero + :frame-id uuid/zero + :components-v2 true + :obj (cts/setup-shape + {:id s2-shid + :name "image" + :frame-id uuid/zero + :parent-id uuid/zero + :type :rect + :fills [{:fill-opacity 1 :fill-image {:id (:id fmo1) :width 103 :height 100 :mtype "image/jpeg"}}]})} {:type :add-obj :page-id page-id :id t-shid @@ -519,7 +533,8 @@ {:fills [{:fill-opacity 1 :fill-color "#000000"}] :text "bye"}]}]}]} - :strokes [{:opacity 1 :stroke-image {:id (:id fmo5) :width 100 :height 100 :mtype "image/jpeg"}}]})}]) + :strokes [{:stroke-opacity 1 :stroke-image {:id (:id fmo5) :width 100 :height 100 :mtype "image/jpeg"}}]})}]) + ;; run the file-gc task immediately without forced min-age (t/is (false? (th/run-task! :file-gc {:file-id (:id file)}))) @@ -557,10 +572,13 @@ :vern 0 :changes [{:type :del-obj :page-id (first (get-in file [:data :pages])) - :id s-shid} + :id s1-shid} {:type :del-obj :page-id (first (get-in file [:data :pages])) - :id t-shid}]) + :id t-shid} + {:type :del-obj + :page-id (first (get-in file [:data :pages])) + :id s2-shid}]) ;; Now, we have deleted the usage of pointers to the ;; file-media-objects, if we paste file-gc, they should be marked diff --git a/common/deps.edn b/common/deps.edn index 43c2129a1..502d74e84 100644 --- a/common/deps.edn +++ b/common/deps.edn @@ -2,9 +2,9 @@ {org.clojure/clojure {:mvn/version "1.12.0"} org.clojure/data.json {:mvn/version "2.5.1"} org.clojure/tools.cli {:mvn/version "1.1.230"} - org.clojure/clojurescript {:mvn/version "1.12.38"} org.clojure/test.check {:mvn/version "1.1.1"} org.clojure/data.fressian {:mvn/version "1.1.0"} + org.clojure/clojurescript {:mvn/version "1.12.42"} ;; Logging org.apache.logging.log4j/log4j-api {:mvn/version "2.24.3"} @@ -59,7 +59,7 @@ {:dev {:extra-deps {org.clojure/tools.namespace {:mvn/version "RELEASE"} - thheller/shadow-cljs {:mvn/version "3.0.5"} + thheller/shadow-cljs {:mvn/version "3.1.5"} com.clojure-goes-fast/clj-async-profiler {:mvn/version "RELEASE"} com.bhauman/rebel-readline {:mvn/version "RELEASE"} criterium/criterium {:mvn/version "RELEASE"} diff --git a/common/package.json b/common/package.json index 46338a335..7288d586f 100644 --- a/common/package.json +++ b/common/package.json @@ -16,7 +16,7 @@ "devDependencies": { "concurrently": "^9.0.1", "nodemon": "^3.1.7", - "shadow-cljs": "3.0.5", + "shadow-cljs": "3.1.5", "source-map-support": "^0.5.21", "ws": "^8.17.0" }, diff --git a/common/src/app/common/buffer.cljc b/common/src/app/common/buffer.cljc new file mode 100644 index 000000000..390f3ffbf --- /dev/null +++ b/common/src/app/common/buffer.cljc @@ -0,0 +1,165 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.common.buffer + "A collection of helpers and macros for work with byte buffers" + (:refer-clojure :exclude [clone]) + (:require + [app.common.uuid :as uuid]) + #?(:cljs + (:require-macros [app.common.buffer]) + :clj + (:import [java.nio ByteBuffer ByteOrder]))) + +(defmacro read-byte + [target offset] + (if (:ns &env) + `(.getInt8 ~target ~offset true) + (let [target (with-meta target {:tag 'java.nio.ByteBuffer})] + `(long (.get ~target ~offset))))) + +(defmacro read-bool + [target offset] + (if (:ns &env) + `(== 1 (.getInt8 ~target ~offset true)) + (let [target (with-meta target {:tag 'java.nio.ByteBuffer})] + `(== 1 (.get ~target ~offset))))) + +(defmacro read-short + [target offset] + (if (:ns &env) + `(.getInt16 ~target ~offset true) + (let [target (with-meta target {:tag 'java.nio.ByteBuffer})] + `(.getShort ~target ~offset)))) + +(defmacro read-int + [target offset] + (if (:ns &env) + `(.getInt32 ~target ~offset true) + (let [target (with-meta target {:tag 'java.nio.ByteBuffer})] + `(long (.getInt ~target ~offset))))) + +(defmacro read-float + [target offset] + (if (:ns &env) + `(.getFloat32 ~target ~offset true) + (let [target (with-meta target {:tag 'java.nio.ByteBuffer})] + `(double (.getFloat ~target ~offset))))) + +(defmacro read-uuid + [target offset] + (if (:ns &env) + `(let [a# (.getUint32 ~target (+ ~offset 0) true) + b# (.getUint32 ~target (+ ~offset 4) true) + c# (.getUint32 ~target (+ ~offset 8) true) + d# (.getUint32 ~target (+ ~offset 12) true)] + (uuid/from-unsigned-parts a# b# c# d#)) + + (let [target (with-meta target {:tag 'java.nio.ByteBuffer})] + `(try + (.order ~target ByteOrder/BIG_ENDIAN) + (let [msb# (.getLong ~target (+ ~offset 0)) + lsb# (.getLong ~target (+ ~offset 8))] + (java.util.UUID. (long msb#) (long lsb#))) + (finally + (.order ~target ByteOrder/LITTLE_ENDIAN)))))) + +(defmacro write-byte + [target offset value] + (if (:ns &env) + `(.setInt8 ~target ~offset ~value true) + (let [target (with-meta target {:tag 'java.nio.ByteBuffer})] + `(.put ~target ~offset (unchecked-byte ~value))))) + +(defmacro write-short + [target offset value] + (if (:ns &env) + `(.setInt16 ~target ~offset ~value true) + (let [target (with-meta target {:tag 'java.nio.ByteBuffer})] + `(.putShort ~target ~offset (unchecked-short ~value))))) + +(defmacro write-int + [target offset value] + (if (:ns &env) + `(.setInt32 ~target ~offset ~value true) + (let [target (with-meta target {:tag 'java.nio.ByteBuffer})] + `(.putInt ~target ~offset (unchecked-int ~value))))) + +(defmacro write-float + [target offset value] + (if (:ns &env) + `(.setFloat32 ~target ~offset ~value true) + (let [target (with-meta target {:tag 'java.nio.ByteBuffer})] + `(.putFloat ~target ~offset (unchecked-float ~value))))) + +(defmacro write-uuid + [target offset value] + (if (:ns &env) + `(let [barray# (uuid/get-u32 ~value)] + (.setUint32 ~target (+ ~offset 0) (aget barray# 0) true) + (.setUint32 ~target (+ ~offset 4) (aget barray# 1) true) + (.setUint32 ~target (+ ~offset 8) (aget barray# 2) true) + (.setUint32 ~target (+ ~offset 12) (aget barray# 3) true)) + + (let [target (with-meta target {:tag 'java.nio.ByteBuffer}) + value (with-meta value {:tag 'java.util.UUID})] + `(try + (.order ~target ByteOrder/BIG_ENDIAN) + (.putLong ~target (+ ~offset 0) (.getMostSignificantBits ~value)) + (.putLong ~target (+ ~offset 8) (.getLeastSignificantBits ~value)) + (finally + (.order ~target ByteOrder/LITTLE_ENDIAN)))))) + +(defn allocate + [size] + #?(:clj (let [buffer (ByteBuffer/allocate (int size))] + (.order buffer ByteOrder/LITTLE_ENDIAN)) + :cljs (new js/DataView (new js/ArrayBuffer size)))) + +(defn clone + [buffer] + #?(:clj + (let [src (.array ^ByteBuffer buffer) + len (alength ^bytes src) + dst (byte-array len)] + (System/arraycopy src 0 dst 0 len) + (let [buffer (ByteBuffer/wrap dst)] + (.order buffer ByteOrder/LITTLE_ENDIAN))) + :cljs + (let [buffer' (.-buffer ^js/DataView buffer) + src-view (js/Uint32Array. buffer') + dst-buff (js/ArrayBuffer. (.-byteLength buffer')) + dst-view (js/Uint32Array. dst-buff)] + (.set dst-view src-view) + (js/DataView. dst-buff)))) + +(defn equals? + [buffer-a buffer-b] + #?(:clj + (.equals ^ByteBuffer buffer-a + ^ByteBuffer buffer-b) + + :cljs + (let [buffer-a (.-buffer buffer-a) + buffer-b (.-buffer buffer-b)] + (if (= (.-byteLength buffer-a) + (.-byteLength buffer-b)) + (let [cb (js/Uint32Array. buffer-a) + ob (js/Uint32Array. buffer-b) + sz (alength cb)] + (loop [i 0] + (if (< i sz) + (if (== (aget ob i) + (aget cb i)) + (recur (inc i)) + false) + true))) + false)))) + +(defn buffer? + [o] + #?(:clj (instance? ByteBuffer o) + :cljs (instance? js/DataView o))) diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index 5e7ae75cf..bb2564133 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -33,6 +33,10 @@ (def boolean-or-nil? (some-fn nil? boolean?)) +(defn in-range? + [size i] + (and (< i size) (>= i 0))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Commonly used transducers ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/common/src/app/common/files/changes.cljc b/common/src/app/common/files/changes.cljc index db7a87460..14b75f868 100644 --- a/common/src/app/common/files/changes.cljc +++ b/common/src/app/common/files/changes.cljc @@ -16,7 +16,6 @@ [app.common.schema.desc-native :as smd] [app.common.schema.generators :as sg] [app.common.types.color :as ctc] - [app.common.types.colors-list :as ctcl] [app.common.types.component :as ctk] [app.common.types.components-list :as ctkl] [app.common.types.container :as ctn] @@ -266,7 +265,7 @@ [:id ::sm/uuid] ;; All props are optional, background can be nil because is the ;; way to remove already set background - [:background {:optional true} [:maybe ::ctc/rgb-color]] + [:background {:optional true} [:maybe ctc/schema:hex-color]] [:name {:optional true} :string]]] [:set-plugin-data schema:set-plugin-data-change] @@ -292,12 +291,12 @@ [:add-color [:map {:title "AddColorChange"} [:type [:= :add-color]] - [:color ::ctc/color]]] + [:color ctc/schema:library-color]]] [:mod-color [:map {:title "ModColorChange"} [:type [:= :mod-color]] - [:color ::ctc/color]]] + [:color ctc/schema:library-color]]] [:del-color [:map {:title "DelColorChange"} @@ -926,15 +925,15 @@ (defmethod process-change :add-color [data {:keys [color]}] - (ctcl/add-color data color)) + (ctc/add-color data color)) (defmethod process-change :mod-color [data {:keys [color]}] - (ctcl/set-color data color)) + (ctc/set-color data color)) (defmethod process-change :del-color [data {:keys [id]}] - (ctcl/delete-color data id)) + (ctc/delete-color data id)) ;; DEPRECATED: remove before 2.3 (defmethod process-change :add-recent-color diff --git a/common/src/app/common/files/migrations.cljc b/common/src/app/common/files/migrations.cljc index a912b46c0..34c96cc64 100644 --- a/common/src/app/common/files/migrations.cljc +++ b/common/src/app/common/files/migrations.cljc @@ -22,10 +22,11 @@ [app.common.schema :as sm] [app.common.svg :as csvg] [app.common.text :as txt] - [app.common.types.color :as ctc] + [app.common.types.color :as types.color] [app.common.types.component :as ctk] [app.common.types.container :as ctn] [app.common.types.file :as ctf] + [app.common.types.fill :as types.fill] [app.common.types.path :as path] [app.common.types.path.segment :as path.segment] [app.common.types.shape :as cts] @@ -826,7 +827,7 @@ (d/update-when :components d/update-vals update-container)))) (def ^:private valid-fill? - (sm/lazy-validator ::cts/fill)) + (sm/lazy-validator types.fill/schema:fill)) (defmethod migrate-data "legacy-43" [data _] @@ -1004,14 +1005,11 @@ (update :pages-index d/update-vals update-container) (d/update-when :components d/update-vals update-container)))) -(def ^:private valid-color? - (sm/lazy-validator ::ctc/color)) - (defmethod migrate-data "legacy-51" [data _] (let [update-colors (fn [colors] - (into {} (filter #(-> % val valid-color?) colors)))] + (into {} (filter #(-> % val types.color/valid-color?) colors)))] (update data :colors update-colors))) (defmethod migrate-data "legacy-52" @@ -1328,7 +1326,6 @@ (update :pages-index d/update-vals update-container) (d/update-when :components d/update-vals update-container)))) - (defmethod migrate-data "0004-add-partial-text-touched-flags" [data _] (letfn [(update-object [page object] @@ -1354,6 +1351,33 @@ (update data :pages-index d/update-vals update-page))) +(defmethod migrate-data "0004-clean-shadow-and-colors" + [data _] + (letfn [(clean-shadow [shadow] + (update shadow :color (fn [color] + (let [ref-id (get color :id) + ref-file (get color :file-id)] + (-> (d/without-qualified color) + (select-keys [:opacity :color :gradient :image :ref-id :ref-file]) + (cond-> ref-id + (assoc :ref-id ref-id)) + (cond-> ref-file + (assoc :ref-file ref-file))))))) + + (update-object [object] + (d/update-when object :shadow #(mapv clean-shadow %))) + + (update-container [container] + (d/update-when container :objects d/update-vals update-object)) + + (clean-library-color [color] + (dissoc color :file-id))] + + (-> data + (update :pages-index d/update-vals update-container) + (d/update-when :components d/update-vals update-container) + (d/update-when :colors d/update-vals clean-library-color)))) + (defmethod migrate-data "0005-deprecate-image-type" [data _] (letfn [(update-object [object] @@ -1369,7 +1393,7 @@ object)) (update-container [container] - (d/update-when container :objects update-vals update-object))] + (d/update-when container :objects d/update-vals update-object))] (-> data (update :pages-index d/update-vals update-container) @@ -1378,17 +1402,12 @@ (defmethod migrate-data "0006-fix-old-texts-fills" [data _] (letfn [(fix-fills [node] - (let [fills (cond - (or (some? (:fill-color node)) - (some? (:fill-opacity node)) - (some? (:fill-color-gradient node))) + (let [fills (if (and (not (seq (:fills node))) + (or (some? (:fill-color node)) + (some? (:fill-opacity node)) + (some? (:fill-color-gradient node)))) [(d/without-nils (select-keys node [:fill-color :fill-opacity :fill-color-gradient :fill-color-ref-id :fill-color-ref-file]))] - - (nil? (:fills node)) - [{:fill-color "#000000" :fill-opacity 1}] - - :else (:fills node))] (-> node (assoc :fills fills) @@ -1407,6 +1426,77 @@ (update :pages-index d/update-vals update-container) (d/update-when :components d/update-vals update-container)))) +(def ^:private valid-stroke? + (sm/lazy-validator cts/schema:stroke)) + +(defmethod migrate-data "0007-clear-invalid-strokes-and-fills" + [data _] + (letfn [(clear-color-image [image] + (select-keys image types.color/image-attrs)) + + (clear-color-gradient [gradient] + (select-keys gradient types.color/gradient-attrs)) + + (clear-stroke [stroke] + (-> stroke + (select-keys cts/stroke-attrs) + (d/update-when :stroke-color-gradient clear-color-gradient) + (d/update-when :stroke-image clear-color-image))) + + (fix-strokes [strokes] + (->> (map clear-stroke strokes) + (filterv valid-stroke?))) + + ;; Fixes shapes with nested :fills in the :fills attribute + ;; introduced in a migration `0006-fix-old-texts-fills` when + ;; txt/transform-nodes with identity pred was broken + (remove-nested-fills [[fill :as fills]] + (if (and (= 1 (count fills)) + (contains? fill :fills)) + (:fills fill) + fills)) + + (clear-fill [fill] + (-> fill + (select-keys types.fill/fill-attrs) + (d/update-when :fill-image clear-color-image) + (d/update-when :fill-color-gradient clear-color-gradient))) + + (fix-fills [fills] + (->> fills + (remove-nested-fills) + (map clear-fill) + (filterv valid-fill?))) + + (fix-object [object] + (-> object + (d/update-when :strokes fix-strokes) + (d/update-when :fills fix-fills))) + + (update-shape [object] + (-> object + (fix-object) + ;; The text shape also can has strokes and fils on the + ;; text fragments so we need to fix them there + (cond-> (cfh/text-shape? object) + (update :content (partial txt/transform-nodes identity fix-object))))) + + (update-container [container] + (d/update-when container :objects d/update-vals update-shape))] + + (-> data + (update :pages-index d/update-vals update-container) + (d/update-when :components d/update-vals update-container)))) + +(defmethod migrate-data "0008-fix-library-colors-opacity" + [data _] + (letfn [(update-color [color] + (if (and (contains? color :opacity) + (nil? (get color :opacity))) + (assoc color :opacity 1) + color))] + (d/update-when data :colors d/update-vals update-color))) + (def available-migrations (into (d/ordered-set) ["legacy-2" @@ -1467,5 +1557,8 @@ "0003-fix-root-shape" "0003-convert-path-content" "0004-add-partial-text-touched-flags" + "0004-clean-shadow-and-colors" "0005-deprecate-image-type" - "0006-fix-old-texts-fills"])) + "0006-fix-old-texts-fills" + "0007-clear-invalid-strokes-and-fills" + "0008-fix-library-colors-opacity"])) diff --git a/common/src/app/common/media.cljc b/common/src/app/common/media.cljc index b6f2311fb..4cdf8488c 100644 --- a/common/src/app/common/media.cljc +++ b/common/src/app/common/media.cljc @@ -9,11 +9,18 @@ (:require [cuerdas.core :as str])) -;; We have added ".ttf" as string to solve a problem with chrome input selector -(def valid-font-types #{"font/ttf" ".ttf" "font/woff", "application/font-woff" "woff" "font/otf" ".otf" "font/opentype"}) -(def valid-image-types #{"image/jpeg", "image/png", "image/webp", "image/gif", "image/svg+xml"}) -(def str-image-types (str/join "," valid-image-types)) -(def str-font-types (str/join "," valid-font-types)) +(def font-types + #{"font/ttf" + "font/woff" + "font/otf" + "font/opentype"}) + +(def image-types + #{"image/jpeg" + "image/png" + "image/webp" + "image/gif" + "image/svg+xml"}) (defn format->extension [format] diff --git a/common/src/app/common/schema.cljc b/common/src/app/common/schema.cljc index a9bd5443d..0c91aa6de 100644 --- a/common/src/app/common/schema.cljc +++ b/common/src/app/common/schema.cljc @@ -5,7 +5,7 @@ ;; Copyright (c) KALEIDOS INC (ns app.common.schema - (:refer-clojure :exclude [deref merge parse-uuid parse-long parse-double parse-boolean type]) + (:refer-clojure :exclude [deref merge parse-uuid parse-long parse-double parse-boolean type keys]) #?(:cljs (:require-macros [app.common.schema :refer [ignoring]])) (:require [app.common.data :as d] @@ -118,6 +118,21 @@ [& transformers] (apply mt/transformer transformers)) +(defn entries + "Get map entires of a map schema" + [schema] + (m/entries schema default-options)) + +(def ^:private xf:map-key + (map key)) + +(defn keys + "Given a map schema, return all keys as set" + [schema] + (->> (entries schema) + (into #{} xf:map-key))) + + ;; (defn key-transformer ;; [& {:as opts}] ;; (mt/key-transformer opts)) @@ -702,7 +717,10 @@ (fn [v] (and (pred v) (>= max v))) - pred)] + pred) + + gen (or (get props :gen/gen) + (sg/small-int :max max :min min))] {:pred pred :type-properties @@ -710,7 +728,7 @@ :description "int" :error/message "expected to be int/long" :error/code "errors.invalid-integer" - :gen/gen (sg/small-int :max max :min min) + :gen/gen gen :decode/string parse-long :decode/json parse-long ::oapi/type "integer" @@ -768,10 +786,11 @@ (>= max v))) pred) - gen (sg/one-of - (sg/small-int :max max :min min) - (->> (sg/small-double :max max :min min) - (sg/fmap #(mth/precision % 2))))] + gen (or (get props :gen/gen) + (sg/one-of + (sg/small-int :max max :min min) + (->> (sg/small-double :max max :min min) + (sg/fmap #(mth/precision % 2)))))] {:pred pred :type-properties @@ -786,7 +805,9 @@ (register! ::safe-int [::int {:max max-safe-int :min min-safe-int}]) (register! ::safe-double [::double {:max max-safe-int :min min-safe-int}]) -(register! ::safe-number [::number {:max max-safe-int :min min-safe-int}]) +(register! ::safe-number [::number {:gen/gen (sg/small-double) + :max max-safe-int + :min min-safe-int}]) (defn parse-boolean [v] diff --git a/common/src/app/common/schema/generators.cljc b/common/src/app/common/schema/generators.cljc index 2f90ab226..72b03f673 100644 --- a/common/src/app/common/schema/generators.cljc +++ b/common/src/app/common/schema/generators.cljc @@ -8,6 +8,7 @@ (:refer-clojure :exclude [set subseq uuid filter map let boolean vector keyword int double]) #?(:cljs (:require-macros [app.common.schema.generators])) (:require + [app.common.math :as mth] [app.common.schema.registry :as sr] [app.common.uri :as u] [app.common.uuid :as uuid] @@ -40,7 +41,8 @@ (defn small-double [& {:keys [min max] :or {min -100 max 100}}] - (tg/double* {:min min, :max max, :infinite? false, :NaN? false})) + (->> (tg/double* {:min min, :max max, :infinite? false, :NaN? false}) + (tg/fmap #(mth/precision % 2)))) (defn small-int [& {:keys [min max] :or {min -100 max 100}}] diff --git a/common/src/app/common/test_helpers/shapes.cljc b/common/src/app/common/test_helpers/shapes.cljc index 903f51f38..96746542f 100644 --- a/common/src/app/common/test_helpers/shapes.cljc +++ b/common/src/app/common/test_helpers/shapes.cljc @@ -7,11 +7,11 @@ (ns app.common.test-helpers.shapes (:require [app.common.colors :as clr] + [app.common.data :as d] [app.common.files.helpers :as cfh] [app.common.test-helpers.files :as thf] [app.common.test-helpers.ids-map :as thi] [app.common.types.color :as ctc] - [app.common.types.colors-list :as ctcl] [app.common.types.container :as ctn] [app.common.types.pages-list :as ctpl] [app.common.types.shape :as cts] @@ -81,9 +81,16 @@ (:id page) #(ctst/set-shape % (ctn/set-shape-attr shape attr val))))))) -(defn sample-color - [label & {:keys [] :as params}] - (ctc/make-color (assoc params :id (thi/new-id! label)))) +(defn sample-library-color + [label & {:keys [name path color opacity gradient image]}] + (-> {:id (thi/new-id! label) + :name (or name color "Black") + :path path + :color (or color "#000000") + :opacity (or opacity 1) + :gradient gradient + :image image} + (d/without-nils))) (defn sample-fill-color [& {:keys [fill-color fill-opacity] :as params}] @@ -101,8 +108,8 @@ (defn add-sample-library-color [file label & {:keys [] :as params}] - (let [color (sample-color label params)] - (update file :data ctcl/add-color color))) + (let [color (sample-library-color label params)] + (update file :data ctc/add-color color))) (defn sample-typography [label & {:keys [] :as params}] diff --git a/common/src/app/common/text.cljc b/common/src/app/common/text.cljc index ad86914ac..5c6bcf313 100644 --- a/common/src/app/common/text.cljc +++ b/common/src/app/common/text.cljc @@ -121,30 +121,6 @@ {:name "Source Sans Pro Regular"} (select-keys default-text-attrs typography-fields))) -(defn transform-nodes - ([transform root] - (transform-nodes identity transform root)) - ([pred transform root] - (walk/postwalk - (fn [item] - (if (and (map? item) (pred item)) - (transform item) - item)) - root))) - -(defn xform-nodes - "The same as transform but instead of receiving a funcion, receives - a transducer." - [xf root] - (let [rf (fn [_ v] v)] - (walk/postwalk - (fn [item] - (let [rf (xf rf)] - (if (map? item) - (d/nilv (rf nil item) item) - item))) - root))) - (defn node-seq ([root] (node-seq identity root)) ([match? root] @@ -165,6 +141,34 @@ [node] (= "root" (:type node))) +(defn is-node? + [node] + (or (is-text-node? node) (is-paragraph-node? node) (is-root-node? node))) + +(defn transform-nodes + ([transform root] + (transform-nodes identity transform root)) + ([pred transform root] + (walk/postwalk + (fn [item] + (if (and (is-node? item) (pred item)) + (transform item) + item)) + root))) + +(defn xform-nodes + "The same as transform but instead of receiving a funcion, receives + a transducer." + [xf root] + (let [rf (fn [_ v] v)] + (walk/postwalk + (fn [item] + (let [rf (xf rf)] + (if (is-node? item) + (d/nilv (rf nil item) item) + item))) + root))) + (defn generate-shape-name [text] (subs text 0 (min 280 (count text)))) diff --git a/common/src/app/common/transit.cljc b/common/src/app/common/transit.cljc index 6d315e613..9226ecbc6 100644 --- a/common/src/app/common/transit.cljc +++ b/common/src/app/common/transit.cljc @@ -208,9 +208,13 @@ ([data] (encode-str data nil)) ([data opts] #?(:cljs - (let [t (:type opts :json) - w (t/writer t {:handlers @write-handler-map})] - (t/write w data)) + (let [type (:type opts :json) + params {:handlers @write-handler-map} + params (if (:with-meta opts) + (assoc params :transform t/write-meta) + params) + writer (t/writer type params)] + (t/write writer data)) :clj (->> (encode data opts) (bytes->str))))) @@ -219,9 +223,10 @@ ([data] (decode-str data nil)) ([data opts] #?(:cljs - (let [t (:type opts :json) - r (t/reader t {:handlers @read-handler-map})] - (t/read r data)) + (let [type (:type opts :json) + params {:handlers @read-handler-map} + reader (t/reader type params)] + (t/read reader data)) :clj (-> (str->bytes data) (decode opts))))) diff --git a/common/src/app/common/types/color.cljc b/common/src/app/common/types/color.cljc index 73f9ac962..9173af243 100644 --- a/common/src/app/common/types/color.cljc +++ b/common/src/app/common/types/color.cljc @@ -8,23 +8,36 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] + [app.common.media :as cm] [app.common.schema :as sm] [app.common.schema.generators :as sg] [app.common.schema.openapi :as-alias oapi] [app.common.text :as txt] + [app.common.time :as dt] [app.common.types.plugins :as ctpg] - [app.common.uuid :as uuid] + [clojure.set :as set] [cuerdas.core :as str])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; SCHEMAS & TYPES ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(def rgb-color-re +(def valid-color-attrs + "A set used for proper check if color should contain only one of the + attrs listed in this set." + #{:image :gradient :color}) + +(defn has-valid-color-attrs? + "Check if color has correct color attrs" + [color] + (let [attrs (set (keys color)) + result (set/intersection attrs valid-color-attrs)] + (= 1 (count result)))) + +(def ^:private hex-color-rx #"^#(?:[0-9a-fA-F]{3}){1,2}$") -(defn- generate-rgb-color - [] +(def ^:private hex-color-generator (sg/fmap (fn [_] #?(:clj (format "#%06x" (rand-int 16rFFFFFF)) :cljs @@ -37,38 +50,48 @@ (.. b (toString 16) (padStart 2 "0")))))) sg/int)) -(defn rgb-color-string? +(defn hex-color-string? [o] - (and (string? o) (some? (re-matches rgb-color-re o)))) + (and (string? o) (some? (re-matches hex-color-rx o)))) -(def schema:rgb-color +(def schema:hex-color (sm/register! - {:type ::rgb-color - :pred rgb-color-string? + {:type ::hex-color + :pred hex-color-string? :type-properties - {:title "rgb-color" - :description "RGB Color String" - :error/message "expected a valid RGB color" - :error/code "errors.invalid-rgb-color" - :gen/gen (generate-rgb-color) + {:title "hex-color" + :description "HEX Color String" + :error/message "expected a valid HEX color" + :error/code "errors.invalid-hex-color" + :gen/gen hex-color-generator ::oapi/type "integer" ::oapi/format "int64"}})) +(def schema:plain-color + [:map [:color schema:hex-color]]) + (def schema:image - [:map {:title "ImageColor"} - [:width ::sm/int] - [:height ::sm/int] - [:mtype ::sm/text] + [:map {:title "ImageColor" :closed true} + [:width [::sm/int {:min 0 :gen/gen sg/int}]] + [:height [::sm/int {:min 0 :gen/gen sg/int}]] + [:mtype {:gen/gen (sg/elements cm/image-types)} ::sm/text] [:id ::sm/uuid] [:name {:optional true} ::sm/text] [:keep-aspect-ratio {:optional true} :boolean]]) +(def image-attrs + "A set of attrs that corresponds to image data type" + (sm/keys schema:image)) + +(def schema:image-color + [:map [:image schema:image]]) + (def gradient-types #{:linear :radial}) (def schema:gradient - [:map {:title "Gradient"} - [:type [::sm/one-of #{:linear :radial}]] + [:map {:title "Gradient" :closed true} + [:type [::sm/one-of gradient-types]] [:start-x ::sm/safe-number] [:start-y ::sm/safe-number] [:end-x ::sm/safe-number] @@ -77,86 +100,83 @@ [:stops [:vector {:min 1 :gen/max 2} [:map {:title "GradientStop"} - [:color schema:rgb-color] - [:opacity {:optional true} [:maybe ::sm/safe-number]] - [:offset ::sm/safe-number]]]]]) + [:color schema:hex-color] + [:opacity {:optional true} [::sm/number {:min 0 :max 1}]] + [:offset [::sm/number {:min 0 :max 1}]]]]]]) + +(def gradient-attrs + "A set of attrs that corresponds to gradient data type" + (sm/keys schema:gradient)) + +(def schema:gradient-color + [:map [:gradient schema:gradient]]) (def schema:color-attrs - [:map {:title "ColorAttrs"} - [:id {:optional true} ::sm/uuid] - [:name {:optional true} :string] - [:path {:optional true} [:maybe :string]] - [:value {:optional true} [:maybe :string]] - [:color {:optional true} [:maybe schema:rgb-color]] - [:opacity {:optional true} [:maybe ::sm/safe-number]] - [:modified-at {:optional true} ::sm/inst] + [:map {:title "ColorAttrs" :closed true} + [:opacity {:optional true} [::sm/number {:min 0 :max 1}]] [:ref-id {:optional true} ::sm/uuid] - [:ref-file {:optional true} ::sm/uuid] - [:gradient {:optional true} [:maybe schema:gradient]] - [:image {:optional true} [:maybe schema:image]] - [:plugin-data {:optional true} ::ctpg/plugin-data]]) + [:ref-file {:optional true} ::sm/uuid]]) (def schema:color - [:and schema:color-attrs - [::sm/contains-any {:strict true} [:color :gradient :image]]]) - -(def schema:recent-color [:and - [:map {:title "RecentColor"} - [:opacity {:optional true} [:maybe ::sm/safe-number]] - [:color {:optional true} [:maybe schema:rgb-color]] - [:gradient {:optional true} [:maybe schema:gradient]] - [:image {:optional true} [:maybe schema:image]]] - [::sm/contains-any {:strict true} [:color :gradient :image]]]) + [:merge {:title "Color"} + schema:color-attrs + (sm/optional-keys schema:plain-color) + (sm/optional-keys schema:gradient-color) + (sm/optional-keys schema:image-color)] + [:fn has-valid-color-attrs?]]) + +(def schema:library-color-attrs + [:map {:title "ColorAttrs" :closed true} + [:id ::sm/uuid] + [:name ::sm/text] + [:path {:optional true} :string] + [:opacity {:optional true} [::sm/number {:min 0 :max 1}]] + [:modified-at {:optional true} ::sm/inst] + [:plugin-data {:optional true} ::ctpg/plugin-data]]) -;; Same as color but with :id prop required (def schema:library-color + "Used for in-transit representation of a color (per example when user + clicks a color on assets sidebar, the color should be properly identified with + the file-id where it belongs)" [:and - (sm/required-keys schema:color-attrs [:id]) - [::sm/contains-any {:strict true} [:color :gradient :image]]]) - -;; FIXME: revisit if we really need this all registers -(sm/register! ::color schema:color) -(sm/register! ::gradient schema:gradient) -(sm/register! ::image-color schema:image) -(sm/register! ::recent-color schema:recent-color) -(sm/register! ::color-attrs schema:color-attrs) + [:merge + schema:library-color-attrs + (sm/optional-keys schema:plain-color) + (sm/optional-keys schema:gradient-color) + (sm/optional-keys schema:image-color)] + [:fn has-valid-color-attrs?]]) (def valid-color? (sm/lazy-validator schema:color)) +(def valid-library-color? + (sm/lazy-validator schema:library-color)) + (def check-color (sm/check-fn schema:color :hint "expected valid color")) (def check-library-color - (sm/check-fn schema:library-color :hint "expected valid library color")) - -(def check-recent-color - (sm/check-fn schema:recent-color :hint "expected valid recent color")) + (sm/check-fn schema:library-color :hint "expected valid color")) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; HELPERS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; --- factory - -(defn make-color - [{:keys [id name path value color opacity ref-id ref-file gradient image]}] - (-> {:id (or id (uuid/next)) - :name (or name color "Black") - :path path - :value value - :color (or color "#000000") - :opacity (or opacity 1) - :ref-id ref-id - :ref-file ref-file - :gradient gradient - :image image} - (d/without-nils))) +(defn library-color->color + "Converts a library color data structure to a plain color data structure" + [lcolor file-id] + (-> lcolor + (select-keys [:image :gradient :color :opacity]) + (assoc :ref-id (get lcolor :id)) + (assoc :ref-file file-id) + (vary-meta assoc + :path (get lcolor :path) + :name (get lcolor :name)))) ;; --- fill -(defn fill->shape-color +(defn fill->color [fill] (d/without-nils {:color (:fill-color fill) @@ -178,126 +198,130 @@ (defn attach-fill-color [shape position ref-id ref-file] - (-> shape - (assoc-in [:fills position :fill-color-ref-id] ref-id) - (assoc-in [:fills position :fill-color-ref-file] ref-file))) + (d/update-in-when shape [:fills position] + (fn [fill] + (-> fill + (assoc :fill-color-ref-file ref-file) + (assoc :fill-color-ref-id ref-id))))) (defn detach-fill-color [shape position] - (-> shape - (d/dissoc-in [:fills position :fill-color-ref-id]) - (d/dissoc-in [:fills position :fill-color-ref-file]))) + (d/update-in-when shape [:fills position] dissoc :fill-color-ref-id :fill-color-ref-file)) ;; stroke -(defn stroke->shape-color +(defn stroke->color [stroke] - (d/without-nils {:color (:stroke-color stroke) - :opacity (:stroke-opacity stroke) - :gradient (:stroke-color-gradient stroke) - :image (:stroke-image stroke) - :ref-id (:stroke-color-ref-id stroke) - :ref-file (:stroke-color-ref-file stroke)})) + (d/without-nils + {:color (str/lower (:stroke-color stroke)) + :opacity (:stroke-opacity stroke) + :gradient (:stroke-color-gradient stroke) + :image (:stroke-image stroke) + :ref-id (:stroke-color-ref-id stroke) + :ref-file (:stroke-color-ref-file stroke)})) (defn set-stroke-color [shape position color opacity gradient image] - (update-in shape [:strokes position] - (fn [stroke] - (d/without-nils (assoc stroke - :stroke-color color - :stroke-opacity opacity - :stroke-color-gradient gradient - :stroke-image image))))) + (d/update-in-when shape [:strokes position] + (fn [stroke] + (-> stroke + (assoc :stroke-color color) + (assoc :stroke-opacity opacity) + (assoc :stroke-color-gradient gradient) + (assoc :stroke-image image) + (d/without-nils))))) (defn attach-stroke-color [shape position ref-id ref-file] - (-> shape - (assoc-in [:strokes position :stroke-color-ref-id] ref-id) - (assoc-in [:strokes position :stroke-color-ref-file] ref-file))) + (d/update-in-when shape [:strokes position] + (fn [stroke] + (-> stroke + (assoc :stroke-color-ref-id ref-id) + (assoc :stroke-color-ref-file ref-file))))) (defn detach-stroke-color [shape position] - (-> shape - (d/dissoc-in [:strokes position :stroke-color-ref-id]) - (d/dissoc-in [:strokes position :stroke-color-ref-file]))) + (d/update-in-when shape [:strokes position] dissoc :stroke-color-ref-id :stroke-color-ref-file)) ;; shadow -(defn shadow->shape-color +(defn shadow->color [shadow] - (d/without-nils {:color (-> shadow :color :color) - :opacity (-> shadow :color :opacity) - :gradient (-> shadow :color :gradient) - :ref-id (-> shadow :color :id) - :ref-file (-> shadow :color :file-id)})) + (:color shadow)) (defn set-shadow-color [shape position color opacity gradient] - (update-in shape [:shadow position :color] - (fn [shadow-color] - (d/without-nils (assoc shadow-color - :color color - :opacity opacity - :gradient gradient))))) + (d/update-in-when shape [:shadow position :color] + (fn [shadow-color] + (-> shadow-color + (assoc :color color) + (assoc :opacity opacity) + (assoc :gradient gradient) + (d/without-nils))))) (defn attach-shadow-color [shape position ref-id ref-file] - (-> shape - (assoc-in [:shadow position :color :id] ref-id) - (assoc-in [:shadow position :color :file-id] ref-file))) + (d/update-in-when shape [:shadow position :color] + (fn [color] + (-> color + (assoc :ref-id ref-id) + (assoc :ref-file ref-file))))) (defn detach-shadow-color [shape position] - (-> shape - (d/dissoc-in [:shadow position :color :id]) - (d/dissoc-in [:shadow position :color :file-id]))) + (d/update-in-when shape [:shadow position :color] dissoc :ref-id :ref-file)) ;; grid -(defn grid->shape-color +;: FIXME: revisit colors...... WTF +(defn grid->color [grid] - (d/without-nils {:color (-> grid :params :color :color) - :opacity (-> grid :params :color :opacity) - :gradient (-> grid :params :color :gradient) - :ref-id (-> grid :params :color :id) - :ref-file (-> grid :params :color :file-id)})) + (let [color (-> grid :params :color)] + (d/without-nils + {:color (-> color :color) + :opacity (-> color :opacity) + :gradient (-> color :gradient) + :ref-id (-> color :id) + :ref-file (-> color :file-id)}))) (defn set-grid-color [shape position color opacity gradient] - (update-in shape [:grids position :params :color] - (fn [grid-color] - (d/without-nils (assoc grid-color - :color color - :opacity opacity - :gradient gradient))))) + (d/update-in-when shape [:grids position :params :color] + (fn [grid-color] + (-> grid-color + (assoc :color color) + (assoc :opacity opacity) + (assoc :gradient gradient) + (d/without-nils))))) + (defn attach-grid-color [shape position ref-id ref-file] - (-> shape - (assoc-in [:grids position :params :color :id] ref-id) - (assoc-in [:grids position :params :color :file-id] ref-file))) + (d/update-in-when shape [:grids position :params :color] + (fn [color] + (-> color + (assoc :ref-id ref-id) + (assoc :ref-file ref-file))))) (defn detach-grid-color [shape position] - (-> shape - (d/dissoc-in [:grids position :params :color :id]) - (d/dissoc-in [:grids position :params :color :file-id]))) + (d/update-in-when shape [:grids position :params :color] dissoc :ref-id :ref-file)) ;; --- Helpers for all colors in a shape (defn get-text-node-colors "Get all colors used by a node of a text shape" [node] - (concat (map fill->shape-color (:fills node)) - (map stroke->shape-color (:strokes node)))) + (concat (map fill->color (:fills node)) + (map stroke->color (:strokes node)))) (defn get-all-colors "Get all colors used by a shape, in any section." [shape] - (concat (map fill->shape-color (:fills shape)) - (map stroke->shape-color (:strokes shape)) - (map shadow->shape-color (:shadow shape)) + (concat (map fill->color (:fills shape)) + (map stroke->color (:strokes shape)) + (map shadow->color (:shadow shape)) (when (= (:type shape) :frame) - (map grid->shape-color (:grids shape))) + (map grid->color (:grids shape))) (when (= (:type shape) :text) (reduce (fn [colors node] (concat colors (get-text-node-colors node))) @@ -326,7 +350,7 @@ (let [process-fill (fn [shape [position fill]] (process-fn shape position - (fill->shape-color fill) + (fill->color fill) set-fill-color attach-fill-color detach-fill-color)) @@ -334,7 +358,7 @@ process-stroke (fn [shape [position stroke]] (process-fn shape position - (stroke->shape-color stroke) + (stroke->color stroke) set-stroke-color attach-stroke-color detach-stroke-color)) @@ -342,7 +366,7 @@ process-shadow (fn [shape [position shadow]] (process-fn shape position - (shadow->shape-color shadow) + (shadow->color shadow) set-shadow-color attach-shadow-color detach-shadow-color)) @@ -350,7 +374,7 @@ process-grid (fn [shape [position grid]] (process-fn shape position - (grid->shape-color grid) + (grid->color grid) set-grid-color attach-grid-color detach-grid-color)) @@ -407,114 +431,76 @@ (process-shape-colors shape sync-color))) -(defn- eq-recent-color? - [c1 c2] - (or (= c1 c2) - (and (some? (:color c1)) - (some? (:color c2)) - (= (:color c1) (:color c2))))) - -(defn add-recent-color - "Moves the color to the top of the list and then truncates up to 15" - [state file-id color] - (update state file-id (fn [colors] - (let [colors (d/removev (partial eq-recent-color? color) colors) - colors (conj colors color)] - (cond-> colors - (> (count colors) 15) - (subvec 1)))))) - -(defn stroke->color-att - [stroke file-id shared-libs] - (let [color-file-id (:stroke-color-ref-file stroke) - color-id (:stroke-color-ref-id stroke) - shared-libs-colors (dm/get-in shared-libs [color-file-id :data :colors]) - is-shared? (contains? shared-libs-colors color-id) - has-color? (or (not (nil? (:stroke-color stroke))) (not (nil? (:stroke-color-gradient stroke)))) - attrs (if (or is-shared? (= color-file-id file-id)) - (d/without-nils {:color (str/lower (:stroke-color stroke)) - :opacity (:stroke-opacity stroke) - :id color-id - :file-id color-file-id - :gradient (:stroke-color-gradient stroke)}) - (d/without-nils {:color (str/lower (:stroke-color stroke)) - :opacity (:stroke-opacity stroke) - :gradient (:stroke-color-gradient stroke)}))] +(defn- stroke->color-att + [stroke file-id libraries] + (let [ref-file (:stroke-color-ref-file stroke) + ref-id (:stroke-color-ref-id stroke) + shared-colors (dm/get-in libraries [ref-file :data :colors]) + is-shared? (contains? shared-colors ref-id) + has-color? (or (:stroke-color stroke) + (:stroke-color-gradient stroke)) + attrs (cond-> (stroke->color stroke) + (not (or is-shared? (= ref-file file-id))) + (dissoc :ref-id :ref-file))] (when has-color? {:attrs attrs :prop :stroke :shape-id (:shape-id stroke) :index (:index stroke)}))) -(defn shadow->color-att - [shadow file-id shared-libs] - (let [color-file-id (dm/get-in shadow [:color :file-id]) - color-id (dm/get-in shadow [:color :id]) - shared-libs-colors (dm/get-in shared-libs [color-file-id :data :colors]) - is-shared? (contains? shared-libs-colors color-id) - attrs (if (or is-shared? (= color-file-id file-id)) - (d/without-nils {:color (str/lower (dm/get-in shadow [:color :color])) - :opacity (dm/get-in shadow [:color :opacity]) - :id color-id - :file-id (dm/get-in shadow [:color :file-id]) - :gradient (dm/get-in shadow [:color :gradient])}) - (d/without-nils {:color (str/lower (dm/get-in shadow [:color :color])) - :opacity (dm/get-in shadow [:color :opacity]) - :gradient (dm/get-in shadow [:color :gradient])}))] - - +(defn- shadow->color-att + [shadow file-id libraries] + (let [color (get shadow :color) + ref-file (get color :ref-file) + ref-id (get color :ref-id) + shared-colors (dm/get-in libraries [ref-file :data :colors]) + is-shared? (contains? shared-colors ref-id) + attrs (cond-> (shadow->color shadow) + (not (or is-shared? (= ref-file file-id))) + (dissoc :ref-file :ref-id))] {:attrs attrs :prop :shadow :shape-id (:shape-id shadow) :index (:index shadow)})) -(defn text->color-att - [fill file-id shared-libs] - (let [color-file-id (:fill-color-ref-file fill) - color-id (:fill-color-ref-id fill) - shared-libs-colors (dm/get-in shared-libs [color-file-id :data :colors]) - is-shared? (contains? shared-libs-colors color-id) - attrs (if (or is-shared? (= color-file-id file-id)) - (d/without-nils {:color (str/lower (:fill-color fill)) - :opacity (:fill-opacity fill) - :id color-id - :file-id color-file-id - :gradient (:fill-color-gradient fill)}) - (d/without-nils {:color (str/lower (:fill-color fill)) - :opacity (:fill-opacity fill) - :gradient (:fill-color-gradient fill)}))] +(defn- text->color-att + [fill file-id libraries] + (let [ref-file (:fill-color-ref-file fill) + ref-id (:fill-color-ref-id fill) + shared-colors (dm/get-in libraries [ref-file :data :colors]) + is-shared? (contains? shared-colors ref-id) + attrs (cond-> (fill->color fill) + (not (or is-shared? (= ref-file file-id))) + (dissoc :ref-file :ref-id))] + {:attrs attrs :prop :content :shape-id (:shape-id fill) :index (:index fill)})) -(defn treat-node +(defn- treat-node [node shape-id] (map-indexed #(assoc %2 :shape-id shape-id :index %1) node)) -(defn extract-text-colors - [text file-id shared-libs] - (let [content (txt/node-seq txt/is-text-node? (:content text)) - content-filtered (map :fills content) - indexed (mapcat #(treat-node % (:id text)) content-filtered)] - (map #(text->color-att % file-id shared-libs) indexed))) +(defn- extract-text-colors + [text file-id libraries] + (->> (txt/node-seq txt/is-text-node? (:content text)) + (map :fills) + (mapcat #(treat-node % (:id text))) + (map #(text->color-att % file-id libraries)))) + +(defn- fill->color-att + [fill file-id libraries] + (let [ref-file (:fill-color-ref-file fill) + ref-id (:fill-color-ref-id fill) + shared-colors (dm/get-in libraries [ref-file :data :colors]) + is-shared? (contains? shared-colors ref-id) + has-color? (or (:fill-color fill) + (:fill-color-gradient fill)) + attrs (cond-> (fill->color fill) + (not (or is-shared? (= ref-file file-id))) + (dissoc :ref-file :ref-id))] -(defn fill->color-att - [fill file-id shared-libs] - (let [color-file-id (:fill-color-ref-file fill) - color-id (:fill-color-ref-id fill) - shared-libs-colors (dm/get-in shared-libs [color-file-id :data :colors]) - is-shared? (contains? shared-libs-colors color-id) - has-color? (or (not (nil? (:fill-color fill))) (not (nil? (:fill-color-gradient fill)))) - attrs (if (or is-shared? (= color-file-id file-id)) - (d/without-nils {:color (str/lower (:fill-color fill)) - :opacity (:fill-opacity fill) - :id color-id - :file-id color-file-id - :gradient (:fill-color-gradient fill)}) - (d/without-nils {:color (str/lower (:fill-color fill)) - :opacity (:fill-opacity fill) - :gradient (:fill-color-gradient fill)}))] (when has-color? {:attrs attrs :prop :fill @@ -522,21 +508,67 @@ :index (:index fill)}))) (defn extract-all-colors - [shapes file-id shared-libs] + [shapes file-id libraries] (reduce - (fn [list shape] + (fn [result shape] (let [fill-obj (map-indexed #(assoc %2 :shape-id (:id shape) :index %1) (:fills shape)) stroke-obj (map-indexed #(assoc %2 :shape-id (:id shape) :index %1) (:strokes shape)) shadow-obj (map-indexed #(assoc %2 :shape-id (:id shape) :index %1) (:shadow shape))] (if (= :text (:type shape)) - (-> list - (into (map #(stroke->color-att % file-id shared-libs)) stroke-obj) - (into (map #(shadow->color-att % file-id shared-libs)) shadow-obj) - (into (extract-text-colors shape file-id shared-libs))) + (-> result + (into (map #(stroke->color-att % file-id libraries)) stroke-obj) + (into (map #(shadow->color-att % file-id libraries)) shadow-obj) + (into (extract-text-colors shape file-id libraries))) - (-> list - (into (map #(fill->color-att % file-id shared-libs)) fill-obj) - (into (map #(stroke->color-att % file-id shared-libs)) stroke-obj) - (into (map #(shadow->color-att % file-id shared-libs)) shadow-obj))))) + (-> result + (into (map #(fill->color-att % file-id libraries)) fill-obj) + (into (map #(stroke->color-att % file-id libraries)) stroke-obj) + (into (map #(shadow->color-att % file-id libraries)) shadow-obj))))) [] shapes)) + +(defn colors-seq + [file-data] + (vals (:colors file-data))) + +(defn- touch + [color] + (assoc color :modified-at (dt/now))) + +(defn add-color + [file-data color] + (update file-data :colors assoc (:id color) (touch color))) + +(defn get-color + [file-data color-id] + (get-in file-data [:colors color-id])) + +(defn get-ref-color + [library-data color] + (when (= (:ref-file color) (:id library-data)) + (get-color library-data (:ref-id color)))) + +(defn set-color + [file-data color] + (d/assoc-in-when file-data [:colors (:id color)] (touch color))) + +(defn update-color + [file-data color-id f & args] + (d/update-in-when file-data [:colors color-id] #(-> (apply f % args) + (touch)))) + +(defn delete-color + [file-data color-id] + (update file-data :colors dissoc color-id)) + +(defn used-colors-changed-since + "Find all usages of any color in the library by the given shape, of colors + that have ben modified after the date." + [shape library since-date] + (->> (get-all-colors shape) + (keep #(get-ref-color (:data library) %)) + (remove #(< (:modified-at %) since-date)) ;; Note that :modified-at may be nil + (map (fn [color] {:shape-id (:id shape) + :asset-id (:id color) + :asset-type :color})))) + diff --git a/common/src/app/common/types/colors_list.cljc b/common/src/app/common/types/colors_list.cljc deleted file mode 100644 index 1134a4d16..000000000 --- a/common/src/app/common/types/colors_list.cljc +++ /dev/null @@ -1,56 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) KALEIDOS INC - -(ns app.common.types.colors-list - (:require - [app.common.data :as d] - [app.common.time :as dt] - [app.common.types.color :as ctc])) - -(defn colors-seq - [file-data] - (vals (:colors file-data))) - -(defn- touch - [color] - (assoc color :modified-at (dt/now))) - -(defn add-color - [file-data color] - (update file-data :colors assoc (:id color) (touch color))) - -(defn get-color - [file-data color-id] - (get-in file-data [:colors color-id])) - -(defn get-ref-color - [library-data color] - (when (= (:ref-file color) (:id library-data)) - (get-color library-data (:ref-id color)))) - -(defn set-color - [file-data color] - (d/assoc-in-when file-data [:colors (:id color)] (touch color))) - -(defn update-color - [file-data color-id f & args] - (d/update-in-when file-data [:colors color-id] #(-> (apply f % args) - (touch)))) - -(defn delete-color - [file-data color-id] - (update file-data :colors dissoc color-id)) - -(defn used-colors-changed-since - "Find all usages of any color in the library by the given shape, of colors - that have ben modified after the date." - [shape library since-date] - (->> (ctc/get-all-colors shape) - (keep #(get-ref-color (:data library) %)) - (remove #(< (:modified-at %) since-date)) ;; Note that :modified-at may be nil - (map (fn [color] {:shape-id (:id shape) - :asset-id (:id color) - :asset-type :color})))) diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index c6765bbd8..f4ecbd4ac 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -18,7 +18,6 @@ [app.common.schema :as sm] [app.common.text :as ct] [app.common.types.color :as ctc] - [app.common.types.colors-list :as ctcl] [app.common.types.component :as ctk] [app.common.types.components-list :as ctkl] [app.common.types.container :as ctn] @@ -59,7 +58,7 @@ [:is-local {:optional true} :boolean]]) (def schema:colors - [:map-of {:gen/max 5} ::sm/uuid ::ctc/color]) + [:map-of {:gen/max 5} ::sm/uuid ctc/schema:library-color]) (def schema:components [:map-of {:gen/max 5} ::sm/uuid ::ctn/container]) @@ -488,7 +487,7 @@ [file-data library-data asset-type] (let [assets-seq (case asset-type :component (ctkl/components-seq library-data) - :color (ctcl/colors-seq library-data) + :color (ctc/colors-seq library-data) :typography (ctyl/typographies-seq library-data)) find-usages-in-container @@ -527,7 +526,7 @@ (letfn [(used-assets-shape [shape] (concat (ctkl/used-components-changed-since shape library since-date) - (ctcl/used-colors-changed-since shape library since-date) + (ctc/used-colors-changed-since shape library since-date) (ctyl/used-typographies-changed-since shape library since-date))) (used-assets-container [container] @@ -663,7 +662,7 @@ % shapes)))] (as-> file-data $ - (ctcl/add-color $ color) + (ctc/add-color $ color) (reduce remap-shapes $ usages))))] (reduce absorb-color @@ -1046,4 +1045,4 @@ (defn set-base-font-size [file-data base-font-size] - (assoc-in file-data [:options :base-font-size] base-font-size)) \ No newline at end of file + (assoc-in file-data [:options :base-font-size] base-font-size)) diff --git a/common/src/app/common/types/fill.cljc b/common/src/app/common/types/fill.cljc new file mode 100644 index 000000000..827b7e24f --- /dev/null +++ b/common/src/app/common/types/fill.cljc @@ -0,0 +1,68 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.common.types.fill + (:require + [app.common.schema :as sm] + [app.common.types.color :as types.color] + [app.common.types.fill.impl :as impl] + [clojure.set :as set])) + +(def ^:const MAX-GRADIENT-STOPS impl/MAX-GRADIENT-STOPS) +(def ^:const MAX-FILLS impl/MAX-FILLS) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; SCHEMAS +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(def schema:fill-attrs + [:map {:title "FillAttrs" :closed true} + [:fill-color-ref-file {:optional true} ::sm/uuid] + [:fill-color-ref-id {:optional true} ::sm/uuid] + [:fill-opacity {:optional true} [::sm/number {:min 0 :max 1}]] + [:fill-color {:optional true} types.color/schema:hex-color] + [:fill-color-gradient {:optional true} types.color/schema:gradient] + [:fill-image {:optional true} types.color/schema:image]]) + +(def fill-attrs + "A set of attrs that corresponds to fill data type" + (sm/keys schema:fill-attrs)) + +(def valid-fill-attrs + "A set used for proper check if color should contain only one of the + attrs listed in this set." + #{:fill-image :fill-color :fill-color-gradient}) + +(defn has-valid-fill-attrs? + "Check if color has correct color attrs" + [color] + (let [attrs (set (keys color)) + result (set/intersection attrs valid-fill-attrs)] + (= 1 (count result)))) + +(def schema:fill + [:and schema:fill-attrs + [:fn has-valid-fill-attrs?]]) + +(def check-fill + (sm/check-fn schema:fill)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; HELPERS +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; CONSTRUCTORS +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn from-plain + [o] + (assert (every? check-fill o) "expected valid fills vector") + (impl/from-plain o)) + +(defn fills? + [o] + (impl/fills? o)) diff --git a/common/src/app/common/types/fill/impl.cljc b/common/src/app/common/types/fill/impl.cljc new file mode 100644 index 000000000..5094e4b29 --- /dev/null +++ b/common/src/app/common/types/fill/impl.cljc @@ -0,0 +1,404 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.common.types.fill.impl + (:require + #?(:clj [clojure.data.json :as json]) + #?(:cljs [app.common.weak-map :as weak-map]) + [app.common.buffer :as buf] + [app.common.data :as d] + [app.common.data.macros :as dm] + [app.common.math :as mth] + [app.common.transit :as t])) + +;; FIXME: Get these from the wasm module, and tweak the values +;; (we'd probably want 12 stops at most) +(def ^:const MAX-GRADIENT-STOPS 16) +(def ^:const MAX-FILLS 8) + +(def ^:const GRADIENT-STOP-SIZE 8) +(def ^:const GRADIENT-BYTE-SIZE 156) +(def ^:const SOLID-BYTE-SIZE 4) +(def ^:const IMAGE-BYTE-SIZE 28) +(def ^:const METADATA-BYTE-SIZE 36) +(def ^:const FILL-BYTE-SIZE + (+ 4 (mth/max GRADIENT-BYTE-SIZE + IMAGE-BYTE-SIZE + SOLID-BYTE-SIZE))) + +(def ^:private xf:take-stops + (take MAX-GRADIENT-STOPS)) + +(def ^:private xf:take-fills + (take MAX-FILLS)) + +(defn- hex->rgb + "Encode an hex string as rgb (int32)" + [hex] + (let [hex (subs hex 1)] + #?(:clj (Integer/parseInt hex 16) + :cljs (js/parseInt hex 16)))) + +(defn- rgb->rgba + "Use the first 2 bytes of in32 for encode the alpha channel" + [n alpha] + (let [result (mth/floor (* alpha 0xff)) + result (unchecked-int result) + result (bit-shift-left result 24) + result (bit-or result n)] + result)) + +(defn- get-color-hex + [n] + (let [n (bit-and n 0x00ffffff) + n #?(:clj n :cljs (.toString n 16))] + (dm/str "#" #?(:clj (String/format "%06x" (into-array Object [n])) + :cljs (.padStart n 6 "0"))))) + +(defn- get-color-alpha + [rgb] + (let [n (bit-and rgb 0xff000000) + n (unsigned-bit-shift-right n 24)] + (mth/precision (/ (float n) 0xff) 2))) + +(defn- write-solid-fill + [offset buffer color alpha] + (buf/write-byte buffer (+ offset 0) 0x00) + (buf/write-int buffer (+ offset 4) + (-> (hex->rgb color) + (rgb->rgba alpha))) + (+ offset FILL-BYTE-SIZE)) + +(defn- write-gradient-fill + [offset buffer gradient opacity] + (let [start-x (:start-x gradient) + start-y (:start-y gradient) + end-x (:end-x gradient) + end-y (:end-y gradient) + width (:width gradient 0) + stops (into [] xf:take-stops (:stops gradient)) + type (if (= (:type gradient) :linear) + 0x01 + 0x02)] + + (buf/write-byte buffer (+ offset 0) type) + (buf/write-float buffer (+ offset 4) start-x) + (buf/write-float buffer (+ offset 8) start-y) + (buf/write-float buffer (+ offset 12) end-x) + (buf/write-float buffer (+ offset 16) end-y) + (buf/write-float buffer (+ offset 20) opacity) + (buf/write-float buffer (+ offset 24) width) + (buf/write-byte buffer (+ offset 28) (count stops)) + + (loop [stops (seq stops) + offset' (+ offset 32)] + (if-let [stop (first stops)] + (let [color (-> (hex->rgb (:color stop)) + (rgb->rgba (:opacity stop 1)))] + ;; NOTE: we write the color as signed integer but on rust + ;; side it will be read as unsigned, on the end the binary + ;; repr of the data is the same independently on how it is + ;; interpreted + (buf/write-int buffer (+ offset' 0) color) + (buf/write-float buffer (+ offset' 4) (:offset stop)) + (recur (rest stops) + (+ offset' GRADIENT-STOP-SIZE))) + (+ offset FILL-BYTE-SIZE))))) + +(defn- write-image-fill + [offset buffer opacity image] + (let [image-id (get image :id) + image-width (get image :width) + image-height (get image :height)] + (buf/write-byte buffer (+ offset 0) 0x03) + (buf/write-uuid buffer (+ offset 4) image-id) + (buf/write-float buffer (+ offset 20) opacity) + (buf/write-int buffer (+ offset 24) image-width) + (buf/write-int buffer (+ offset 28) image-height) + (+ offset FILL-BYTE-SIZE))) + +(defn- write-metadata + [offset buffer fill] + (let [ref-id (:fill-color-ref-id fill) + ref-file (:fill-color-ref-file fill) + mtype (dm/get-in fill [:fill-image :mtype])] + + (when mtype + (let [val (case mtype + "image/jpeg" 0x01 + "image/png" 0x02 + "image/gif" 0x03 + "image/webp" 0x04 + "image/svg+xml" 0x05)] + (buf/write-short buffer (+ offset 2) val))) + + (if (and (some? ref-file) + (some? ref-id)) + (do + (buf/write-byte buffer (+ offset 0) 0x01) + (buf/write-uuid buffer (+ offset 4) ref-file) + (buf/write-uuid buffer (+ offset 20) ref-id)) + (do + (buf/write-byte buffer (+ offset 0) 0x00))))) + +(defn- read-stop + [buffer offset] + (let [rgba (buf/read-int buffer (+ offset 0)) + soff (buf/read-float buffer (+ offset 4))] + {:color (get-color-hex rgba) + :opacity (get-color-alpha rgba) + :offset (mth/precision soff 2)})) + +(defn- read-fill + "Read segment from binary buffer at specified index" + [dbuffer mbuffer index] + (let [doffset (+ 4 (* index FILL-BYTE-SIZE)) + moffset (* index METADATA-BYTE-SIZE) + type (buf/read-byte dbuffer doffset) + refs? (buf/read-bool mbuffer (+ moffset 0)) + fill (case type + 0 + (let [rgba (buf/read-int dbuffer (+ doffset 4))] + {:fill-color (get-color-hex rgba) + :fill-opacity (get-color-alpha rgba)}) + + (1 2) + (let [start-x (buf/read-float dbuffer (+ doffset 4)) + start-y (buf/read-float dbuffer (+ doffset 8)) + end-x (buf/read-float dbuffer (+ doffset 12)) + end-y (buf/read-float dbuffer (+ doffset 16)) + alpha (buf/read-float dbuffer (+ doffset 20)) + width (buf/read-float dbuffer (+ doffset 24)) + stops (buf/read-byte dbuffer (+ doffset 28)) + type (if (= type 1) + :linear + :radial) + stops (loop [index 0 + result []] + (if (< index stops) + (recur (inc index) + (conj result (read-stop dbuffer (+ doffset 32 (* GRADIENT-STOP-SIZE index))))) + result))] + + {:fill-opacity alpha + :fill-color-gradient {:start-x start-x + :start-y start-y + :end-x end-x + :end-y end-y + :width width + :stops stops + :type type}}) + + 3 + (let [id (buf/read-uuid dbuffer (+ doffset 4)) + alpha (buf/read-float dbuffer (+ doffset 20)) + width (buf/read-int dbuffer (+ doffset 24)) + height (buf/read-int dbuffer (+ doffset 28)) + mtype (buf/read-short mbuffer (+ moffset 2)) + mtype (case mtype + 0x01 "image/jpeg" + 0x02 "image/png" + 0x03 "image/gif" + 0x04 "image/webp" + 0x05 "image/svg+xml")] + {:fill-opacity alpha + :fill-image {:id id + :width width + :height height + :mtype mtype + ;; FIXME: we are not encodign the name, looks useless + :name "sample"}}))] + + (if refs? + (let [ref-file (buf/read-uuid mbuffer (+ moffset 4)) + ref-id (buf/read-uuid mbuffer (+ moffset 20))] + (-> fill + (assoc :fill-color-ref-id ref-id) + (assoc :fill-color-ref-file ref-file))) + fill))) + +(declare from-plain) + +#?(:clj + (deftype Fills [size dbuffer mbuffer ^:unsynchronized-mutable hash] + Object + (equals [_ other] + (if (instance? Fills other) + (and (buf/equals? dbuffer (.-dbuffer ^Fills other)) + (buf/equals? mbuffer (.-mbuffer ^Fills other))) + false)) + + json/JSONWriter + (-write [this writter options] + (json/-write (vec this) writter options)) + + clojure.lang.IHashEq + (hasheq [this] + (when-not hash + (set! hash (clojure.lang.Murmur3/hashOrdered (seq this)))) + hash) + + clojure.lang.Sequential + clojure.lang.Seqable + (seq [_] + (when (pos? size) + ((fn next-seq [i] + (when (< i size) + (cons (read-fill dbuffer mbuffer i) + (lazy-seq (next-seq (inc i)))))) + 0))) + + clojure.lang.IReduceInit + (reduce [_ f start] + (loop [index 0 + result start] + (if (< index size) + (let [result (f result (read-fill dbuffer mbuffer index))] + (if (reduced? result) + @result + (recur (inc index) result))) + result))) + + clojure.lang.Indexed + (nth [_ i] + (if (d/in-range? size i) + (read-fill dbuffer mbuffer i) + nil)) + + (nth [_ i default] + (if (d/in-range? size i) + (read-fill dbuffer mbuffer i) + default)) + + clojure.lang.Counted + (count [_] size)) + + :cljs + #_:clj-kondo/ignore + (deftype Fills [size dbuffer mbuffer cache ^:mutable __hash] + cljs.core/ISequential + cljs.core/IEquiv + (-equiv [this other] + (if (instance? Fills other) + (and ^boolean (buf/equals? (.-dbuffer ^Fills other) dbuffer) + ^boolean (buf/equals? (.-mbuffer ^Fills other) mbuffer)) + false)) + + cljs.core/IEncodeJS + (-clj->js [this] + (clj->js (vec this))) + + ;; cljs.core/APersistentVector + cljs.core/IAssociative + (-assoc [coll k v] + (if (number? k) + (-> (vec coll) + (assoc k v) + (from-plain)) + (throw (js/Error. "Vector's key for assoc must be a number.")))) + + (-contains-key? [coll k] + (if (integer? k) + (and (<= 0 k) (< k size)) + false)) + + cljs.core/IReduce + (-reduce [_ f] + (loop [index 1 + result (if (pos? size) + (read-fill dbuffer mbuffer 0) + nil)] + (if (< index size) + (let [result (f result (read-fill dbuffer mbuffer index))] + (if (reduced? result) + @result + (recur (inc index) result))) + result))) + + (-reduce [_ f start] + (loop [index 0 + result start] + (if (< index size) + (let [result (f result (read-fill dbuffer mbuffer index))] + (if (reduced? result) + @result + (recur (inc index) result))) + result))) + + cljs.core/IHash + (-hash [coll] + (caching-hash coll hash-ordered-coll __hash)) + + cljs.core/ICounted + (-count [_] size) + + cljs.core/IIndexed + (-nth [_ i] + (if (d/in-range? size i) + (read-fill dbuffer mbuffer i) + nil)) + + (-nth [_ i default] + (if (d/in-range? i size) + (read-fill dbuffer mbuffer i) + default)) + + cljs.core/ISeqable + (-seq [this] + (when (pos? size) + ((fn next-seq [i] + (when (< i size) + (cons (read-fill dbuffer mbuffer i) + (lazy-seq (next-seq (inc i)))))) + 0))))) + +(defn from-plain + [fills] + (let [fills (into [] xf:take-fills fills) + total (count fills) + dbuffer (buf/allocate (+ 4 (* MAX-FILLS FILL-BYTE-SIZE))) + mbuffer (buf/allocate (* total METADATA-BYTE-SIZE))] + + (buf/write-byte dbuffer 0 total) + + (loop [index 0] + (when (< index total) + (let [fill (nth fills index) + doffset (+ 4 (* index FILL-BYTE-SIZE)) + moffset (* index METADATA-BYTE-SIZE) + opacity (get fill :fill-opacity 1)] + + (if-let [color (get fill :fill-color)] + (do + (write-solid-fill doffset dbuffer color opacity) + (write-metadata moffset mbuffer fill) + (recur (inc index))) + (if-let [gradient (get fill :fill-color-gradient)] + (do + (write-gradient-fill doffset dbuffer gradient opacity) + (write-metadata moffset mbuffer fill) + (recur (inc index))) + (if-let [image (get fill :fill-image)] + (do + (write-image-fill doffset dbuffer opacity image) + (write-metadata moffset mbuffer fill) + (recur (inc index))) + (recur (inc index)))))))) + + #?(:cljs (Fills. total dbuffer mbuffer (weak-map/create) nil) + :clj (Fills. total dbuffer mbuffer nil)))) + +(defn fills? + [o] + (instance? Fills o)) + +(t/add-handlers! + {:id "penpot/fills" + :class Fills + :wfn (fn [^Fills fills] + (vec fills)) + :rfn #?(:cljs from-plain + :clj identity)}) diff --git a/common/src/app/common/types/grid.cljc b/common/src/app/common/types/grid.cljc index 45a73383f..af6338520 100644 --- a/common/src/app/common/types/grid.cljc +++ b/common/src/app/common/types/grid.cljc @@ -8,7 +8,7 @@ (:require [app.common.colors :as clr] [app.common.schema :as sm] - [app.common.types.color :as ctc])) + [app.common.types.color :refer [schema:hex-color]])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; SCHEMA @@ -16,7 +16,7 @@ (def schema:grid-color [:map {:title "PageGridColor"} - [:color ::ctc/rgb-color] + [:color schema:hex-color] [:opacity ::sm/safe-number]]) (def schema:column-params diff --git a/common/src/app/common/types/page.cljc b/common/src/app/common/types/page.cljc index 85bb02bb0..230f7460d 100644 --- a/common/src/app/common/types/page.cljc +++ b/common/src/app/common/types/page.cljc @@ -10,7 +10,7 @@ [app.common.data :as d] [app.common.geom.point :as-alias gpt] [app.common.schema :as sm] - [app.common.types.color :as-alias ctc] + [app.common.types.color :as ctc] [app.common.types.grid :as ctg] [app.common.types.plugins :as ctpg] [app.common.types.shape :as cts] @@ -57,7 +57,7 @@ [:flows {:optional true} schema:flows] [:guides {:optional true} schema:guides] [:plugin-data {:optional true} ::ctpg/plugin-data] - [:background {:optional true} ::ctc/rgb-color] + [:background {:optional true} ctc/schema:hex-color] [:comment-thread-positions {:optional true} [:map-of ::sm/uuid schema:comment-thread-position]]]) diff --git a/common/src/app/common/types/path/impl.cljc b/common/src/app/common/types/path/impl.cljc index ecafa45a7..cb973ca70 100644 --- a/common/src/app/common/types/path/impl.cljc +++ b/common/src/app/common/types/path/impl.cljc @@ -7,13 +7,14 @@ (ns app.common.types.path.impl "Contains schemas and data type implementation for PathData binary and plain formats" - #?(:cljs - (:require-macros [app.common.types.path.impl :refer [read-float read-short write-float write-short]])) (:refer-clojure :exclude [-lookup -reduce]) + #?(:cljs (:require-macros [app.common.types.path.impl])) (:require #?(:clj [app.common.fressian :as fres]) #?(:clj [clojure.data.json :as json]) #?(:cljs [app.common.weak-map :as weak-map]) + [app.common.buffer :as buf] + [app.common.data :as d] [app.common.data.macros :as dm] [app.common.schema :as sm] [app.common.schema.generators :as sg] @@ -42,93 +43,42 @@ ;; IMPL HELPERS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defmacro read-short - [target offset] - (if (:ns &env) - `(.getInt16 ~target ~offset true) - (let [target (with-meta target {:tag 'java.nio.ByteBuffer})] - `(.getShort ~target ~offset)))) - -(defmacro read-float - [target offset] - (if (:ns &env) - `(.getFloat32 ~target ~offset true) - (let [target (with-meta target {:tag 'java.nio.ByteBuffer})] - `(double (.getFloat ~target ~offset))))) - -(defmacro write-float - [target offset value] - (if (:ns &env) - `(.setFloat32 ~target ~offset ~value true) - (let [target (with-meta target {:tag 'java.nio.ByteBuffer})] - `(.putFloat ~target ~offset ~value)))) - -(defmacro write-short - [target offset value] - (if (:ns &env) - `(.setInt16 ~target ~offset ~value true) - (let [target (with-meta target {:tag 'java.nio.ByteBuffer})] - `(.putShort ~target ~offset ~value)))) - (defmacro with-cache "A helper macro that facilitates cache handling for content instance, only relevant on CLJS" [target key & expr] (if (:ns &env) - (let [cache (gensym "cache-") - target (with-meta target {:tag 'js})] - `(let [~cache (.-cache ~target) - ~'result (.get ~cache ~key)] + (let [target (with-meta target {:tag 'js})] + `(let [~'cache (.-cache ~target) + ~'result (.get ~'cache ~key)] (if ~'result (do ~'result) (let [~'result (do ~@expr)] - (.set ~cache ~key ~'result) + (.set ~'cache ~key ~'result) ~'result)))) `(do ~@expr))) -(defn- allocate - [n-segments] - #?(:clj (let [buffer (ByteBuffer/allocate (* n-segments SEGMENT-BYTE-SIZE))] - (.order buffer ByteOrder/LITTLE_ENDIAN)) - :cljs (new js/ArrayBuffer (* n-segments SEGMENT-BYTE-SIZE)))) - -(defn- clone-buffer - [buffer] - #?(:clj - (let [src (.array ^ByteBuffer buffer) - len (alength ^bytes src) - dst (byte-array len)] - (System/arraycopy src 0 dst 0 len) - (let [buffer (ByteBuffer/wrap dst)] - (.order buffer ByteOrder/LITTLE_ENDIAN))) - :cljs - (let [src-view (js/Uint32Array. buffer) - dst-buff (js/ArrayBuffer. (.-byteLength buffer)) - dst-view (js/Uint32Array. dst-buff)] - (.set dst-view src-view) - dst-buff))) - (defn- impl-transform-segment "Apply a transformation to a segment located under specified offset" [buffer offset a b c d e f] - (let [t (read-short buffer offset)] + (let [t (buf/read-short buffer offset)] (case t (1 2) - (let [x (read-float buffer (+ offset 20)) - y (read-float buffer (+ offset 24)) + (let [x (buf/read-float buffer (+ offset 20)) + y (buf/read-float buffer (+ offset 24)) x (+ (* x a) (* y c) e) y (+ (* x b) (* y d) f)] - (write-float buffer (+ offset 20) x) - (write-float buffer (+ offset 24) y)) + (buf/write-float buffer (+ offset 20) x) + (buf/write-float buffer (+ offset 24) y)) 3 - (let [c1x (read-float buffer (+ offset 4)) - c1y (read-float buffer (+ offset 8)) - c2x (read-float buffer (+ offset 12)) - c2y (read-float buffer (+ offset 16)) - x (read-float buffer (+ offset 20)) - y (read-float buffer (+ offset 24)) + (let [c1x (buf/read-float buffer (+ offset 4)) + c1y (buf/read-float buffer (+ offset 8)) + c2x (buf/read-float buffer (+ offset 12)) + c2y (buf/read-float buffer (+ offset 16)) + x (buf/read-float buffer (+ offset 20)) + y (buf/read-float buffer (+ offset 24)) c1x (+ (* c1x a) (* c1y c) e) c1y (+ (* c1x b) (* c1y d) f) @@ -137,12 +87,12 @@ x (+ (* x a) (* y c) e) y (+ (* x b) (* y d) f)] - (write-float buffer (+ offset 4) c1x) - (write-float buffer (+ offset 8) c1y) - (write-float buffer (+ offset 12) c2x) - (write-float buffer (+ offset 16) c2y) - (write-float buffer (+ offset 20) x) - (write-float buffer (+ offset 24) y)) + (buf/write-float buffer (+ offset 4) c1x) + (buf/write-float buffer (+ offset 8) c1y) + (buf/write-float buffer (+ offset 12) c2x) + (buf/write-float buffer (+ offset 16) c2y) + (buf/write-float buffer (+ offset 20) x) + (buf/write-float buffer (+ offset 24) y)) nil))) @@ -166,13 +116,13 @@ result (transient initial)] (if (< index size) (let [offset (* index SEGMENT-BYTE-SIZE) - type (read-short buffer offset) - c1x (read-float buffer (+ offset 4)) - c1y (read-float buffer (+ offset 8)) - c2x (read-float buffer (+ offset 12)) - c2y (read-float buffer (+ offset 16)) - x (read-float buffer (+ offset 20)) - y (read-float buffer (+ offset 24)) + type (buf/read-short buffer offset) + c1x (buf/read-float buffer (+ offset 4)) + c1y (buf/read-float buffer (+ offset 8)) + c2x (buf/read-float buffer (+ offset 12)) + c2y (buf/read-float buffer (+ offset 16)) + x (buf/read-float buffer (+ offset 20)) + y (buf/read-float buffer (+ offset 24)) type (case type 1 :line-to 2 :move-to @@ -191,13 +141,13 @@ result initial] (if (< index size) (let [offset (* index SEGMENT-BYTE-SIZE) - type (read-short buffer offset) - c1x (read-float buffer (+ offset 4)) - c1y (read-float buffer (+ offset 8)) - c2x (read-float buffer (+ offset 12)) - c2y (read-float buffer (+ offset 16)) - x (read-float buffer (+ offset 20)) - y (read-float buffer (+ offset 24)) + type (buf/read-short buffer offset) + c1x (buf/read-float buffer (+ offset 4)) + c1y (buf/read-float buffer (+ offset 8)) + c2x (buf/read-float buffer (+ offset 12)) + c2y (buf/read-float buffer (+ offset 16)) + x (buf/read-float buffer (+ offset 20)) + y (buf/read-float buffer (+ offset 24)) type (case type 1 :line-to 2 :move-to @@ -212,13 +162,13 @@ (defn impl-lookup [buffer index f] (let [offset (* index SEGMENT-BYTE-SIZE) - type (read-short buffer offset) - c1x (read-float buffer (+ offset 4)) - c1y (read-float buffer (+ offset 8)) - c2x (read-float buffer (+ offset 12)) - c2y (read-float buffer (+ offset 16)) - x (read-float buffer (+ offset 20)) - y (read-float buffer (+ offset 24)) + type (buf/read-short buffer offset) + c1x (buf/read-float buffer (+ offset 4)) + c1y (buf/read-float buffer (+ offset 8)) + c2x (buf/read-float buffer (+ offset 12)) + c2y (buf/read-float buffer (+ offset 16)) + x (buf/read-float buffer (+ offset 20)) + y (buf/read-float buffer (+ offset 24)) type (case type 1 :line-to 2 :move-to @@ -230,27 +180,27 @@ (defn- to-string-segment* [buffer offset type ^StringBuilder builder] (case (long type) - 1 (let [x (read-float buffer (+ offset 20)) - y (read-float buffer (+ offset 24))] + 1 (let [x (buf/read-float buffer (+ offset 20)) + y (buf/read-float buffer (+ offset 24))] (doto builder (.append "M") (.append x) (.append ",") (.append y))) - 2 (let [x (read-float buffer (+ offset 20)) - y (read-float buffer (+ offset 24))] + 2 (let [x (buf/read-float buffer (+ offset 20)) + y (buf/read-float buffer (+ offset 24))] (doto builder (.append "L") (.append x) (.append ",") (.append y))) - 3 (let [c1x (read-float buffer (+ offset 4)) - c1y (read-float buffer (+ offset 8)) - c2x (read-float buffer (+ offset 12)) - c2y (read-float buffer (+ offset 16)) - x (read-float buffer (+ offset 20)) - y (read-float buffer (+ offset 24))] + 3 (let [c1x (buf/read-float buffer (+ offset 4)) + c1y (buf/read-float buffer (+ offset 8)) + c2x (buf/read-float buffer (+ offset 12)) + c2y (buf/read-float buffer (+ offset 16)) + x (buf/read-float buffer (+ offset 20)) + y (buf/read-float buffer (+ offset 24))] (doto builder (.append "C") (.append c1x) @@ -275,7 +225,7 @@ (loop [index 0] (when (< index size) (let [offset (* index SEGMENT-BYTE-SIZE) - type (read-short buffer offset)] + type (buf/read-short buffer offset)] (to-string-segment* buffer offset type builder) (recur (inc index))))) @@ -285,26 +235,26 @@ "Read segment from binary buffer at specified index" [buffer index] (let [offset (* index SEGMENT-BYTE-SIZE) - type (read-short buffer offset)] + type (buf/read-short buffer offset)] (case (long type) - 1 (let [x (read-float buffer (+ offset 20)) - y (read-float buffer (+ offset 24))] + 1 (let [x (buf/read-float buffer (+ offset 20)) + y (buf/read-float buffer (+ offset 24))] {:command :move-to :params {:x (double x) :y (double y)}}) - 2 (let [x (read-float buffer (+ offset 20)) - y (read-float buffer (+ offset 24))] + 2 (let [x (buf/read-float buffer (+ offset 20)) + y (buf/read-float buffer (+ offset 24))] {:command :line-to :params {:x (double x) :y (double y)}}) - 3 (let [c1x (read-float buffer (+ offset 4)) - c1y (read-float buffer (+ offset 8)) - c2x (read-float buffer (+ offset 12)) - c2y (read-float buffer (+ offset 16)) - x (read-float buffer (+ offset 20)) - y (read-float buffer (+ offset 24))] + 3 (let [c1x (buf/read-float buffer (+ offset 4)) + c1y (buf/read-float buffer (+ offset 8)) + c2x (buf/read-float buffer (+ offset 12)) + c2y (buf/read-float buffer (+ offset 16)) + x (buf/read-float buffer (+ offset 20)) + y (buf/read-float buffer (+ offset 24))] {:command :curve-to :params {:x (double x) :y (double y) @@ -316,10 +266,6 @@ 4 {:command :close-path :params {}}))) -(defn- in-range? - [size i] - (and (< i size) (>= i 0))) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; TYPE: PATH-DATA ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -334,12 +280,12 @@ (equals [_ other] (if (instance? PathData other) - (.equals ^ByteBuffer buffer (.-buffer ^PathData other)) + (buf/equals? buffer (.-buffer ^PathData other)) false)) ITransformable (-transform [_ m] - (let [buffer (clone-buffer buffer)] + (let [buffer (buf/clone buffer)] (impl-transform buffer m size) (PathData. size buffer nil))) @@ -361,7 +307,7 @@ clojure.lang.IHashEq (hasheq [this] (when-not hash - (set! hash (clojure.lang.Murmur3/hashOrdered (seq this)))) + (set! hash (clojure.lang.Murmur3/hashOrdered (vec this)))) hash) clojure.lang.Sequential @@ -387,12 +333,12 @@ clojure.lang.Indexed (nth [_ i] - (if (in-range? size i) + (if (d/in-range? size i) (read-segment buffer i) nil)) (nth [_ i default] - (if (in-range? size i) + (if (d/in-range? size i) (read-segment buffer i) default)) @@ -408,10 +354,10 @@ :cljs #_:clj-kondo/ignore - (deftype PathData [size buffer dview cache ^:mutable __hash] + (deftype PathData [size buffer cache ^:mutable __hash] Object (toString [_] - (to-string dview size)) + (to-string buffer size)) IPathData (-get-byte-size [_] @@ -421,56 +367,43 @@ ;; NOTE: we still use u8 because until the heap refactor merge ;; we can't guarrantee the alignment of offset on 4 bytes (assert (instance? js/ArrayBuffer into-buffer)) - (let [size (.-byteLength buffer) - mem (js/Uint8Array. into-buffer offset size)] - (.set mem (js/Uint8Array. buffer)))) + (let [buffer' (.-buffer ^js/DataView buffer) + size (.-byteLength buffer') + mem (js/Uint8Array. into-buffer offset size)] + (.set mem (js/Uint8Array. buffer')))) ITransformable (-transform [this m] - (let [buffer (clone-buffer buffer) - dview (js/DataView. buffer)] - (impl-transform dview m size) - (PathData. size buffer dview (weak-map/create) nil))) + (let [buffer (buf/clone buffer)] + (impl-transform buffer m size) + (PathData. size buffer (weak-map/create) nil))) (-walk [_ f initial] - (impl-walk dview f initial size)) + (impl-walk buffer f initial size)) (-reduce [_ f initial] - (impl-reduce dview f initial size)) + (impl-reduce buffer f initial size)) (-lookup [_ index f] (when (and (<= 0 index) (< index size)) - (impl-lookup dview index f))) + (impl-lookup buffer index f))) cljs.core/ISequential cljs.core/IEquiv (-equiv [this other] (if (instance? PathData other) - (let [obuffer (.-buffer other)] - (if (= (.-byteLength obuffer) - (.-byteLength buffer)) - (let [cb (js/Uint32Array. buffer) - ob (js/Uint32Array. obuffer) - sz (alength cb)] - (loop [i 0] - (if (< i sz) - (if (= (aget ob i) - (aget cb i)) - (recur (inc i)) - false) - true))) - false)) + (buf/equals? buffer (.-buffer other)) false)) cljs.core/IReduce (-reduce [_ f] (loop [index 1 result (if (pos? size) - (read-segment dview 0) + (read-segment buffer 0) nil)] (if (< index size) - (let [result (f result (read-segment dview index))] + (let [result (f result (read-segment buffer index))] (if (reduced? result) @result (recur (inc index) result))) @@ -480,7 +413,7 @@ (loop [index 0 result start] (if (< index size) - (let [result (f result (read-segment dview index))] + (let [result (f result (read-segment buffer index))] (if (reduced? result) @result (recur (inc index) result))) @@ -495,13 +428,13 @@ cljs.core/IIndexed (-nth [_ i] - (if (in-range? size i) - (read-segment dview i) + (if (d/in-range? size i) + (read-segment buffer i) nil)) (-nth [_ i default] - (if (in-range? i size) - (read-segment dview i) + (if (d/in-range? i size) + (read-segment buffer i) default)) cljs.core/ISeqable @@ -509,7 +442,7 @@ (when (pos? size) ((fn next-seq [i] (when (< i size) - (cons (read-segment dview i) + (cons (read-segment buffer i) (lazy-seq (next-seq (inc i)))))) 0))) @@ -659,17 +592,15 @@ (let [size (.-byteLength buffer) count (long (/ size SEGMENT-BYTE-SIZE))] (PathData. count - buffer (js/DataView. buffer) (weak-map/create) nil)) (instance? js/DataView buffer) - (let [dview buffer - buffer (.-buffer dview) - size (.-byteLength buffer) - count (long (/ size SEGMENT-BYTE-SIZE))] - (PathData. count buffer dview (weak-map/create) nil)) + (let [buffer' (.-buffer ^js/DataView buffer) + size (.-byteLength ^js/ArrayBuffer buffer') + count (long (/ size SEGMENT-BYTE-SIZE))] + (PathData. count buffer (weak-map/create) nil)) (instance? js/Uint8Array buffer) (from-bytes (.-buffer buffer)) @@ -688,10 +619,8 @@ [segments] (assert (check-segments segments)) - (let [total (count segments) - #?@(:cljs [buffer' (allocate total) - buffer (new js/DataView buffer')] - :clj [buffer (allocate total)])] + (let [total (count segments) + buffer (buf/allocate (* total SEGMENT-BYTE-SIZE))] (loop [index 0] (when (< index total) (let [segment (nth segments index) @@ -701,18 +630,18 @@ (let [params (get segment :params) x (float (get params :x)) y (float (get params :y))] - (write-short buffer offset 1) - (write-float buffer (+ offset 20) x) - (write-float buffer (+ offset 24) y)) + (buf/write-short buffer offset 1) + (buf/write-float buffer (+ offset 20) x) + (buf/write-float buffer (+ offset 24) y)) :line-to (let [params (get segment :params) x (float (get params :x)) y (float (get params :y))] - (write-short buffer offset 2) - (write-float buffer (+ offset 20) x) - (write-float buffer (+ offset 24) y)) + (buf/write-short buffer offset 2) + (buf/write-float buffer (+ offset 20) x) + (buf/write-float buffer (+ offset 24) y)) :curve-to (let [params (get segment :params) @@ -723,16 +652,16 @@ c2x (float (get params :c2x x)) c2y (float (get params :c2y y))] - (write-short buffer offset 3) - (write-float buffer (+ offset 4) c1x) - (write-float buffer (+ offset 8) c1y) - (write-float buffer (+ offset 12) c2x) - (write-float buffer (+ offset 16) c2y) - (write-float buffer (+ offset 20) x) - (write-float buffer (+ offset 24) y)) + (buf/write-short buffer offset 3) + (buf/write-float buffer (+ offset 4) c1x) + (buf/write-float buffer (+ offset 8) c1y) + (buf/write-float buffer (+ offset 12) c2x) + (buf/write-float buffer (+ offset 16) c2y) + (buf/write-float buffer (+ offset 20) x) + (buf/write-float buffer (+ offset 24) y)) :close-path - (write-short buffer offset 4)) + (buf/write-short buffer offset 4)) (recur (inc index))))) (from-bytes buffer))) @@ -763,7 +692,7 @@ :class PathData :wfn (fn [^PathData pdata] (let [buffer (.-buffer pdata)] - #?(:cljs (js/Uint8Array. buffer) + #?(:cljs (js/Uint8Array. (.-buffer ^js/DataView buffer)) :clj (.array ^ByteBuffer buffer)))) :rfn from-bytes}) diff --git a/common/src/app/common/types/shape.cljc b/common/src/app/common/types/shape.cljc index e46b009cf..3e473df67 100644 --- a/common/src/app/common/types/shape.cljc +++ b/common/src/app/common/types/shape.cljc @@ -20,7 +20,8 @@ [app.common.schema.generators :as sg] [app.common.text :as txt] [app.common.transit :as t] - [app.common.types.color :as ctc] + [app.common.types.color :as types.color] + [app.common.types.fill :refer [schema:fill]] [app.common.types.grid :as ctg] [app.common.types.path :as path] [app.common.types.path.segment :as path.segment] @@ -119,36 +120,47 @@ (def schema:points [:vector {:gen/max 4 :gen/min 4} ::gpt/point]) -(def schema:fill - (sm/register! - ^{::sm/type ::fill} - [:map {:title "Fill"} - [:fill-color {:optional true} ::ctc/rgb-color] - [:fill-opacity {:optional true} ::sm/safe-number] - [:fill-color-gradient {:optional true} [:maybe ::ctc/gradient]] - [:fill-color-ref-file {:optional true} [:maybe ::sm/uuid]] - [:fill-color-ref-id {:optional true} [:maybe ::sm/uuid]] - [:fill-image {:optional true} ::ctc/image-color]])) +;; FIXME: the register is necessary until this is moved to a separated +;; ns because it is used on shapes.text +(def valid-stroke-attrs + "A set used for proper check if color should contain only one of the + attrs listed in this set." + #{:stroke-image :stroke-color :stroke-color-gradient}) + +(defn has-valid-stroke-attrs? + "Check if color has correct color attrs" + [color] + (let [attrs (set (keys color)) + result (set/intersection attrs valid-stroke-attrs)] + (= 1 (count result)))) + +(def schema:stroke-attrs + [:map {:title "StrokeAttrs" :closed true} + [:stroke-color-ref-file {:optional true} ::sm/uuid] + [:stroke-color-ref-id {:optional true} ::sm/uuid] + [:stroke-opacity {:optional true} ::sm/safe-number] + [:stroke-style {:optional true} + [::sm/one-of #{:solid :dotted :dashed :mixed}]] + [:stroke-width {:optional true} ::sm/safe-number] + [:stroke-alignment {:optional true} + [::sm/one-of #{:center :inner :outer}]] + [:stroke-cap-start {:optional true} + [::sm/one-of stroke-caps]] + [:stroke-cap-end {:optional true} + [::sm/one-of stroke-caps]] + [:stroke-color {:optional true} types.color/schema:hex-color] + [:stroke-color-gradient {:optional true} types.color/schema:gradient] + [:stroke-image {:optional true} types.color/schema:image]]) + +(def stroke-attrs + "A set of attrs that corresponds to stroke data type" + (sm/keys schema:stroke-attrs)) (def schema:stroke (sm/register! ^{::sm/type ::stroke} - [:map {:title "Stroke"} - [:stroke-color {:optional true} :string] - [:stroke-color-ref-file {:optional true} ::sm/uuid] - [:stroke-color-ref-id {:optional true} ::sm/uuid] - [:stroke-opacity {:optional true} ::sm/safe-number] - [:stroke-style {:optional true} - [::sm/one-of #{:solid :dotted :dashed :mixed :none :svg}]] - [:stroke-width {:optional true} ::sm/safe-number] - [:stroke-alignment {:optional true} - [::sm/one-of #{:center :inner :outer}]] - [:stroke-cap-start {:optional true} - [::sm/one-of stroke-caps]] - [:stroke-cap-end {:optional true} - [::sm/one-of stroke-caps]] - [:stroke-color-gradient {:optional true} ::ctc/gradient] - [:stroke-image {:optional true} ::ctc/image-color]])) + [:and schema:stroke-attrs + [:fn has-valid-stroke-attrs?]])) (def check-stroke (sm/check-fn schema:stroke)) @@ -761,8 +773,3 @@ (d/patch-object (select-keys props basic-extract-props)) (cond-> (cfh/text-shape? shape) (patch-text-props props)) (cond-> (cfh/frame-shape? shape) (patch-layout-props props))))) - -;; FIXME: Get these from the wasm module, and tweak the values -;; (we'd probably want 12 stops at most) -(def MAX-GRADIENT-STOPS 16) -(def MAX-FILLS 8) diff --git a/common/src/app/common/types/shape/shadow.cljc b/common/src/app/common/types/shape/shadow.cljc index c00a1ce82..ef44bc4a7 100644 --- a/common/src/app/common/types/shape/shadow.cljc +++ b/common/src/app/common/types/shape/shadow.cljc @@ -24,7 +24,7 @@ [:blur ::sm/safe-number] [:spread ::sm/safe-number] [:hidden :boolean] - [:color ::ctc/color]]) + [:color ctc/schema:color]]) (def check-shadow (sm/check-fn schema:shadow)) diff --git a/common/src/app/common/types/shape/text.cljc b/common/src/app/common/types/shape/text.cljc index 9b0e6908c..03dca9fb7 100644 --- a/common/src/app/common/types/shape/text.cljc +++ b/common/src/app/common/types/shape/text.cljc @@ -7,6 +7,7 @@ (ns app.common.types.shape.text (:require [app.common.schema :as sm] + [app.common.types.fill :refer [schema:fill]] [app.common.types.shape :as-alias shape] [app.common.types.shape.text.position-data :as-alias position-data])) @@ -34,7 +35,7 @@ [:key {:optional true} :string] [:fills {:optional true} [:maybe - [:vector {:gen/max 2} ::shape/fill]]] + [:vector {:gen/max 2} schema:fill]]] [:font-family {:optional true} :string] [:font-size {:optional true} :string] [:font-style {:optional true} :string] @@ -51,7 +52,7 @@ [:key {:optional true} :string] [:fills {:optional true} [:maybe - [:vector {:gen/max 2} ::shape/fill]]] + [:vector {:gen/max 2} schema:fill]]] [:font-family {:optional true} :string] [:font-size {:optional true} :string] [:font-style {:optional true} :string] @@ -75,7 +76,7 @@ [:y ::sm/safe-number] [:width ::sm/safe-number] [:height ::sm/safe-number] - [:fills [:vector {:gen/max 2} ::shape/fill]] + [:fills [:vector {:gen/max 2} schema:fill]] [:font-family {:optional true} :string] [:font-size {:optional true} :string] [:font-style {:optional true} :string] diff --git a/common/src/app/common/types/tokens_lib.cljc b/common/src/app/common/types/tokens_lib.cljc index 73c6c0dd1..a4d722916 100644 --- a/common/src/app/common/types/tokens_lib.cljc +++ b/common/src/app/common/types/tokens_lib.cljc @@ -796,6 +796,7 @@ Will return a value that matches this schema: (declare parse-multi-set-dtcg-json) (declare export-dtcg-json) + (deftype TokensLib [sets themes active-themes] ;; NOTE: This is only for debug purposes, pending to properly ;; implement the toString and alternative printing. diff --git a/common/test/common_tests/buffer_test.cljc b/common/test/common_tests/buffer_test.cljc new file mode 100644 index 000000000..b4f822e52 --- /dev/null +++ b/common/test/common_tests/buffer_test.cljc @@ -0,0 +1,50 @@ +;; 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 common-tests.buffer-test + (:require + [app.common.buffer :as buf] + [app.common.uuid :as uuid] + [clojure.test :as t])) + +(t/deftest allocate + (let [b (buf/allocate 1)] + (t/is (buf/buffer? b)))) + +(t/deftest rw-byte + (let [b (buf/allocate 1)] + (buf/write-byte b 0 123) + (let [res (buf/read-byte b 0)] + (t/is (= 123 res))) + + (buf/write-byte b 0 252) + (let [res (buf/read-byte b 0)] + (t/is (= -4 res))))) + +(t/deftest rw-int + (let [b (buf/allocate 4)] + (buf/write-int b 0 123) + (let [res (buf/read-int b 0)] + (t/is (= 123 res))))) + +(t/deftest rw-float + (let [b (buf/allocate 4)] + (buf/write-float b 0 123) + (let [res (buf/read-float b 0)] + (t/is (= 123.0 res))))) + +(t/deftest rw-short + (let [b (buf/allocate 2)] + (buf/write-short b 0 123) + (let [res (buf/read-short b 0)] + (t/is (= 123 res))))) + +(t/deftest rw-uuid + (let [b (buf/allocate 16) + id (uuid/next)] + (buf/write-uuid b 0 id) + (let [res (buf/read-uuid b 0)] + (t/is (= id res))))) diff --git a/common/test/common_tests/runner.cljc b/common/test/common_tests/runner.cljc index 287f6c42d..c09ae6541 100644 --- a/common/test/common_tests/runner.cljc +++ b/common/test/common_tests/runner.cljc @@ -7,6 +7,7 @@ (ns common-tests.runner (:require [clojure.test :as t] + [common-tests.buffer-test] [common-tests.colors-test] [common-tests.data-test] [common-tests.files-changes-test] @@ -38,6 +39,7 @@ [common-tests.time-test] [common-tests.types.absorb-assets-test] [common-tests.types.components-test] + [common-tests.types.fill-test] [common-tests.types.modifiers-test] [common-tests.types.path-data-test] [common-tests.types.shape-decode-encode-test] @@ -56,6 +58,7 @@ (defn -main [& args] (t/run-tests + 'common-tests.buffer-test 'common-tests.colors-test 'common-tests.data-test 'common-tests.files-changes-test @@ -89,6 +92,7 @@ 'common-tests.types.components-test 'common-tests.types.modifiers-test 'common-tests.types.path-data-test + 'common-tests.types.fill-test 'common-tests.types.shape-decode-encode-test 'common-tests.types.shape-interactions-test 'common-tests.types.tokens-lib-test diff --git a/common/test/common_tests/types/absorb_assets_test.cljc b/common/test/common_tests/types/absorb_assets_test.cljc index 382b3a681..fefb5a043 100644 --- a/common/test/common_tests/types/absorb_assets_test.cljc +++ b/common/test/common_tests/types/absorb_assets_test.cljc @@ -13,7 +13,7 @@ [app.common.test-helpers.ids-map :as thi] [app.common.test-helpers.shapes :as ths] [app.common.text :as txt] - [app.common.types.colors-list :as ctcl] + [app.common.types.color :as ctc] [app.common.types.component :as ctk] [app.common.types.components-list :as ctkl] [app.common.types.file :as ctf] @@ -80,7 +80,7 @@ _ (thf/validate-file! file') ;; Get - colors' (ctcl/colors-seq (ctf/file-data file')) + colors' (ctc/colors-seq (ctf/file-data file')) shape1' (ths/get-shape file' :shape1) fill' (first (:fills shape1'))] diff --git a/common/test/common_tests/types/fill_test.cljc b/common/test/common_tests/types/fill_test.cljc new file mode 100644 index 000000000..de1514ccc --- /dev/null +++ b/common/test/common_tests/types/fill_test.cljc @@ -0,0 +1,214 @@ +;; 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 common-tests.types.fill-test + (:require + #?(:clj [app.common.fressian :as fres]) + [app.common.data :as d] + [app.common.exceptions :as ex] + [app.common.math :as mth] + [app.common.pprint :as pp] + [app.common.pprint :as pp] + [app.common.schema.generators :as sg] + [app.common.schema.test :as smt] + [app.common.transit :as trans] + [app.common.types.fill :as types.fill] + [app.common.uuid :as uuid] + [clojure.test :as t])) + +(defn equivalent-fill? + [fill-a fill-b] + ;; (prn "-------------------") + ;; (app.common.pprint/pprint fill-a) + ;; (app.common.pprint/pprint fill-b) + + (and (= (get fill-a :fill-color-ref-file) + (get fill-b :fill-color-ref-file)) + + (= (get fill-a :fill-color-ref-id) + (get fill-b :fill-color-ref-id)) + + (or (and (contains? fill-a :fill-color) + (= (:fill-color fill-a) + (:fill-color fill-b)) + (mth/close? (:fill-opacity fill-a 1.0) + (:fill-opacity fill-b 1.0))) + (and (contains? fill-a :fill-image) + (mth/close? (:fill-opacity fill-a 1.0) + (:fill-opacity fill-b 1.0)) + (let [image-a (:fill-image fill-a) + image-b (:fill-image fill-b)] + (and (= (:id image-a) + (:id image-b)) + (= (:mtype image-a) + (:mtype image-b)) + (mth/close? (:width image-a) + (:width image-b)) + (mth/close? (:height image-a) + (:height image-b))))) + (and (contains? fill-a :fill-color-gradient) + (mth/close? (:fill-opacity fill-a 1) + (:fill-opacity fill-b 1)) + (let [gradient-a (:fill-color-gradient fill-a) + gradient-b (:fill-color-gradient fill-b)] + (and (= (count (:stops gradient-a)) + (count (:stops gradient-b))) + (= (get gradient-a :type) + (get gradient-b :type)) + (mth/close? (get gradient-a :start-x) + (get gradient-b :start-x)) + (mth/close? (get gradient-a :start-y) + (get gradient-b :start-y)) + (mth/close? (get gradient-a :end-x) + (get gradient-b :end-x)) + (mth/close? (get gradient-a :end-y) + (get gradient-b :end-y)) + (mth/close? (get gradient-a :width) + (get gradient-b :width)) + (every? true? + (map (fn [stop-a stop-b] + (and (= (get stop-a :color) + (get stop-b :color)) + (mth/close? (get stop-a :opacity 1) + (get stop-b :opacity 1)) + (mth/close? (get stop-a :offset) + (get stop-b :offset)))) + (get gradient-a :stops) + (get gradient-b :stops))))))))) + + +(def sample-fill-1 + {:fill-color "#fabada" + :fill-opacity 0.7}) + +(t/deftest build-from-plain-1 + (let [fills (types.fill/from-plain [sample-fill-1])] + (t/is (types.fill/fills? fills)) + (t/is (= 1 (count fills))) + (t/is (equivalent-fill? (first fills) sample-fill-1)))) + +(def sample-fill-2 + {:fill-color-ref-file #uuid "4fcb3db7-d281-8004-8006-3a97e2e142ad" + :fill-color-ref-id #uuid "fb19956a-c9e0-8056-8006-3a9c78f531c6" + :fill-image {:width 200, :height 100, :mtype "image/gif", + :id #uuid "b30f028d-cc2f-8035-8006-3a93bd0e137b", + :name "ovba", + :keep-aspect-ratio false}}) + +(t/deftest build-from-plain-2 + (let [fills (types.fill/from-plain [sample-fill-2])] + (t/is (types.fill/fills? fills)) + (t/is (= 1 (count fills))) + (t/is (equivalent-fill? (first fills) sample-fill-2)))) + +(def sample-fill-3 + {:fill-color-ref-id #uuid "fb19956a-c9e0-8056-8006-3a9c78f531c6" + :fill-color-ref-file #uuid "fb19956a-c9e0-8056-8006-3a9c78f531c5" + :fill-color-gradient + {:type :linear, + :start-x 0.75, + :start-y 3.0, + :end-x 1.0, + :end-y 1.5, + :width 200, + :stops [{:color "#631aa8", :offset 0.5}]}}) + +(t/deftest build-from-plain-3 + (let [fills (types.fill/from-plain [sample-fill-3])] + (t/is (types.fill/fills? fills)) + (t/is (= 1 (count fills))) + (t/is (equivalent-fill? (first fills) sample-fill-3)))) + +(def sample-fill-4 + {:fill-color-ref-file #uuid "2eef07f1-e38a-8062-8006-3aa264d5b784", + :fill-color-gradient + {:type :radial, + :start-x 0.5, + :start-y -1.0, + :end-x -0.5, + :end-y 2, + :width 0.5, + :stops [{:color "#781025", :offset 0.0} {:color "#035c3f", :offset 0.2}]}, + :fill-opacity 1.0, + :fill-color-ref-id #uuid "2eef07f1-e38a-8062-8006-3aa264d5b785"}) + +(t/deftest build-from-plain-4 + (let [fills (types.fill/from-plain [sample-fill-4])] + (t/is (types.fill/fills? fills)) + (t/is (= 1 (count fills))) + (t/is (equivalent-fill? (first fills) sample-fill-4)))) + +(def sample-fill-5 + {:fill-color-ref-file #uuid "b0f76f9a-f548-806e-8006-3aa4456131d1", + :fill-color-ref-id #uuid "b0f76f9a-f548-806e-8006-3aa445618851", + :fill-color-gradient + {:type :radial, + :start-x -0.86, + :start-y 6.0, + :end-x 0.25, + :end-y -0.5, + :width 3.8, + :stops [{:color "#bba1aa", :opacity 0.37, :offset 0.84}]}}) + +(t/deftest build-from-plain-5 + (let [fills (types.fill/from-plain [sample-fill-5])] + (t/is (types.fill/fills? fills)) + (t/is (= 1 (count fills))) + (t/is (equivalent-fill? (first fills) sample-fill-5)))) + +(def sample-fill-6 + {:fill-color-gradient + {:type :linear, + :start-x 3.5, + :start-y 0.39, + :end-x -1.87, + :end-y 1.95, + :width 2.62, + :stops [{:color "#e15610", :offset 0.4} {:color "#005a9e", :opacity 0.62, :offset 0.81}]}}) + +(t/deftest build-from-plain-6 + (let [fills (types.fill/from-plain [sample-fill-6])] + (t/is (types.fill/fills? fills)) + (t/is (= 1 (count fills))) + (t/is (equivalent-fill? (first fills) sample-fill-6)))) + +(t/deftest fills-datatype-roundtrip + (smt/check! + (smt/for [fill (->> (sg/generator types.fill/schema:fill) + (sg/fmap d/without-nils) + (sg/fmap (fn [fill] + (cond-> fill + (and (not (and (contains? fill :fill-color-ref-id) + (contains? fill :fill-color-ref-file))) + (or (contains? fill :fill-color-ref-id) + (contains? fill :fill-color-ref-file))) + (-> (assoc :fill-color-ref-file (uuid/next)) + (assoc :fill-color-ref-id (uuid/next)))))))] + (let [bfills (types.fill/from-plain [fill])] + (and (= (count bfills) 1) + (equivalent-fill? (first bfills) fill)))) + {:num 2000})) + +(t/deftest equality-operation + (let [fills1 (types.fill/from-plain [sample-fill-6]) + fills2 (types.fill/from-plain [sample-fill-6])] + (t/is (= fills1 fills2)))) + +(t/deftest reduce-impl + (let [fills1 (types.fill/from-plain [sample-fill-6]) + fills2 (reduce (fn [result fill] + (conj result fill)) + [] + fills1) + fills3 (types.fill/from-plain fills2)] + (t/is (= fills1 fills3)))) + +(t/deftest indexed-access + (let [fills1 (types.fill/from-plain [sample-fill-6]) + fill0 (nth fills1 0) + fill1 (nth fills1 1)] + (t/is (nil? fill1)) + (t/is (equivalent-fill? fill0 sample-fill-6)))) diff --git a/common/test/common_tests/types/path_data_test.cljc b/common/test/common_tests/types/path_data_test.cljc index b283dd9f5..b96d0e845 100644 --- a/common/test/common_tests/types/path_data_test.cljc +++ b/common/test/common_tests/types/path_data_test.cljc @@ -101,7 +101,7 @@ (let [pdata (path/content sample-content)] (t/is (= sample-bytes (vec - #?(:cljs (js/Int8Array. (.-buffer pdata)) + #?(:cljs (js/Int8Array. (.-buffer (.-buffer pdata))) :clj (.array (.-buffer pdata)))))) (t/is (= sample-content (vec pdata))))) diff --git a/common/test/common_tests/types/shape_decode_encode_test.cljc b/common/test/common_tests/types/shape_decode_encode_test.cljc index c14f03d05..6098f82e1 100644 --- a/common/test/common_tests/types/shape_decode_encode_test.cljc +++ b/common/test/common_tests/types/shape_decode_encode_test.cljc @@ -145,4 +145,4 @@ ;; (app.common.pprint/pprint shape) ;; (app.common.pprint/pprint shape-3) (= shape shape-3))) - {:num 100}))) + {:num 200}))) diff --git a/common/test/common_tests/uuid_test.cljc b/common/test/common_tests/uuid_test.cljc index e0031e1c3..c7c426913 100644 --- a/common/test/common_tests/uuid_test.cljc +++ b/common/test/common_tests/uuid_test.cljc @@ -18,6 +18,7 @@ (let [uuid (uuid/uuid "0227df82-63d7-8016-8005-48d9c0f33011") result-bytes (uuid/get-bytes uuid) expected-bytes [2 39 -33 -126 99 -41 -128 22 -128 5 72 -39 -64 -13 48 17]] + (t/testing "get-bytes" (let [data (uuid/get-bytes uuid)] (t/is (= (nth expected-bytes 0) (aget data 0))) diff --git a/common/yarn.lock b/common/yarn.lock index d323d4bf9..3732d1a0c 100644 --- a/common/yarn.lock +++ b/common/yarn.lock @@ -121,6 +121,13 @@ __metadata: languageName: node linkType: hard +"base64-js@npm:^1.3.1": + version: 1.5.1 + resolution: "base64-js@npm:1.5.1" + checksum: 10c0/f23823513b63173a001030fae4f2dabe283b99a9d324ade3ad3d148e218134676f1ee8568c877cd79ec1c53158dcf2d2ba527a97c606618928ba99dd930102bf + languageName: node + linkType: hard + "binary-extensions@npm:^2.0.0": version: 2.3.0 resolution: "binary-extensions@npm:2.3.0" @@ -163,6 +170,16 @@ __metadata: languageName: node linkType: hard +"buffer@npm:^6.0.3": + version: 6.0.3 + resolution: "buffer@npm:6.0.3" + dependencies: + base64-js: "npm:^1.3.1" + ieee754: "npm:^1.2.1" + checksum: 10c0/2a905fbbcde73cc5d8bd18d1caa23715d5f83a5935867c2329f0ac06104204ba7947be098fe1317fbd8830e26090ff8e764f08cd14fefc977bb248c3487bcbd0 + languageName: node + linkType: hard + "cacache@npm:^18.0.0": version: 18.0.3 resolution: "cacache@npm:18.0.3" @@ -260,7 +277,7 @@ __metadata: concurrently: "npm:^9.0.1" luxon: "npm:^3.4.4" nodemon: "npm:^3.1.7" - shadow-cljs: "npm:3.0.5" + shadow-cljs: "npm:3.1.5" source-map-support: "npm:^0.5.21" ws: "npm:^8.17.0" languageName: unknown @@ -528,6 +545,13 @@ __metadata: languageName: node linkType: hard +"ieee754@npm:^1.2.1": + version: 1.2.1 + resolution: "ieee754@npm:1.2.1" + checksum: 10c0/b0782ef5e0935b9f12883a2e2aa37baa75da6e66ce6515c168697b42160807d9330de9a32ec1ed73149aea02e0d822e572bca6f1e22bdcbd2149e13b050b17bb + languageName: node + linkType: hard + "ignore-by-default@npm:^1.0.1": version: 1.0.1 resolution: "ignore-by-default@npm:1.0.1" @@ -917,6 +941,13 @@ __metadata: languageName: node linkType: hard +"process@npm:^0.11.10": + version: 0.11.10 + resolution: "process@npm:0.11.10" + checksum: 10c0/40c3ce4b7e6d4b8c3355479df77aeed46f81b279818ccdc500124e6a5ab882c0cc81ff7ea16384873a95a74c4570b01b120f287abbdd4c877931460eca6084b3 + languageName: node + linkType: hard + "promise-retry@npm:^2.0.1": version: 2.0.1 resolution: "promise-retry@npm:2.0.1" @@ -1005,10 +1036,12 @@ __metadata: languageName: node linkType: hard -"shadow-cljs@npm:3.0.5": - version: 3.0.5 - resolution: "shadow-cljs@npm:3.0.5" +"shadow-cljs@npm:3.1.5": + version: 3.1.5 + resolution: "shadow-cljs@npm:3.1.5" dependencies: + buffer: "npm:^6.0.3" + process: "npm:^0.11.10" readline-sync: "npm:^1.4.10" shadow-cljs-jar: "npm:1.3.4" source-map-support: "npm:^0.5.21" @@ -1016,7 +1049,7 @@ __metadata: ws: "npm:^8.18.1" bin: shadow-cljs: cli/runner.js - checksum: 10c0/2c5f3976f7bec16b7fb9fbba5d4a7581e0d0157384a470ce0670120f02cfe6b9c7183102133e0e9b300cbe318e9a3b6001309f96840999ca2814d39fc83c23e8 + checksum: 10c0/29da68f7645c258becf4074e4401e5c86dd3af04622c2e10fdac09824e9832290918d90aaf80ef7df0c35731f1b51b84101cbfd0c6819772a493173d4ae69415 languageName: node linkType: hard diff --git a/frontend/deps.edn b/frontend/deps.edn index 1f0889c58..a2aff1fca 100644 --- a/frontend/deps.edn +++ b/frontend/deps.edn @@ -20,8 +20,8 @@ :git/url "https://github.com/funcool/beicon.git"} funcool/rumext - {:git/tag "v2.21" - :git/sha "072d671" + {:git/tag "v2.22" + :git/sha "92879b6" :git/url "https://github.com/funcool/rumext.git"} instaparse/instaparse {:mvn/version "1.5.0"} @@ -42,7 +42,7 @@ :dev {:extra-paths ["dev"] :extra-deps - {thheller/shadow-cljs {:mvn/version "3.0.5"} + {thheller/shadow-cljs {:mvn/version "3.1.5"} com.bhauman/rebel-readline {:mvn/version "RELEASE"} org.clojure/tools.namespace {:mvn/version "RELEASE"} criterium/criterium {:mvn/version "RELEASE"} diff --git a/frontend/package.json b/frontend/package.json index eb6e66a29..7c169a68c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -90,7 +90,7 @@ "rimraf": "^6.0.1", "sass": "^1.89.0", "sass-embedded": "^1.89.0", - "shadow-cljs": "3.0.5", + "shadow-cljs": "3.1.5", "storybook": "^8.6.14", "svg-sprite": "^2.0.4", "typescript": "^5.8.3", diff --git a/frontend/shadow-cljs.edn b/frontend/shadow-cljs.edn index 4e3061122..f80bdc098 100644 --- a/frontend/shadow-cljs.edn +++ b/frontend/shadow-cljs.edn @@ -122,6 +122,7 @@ :storybook {:target :esm :output-dir "target/storybook/" + :devtools {:enabled false} :js-options {:js-provider :import :entry-keys ["module" "browser" "main"] @@ -134,6 +135,7 @@ :components {:exports {default app.main.ui.ds/default helpers app.main.ui.ds.helpers/default} + :prepend-js ";(globalThis.goog.provide = globalThis.goog.constructNamespace_);(globalThis.goog.require = globalThis.goog.module.get);" :depends-on #{:base}}} :compiler-options diff --git a/frontend/src/app/main/data/media.cljs b/frontend/src/app/main/data/media.cljs index 904623505..0b8fff67b 100644 --- a/frontend/src/app/main/data/media.cljs +++ b/frontend/src/app/main/data/media.cljs @@ -15,6 +15,8 @@ [cljs.spec.alpha :as s] [cuerdas.core :as str])) +;; FIXME: revisit the need of this NS + ;; --- Predicates (defn file? @@ -38,7 +40,7 @@ (defn validate-file "Check that a file obtained with the file javascript API is valid." [file] - (when-not (contains? cm/valid-image-types (.-type file)) + (when-not (contains? cm/image-types (.-type file)) (ex/raise :type :validation :code :media-type-not-allowed :hint (str/ffmt "media type % is not supported" (.-type file)))) diff --git a/frontend/src/app/main/data/workspace/colors.cljs b/frontend/src/app/main/data/workspace/colors.cljs index 09cf5af7e..b78f2c407 100644 --- a/frontend/src/app/main/data/workspace/colors.cljs +++ b/frontend/src/app/main/data/workspace/colors.cljs @@ -13,6 +13,7 @@ [app.common.schema :as sm] [app.common.text :as txt] [app.common.types.color :as ctc] + [app.common.types.fill :as types.fill] [app.common.types.shape :as shp] [app.common.types.shape.shadow :refer [check-shadow]] [app.config :as cfg] @@ -21,7 +22,6 @@ [app.main.data.helpers :as dsh] [app.main.data.modal :as md] [app.main.data.workspace.layout :as layout] - [app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.texts :as dwt] [app.main.data.workspace.undo :as dwu] @@ -114,9 +114,9 @@ ([state ids color transform] (transform-fill state ids color transform nil)) ([state ids color transform options] - (let [page-id (or (get options :page-id) - (get state :current-page-id)) - objects (dsh/lookup-page-objects state page-id) + (let [page-id (or (get options :page-id) + (get state :current-page-id)) + objects (dsh/lookup-page-objects state page-id) [text-ids shape-ids] (split-text-shapes objects ids) @@ -126,11 +126,11 @@ (contains? color :color) (assoc :fill-color (:color color)) - (contains? color :id) - (assoc :fill-color-ref-id (:id color)) + (contains? color :ref-id) + (assoc :fill-color-ref-id (:ref-id color)) - (contains? color :file-id) - (assoc :fill-color-ref-file (:file-id color)) + (contains? color :ref-file) + (assoc :fill-color-ref-file (:ref-file color)) (contains? color :gradient) (assoc :fill-color-gradient (:gradient color)) @@ -142,7 +142,10 @@ (assoc :fill-image (:image color)) :always - (d/without-nils)) + (d/without-nils) + + :always + (types.fill/check-fill)) transform-attrs #(transform % fill)] @@ -327,11 +330,11 @@ (contains? color :color) (assoc :stroke-color (:color color)) - (contains? color :id) - (assoc :stroke-color-ref-id (:id color)) + (contains? color :ref-id) + (assoc :stroke-color-ref-id (:ref-id color)) - (contains? color :file-id) - (assoc :stroke-color-ref-file (:file-id color)) + (contains? color :ref-file) + (assoc :stroke-color-ref-file (:ref-file color)) (contains? color :gradient) (assoc :stroke-color-gradient (:gradient color)) @@ -533,18 +536,19 @@ (defn- color-att->text [color] - {:fill-color (when (:color color) (str/lower (:color color))) - :fill-opacity (:opacity color) - :fill-color-ref-id (:id color) - :fill-color-ref-file (:file-id color) - :fill-color-gradient (:gradient color)}) + (d/without-nils + {:fill-color (when (:color color) (str/lower (:color color))) + :fill-opacity (:opacity color) + :fill-color-ref-id (:ref-id color) + :fill-color-ref-file (:ref-file color) + :fill-color-gradient (:gradient color)})) (defn change-text-color [old-color new-color index node] (let [fills (map #(dissoc % :fill-color-ref-id :fill-color-ref-file) (:fills node)) - parsed-color (-> (d/without-nils (color-att->text old-color)) + parsed-color (-> (color-att->text old-color) (dissoc :fill-color-ref-id :fill-color-ref-file)) - parsed-new-color (d/without-nils (color-att->text new-color)) + parsed-new-color (color-att->text new-color) has-color? (d/index-of fills parsed-color)] (cond-> node (some? has-color?) @@ -595,37 +599,33 @@ (defn apply-color-from-palette [color stroke?] + (let [color (ctc/check-color color)] + (ptk/reify ::apply-color-from-palette + ptk/WatchEvent + (watch [_ state _] + (let [objects (dsh/lookup-page-objects state) + selected (->> (dsh/lookup-selected state) + (cfh/clean-loops objects)) - (assert - (ctc/check-color color) - "expected valid color structure") + ids + (loop [pending (seq selected) + result []] + (if (empty? pending) + result + (let [cur (first pending) + group? (cfh/group-shape? objects cur) - (ptk/reify ::apply-color-from-palette - ptk/WatchEvent - (watch [_ state _] - (let [objects (dsh/lookup-page-objects state) - selected (->> (dsh/lookup-selected state) - (cfh/clean-loops objects)) + pending + (if group? + (concat pending (dm/get-in objects [cur :shapes])) + pending) - ids - (loop [pending (seq selected) - result []] - (if (empty? pending) - result - (let [cur (first pending) - group? (cfh/group-shape? objects cur) + result (cond-> result (not group?) (conj cur))] + (recur (rest pending) result))))] - pending - (if group? - (concat pending (dm/get-in objects [cur :shapes])) - pending) - - result (cond-> result (not group?) (conj cur))] - (recur (rest pending) result))))] - - (if stroke? - (rx/of (change-stroke-color ids color 0)) - (rx/of (change-fill ids color 0))))))) + (if stroke? + (rx/of (change-stroke-color ids color 0)) + (rx/of (change-fill ids color 0)))))))) (declare activate-colorpicker-color) (declare activate-colorpicker-gradient) @@ -634,14 +634,10 @@ (defn apply-color-from-colorpicker [color] - - (assert (ctc/check-color color) - "expected valid color structure") - - (ptk/reify ::apply-color-from-colorpicker - ptk/UpdateEvent - (update [_ state] - (let [gradient-type (dm/get-in color [:gradient :type])] + (let [color (ctc/check-color color)] + (ptk/reify ::apply-color-from-colorpicker + ptk/UpdateEvent + (update [_ state] (update state :colorpicker (fn [state] (cond @@ -655,19 +651,57 @@ (assoc :type :color) (dissoc :editing-stop :stops :gradient)) + :else + (let [gradient-type (dm/get-in color [:gradient :type])] + (cond + (= :linear gradient-type) + (-> state + (assoc :type :linear-gradient) + (assoc :editing-stop 0) + (update :current-color dissoc :image)) - (= :linear gradient-type) - (-> state - (assoc :type :linear-gradient) - (assoc :editing-stop 0) - (d/dissoc-in [:current-color :image])) + (= :radial gradient-type) + (-> state + (assoc :type :radial-gradient) + (assoc :editing-stop 0) + (update :current-color dissoc :image))))))))))) - (= :radial gradient-type) - (-> state - (assoc :type :radial-gradient) - (assoc :editing-stop 0) - (d/dissoc-in [:current-color :image]))))))))) +(defn- recent-color-equal? + [c1 c2] + (or (= c1 c2) + (and (some? (:color c1)) + (some? (:color c2)) + (= (:color c1) (:color c2))))) +(defn add-recent-color + [color] + (let [color (ctc/check-color color)] + (ptk/reify ::add-recent-color + ptk/UpdateEvent + (update [_ state] + (let [file-id (:current-file-id state)] + (update-in state [:recent-colors file-id] + (fn [colors] + (let [colors (d/removev (partial recent-color-equal? color) colors) + colors (conj colors color)] + (cond-> colors + (> (count colors) 15) + (subvec 1))))))) + + ptk/EffectEvent + (effect [_ state _] + (let [recent-colors (:recent-colors state)] + (swap! storage/user assoc :recent-colors recent-colors)))))) + +(defn apply-color-from-assets + [file-id color stroke?] + (let [color (ctc/check-library-color color)] + (ptk/reify ::apply-color-from-asserts + ptk/WatchEvent + (watch [_ _ _] + (let [color (ctc/library-color->color color file-id)] + (rx/of (apply-color-from-palette color stroke?) + (add-recent-color color))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; COLORPICKER STATE MANAGEMENT @@ -823,7 +857,7 @@ (update state :colorpicker (fn [{:keys [stops editing-stop] :as state}] (let [cap-stops? (or (features/active-feature? state "render-wasm/v1") (contains? cfg/flags :frontend-binary-fills)) - can-add-stop? (or (not cap-stops?) (< (count stops) shp/MAX-GRADIENT-STOPS))] + can-add-stop? (or (not cap-stops?) (< (count stops) types.fill/MAX-GRADIENT-STOPS))] (if can-add-stop? (if (cc/uniform-spread? stops) ;; Add to uniform @@ -869,7 +903,7 @@ (fn [state] (let [stops (:stops state) cap-stops? (or (features/active-feature? state "render-wasm/v1") (contains? cfg/flags :frontend-binary-fills)) - can-add-stop? (or (not cap-stops?) (< (count stops) shp/MAX-GRADIENT-STOPS))] + can-add-stop? (or (not cap-stops?) (< (count stops) types.fill/MAX-GRADIENT-STOPS))] (if can-add-stop? (let [new-stop (-> (cc/interpolate-gradient stops offset) (split-color-components)) stops (conj stops new-stop) @@ -889,8 +923,12 @@ (update state :colorpicker (fn [state] (let [stop (or (:editing-stop state) 0) - cap-stops? (or (features/active-feature? state "render-wasm/v1") (contains? cfg/flags :frontend-binary-fills)) - stops (mapv split-color-components (if cap-stops? (take shp/MAX-GRADIENT-STOPS stops) stops))] + cap-stops? (or (features/active-feature? state "render-wasm/v1") + (contains? cfg/flags :frontend-binary-fills)) + stops (mapv split-color-components + (if cap-stops? + (take types.fill/MAX-GRADIENT-STOPS stops) + stops))] (-> state (assoc :current-color (get stops stop)) (assoc :stops stops)))))))) @@ -953,8 +991,7 @@ (update :current-color #(if (not= type :image) (dissoc % :image) %)) ;; current color can be a library one ;; I'm changing via colorpicker - (d/dissoc-in [:current-color :id]) - (d/dissoc-in [:current-color :file-id]))] + (update :current-color dissoc :ref-id :ref-file))] (if-let [stop (:editing-stop state)] (update-in state [:stops stop] (fn [data] (->> changes (merge data) @@ -966,15 +1003,17 @@ (assoc :type :color)))))))) ptk/WatchEvent (watch [_ state _] - (let [selected-type (-> state - :colorpicker - :type) - formated-color (get-color-from-colorpicker-state (:colorpicker state)) + (let [state (get-color-from-colorpicker-state (:colorpicker state)) + type (get state :type) + ;; Type is set to color on closing the colorpicker, but we ;; can can close it while still uploading an image fill - ignore-color? (and (= selected-type :color) (nil? (:color formated-color)))] + ignore-color? + (and (= type :color) (nil? (:color state)))] + (when (and add-recent? (not ignore-color?)) - (rx/of (dwl/add-recent-color formated-color))))))) + (let [color (select-keys state [:image :gradient :color :opacity])] + (rx/of (add-recent-color color)))))))) (defn update-colorpicker-gradient [changes] diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index fd7bd595a..034bdd6b5 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -50,7 +50,6 @@ [app.main.store :as st] [app.util.color :as uc] [app.util.i18n :refer [tr]] - [app.util.storage :as storage] [app.util.time :as dt] [beicon.v2.core :as rx] [cuerdas.core :as str] @@ -119,11 +118,8 @@ (assoc :name (or (get-in color [:image :name]) (:color color) (uc/gradient-type->string (get-in color [:gradient :type])))) - (d/without-nils))] - - (dm/assert! - "expect valid color structure" - (ctc/check-color color)) + (d/without-nils) + (ctc/check-library-color))] (ptk/reify ::add-color ev/Event @@ -138,22 +134,6 @@ (fn [state] (assoc-in state [:workspace-local :color-for-rename] (:id color)))) (dch/commit-changes changes)))))))) -(defn add-recent-color - [color] - (assert (ctc/check-recent-color color) - "expected valid recent color structure") - - (ptk/reify ::add-recent-color - ptk/UpdateEvent - (update [_ state] - (let [file-id (:current-file-id state)] - (update state :recent-colors ctc/add-recent-color file-id color))) - - ptk/EffectEvent - (effect [_ state _] - (let [recent-colors (:recent-colors state)] - (swap! storage/user assoc :recent-colors recent-colors))))) - (def clear-color-for-rename (ptk/reify ::clear-color-for-rename ptk/UpdateEvent @@ -176,16 +156,10 @@ (defn update-color [color file-id] - (let [color (d/without-nils color)] - - (dm/assert! - "expected valid color data structure" - (ctc/check-color color)) - - (dm/assert! - "expected file-id" - (uuid? file-id)) + (assert (uuid? file-id) "expected a uuid instance for `file-id`") + (let [color (-> (d/without-nils color) + (ctc/check-library-color))] (ptk/reify ::update-color ptk/WatchEvent (watch [it state _] @@ -194,15 +168,10 @@ (defn update-color-data "Update color data without affecting the path location" [color file-id] - (let [color (d/without-nils color)] + (assert (uuid? file-id) "expected a uuid instance for `file-id`") - (dm/assert! - "expected valid color data structure" - (ctc/check-color color)) - - (dm/assert! - "expected file-id" - (uuid? file-id)) + (let [color (-> (d/without-nils color) + (ctc/check-library-color))] (ptk/reify ::update-color-data ptk/WatchEvent @@ -213,17 +182,10 @@ ;; FIXME: revisit why file-id is passed on the event (defn rename-color [file-id id new-name] - (dm/assert! - "expected valid uuid for `id`" - (uuid? id)) - (dm/assert! - "expected valid uuid for `file-id`" - (uuid? file-id)) - - (dm/assert! - "expected valid string for `new-name`" - (string? new-name)) + (assert (uuid? id) "expected valid uuid instance for `id`") + (assert (uuid? file-id) "expected a uuid instance for `file-id`") + (assert (string? new-name) "expected a string instance for `new-name`") (ptk/reify ::rename-color ptk/WatchEvent @@ -232,14 +194,16 @@ (if (str/empty? new-name) (rx/empty) (let [data (dsh/lookup-file-data state) - color (get-in data [:colors id]) - color (assoc color :name new-name) - color (d/without-nils color)] + color (-> (ctc/get-color data id) + (assoc :name new-name) + (d/without-nils) + (ctc/check-library-color))] (update-color* it state color file-id))))))) (defn delete-color [{:keys [id] :as params}] - (dm/assert! (uuid? id)) + (assert (uuid? id) "expected valid uuid instance for `id`") + (ptk/reify ::delete-color ev/Event (-data [_] {:id id}) @@ -252,6 +216,7 @@ (pcb/delete-color id))] (rx/of (dch/commit-changes changes)))))) +;; FIXME: this should be deleted (defn add-media [media] (let [media (ctf/check-file-media media)] diff --git a/frontend/src/app/main/data/workspace/media.cljs b/frontend/src/app/main/data/workspace/media.cljs index 0a2d920fb..eafd05570 100644 --- a/frontend/src/app/main/data/workspace/media.cljs +++ b/frontend/src/app/main/data/workspace/media.cljs @@ -35,6 +35,9 @@ [promesa.core :as p] [tubax.core :as tubax])) +(def accept-image-types + (str/join "," media/image-types)) + (defn- optimize [input] (svgo/optimize input svgo/defaultOptions)) diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index 9432aafee..53106f25e 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -16,6 +16,7 @@ [app.common.geom.shapes :as gsh] [app.common.math :as mth] [app.common.text :as txt] + [app.common.types.fill :as types.fill] [app.common.types.modifiers :as ctm] [app.common.uuid :as uuid] [app.main.data.event :as ev] @@ -234,9 +235,9 @@ ;; --- Helpers -(defn to-new-fills +(defn- to-new-fills [data] - [(d/without-nils (select-keys data [:fill-color :fill-opacity :fill-color-gradient :fill-color-ref-id :fill-color-ref-file]))]) + [(d/without-nils (select-keys data types.fill/fill-attrs))]) (defn- shape-current-values [shape pred attrs] @@ -246,9 +247,7 @@ (if (txt/is-text-node? node) (let [fills (cond - (or (some? (:fill-color node)) - (some? (:fill-opacity node)) - (some? (:fill-color-gradient node))) + (types.fill/has-valid-fill-attrs? node) (to-new-fills node) (some? (:fills node)) @@ -474,13 +473,13 @@ (defn migrate-node [node] - (let [color-attrs (select-keys node [:fill-color :fill-opacity :fill-color-ref-id :fill-color-ref-file :fill-color-gradient])] + (let [color-attrs (not-empty (select-keys node types.fill/fill-attrs))] (cond-> node (nil? (:fills node)) (assoc :fills []) ;; Migrate old colors and remove the old fromat - (d/not-empty? color-attrs) + color-attrs (-> (dissoc :fill-color :fill-opacity :fill-color-ref-id :fill-color-ref-file :fill-color-gradient) (update :fills conj color-attrs)) diff --git a/frontend/src/app/main/ui/components/color_bullet.cljs b/frontend/src/app/main/ui/components/color_bullet.cljs index 79fae7c6b..d94938d14 100644 --- a/frontend/src/app/main/ui/components/color_bullet.cljs +++ b/frontend/src/app/main/ui/components/color_bullet.cljs @@ -15,8 +15,7 @@ (defn- color-title [color-item] - (let [name (:name color-item) - path (:path color-item) + (let [{:keys [name path]} (meta color-item) path-and-name (if path (str path " / " name) name) gradient (:gradient color-item) image (:image color-item) @@ -107,7 +106,8 @@ (mf/defc color-name {::mf/wrap-props false} [{:keys [color size on-click on-double-click origin]}] - (let [{:keys [name color gradient]} (if (string? color) {:color color :opacity 1} color)] + (let [{:keys [name]} (meta color) + {:keys [color gradient]} (if (string? color) {:color color :opacity 1} color)] (when (or (not size) (> size 64)) [:span {:class (stl/css-case :color-text (and (= origin :palette) (< size 72)) diff --git a/frontend/src/app/main/ui/dashboard/fonts.cljs b/frontend/src/app/main/ui/dashboard/fonts.cljs index c03aa5beb..833de7d1d 100644 --- a/frontend/src/app/main/ui/dashboard/fonts.cljs +++ b/frontend/src/app/main/ui/dashboard/fonts.cljs @@ -29,6 +29,11 @@ [okulary.core :as l] [rumext.v2 :as mf])) +(def ^:private accept-font-types + (str (str/join "," cm/font-types) + ;; A workaround to solve a problem with chrome input selector + ",.ttf,application/font-woff,woff,.otf")) + (defn- use-page-title [team section] (mf/with-effect [team] @@ -180,7 +185,7 @@ :tab-index "0"} [:span (tr "labels.add-custom-font")] [:& file-uploader {:input-id "font-upload" - :accept cm/str-font-types + :accept accept-font-types :multi true :ref input-ref :on-selected on-selected}]] diff --git a/frontend/src/app/main/ui/ds/utilities/swatch.cljs b/frontend/src/app/main/ui/ds/utilities/swatch.cljs index 1d02e5fdb..bd8d086fd 100644 --- a/frontend/src/app/main/ui/ds/utilities/swatch.cljs +++ b/frontend/src/app/main/ui/ds/utilities/swatch.cljs @@ -21,12 +21,16 @@ (defn- color-title [color-item] - (let [name (:name color-item) - path (:path color-item) - path-and-name (if (and path (not (str/empty? path))) (str path " / " name) name) + (let [{:keys [name path]} (meta color-item) + + path-and-name + (if (and path (not (str/empty? path))) + (str path " / " name) + name) + gradient (:gradient color-item) - image (:image color-item) - color (:color color-item)] + image (:image color-item) + color (:color color-item)] (if (some? name) (cond @@ -71,34 +75,38 @@ ;; automatically convert them to clojure map (which is exactly ;; what this component expects). On normal usage of this ;; component this code should be always fallback to else case. - background (if (object? background) - (json/->clj background) - background) - read-only? (nil? on-click) - id? (some? (:id background)) - element-type (if read-only? "div" "button") - button-type (if (not read-only?) "button" nil) - size (or size "small") - active (or active false) - gradient-type (-> background :gradient :type) + background (if (object? background) + (json/->clj background) + background) + read-only? (nil? on-click) + id? (some? (:ref-id background)) + element-type (if read-only? "div" "button") + button-type (if (not read-only?) "button" nil) + size (or size "small") + active (or active false) + gradient-type (-> background :gradient :type) gradient-stops (-> background :gradient :stops) - gradient-data {:type gradient-type - :stops gradient-stops} - image (:image background) - format (if id? "rounded" "square") - class (dm/str class " " (stl/css-case - :swatch true - :small (= size "small") - :medium (= size "medium") - :large (= size "large") - :square (= format "square") - :active (= active true) - :interactive (= element-type "button") - :rounded (= format "rounded"))) - props (mf/spread-props props {:class class - :on-click on-click - :type button-type - :title (color-title background)})] + gradient-data {:type gradient-type + :stops gradient-stops} + image (:image background) + format (if id? "rounded" "square") + + class + (dm/str class " " (stl/css-case + :swatch true + :small (= size "small") + :medium (= size "medium") + :large (= size "large") + :square (= format "square") + :active (= active true) + :interactive (= element-type "button") + :rounded (= format "rounded"))) + + props + (mf/spread-props props {:class class + :on-click on-click + :type button-type + :title (color-title background)})] [:> element-type props (cond diff --git a/frontend/src/app/main/ui/ds/utilities/swatch.stories.jsx b/frontend/src/app/main/ui/ds/utilities/swatch.stories.jsx index 8a7aa5f14..3da5f81fe 100644 --- a/frontend/src/app/main/ui/ds/utilities/swatch.stories.jsx +++ b/frontend/src/app/main/ui/ds/utilities/swatch.stories.jsx @@ -93,7 +93,8 @@ export const RadialGradient = { export const Rounded = { args: { background: { - id: helpers.generateUuid(), + refId: helpers.generateUuid(), + refFile: helpers.generateUuid(), color: "#2f226c", opacity: 0.5, }, diff --git a/frontend/src/app/main/ui/inspect/attributes/common.cljs b/frontend/src/app/main/ui/inspect/attributes/common.cljs index 1a0f16d2c..79e953a81 100644 --- a/frontend/src/app/main/ui/inspect/attributes/common.cljs +++ b/frontend/src/app/main/ui/inspect/attributes/common.cljs @@ -26,18 +26,20 @@ (def file-colors-ref (l/derived (l/in [:viewer :file :data :colors]) st/state)) -(defn make-colors-library-ref [libraries-place file-id] +(defn make-colors-library-ref + [libraries-place file-id] (let [get-library (fn [state] (get-in state [libraries-place file-id :data :colors]))] (l/derived get-library st/state))) -(defn- use-colors-library [color] - (-> (mf/use-memo - (mf/deps (:file-id color)) - #(make-colors-library-ref :files (:file-id color))) - mf/deref)) +(defn- use-colors-library + [{:keys [ref-file] :as color}] + (let [library (mf/with-memo [ref-file] + (make-colors-library-ref :files ref-file))] + (mf/deref library))) +;; FIXME: this breaks react hooks rule (broken code) (defn- get-file-colors [] (or (mf/deref file-colors-ref) (mf/deref refs/workspace-file-colors))) @@ -51,8 +53,8 @@ (mf/defc color-row [{:keys [color format copy-data on-change-format]}] (let [colors-library (use-colors-library color) file-colors (get-file-colors) - color-library-name (get-in (or colors-library file-colors) [(:id color) :name]) - color (assoc color :color-library-name color-library-name) + color-library-name (get-in (or colors-library file-colors) [(:ref-id color) :name]) + color (assoc color :name color-library-name) image (:image color)] diff --git a/frontend/src/app/main/ui/inspect/attributes/fill.cljs b/frontend/src/app/main/ui/inspect/attributes/fill.cljs index 415cfce75..a1d8afeae 100644 --- a/frontend/src/app/main/ui/inspect/attributes/fill.cljs +++ b/frontend/src/app/main/ui/inspect/attributes/fill.cljs @@ -7,6 +7,7 @@ (ns app.main.ui.inspect.attributes.fill (:require-macros [app.main.style :as stl]) (:require + [app.common.types.color :as types.color] [app.main.ui.components.title-bar :refer [inspect-title-bar*]] [app.main.ui.inspect.attributes.common :refer [color-row]] [app.util.code-gen.style-css :as css] @@ -15,14 +16,6 @@ (def properties [:background :background-color :background-image]) -(defn shape->color [shape] - {:color (:fill-color shape) - :opacity (:fill-opacity shape) - :gradient (:fill-color-gradient shape) - :id (:fill-color-ref-id shape) - :file-id (:fill-color-ref-file shape) - :image (:fill-image shape)}) - (defn has-fill? [shape] (and (not (contains? #{:text :group} (:type shape))) @@ -35,7 +28,10 @@ [{:keys [objects shape]}] (let [format* (mf/use-state :hex) format (deref format*) - color (shape->color shape) + ;; FIXME: this looks broken code, because shape does not + ;; longer contains :fill-xxxx attributes but it is preserved + ;; as it was just moved the impl; this need to be fixed + color (types.color/fill->color shape) on-change (mf/use-fn (fn [format] diff --git a/frontend/src/app/main/ui/inspect/attributes/text.cljs b/frontend/src/app/main/ui/inspect/attributes/text.cljs index 71b4306c7..f99037a18 100644 --- a/frontend/src/app/main/ui/inspect/attributes/text.cljs +++ b/frontend/src/app/main/ui/inspect/attributes/text.cljs @@ -10,6 +10,7 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.text :as txt] + [app.common.types.color :as types.color] [app.main.fonts :as fonts] [app.main.refs :as refs] [app.main.store :as st] @@ -34,14 +35,6 @@ (get-in state [:viewer-libraries file-id :data :typographies]))] #(l/derived get-library st/state))) -(defn fill->color [{:keys [fill-color fill-opacity fill-color-gradient fill-color-ref-id fill-color-ref-file fill-image]}] - {:color fill-color - :opacity fill-opacity - :gradient fill-color-gradient - :id fill-color-ref-id - :file-id fill-color-ref-file - :image fill-image}) - (defn copy-style-data [style & properties] (->> properties @@ -73,7 +66,7 @@ (for [[idx fill] (map-indexed vector (:fills style))] [:& color-row {:key idx :format @color-format - :color (fill->color fill) + :color (types.color/fill->color fill) :copy-data (copy-style-data fill :fill-color :fill-color-gradient) :on-change-format #(reset! color-format %)}])) diff --git a/frontend/src/app/main/ui/workspace/color_palette.cljs b/frontend/src/app/main/ui/workspace/color_palette.cljs index fd93b321d..6799a1510 100644 --- a/frontend/src/app/main/ui/workspace/color_palette.cljs +++ b/frontend/src/app/main/ui/workspace/color_palette.cljs @@ -11,7 +11,6 @@ [app.common.types.color :as ctc] [app.main.data.event :as ev] [app.main.data.workspace.colors :as mdc] - [app.main.data.workspace.libraries :as dwl] [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.color-bullet :as cb] @@ -34,25 +33,29 @@ (mf/use-fn (mf/deps color selected) (fn [event] - (st/emit! (dwl/add-recent-color color) + (st/emit! (mdc/add-recent-color color) (mdc/apply-color-from-palette color (kbd/alt? event)) (when (not= selected :recent) (ptk/data-event ::ev/event {::ev/name "use-library-color" ::ev/origin "color-palette" - :external-library (not= selected :file)})))))] + :external-library (not= selected :file)}))))) + title + (uc/get-color-name color)] + [:button {:class (stl/css-case :color-cell true :is-not-library-color (nil? (:id color)) :no-text (<= size 64)) - :title (uc/get-color-name color) - :aria-label (uc/get-color-name color) + :title title + :aria-label title :type "button" :on-click select-color} [:> swatch* {:background color :size "medium"}] [:& cb/color-name {:color color :size size :origin :palette}]])) (mf/defc palette* + {::mf/wrap [mf/memo]} [{:keys [colors size width selected]}] (let [state (mf/use-state #(do {:show-menu false})) offset-step (cond @@ -121,9 +124,11 @@ (when (not= 0 (:offset @state)) (swap! state assoc :offset 0))) - [:div {:class (stl/css-case :color-palette true - :no-text (< size 64)) - :style #js {"--bullet-size" (dm/str bullet-size "px") "--color-cell-width" (dm/str color-cell-width "px")}} + [:div {:class (stl/css-case + :color-palette true + :no-text (< size 64)) + :style #js {"--bullet-size" (dm/str bullet-size "px") + "--color-cell-width" (dm/str color-cell-width "px")}} (when show-arrows? [:button {:class (stl/css :left-arrow) @@ -154,14 +159,25 @@ (mf/defc recent-colors-palette* {::mf/private true} [props] - (let [colors (mf/deref refs/recent-colors) - - colors (mf/with-memo [colors] - (->> (reverse colors) - (filter ctc/valid-color?) - (vec))) + (let [libraries (mf/deref refs/files) + colors (mf/deref refs/recent-colors) + colors (mf/with-memo [colors libraries] + (->> (reverse colors) + (filter ctc/valid-color?) + (map (fn [{:keys [ref-id ref-file] :as color}] + ;; For make the UI consistent we need to ensure that a + ;; library color looks exactly as it is actually and not + ;; how it was saved first time + (if (and ref-id ref-file) + (let [fdata (dm/get-in libraries [ref-file :data])] + (or (some-> (ctc/get-color fdata ref-id) + (ctc/library-color->color ref-file)) + (dissoc color :ref-id :ref-file))) + color))) + (vec))) props (mf/spread-props props {:colors colors})] + [:> palette* props])) (defn- make-library-colors-ref @@ -178,9 +194,9 @@ colors (mf/deref colors-ref) colors (mf/with-memo [colors file-id] (->> (vals colors) - (filter ctc/valid-color?) - (map #(assoc % :file-id file-id)) + (filter ctc/valid-library-color?) (sort-by :name) + (map #(ctc/library-color->color % file-id)) (vec))) props (mf/spread-props props {:colors colors})] diff --git a/frontend/src/app/main/ui/workspace/colorpicker.cljs b/frontend/src/app/main/ui/workspace/colorpicker.cljs index dfa45562b..97abf5443 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker.cljs @@ -12,7 +12,7 @@ [app.common.data.macros :as dm] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] - [app.common.types.shape :as shp] + [app.common.types.fill :as types.fill] [app.config :as cfg] [app.main.data.event :as-alias ev] [app.main.data.modal :as modal] @@ -134,6 +134,7 @@ on-fill-image-success (mf/use-fn (fn [image] + ;; FIXME: revisit (st/emit! (dc/update-colorpicker-color {:image (-> (select-keys image [:id :width :height :mtype :name]) (assoc :keep-aspect-ratio true))} @@ -200,10 +201,9 @@ (fn [_ color] (if (and (some? (:color color)) (some? (:gradient data))) (handle-change-color {:hex (:color color) :alpha (:opacity color)}) - (do - (st/emit! - (dwl/add-recent-color color) - (dc/apply-color-from-colorpicker color)) + (let [color (d/without-qualified color)] + (st/emit! (dc/add-recent-color color) + (dc/apply-color-from-colorpicker color)) (on-change color))))) on-add-library-color @@ -439,7 +439,7 @@ (when (= selected-mode :gradient) [:> gradients* {:type (:type state) - :stops (if cap-stops? (vec (take shp/MAX-GRADIENT-STOPS (:stops state))) (:stops state)) + :stops (if cap-stops? (vec (take types.fill/MAX-GRADIENT-STOPS (:stops state))) (:stops state)) :editing-stop (:editing-stop state) :on-stop-edit-start handle-stop-edit-start :on-stop-edit-finish handle-stop-edit-finish diff --git a/frontend/src/app/main/ui/workspace/colorpicker/gradients.cljs b/frontend/src/app/main/ui/workspace/colorpicker/gradients.cljs index d7bd7823e..961affdc1 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/gradients.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/gradients.cljs @@ -11,7 +11,7 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.math :as mth] - [app.common.types.shape :as shp] + [app.common.types.fill :as types.fill] [app.config :as cfg] [app.main.features :as features] [app.main.ui.components.numeric-input :refer [numeric-input*]] @@ -288,7 +288,7 @@ (when on-reverse-stops (on-reverse-stops)))) cap-stops? (or (features/use-feature "render-wasm/v1") (contains? cfg/flags :frontend-binary-fills)) - add-stop-disabled? (when cap-stops? (>= (count stops) shp/MAX-GRADIENT-STOPS))] + add-stop-disabled? (when cap-stops? (>= (count stops) types.fill/MAX-GRADIENT-STOPS))] [:div {:class (stl/css :gradient-panel)} [:div {:class (stl/css :gradient-preview)} diff --git a/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs b/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs index 4996eb9d1..bc1d3f83c 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs @@ -108,16 +108,15 @@ (filter valid-color?) (map-indexed (fn [index color] (let [color (if (map? color) color {:color color})] - (assoc color ::id (dm/str index))))) + (vary-meta color assoc ::id (dm/str index))))) (sort c/sort-colors)) (->> (dm/get-in libraries [file-id :data :colors]) (vals) (filter valid-color?) + (sort-by :name) + (map #(ctc/library-color->color % file-id)) (map-indexed (fn [index color] - (-> color - (assoc :file-id file-id) - (assoc ::id (dm/str index))))) - (sort-by :name)))] + (vary-meta color assoc ::id (dm/str index))))))] (reset! current-colors* colors))) @@ -142,6 +141,6 @@ (for [color current-colors] [:& cb/color-bullet - {:key (dm/str "color-" (::id color)) + {:key (-> color meta ::id) :color color :on-click on-color-click}])]])) diff --git a/frontend/src/app/main/ui/workspace/libraries.cljs b/frontend/src/app/main/ui/workspace/libraries.cljs index 40de66b24..e00c06fc3 100644 --- a/frontend/src/app/main/ui/workspace/libraries.cljs +++ b/frontend/src/app/main/ui/workspace/libraries.cljs @@ -9,7 +9,7 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] - [app.common.types.colors-list :as ctcl] + [app.common.types.color :as ctc] [app.common.types.components-list :as ctkl] [app.common.types.file :as ctf] [app.common.types.typographies-list :as ctyl] @@ -407,7 +407,7 @@ (sort-by #(str/lower (:name %))) (truncate :components)) colors (->> color-ids - (map #(ctcl/get-color (:data library) %)) + (map #(ctc/get-color (:data library) %)) (sort-by #(str/lower (:name %))) (truncate :colors)) typographies (->> typography-ids diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets/colors.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets/colors.cljs index 79f17a547..bdba85ef2 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets/colors.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets/colors.cljs @@ -42,8 +42,7 @@ (let [color (mf/with-memo [color file-id] (cond-> color (:value color) (assoc :color (:value color) :opacity 1) - (:value color) (dissoc :value) - :always (assoc :file-id file-id))) + (:value color) (dissoc :value))) color-id (:id color) @@ -78,7 +77,6 @@ (let [name (cfh/merge-path-item (:path color) (:name color)) color (-> attrs (assoc :id (:id color)) - (assoc :file-id file-id) (assoc :name name))] (st/emit! (dwl/update-color color file-id))))) @@ -177,7 +175,7 @@ on-click (mf/use-fn - (mf/deps color on-asset-click read-only?) + (mf/deps color on-asset-click read-only? file-id) (fn [event] (when-not read-only? (st/emit! (ptk/data-event ::ev/event @@ -186,8 +184,7 @@ :external-library (not local?)})) (when-not (on-asset-click event (:id color)) - (st/emit! (dwl/add-recent-color color) - (dc/apply-color-from-palette color (kbd/alt? event)))))))] + (st/emit! (dc/apply-color-from-assets file-id color (kbd/alt? event)))))))] (mf/with-effect [editing?] (when editing? diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets/components.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets/components.cljs index c53002203..c2bed40ac 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets/components.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets/components.cljs @@ -10,7 +10,6 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.files.helpers :as cfh] - [app.common.media :as cm] [app.common.types.component :as ctc] [app.common.types.file :as ctf] [app.main.data.event :as ev] @@ -525,7 +524,7 @@ :aria-label (tr "workspace.assets.components.add-component") :on-click add-component :icon "add"} - [:& file-uploader {:accept cm/str-image-types + [:& file-uploader {:accept dwm/accept-image-types :multi true :ref input-ref :on-selected on-file-selected}]])] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/fill.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/fill.cljs index d5e4538ca..9090730cb 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/fill.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/fill.cljs @@ -10,7 +10,7 @@ [app.common.colors :as clr] [app.common.data :as d] [app.common.types.color :as ctc] - [app.common.types.shape :as shp] + [app.common.types.fill :as types.fill] [app.common.types.shape.attrs :refer [default-color]] [app.config :as cfg] [app.main.data.workspace.colors :as dc] @@ -24,6 +24,7 @@ [app.util.i18n :as i18n :refer [tr]] [rumext.v2 :as mf])) +;; FIXME:revisit this (def fill-attrs [:fills :fill-color @@ -55,12 +56,12 @@ ;; Excluding nil values values (d/without-nils values) fills (if (contains? cfg/flags :frontend-binary-fills) - (take shp/MAX-FILLS (d/nilv (:fills values) [])) + (take types.fill/MAX-FILLS (d/nilv (:fills values) [])) (:fills values)) has-fills? (or (= :multiple fills) (some? (seq fills))) can-add-fills? (if (contains? cfg/flags :frontend-binary-fills) (and (not (= :multiple fills)) - (< (count fills) shp/MAX-FILLS)) + (< (count fills) types.fill/MAX-FILLS)) (not (= :multiple fills))) state* (mf/use-state has-fills?) @@ -176,7 +177,7 @@ (seq fills) [:& h/sortable-container {} (for [[index value] (d/enumerate fills)] - [:> color-row* {:color (ctc/fill->shape-color value) + [:> color-row* {:color (ctc/fill->color value) :key index :index index :title (tr "workspace.options.fill") diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs index 67ac4c522..a229f1ec8 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs @@ -10,6 +10,7 @@ [app.common.colors :as clr] [app.common.data :as d] [app.common.data.macros :as dm] + [app.common.types.shape.shadow :as ctss] [app.common.uuid :as uuid] [app.main.data.workspace.colors :as dc] [app.main.data.workspace.shapes :as dwsh] @@ -91,7 +92,10 @@ (mf/use-fn (mf/deps index) #(on-update index :blur %)) on-update-color - (mf/use-fn (mf/deps index) #(on-update index :color (d/without-nils %))) + (mf/use-fn + (mf/deps index on-update) + (fn [color] + (on-update index :color color))) on-detach-color (mf/use-fn (mf/deps index) #(on-detach-color index)) @@ -275,8 +279,13 @@ (mf/use-fn (fn [index attr value] (let [ids (mf/ref-val ids-ref)] - (st/emit! (dwsh/update-shapes ids #(assoc-in % [:shadow index attr] value))))))] - + (st/emit! (dwsh/update-shapes ids + (fn [shape] + (update-in shape [:shadow index] + (fn [shadow] + (-> shadow + (assoc attr value) + (ctss/check-shadow))))))))))] [:div {:class (stl/css :element-set)} [:div {:class (stl/css :element-title)} [:& title-bar {:collapsable has-shadows? diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs index d02bfdf43..5b9744589 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs @@ -12,7 +12,7 @@ [app.common.data.macros :as dm] [app.common.types.shape.attrs :refer [default-color]] [app.main.data.modal :as modal] - [app.main.data.workspace.libraries :as dwl] + [app.main.data.workspace.colors :as dwc] [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.color-bullet :as cb] @@ -116,7 +116,7 @@ (let [color (-> color (assoc :color value) (dissoc :gradient))] - (st/emit! (dwl/add-recent-color color) + (st/emit! (dwc/add-recent-color color) (on-change color))))) handle-opacity-change @@ -126,7 +126,7 @@ (let [color (-> color (assoc :opacity (/ value 100)) (dissoc :ref-id :ref-file))] - (st/emit! (dwl/add-recent-color color) + (st/emit! (dwc/add-recent-color color) (on-change color))))) handle-click-color diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.cljs index aba49dbcb..7b2e3edfe 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.cljs @@ -149,7 +149,7 @@ ;; Stroke Color ;; FIXME: memorize stroke color - [:> color-row* {:color (ctc/stroke->shape-color stroke) + [:> color-row* {:color (ctc/stroke->color stroke) :index index :title title :on-change on-color-change-refactor diff --git a/frontend/src/app/main/ui/workspace/top_toolbar.cljs b/frontend/src/app/main/ui/workspace/top_toolbar.cljs index 0532008ff..a904761f6 100644 --- a/frontend/src/app/main/ui/workspace/top_toolbar.cljs +++ b/frontend/src/app/main/ui/workspace/top_toolbar.cljs @@ -9,7 +9,6 @@ (:require [app.common.data.macros :as dm] [app.common.geom.point :as gpt] - [app.common.media :as cm] [app.main.data.event :as ev] [app.main.data.modal :as modal] [app.main.data.workspace :as dw] @@ -64,7 +63,7 @@ i/img [:& file-uploader {:input-id "image-upload" - :accept cm/str-image-types + :accept dwm/accept-image-types :multi true :ref ref :on-selected on-selected}]]])) diff --git a/frontend/src/app/main/ui/workspace/viewport/gradients.cljs b/frontend/src/app/main/ui/workspace/viewport/gradients.cljs index 3893216da..2d75db945 100644 --- a/frontend/src/app/main/ui/workspace/viewport/gradients.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/gradients.cljs @@ -15,7 +15,7 @@ [app.common.geom.shapes :as gsh] [app.common.geom.shapes.points :as gsp] [app.common.math :as mth] - [app.common.types.shape :as shp] + [app.common.types.fill :as types.fill] [app.config :as cfg] [app.main.data.workspace.colors :as dc] [app.main.features :as features] @@ -135,7 +135,7 @@ handler-state (mf/use-state {:display? false :offset 0 :hover nil}) cap-stops? (or (features/use-feature "render-wasm/v1") (contains? cfg/flags :frontend-binary-fills)) - can-add-stop? (if cap-stops? (< (count stops) shp/MAX-GRADIENT-STOPS) true) + can-add-stop? (if cap-stops? (< (count stops) types.fill/MAX-GRADIENT-STOPS) true) endpoint-on-pointer-down (fn [position event] @@ -527,7 +527,7 @@ gradient (:gradient state) cap-stops? (or (features/use-feature "render-wasm/v1") (contains? cfg/flags :frontend-binary-fills)) stops (if cap-stops? - (vec (take shp/MAX-GRADIENT-STOPS (:stops state))) + (vec (take types.fill/MAX-GRADIENT-STOPS (:stops state))) (:stops state)) editing-stop (:editing-stop state)] diff --git a/frontend/src/app/plugins/api.cljs b/frontend/src/app/plugins/api.cljs index 2e381ce4d..5845994af 100644 --- a/frontend/src/app/plugins/api.cljs +++ b/frontend/src/app/plugins/api.cljs @@ -176,10 +176,10 @@ (or (not (array? shapes)) (not (every? shape/shape-proxy? shapes))) (u/display-not-valid :replaceColor-shapes shapes) - (not (sm/validate ::ctc/color old-color)) + (not (sm/validate ctc/schema:color old-color)) (u/display-not-valid :replaceColor-oldColor old-color) - (not (sm/validate ::ctc/color new-color)) + (not (sm/validate ctc/schema:color new-color)) (u/display-not-valid :replaceColor-newColor new-color) :else diff --git a/frontend/src/app/plugins/format.cljs b/frontend/src/app/plugins/format.cljs index fdd1c7be9..0ea383240 100644 --- a/frontend/src/app/plugins/format.cljs +++ b/frontend/src/app/plugins/format.cljs @@ -139,6 +139,8 @@ :path path :color color :opacity opacity + :refId (format-id ref-id) + :refFile (format-id ref-file) :gradient (format-gradient gradient) :image (format-image image)})))) @@ -188,6 +190,7 @@ ;; fillColorRefId?: string; ;; fillImage?: ImageData; ;;} + (defn format-fill [{:keys [fill-color fill-opacity fill-color-gradient fill-color-ref-file fill-color-ref-id fill-image] :as fill}] (when (some? fill) diff --git a/frontend/src/app/plugins/library.cljs b/frontend/src/app/plugins/library.cljs index 068b7b516..43e3d7ecd 100644 --- a/frontend/src/app/plugins/library.cljs +++ b/frontend/src/app/plugins/library.cljs @@ -126,7 +126,7 @@ (fn [self value] (let [value (parser/parse-gradient value)] (cond - (not (sm/validate ::ctc/gradient value)) + (not (sm/validate ctc/schema:gradient value)) (u/display-not-valid :gradient value) (not (r/check-permission plugin-id "library:write")) @@ -144,7 +144,7 @@ (fn [self value] (let [value (parser/parse-image-data value)] (cond - (not (sm/validate ::ctc/image-color value)) + (not (sm/validate ctc/schema:image value)) (u/display-not-valid :image value) (not (r/check-permission plugin-id "library:write")) diff --git a/frontend/src/app/plugins/shape.cljs b/frontend/src/app/plugins/shape.cljs index 64194e04c..c1dc10c8f 100644 --- a/frontend/src/app/plugins/shape.cljs +++ b/frontend/src/app/plugins/shape.cljs @@ -20,6 +20,7 @@ [app.common.types.component :as ctk] [app.common.types.container :as ctn] [app.common.types.file :as ctf] + [app.common.types.fill :as types.fill] [app.common.types.grid :as ctg] [app.common.types.path :as path] [app.common.types.path.segment :as path.segm] @@ -708,7 +709,7 @@ id (:id shape) value (parser/parse-fills value)] (cond - (not (sm/validate [:vector ::cts/fill] value)) + (not (sm/validate [:vector types.fill/schema:fill] value)) (u/display-not-valid :fills value) (cfh/text-shape? shape) @@ -728,7 +729,7 @@ (let [id (obj/get self "$id") value (parser/parse-strokes value)] (cond - (not (sm/validate [:vector ::cts/stroke] value)) + (not (sm/validate [:vector cts/schema:stroke] value)) (u/display-not-valid :strokes value) (not (r/check-permission plugin-id "content:write")) diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index a4e02bee9..8be51712a 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -12,8 +12,8 @@ [app.common.data.macros :as dm] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] + [app.common.types.fill :as types.fill] [app.common.types.path :as path] - [app.common.types.shape :as shp] [app.common.types.shape.layout :as ctl] [app.common.uuid :as uuid] [app.config :as cf] @@ -240,7 +240,7 @@ (defn set-shape-fills [fills] - (let [fills (take shp/MAX-FILLS fills) + (let [fills (take types.fill/MAX-FILLS fills) image-fills (filter :fill-image fills) offset (mem/alloc-bytes (* (count fills) sr-fills/FILL-BYTE-SIZE)) heap (mem/get-heap-u8) @@ -302,7 +302,9 @@ (let [id (dm/get-prop image :id) buffer (uuid/get-u32 id) cached-image? (h/call wasm/internal-module "_is_image_cached" (aget buffer 0) (aget buffer 1) (aget buffer 2) (aget buffer 3))] - (sr-fills/write-image-fill! offset dview id opacity (dm/get-prop image :width) (dm/get-prop image :height)) + (sr-fills/write-image-fill! offset dview id opacity + (dm/get-prop image :width) + (dm/get-prop image :height)) (h/call wasm/internal-module "_add_shape_stroke_fill") (when (== cached-image? 0) (store-image id))) diff --git a/frontend/src/app/render_wasm/serializers/fills.cljs b/frontend/src/app/render_wasm/serializers/fills.cljs index 404e29e22..b39e4a0db 100644 --- a/frontend/src/app/render_wasm/serializers/fills.cljs +++ b/frontend/src/app/render_wasm/serializers/fills.cljs @@ -1,7 +1,7 @@ (ns app.render-wasm.serializers.fills (:require [app.common.data.macros :as dm] - [app.common.types.shape :as shp] + [app.common.types.fill :as types.fill] [app.common.uuid :as uuid] [app.render-wasm.serializers.color :as clr])) @@ -41,7 +41,7 @@ end-x (:end-x gradient) end-y (:end-y gradient) width (or (:width gradient) 0) - stops (take shp/MAX-GRADIENT-STOPS (:stops gradient)) + stops (take types.fill/MAX-GRADIENT-STOPS (:stops gradient)) type (if (= (:type gradient) :linear) 0x01 0x02)] (.setUint8 dview offset type true) (.setFloat32 dview (+ offset 4) start-x true) @@ -78,4 +78,4 @@ (some? image) (let [id (dm/get-prop image :id)] - (write-image-fill! offset dview id opacity (dm/get-prop image :width) (dm/get-prop image :height)))))) \ No newline at end of file + (write-image-fill! offset dview id opacity (dm/get-prop image :width) (dm/get-prop image :height)))))) diff --git a/frontend/src/app/util/color.cljs b/frontend/src/app/util/color.cljs index b7a638c61..e44fbad2f 100644 --- a/frontend/src/app/util/color.cljs +++ b/frontend/src/app/util/color.cljs @@ -82,7 +82,7 @@ (defn get-color-name [color] - (or (:color-library-name color) + (or (:name (meta color)) (:name color) (:color color) (gradient-type->string (:type (:gradient color))))) diff --git a/frontend/src/app/util/storage.cljs b/frontend/src/app/util/storage.cljs index b7b604c14..3cc29e39b 100644 --- a/frontend/src/app/util/storage.cljs +++ b/frontend/src/app/util/storage.cljs @@ -80,7 +80,7 @@ (fn [key val] (when (some? backend) (if (some? val) - (.setItem ^js backend (encode-key prefix key) (t/encode-str val)) + (.setItem ^js backend (encode-key prefix key) (t/encode-str val {:with-meta true})) (.removeItem ^js backend (encode-key prefix key))))) on-change* diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs index 49a1fcd81..e9c235be4 100644 --- a/frontend/src/app/worker/import.cljs +++ b/frontend/src/app/worker/import.cljs @@ -7,11 +7,9 @@ (ns app.worker.import (:refer-clojure :exclude [resolve]) (:require - [app.common.data :as d] [app.common.json :as json] [app.common.logging :as log] [app.common.schema :as sm] - [app.common.text :as ct] [app.common.uuid :as uuid] [app.main.repo :as rp] [app.util.http :as http] @@ -44,51 +42,6 @@ :method :get}) (rx/map :body)))) -(defn resolve-text-content - [node context] - (let [resolve (:resolve context)] - (->> node - (ct/transform-nodes - (fn [item] - (cond-> item - (uuid? (get item :fill-color-ref-id)) - (d/update-when :fill-color-ref-id resolve) - - (uuid? (get item :fill-color-ref-file)) - (d/update-when :fill-color-ref-file resolve) - - (uuid? (get item :typography-ref-id)) - (d/update-when :typography-ref-id resolve) - - (uuid? (get item :typography-ref-file)) - (d/update-when :typography-ref-file resolve))))))) - -(defn resolve-fills-content - [fills context] - (let [resolve (:resolve context)] - (->> fills - (mapv - (fn [fill] - (cond-> fill - (uuid? (get fill :fill-color-ref-id)) - (d/update-when :fill-color-ref-id resolve) - - (uuid? (get fill :fill-color-ref-file)) - (d/update-when :fill-color-ref-file resolve))))))) - -(defn resolve-strokes-content - [fills context] - (let [resolve (:resolve context)] - (->> fills - (mapv - (fn [fill] - (cond-> fill - (uuid? (get fill :stroke-color-ref-id)) - (d/update-when :stroke-color-ref-id resolve) - - (uuid? (get fill :stroke-color-ref-file)) - (d/update-when :stroke-color-ref-file resolve))))))) - (defn parse-mtype [ba] (let [u8 (js/Uint8Array. ba 0 4) sg (areduce u8 i ret "" (str ret (if (zero? i) "" " ") (.toString (aget u8 i) 8)))] diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 9867e0099..6aaba64b4 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -3768,14 +3768,7 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.30001669": - version: 1.0.30001677 - resolution: "caniuse-lite@npm:1.0.30001677" - checksum: 10c0/22b4aa738b213b5d0bc820c26ba23fa265ca90a5c59776e1a686b9ab6fff9120d0825fd920c0a601a4b65056ef40d01548405feb95c8dd6083255f50c71a0864 - languageName: node - linkType: hard - -"caniuse-lite@npm:^1.0.30001702, caniuse-lite@npm:^1.0.30001716": +"caniuse-lite@npm:^1.0.30001669, caniuse-lite@npm:^1.0.30001702, caniuse-lite@npm:^1.0.30001716": version: 1.0.30001718 resolution: "caniuse-lite@npm:1.0.30001718" checksum: 10c0/67f9ad09bc16443e28d14f265d6e468480cd8dc1900d0d8b982222de80c699c4f2306599c3da8a3fa7139f110d4b30d49dbac78f215470f479abb6ffe141d5d3 @@ -5989,7 +5982,7 @@ __metadata: sass: "npm:^1.89.0" sass-embedded: "npm:^1.89.0" sax: "npm:^1.4.1" - shadow-cljs: "npm:3.0.5" + shadow-cljs: "npm:3.1.5" source-map-support: "npm:^0.5.21" storybook: "npm:^8.6.14" style-dictionary: "npm:5.0.0-rc.1" @@ -10793,10 +10786,12 @@ __metadata: languageName: node linkType: hard -"shadow-cljs@npm:3.0.5": - version: 3.0.5 - resolution: "shadow-cljs@npm:3.0.5" +"shadow-cljs@npm:3.1.5": + version: 3.1.5 + resolution: "shadow-cljs@npm:3.1.5" dependencies: + buffer: "npm:^6.0.3" + process: "npm:^0.11.10" readline-sync: "npm:^1.4.10" shadow-cljs-jar: "npm:1.3.4" source-map-support: "npm:^0.5.21" @@ -10804,7 +10799,7 @@ __metadata: ws: "npm:^8.18.1" bin: shadow-cljs: cli/runner.js - checksum: 10c0/2c5f3976f7bec16b7fb9fbba5d4a7581e0d0157384a470ce0670120f02cfe6b9c7183102133e0e9b300cbe318e9a3b6001309f96840999ca2814d39fc83c23e8 + checksum: 10c0/29da68f7645c258becf4074e4401e5c86dd3af04622c2e10fdac09824e9832290918d90aaf80ef7df0c35731f1b51b84101cbfd0c6819772a493173d4ae69415 languageName: node linkType: hard