diff --git a/common/src/app/common/files/helpers.cljc b/common/src/app/common/files/helpers.cljc index e73aed5ea..1b49099f2 100644 --- a/common/src/app/common/files/helpers.cljc +++ b/common/src/app/common/files/helpers.cljc @@ -200,7 +200,7 @@ result)))) (defn get-parent-seq - "Returns a vector of parents of the specified shape." + "Returns a lazy seq of parents of the specified shape." ([objects shape-id] (get-parent-seq objects (get objects shape-id) shape-id)) diff --git a/common/src/app/common/types/color.cljc b/common/src/app/common/types/color.cljc index c5cbb888b..f93f75617 100644 --- a/common/src/app/common/types/color.cljc +++ b/common/src/app/common/types/color.cljc @@ -118,10 +118,10 @@ (def valid-color? (sm/lazy-validator schema:color)) -(def check-color! +(def check-color (sm/check-fn schema:color :hint "expected valid color struct")) -(def check-recent-color! +(def check-recent-color (sm/check-fn schema:recent-color)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/common/src/app/common/types/shape.cljc b/common/src/app/common/types/shape.cljc index d32549782..5033965db 100644 --- a/common/src/app/common/types/shape.cljc +++ b/common/src/app/common/types/shape.cljc @@ -148,7 +148,7 @@ (sm/register! ::stroke schema:stroke) -(def check-stroke! +(def check-stroke (sm/check-fn schema:stroke)) (def schema:shape-base-attrs diff --git a/common/src/app/common/types/shape/shadow.cljc b/common/src/app/common/types/shape/shadow.cljc index b1ec5342f..1b37dd3e9 100644 --- a/common/src/app/common/types/shape/shadow.cljc +++ b/common/src/app/common/types/shape/shadow.cljc @@ -28,5 +28,5 @@ (sm/register! ::shadow schema:shadow) -(def check-shadow! +(def check-shadow (sm/check-fn schema:shadow)) diff --git a/common/src/app/common/types/tokens_lib.cljc b/common/src/app/common/types/tokens_lib.cljc index 8249b9a8a..71e7f66c1 100644 --- a/common/src/app/common/types/tokens_lib.cljc +++ b/common/src/app/common/types/tokens_lib.cljc @@ -286,11 +286,10 @@ Optionally use `update-token-fn` option to transform the token." [tokens & {:keys [update-token-fn] :or {update-token-fn identity}}] - (reduce - (fn [acc [_ token]] - (let [path (split-token-path (:name token))] - (assoc-in acc path (update-token-fn token)))) - {} tokens)) + (reduce-kv (fn [acc _ token] + (let [path (split-token-path (:name token))] + (assoc-in acc path (update-token-fn token)))) + {} tokens)) (defn backtrace-tokens-tree "Convert tokens into a nested tree with their `:name` as the path. diff --git a/frontend/src/app/main/data/workspace/colors.cljs b/frontend/src/app/main/data/workspace/colors.cljs index 0fd1239f1..5b2753775 100644 --- a/frontend/src/app/main/data/workspace/colors.cljs +++ b/frontend/src/app/main/data/workspace/colors.cljs @@ -13,8 +13,8 @@ [app.common.schema :as sm] [app.common.text :as txt] [app.common.types.color :as ctc] - [app.common.types.shape :refer [check-stroke!]] - [app.common.types.shape.shadow :refer [check-shadow!]] + [app.common.types.shape :refer [check-stroke]] + [app.common.types.shape.shadow :refer [check-shadow]] [app.main.broadcast :as mbc] [app.main.data.event :as ev] [app.main.data.helpers :as dsh] @@ -81,19 +81,45 @@ (assoc-in [:workspace-global :picked-color-select] value) (assoc-in [:workspace-global :picked-shift?] shift?))))) + +(defn- split-text-shapes + "Split text shapes from non-text shapes" + [objects ids] + (loop [ids (seq ids) + text-ids [] + shape-ids []] + (if-let [id (first ids)] + (let [shape (get objects id)] + (if (cfh/text-shape? shape) + (recur (rest ids) + (conj text-ids id) + shape-ids) + (recur (rest ids) + text-ids + (conj shape-ids id)))) + [text-ids shape-ids]))) + +(defn assoc-shape-fill + [shape position fill] + (update shape :fills + (fn [fills] + (if (nil? fills) + [fill] + (assoc fills position fill))))) + (defn transform-fill - ([state ids color transform] (transform-fill state ids color transform nil)) + "A low level function that creates a shape fill transformations stream" + ([state ids color transform] + (transform-fill state ids color transform nil)) ([state ids color transform options] (let [page-id (or (get options :page-id) (get state :current-page-id)) objects (dsh/lookup-page-objects state page-id) - is-text? #(= :text (:type (get objects %))) - text-ids (filter is-text? ids) - shape-ids (remove is-text? ids) - undo-id (js/Symbol) + [text-ids shape-ids] + (split-text-shapes objects ids) - attrs + fill (cond-> {} (contains? color :color) (assoc :fill-color (:color color)) @@ -116,13 +142,12 @@ :always (d/without-nils)) - transform-attrs #(transform % attrs)] + transform-attrs #(transform % fill)] (rx/concat - (rx/of (dwu/start-undo-transaction undo-id)) - (rx/from (map #(dwt/update-text-with-function % transform-attrs options) text-ids)) - (rx/of (dwsh/update-shapes shape-ids transform-attrs options)) - (rx/of (dwu/commit-undo-transaction undo-id)))))) + (->> (rx/from text-ids) + (rx/map #(dwt/update-text-with-function % transform-attrs options))) + (rx/of (dwsh/update-shapes shape-ids transform-attrs options)))))) (defn swap-attrs [shape attr index new-index] (let [first (get-in shape [attr index]) @@ -154,12 +179,12 @@ (ptk/reify ::change-fill ptk/WatchEvent (watch [_ state _] - (let [change-fn (fn [shape attrs] - (-> shape - (cond-> (not (contains? shape :fills)) - (assoc :fills [])) - (assoc-in [:fills position] (into {} attrs))))] - (transform-fill state ids color change-fn options)))))) + (let [change-fn #(assoc-shape-fill %1 position %2) + undo-id (js/Symbol)] + (rx/concat + (rx/of (dwu/start-undo-transaction undo-id)) + (transform-fill state ids color change-fn options) + (rx/of (dwu/commit-undo-transaction undo-id)))))))) (defn change-fill-and-clear ([ids color] (change-fill-and-clear ids color nil)) @@ -167,68 +192,84 @@ (ptk/reify ::change-fill-and-clear ptk/WatchEvent (watch [_ state _] - (let [set (fn [shape attrs] (assoc shape :fills [attrs]))] - (transform-fill state ids color set options)))))) + (let [change-fn (fn [shape attrs] (assoc shape :fills [attrs])) + undo-id (js/Symbol)] + (rx/of (dwu/start-undo-transaction undo-id)) + (transform-fill state ids color change-fn options) + (rx/of (dwu/commit-undo-transaction undo-id))))))) (defn add-fill ([ids color] (add-fill ids color nil)) ([ids color options] - (dm/assert! - "expected a valid color struct" - (ctc/check-color! color)) + (assert + (ctc/check-color color) + "expected a valid color struct") - (dm/assert! - "expected a valid coll of uuid's" - (every? uuid? ids)) + (assert + (every? uuid? ids) + "expected a valid coll of uuid's") (ptk/reify ::add-fill ptk/WatchEvent (watch [_ state _] - (let [add (fn [shape attrs] - (-> shape - (update :fills #(into [attrs] %))))] - (transform-fill state ids color add options)))))) + (let [change-fn + (fn [shape attrs] + (-> shape + (update :fills #(into [attrs] %)))) + undo-id + (js/Symbol)] + + (rx/of (dwu/start-undo-transaction undo-id)) + (transform-fill state ids color change-fn options) + (rx/of (dwu/commit-undo-transaction undo-id))))))) (defn remove-fill ([ids color position] (remove-fill ids color position nil)) ([ids color position options] - (dm/assert! - "expected a valid color struct" - (ctc/check-color! color)) - - (dm/assert! - "expected a valid coll of uuid's" - (every? uuid? ids)) + (assert (ctc/check-color color) + "expected a valid color struct") + (assert (every? uuid? ids) + "expected a valid coll of uuid's") (ptk/reify ::remove-fill ptk/WatchEvent (watch [_ state _] - (let [remove-fill-by-index (fn [values index] (->> (d/enumerate values) - (filterv (fn [[idx _]] (not= idx index))) - (mapv second))) + (let [remove-fill-by-index + (fn [values index] + (->> (d/enumerate values) + (filterv (fn [[idx _]] (not= idx index))) + (mapv second))) - remove (fn [shape _] (update shape :fills remove-fill-by-index position))] - (transform-fill state ids color remove options)))))) + change-fn + (fn [shape _] (update shape :fills remove-fill-by-index position)) + + undo-id + (js/Symbol)] + + (rx/concat + (rx/of (dwu/start-undo-transaction undo-id)) + (transform-fill state ids color change-fn options) + (rx/of (dwu/commit-undo-transaction undo-id)))))))) (defn remove-all-fills ([ids color] (remove-all-fills ids color nil)) ([ids color options] - (dm/assert! - "expected a valid color struct" - (ctc/check-color! color)) + (assert (ctc/check-color color) "expected a valid color struct") + (assert (every? uuid? ids) "expected a valid coll of uuid's") - (dm/assert! - "expected a valid coll of uuid's" - (every? uuid? ids)) (ptk/reify ::remove-all-fills ptk/WatchEvent (watch [_ state _] - (let [remove-all (fn [shape _] (assoc shape :fills []))] - (transform-fill state ids color remove-all options)))))) + (let [change-fn (fn [shape _] (assoc shape :fills [])) + undo-id (js/Symbol)] + (rx/concat + (rx/of (dwu/start-undo-transaction undo-id)) + (transform-fill state ids color change-fn options) + (rx/of (dwu/commit-undo-transaction undo-id)))))))) (defn change-hide-fill-on-export [ids hide-fill-on-export] @@ -252,6 +293,8 @@ :stroke-cap-start :stroke-cap-end]) +;; FIXME: this function initializes an empty stroke, maybe we can move +;; it to common.types (defn- build-stroke-style-attrs [stroke] (let [attrs (select-keys stroke stroke-style-attrs)] @@ -268,6 +311,40 @@ :always (d/without-nils)))) +(defn update-shape-stroke-color + "Given a shape, update color attributes on the stroke on the specified + `position`; if no stroke is found a new empty stroke is created." + [shape position color] + (update shape :strokes + (fn [strokes] + (let [stroke (if (nil? strokes) + (build-stroke-style-attrs nil) + (build-stroke-style-attrs (get strokes position))) + stroke (cond-> (build-stroke-style-attrs stroke) + (contains? color :color) + (assoc :stroke-color (:color color)) + + (contains? color :id) + (assoc :stroke-color-ref-id (:id color)) + + (contains? color :file-id) + (assoc :stroke-color-ref-file (:file-id color)) + + (contains? color :gradient) + (assoc :stroke-color-gradient (:gradient color)) + + (contains? color :opacity) + (assoc :stroke-opacity (:opacity color)) + + (contains? color :image) + (assoc :stroke-image (:image color)) + + :always + (d/without-nils))] + (if (nil? strokes) + [stroke] + (assoc strokes position stroke)))))) + (defn change-stroke-color ([ids color index] (change-stroke-color ids color index nil)) ([ids color index options] @@ -275,38 +352,7 @@ ptk/WatchEvent (watch [_ _ _] (rx/of (let [options (assoc options :changed-sub-attr [:stroke-color])] - (dwsh/update-shapes - ids - (fn [shape] - (let [stroke (get-in shape [:strokes index]) - attrs (cond-> (build-stroke-style-attrs stroke) - (contains? color :color) - (assoc :stroke-color (:color color)) - - (contains? color :id) - (assoc :stroke-color-ref-id (:id color)) - - (contains? color :file-id) - (assoc :stroke-color-ref-file (:file-id color)) - - (contains? color :gradient) - (assoc :stroke-color-gradient (:gradient color)) - - (contains? color :opacity) - (assoc :stroke-opacity (:opacity color)) - - (contains? color :image) - (assoc :stroke-image (:image color)) - - :always - (d/without-nils))] - (cond-> shape - (not (contains? shape :strokes)) - (assoc :strokes []) - - :always - (assoc-in [:strokes index] attrs)))) - options))))))) + (dwsh/update-shapes ids #(update-shape-stroke-color % index color) options))))))) (defn change-stroke-attrs ([ids attrs index] (change-stroke-attrs ids attrs index nil)) @@ -354,13 +400,13 @@ (defn add-shadow [ids shadow] - (dm/assert! - "expected a valid shadow struct" - (check-shadow! shadow)) + (assert + (check-shadow shadow) + "expected a valid shadow struct") - (dm/assert! - "expected a valid coll of uuid's" - (every? uuid? ids)) + (assert + (every? uuid? ids) + "expected a valid coll of uuid's") (ptk/reify ::add-shadow ptk/WatchEvent @@ -372,13 +418,14 @@ (defn add-stroke [ids stroke] - (dm/assert! - "expected a valid stroke struct" - (check-stroke! stroke)) + (assert + (check-stroke stroke) + "expected a valid stroke struct") + + (assert + (every? uuid? ids) + "expected a valid coll of uuid's") - (dm/assert! - "expected a valid coll of uuid's" - (every? uuid? ids)) (ptk/reify ::add-stroke ptk/WatchEvent @@ -391,9 +438,9 @@ (defn remove-stroke [ids position] - (dm/assert! - "expected a valid coll of uuid's" - (every? uuid? ids)) + (assert + (every? uuid? ids) + "expected a valid coll of uuid's") (ptk/reify ::remove-stroke ptk/WatchEvent @@ -411,9 +458,10 @@ (defn remove-all-strokes [ids] - (dm/assert! - "expected a valid coll of uuid's" - (every? uuid? ids)) + (assert + (every? uuid? ids) + "expected a valid coll of uuid's") + (ptk/reify ::remove-all-strokes ptk/WatchEvent @@ -508,23 +556,22 @@ (def ^:private schema:change-color-operations [:vector schema:change-color-operation]) -(def ^:private check-change-color-operations! +(def ^:private check-change-color-operations (sm/check-fn schema:change-color-operations)) (defn change-color-in-selected [operations new-color old-color] - (dm/assert! - "expected valid color operations" - (check-change-color-operations! operations)) + (assert (check-change-color-operations operations) + "expected valid color operations") - (dm/assert! - "expected valid color structure" - (ctc/check-color! new-color)) + (assert + (ctc/check-color new-color) + "expected valid color structure") - (dm/assert! - "expected valid color structure" - (ctc/check-color! old-color)) + (assert + (ctc/check-color old-color) + "expected valid color structure") (ptk/reify ::change-color-in-selected ptk/WatchEvent @@ -546,9 +593,9 @@ (defn apply-color-from-palette [color stroke?] - (dm/assert! - "expected valid color structure" - (ctc/check-color! color)) + (assert + (ctc/check-color color) + "expected valid color structure") (ptk/reify ::apply-color-from-palette ptk/WatchEvent @@ -585,9 +632,8 @@ (defn apply-color-from-colorpicker [color] - (dm/assert! - "expected valid color structure" - (ctc/check-color! color)) + (assert (ctc/check-color color) + "expected valid color structure") (ptk/reify ::apply-color-from-colorpicker ptk/UpdateEvent diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index b24b7a807..50c34adbb 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -123,7 +123,7 @@ (dm/assert! "expect valid color structure" - (ctc/check-color! color)) + (ctc/check-color color)) (ptk/reify ::add-color ev/Event @@ -140,10 +140,8 @@ (defn add-recent-color [color] - - (dm/assert! - "expected valid recent color structure" - (ctc/check-recent-color! color)) + (assert (ctc/check-recent-color color) + "expected valid recent color structure") (ptk/reify ::add-recent-color ptk/UpdateEvent @@ -182,7 +180,7 @@ (dm/assert! "expected valid color data structure" - (ctc/check-color! color)) + (ctc/check-color color)) (dm/assert! "expected file-id" @@ -200,7 +198,7 @@ (dm/assert! "expected valid color data structure" - (ctc/check-color! color)) + (ctc/check-color color)) (dm/assert! "expected file-id" diff --git a/frontend/src/app/main/data/workspace/thumbnails.cljs b/frontend/src/app/main/data/workspace/thumbnails.cljs index 7041d53c1..0fefbd47a 100644 --- a/frontend/src/app/main/data/workspace/thumbnails.cljs +++ b/frontend/src/app/main/data/workspace/thumbnails.cljs @@ -282,8 +282,9 @@ ;; and interrupt any ongoing update-thumbnail process ;; related to current frame-id (->> all-commits-s - (rx/map (fn [frame-id] - (clear-thumbnail file-id page-id frame-id "frame")))) + (rx/mapcat (fn [frame-id] + (rx/of (clear-thumbnail file-id page-id frame-id "frame") + (clear-thumbnail file-id page-id frame-id "component"))))) ;; Generate thumbnails in batches, once user becomes ;; inactive for some instant. diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets/common.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets/common.cljs index 282b4a875..acd214d0c 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets/common.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets/common.cljs @@ -282,9 +282,8 @@ (:id target-asset) (cfh/merge-path-item prefix (:name target-asset)))))))) -(mf/defc component-item-thumbnail +(mf/defc component-item-thumbnail* "Component that renders the thumbnail image or the original SVG." - {::mf/props :obj} [{:keys [file-id root-shape component container class is-hidden]}] (let [page-id (:main-instance-page component) root-id (:main-instance-id component) diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets/components.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets/components.cljs index bc4b6bc76..adf11fede 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets/components.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets/components.cljs @@ -158,13 +158,14 @@ (when ^boolean dragging? [:div {:class (stl/css :dragging)}])] - [:& cmm/component-item-thumbnail {:file-id file-id - :class (stl/css-case :thumbnail true - :asset-list-thumbnail (not listing-thumbs?)) - :root-shape root-shape - :component component - :container container - :is-hidden (not visible?)}]])])) + [:> cmm/component-item-thumbnail* + {:file-id file-id + :class (stl/css-case :thumbnail true + :asset-list-thumbnail (not listing-thumbs?)) + :root-shape root-shape + :component component + :container container + :is-hidden (not visible?)}]])])) (mf/defc components-group {::mf/wrap-props false} diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs index 662cca7ae..19de318e3 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs @@ -322,10 +322,11 @@ :key (str "swap-item-" (:id item)) :on-click on-select} (when visible? - [:& cmm/component-item-thumbnail {:file-id (:file-id item) - :root-shape root-shape - :component item - :container container}]) + [:> cmm/component-item-thumbnail* + {:file-id (:file-id item) + :root-shape root-shape + :component item + :container container}]) [:span {:class (stl/css-case :component-name true :selected (= (:id item) component-id))} (if is-search (:full-name item) (:name item))]])) diff --git a/frontend/src/app/main/ui/workspace/tokens/changes.cljs b/frontend/src/app/main/ui/workspace/tokens/changes.cljs index ff107cf63..c9ce8b29e 100644 --- a/frontend/src/app/main/ui/workspace/tokens/changes.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/changes.cljs @@ -170,18 +170,42 @@ (f shape-ids {:color hex :opacity opacity} 0 {:ignore-touched true :page-id page-id})))) +(defn- value->color + "Transform a token color value into penpot color data structure" + [color] + (when-let [tc (tinycolor/valid-color color)] + (let [hex (tinycolor/->hex-string tc) + opacity (tinycolor/alpha tc)] + {:color hex :opacity opacity}))) + (defn update-fill - ([value shape-ids attributes] (update-fill value shape-ids attributes nil)) - ([value shape-ids _attributes page-id] ; The attributes param is needed to have the same arity that other update functions - (update-color wdc/change-fill value shape-ids page-id))) + ([value shape-ids attributes] + (update-fill value shape-ids attributes nil)) + ([value shape-ids _attributes page-id] ;; The attributes param is needed to have the same arity that other update functions + (ptk/reify ::update-fill + ptk/WatchEvent + (watch [_ state _] + (when-let [color (value->color value)] + (let [update-fn #(wdc/assoc-shape-fill %1 0 %2)] + (wdc/transform-fill state shape-ids color update-fn {:ignore-touched true + :page-id page-id}))))))) (defn update-stroke-color - ([value shape-ids attributes] (update-stroke-color value shape-ids attributes nil)) - ([value shape-ids _attributes page-id] ; The attributes param is needed to have the same arity that other update functions - (update-color wdc/change-stroke-color value shape-ids page-id))) + ([value shape-ids attributes] + (update-stroke-color value shape-ids attributes nil)) + + ;; The attributes param is needed to have the same arity that other update functions + ([value shape-ids _attributes page-id] + (when-let [color (value->color value)] + (dwsh/update-shapes shape-ids + #(wdc/update-shape-stroke-color % 0 color) + {:page-id page-id + :ignore-touched true + :changed-sub-attr [:stroke-color]})))) (defn update-fill-stroke - ([value shape-ids attributes] (update-fill-stroke value shape-ids attributes nil)) + ([value shape-ids attributes] + (update-fill-stroke value shape-ids attributes nil)) ([value shape-ids attributes page-id] (ptk/reify ::update-fill-stroke ptk/WatchEvent diff --git a/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs b/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs index 43585e548..26a4eaf23 100644 --- a/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs @@ -10,12 +10,13 @@ [app.main.ui.workspace.tokens.token :as wtt] [app.main.ui.workspace.tokens.warnings :as wtw] [app.util.i18n :refer [tr]] + [app.util.time :as dt] [beicon.v2.core :as rx] [cuerdas.core :as str] [promesa.core :as p] [rumext.v2 :as mf])) -(l/set-level! "app.main.ui.workspace.tokens.style-dictionary" :warn) +(l/set-level! :debug) ;; === Style Dictionary @@ -158,9 +159,10 @@ config) (build-dictionary [_] - (-> (sd. (clj->js config)) - (.buildAllPlatforms "json") - (p/then #(.-allTokens ^js %))))) + (let [config' (clj->js config)] + (-> (sd. config') + (.buildAllPlatforms "json") + (p/then #(.-allTokens ^js %)))))) (defn resolve-tokens-tree+ ([tokens-tree get-token] @@ -302,11 +304,14 @@ (let [state* (mf/use-state tokens)] (mf/with-effect [tokens interactive?] (when (seq tokens) - (let [promise (if interactive? + (let [tpoint (dt/tpoint-ms) + promise (if interactive? (resolve-tokens-interactive+ tokens) (resolve-tokens+ tokens))] (->> promise (p/fmap (fn [resolved-tokens] - (reset! state* resolved-tokens))))))) + (let [elapsed (tpoint)] + (l/dbg :hint "use-resolved-tokens*" :elapsed elapsed) + (reset! state* resolved-tokens)))))))) @state*))