Merge pull request #6569 from penpot/niwinz-fills-cleanup

♻️ Clean and sanitize color types
This commit is contained in:
Alejandro Alonso 2025-06-04 14:26:12 +02:00 committed by GitHub
commit 687e1e7b0b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
76 changed files with 2009 additions and 984 deletions

1
.gitignore vendored
View file

@ -30,6 +30,7 @@
/*.zip /*.zip
/.clj-kondo/.cache /.clj-kondo/.cache
/_dump /_dump
/notes
/backend/*.md /backend/*.md
/backend/*.sql /backend/*.sql
/backend/*.txt /backend/*.txt

View file

@ -19,6 +19,7 @@
io.lettuce/lettuce-core {:mvn/version "6.6.0.RELEASE"} io.lettuce/lettuce-core {:mvn/version "6.6.0.RELEASE"}
java-http-clj/java-http-clj {:mvn/version "0.4.3"} java-http-clj/java-http-clj {:mvn/version "0.4.3"}
com.google.guava/guava {:mvn/version "33.4.8-jre"}
funcool/yetti funcool/yetti
{:git/tag "v11.4" {:git/tag "v11.4"

View file

@ -16,16 +16,40 @@
;; PRE DECODE ;; 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 (defn clean-shape-pre-decode
"Applies a pre-decode phase migration to the shape" "Applies a pre-decode phase migration to the shape"
[shape] [shape]
(if (= "bool" (:type shape)) (cond-> shape
(if-let [content (get shape :bool-content)] (= "bool" (:type shape))
(-> shape (pre-clean-bool-content)
(assoc :content content)
(dissoc :bool-content)) (contains? shape :shadow)
shape) (pre-clean-shadow-color)))
shape))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; POST DECODE ;; POST DECODE

View file

@ -113,7 +113,7 @@
(sm/encoder ::ctc/component sm/json-transformer)) (sm/encoder ::ctc/component sm/json-transformer))
(def encode-color (def encode-color
(sm/encoder ::ctcl/color sm/json-transformer)) (sm/encoder ctcl/schema:library-color sm/json-transformer))
(def encode-typography (def encode-typography
(sm/encoder ::cty/typography sm/json-transformer)) (sm/encoder ::cty/typography sm/json-transformer))
@ -142,7 +142,7 @@
(sm/decoder ::ctc/component sm/json-transformer)) (sm/decoder ::ctc/component sm/json-transformer))
(def decode-color (def decode-color
(sm/decoder ::ctcl/color sm/json-transformer)) (sm/decoder ctcl/schema:library-color sm/json-transformer))
(def decode-file (def decode-file
(sm/decoder schema:file sm/json-transformer)) (sm/decoder schema:file sm/json-transformer))
@ -157,7 +157,7 @@
(sm/decoder ::cty/typography sm/json-transformer)) (sm/decoder ::cty/typography sm/json-transformer))
(def decode-tokens-lib (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 (def decode-plugin-data
(sm/decoder ::ctpg/plugin-data sm/json-transformer)) (sm/decoder ::ctpg/plugin-data sm/json-transformer))
@ -186,7 +186,7 @@
(sm/check-fn ::ctf/media)) (sm/check-fn ::ctf/media))
(def validate-color (def validate-color
(sm/check-fn ::ctcl/color)) (sm/check-fn ctcl/schema:library-color))
(def validate-component (def validate-component
(sm/check-fn ::ctc/component)) (sm/check-fn ::ctc/component))
@ -617,8 +617,7 @@
(let [object (->> (read-entry input entry) (let [object (->> (read-entry input entry)
(clean-component-pre-decode) (clean-component-pre-decode)
(decode-component) (decode-component)
(clean-component-post-decode) (clean-component-post-decode))]
(validate-component))]
(if (= id (:id object)) (if (= id (:id object))
(assoc result id object) (assoc result id object)
result))) result)))
@ -652,8 +651,7 @@
(let [object (->> (read-entry input entry) (let [object (->> (read-entry input entry)
(bfl/clean-shape-pre-decode) (bfl/clean-shape-pre-decode)
(decode-shape) (decode-shape)
(bfl/clean-shape-post-decode) (bfl/clean-shape-post-decode))]
(validate-shape))]
(if (= id (:id object)) (if (= id (:id object))
(assoc result id object) (assoc result id object)
result))) result)))
@ -699,7 +697,6 @@
components (read-file-components cfg) components (read-file-components cfg)
plugin-data (read-file-plugin-data cfg) plugin-data (read-file-plugin-data cfg)
pages (read-file-pages cfg)] pages (read-file-pages cfg)]
{:pages (-> pages keys vec) {:pages (-> pages keys vec)
:pages-index (into {} pages) :pages-index (into {} pages)
:colors colors :colors colors
@ -756,7 +753,8 @@
(assoc :project-id project-id) (assoc :project-id project-id)
(dissoc :options)) (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) (bfm/register-pending-migrations! cfg file)
(bfc/save-file! cfg file ::db/return-keys false) (bfc/save-file! cfg file ::db/return-keys false)

View file

@ -55,7 +55,7 @@
(sm/check-fn schema:input)) (sm/check-fn schema:input))
(defn validate-media-type! (defn validate-media-type!
([upload] (validate-media-type! upload cm/valid-image-types)) ([upload] (validate-media-type! upload cm/image-types))
([upload allowed] ([upload allowed]
(when-not (contains? allowed (:mtype upload)) (when-not (contains? allowed (:mtype upload))
(ex/raise :type :validation (ex/raise :type :validation

View file

@ -343,8 +343,9 @@
:name "image" :name "image"
:frame-id uuid/zero :frame-id uuid/zero
:parent-id uuid/zero :parent-id uuid/zero
:type :image :type :rect
:metadata {:id (:id fmo1) :width 100 :height 100 :mtype "image/jpeg"}})}]) :fills [{:fill-opacity 1
:fill-image {:id (:id fmo1) :width 100 :height 100 :mtype "image/jpeg"}}]})}])
;; Check that reference storage objects on filemediaobjects ;; Check that reference storage objects on filemediaobjects
;; are the same because of deduplication feature. ;; are the same because of deduplication feature.
@ -462,7 +463,8 @@
fmo3 (add-file-media-object :profile-id (:id profile) :file-id (:id file)) 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)) 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)) 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) t-shid (uuid/random)
page-id (first (get-in file [:data :pages]))] page-id (first (get-in file [:data :pages]))]
@ -481,19 +483,31 @@
:changes :changes
[{:type :add-obj [{:type :add-obj
:page-id page-id :page-id page-id
:id s-shid :id s1-shid
:parent-id uuid/zero :parent-id uuid/zero
:frame-id uuid/zero :frame-id uuid/zero
:components-v2 true :components-v2 true
:obj (cts/setup-shape :obj (cts/setup-shape
{:id s-shid {:id s1-shid
:name "image" :name "image"
:frame-id uuid/zero :frame-id uuid/zero
:parent-id uuid/zero :parent-id uuid/zero
:type :image :type :rect
:metadata {:id (:id fmo1) :width 100 :height 100 :mtype "image/jpeg"} :fills [{:fill-opacity 1 :fill-image {:id (:id fmo2) :width 101 :height 100 :mtype "image/jpeg"}}]
:fills [{:opacity 1 :fill-image {:id (:id fmo2) :width 100 :height 100 :mtype "image/jpeg"}}] :strokes [{:stroke-opacity 1 :stroke-image {:id (:id fmo3) :width 102 :height 100 :mtype "image/jpeg"}}]})}
:strokes [{:opacity 1 :stroke-image {:id (:id fmo3) :width 100 :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 {:type :add-obj
:page-id page-id :page-id page-id
:id t-shid :id t-shid
@ -519,7 +533,8 @@
{:fills [{:fill-opacity 1 {:fills [{:fill-opacity 1
:fill-color "#000000"}] :fill-color "#000000"}]
:text "bye"}]}]}]} :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 ;; run the file-gc task immediately without forced min-age
(t/is (false? (th/run-task! :file-gc {:file-id (:id file)}))) (t/is (false? (th/run-task! :file-gc {:file-id (:id file)})))
@ -557,10 +572,13 @@
:vern 0 :vern 0
:changes [{:type :del-obj :changes [{:type :del-obj
:page-id (first (get-in file [:data :pages])) :page-id (first (get-in file [:data :pages]))
:id s-shid} :id s1-shid}
{:type :del-obj {:type :del-obj
:page-id (first (get-in file [:data :pages])) :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 ;; Now, we have deleted the usage of pointers to the
;; file-media-objects, if we paste file-gc, they should be marked ;; file-media-objects, if we paste file-gc, they should be marked

View file

@ -2,9 +2,9 @@
{org.clojure/clojure {:mvn/version "1.12.0"} {org.clojure/clojure {:mvn/version "1.12.0"}
org.clojure/data.json {:mvn/version "2.5.1"} org.clojure/data.json {:mvn/version "2.5.1"}
org.clojure/tools.cli {:mvn/version "1.1.230"} 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/test.check {:mvn/version "1.1.1"}
org.clojure/data.fressian {:mvn/version "1.1.0"} org.clojure/data.fressian {:mvn/version "1.1.0"}
org.clojure/clojurescript {:mvn/version "1.12.42"}
;; Logging ;; Logging
org.apache.logging.log4j/log4j-api {:mvn/version "2.24.3"} org.apache.logging.log4j/log4j-api {:mvn/version "2.24.3"}
@ -59,7 +59,7 @@
{:dev {:dev
{:extra-deps {:extra-deps
{org.clojure/tools.namespace {:mvn/version "RELEASE"} {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.clojure-goes-fast/clj-async-profiler {:mvn/version "RELEASE"}
com.bhauman/rebel-readline {:mvn/version "RELEASE"} com.bhauman/rebel-readline {:mvn/version "RELEASE"}
criterium/criterium {:mvn/version "RELEASE"} criterium/criterium {:mvn/version "RELEASE"}

View file

@ -16,7 +16,7 @@
"devDependencies": { "devDependencies": {
"concurrently": "^9.0.1", "concurrently": "^9.0.1",
"nodemon": "^3.1.7", "nodemon": "^3.1.7",
"shadow-cljs": "3.0.5", "shadow-cljs": "3.1.5",
"source-map-support": "^0.5.21", "source-map-support": "^0.5.21",
"ws": "^8.17.0" "ws": "^8.17.0"
}, },

View file

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

View file

@ -33,6 +33,10 @@
(def boolean-or-nil? (def boolean-or-nil?
(some-fn nil? boolean?)) (some-fn nil? boolean?))
(defn in-range?
[size i]
(and (< i size) (>= i 0)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Commonly used transducers ;; Commonly used transducers
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View file

@ -16,7 +16,6 @@
[app.common.schema.desc-native :as smd] [app.common.schema.desc-native :as smd]
[app.common.schema.generators :as sg] [app.common.schema.generators :as sg]
[app.common.types.color :as ctc] [app.common.types.color :as ctc]
[app.common.types.colors-list :as ctcl]
[app.common.types.component :as ctk] [app.common.types.component :as ctk]
[app.common.types.components-list :as ctkl] [app.common.types.components-list :as ctkl]
[app.common.types.container :as ctn] [app.common.types.container :as ctn]
@ -266,7 +265,7 @@
[:id ::sm/uuid] [:id ::sm/uuid]
;; All props are optional, background can be nil because is the ;; All props are optional, background can be nil because is the
;; way to remove already set background ;; 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]]] [:name {:optional true} :string]]]
[:set-plugin-data schema:set-plugin-data-change] [:set-plugin-data schema:set-plugin-data-change]
@ -292,12 +291,12 @@
[:add-color [:add-color
[:map {:title "AddColorChange"} [:map {:title "AddColorChange"}
[:type [:= :add-color]] [:type [:= :add-color]]
[:color ::ctc/color]]] [:color ctc/schema:library-color]]]
[:mod-color [:mod-color
[:map {:title "ModColorChange"} [:map {:title "ModColorChange"}
[:type [:= :mod-color]] [:type [:= :mod-color]]
[:color ::ctc/color]]] [:color ctc/schema:library-color]]]
[:del-color [:del-color
[:map {:title "DelColorChange"} [:map {:title "DelColorChange"}
@ -926,15 +925,15 @@
(defmethod process-change :add-color (defmethod process-change :add-color
[data {:keys [color]}] [data {:keys [color]}]
(ctcl/add-color data color)) (ctc/add-color data color))
(defmethod process-change :mod-color (defmethod process-change :mod-color
[data {:keys [color]}] [data {:keys [color]}]
(ctcl/set-color data color)) (ctc/set-color data color))
(defmethod process-change :del-color (defmethod process-change :del-color
[data {:keys [id]}] [data {:keys [id]}]
(ctcl/delete-color data id)) (ctc/delete-color data id))
;; DEPRECATED: remove before 2.3 ;; DEPRECATED: remove before 2.3
(defmethod process-change :add-recent-color (defmethod process-change :add-recent-color

View file

@ -22,10 +22,11 @@
[app.common.schema :as sm] [app.common.schema :as sm]
[app.common.svg :as csvg] [app.common.svg :as csvg]
[app.common.text :as txt] [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.component :as ctk]
[app.common.types.container :as ctn] [app.common.types.container :as ctn]
[app.common.types.file :as ctf] [app.common.types.file :as ctf]
[app.common.types.fill :as types.fill]
[app.common.types.path :as path] [app.common.types.path :as path]
[app.common.types.path.segment :as path.segment] [app.common.types.path.segment :as path.segment]
[app.common.types.shape :as cts] [app.common.types.shape :as cts]
@ -826,7 +827,7 @@
(d/update-when :components d/update-vals update-container)))) (d/update-when :components d/update-vals update-container))))
(def ^:private valid-fill? (def ^:private valid-fill?
(sm/lazy-validator ::cts/fill)) (sm/lazy-validator types.fill/schema:fill))
(defmethod migrate-data "legacy-43" (defmethod migrate-data "legacy-43"
[data _] [data _]
@ -1004,14 +1005,11 @@
(update :pages-index d/update-vals update-container) (update :pages-index d/update-vals update-container)
(d/update-when :components 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" (defmethod migrate-data "legacy-51"
[data _] [data _]
(let [update-colors (let [update-colors
(fn [colors] (fn [colors]
(into {} (filter #(-> % val valid-color?) colors)))] (into {} (filter #(-> % val types.color/valid-color?) colors)))]
(update data :colors update-colors))) (update data :colors update-colors)))
(defmethod migrate-data "legacy-52" (defmethod migrate-data "legacy-52"
@ -1328,7 +1326,6 @@
(update :pages-index d/update-vals update-container) (update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container)))) (d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "0004-add-partial-text-touched-flags" (defmethod migrate-data "0004-add-partial-text-touched-flags"
[data _] [data _]
(letfn [(update-object [page object] (letfn [(update-object [page object]
@ -1354,6 +1351,33 @@
(update data :pages-index d/update-vals update-page))) (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" (defmethod migrate-data "0005-deprecate-image-type"
[data _] [data _]
(letfn [(update-object [object] (letfn [(update-object [object]
@ -1369,7 +1393,7 @@
object)) object))
(update-container [container] (update-container [container]
(d/update-when container :objects update-vals update-object))] (d/update-when container :objects d/update-vals update-object))]
(-> data (-> data
(update :pages-index d/update-vals update-container) (update :pages-index d/update-vals update-container)
@ -1378,17 +1402,12 @@
(defmethod migrate-data "0006-fix-old-texts-fills" (defmethod migrate-data "0006-fix-old-texts-fills"
[data _] [data _]
(letfn [(fix-fills [node] (letfn [(fix-fills [node]
(let [fills (cond (let [fills (if (and (not (seq (:fills node)))
(or (some? (:fill-color node)) (or (some? (:fill-color node))
(some? (:fill-opacity node)) (some? (:fill-opacity node))
(some? (:fill-color-gradient node))) (some? (:fill-color-gradient node))))
[(d/without-nils (select-keys node [:fill-color :fill-opacity :fill-color-gradient [(d/without-nils (select-keys node [:fill-color :fill-opacity :fill-color-gradient
:fill-color-ref-id :fill-color-ref-file]))] :fill-color-ref-id :fill-color-ref-file]))]
(nil? (:fills node))
[{:fill-color "#000000" :fill-opacity 1}]
:else
(:fills node))] (:fills node))]
(-> node (-> node
(assoc :fills fills) (assoc :fills fills)
@ -1407,6 +1426,77 @@
(update :pages-index d/update-vals update-container) (update :pages-index d/update-vals update-container)
(d/update-when :components 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 (def available-migrations
(into (d/ordered-set) (into (d/ordered-set)
["legacy-2" ["legacy-2"
@ -1467,5 +1557,8 @@
"0003-fix-root-shape" "0003-fix-root-shape"
"0003-convert-path-content" "0003-convert-path-content"
"0004-add-partial-text-touched-flags" "0004-add-partial-text-touched-flags"
"0004-clean-shadow-and-colors"
"0005-deprecate-image-type" "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"]))

View file

@ -9,11 +9,18 @@
(:require (:require
[cuerdas.core :as str])) [cuerdas.core :as str]))
;; We have added ".ttf" as string to solve a problem with chrome input selector (def font-types
(def valid-font-types #{"font/ttf" ".ttf" "font/woff", "application/font-woff" "woff" "font/otf" ".otf" "font/opentype"}) #{"font/ttf"
(def valid-image-types #{"image/jpeg", "image/png", "image/webp", "image/gif", "image/svg+xml"}) "font/woff"
(def str-image-types (str/join "," valid-image-types)) "font/otf"
(def str-font-types (str/join "," valid-font-types)) "font/opentype"})
(def image-types
#{"image/jpeg"
"image/png"
"image/webp"
"image/gif"
"image/svg+xml"})
(defn format->extension (defn format->extension
[format] [format]

View file

@ -5,7 +5,7 @@
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns app.common.schema (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]])) #?(:cljs (:require-macros [app.common.schema :refer [ignoring]]))
(:require (:require
[app.common.data :as d] [app.common.data :as d]
@ -118,6 +118,21 @@
[& transformers] [& transformers]
(apply mt/transformer 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 ;; (defn key-transformer
;; [& {:as opts}] ;; [& {:as opts}]
;; (mt/key-transformer opts)) ;; (mt/key-transformer opts))
@ -702,7 +717,10 @@
(fn [v] (fn [v]
(and (pred v) (and (pred v)
(>= max v))) (>= max v)))
pred)] pred)
gen (or (get props :gen/gen)
(sg/small-int :max max :min min))]
{:pred pred {:pred pred
:type-properties :type-properties
@ -710,7 +728,7 @@
:description "int" :description "int"
:error/message "expected to be int/long" :error/message "expected to be int/long"
:error/code "errors.invalid-integer" :error/code "errors.invalid-integer"
:gen/gen (sg/small-int :max max :min min) :gen/gen gen
:decode/string parse-long :decode/string parse-long
:decode/json parse-long :decode/json parse-long
::oapi/type "integer" ::oapi/type "integer"
@ -768,10 +786,11 @@
(>= max v))) (>= max v)))
pred) pred)
gen (sg/one-of gen (or (get props :gen/gen)
(sg/small-int :max max :min min) (sg/one-of
(->> (sg/small-double :max max :min min) (sg/small-int :max max :min min)
(sg/fmap #(mth/precision % 2))))] (->> (sg/small-double :max max :min min)
(sg/fmap #(mth/precision % 2)))))]
{:pred pred {:pred pred
:type-properties :type-properties
@ -786,7 +805,9 @@
(register! ::safe-int [::int {:max max-safe-int :min min-safe-int}]) (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-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 (defn parse-boolean
[v] [v]

View file

@ -8,6 +8,7 @@
(:refer-clojure :exclude [set subseq uuid filter map let boolean vector keyword int double]) (:refer-clojure :exclude [set subseq uuid filter map let boolean vector keyword int double])
#?(:cljs (:require-macros [app.common.schema.generators])) #?(:cljs (:require-macros [app.common.schema.generators]))
(:require (:require
[app.common.math :as mth]
[app.common.schema.registry :as sr] [app.common.schema.registry :as sr]
[app.common.uri :as u] [app.common.uri :as u]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
@ -40,7 +41,8 @@
(defn small-double (defn small-double
[& {:keys [min max] :or {min -100 max 100}}] [& {: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 (defn small-int
[& {:keys [min max] :or {min -100 max 100}}] [& {:keys [min max] :or {min -100 max 100}}]

View file

@ -7,11 +7,11 @@
(ns app.common.test-helpers.shapes (ns app.common.test-helpers.shapes
(:require (:require
[app.common.colors :as clr] [app.common.colors :as clr]
[app.common.data :as d]
[app.common.files.helpers :as cfh] [app.common.files.helpers :as cfh]
[app.common.test-helpers.files :as thf] [app.common.test-helpers.files :as thf]
[app.common.test-helpers.ids-map :as thi] [app.common.test-helpers.ids-map :as thi]
[app.common.types.color :as ctc] [app.common.types.color :as ctc]
[app.common.types.colors-list :as ctcl]
[app.common.types.container :as ctn] [app.common.types.container :as ctn]
[app.common.types.pages-list :as ctpl] [app.common.types.pages-list :as ctpl]
[app.common.types.shape :as cts] [app.common.types.shape :as cts]
@ -81,9 +81,16 @@
(:id page) (:id page)
#(ctst/set-shape % (ctn/set-shape-attr shape attr val))))))) #(ctst/set-shape % (ctn/set-shape-attr shape attr val)))))))
(defn sample-color (defn sample-library-color
[label & {:keys [] :as params}] [label & {:keys [name path color opacity gradient image]}]
(ctc/make-color (assoc params :id (thi/new-id! label)))) (-> {: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 (defn sample-fill-color
[& {:keys [fill-color fill-opacity] :as params}] [& {:keys [fill-color fill-opacity] :as params}]
@ -101,8 +108,8 @@
(defn add-sample-library-color (defn add-sample-library-color
[file label & {:keys [] :as params}] [file label & {:keys [] :as params}]
(let [color (sample-color label params)] (let [color (sample-library-color label params)]
(update file :data ctcl/add-color color))) (update file :data ctc/add-color color)))
(defn sample-typography (defn sample-typography
[label & {:keys [] :as params}] [label & {:keys [] :as params}]

View file

@ -121,30 +121,6 @@
{:name "Source Sans Pro Regular"} {:name "Source Sans Pro Regular"}
(select-keys default-text-attrs typography-fields))) (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 (defn node-seq
([root] (node-seq identity root)) ([root] (node-seq identity root))
([match? root] ([match? root]
@ -165,6 +141,34 @@
[node] [node]
(= "root" (:type 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 (defn generate-shape-name
[text] [text]
(subs text 0 (min 280 (count text)))) (subs text 0 (min 280 (count text))))

View file

@ -208,9 +208,13 @@
([data] (encode-str data nil)) ([data] (encode-str data nil))
([data opts] ([data opts]
#?(:cljs #?(:cljs
(let [t (:type opts :json) (let [type (:type opts :json)
w (t/writer t {:handlers @write-handler-map})] params {:handlers @write-handler-map}
(t/write w data)) params (if (:with-meta opts)
(assoc params :transform t/write-meta)
params)
writer (t/writer type params)]
(t/write writer data))
:clj :clj
(->> (encode data opts) (->> (encode data opts)
(bytes->str))))) (bytes->str)))))
@ -219,9 +223,10 @@
([data] (decode-str data nil)) ([data] (decode-str data nil))
([data opts] ([data opts]
#?(:cljs #?(:cljs
(let [t (:type opts :json) (let [type (:type opts :json)
r (t/reader t {:handlers @read-handler-map})] params {:handlers @read-handler-map}
(t/read r data)) reader (t/reader type params)]
(t/read reader data))
:clj :clj
(-> (str->bytes data) (-> (str->bytes data)
(decode opts))))) (decode opts)))))

View file

@ -8,23 +8,36 @@
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.media :as cm]
[app.common.schema :as sm] [app.common.schema :as sm]
[app.common.schema.generators :as sg] [app.common.schema.generators :as sg]
[app.common.schema.openapi :as-alias oapi] [app.common.schema.openapi :as-alias oapi]
[app.common.text :as txt] [app.common.text :as txt]
[app.common.time :as dt]
[app.common.types.plugins :as ctpg] [app.common.types.plugins :as ctpg]
[app.common.uuid :as uuid] [clojure.set :as set]
[cuerdas.core :as str])) [cuerdas.core :as str]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; SCHEMAS & TYPES ;; 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}$") #"^#(?:[0-9a-fA-F]{3}){1,2}$")
(defn- generate-rgb-color (def ^:private hex-color-generator
[]
(sg/fmap (fn [_] (sg/fmap (fn [_]
#?(:clj (format "#%06x" (rand-int 16rFFFFFF)) #?(:clj (format "#%06x" (rand-int 16rFFFFFF))
:cljs :cljs
@ -37,38 +50,48 @@
(.. b (toString 16) (padStart 2 "0")))))) (.. b (toString 16) (padStart 2 "0"))))))
sg/int)) sg/int))
(defn rgb-color-string? (defn hex-color-string?
[o] [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! (sm/register!
{:type ::rgb-color {:type ::hex-color
:pred rgb-color-string? :pred hex-color-string?
:type-properties :type-properties
{:title "rgb-color" {:title "hex-color"
:description "RGB Color String" :description "HEX Color String"
:error/message "expected a valid RGB color" :error/message "expected a valid HEX color"
:error/code "errors.invalid-rgb-color" :error/code "errors.invalid-hex-color"
:gen/gen (generate-rgb-color) :gen/gen hex-color-generator
::oapi/type "integer" ::oapi/type "integer"
::oapi/format "int64"}})) ::oapi/format "int64"}}))
(def schema:plain-color
[:map [:color schema:hex-color]])
(def schema:image (def schema:image
[:map {:title "ImageColor"} [:map {:title "ImageColor" :closed true}
[:width ::sm/int] [:width [::sm/int {:min 0 :gen/gen sg/int}]]
[:height ::sm/int] [:height [::sm/int {:min 0 :gen/gen sg/int}]]
[:mtype ::sm/text] [:mtype {:gen/gen (sg/elements cm/image-types)} ::sm/text]
[:id ::sm/uuid] [:id ::sm/uuid]
[:name {:optional true} ::sm/text] [:name {:optional true} ::sm/text]
[:keep-aspect-ratio {:optional true} :boolean]]) [: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 (def gradient-types
#{:linear :radial}) #{:linear :radial})
(def schema:gradient (def schema:gradient
[:map {:title "Gradient"} [:map {:title "Gradient" :closed true}
[:type [::sm/one-of #{:linear :radial}]] [:type [::sm/one-of gradient-types]]
[:start-x ::sm/safe-number] [:start-x ::sm/safe-number]
[:start-y ::sm/safe-number] [:start-y ::sm/safe-number]
[:end-x ::sm/safe-number] [:end-x ::sm/safe-number]
@ -77,86 +100,83 @@
[:stops [:stops
[:vector {:min 1 :gen/max 2} [:vector {:min 1 :gen/max 2}
[:map {:title "GradientStop"} [:map {:title "GradientStop"}
[:color schema:rgb-color] [:color schema:hex-color]
[:opacity {:optional true} [:maybe ::sm/safe-number]] [:opacity {:optional true} [::sm/number {:min 0 :max 1}]]
[:offset ::sm/safe-number]]]]]) [: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 (def schema:color-attrs
[:map {:title "ColorAttrs"} [:map {:title "ColorAttrs" :closed true}
[:id {:optional true} ::sm/uuid] [:opacity {:optional true} [::sm/number {:min 0 :max 1}]]
[: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]
[:ref-id {:optional true} ::sm/uuid] [:ref-id {:optional true} ::sm/uuid]
[:ref-file {: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]])
(def schema:color (def schema:color
[:and schema:color-attrs
[::sm/contains-any {:strict true} [:color :gradient :image]]])
(def schema:recent-color
[:and [:and
[:map {:title "RecentColor"} [:merge {:title "Color"}
[:opacity {:optional true} [:maybe ::sm/safe-number]] schema:color-attrs
[:color {:optional true} [:maybe schema:rgb-color]] (sm/optional-keys schema:plain-color)
[:gradient {:optional true} [:maybe schema:gradient]] (sm/optional-keys schema:gradient-color)
[:image {:optional true} [:maybe schema:image]]] (sm/optional-keys schema:image-color)]
[::sm/contains-any {:strict true} [:color :gradient :image]]]) [: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 (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 [:and
(sm/required-keys schema:color-attrs [:id]) [:merge
[::sm/contains-any {:strict true} [:color :gradient :image]]]) schema:library-color-attrs
(sm/optional-keys schema:plain-color)
;; FIXME: revisit if we really need this all registers (sm/optional-keys schema:gradient-color)
(sm/register! ::color schema:color) (sm/optional-keys schema:image-color)]
(sm/register! ::gradient schema:gradient) [:fn has-valid-color-attrs?]])
(sm/register! ::image-color schema:image)
(sm/register! ::recent-color schema:recent-color)
(sm/register! ::color-attrs schema:color-attrs)
(def valid-color? (def valid-color?
(sm/lazy-validator schema:color)) (sm/lazy-validator schema:color))
(def valid-library-color?
(sm/lazy-validator schema:library-color))
(def check-color (def check-color
(sm/check-fn schema:color :hint "expected valid color")) (sm/check-fn schema:color :hint "expected valid color"))
(def check-library-color (def check-library-color
(sm/check-fn schema:library-color :hint "expected valid library color")) (sm/check-fn schema:library-color :hint "expected valid color"))
(def check-recent-color
(sm/check-fn schema:recent-color :hint "expected valid recent color"))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; HELPERS ;; HELPERS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; --- factory (defn library-color->color
"Converts a library color data structure to a plain color data structure"
(defn make-color [lcolor file-id]
[{:keys [id name path value color opacity ref-id ref-file gradient image]}] (-> lcolor
(-> {:id (or id (uuid/next)) (select-keys [:image :gradient :color :opacity])
:name (or name color "Black") (assoc :ref-id (get lcolor :id))
:path path (assoc :ref-file file-id)
:value value (vary-meta assoc
:color (or color "#000000") :path (get lcolor :path)
:opacity (or opacity 1) :name (get lcolor :name))))
:ref-id ref-id
:ref-file ref-file
:gradient gradient
:image image}
(d/without-nils)))
;; --- fill ;; --- fill
(defn fill->shape-color (defn fill->color
[fill] [fill]
(d/without-nils (d/without-nils
{:color (:fill-color fill) {:color (:fill-color fill)
@ -178,126 +198,130 @@
(defn attach-fill-color (defn attach-fill-color
[shape position ref-id ref-file] [shape position ref-id ref-file]
(-> shape (d/update-in-when shape [:fills position]
(assoc-in [:fills position :fill-color-ref-id] ref-id) (fn [fill]
(assoc-in [:fills position :fill-color-ref-file] ref-file))) (-> fill
(assoc :fill-color-ref-file ref-file)
(assoc :fill-color-ref-id ref-id)))))
(defn detach-fill-color (defn detach-fill-color
[shape position] [shape position]
(-> shape (d/update-in-when shape [:fills position] dissoc :fill-color-ref-id :fill-color-ref-file))
(d/dissoc-in [:fills position :fill-color-ref-id])
(d/dissoc-in [:fills position :fill-color-ref-file])))
;; stroke ;; stroke
(defn stroke->shape-color (defn stroke->color
[stroke] [stroke]
(d/without-nils {:color (:stroke-color stroke) (d/without-nils
:opacity (:stroke-opacity stroke) {:color (str/lower (:stroke-color stroke))
:gradient (:stroke-color-gradient stroke) :opacity (:stroke-opacity stroke)
:image (:stroke-image stroke) :gradient (:stroke-color-gradient stroke)
:ref-id (:stroke-color-ref-id stroke) :image (:stroke-image stroke)
:ref-file (:stroke-color-ref-file stroke)})) :ref-id (:stroke-color-ref-id stroke)
:ref-file (:stroke-color-ref-file stroke)}))
(defn set-stroke-color (defn set-stroke-color
[shape position color opacity gradient image] [shape position color opacity gradient image]
(update-in shape [:strokes position] (d/update-in-when shape [:strokes position]
(fn [stroke] (fn [stroke]
(d/without-nils (assoc stroke (-> stroke
:stroke-color color (assoc :stroke-color color)
:stroke-opacity opacity (assoc :stroke-opacity opacity)
:stroke-color-gradient gradient (assoc :stroke-color-gradient gradient)
:stroke-image image))))) (assoc :stroke-image image)
(d/without-nils)))))
(defn attach-stroke-color (defn attach-stroke-color
[shape position ref-id ref-file] [shape position ref-id ref-file]
(-> shape (d/update-in-when shape [:strokes position]
(assoc-in [:strokes position :stroke-color-ref-id] ref-id) (fn [stroke]
(assoc-in [:strokes position :stroke-color-ref-file] ref-file))) (-> stroke
(assoc :stroke-color-ref-id ref-id)
(assoc :stroke-color-ref-file ref-file)))))
(defn detach-stroke-color (defn detach-stroke-color
[shape position] [shape position]
(-> shape (d/update-in-when shape [:strokes position] dissoc :stroke-color-ref-id :stroke-color-ref-file))
(d/dissoc-in [:strokes position :stroke-color-ref-id])
(d/dissoc-in [:strokes position :stroke-color-ref-file])))
;; shadow ;; shadow
(defn shadow->shape-color (defn shadow->color
[shadow] [shadow]
(d/without-nils {:color (-> shadow :color :color) (:color shadow))
:opacity (-> shadow :color :opacity)
:gradient (-> shadow :color :gradient)
:ref-id (-> shadow :color :id)
:ref-file (-> shadow :color :file-id)}))
(defn set-shadow-color (defn set-shadow-color
[shape position color opacity gradient] [shape position color opacity gradient]
(update-in shape [:shadow position :color] (d/update-in-when shape [:shadow position :color]
(fn [shadow-color] (fn [shadow-color]
(d/without-nils (assoc shadow-color (-> shadow-color
:color color (assoc :color color)
:opacity opacity (assoc :opacity opacity)
:gradient gradient))))) (assoc :gradient gradient)
(d/without-nils)))))
(defn attach-shadow-color (defn attach-shadow-color
[shape position ref-id ref-file] [shape position ref-id ref-file]
(-> shape (d/update-in-when shape [:shadow position :color]
(assoc-in [:shadow position :color :id] ref-id) (fn [color]
(assoc-in [:shadow position :color :file-id] ref-file))) (-> color
(assoc :ref-id ref-id)
(assoc :ref-file ref-file)))))
(defn detach-shadow-color (defn detach-shadow-color
[shape position] [shape position]
(-> shape (d/update-in-when shape [:shadow position :color] dissoc :ref-id :ref-file))
(d/dissoc-in [:shadow position :color :id])
(d/dissoc-in [:shadow position :color :file-id])))
;; grid ;; grid
(defn grid->shape-color ;: FIXME: revisit colors...... WTF
(defn grid->color
[grid] [grid]
(d/without-nils {:color (-> grid :params :color :color) (let [color (-> grid :params :color)]
:opacity (-> grid :params :color :opacity) (d/without-nils
:gradient (-> grid :params :color :gradient) {:color (-> color :color)
:ref-id (-> grid :params :color :id) :opacity (-> color :opacity)
:ref-file (-> grid :params :color :file-id)})) :gradient (-> color :gradient)
:ref-id (-> color :id)
:ref-file (-> color :file-id)})))
(defn set-grid-color (defn set-grid-color
[shape position color opacity gradient] [shape position color opacity gradient]
(update-in shape [:grids position :params :color] (d/update-in-when shape [:grids position :params :color]
(fn [grid-color] (fn [grid-color]
(d/without-nils (assoc grid-color (-> grid-color
:color color (assoc :color color)
:opacity opacity (assoc :opacity opacity)
:gradient gradient))))) (assoc :gradient gradient)
(d/without-nils)))))
(defn attach-grid-color (defn attach-grid-color
[shape position ref-id ref-file] [shape position ref-id ref-file]
(-> shape (d/update-in-when shape [:grids position :params :color]
(assoc-in [:grids position :params :color :id] ref-id) (fn [color]
(assoc-in [:grids position :params :color :file-id] ref-file))) (-> color
(assoc :ref-id ref-id)
(assoc :ref-file ref-file)))))
(defn detach-grid-color (defn detach-grid-color
[shape position] [shape position]
(-> shape (d/update-in-when shape [:grids position :params :color] dissoc :ref-id :ref-file))
(d/dissoc-in [:grids position :params :color :id])
(d/dissoc-in [:grids position :params :color :file-id])))
;; --- Helpers for all colors in a shape ;; --- Helpers for all colors in a shape
(defn get-text-node-colors (defn get-text-node-colors
"Get all colors used by a node of a text shape" "Get all colors used by a node of a text shape"
[node] [node]
(concat (map fill->shape-color (:fills node)) (concat (map fill->color (:fills node))
(map stroke->shape-color (:strokes node)))) (map stroke->color (:strokes node))))
(defn get-all-colors (defn get-all-colors
"Get all colors used by a shape, in any section." "Get all colors used by a shape, in any section."
[shape] [shape]
(concat (map fill->shape-color (:fills shape)) (concat (map fill->color (:fills shape))
(map stroke->shape-color (:strokes shape)) (map stroke->color (:strokes shape))
(map shadow->shape-color (:shadow shape)) (map shadow->color (:shadow shape))
(when (= (:type shape) :frame) (when (= (:type shape) :frame)
(map grid->shape-color (:grids shape))) (map grid->color (:grids shape)))
(when (= (:type shape) :text) (when (= (:type shape) :text)
(reduce (fn [colors node] (reduce (fn [colors node]
(concat colors (get-text-node-colors node))) (concat colors (get-text-node-colors node)))
@ -326,7 +350,7 @@
(let [process-fill (fn [shape [position fill]] (let [process-fill (fn [shape [position fill]]
(process-fn shape (process-fn shape
position position
(fill->shape-color fill) (fill->color fill)
set-fill-color set-fill-color
attach-fill-color attach-fill-color
detach-fill-color)) detach-fill-color))
@ -334,7 +358,7 @@
process-stroke (fn [shape [position stroke]] process-stroke (fn [shape [position stroke]]
(process-fn shape (process-fn shape
position position
(stroke->shape-color stroke) (stroke->color stroke)
set-stroke-color set-stroke-color
attach-stroke-color attach-stroke-color
detach-stroke-color)) detach-stroke-color))
@ -342,7 +366,7 @@
process-shadow (fn [shape [position shadow]] process-shadow (fn [shape [position shadow]]
(process-fn shape (process-fn shape
position position
(shadow->shape-color shadow) (shadow->color shadow)
set-shadow-color set-shadow-color
attach-shadow-color attach-shadow-color
detach-shadow-color)) detach-shadow-color))
@ -350,7 +374,7 @@
process-grid (fn [shape [position grid]] process-grid (fn [shape [position grid]]
(process-fn shape (process-fn shape
position position
(grid->shape-color grid) (grid->color grid)
set-grid-color set-grid-color
attach-grid-color attach-grid-color
detach-grid-color)) detach-grid-color))
@ -407,114 +431,76 @@
(process-shape-colors shape sync-color))) (process-shape-colors shape sync-color)))
(defn- eq-recent-color? (defn- stroke->color-att
[c1 c2] [stroke file-id libraries]
(or (= c1 c2) (let [ref-file (:stroke-color-ref-file stroke)
(and (some? (:color c1)) ref-id (:stroke-color-ref-id stroke)
(some? (:color c2)) shared-colors (dm/get-in libraries [ref-file :data :colors])
(= (:color c1) (:color c2))))) is-shared? (contains? shared-colors ref-id)
has-color? (or (:stroke-color stroke)
(defn add-recent-color (:stroke-color-gradient stroke))
"Moves the color to the top of the list and then truncates up to 15" attrs (cond-> (stroke->color stroke)
[state file-id color] (not (or is-shared? (= ref-file file-id)))
(update state file-id (fn [colors] (dissoc :ref-id :ref-file))]
(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)}))]
(when has-color? (when has-color?
{:attrs attrs {:attrs attrs
:prop :stroke :prop :stroke
:shape-id (:shape-id stroke) :shape-id (:shape-id stroke)
:index (:index stroke)}))) :index (:index stroke)})))
(defn shadow->color-att (defn- shadow->color-att
[shadow file-id shared-libs] [shadow file-id libraries]
(let [color-file-id (dm/get-in shadow [:color :file-id]) (let [color (get shadow :color)
color-id (dm/get-in shadow [:color :id]) ref-file (get color :ref-file)
shared-libs-colors (dm/get-in shared-libs [color-file-id :data :colors]) ref-id (get color :ref-id)
is-shared? (contains? shared-libs-colors color-id) shared-colors (dm/get-in libraries [ref-file :data :colors])
attrs (if (or is-shared? (= color-file-id file-id)) is-shared? (contains? shared-colors ref-id)
(d/without-nils {:color (str/lower (dm/get-in shadow [:color :color])) attrs (cond-> (shadow->color shadow)
:opacity (dm/get-in shadow [:color :opacity]) (not (or is-shared? (= ref-file file-id)))
:id color-id (dissoc :ref-file :ref-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])}))]
{:attrs attrs {:attrs attrs
:prop :shadow :prop :shadow
:shape-id (:shape-id shadow) :shape-id (:shape-id shadow)
:index (:index shadow)})) :index (:index shadow)}))
(defn text->color-att (defn- text->color-att
[fill file-id shared-libs] [fill file-id libraries]
(let [color-file-id (:fill-color-ref-file fill) (let [ref-file (:fill-color-ref-file fill)
color-id (:fill-color-ref-id fill) ref-id (:fill-color-ref-id fill)
shared-libs-colors (dm/get-in shared-libs [color-file-id :data :colors]) shared-colors (dm/get-in libraries [ref-file :data :colors])
is-shared? (contains? shared-libs-colors color-id) is-shared? (contains? shared-colors ref-id)
attrs (if (or is-shared? (= color-file-id file-id)) attrs (cond-> (fill->color fill)
(d/without-nils {:color (str/lower (:fill-color fill)) (not (or is-shared? (= ref-file file-id)))
:opacity (:fill-opacity fill) (dissoc :ref-file :ref-id))]
: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)}))]
{:attrs attrs {:attrs attrs
:prop :content :prop :content
:shape-id (:shape-id fill) :shape-id (:shape-id fill)
:index (:index fill)})) :index (:index fill)}))
(defn treat-node (defn- treat-node
[node shape-id] [node shape-id]
(map-indexed #(assoc %2 :shape-id shape-id :index %1) node)) (map-indexed #(assoc %2 :shape-id shape-id :index %1) node))
(defn extract-text-colors (defn- extract-text-colors
[text file-id shared-libs] [text file-id libraries]
(let [content (txt/node-seq txt/is-text-node? (:content text)) (->> (txt/node-seq txt/is-text-node? (:content text))
content-filtered (map :fills content) (map :fills)
indexed (mapcat #(treat-node % (:id text)) content-filtered)] (mapcat #(treat-node % (:id text)))
(map #(text->color-att % file-id shared-libs) indexed))) (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? (when has-color?
{:attrs attrs {:attrs attrs
:prop :fill :prop :fill
@ -522,21 +508,67 @@
:index (:index fill)}))) :index (:index fill)})))
(defn extract-all-colors (defn extract-all-colors
[shapes file-id shared-libs] [shapes file-id libraries]
(reduce (reduce
(fn [list shape] (fn [result shape]
(let [fill-obj (map-indexed #(assoc %2 :shape-id (:id shape) :index %1) (:fills 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)) 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))] shadow-obj (map-indexed #(assoc %2 :shape-id (:id shape) :index %1) (:shadow shape))]
(if (= :text (:type shape)) (if (= :text (:type shape))
(-> list (-> result
(into (map #(stroke->color-att % file-id shared-libs)) stroke-obj) (into (map #(stroke->color-att % file-id libraries)) stroke-obj)
(into (map #(shadow->color-att % file-id shared-libs)) shadow-obj) (into (map #(shadow->color-att % file-id libraries)) shadow-obj)
(into (extract-text-colors shape file-id shared-libs))) (into (extract-text-colors shape file-id libraries)))
(-> list (-> result
(into (map #(fill->color-att % file-id shared-libs)) fill-obj) (into (map #(fill->color-att % file-id libraries)) fill-obj)
(into (map #(stroke->color-att % file-id shared-libs)) stroke-obj) (into (map #(stroke->color-att % file-id libraries)) stroke-obj)
(into (map #(shadow->color-att % file-id shared-libs)) shadow-obj))))) (into (map #(shadow->color-att % file-id libraries)) shadow-obj)))))
[] []
shapes)) 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}))))

View file

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

View file

@ -18,7 +18,6 @@
[app.common.schema :as sm] [app.common.schema :as sm]
[app.common.text :as ct] [app.common.text :as ct]
[app.common.types.color :as ctc] [app.common.types.color :as ctc]
[app.common.types.colors-list :as ctcl]
[app.common.types.component :as ctk] [app.common.types.component :as ctk]
[app.common.types.components-list :as ctkl] [app.common.types.components-list :as ctkl]
[app.common.types.container :as ctn] [app.common.types.container :as ctn]
@ -59,7 +58,7 @@
[:is-local {:optional true} :boolean]]) [:is-local {:optional true} :boolean]])
(def schema:colors (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 (def schema:components
[:map-of {:gen/max 5} ::sm/uuid ::ctn/container]) [:map-of {:gen/max 5} ::sm/uuid ::ctn/container])
@ -488,7 +487,7 @@
[file-data library-data asset-type] [file-data library-data asset-type]
(let [assets-seq (case asset-type (let [assets-seq (case asset-type
:component (ctkl/components-seq library-data) :component (ctkl/components-seq library-data)
:color (ctcl/colors-seq library-data) :color (ctc/colors-seq library-data)
:typography (ctyl/typographies-seq library-data)) :typography (ctyl/typographies-seq library-data))
find-usages-in-container find-usages-in-container
@ -527,7 +526,7 @@
(letfn [(used-assets-shape [shape] (letfn [(used-assets-shape [shape]
(concat (concat
(ctkl/used-components-changed-since shape library since-date) (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))) (ctyl/used-typographies-changed-since shape library since-date)))
(used-assets-container [container] (used-assets-container [container]
@ -663,7 +662,7 @@
% %
shapes)))] shapes)))]
(as-> file-data $ (as-> file-data $
(ctcl/add-color $ color) (ctc/add-color $ color)
(reduce remap-shapes $ usages))))] (reduce remap-shapes $ usages))))]
(reduce absorb-color (reduce absorb-color

View file

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

View file

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

View file

@ -8,7 +8,7 @@
(:require (:require
[app.common.colors :as clr] [app.common.colors :as clr]
[app.common.schema :as sm] [app.common.schema :as sm]
[app.common.types.color :as ctc])) [app.common.types.color :refer [schema:hex-color]]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; SCHEMA ;; SCHEMA
@ -16,7 +16,7 @@
(def schema:grid-color (def schema:grid-color
[:map {:title "PageGridColor"} [:map {:title "PageGridColor"}
[:color ::ctc/rgb-color] [:color schema:hex-color]
[:opacity ::sm/safe-number]]) [:opacity ::sm/safe-number]])
(def schema:column-params (def schema:column-params

View file

@ -10,7 +10,7 @@
[app.common.data :as d] [app.common.data :as d]
[app.common.geom.point :as-alias gpt] [app.common.geom.point :as-alias gpt]
[app.common.schema :as sm] [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.grid :as ctg]
[app.common.types.plugins :as ctpg] [app.common.types.plugins :as ctpg]
[app.common.types.shape :as cts] [app.common.types.shape :as cts]
@ -57,7 +57,7 @@
[:flows {:optional true} schema:flows] [:flows {:optional true} schema:flows]
[:guides {:optional true} schema:guides] [:guides {:optional true} schema:guides]
[:plugin-data {:optional true} ::ctpg/plugin-data] [: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} [:comment-thread-positions {:optional true}
[:map-of ::sm/uuid schema:comment-thread-position]]]) [:map-of ::sm/uuid schema:comment-thread-position]]])

View file

@ -7,13 +7,14 @@
(ns app.common.types.path.impl (ns app.common.types.path.impl
"Contains schemas and data type implementation for PathData binary "Contains schemas and data type implementation for PathData binary
and plain formats" 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]) (:refer-clojure :exclude [-lookup -reduce])
#?(:cljs (:require-macros [app.common.types.path.impl]))
(:require (:require
#?(:clj [app.common.fressian :as fres]) #?(:clj [app.common.fressian :as fres])
#?(:clj [clojure.data.json :as json]) #?(:clj [clojure.data.json :as json])
#?(:cljs [app.common.weak-map :as weak-map]) #?(: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.data.macros :as dm]
[app.common.schema :as sm] [app.common.schema :as sm]
[app.common.schema.generators :as sg] [app.common.schema.generators :as sg]
@ -42,93 +43,42 @@
;; IMPL HELPERS ;; 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 (defmacro with-cache
"A helper macro that facilitates cache handling for content "A helper macro that facilitates cache handling for content
instance, only relevant on CLJS" instance, only relevant on CLJS"
[target key & expr] [target key & expr]
(if (:ns &env) (if (:ns &env)
(let [cache (gensym "cache-") (let [target (with-meta target {:tag 'js})]
target (with-meta target {:tag 'js})] `(let [~'cache (.-cache ~target)
`(let [~cache (.-cache ~target) ~'result (.get ~'cache ~key)]
~'result (.get ~cache ~key)]
(if ~'result (if ~'result
(do (do
~'result) ~'result)
(let [~'result (do ~@expr)] (let [~'result (do ~@expr)]
(.set ~cache ~key ~'result) (.set ~'cache ~key ~'result)
~'result)))) ~'result))))
`(do ~@expr))) `(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 (defn- impl-transform-segment
"Apply a transformation to a segment located under specified offset" "Apply a transformation to a segment located under specified offset"
[buffer offset a b c d e f] [buffer offset a b c d e f]
(let [t (read-short buffer offset)] (let [t (buf/read-short buffer offset)]
(case t (case t
(1 2) (1 2)
(let [x (read-float buffer (+ offset 20)) (let [x (buf/read-float buffer (+ offset 20))
y (read-float buffer (+ offset 24)) y (buf/read-float buffer (+ offset 24))
x (+ (* x a) (* y c) e) x (+ (* x a) (* y c) e)
y (+ (* x b) (* y d) f)] y (+ (* x b) (* y d) f)]
(write-float buffer (+ offset 20) x) (buf/write-float buffer (+ offset 20) x)
(write-float buffer (+ offset 24) y)) (buf/write-float buffer (+ offset 24) y))
3 3
(let [c1x (read-float buffer (+ offset 4)) (let [c1x (buf/read-float buffer (+ offset 4))
c1y (read-float buffer (+ offset 8)) c1y (buf/read-float buffer (+ offset 8))
c2x (read-float buffer (+ offset 12)) c2x (buf/read-float buffer (+ offset 12))
c2y (read-float buffer (+ offset 16)) c2y (buf/read-float buffer (+ offset 16))
x (read-float buffer (+ offset 20)) x (buf/read-float buffer (+ offset 20))
y (read-float buffer (+ offset 24)) y (buf/read-float buffer (+ offset 24))
c1x (+ (* c1x a) (* c1y c) e) c1x (+ (* c1x a) (* c1y c) e)
c1y (+ (* c1x b) (* c1y d) f) c1y (+ (* c1x b) (* c1y d) f)
@ -137,12 +87,12 @@
x (+ (* x a) (* y c) e) x (+ (* x a) (* y c) e)
y (+ (* x b) (* y d) f)] y (+ (* x b) (* y d) f)]
(write-float buffer (+ offset 4) c1x) (buf/write-float buffer (+ offset 4) c1x)
(write-float buffer (+ offset 8) c1y) (buf/write-float buffer (+ offset 8) c1y)
(write-float buffer (+ offset 12) c2x) (buf/write-float buffer (+ offset 12) c2x)
(write-float buffer (+ offset 16) c2y) (buf/write-float buffer (+ offset 16) c2y)
(write-float buffer (+ offset 20) x) (buf/write-float buffer (+ offset 20) x)
(write-float buffer (+ offset 24) y)) (buf/write-float buffer (+ offset 24) y))
nil))) nil)))
@ -166,13 +116,13 @@
result (transient initial)] result (transient initial)]
(if (< index size) (if (< index size)
(let [offset (* index SEGMENT-BYTE-SIZE) (let [offset (* index SEGMENT-BYTE-SIZE)
type (read-short buffer offset) type (buf/read-short buffer offset)
c1x (read-float buffer (+ offset 4)) c1x (buf/read-float buffer (+ offset 4))
c1y (read-float buffer (+ offset 8)) c1y (buf/read-float buffer (+ offset 8))
c2x (read-float buffer (+ offset 12)) c2x (buf/read-float buffer (+ offset 12))
c2y (read-float buffer (+ offset 16)) c2y (buf/read-float buffer (+ offset 16))
x (read-float buffer (+ offset 20)) x (buf/read-float buffer (+ offset 20))
y (read-float buffer (+ offset 24)) y (buf/read-float buffer (+ offset 24))
type (case type type (case type
1 :line-to 1 :line-to
2 :move-to 2 :move-to
@ -191,13 +141,13 @@
result initial] result initial]
(if (< index size) (if (< index size)
(let [offset (* index SEGMENT-BYTE-SIZE) (let [offset (* index SEGMENT-BYTE-SIZE)
type (read-short buffer offset) type (buf/read-short buffer offset)
c1x (read-float buffer (+ offset 4)) c1x (buf/read-float buffer (+ offset 4))
c1y (read-float buffer (+ offset 8)) c1y (buf/read-float buffer (+ offset 8))
c2x (read-float buffer (+ offset 12)) c2x (buf/read-float buffer (+ offset 12))
c2y (read-float buffer (+ offset 16)) c2y (buf/read-float buffer (+ offset 16))
x (read-float buffer (+ offset 20)) x (buf/read-float buffer (+ offset 20))
y (read-float buffer (+ offset 24)) y (buf/read-float buffer (+ offset 24))
type (case type type (case type
1 :line-to 1 :line-to
2 :move-to 2 :move-to
@ -212,13 +162,13 @@
(defn impl-lookup (defn impl-lookup
[buffer index f] [buffer index f]
(let [offset (* index SEGMENT-BYTE-SIZE) (let [offset (* index SEGMENT-BYTE-SIZE)
type (read-short buffer offset) type (buf/read-short buffer offset)
c1x (read-float buffer (+ offset 4)) c1x (buf/read-float buffer (+ offset 4))
c1y (read-float buffer (+ offset 8)) c1y (buf/read-float buffer (+ offset 8))
c2x (read-float buffer (+ offset 12)) c2x (buf/read-float buffer (+ offset 12))
c2y (read-float buffer (+ offset 16)) c2y (buf/read-float buffer (+ offset 16))
x (read-float buffer (+ offset 20)) x (buf/read-float buffer (+ offset 20))
y (read-float buffer (+ offset 24)) y (buf/read-float buffer (+ offset 24))
type (case type type (case type
1 :line-to 1 :line-to
2 :move-to 2 :move-to
@ -230,27 +180,27 @@
(defn- to-string-segment* (defn- to-string-segment*
[buffer offset type ^StringBuilder builder] [buffer offset type ^StringBuilder builder]
(case (long type) (case (long type)
1 (let [x (read-float buffer (+ offset 20)) 1 (let [x (buf/read-float buffer (+ offset 20))
y (read-float buffer (+ offset 24))] y (buf/read-float buffer (+ offset 24))]
(doto builder (doto builder
(.append "M") (.append "M")
(.append x) (.append x)
(.append ",") (.append ",")
(.append y))) (.append y)))
2 (let [x (read-float buffer (+ offset 20)) 2 (let [x (buf/read-float buffer (+ offset 20))
y (read-float buffer (+ offset 24))] y (buf/read-float buffer (+ offset 24))]
(doto builder (doto builder
(.append "L") (.append "L")
(.append x) (.append x)
(.append ",") (.append ",")
(.append y))) (.append y)))
3 (let [c1x (read-float buffer (+ offset 4)) 3 (let [c1x (buf/read-float buffer (+ offset 4))
c1y (read-float buffer (+ offset 8)) c1y (buf/read-float buffer (+ offset 8))
c2x (read-float buffer (+ offset 12)) c2x (buf/read-float buffer (+ offset 12))
c2y (read-float buffer (+ offset 16)) c2y (buf/read-float buffer (+ offset 16))
x (read-float buffer (+ offset 20)) x (buf/read-float buffer (+ offset 20))
y (read-float buffer (+ offset 24))] y (buf/read-float buffer (+ offset 24))]
(doto builder (doto builder
(.append "C") (.append "C")
(.append c1x) (.append c1x)
@ -275,7 +225,7 @@
(loop [index 0] (loop [index 0]
(when (< index size) (when (< index size)
(let [offset (* index SEGMENT-BYTE-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) (to-string-segment* buffer offset type builder)
(recur (inc index))))) (recur (inc index)))))
@ -285,26 +235,26 @@
"Read segment from binary buffer at specified index" "Read segment from binary buffer at specified index"
[buffer index] [buffer index]
(let [offset (* index SEGMENT-BYTE-SIZE) (let [offset (* index SEGMENT-BYTE-SIZE)
type (read-short buffer offset)] type (buf/read-short buffer offset)]
(case (long type) (case (long type)
1 (let [x (read-float buffer (+ offset 20)) 1 (let [x (buf/read-float buffer (+ offset 20))
y (read-float buffer (+ offset 24))] y (buf/read-float buffer (+ offset 24))]
{:command :move-to {:command :move-to
:params {:x (double x) :params {:x (double x)
:y (double y)}}) :y (double y)}})
2 (let [x (read-float buffer (+ offset 20)) 2 (let [x (buf/read-float buffer (+ offset 20))
y (read-float buffer (+ offset 24))] y (buf/read-float buffer (+ offset 24))]
{:command :line-to {:command :line-to
:params {:x (double x) :params {:x (double x)
:y (double y)}}) :y (double y)}})
3 (let [c1x (read-float buffer (+ offset 4)) 3 (let [c1x (buf/read-float buffer (+ offset 4))
c1y (read-float buffer (+ offset 8)) c1y (buf/read-float buffer (+ offset 8))
c2x (read-float buffer (+ offset 12)) c2x (buf/read-float buffer (+ offset 12))
c2y (read-float buffer (+ offset 16)) c2y (buf/read-float buffer (+ offset 16))
x (read-float buffer (+ offset 20)) x (buf/read-float buffer (+ offset 20))
y (read-float buffer (+ offset 24))] y (buf/read-float buffer (+ offset 24))]
{:command :curve-to {:command :curve-to
:params {:x (double x) :params {:x (double x)
:y (double y) :y (double y)
@ -316,10 +266,6 @@
4 {:command :close-path 4 {:command :close-path
:params {}}))) :params {}})))
(defn- in-range?
[size i]
(and (< i size) (>= i 0)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TYPE: PATH-DATA ;; TYPE: PATH-DATA
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -334,12 +280,12 @@
(equals [_ other] (equals [_ other]
(if (instance? PathData other) (if (instance? PathData other)
(.equals ^ByteBuffer buffer (.-buffer ^PathData other)) (buf/equals? buffer (.-buffer ^PathData other))
false)) false))
ITransformable ITransformable
(-transform [_ m] (-transform [_ m]
(let [buffer (clone-buffer buffer)] (let [buffer (buf/clone buffer)]
(impl-transform buffer m size) (impl-transform buffer m size)
(PathData. size buffer nil))) (PathData. size buffer nil)))
@ -361,7 +307,7 @@
clojure.lang.IHashEq clojure.lang.IHashEq
(hasheq [this] (hasheq [this]
(when-not hash (when-not hash
(set! hash (clojure.lang.Murmur3/hashOrdered (seq this)))) (set! hash (clojure.lang.Murmur3/hashOrdered (vec this))))
hash) hash)
clojure.lang.Sequential clojure.lang.Sequential
@ -387,12 +333,12 @@
clojure.lang.Indexed clojure.lang.Indexed
(nth [_ i] (nth [_ i]
(if (in-range? size i) (if (d/in-range? size i)
(read-segment buffer i) (read-segment buffer i)
nil)) nil))
(nth [_ i default] (nth [_ i default]
(if (in-range? size i) (if (d/in-range? size i)
(read-segment buffer i) (read-segment buffer i)
default)) default))
@ -408,10 +354,10 @@
:cljs :cljs
#_:clj-kondo/ignore #_:clj-kondo/ignore
(deftype PathData [size buffer dview cache ^:mutable __hash] (deftype PathData [size buffer cache ^:mutable __hash]
Object Object
(toString [_] (toString [_]
(to-string dview size)) (to-string buffer size))
IPathData IPathData
(-get-byte-size [_] (-get-byte-size [_]
@ -421,56 +367,43 @@
;; NOTE: we still use u8 because until the heap refactor merge ;; NOTE: we still use u8 because until the heap refactor merge
;; we can't guarrantee the alignment of offset on 4 bytes ;; we can't guarrantee the alignment of offset on 4 bytes
(assert (instance? js/ArrayBuffer into-buffer)) (assert (instance? js/ArrayBuffer into-buffer))
(let [size (.-byteLength buffer) (let [buffer' (.-buffer ^js/DataView buffer)
mem (js/Uint8Array. into-buffer offset size)] size (.-byteLength buffer')
(.set mem (js/Uint8Array. buffer)))) mem (js/Uint8Array. into-buffer offset size)]
(.set mem (js/Uint8Array. buffer'))))
ITransformable ITransformable
(-transform [this m] (-transform [this m]
(let [buffer (clone-buffer buffer) (let [buffer (buf/clone buffer)]
dview (js/DataView. buffer)] (impl-transform buffer m size)
(impl-transform dview m size) (PathData. size buffer (weak-map/create) nil)))
(PathData. size buffer dview (weak-map/create) nil)))
(-walk [_ f initial] (-walk [_ f initial]
(impl-walk dview f initial size)) (impl-walk buffer f initial size))
(-reduce [_ f initial] (-reduce [_ f initial]
(impl-reduce dview f initial size)) (impl-reduce buffer f initial size))
(-lookup [_ index f] (-lookup [_ index f]
(when (and (<= 0 index) (when (and (<= 0 index)
(< index size)) (< index size))
(impl-lookup dview index f))) (impl-lookup buffer index f)))
cljs.core/ISequential cljs.core/ISequential
cljs.core/IEquiv cljs.core/IEquiv
(-equiv [this other] (-equiv [this other]
(if (instance? PathData other) (if (instance? PathData other)
(let [obuffer (.-buffer other)] (buf/equals? buffer (.-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))
false)) false))
cljs.core/IReduce cljs.core/IReduce
(-reduce [_ f] (-reduce [_ f]
(loop [index 1 (loop [index 1
result (if (pos? size) result (if (pos? size)
(read-segment dview 0) (read-segment buffer 0)
nil)] nil)]
(if (< index size) (if (< index size)
(let [result (f result (read-segment dview index))] (let [result (f result (read-segment buffer index))]
(if (reduced? result) (if (reduced? result)
@result @result
(recur (inc index) result))) (recur (inc index) result)))
@ -480,7 +413,7 @@
(loop [index 0 (loop [index 0
result start] result start]
(if (< index size) (if (< index size)
(let [result (f result (read-segment dview index))] (let [result (f result (read-segment buffer index))]
(if (reduced? result) (if (reduced? result)
@result @result
(recur (inc index) result))) (recur (inc index) result)))
@ -495,13 +428,13 @@
cljs.core/IIndexed cljs.core/IIndexed
(-nth [_ i] (-nth [_ i]
(if (in-range? size i) (if (d/in-range? size i)
(read-segment dview i) (read-segment buffer i)
nil)) nil))
(-nth [_ i default] (-nth [_ i default]
(if (in-range? i size) (if (d/in-range? i size)
(read-segment dview i) (read-segment buffer i)
default)) default))
cljs.core/ISeqable cljs.core/ISeqable
@ -509,7 +442,7 @@
(when (pos? size) (when (pos? size)
((fn next-seq [i] ((fn next-seq [i]
(when (< i size) (when (< i size)
(cons (read-segment dview i) (cons (read-segment buffer i)
(lazy-seq (next-seq (inc i)))))) (lazy-seq (next-seq (inc i))))))
0))) 0)))
@ -659,17 +592,15 @@
(let [size (.-byteLength buffer) (let [size (.-byteLength buffer)
count (long (/ size SEGMENT-BYTE-SIZE))] count (long (/ size SEGMENT-BYTE-SIZE))]
(PathData. count (PathData. count
buffer
(js/DataView. buffer) (js/DataView. buffer)
(weak-map/create) (weak-map/create)
nil)) nil))
(instance? js/DataView buffer) (instance? js/DataView buffer)
(let [dview buffer (let [buffer' (.-buffer ^js/DataView buffer)
buffer (.-buffer dview) size (.-byteLength ^js/ArrayBuffer buffer')
size (.-byteLength buffer) count (long (/ size SEGMENT-BYTE-SIZE))]
count (long (/ size SEGMENT-BYTE-SIZE))] (PathData. count buffer (weak-map/create) nil))
(PathData. count buffer dview (weak-map/create) nil))
(instance? js/Uint8Array buffer) (instance? js/Uint8Array buffer)
(from-bytes (.-buffer buffer)) (from-bytes (.-buffer buffer))
@ -688,10 +619,8 @@
[segments] [segments]
(assert (check-segments segments)) (assert (check-segments segments))
(let [total (count segments) (let [total (count segments)
#?@(:cljs [buffer' (allocate total) buffer (buf/allocate (* total SEGMENT-BYTE-SIZE))]
buffer (new js/DataView buffer')]
:clj [buffer (allocate total)])]
(loop [index 0] (loop [index 0]
(when (< index total) (when (< index total)
(let [segment (nth segments index) (let [segment (nth segments index)
@ -701,18 +630,18 @@
(let [params (get segment :params) (let [params (get segment :params)
x (float (get params :x)) x (float (get params :x))
y (float (get params :y))] y (float (get params :y))]
(write-short buffer offset 1) (buf/write-short buffer offset 1)
(write-float buffer (+ offset 20) x) (buf/write-float buffer (+ offset 20) x)
(write-float buffer (+ offset 24) y)) (buf/write-float buffer (+ offset 24) y))
:line-to :line-to
(let [params (get segment :params) (let [params (get segment :params)
x (float (get params :x)) x (float (get params :x))
y (float (get params :y))] y (float (get params :y))]
(write-short buffer offset 2) (buf/write-short buffer offset 2)
(write-float buffer (+ offset 20) x) (buf/write-float buffer (+ offset 20) x)
(write-float buffer (+ offset 24) y)) (buf/write-float buffer (+ offset 24) y))
:curve-to :curve-to
(let [params (get segment :params) (let [params (get segment :params)
@ -723,16 +652,16 @@
c2x (float (get params :c2x x)) c2x (float (get params :c2x x))
c2y (float (get params :c2y y))] c2y (float (get params :c2y y))]
(write-short buffer offset 3) (buf/write-short buffer offset 3)
(write-float buffer (+ offset 4) c1x) (buf/write-float buffer (+ offset 4) c1x)
(write-float buffer (+ offset 8) c1y) (buf/write-float buffer (+ offset 8) c1y)
(write-float buffer (+ offset 12) c2x) (buf/write-float buffer (+ offset 12) c2x)
(write-float buffer (+ offset 16) c2y) (buf/write-float buffer (+ offset 16) c2y)
(write-float buffer (+ offset 20) x) (buf/write-float buffer (+ offset 20) x)
(write-float buffer (+ offset 24) y)) (buf/write-float buffer (+ offset 24) y))
:close-path :close-path
(write-short buffer offset 4)) (buf/write-short buffer offset 4))
(recur (inc index))))) (recur (inc index)))))
(from-bytes buffer))) (from-bytes buffer)))
@ -763,7 +692,7 @@
:class PathData :class PathData
:wfn (fn [^PathData pdata] :wfn (fn [^PathData pdata]
(let [buffer (.-buffer pdata)] (let [buffer (.-buffer pdata)]
#?(:cljs (js/Uint8Array. buffer) #?(:cljs (js/Uint8Array. (.-buffer ^js/DataView buffer))
:clj (.array ^ByteBuffer buffer)))) :clj (.array ^ByteBuffer buffer))))
:rfn from-bytes}) :rfn from-bytes})

View file

@ -20,7 +20,8 @@
[app.common.schema.generators :as sg] [app.common.schema.generators :as sg]
[app.common.text :as txt] [app.common.text :as txt]
[app.common.transit :as t] [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.grid :as ctg]
[app.common.types.path :as path] [app.common.types.path :as path]
[app.common.types.path.segment :as path.segment] [app.common.types.path.segment :as path.segment]
@ -119,36 +120,47 @@
(def schema:points (def schema:points
[:vector {:gen/max 4 :gen/min 4} ::gpt/point]) [:vector {:gen/max 4 :gen/min 4} ::gpt/point])
(def schema:fill ;; FIXME: the register is necessary until this is moved to a separated
(sm/register! ;; ns because it is used on shapes.text
^{::sm/type ::fill} (def valid-stroke-attrs
[:map {:title "Fill"} "A set used for proper check if color should contain only one of the
[:fill-color {:optional true} ::ctc/rgb-color] attrs listed in this set."
[:fill-opacity {:optional true} ::sm/safe-number] #{:stroke-image :stroke-color :stroke-color-gradient})
[:fill-color-gradient {:optional true} [:maybe ::ctc/gradient]]
[:fill-color-ref-file {:optional true} [:maybe ::sm/uuid]] (defn has-valid-stroke-attrs?
[:fill-color-ref-id {:optional true} [:maybe ::sm/uuid]] "Check if color has correct color attrs"
[:fill-image {:optional true} ::ctc/image-color]])) [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 (def schema:stroke
(sm/register! (sm/register!
^{::sm/type ::stroke} ^{::sm/type ::stroke}
[:map {:title "Stroke"} [:and schema:stroke-attrs
[:stroke-color {:optional true} :string] [:fn has-valid-stroke-attrs?]]))
[: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]]))
(def check-stroke (def check-stroke
(sm/check-fn schema:stroke)) (sm/check-fn schema:stroke))
@ -761,8 +773,3 @@
(d/patch-object (select-keys props basic-extract-props)) (d/patch-object (select-keys props basic-extract-props))
(cond-> (cfh/text-shape? shape) (patch-text-props props)) (cond-> (cfh/text-shape? shape) (patch-text-props props))
(cond-> (cfh/frame-shape? shape) (patch-layout-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)

View file

@ -24,7 +24,7 @@
[:blur ::sm/safe-number] [:blur ::sm/safe-number]
[:spread ::sm/safe-number] [:spread ::sm/safe-number]
[:hidden :boolean] [:hidden :boolean]
[:color ::ctc/color]]) [:color ctc/schema:color]])
(def check-shadow (def check-shadow
(sm/check-fn schema:shadow)) (sm/check-fn schema:shadow))

View file

@ -7,6 +7,7 @@
(ns app.common.types.shape.text (ns app.common.types.shape.text
(:require (:require
[app.common.schema :as sm] [app.common.schema :as sm]
[app.common.types.fill :refer [schema:fill]]
[app.common.types.shape :as-alias shape] [app.common.types.shape :as-alias shape]
[app.common.types.shape.text.position-data :as-alias position-data])) [app.common.types.shape.text.position-data :as-alias position-data]))
@ -34,7 +35,7 @@
[:key {:optional true} :string] [:key {:optional true} :string]
[:fills {:optional true} [:fills {:optional true}
[:maybe [:maybe
[:vector {:gen/max 2} ::shape/fill]]] [:vector {:gen/max 2} schema:fill]]]
[:font-family {:optional true} :string] [:font-family {:optional true} :string]
[:font-size {:optional true} :string] [:font-size {:optional true} :string]
[:font-style {:optional true} :string] [:font-style {:optional true} :string]
@ -51,7 +52,7 @@
[:key {:optional true} :string] [:key {:optional true} :string]
[:fills {:optional true} [:fills {:optional true}
[:maybe [:maybe
[:vector {:gen/max 2} ::shape/fill]]] [:vector {:gen/max 2} schema:fill]]]
[:font-family {:optional true} :string] [:font-family {:optional true} :string]
[:font-size {:optional true} :string] [:font-size {:optional true} :string]
[:font-style {:optional true} :string] [:font-style {:optional true} :string]
@ -75,7 +76,7 @@
[:y ::sm/safe-number] [:y ::sm/safe-number]
[:width ::sm/safe-number] [:width ::sm/safe-number]
[:height ::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-family {:optional true} :string]
[:font-size {:optional true} :string] [:font-size {:optional true} :string]
[:font-style {:optional true} :string] [:font-style {:optional true} :string]

View file

@ -796,6 +796,7 @@ Will return a value that matches this schema:
(declare parse-multi-set-dtcg-json) (declare parse-multi-set-dtcg-json)
(declare export-dtcg-json) (declare export-dtcg-json)
(deftype TokensLib [sets themes active-themes] (deftype TokensLib [sets themes active-themes]
;; NOTE: This is only for debug purposes, pending to properly ;; NOTE: This is only for debug purposes, pending to properly
;; implement the toString and alternative printing. ;; implement the toString and alternative printing.

View file

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

View file

@ -7,6 +7,7 @@
(ns common-tests.runner (ns common-tests.runner
(:require (:require
[clojure.test :as t] [clojure.test :as t]
[common-tests.buffer-test]
[common-tests.colors-test] [common-tests.colors-test]
[common-tests.data-test] [common-tests.data-test]
[common-tests.files-changes-test] [common-tests.files-changes-test]
@ -38,6 +39,7 @@
[common-tests.time-test] [common-tests.time-test]
[common-tests.types.absorb-assets-test] [common-tests.types.absorb-assets-test]
[common-tests.types.components-test] [common-tests.types.components-test]
[common-tests.types.fill-test]
[common-tests.types.modifiers-test] [common-tests.types.modifiers-test]
[common-tests.types.path-data-test] [common-tests.types.path-data-test]
[common-tests.types.shape-decode-encode-test] [common-tests.types.shape-decode-encode-test]
@ -56,6 +58,7 @@
(defn -main (defn -main
[& args] [& args]
(t/run-tests (t/run-tests
'common-tests.buffer-test
'common-tests.colors-test 'common-tests.colors-test
'common-tests.data-test 'common-tests.data-test
'common-tests.files-changes-test 'common-tests.files-changes-test
@ -89,6 +92,7 @@
'common-tests.types.components-test 'common-tests.types.components-test
'common-tests.types.modifiers-test 'common-tests.types.modifiers-test
'common-tests.types.path-data-test 'common-tests.types.path-data-test
'common-tests.types.fill-test
'common-tests.types.shape-decode-encode-test 'common-tests.types.shape-decode-encode-test
'common-tests.types.shape-interactions-test 'common-tests.types.shape-interactions-test
'common-tests.types.tokens-lib-test 'common-tests.types.tokens-lib-test

View file

@ -13,7 +13,7 @@
[app.common.test-helpers.ids-map :as thi] [app.common.test-helpers.ids-map :as thi]
[app.common.test-helpers.shapes :as ths] [app.common.test-helpers.shapes :as ths]
[app.common.text :as txt] [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.component :as ctk]
[app.common.types.components-list :as ctkl] [app.common.types.components-list :as ctkl]
[app.common.types.file :as ctf] [app.common.types.file :as ctf]
@ -80,7 +80,7 @@
_ (thf/validate-file! file') _ (thf/validate-file! file')
;; Get ;; Get
colors' (ctcl/colors-seq (ctf/file-data file')) colors' (ctc/colors-seq (ctf/file-data file'))
shape1' (ths/get-shape file' :shape1) shape1' (ths/get-shape file' :shape1)
fill' (first (:fills shape1'))] fill' (first (:fills shape1'))]

View file

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

View file

@ -101,7 +101,7 @@
(let [pdata (path/content sample-content)] (let [pdata (path/content sample-content)]
(t/is (= sample-bytes (t/is (= sample-bytes
(vec (vec
#?(:cljs (js/Int8Array. (.-buffer pdata)) #?(:cljs (js/Int8Array. (.-buffer (.-buffer pdata)))
:clj (.array (.-buffer pdata)))))) :clj (.array (.-buffer pdata))))))
(t/is (= sample-content (t/is (= sample-content
(vec pdata))))) (vec pdata)))))

View file

@ -145,4 +145,4 @@
;; (app.common.pprint/pprint shape) ;; (app.common.pprint/pprint shape)
;; (app.common.pprint/pprint shape-3) ;; (app.common.pprint/pprint shape-3)
(= shape shape-3))) (= shape shape-3)))
{:num 100}))) {:num 200})))

View file

@ -18,6 +18,7 @@
(let [uuid (uuid/uuid "0227df82-63d7-8016-8005-48d9c0f33011") (let [uuid (uuid/uuid "0227df82-63d7-8016-8005-48d9c0f33011")
result-bytes (uuid/get-bytes uuid) result-bytes (uuid/get-bytes uuid)
expected-bytes [2 39 -33 -126 99 -41 -128 22 -128 5 72 -39 -64 -13 48 17]] expected-bytes [2 39 -33 -126 99 -41 -128 22 -128 5 72 -39 -64 -13 48 17]]
(t/testing "get-bytes" (t/testing "get-bytes"
(let [data (uuid/get-bytes uuid)] (let [data (uuid/get-bytes uuid)]
(t/is (= (nth expected-bytes 0) (aget data 0))) (t/is (= (nth expected-bytes 0) (aget data 0)))

View file

@ -121,6 +121,13 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "binary-extensions@npm:^2.0.0":
version: 2.3.0 version: 2.3.0
resolution: "binary-extensions@npm:2.3.0" resolution: "binary-extensions@npm:2.3.0"
@ -163,6 +170,16 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "cacache@npm:^18.0.0":
version: 18.0.3 version: 18.0.3
resolution: "cacache@npm:18.0.3" resolution: "cacache@npm:18.0.3"
@ -260,7 +277,7 @@ __metadata:
concurrently: "npm:^9.0.1" concurrently: "npm:^9.0.1"
luxon: "npm:^3.4.4" luxon: "npm:^3.4.4"
nodemon: "npm:^3.1.7" nodemon: "npm:^3.1.7"
shadow-cljs: "npm:3.0.5" shadow-cljs: "npm:3.1.5"
source-map-support: "npm:^0.5.21" source-map-support: "npm:^0.5.21"
ws: "npm:^8.17.0" ws: "npm:^8.17.0"
languageName: unknown languageName: unknown
@ -528,6 +545,13 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "ignore-by-default@npm:^1.0.1":
version: 1.0.1 version: 1.0.1
resolution: "ignore-by-default@npm:1.0.1" resolution: "ignore-by-default@npm:1.0.1"
@ -917,6 +941,13 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "promise-retry@npm:^2.0.1":
version: 2.0.1 version: 2.0.1
resolution: "promise-retry@npm:2.0.1" resolution: "promise-retry@npm:2.0.1"
@ -1005,10 +1036,12 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"shadow-cljs@npm:3.0.5": "shadow-cljs@npm:3.1.5":
version: 3.0.5 version: 3.1.5
resolution: "shadow-cljs@npm:3.0.5" resolution: "shadow-cljs@npm:3.1.5"
dependencies: dependencies:
buffer: "npm:^6.0.3"
process: "npm:^0.11.10"
readline-sync: "npm:^1.4.10" readline-sync: "npm:^1.4.10"
shadow-cljs-jar: "npm:1.3.4" shadow-cljs-jar: "npm:1.3.4"
source-map-support: "npm:^0.5.21" source-map-support: "npm:^0.5.21"
@ -1016,7 +1049,7 @@ __metadata:
ws: "npm:^8.18.1" ws: "npm:^8.18.1"
bin: bin:
shadow-cljs: cli/runner.js shadow-cljs: cli/runner.js
checksum: 10c0/2c5f3976f7bec16b7fb9fbba5d4a7581e0d0157384a470ce0670120f02cfe6b9c7183102133e0e9b300cbe318e9a3b6001309f96840999ca2814d39fc83c23e8 checksum: 10c0/29da68f7645c258becf4074e4401e5c86dd3af04622c2e10fdac09824e9832290918d90aaf80ef7df0c35731f1b51b84101cbfd0c6819772a493173d4ae69415
languageName: node languageName: node
linkType: hard linkType: hard

View file

@ -20,8 +20,8 @@
:git/url "https://github.com/funcool/beicon.git"} :git/url "https://github.com/funcool/beicon.git"}
funcool/rumext funcool/rumext
{:git/tag "v2.21" {:git/tag "v2.22"
:git/sha "072d671" :git/sha "92879b6"
:git/url "https://github.com/funcool/rumext.git"} :git/url "https://github.com/funcool/rumext.git"}
instaparse/instaparse {:mvn/version "1.5.0"} instaparse/instaparse {:mvn/version "1.5.0"}
@ -42,7 +42,7 @@
:dev :dev
{:extra-paths ["dev"] {:extra-paths ["dev"]
:extra-deps :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"} com.bhauman/rebel-readline {:mvn/version "RELEASE"}
org.clojure/tools.namespace {:mvn/version "RELEASE"} org.clojure/tools.namespace {:mvn/version "RELEASE"}
criterium/criterium {:mvn/version "RELEASE"} criterium/criterium {:mvn/version "RELEASE"}

View file

@ -90,7 +90,7 @@
"rimraf": "^6.0.1", "rimraf": "^6.0.1",
"sass": "^1.89.0", "sass": "^1.89.0",
"sass-embedded": "^1.89.0", "sass-embedded": "^1.89.0",
"shadow-cljs": "3.0.5", "shadow-cljs": "3.1.5",
"storybook": "^8.6.14", "storybook": "^8.6.14",
"svg-sprite": "^2.0.4", "svg-sprite": "^2.0.4",
"typescript": "^5.8.3", "typescript": "^5.8.3",

View file

@ -122,6 +122,7 @@
:storybook :storybook
{:target :esm {:target :esm
:output-dir "target/storybook/" :output-dir "target/storybook/"
:devtools {:enabled false}
:js-options :js-options
{:js-provider :import {:js-provider :import
:entry-keys ["module" "browser" "main"] :entry-keys ["module" "browser" "main"]
@ -134,6 +135,7 @@
:components :components
{:exports {default app.main.ui.ds/default {:exports {default app.main.ui.ds/default
helpers app.main.ui.ds.helpers/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}}} :depends-on #{:base}}}
:compiler-options :compiler-options

View file

@ -15,6 +15,8 @@
[cljs.spec.alpha :as s] [cljs.spec.alpha :as s]
[cuerdas.core :as str])) [cuerdas.core :as str]))
;; FIXME: revisit the need of this NS
;; --- Predicates ;; --- Predicates
(defn file? (defn file?
@ -38,7 +40,7 @@
(defn validate-file (defn validate-file
"Check that a file obtained with the file javascript API is valid." "Check that a file obtained with the file javascript API is valid."
[file] [file]
(when-not (contains? cm/valid-image-types (.-type file)) (when-not (contains? cm/image-types (.-type file))
(ex/raise :type :validation (ex/raise :type :validation
:code :media-type-not-allowed :code :media-type-not-allowed
:hint (str/ffmt "media type % is not supported" (.-type file)))) :hint (str/ffmt "media type % is not supported" (.-type file))))

View file

@ -13,6 +13,7 @@
[app.common.schema :as sm] [app.common.schema :as sm]
[app.common.text :as txt] [app.common.text :as txt]
[app.common.types.color :as ctc] [app.common.types.color :as ctc]
[app.common.types.fill :as types.fill]
[app.common.types.shape :as shp] [app.common.types.shape :as shp]
[app.common.types.shape.shadow :refer [check-shadow]] [app.common.types.shape.shadow :refer [check-shadow]]
[app.config :as cfg] [app.config :as cfg]
@ -21,7 +22,6 @@
[app.main.data.helpers :as dsh] [app.main.data.helpers :as dsh]
[app.main.data.modal :as md] [app.main.data.modal :as md]
[app.main.data.workspace.layout :as layout] [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.shapes :as dwsh]
[app.main.data.workspace.texts :as dwt] [app.main.data.workspace.texts :as dwt]
[app.main.data.workspace.undo :as dwu] [app.main.data.workspace.undo :as dwu]
@ -114,9 +114,9 @@
([state ids color transform] ([state ids color transform]
(transform-fill state ids color transform nil)) (transform-fill state ids color transform nil))
([state ids color transform options] ([state ids color transform options]
(let [page-id (or (get options :page-id) (let [page-id (or (get options :page-id)
(get state :current-page-id)) (get state :current-page-id))
objects (dsh/lookup-page-objects state page-id) objects (dsh/lookup-page-objects state page-id)
[text-ids shape-ids] [text-ids shape-ids]
(split-text-shapes objects ids) (split-text-shapes objects ids)
@ -126,11 +126,11 @@
(contains? color :color) (contains? color :color)
(assoc :fill-color (:color color)) (assoc :fill-color (:color color))
(contains? color :id) (contains? color :ref-id)
(assoc :fill-color-ref-id (:id color)) (assoc :fill-color-ref-id (:ref-id color))
(contains? color :file-id) (contains? color :ref-file)
(assoc :fill-color-ref-file (:file-id color)) (assoc :fill-color-ref-file (:ref-file color))
(contains? color :gradient) (contains? color :gradient)
(assoc :fill-color-gradient (:gradient color)) (assoc :fill-color-gradient (:gradient color))
@ -142,7 +142,10 @@
(assoc :fill-image (:image color)) (assoc :fill-image (:image color))
:always :always
(d/without-nils)) (d/without-nils)
:always
(types.fill/check-fill))
transform-attrs #(transform % fill)] transform-attrs #(transform % fill)]
@ -327,11 +330,11 @@
(contains? color :color) (contains? color :color)
(assoc :stroke-color (:color color)) (assoc :stroke-color (:color color))
(contains? color :id) (contains? color :ref-id)
(assoc :stroke-color-ref-id (:id color)) (assoc :stroke-color-ref-id (:ref-id color))
(contains? color :file-id) (contains? color :ref-file)
(assoc :stroke-color-ref-file (:file-id color)) (assoc :stroke-color-ref-file (:ref-file color))
(contains? color :gradient) (contains? color :gradient)
(assoc :stroke-color-gradient (:gradient color)) (assoc :stroke-color-gradient (:gradient color))
@ -533,18 +536,19 @@
(defn- color-att->text (defn- color-att->text
[color] [color]
{:fill-color (when (:color color) (str/lower (:color color))) (d/without-nils
:fill-opacity (:opacity color) {:fill-color (when (:color color) (str/lower (:color color)))
:fill-color-ref-id (:id color) :fill-opacity (:opacity color)
:fill-color-ref-file (:file-id color) :fill-color-ref-id (:ref-id color)
:fill-color-gradient (:gradient color)}) :fill-color-ref-file (:ref-file color)
:fill-color-gradient (:gradient color)}))
(defn change-text-color (defn change-text-color
[old-color new-color index node] [old-color new-color index node]
(let [fills (map #(dissoc % :fill-color-ref-id :fill-color-ref-file) (:fills 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)) (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)] has-color? (d/index-of fills parsed-color)]
(cond-> node (cond-> node
(some? has-color?) (some? has-color?)
@ -595,37 +599,33 @@
(defn apply-color-from-palette (defn apply-color-from-palette
[color stroke?] [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 ids
(ctc/check-color color) (loop [pending (seq selected)
"expected valid color structure") result []]
(if (empty? pending)
result
(let [cur (first pending)
group? (cfh/group-shape? objects cur)
(ptk/reify ::apply-color-from-palette pending
ptk/WatchEvent (if group?
(watch [_ state _] (concat pending (dm/get-in objects [cur :shapes]))
(let [objects (dsh/lookup-page-objects state) pending)
selected (->> (dsh/lookup-selected state)
(cfh/clean-loops objects))
ids result (cond-> result (not group?) (conj cur))]
(loop [pending (seq selected) (recur (rest pending) result))))]
result []]
(if (empty? pending)
result
(let [cur (first pending)
group? (cfh/group-shape? objects cur)
pending (if stroke?
(if group? (rx/of (change-stroke-color ids color 0))
(concat pending (dm/get-in objects [cur :shapes])) (rx/of (change-fill ids color 0))))))))
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)))))))
(declare activate-colorpicker-color) (declare activate-colorpicker-color)
(declare activate-colorpicker-gradient) (declare activate-colorpicker-gradient)
@ -634,14 +634,10 @@
(defn apply-color-from-colorpicker (defn apply-color-from-colorpicker
[color] [color]
(let [color (ctc/check-color color)]
(assert (ctc/check-color color) (ptk/reify ::apply-color-from-colorpicker
"expected valid color structure") ptk/UpdateEvent
(update [_ state]
(ptk/reify ::apply-color-from-colorpicker
ptk/UpdateEvent
(update [_ state]
(let [gradient-type (dm/get-in color [:gradient :type])]
(update state :colorpicker (update state :colorpicker
(fn [state] (fn [state]
(cond (cond
@ -655,19 +651,57 @@
(assoc :type :color) (assoc :type :color)
(dissoc :editing-stop :stops :gradient)) (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) (= :radial gradient-type)
(-> state (-> state
(assoc :type :linear-gradient) (assoc :type :radial-gradient)
(assoc :editing-stop 0) (assoc :editing-stop 0)
(d/dissoc-in [:current-color :image])) (update :current-color dissoc :image)))))))))))
(= :radial gradient-type) (defn- recent-color-equal?
(-> state [c1 c2]
(assoc :type :radial-gradient) (or (= c1 c2)
(assoc :editing-stop 0) (and (some? (:color c1))
(d/dissoc-in [:current-color :image]))))))))) (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 ;; COLORPICKER STATE MANAGEMENT
@ -823,7 +857,7 @@
(update state :colorpicker (update state :colorpicker
(fn [{:keys [stops editing-stop] :as state}] (fn [{:keys [stops editing-stop] :as state}]
(let [cap-stops? (or (features/active-feature? state "render-wasm/v1") (contains? cfg/flags :frontend-binary-fills)) (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 can-add-stop?
(if (cc/uniform-spread? stops) (if (cc/uniform-spread? stops)
;; Add to uniform ;; Add to uniform
@ -869,7 +903,7 @@
(fn [state] (fn [state]
(let [stops (:stops state) (let [stops (:stops state)
cap-stops? (or (features/active-feature? state "render-wasm/v1") (contains? cfg/flags :frontend-binary-fills)) 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) (if can-add-stop? (let [new-stop (-> (cc/interpolate-gradient stops offset)
(split-color-components)) (split-color-components))
stops (conj stops new-stop) stops (conj stops new-stop)
@ -889,8 +923,12 @@
(update state :colorpicker (update state :colorpicker
(fn [state] (fn [state]
(let [stop (or (:editing-stop state) 0) (let [stop (or (:editing-stop state) 0)
cap-stops? (or (features/active-feature? state "render-wasm/v1") (contains? cfg/flags :frontend-binary-fills)) cap-stops? (or (features/active-feature? state "render-wasm/v1")
stops (mapv split-color-components (if cap-stops? (take shp/MAX-GRADIENT-STOPS stops) stops))] (contains? cfg/flags :frontend-binary-fills))
stops (mapv split-color-components
(if cap-stops?
(take types.fill/MAX-GRADIENT-STOPS stops)
stops))]
(-> state (-> state
(assoc :current-color (get stops stop)) (assoc :current-color (get stops stop))
(assoc :stops stops)))))))) (assoc :stops stops))))))))
@ -953,8 +991,7 @@
(update :current-color #(if (not= type :image) (dissoc % :image) %)) (update :current-color #(if (not= type :image) (dissoc % :image) %))
;; current color can be a library one ;; current color can be a library one
;; I'm changing via colorpicker ;; I'm changing via colorpicker
(d/dissoc-in [:current-color :id]) (update :current-color dissoc :ref-id :ref-file))]
(d/dissoc-in [:current-color :file-id]))]
(if-let [stop (:editing-stop state)] (if-let [stop (:editing-stop state)]
(update-in state [:stops stop] (fn [data] (->> changes (update-in state [:stops stop] (fn [data] (->> changes
(merge data) (merge data)
@ -966,15 +1003,17 @@
(assoc :type :color)))))))) (assoc :type :color))))))))
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
(let [selected-type (-> state (let [state (get-color-from-colorpicker-state (:colorpicker state))
:colorpicker type (get state :type)
:type)
formated-color (get-color-from-colorpicker-state (:colorpicker state))
;; Type is set to color on closing the colorpicker, but we ;; Type is set to color on closing the colorpicker, but we
;; can can close it while still uploading an image fill ;; 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?)) (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 (defn update-colorpicker-gradient
[changes] [changes]

View file

@ -50,7 +50,6 @@
[app.main.store :as st] [app.main.store :as st]
[app.util.color :as uc] [app.util.color :as uc]
[app.util.i18n :refer [tr]] [app.util.i18n :refer [tr]]
[app.util.storage :as storage]
[app.util.time :as dt] [app.util.time :as dt]
[beicon.v2.core :as rx] [beicon.v2.core :as rx]
[cuerdas.core :as str] [cuerdas.core :as str]
@ -119,11 +118,8 @@
(assoc :name (or (get-in color [:image :name]) (assoc :name (or (get-in color [:image :name])
(:color color) (:color color)
(uc/gradient-type->string (get-in color [:gradient :type])))) (uc/gradient-type->string (get-in color [:gradient :type]))))
(d/without-nils))] (d/without-nils)
(ctc/check-library-color))]
(dm/assert!
"expect valid color structure"
(ctc/check-color color))
(ptk/reify ::add-color (ptk/reify ::add-color
ev/Event ev/Event
@ -138,22 +134,6 @@
(fn [state] (assoc-in state [:workspace-local :color-for-rename] (:id color)))) (fn [state] (assoc-in state [:workspace-local :color-for-rename] (:id color))))
(dch/commit-changes changes)))))))) (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 (def clear-color-for-rename
(ptk/reify ::clear-color-for-rename (ptk/reify ::clear-color-for-rename
ptk/UpdateEvent ptk/UpdateEvent
@ -176,16 +156,10 @@
(defn update-color (defn update-color
[color file-id] [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 (ptk/reify ::update-color
ptk/WatchEvent ptk/WatchEvent
(watch [it state _] (watch [it state _]
@ -194,15 +168,10 @@
(defn update-color-data (defn update-color-data
"Update color data without affecting the path location" "Update color data without affecting the path location"
[color file-id] [color file-id]
(let [color (d/without-nils color)] (assert (uuid? file-id) "expected a uuid instance for `file-id`")
(dm/assert! (let [color (-> (d/without-nils color)
"expected valid color data structure" (ctc/check-library-color))]
(ctc/check-color color))
(dm/assert!
"expected file-id"
(uuid? file-id))
(ptk/reify ::update-color-data (ptk/reify ::update-color-data
ptk/WatchEvent ptk/WatchEvent
@ -213,17 +182,10 @@
;; FIXME: revisit why file-id is passed on the event ;; FIXME: revisit why file-id is passed on the event
(defn rename-color (defn rename-color
[file-id id new-name] [file-id id new-name]
(dm/assert!
"expected valid uuid for `id`"
(uuid? id))
(dm/assert! (assert (uuid? id) "expected valid uuid instance for `id`")
"expected valid uuid for `file-id`" (assert (uuid? file-id) "expected a uuid instance for `file-id`")
(uuid? file-id)) (assert (string? new-name) "expected a string instance for `new-name`")
(dm/assert!
"expected valid string for `new-name`"
(string? new-name))
(ptk/reify ::rename-color (ptk/reify ::rename-color
ptk/WatchEvent ptk/WatchEvent
@ -232,14 +194,16 @@
(if (str/empty? new-name) (if (str/empty? new-name)
(rx/empty) (rx/empty)
(let [data (dsh/lookup-file-data state) (let [data (dsh/lookup-file-data state)
color (get-in data [:colors id]) color (-> (ctc/get-color data id)
color (assoc color :name new-name) (assoc :name new-name)
color (d/without-nils color)] (d/without-nils)
(ctc/check-library-color))]
(update-color* it state color file-id))))))) (update-color* it state color file-id)))))))
(defn delete-color (defn delete-color
[{:keys [id] :as params}] [{:keys [id] :as params}]
(dm/assert! (uuid? id)) (assert (uuid? id) "expected valid uuid instance for `id`")
(ptk/reify ::delete-color (ptk/reify ::delete-color
ev/Event ev/Event
(-data [_] {:id id}) (-data [_] {:id id})
@ -252,6 +216,7 @@
(pcb/delete-color id))] (pcb/delete-color id))]
(rx/of (dch/commit-changes changes)))))) (rx/of (dch/commit-changes changes))))))
;; FIXME: this should be deleted
(defn add-media (defn add-media
[media] [media]
(let [media (ctf/check-file-media media)] (let [media (ctf/check-file-media media)]

View file

@ -35,6 +35,9 @@
[promesa.core :as p] [promesa.core :as p]
[tubax.core :as tubax])) [tubax.core :as tubax]))
(def accept-image-types
(str/join "," media/image-types))
(defn- optimize (defn- optimize
[input] [input]
(svgo/optimize input svgo/defaultOptions)) (svgo/optimize input svgo/defaultOptions))

View file

@ -16,6 +16,7 @@
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.math :as mth] [app.common.math :as mth]
[app.common.text :as txt] [app.common.text :as txt]
[app.common.types.fill :as types.fill]
[app.common.types.modifiers :as ctm] [app.common.types.modifiers :as ctm]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.main.data.event :as ev] [app.main.data.event :as ev]
@ -234,9 +235,9 @@
;; --- Helpers ;; --- Helpers
(defn to-new-fills (defn- to-new-fills
[data] [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 (defn- shape-current-values
[shape pred attrs] [shape pred attrs]
@ -246,9 +247,7 @@
(if (txt/is-text-node? node) (if (txt/is-text-node? node)
(let [fills (let [fills
(cond (cond
(or (some? (:fill-color node)) (types.fill/has-valid-fill-attrs? node)
(some? (:fill-opacity node))
(some? (:fill-color-gradient node)))
(to-new-fills node) (to-new-fills node)
(some? (:fills node)) (some? (:fills node))
@ -474,13 +473,13 @@
(defn migrate-node (defn migrate-node
[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 (cond-> node
(nil? (:fills node)) (nil? (:fills node))
(assoc :fills []) (assoc :fills [])
;; Migrate old colors and remove the old fromat ;; 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) (-> (dissoc :fill-color :fill-opacity :fill-color-ref-id :fill-color-ref-file :fill-color-gradient)
(update :fills conj color-attrs)) (update :fills conj color-attrs))

View file

@ -15,8 +15,7 @@
(defn- color-title (defn- color-title
[color-item] [color-item]
(let [name (:name color-item) (let [{:keys [name path]} (meta color-item)
path (:path color-item)
path-and-name (if path (str path " / " name) name) path-and-name (if path (str path " / " name) name)
gradient (:gradient color-item) gradient (:gradient color-item)
image (:image color-item) image (:image color-item)
@ -107,7 +106,8 @@
(mf/defc color-name (mf/defc color-name
{::mf/wrap-props false} {::mf/wrap-props false}
[{:keys [color size on-click on-double-click origin]}] [{: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)) (when (or (not size) (> size 64))
[:span {:class (stl/css-case [:span {:class (stl/css-case
:color-text (and (= origin :palette) (< size 72)) :color-text (and (= origin :palette) (< size 72))

View file

@ -29,6 +29,11 @@
[okulary.core :as l] [okulary.core :as l]
[rumext.v2 :as mf])) [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 (defn- use-page-title
[team section] [team section]
(mf/with-effect [team] (mf/with-effect [team]
@ -180,7 +185,7 @@
:tab-index "0"} :tab-index "0"}
[:span (tr "labels.add-custom-font")] [:span (tr "labels.add-custom-font")]
[:& file-uploader {:input-id "font-upload" [:& file-uploader {:input-id "font-upload"
:accept cm/str-font-types :accept accept-font-types
:multi true :multi true
:ref input-ref :ref input-ref
:on-selected on-selected}]] :on-selected on-selected}]]

View file

@ -21,12 +21,16 @@
(defn- color-title (defn- color-title
[color-item] [color-item]
(let [name (:name color-item) (let [{:keys [name path]} (meta color-item)
path (:path color-item)
path-and-name (if (and path (not (str/empty? path))) (str path " / " name) name) path-and-name
(if (and path (not (str/empty? path)))
(str path " / " name)
name)
gradient (:gradient color-item) gradient (:gradient color-item)
image (:image color-item) image (:image color-item)
color (:color color-item)] color (:color color-item)]
(if (some? name) (if (some? name)
(cond (cond
@ -71,34 +75,38 @@
;; automatically convert them to clojure map (which is exactly ;; automatically convert them to clojure map (which is exactly
;; what this component expects). On normal usage of this ;; what this component expects). On normal usage of this
;; component this code should be always fallback to else case. ;; component this code should be always fallback to else case.
background (if (object? background) background (if (object? background)
(json/->clj background) (json/->clj background)
background) background)
read-only? (nil? on-click) read-only? (nil? on-click)
id? (some? (:id background)) id? (some? (:ref-id background))
element-type (if read-only? "div" "button") element-type (if read-only? "div" "button")
button-type (if (not read-only?) "button" nil) button-type (if (not read-only?) "button" nil)
size (or size "small") size (or size "small")
active (or active false) active (or active false)
gradient-type (-> background :gradient :type) gradient-type (-> background :gradient :type)
gradient-stops (-> background :gradient :stops) gradient-stops (-> background :gradient :stops)
gradient-data {:type gradient-type gradient-data {:type gradient-type
:stops gradient-stops} :stops gradient-stops}
image (:image background) image (:image background)
format (if id? "rounded" "square") format (if id? "rounded" "square")
class (dm/str class " " (stl/css-case
:swatch true class
:small (= size "small") (dm/str class " " (stl/css-case
:medium (= size "medium") :swatch true
:large (= size "large") :small (= size "small")
:square (= format "square") :medium (= size "medium")
:active (= active true) :large (= size "large")
:interactive (= element-type "button") :square (= format "square")
:rounded (= format "rounded"))) :active (= active true)
props (mf/spread-props props {:class class :interactive (= element-type "button")
:on-click on-click :rounded (= format "rounded")))
:type button-type
:title (color-title background)})] props
(mf/spread-props props {:class class
:on-click on-click
:type button-type
:title (color-title background)})]
[:> element-type props [:> element-type props
(cond (cond

View file

@ -93,7 +93,8 @@ export const RadialGradient = {
export const Rounded = { export const Rounded = {
args: { args: {
background: { background: {
id: helpers.generateUuid(), refId: helpers.generateUuid(),
refFile: helpers.generateUuid(),
color: "#2f226c", color: "#2f226c",
opacity: 0.5, opacity: 0.5,
}, },

View file

@ -26,18 +26,20 @@
(def file-colors-ref (def file-colors-ref
(l/derived (l/in [:viewer :file :data :colors]) st/state)) (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 (let [get-library
(fn [state] (fn [state]
(get-in state [libraries-place file-id :data :colors]))] (get-in state [libraries-place file-id :data :colors]))]
(l/derived get-library st/state))) (l/derived get-library st/state)))
(defn- use-colors-library [color] (defn- use-colors-library
(-> (mf/use-memo [{:keys [ref-file] :as color}]
(mf/deps (:file-id color)) (let [library (mf/with-memo [ref-file]
#(make-colors-library-ref :files (:file-id color))) (make-colors-library-ref :files ref-file))]
mf/deref)) (mf/deref library)))
;; FIXME: this breaks react hooks rule (broken code)
(defn- get-file-colors [] (defn- get-file-colors []
(or (mf/deref file-colors-ref) (mf/deref refs/workspace-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]}] (mf/defc color-row [{:keys [color format copy-data on-change-format]}]
(let [colors-library (use-colors-library color) (let [colors-library (use-colors-library color)
file-colors (get-file-colors) file-colors (get-file-colors)
color-library-name (get-in (or colors-library file-colors) [(:id color) :name]) color-library-name (get-in (or colors-library file-colors) [(:ref-id color) :name])
color (assoc color :color-library-name color-library-name) color (assoc color :name color-library-name)
image (:image color)] image (:image color)]

View file

@ -7,6 +7,7 @@
(ns app.main.ui.inspect.attributes.fill (ns app.main.ui.inspect.attributes.fill
(:require-macros [app.main.style :as stl]) (:require-macros [app.main.style :as stl])
(:require (:require
[app.common.types.color :as types.color]
[app.main.ui.components.title-bar :refer [inspect-title-bar*]] [app.main.ui.components.title-bar :refer [inspect-title-bar*]]
[app.main.ui.inspect.attributes.common :refer [color-row]] [app.main.ui.inspect.attributes.common :refer [color-row]]
[app.util.code-gen.style-css :as css] [app.util.code-gen.style-css :as css]
@ -15,14 +16,6 @@
(def properties [:background :background-color :background-image]) (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] (defn has-fill? [shape]
(and (and
(not (contains? #{:text :group} (:type shape))) (not (contains? #{:text :group} (:type shape)))
@ -35,7 +28,10 @@
[{:keys [objects shape]}] [{:keys [objects shape]}]
(let [format* (mf/use-state :hex) (let [format* (mf/use-state :hex)
format (deref format*) 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 on-change
(mf/use-fn (mf/use-fn
(fn [format] (fn [format]

View file

@ -10,6 +10,7 @@
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.text :as txt] [app.common.text :as txt]
[app.common.types.color :as types.color]
[app.main.fonts :as fonts] [app.main.fonts :as fonts]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
@ -34,14 +35,6 @@
(get-in state [:viewer-libraries file-id :data :typographies]))] (get-in state [:viewer-libraries file-id :data :typographies]))]
#(l/derived get-library st/state))) #(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 (defn copy-style-data
[style & properties] [style & properties]
(->> properties (->> properties
@ -73,7 +66,7 @@
(for [[idx fill] (map-indexed vector (:fills style))] (for [[idx fill] (map-indexed vector (:fills style))]
[:& color-row {:key idx [:& color-row {:key idx
:format @color-format :format @color-format
:color (fill->color fill) :color (types.color/fill->color fill)
:copy-data (copy-style-data fill :fill-color :fill-color-gradient) :copy-data (copy-style-data fill :fill-color :fill-color-gradient)
:on-change-format #(reset! color-format %)}])) :on-change-format #(reset! color-format %)}]))

View file

@ -11,7 +11,6 @@
[app.common.types.color :as ctc] [app.common.types.color :as ctc]
[app.main.data.event :as ev] [app.main.data.event :as ev]
[app.main.data.workspace.colors :as mdc] [app.main.data.workspace.colors :as mdc]
[app.main.data.workspace.libraries :as dwl]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.color-bullet :as cb] [app.main.ui.components.color-bullet :as cb]
@ -34,25 +33,29 @@
(mf/use-fn (mf/use-fn
(mf/deps color selected) (mf/deps color selected)
(fn [event] (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)) (mdc/apply-color-from-palette color (kbd/alt? event))
(when (not= selected :recent) (when (not= selected :recent)
(ptk/data-event ::ev/event (ptk/data-event ::ev/event
{::ev/name "use-library-color" {::ev/name "use-library-color"
::ev/origin "color-palette" ::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 [:button {:class (stl/css-case
:color-cell true :color-cell true
:is-not-library-color (nil? (:id color)) :is-not-library-color (nil? (:id color))
:no-text (<= size 64)) :no-text (<= size 64))
:title (uc/get-color-name color) :title title
:aria-label (uc/get-color-name color) :aria-label title
:type "button" :type "button"
:on-click select-color} :on-click select-color}
[:> swatch* {:background color :size "medium"}] [:> swatch* {:background color :size "medium"}]
[:& cb/color-name {:color color :size size :origin :palette}]])) [:& cb/color-name {:color color :size size :origin :palette}]]))
(mf/defc palette* (mf/defc palette*
{::mf/wrap [mf/memo]}
[{:keys [colors size width selected]}] [{:keys [colors size width selected]}]
(let [state (mf/use-state #(do {:show-menu false})) (let [state (mf/use-state #(do {:show-menu false}))
offset-step (cond offset-step (cond
@ -121,9 +124,11 @@
(when (not= 0 (:offset @state)) (when (not= 0 (:offset @state))
(swap! state assoc :offset 0))) (swap! state assoc :offset 0)))
[:div {:class (stl/css-case :color-palette true [:div {:class (stl/css-case
:no-text (< size 64)) :color-palette true
:style #js {"--bullet-size" (dm/str bullet-size "px") "--color-cell-width" (dm/str color-cell-width "px")}} :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? (when show-arrows?
[:button {:class (stl/css :left-arrow) [:button {:class (stl/css :left-arrow)
@ -154,14 +159,25 @@
(mf/defc recent-colors-palette* (mf/defc recent-colors-palette*
{::mf/private true} {::mf/private true}
[props] [props]
(let [colors (mf/deref refs/recent-colors) (let [libraries (mf/deref refs/files)
colors (mf/deref refs/recent-colors)
colors (mf/with-memo [colors] colors (mf/with-memo [colors libraries]
(->> (reverse colors) (->> (reverse colors)
(filter ctc/valid-color?) (filter ctc/valid-color?)
(vec))) (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})] props (mf/spread-props props {:colors colors})]
[:> palette* props])) [:> palette* props]))
(defn- make-library-colors-ref (defn- make-library-colors-ref
@ -178,9 +194,9 @@
colors (mf/deref colors-ref) colors (mf/deref colors-ref)
colors (mf/with-memo [colors file-id] colors (mf/with-memo [colors file-id]
(->> (vals colors) (->> (vals colors)
(filter ctc/valid-color?) (filter ctc/valid-library-color?)
(map #(assoc % :file-id file-id))
(sort-by :name) (sort-by :name)
(map #(ctc/library-color->color % file-id))
(vec))) (vec)))
props (mf/spread-props props {:colors colors})] props (mf/spread-props props {:colors colors})]

View file

@ -12,7 +12,7 @@
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.geom.matrix :as gmt] [app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.types.shape :as shp] [app.common.types.fill :as types.fill]
[app.config :as cfg] [app.config :as cfg]
[app.main.data.event :as-alias ev] [app.main.data.event :as-alias ev]
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
@ -134,6 +134,7 @@
on-fill-image-success on-fill-image-success
(mf/use-fn (mf/use-fn
(fn [image] (fn [image]
;; FIXME: revisit
(st/emit! (dc/update-colorpicker-color (st/emit! (dc/update-colorpicker-color
{:image (-> (select-keys image [:id :width :height :mtype :name]) {:image (-> (select-keys image [:id :width :height :mtype :name])
(assoc :keep-aspect-ratio true))} (assoc :keep-aspect-ratio true))}
@ -200,10 +201,9 @@
(fn [_ color] (fn [_ color]
(if (and (some? (:color color)) (some? (:gradient data))) (if (and (some? (:color color)) (some? (:gradient data)))
(handle-change-color {:hex (:color color) :alpha (:opacity color)}) (handle-change-color {:hex (:color color) :alpha (:opacity color)})
(do (let [color (d/without-qualified color)]
(st/emit! (st/emit! (dc/add-recent-color color)
(dwl/add-recent-color color) (dc/apply-color-from-colorpicker color))
(dc/apply-color-from-colorpicker color))
(on-change color))))) (on-change color)))))
on-add-library-color on-add-library-color
@ -439,7 +439,7 @@
(when (= selected-mode :gradient) (when (= selected-mode :gradient)
[:> gradients* [:> gradients*
{:type (:type state) {: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) :editing-stop (:editing-stop state)
:on-stop-edit-start handle-stop-edit-start :on-stop-edit-start handle-stop-edit-start
:on-stop-edit-finish handle-stop-edit-finish :on-stop-edit-finish handle-stop-edit-finish

View file

@ -11,7 +11,7 @@
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.math :as mth] [app.common.math :as mth]
[app.common.types.shape :as shp] [app.common.types.fill :as types.fill]
[app.config :as cfg] [app.config :as cfg]
[app.main.features :as features] [app.main.features :as features]
[app.main.ui.components.numeric-input :refer [numeric-input*]] [app.main.ui.components.numeric-input :refer [numeric-input*]]
@ -288,7 +288,7 @@
(when on-reverse-stops (when on-reverse-stops
(on-reverse-stops)))) (on-reverse-stops))))
cap-stops? (or (features/use-feature "render-wasm/v1") (contains? cfg/flags :frontend-binary-fills)) 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-panel)}
[:div {:class (stl/css :gradient-preview)} [:div {:class (stl/css :gradient-preview)}

View file

@ -108,16 +108,15 @@
(filter valid-color?) (filter valid-color?)
(map-indexed (fn [index color] (map-indexed (fn [index color]
(let [color (if (map? color) color {:color 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)) (sort c/sort-colors))
(->> (dm/get-in libraries [file-id :data :colors]) (->> (dm/get-in libraries [file-id :data :colors])
(vals) (vals)
(filter valid-color?) (filter valid-color?)
(sort-by :name)
(map #(ctc/library-color->color % file-id))
(map-indexed (fn [index color] (map-indexed (fn [index color]
(-> color (vary-meta color assoc ::id (dm/str index))))))]
(assoc :file-id file-id)
(assoc ::id (dm/str index)))))
(sort-by :name)))]
(reset! current-colors* colors))) (reset! current-colors* colors)))
@ -142,6 +141,6 @@
(for [color current-colors] (for [color current-colors]
[:& cb/color-bullet [:& cb/color-bullet
{:key (dm/str "color-" (::id color)) {:key (-> color meta ::id)
:color color :color color
:on-click on-color-click}])]])) :on-click on-color-click}])]]))

View file

@ -9,7 +9,7 @@
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [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.components-list :as ctkl]
[app.common.types.file :as ctf] [app.common.types.file :as ctf]
[app.common.types.typographies-list :as ctyl] [app.common.types.typographies-list :as ctyl]
@ -407,7 +407,7 @@
(sort-by #(str/lower (:name %))) (sort-by #(str/lower (:name %)))
(truncate :components)) (truncate :components))
colors (->> color-ids colors (->> color-ids
(map #(ctcl/get-color (:data library) %)) (map #(ctc/get-color (:data library) %))
(sort-by #(str/lower (:name %))) (sort-by #(str/lower (:name %)))
(truncate :colors)) (truncate :colors))
typographies (->> typography-ids typographies (->> typography-ids

View file

@ -42,8 +42,7 @@
(let [color (mf/with-memo [color file-id] (let [color (mf/with-memo [color file-id]
(cond-> color (cond-> color
(:value color) (assoc :color (:value color) :opacity 1) (:value color) (assoc :color (:value color) :opacity 1)
(:value color) (dissoc :value) (:value color) (dissoc :value)))
:always (assoc :file-id file-id)))
color-id (:id color) color-id (:id color)
@ -78,7 +77,6 @@
(let [name (cfh/merge-path-item (:path color) (:name color)) (let [name (cfh/merge-path-item (:path color) (:name color))
color (-> attrs color (-> attrs
(assoc :id (:id color)) (assoc :id (:id color))
(assoc :file-id file-id)
(assoc :name name))] (assoc :name name))]
(st/emit! (dwl/update-color color file-id))))) (st/emit! (dwl/update-color color file-id)))))
@ -177,7 +175,7 @@
on-click on-click
(mf/use-fn (mf/use-fn
(mf/deps color on-asset-click read-only?) (mf/deps color on-asset-click read-only? file-id)
(fn [event] (fn [event]
(when-not read-only? (when-not read-only?
(st/emit! (ptk/data-event ::ev/event (st/emit! (ptk/data-event ::ev/event
@ -186,8 +184,7 @@
:external-library (not local?)})) :external-library (not local?)}))
(when-not (on-asset-click event (:id color)) (when-not (on-asset-click event (:id color))
(st/emit! (dwl/add-recent-color color) (st/emit! (dc/apply-color-from-assets file-id color (kbd/alt? event)))))))]
(dc/apply-color-from-palette color (kbd/alt? event)))))))]
(mf/with-effect [editing?] (mf/with-effect [editing?]
(when editing? (when editing?

View file

@ -10,7 +10,6 @@
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.files.helpers :as cfh] [app.common.files.helpers :as cfh]
[app.common.media :as cm]
[app.common.types.component :as ctc] [app.common.types.component :as ctc]
[app.common.types.file :as ctf] [app.common.types.file :as ctf]
[app.main.data.event :as ev] [app.main.data.event :as ev]
@ -525,7 +524,7 @@
:aria-label (tr "workspace.assets.components.add-component") :aria-label (tr "workspace.assets.components.add-component")
:on-click add-component :on-click add-component
:icon "add"} :icon "add"}
[:& file-uploader {:accept cm/str-image-types [:& file-uploader {:accept dwm/accept-image-types
:multi true :multi true
:ref input-ref :ref input-ref
:on-selected on-file-selected}]])] :on-selected on-file-selected}]])]

View file

@ -10,7 +10,7 @@
[app.common.colors :as clr] [app.common.colors :as clr]
[app.common.data :as d] [app.common.data :as d]
[app.common.types.color :as ctc] [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.common.types.shape.attrs :refer [default-color]]
[app.config :as cfg] [app.config :as cfg]
[app.main.data.workspace.colors :as dc] [app.main.data.workspace.colors :as dc]
@ -24,6 +24,7 @@
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
;; FIXME:revisit this
(def fill-attrs (def fill-attrs
[:fills [:fills
:fill-color :fill-color
@ -55,12 +56,12 @@
;; Excluding nil values ;; Excluding nil values
values (d/without-nils values) values (d/without-nils values)
fills (if (contains? cfg/flags :frontend-binary-fills) 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)) (:fills values))
has-fills? (or (= :multiple fills) (some? (seq fills))) has-fills? (or (= :multiple fills) (some? (seq fills)))
can-add-fills? (if (contains? cfg/flags :frontend-binary-fills) can-add-fills? (if (contains? cfg/flags :frontend-binary-fills)
(and (not (= :multiple fills)) (and (not (= :multiple fills))
(< (count fills) shp/MAX-FILLS)) (< (count fills) types.fill/MAX-FILLS))
(not (= :multiple fills))) (not (= :multiple fills)))
state* (mf/use-state has-fills?) state* (mf/use-state has-fills?)
@ -176,7 +177,7 @@
(seq fills) (seq fills)
[:& h/sortable-container {} [:& h/sortable-container {}
(for [[index value] (d/enumerate fills)] (for [[index value] (d/enumerate fills)]
[:> color-row* {:color (ctc/fill->shape-color value) [:> color-row* {:color (ctc/fill->color value)
:key index :key index
:index index :index index
:title (tr "workspace.options.fill") :title (tr "workspace.options.fill")

View file

@ -10,6 +10,7 @@
[app.common.colors :as clr] [app.common.colors :as clr]
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.types.shape.shadow :as ctss]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.main.data.workspace.colors :as dc] [app.main.data.workspace.colors :as dc]
[app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.shapes :as dwsh]
@ -91,7 +92,10 @@
(mf/use-fn (mf/deps index) #(on-update index :blur %)) (mf/use-fn (mf/deps index) #(on-update index :blur %))
on-update-color 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 on-detach-color
(mf/use-fn (mf/deps index) #(on-detach-color index)) (mf/use-fn (mf/deps index) #(on-detach-color index))
@ -275,8 +279,13 @@
(mf/use-fn (mf/use-fn
(fn [index attr value] (fn [index attr value]
(let [ids (mf/ref-val ids-ref)] (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-set)}
[:div {:class (stl/css :element-title)} [:div {:class (stl/css :element-title)}
[:& title-bar {:collapsable has-shadows? [:& title-bar {:collapsable has-shadows?

View file

@ -12,7 +12,7 @@
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.types.shape.attrs :refer [default-color]] [app.common.types.shape.attrs :refer [default-color]]
[app.main.data.modal :as modal] [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.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.color-bullet :as cb] [app.main.ui.components.color-bullet :as cb]
@ -116,7 +116,7 @@
(let [color (-> color (let [color (-> color
(assoc :color value) (assoc :color value)
(dissoc :gradient))] (dissoc :gradient))]
(st/emit! (dwl/add-recent-color color) (st/emit! (dwc/add-recent-color color)
(on-change color))))) (on-change color)))))
handle-opacity-change handle-opacity-change
@ -126,7 +126,7 @@
(let [color (-> color (let [color (-> color
(assoc :opacity (/ value 100)) (assoc :opacity (/ value 100))
(dissoc :ref-id :ref-file))] (dissoc :ref-id :ref-file))]
(st/emit! (dwl/add-recent-color color) (st/emit! (dwc/add-recent-color color)
(on-change color))))) (on-change color)))))
handle-click-color handle-click-color

View file

@ -149,7 +149,7 @@
;; Stroke Color ;; Stroke Color
;; FIXME: memorize stroke color ;; FIXME: memorize stroke color
[:> color-row* {:color (ctc/stroke->shape-color stroke) [:> color-row* {:color (ctc/stroke->color stroke)
:index index :index index
:title title :title title
:on-change on-color-change-refactor :on-change on-color-change-refactor

View file

@ -9,7 +9,6 @@
(:require (:require
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.media :as cm]
[app.main.data.event :as ev] [app.main.data.event :as ev]
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
[app.main.data.workspace :as dw] [app.main.data.workspace :as dw]
@ -64,7 +63,7 @@
i/img i/img
[:& file-uploader [:& file-uploader
{:input-id "image-upload" {:input-id "image-upload"
:accept cm/str-image-types :accept dwm/accept-image-types
:multi true :multi true
:ref ref :ref ref
:on-selected on-selected}]]])) :on-selected on-selected}]]]))

View file

@ -15,7 +15,7 @@
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.geom.shapes.points :as gsp] [app.common.geom.shapes.points :as gsp]
[app.common.math :as mth] [app.common.math :as mth]
[app.common.types.shape :as shp] [app.common.types.fill :as types.fill]
[app.config :as cfg] [app.config :as cfg]
[app.main.data.workspace.colors :as dc] [app.main.data.workspace.colors :as dc]
[app.main.features :as features] [app.main.features :as features]
@ -135,7 +135,7 @@
handler-state (mf/use-state {:display? false :offset 0 :hover nil}) 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)) 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 endpoint-on-pointer-down
(fn [position event] (fn [position event]
@ -527,7 +527,7 @@
gradient (:gradient state) gradient (:gradient state)
cap-stops? (or (features/use-feature "render-wasm/v1") (contains? cfg/flags :frontend-binary-fills)) cap-stops? (or (features/use-feature "render-wasm/v1") (contains? cfg/flags :frontend-binary-fills))
stops (if cap-stops? stops (if cap-stops?
(vec (take shp/MAX-GRADIENT-STOPS (:stops state))) (vec (take types.fill/MAX-GRADIENT-STOPS (:stops state)))
(:stops state)) (:stops state))
editing-stop (:editing-stop state)] editing-stop (:editing-stop state)]

View file

@ -176,10 +176,10 @@
(or (not (array? shapes)) (not (every? shape/shape-proxy? shapes))) (or (not (array? shapes)) (not (every? shape/shape-proxy? shapes)))
(u/display-not-valid :replaceColor-shapes 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) (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) (u/display-not-valid :replaceColor-newColor new-color)
:else :else

View file

@ -139,6 +139,8 @@
:path path :path path
:color color :color color
:opacity opacity :opacity opacity
:refId (format-id ref-id)
:refFile (format-id ref-file)
:gradient (format-gradient gradient) :gradient (format-gradient gradient)
:image (format-image image)})))) :image (format-image image)}))))
@ -188,6 +190,7 @@
;; fillColorRefId?: string; ;; fillColorRefId?: string;
;; fillImage?: ImageData; ;; fillImage?: ImageData;
;;} ;;}
(defn format-fill (defn format-fill
[{:keys [fill-color fill-opacity fill-color-gradient fill-color-ref-file fill-color-ref-id fill-image] :as fill}] [{:keys [fill-color fill-opacity fill-color-gradient fill-color-ref-file fill-color-ref-id fill-image] :as fill}]
(when (some? fill) (when (some? fill)

View file

@ -126,7 +126,7 @@
(fn [self value] (fn [self value]
(let [value (parser/parse-gradient value)] (let [value (parser/parse-gradient value)]
(cond (cond
(not (sm/validate ::ctc/gradient value)) (not (sm/validate ctc/schema:gradient value))
(u/display-not-valid :gradient value) (u/display-not-valid :gradient value)
(not (r/check-permission plugin-id "library:write")) (not (r/check-permission plugin-id "library:write"))
@ -144,7 +144,7 @@
(fn [self value] (fn [self value]
(let [value (parser/parse-image-data value)] (let [value (parser/parse-image-data value)]
(cond (cond
(not (sm/validate ::ctc/image-color value)) (not (sm/validate ctc/schema:image value))
(u/display-not-valid :image value) (u/display-not-valid :image value)
(not (r/check-permission plugin-id "library:write")) (not (r/check-permission plugin-id "library:write"))

View file

@ -20,6 +20,7 @@
[app.common.types.component :as ctk] [app.common.types.component :as ctk]
[app.common.types.container :as ctn] [app.common.types.container :as ctn]
[app.common.types.file :as ctf] [app.common.types.file :as ctf]
[app.common.types.fill :as types.fill]
[app.common.types.grid :as ctg] [app.common.types.grid :as ctg]
[app.common.types.path :as path] [app.common.types.path :as path]
[app.common.types.path.segment :as path.segm] [app.common.types.path.segment :as path.segm]
@ -708,7 +709,7 @@
id (:id shape) id (:id shape)
value (parser/parse-fills value)] value (parser/parse-fills value)]
(cond (cond
(not (sm/validate [:vector ::cts/fill] value)) (not (sm/validate [:vector types.fill/schema:fill] value))
(u/display-not-valid :fills value) (u/display-not-valid :fills value)
(cfh/text-shape? shape) (cfh/text-shape? shape)
@ -728,7 +729,7 @@
(let [id (obj/get self "$id") (let [id (obj/get self "$id")
value (parser/parse-strokes value)] value (parser/parse-strokes value)]
(cond (cond
(not (sm/validate [:vector ::cts/stroke] value)) (not (sm/validate [:vector cts/schema:stroke] value))
(u/display-not-valid :strokes value) (u/display-not-valid :strokes value)
(not (r/check-permission plugin-id "content:write")) (not (r/check-permission plugin-id "content:write"))

View file

@ -12,8 +12,8 @@
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.geom.matrix :as gmt] [app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.types.fill :as types.fill]
[app.common.types.path :as path] [app.common.types.path :as path]
[app.common.types.shape :as shp]
[app.common.types.shape.layout :as ctl] [app.common.types.shape.layout :as ctl]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.config :as cf] [app.config :as cf]
@ -240,7 +240,7 @@
(defn set-shape-fills (defn set-shape-fills
[fills] [fills]
(let [fills (take shp/MAX-FILLS fills) (let [fills (take types.fill/MAX-FILLS fills)
image-fills (filter :fill-image fills) image-fills (filter :fill-image fills)
offset (mem/alloc-bytes (* (count fills) sr-fills/FILL-BYTE-SIZE)) offset (mem/alloc-bytes (* (count fills) sr-fills/FILL-BYTE-SIZE))
heap (mem/get-heap-u8) heap (mem/get-heap-u8)
@ -302,7 +302,9 @@
(let [id (dm/get-prop image :id) (let [id (dm/get-prop image :id)
buffer (uuid/get-u32 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))] 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") (h/call wasm/internal-module "_add_shape_stroke_fill")
(when (== cached-image? 0) (when (== cached-image? 0)
(store-image id))) (store-image id)))

View file

@ -1,7 +1,7 @@
(ns app.render-wasm.serializers.fills (ns app.render-wasm.serializers.fills
(:require (:require
[app.common.data.macros :as dm] [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.common.uuid :as uuid]
[app.render-wasm.serializers.color :as clr])) [app.render-wasm.serializers.color :as clr]))
@ -41,7 +41,7 @@
end-x (:end-x gradient) end-x (:end-x gradient)
end-y (:end-y gradient) end-y (:end-y gradient)
width (or (:width gradient) 0) 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)] type (if (= (:type gradient) :linear) 0x01 0x02)]
(.setUint8 dview offset type true) (.setUint8 dview offset type true)
(.setFloat32 dview (+ offset 4) start-x true) (.setFloat32 dview (+ offset 4) start-x true)

View file

@ -82,7 +82,7 @@
(defn get-color-name (defn get-color-name
[color] [color]
(or (:color-library-name color) (or (:name (meta color))
(:name color) (:name color)
(:color color) (:color color)
(gradient-type->string (:type (:gradient color))))) (gradient-type->string (:type (:gradient color)))))

View file

@ -80,7 +80,7 @@
(fn [key val] (fn [key val]
(when (some? backend) (when (some? backend)
(if (some? val) (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))))) (.removeItem ^js backend (encode-key prefix key)))))
on-change* on-change*

View file

@ -7,11 +7,9 @@
(ns app.worker.import (ns app.worker.import
(:refer-clojure :exclude [resolve]) (:refer-clojure :exclude [resolve])
(:require (:require
[app.common.data :as d]
[app.common.json :as json] [app.common.json :as json]
[app.common.logging :as log] [app.common.logging :as log]
[app.common.schema :as sm] [app.common.schema :as sm]
[app.common.text :as ct]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.main.repo :as rp] [app.main.repo :as rp]
[app.util.http :as http] [app.util.http :as http]
@ -44,51 +42,6 @@
:method :get}) :method :get})
(rx/map :body)))) (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] (defn parse-mtype [ba]
(let [u8 (js/Uint8Array. ba 0 4) (let [u8 (js/Uint8Array. ba 0 4)
sg (areduce u8 i ret "" (str ret (if (zero? i) "" " ") (.toString (aget u8 i) 8)))] sg (areduce u8 i ret "" (str ret (if (zero? i) "" " ") (.toString (aget u8 i) 8)))]

View file

@ -3768,14 +3768,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"caniuse-lite@npm:^1.0.30001669": "caniuse-lite@npm:^1.0.30001669, caniuse-lite@npm:^1.0.30001702, caniuse-lite@npm:^1.0.30001716":
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":
version: 1.0.30001718 version: 1.0.30001718
resolution: "caniuse-lite@npm:1.0.30001718" resolution: "caniuse-lite@npm:1.0.30001718"
checksum: 10c0/67f9ad09bc16443e28d14f265d6e468480cd8dc1900d0d8b982222de80c699c4f2306599c3da8a3fa7139f110d4b30d49dbac78f215470f479abb6ffe141d5d3 checksum: 10c0/67f9ad09bc16443e28d14f265d6e468480cd8dc1900d0d8b982222de80c699c4f2306599c3da8a3fa7139f110d4b30d49dbac78f215470f479abb6ffe141d5d3
@ -5989,7 +5982,7 @@ __metadata:
sass: "npm:^1.89.0" sass: "npm:^1.89.0"
sass-embedded: "npm:^1.89.0" sass-embedded: "npm:^1.89.0"
sax: "npm:^1.4.1" sax: "npm:^1.4.1"
shadow-cljs: "npm:3.0.5" shadow-cljs: "npm:3.1.5"
source-map-support: "npm:^0.5.21" source-map-support: "npm:^0.5.21"
storybook: "npm:^8.6.14" storybook: "npm:^8.6.14"
style-dictionary: "npm:5.0.0-rc.1" style-dictionary: "npm:5.0.0-rc.1"
@ -10793,10 +10786,12 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"shadow-cljs@npm:3.0.5": "shadow-cljs@npm:3.1.5":
version: 3.0.5 version: 3.1.5
resolution: "shadow-cljs@npm:3.0.5" resolution: "shadow-cljs@npm:3.1.5"
dependencies: dependencies:
buffer: "npm:^6.0.3"
process: "npm:^0.11.10"
readline-sync: "npm:^1.4.10" readline-sync: "npm:^1.4.10"
shadow-cljs-jar: "npm:1.3.4" shadow-cljs-jar: "npm:1.3.4"
source-map-support: "npm:^0.5.21" source-map-support: "npm:^0.5.21"
@ -10804,7 +10799,7 @@ __metadata:
ws: "npm:^8.18.1" ws: "npm:^8.18.1"
bin: bin:
shadow-cljs: cli/runner.js shadow-cljs: cli/runner.js
checksum: 10c0/2c5f3976f7bec16b7fb9fbba5d4a7581e0d0157384a470ce0670120f02cfe6b9c7183102133e0e9b300cbe318e9a3b6001309f96840999ca2814d39fc83c23e8 checksum: 10c0/29da68f7645c258becf4074e4401e5c86dd3af04622c2e10fdac09824e9832290918d90aaf80ef7df0c35731f1b51b84101cbfd0c6819772a493173d4ae69415
languageName: node languageName: node
linkType: hard linkType: hard