From 8badd1f2eb3398ef23a3f5a757100638663ef7b0 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 7 May 2025 12:13:40 +0200 Subject: [PATCH 1/7] :lipstick: Add cosmetic improvements to common scripts/repl Make it consistent with backend scripts/repl --- common/scripts/repl | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/common/scripts/repl b/common/scripts/repl index 1efe773de..e736b2ad9 100755 --- a/common/scripts/repl +++ b/common/scripts/repl @@ -2,16 +2,20 @@ export PENPOT_FLAGS="enable-asserts enable-audit-log $PENPOT_FLAGS" -export OPTIONS=" - -A:dev \ - -J-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager \ - -J-Djdk.attach.allowAttachSelf \ - -J-Dpolyglot.engine.WarnInterpreterOnly=false \ - -J-XX:+EnableDynamicAgentLoading \ - -J-XX:-OmitStackTraceInFastThrow \ - -J-XX:+UnlockDiagnosticVMOptions \ - -J-XX:+DebugNonSafepoints \ - -J-Djdk.tracePinnedThreads=full" +export JAVA_OPTS="\ + -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager \ + -Djdk.attach.allowAttachSelf \ + -Dlog4j2.configurationFile=log4j2-devenv-repl.xml \ + -Djdk.tracePinnedThreads=full \ + -XX:+EnableDynamicAgentLoading \ + -XX:-OmitStackTraceInFastThrow \ + -XX:+UnlockDiagnosticVMOptions \ + -XX:+DebugNonSafepoints \ + --sun-misc-unsafe-memory-access=allow \ + --enable-preview \ + --enable-native-access=ALL-UNNAMED"; + +export OPTIONS="-A:dev" export OPTIONS_EVAL="nil" # export OPTIONS_EVAL="(set! *warn-on-reflection* true)" From 99e325acaff601bf536c3a0182a9b40ea4669447 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 6 May 2025 14:28:30 +0200 Subject: [PATCH 2/7] :fire: Remove support from legacy-zip format --- frontend/src/app/worker/import.cljs | 735 +----------- frontend/src/app/worker/import/parser.cljs | 1167 -------------------- 2 files changed, 6 insertions(+), 1896 deletions(-) delete mode 100644 frontend/src/app/worker/import/parser.cljs diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs index f658d97ea..0432b23d9 100644 --- a/frontend/src/app/worker/import.cljs +++ b/frontend/src/app/worker/import.cljs @@ -8,16 +8,10 @@ (:refer-clojure :exclude [resolve]) (:require [app.common.data :as d] - [app.common.exceptions :as ex] - [app.common.files.builder :as fb] - [app.common.geom.point :as gpt] [app.common.json :as json] [app.common.logging :as log] - [app.common.media :as cm] [app.common.schema :as sm] [app.common.text :as ct] - [app.common.time :as tm] - [app.common.types.path :as path] [app.common.uuid :as uuid] [app.main.repo :as rp] [app.util.http :as http] @@ -25,10 +19,8 @@ [app.util.sse :as sse] [app.util.zip :as uz] [app.worker.impl :as impl] - [app.worker.import.parser :as parser] [beicon.v2.core :as rx] - [cuerdas.core :as str] - [tubax.core :as tubax])) + [cuerdas.core :as str])) (log/set-level! :warn) @@ -37,185 +29,12 @@ (def conjv (fnil conj [])) -(def ^:private iso-date-rx - "Incomplete ISO regex for detect datetime-like values on strings" - #"^\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d.*") - -(defn read-json-key - [m] - (or (uuid/parse m) - (json/read-kebab-key m))) - -(defn read-json-val - [m] - (cond - (and (string? m) - (re-matches uuid/regex m)) - (uuid/uuid m) - - (and (string? m) - (re-matches iso-date-rx m)) - (or (ex/ignoring (tm/parse-instant m)) m) - - :else - m)) - -(defn get-file - "Resolves the file inside the context given its id and the - data. LEGACY" - ([context type] - (get-file context type nil nil)) - - ([context type id] - (get-file context type id nil)) - - ([context type id media] - (let [file-id (:file-id context) - path (case type - :manifest "manifest.json" - :page (str file-id "/" id ".svg") - :colors-list (str file-id "/colors.json") - :colors (let [ext (cm/mtype->extension (:mtype media))] - (str/concat file-id "/colors/" id ext)) - :typographies (str file-id "/typographies.json") - :media-list (str file-id "/media.json") - :media (let [ext (cm/mtype->extension (:mtype media))] - (str/concat file-id "/media/" id ext)) - :components (str file-id "/components.svg") - :deleted-components (str file-id "/deleted-components.svg")) - - parse-svg? (and (not= type :media) (str/ends-with? path "svg")) - parse-json? (and (not= type :media) (str/ends-with? path "json")) - file-type (if (or parse-svg? parse-json?) "text" "blob")] - - (log/debug :action "parsing" :path path) - - (let [stream (->> (uz/get-file (:zip context) path file-type) - (rx/map :content))] - - (cond - parse-svg? - (rx/map tubax/xml->clj stream) - - parse-json? - (rx/map #(json/decode % :key-fn read-json-key :val-fn read-json-val) stream) - - :else - stream))))) - (defn- read-zip-manifest [zipfile] (->> (uz/get-file zipfile "manifest.json") (rx/map :content) (rx/map json/decode))) -(defn progress! - ([context type] - (assert (keyword? type)) - (progress! context type nil nil nil)) - - ([context type file] - (assert (keyword? type)) - (assert (string? file)) - (progress! context type file nil nil)) - - ([context type current total] - (assert (keyword? type)) - (assert (number? current)) - (assert (number? total)) - (progress! context type nil current total)) - - ([context type file current total] - (when (and context (contains? context :progress)) - (let [progress {:type type - :file file - :current current - :total total}] - (log/debug :status :progress :progress progress) - (rx/push! (:progress context) {:file-id (:file-id context) - :status :progress - :progress progress}))))) - -(defn resolve-factory - "Creates a wrapper around the atom to remap ids to new ids and keep - their relationship so they ids are coherent." - [] - (let [id-mapping-atom (atom {}) - resolve - (fn [id-mapping id] - (assert (uuid? id) (str id)) - (get id-mapping id)) - - set-id - (fn [id-mapping id] - (assert (uuid? id) (str id)) - (cond-> id-mapping - (nil? (resolve id-mapping id)) - (assoc id (uuid/next))))] - - (fn [id] - (when (some? id) - (swap! id-mapping-atom set-id id) - (resolve @id-mapping-atom id))))) - -(defn create-file - "Create a new file on the back-end" - [context features] - (let [resolve-fn (:resolve context) - file-id (resolve-fn (:file-id context))] - (rp/cmd! :create-temp-file - {:id file-id - :name (:name context) - :is-shared (:is-shared context) - :project-id (:project-id context) - :create-page false - - ;; If the features object exists send that. Otherwise we remove the components/v2 because - ;; if the features attribute doesn't exist is a version < 2.0. The other features will - ;; be kept so the shapes are created full featured - :features (d/nilv (:features context) (disj features "components/v2"))}))) - -(defn link-file-libraries - "Create a new file on the back-end" - [context] - (let [resolve (:resolve context) - file-id (resolve (:file-id context)) - libraries (->> context :libraries (mapv resolve))] - (->> (rx/from libraries) - (rx/map #(hash-map :file-id file-id :library-id %)) - (rx/merge-map (partial rp/cmd! :link-file-to-library))))) - -(defn send-changes - "Creates batches of changes to be sent to the backend" - [context file] - (let [file-id (:id file) - session-id (uuid/next) - changes (fb/generate-changes file) - batches (->> changes - (partition change-batch-size change-batch-size nil) - (mapv vec)) - - processed (atom 0) - total (count batches)] - - (rx/concat - (->> (rx/from (d/enumerate batches)) - (rx/merge-map - (fn [[i change-batch]] - (->> (rp/cmd! :update-temp-file - {:id file-id - :session-id session-id - :revn i - :changes change-batch}) - (rx/tap #(do (swap! processed inc) - (progress! context :upload-data @processed total)))))) - (rx/map first) - (rx/ignore)) - - (->> (rp/cmd! :persist-temp-file {:id file-id}) - ;; We use merge to keep some information not stored in back-end - (rx/map #(merge file %)))))) - (defn slurp-uri ([uri] (slurp-uri uri :text)) ([uri response-type] @@ -225,26 +44,6 @@ :method :get}) (rx/map :body)))) -(defn upload-media-files - "Upload a image to the backend and returns its id" - [context file-id name data-uri] - - (log/debug :action "Uploading" :file-id file-id :name name) - - (->> (http/send! - {:uri data-uri - :response-type :blob - :method :get}) - (rx/map :body) - (rx/map - (fn [blob] - {:name name - :file-id file-id - :content blob - :is-local true})) - (rx/tap #(progress! context :upload-media name)) - (rx/merge-map #(rp/cmd! :upload-file-media-object %)))) - (defn resolve-text-content [node context] (let [resolve (:resolve context)] @@ -290,456 +89,6 @@ (uuid? (get fill :stroke-color-ref-file)) (d/update-when :stroke-color-ref-file resolve))))))) -(defn resolve-data-ids - [data type context] - (let [resolve (:resolve context)] - (-> data - (d/update-when :fill-color-ref-id resolve) - (d/update-when :fill-color-ref-file resolve) - (d/update-when :stroke-color-ref-id resolve) - (d/update-when :stroke-color-ref-file resolve) - (d/update-when :component-id resolve) - (d/update-when :component-file resolve) - (d/update-when :shape-ref resolve) - - (cond-> (= type :text) - (d/update-when :content resolve-text-content context)) - - (cond-> (:fills data) - (d/update-when :fills resolve-fills-content context)) - - (cond-> (:strokes data) - (d/update-when :strokes resolve-strokes-content context)) - - (cond-> (and (= type :frame) (= :grid (:layout data))) - (update - :layout-grid-cells - (fn [cells] - (->> (vals cells) - (reduce (fn [cells {:keys [id shapes]}] - (assoc-in cells [id :shapes] (mapv resolve shapes))) - cells)))))))) - -(defn- translate-frame - [data type file] - (let [frame-id (:current-frame-id file) - frame (when (and (some? frame-id) (not= frame-id uuid/zero)) - (fb/lookup-shape file frame-id))] - (if (some? frame) - (-> data - (d/update-when :x + (:x frame)) - (d/update-when :y + (:y frame)) - (cond-> (= :path type) - (update :content path/move-content (gpt/point (:x frame) (:y frame))))) - - data))) - -(defn process-import-node - [context file node] - (let [type (parser/get-type node) - close? (parser/close? node)] - (if close? - (case type - :frame (fb/close-artboard file) - :group (fb/close-group file) - :bool (fb/close-bool file) - :svg-raw (fb/close-svg-raw file) - #_default file) - - (let [resolve (:resolve context) - old-id (parser/get-id node) - interactions (->> (parser/parse-interactions node) - (mapv #(update % :destination resolve))) - data (-> (parser/parse-data type node) - (resolve-data-ids type context) - (cond-> (some? old-id) - (assoc :id (resolve old-id))) - (cond-> (< (:version context 1) 2) - (translate-frame type file)) - ;; Shapes inside the deleted component should be stored with absolute coordinates - ;; so we calculate that with the x and y stored in the context - (cond-> (:x context) - (assoc :x (:x context))) - (cond-> (:y context) - (assoc :y (:y context))))] - (try - (let [file (case type - :frame (fb/add-artboard file data) - :group (fb/add-group file data) - :bool (fb/add-bool file data) - :rect (fb/create-rect file data) - :circle (fb/create-circle file data) - :path (fb/create-path file data) - :text (fb/create-text file data) - :image (fb/create-image file data) - :svg-raw (fb/create-svg-raw file data) - #_default file)] - - ;; We store this data for post-processing after every shape has been - ;; added - (cond-> file - (d/not-empty? interactions) - (assoc-in [:interactions (:id data)] interactions))) - - (catch :default err - (log/error :hint (ex-message err) :cause err :js/data data) - (update file :errors conjv data))))))) - -(defn setup-interactions - [file] - (letfn [(add-interactions - [file [id interactions]] - (->> interactions - (reduce #(fb/add-interaction %1 id %2) file))) - - (process-interactions - [file] - (let [interactions (:interactions file) - file (dissoc file :interactions)] - (->> interactions (reduce add-interactions file))))] - (-> file process-interactions))) - -(defn resolve-media - [context file-id node] - (if (or (and (not (parser/close? node)) - (parser/has-image? node)) - (parser/has-stroke-images? node) - (parser/has-fill-images? node)) - (let [name (parser/get-image-name node) - has-image (parser/has-image? node) - image-data (parser/get-image-data node) - image-fill (parser/get-image-fill node) - fill-images-data (->> (parser/get-fill-images-data node) - (map #(assoc % :type :fill))) - stroke-images-data (->> (parser/get-stroke-images-data node) - (map #(assoc % :type :stroke))) - - images-data (concat - fill-images-data - stroke-images-data - (when has-image - [{:href image-data}]))] - (->> (rx/from images-data) - (rx/mapcat (fn [image-data] - (->> (upload-media-files context file-id name (:href image-data)) - (rx/catch #(do (.error js/console "Error uploading media: " name) - (rx/of node))) - (rx/map (fn [data] - (let [data - (cond-> data - (some? (:keep-aspect-ratio image-data)) - (assoc :keep-aspect-ratio (:keep-aspect-ratio image-data)))] - [(:id image-data) data])))))) - (rx/reduce (fn [acc [id data]] (assoc acc id data)) {}) - (rx/map - (fn [images] - (let [media (get images nil)] - (-> node - (assoc :images images) - (cond-> (some? media) - (-> - (assoc-in [:attrs :penpot:media-id] (:id media)) - (assoc-in [:attrs :penpot:media-width] (:width media)) - (assoc-in [:attrs :penpot:media-height] (:height media)) - (assoc-in [:attrs :penpot:media-mtype] (:mtype media)) - (cond-> (some? (:keep-aspect-ratio media)) - (assoc-in [:attrs :penpot:media-keep-aspect-ratio] (:keep-aspect-ratio media))) - (assoc-in [:attrs :penpot:fill-color] (:fill image-fill)) - (assoc-in [:attrs :penpot:fill-color-ref-file] (:fill-color-ref-file image-fill)) - (assoc-in [:attrs :penpot:fill-color-ref-id] (:fill-color-ref-id image-fill)) - (assoc-in [:attrs :penpot:fill-opacity] (:fill-opacity image-fill)) - (assoc-in [:attrs :penpot:fill-color-gradient] (:fill-color-gradient image-fill)))))))))) - - ;; If the node is not an image just return the node - (->> (rx/of node) - (rx/observe-on :async)))) - -(defn media-node? [node] - (or (and (parser/shape? node) - (parser/has-image? node) - (not (parser/close? node))) - (parser/has-stroke-images? node) - (parser/has-fill-images? node))) - -(defn import-page - [context file [page-id page-name content]] - (let [nodes (parser/node-seq content) - file-id (:id file) - resolve (:resolve context) - page-data (-> (parser/parse-page-data content) - (assoc :name page-name) - (assoc :id (resolve page-id))) - - flows (->> (get page-data :flows) - (map #(update % :starting-frame resolve)) - (d/index-by :id) - (not-empty)) - - guides (-> (get page-data :guides) - (update-vals #(update % :frame-id resolve)) - (not-empty)) - - page-data (cond-> page-data - flows - (assoc :flows flows) - - guides - (assoc :guides guides)) - - file (fb/add-page file page-data) - - ;; Preprocess nodes to parallel upload the images. Store the result in a table - ;; old-node => node with image - ;; that will be used in the second pass immediately - pre-process-images - (->> (rx/from nodes) - (rx/filter media-node?) - ;; TODO: this should be merge-map, but we disable the - ;; parallel upload until we resolve resource usage issues - ;; on backend. - (rx/mapcat - (fn [node] - (->> (resolve-media context file-id node) - (rx/map (fn [result] - [node result]))))) - (rx/reduce conj {}))] - - (->> pre-process-images - (rx/merge-map - (fn [pre-proc] - (->> (rx/from nodes) - (rx/filter parser/shape?) - (rx/map (fn [node] (or (get pre-proc node) node))) - (rx/reduce (partial process-import-node context) file) - (rx/map (comp fb/close-page setup-interactions)))))))) - -(defn import-component [context file node] - (let [resolve (:resolve context) - content (parser/find-node node :g) - file-id (:id file) - old-id (parser/get-id node) - id (resolve old-id) - path (get-in node [:attrs :penpot:path] "") - type (parser/get-type content) - main-instance-id (resolve (uuid/parse (get-in node [:attrs :penpot:main-instance-id] ""))) - main-instance-page (resolve (uuid/parse (get-in node [:attrs :penpot:main-instance-page] ""))) - data (-> (parser/parse-data type content) - (assoc :path path) - (assoc :id id) - (assoc :main-instance-id main-instance-id) - (assoc :main-instance-page main-instance-page)) - - file (-> file (fb/start-component data type)) - children (parser/node-seq node)] - - (->> (rx/from children) - (rx/filter parser/shape?) - (rx/skip 1) ;; Skip the outer component and the respective closint tag - (rx/skip-last 1) ;; because they are handled in start-component an finish-component - (rx/mapcat (partial resolve-media context file-id)) - (rx/reduce (partial process-import-node context) file) - (rx/map fb/finish-component)))) - -(defn import-deleted-component [context file node] - (let [resolve (:resolve context) - content (parser/find-node node :g) - file-id (:id file) - old-id (parser/get-id node) - id (resolve old-id) - path (get-in node [:attrs :penpot:path] "") - main-instance-id (resolve (uuid/parse (get-in node [:attrs :penpot:main-instance-id] ""))) - main-instance-page (resolve (uuid/parse (get-in node [:attrs :penpot:main-instance-page] ""))) - main-instance-x (-> (get-in node [:attrs :penpot:main-instance-x] "") (d/parse-double)) - main-instance-y (-> (get-in node [:attrs :penpot:main-instance-y] "") (d/parse-double)) - main-instance-parent (resolve (uuid/parse (get-in node [:attrs :penpot:main-instance-parent] ""))) - main-instance-frame (resolve (uuid/parse (get-in node [:attrs :penpot:main-instance-frame] ""))) - type (parser/get-type content) - - data (-> (parser/parse-data type content) - (assoc :path path) - (assoc :id id) - (assoc :main-instance-id main-instance-id) - (assoc :main-instance-page main-instance-page) - (assoc :main-instance-x main-instance-x) - (assoc :main-instance-y main-instance-y) - (assoc :main-instance-parent main-instance-parent) - (assoc :main-instance-frame main-instance-frame)) - - file (-> file - (fb/start-component data) - (fb/start-deleted-component data)) - component-id (:current-component-id file) - children (parser/node-seq node) - - ;; Shapes inside the deleted component should be stored with absolute coordinates so we include this info in the context. - context (-> context - (assoc :x main-instance-x) - (assoc :y main-instance-y))] - (->> (rx/from children) - (rx/filter parser/shape?) - (rx/skip 1) - (rx/skip-last 1) - (rx/mapcat (partial resolve-media context file-id)) - (rx/reduce (partial process-import-node context) file) - (rx/map fb/finish-component) - (rx/map (partial fb/finish-deleted-component component-id))))) - -(defn process-pages - [context file] - (let [index (:pages-index context) - get-page-data - (fn [page-id] - [page-id (get-in index [page-id :name])]) - - pages (->> (:pages context) (mapv get-page-data))] - - (->> (rx/from pages) - (rx/tap (fn [[_ page-name]] - (progress! context :process-page page-name))) - (rx/mapcat - (fn [[page-id page-name]] - (->> (get-file context :page page-id) - (rx/map (fn [page-data] [page-id page-name page-data]))))) - (rx/concat-reduce (partial import-page context) file)))) - -(defn process-library-colors - [context file] - (if (:has-colors context) - (let [resolve (:resolve context) - add-color - (fn [file color] - (let [color (-> color - (d/update-in-when [:gradient :type] keyword) - (d/update-in-when [:image :id] resolve) - (update :id resolve))] - (fb/add-library-color file color)))] - (->> (get-file context :colors-list) - (rx/merge-map identity) - (rx/mapcat - (fn [[id color]] - (let [color (assoc color :id id) - color-image (:image color) - upload-image? (some? color-image) - color-image-id (:id color-image)] - (if upload-image? - (->> (get-file context :colors color-image-id color-image) - (rx/map (fn [blob] - (let [content (.slice blob 0 (.-size blob) (:mtype color-image))] - {:name (:name color-image) - :id (resolve color-image-id) - :file-id (:id file) - :content content - :is-local false}))) - (rx/tap #(progress! context :upload-media (:name %))) - (rx/merge-map #(rp/cmd! :upload-file-media-object %)) - (rx/map (constantly color)) - (rx/catch #(do (.error js/console (str "Error uploading color-image: " (:name color-image))) - (rx/empty)))) - (rx/of color))))) - (rx/reduce add-color file))) - (rx/of file))) - -(defn process-library-typographies - [context file] - (if (:has-typographies context) - (let [resolve (:resolve context)] - (->> (get-file context :typographies) - (rx/merge-map identity) - (rx/map (fn [[id typography]] - (-> typography - (d/kebab-keys) - (assoc :id (resolve id))))) - (rx/reduce fb/add-library-typography file))) - - (rx/of file))) - -(defn process-library-media - [context file] - (if (:has-media context) - (let [resolve (:resolve context)] - (->> (get-file context :media-list) - (rx/merge-map identity) - (rx/mapcat - (fn [[id media]] - (let [media (-> media - (assoc :id (resolve id)) - (update :name str))] - (->> (get-file context :media id media) - (rx/map (fn [blob] - (let [content (.slice blob 0 (.-size blob) (:mtype media))] - {:name (:name media) - :id (:id media) - :file-id (:id file) - :content content - :is-local false}))) - (rx/tap #(progress! context :upload-media (:name %))) - (rx/merge-map #(rp/cmd! :upload-file-media-object %)) - (rx/map (constantly media)) - (rx/catch #(do (.error js/console (str "Error uploading media: " (:name media))) - (rx/empty))))))) - (rx/reduce fb/add-library-media file))) - (rx/of file))) - -(defn process-library-components - [context file] - (if (:has-components context) - (let [split-components - (fn [content] (->> (parser/node-seq content) - (filter #(= :symbol (:tag %)))))] - - (->> (get-file context :components) - (rx/merge-map split-components) - (rx/concat-reduce (partial import-component context) file))) - (rx/of file))) - -(defn process-deleted-components - [context file] - (if (:has-deleted-components context) - (let [split-components - (fn [content] (->> (parser/node-seq content) - (filter #(= :symbol (:tag %)))))] - - (->> (get-file context :deleted-components) - (rx/merge-map split-components) - (rx/concat-reduce (partial import-deleted-component context) file))) - (rx/of file))) - -(defn process-file - [context file] - - (let [progress-str (rx/subject) - context (assoc context :progress progress-str)] - [progress-str - (->> (rx/of file) - (rx/merge-map (partial process-pages context)) - (rx/tap #(progress! context :process-colors)) - (rx/merge-map (partial process-library-colors context)) - (rx/tap #(progress! context :process-typographies)) - (rx/merge-map (partial process-library-typographies context)) - (rx/tap #(progress! context :process-media)) - (rx/merge-map (partial process-library-media context)) - (rx/tap #(progress! context :process-components)) - (rx/merge-map (partial process-library-components context)) - (rx/tap #(progress! context :process-deleted-components)) - (rx/merge-map (partial process-deleted-components context)) - (rx/merge-map (partial send-changes context)) - (rx/tap #(rx/end! progress-str)))])) - -(defn create-files - [{:keys [system-features] :as context} files] - (let [data (group-by :file-id files)] - (rx/concat - (->> (rx/from files) - (rx/map #(merge context %)) - (rx/merge-map (fn [context] - (->> (create-file context system-features) - (rx/map #(vector % (first (get data (:file-id context))))))))) - - (->> (rx/from files) - (rx/map #(merge context %)) - (rx/merge-map link-file-libraries) - (rx/ignore))))) - (defn parse-mtype [ba] (let [u8 (js/Uint8Array. ba 0 4) sg (areduce u8 i ret "" (str ret (if (zero? i) "" " ") (.toString (aget u8 i) 8)))] @@ -748,35 +97,6 @@ "1 13 32 206" "application/octet-stream" "other"))) -(defn- analyze-file-legacy-zip-entry - [features entry] - ;; NOTE: LEGACY manifest reading mechanism, we can't - ;; reuse the new read-zip-manifest funcion here - (->> (rx/from (uz/load (:body entry))) - (rx/merge-map #(get-file {:zip %} :manifest)) - (rx/mapcat - (fn [manifest] - ;; Checks if the file is exported with - ;; components v2 and the current team - ;; only supports components v1 - (let [has-file-v2? - (->> (:files manifest) - (d/seek (fn [[_ file]] (contains? (set (:features file)) "components/v2"))))] - - (if (and has-file-v2? (not (contains? features "components/v2"))) - (rx/of (-> entry - (assoc :error "dashboard.import.analyze-error.components-v2") - (dissoc :body))) - (->> (rx/from (:files manifest)) - (rx/map (fn [[file-id data]] - (-> entry - (dissoc :body) - (merge data) - (dissoc :shared) - (assoc :is-shared (:shared data)) - (assoc :file-id file-id) - (assoc :status :success))))))))))) - ;; NOTE: this is a limited subset schema for the manifest file of ;; binfile-v3 format; is used for partially parse it and read the ;; files referenced inside the exported file @@ -794,7 +114,7 @@ (sm/decoder schema:manifest sm/json-transformer)) (defn analyze-file - [features {:keys [uri] :as file}] + [{:keys [uri] :as file}] (let [stream (->> (slurp-uri uri :buffer) (rx/merge-map (fn [body] @@ -819,10 +139,6 @@ (rx/share))] (->> (rx/merge - (->> stream - (rx/filter (fn [entry] (= :legacy-zip (:type entry)))) - (rx/merge-map (partial analyze-file-legacy-zip-entry features))) - (->> stream (rx/filter (fn [entry] (= :binfile-v1 (:type entry)))) (rx/map (fn [entry] @@ -855,55 +171,16 @@ (rx/of (assoc file :error error :status :error)))))))) (defmethod impl/handler :analyze-import - [{:keys [files features]}] + [{:keys [files]}] (->> (rx/from files) - (rx/merge-map (partial analyze-file features)))) + (rx/merge-map analyze-file))) (defmethod impl/handler :import-files - [{:keys [project-id files features]}] - (let [context {:project-id project-id - :resolve (resolve-factory) - :system-features features} - - legacy-zip (filter #(= :legacy-zip (:type %)) files) - binfile-v1 (filter #(= :binfile-v1 (:type %)) files) + [{:keys [project-id files]}] + (let [binfile-v1 (filter #(= :binfile-v1 (:type %)) files) binfile-v3 (filter #(= :binfile-v3 (:type %)) files)] (rx/merge - - ;; NOTE: LEGACY, will be removed so no new development should be - ;; done for this part - (->> (create-files context legacy-zip) - (rx/merge-map - (fn [[file data]] - (->> (uz/load-from-url (:uri data)) - (rx/map #(-> context (assoc :zip %) (merge data))) - (rx/merge-map - (fn [context] - ;; process file retrieves a stream that will emit progress notifications - ;; and other that will emit the files once imported - (let [[progress-stream file-stream] (process-file context file)] - (rx/merge progress-stream - (->> file-stream - (rx/map - (fn [file] - (if-let [errors (not-empty (:errors file))] - {:status :error - :error (first errors) - :file-id (:file-id data)} - {:status :finish - :file-id (:file-id data)})))))))) - (rx/catch (fn [cause] - (let [data (ex-data cause)] - (log/error :hint (ex-message cause) - :file-id (:file-id data)) - (when-let [explain (:explain data)] - (js/console.log explain))) - - (rx/of {:status :error - :file-id (:file-id data) - :error (ex-message cause)}))))))) - (->> (rx/from binfile-v1) (rx/merge-map (fn [data] diff --git a/frontend/src/app/worker/import/parser.cljs b/frontend/src/app/worker/import/parser.cljs deleted file mode 100644 index 8bf5158ee..000000000 --- a/frontend/src/app/worker/import/parser.cljs +++ /dev/null @@ -1,1167 +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.worker.import.parser - (:require - [app.common.colors :as cc] - [app.common.data :as d] - [app.common.geom.matrix :as gmt] - [app.common.geom.point :as gpt] - [app.common.svg.path :as svg.path] - [app.common.types.component :as ctk] - [app.common.types.shape.interactions :as ctsi] - [app.common.uuid :as uuid] - [app.util.json :as json] - [cuerdas.core :as str])) - -(def url-regex - #"url\(#([^\)]*)\)") - -(def uuid-regex-prefix - #"\w{8}-\w{4}-\w{4}-\w{4}-\w{12}-") - -(defn valid? - [root] - (contains? (:attrs root) :xmlns:penpot)) - -(defn branch? - [node] - (and (contains? node :content) - (some? (:content node)))) - -(defn close? - [node] - (and (vector? node) - (= ::close (first node)))) - -(defn find-node - [node tag] - (when (some? node) - (->> node :content (d/seek #(= (:tag %) tag))))) - -(defn find-node-by-id - [id coll] - (->> coll (d/seek #(= id (-> % :attrs :id))))) - -(defn find-all-nodes - [node tag] - (when (some? node) - (let [predicate? - (if (set? tag) - ;; We can pass a tag set or a single tag - #(contains? tag (:tag %)) - #(= (:tag %) tag))] - (->> node :content (filterv predicate?))))) - -(defn get-data - ([node] - (or (find-node node :penpot:shape) - (find-node node :penpot:page))) - - ([node tag] - (-> (get-data node) - (find-node tag)))) - -(defn get-type - [node] - (if (close? node) - (second node) - (let [data (get-data node)] - (-> (get-in data [:attrs :penpot:type]) - (keyword))))) - -(defn shape? - [node] - (or (close? node) - (some? (get-data node)))) - -(defn get-id - [node] - (let [attr-id (get-in node [:attrs :id]) - id (when (string? attr-id) (re-find uuid/regex attr-id))] - (when (some? id) - (uuid/uuid id)))) - -(defn str->bool - [val] - (when (some? val) (= val "true"))) - -(defn get-meta - ([m att] - (get-meta m att identity)) - ([m att val-fn] - (let [ns-att (->> att d/name (str "penpot:") keyword) - val (or (get-in m [:attrs ns-att]) - (get-in (get-data m) [:attrs ns-att]))] - (when val (val-fn val))))) - -(defn find-node-by-metadata-value - [meta value coll] - (->> coll (d/seek #(= value (get-meta % meta))))) - -(defn get-children - [node] - (cond-> (:content node) - ;; We add a "fake" node to know when we are leaving the shape children - (shape? node) - (conj [::close (get-type node)]))) - -(defn node-seq - [content] - (->> content (tree-seq branch? get-children))) - -(defn parse-style - "Transform style list into a map" - [style-str] - (if (string? style-str) - (->> (str/split style-str ";") - (map str/trim) - (map #(str/split % ":")) - (group-by first) - (map (fn [[key val]] - (vector (keyword key) (second (first val))))) - (into {})) - style-str)) - -(defn parse-touched - "Transform a string of :touched-groups into a set" - [touched-str] - (let [touched (->> (str/split touched-str " ") - (map #(keyword (subs % 1))) - (filter ctk/valid-touched-group?) - (into #{}))] - touched)) - -(defn add-attrs - [m attrs] - (reduce-kv - (fn [m k v] - (if (#{:style :data-style} k) - (merge m (parse-style v)) - (assoc m k v))) - m - attrs)) - -(defn without-penpot-prefix - [m] - (let [no-penpot-prefix? - (fn [[k _]] - (not (str/starts-with? (d/name k) "penpot:")))] - (into {} (filter no-penpot-prefix?) m))) - -(defn remove-penpot-prefix - [m] - (into {} - (map (fn [[k v]] - (if (str/starts-with? (d/name k) "penpot:") - [(-> k d/name (str/replace "penpot:" "") keyword) v] - [k v]))) - m)) - -(defn camelize [[k v]] - [(-> k d/name str/camel keyword) v]) - -(defn camelize-keys - [m] - (assert (map? m) (str m)) - - (into {} (map camelize) m)) - -(defn fix-style-attr - [m] - (let [fix-style - (fn [[k v]] - (if (= k :style) - [k (-> v parse-style camelize-keys)] - [k v]))] - - (d/deep-mapm (comp camelize fix-style) m))) - -(defn string->uuid - "Looks in a map for keys or values that have uuid shape and converts them - into uuid objects" - [m] - (letfn [(convert [value] - (cond - (and (string? value) (re-matches uuid/regex value)) - (uuid/uuid value) - - (and (keyword? value) (re-matches uuid/regex (d/name value))) - (uuid/uuid (d/name value)) - - (vector? value) - (mapv convert value) - - :else - value))] - (->> m - (d/deep-mapm - (fn [pair] (->> pair (mapv convert))))))) - -(def search-data-node? #{:rect :path :circle}) - -(defn get-svg-data - [type node] - (let [node-attrs (add-attrs {} (:attrs node))] - (cond - (search-data-node? type) - (let [data-tags #{:ellipse :rect :path :text :foreignObject}] - (->> node - (node-seq) - (filter #(contains? data-tags (:tag %))) - (map #(:attrs %)) - (reduce add-attrs node-attrs))) - - (= type :image) - (let [data-tags #{:rect :image}] - (->> node - (node-seq) - (filter #(contains? data-tags (:tag %))) - (map #(:attrs %)) - (reduce add-attrs node-attrs))) - - (= type :text) - (->> node - (node-seq) - (filter #(contains? #{:g :foreignObject} (:tag %))) - (map #(:attrs %)) - (reduce add-attrs node-attrs)) - - (= type :frame) - (let [;; Old .penpot files doesn't have "g" nodes. They have a clipPath reference as a node attribute - frame-clip-rect-node (->> (find-all-nodes node :defs) - (mapcat #(find-all-nodes % :clipPath)) - (mapcat #(find-all-nodes % #{:rect :path})) - (filter #(contains? (:attrs %) :width)) - (first)) - - ;; The nodes with the "frame-background" class can have some anidation depending on the strokes they have - g-nodes (find-all-nodes node :g) - defs-nodes (flatten (map #(find-all-nodes % :defs) g-nodes)) - gg-nodes (flatten (map #(find-all-nodes % :g) g-nodes)) - - ;; The first g node contains the opacity for frames - main-g-node (first g-nodes) - - rect-nodes (flatten [[(find-all-nodes node :rect)] - (map #(find-all-nodes % #{:rect :path}) defs-nodes) - (map #(find-all-nodes % #{:rect :path}) g-nodes) - (map #(find-all-nodes % #{:rect :path}) gg-nodes)]) - svg-node (d/seek #(= "frame-background" (get-in % [:attrs :class])) rect-nodes)] - (merge - (add-attrs {} (:attrs frame-clip-rect-node)) - (add-attrs {} (:attrs svg-node)) - (add-attrs {} (:attrs main-g-node)) - node-attrs)) - - (= type :svg-raw) - (let [svg-content (get-data node :penpot:svg-content) - tag (-> svg-content :attrs :penpot:tag keyword) - - svg-node (if (= :svg tag) - (->> node :content last :content last) - (->> node :content last)) - svg-node (d/update-in-when svg-node [:attrs :style] parse-style)] - (merge (add-attrs {} (:attrs svg-node)) node-attrs)) - - (= type :bool) - (->> node - (:content) - (filter #(= :path (:tag %))) - (map #(:attrs %)) - (reduce add-attrs node-attrs)) - - :else - node-attrs))) - -(def has-position? #{:frame :rect :image :text}) - -(defn parse-position - [props node svg-data] - (let [x (get-meta node :x d/parse-double) - y (get-meta node :y d/parse-double) - width (get-meta node :width d/parse-double) - height (get-meta node :height d/parse-double) - - values (->> (select-keys svg-data [:x :y :width :height]) - (d/mapm (fn [_ val] (d/parse-double val)))) - - values - (cond-> values - (some? x) (assoc :x x) - (some? y) (assoc :y y) - (some? width) (assoc :width width) - (some? height) (assoc :height height))] - (d/merge props values))) - -(defn parse-circle - [props svg-data] - (let [values (->> (select-keys svg-data [:cx :cy :rx :ry]) - (d/mapm (fn [_ val] (d/parse-double val))))] - (-> props - (assoc :x (- (:cx values) (:rx values)) - :y (- (:cy values) (:ry values)) - :width (* (:rx values) 2) - :height (* (:ry values) 2))))) - -(defn parse-path - [props center svg-data] - (let [content (svg.path/parse (:d svg-data))] - (-> props - (assoc :content content) - (assoc :center center)))) - -(defn parse-stops - [gradient-node] - (->> gradient-node - (node-seq) - (filter #(= :stop (:tag %))) - (mapv (fn [{{:keys [offset stop-color stop-opacity]} :attrs}] - {:color stop-color - :opacity (d/parse-double stop-opacity) - :offset (d/parse-double offset)})))) - -(defn parse-gradient - [node ref-url] - (let [[_ url] (re-find url-regex ref-url) - gradient-node (->> node (node-seq) (find-node-by-id url)) - stops (parse-stops gradient-node)] - - (when (contains? (:attrs gradient-node) :penpot:gradient) - (cond-> {:stops stops} - (= :linearGradient (:tag gradient-node)) - (assoc :type :linear - :start-x (-> gradient-node :attrs :x1 d/parse-double) - :start-y (-> gradient-node :attrs :y1 d/parse-double) - :end-x (-> gradient-node :attrs :x2 d/parse-double) - :end-y (-> gradient-node :attrs :y2 d/parse-double) - :width 1) - - (= :radialGradient (:tag gradient-node)) - (assoc :type :radial - :start-x (get-meta gradient-node :start-x d/parse-double) - :start-y (get-meta gradient-node :start-y d/parse-double) - :end-x (get-meta gradient-node :end-x d/parse-double) - :end-y (get-meta gradient-node :end-y d/parse-double) - :width (get-meta gradient-node :width d/parse-double)))))) - -(defn add-svg-position [props node] - (let [svg-content (get-data node :penpot:svg-content)] - (cond-> props - (contains? (:attrs svg-content) :penpot:x) - (assoc :x (-> svg-content :attrs :penpot:x d/parse-double)) - - (contains? (:attrs svg-content) :penpot:y) - (assoc :y (-> svg-content :attrs :penpot:y d/parse-double)) - - (contains? (:attrs svg-content) :penpot:width) - (assoc :width (-> svg-content :attrs :penpot:width d/parse-double)) - - (contains? (:attrs svg-content) :penpot:height) - (assoc :height (-> svg-content :attrs :penpot:height d/parse-double))))) - -(defn add-common-data - [props node] - - (let [name (get-meta node :name) - blocked (get-meta node :blocked str->bool) - hidden (get-meta node :hidden str->bool) - transform (get-meta node :transform gmt/str->matrix) - transform-inverse (get-meta node :transform-inverse gmt/str->matrix) - flip-x (get-meta node :flip-x str->bool) - flip-y (get-meta node :flip-y str->bool) - proportion (get-meta node :proportion d/parse-double) - proportion-lock (get-meta node :proportion-lock str->bool) - rotation (get-meta node :rotation d/parse-double) - constraints-h (get-meta node :constraints-h keyword) - constraints-v (get-meta node :constraints-v keyword) - fixed-scroll (get-meta node :fixed-scroll str->bool)] - - (-> props - (assoc :name name) - (assoc :blocked blocked) - (assoc :hidden hidden) - (assoc :flip-x flip-x) - (assoc :flip-y flip-y) - (assoc :proportion proportion) - (assoc :proportion-lock proportion-lock) - (assoc :rotation rotation) - - (cond-> (some? transform) - (assoc :transform transform)) - - (cond-> (some? transform-inverse) - (assoc :transform-inverse transform-inverse)) - - (cond-> (some? constraints-h) - (assoc :constraints-h constraints-h)) - - (cond-> (some? constraints-v) - (assoc :constraints-v constraints-v)) - - (cond-> (some? fixed-scroll) - (assoc :fixed-scroll fixed-scroll))))) - -(defn add-position - [props type node svg-data] - (let [center-x (get-meta node :center-x d/parse-double) - center-y (get-meta node :center-y d/parse-double) - center (gpt/point center-x center-y)] - (cond-> props - (has-position? type) - (parse-position node svg-data) - - (= type :svg-raw) - (add-svg-position node) - - (= type :circle) - (parse-circle svg-data) - - (= type :path) - (parse-path center svg-data)))) - -(defn add-library-refs - [props node] - - (let [stroke-color-ref-id (get-meta node :stroke-color-ref-id uuid/parse) - stroke-color-ref-file (get-meta node :stroke-color-ref-file uuid/parse) - component-id (get-meta node :component-id uuid/parse) - component-file (get-meta node :component-file uuid/parse) - shape-ref (get-meta node :shape-ref uuid/parse) - component-root? (get-meta node :component-root str->bool) - main-instance? (get-meta node :main-instance str->bool) - touched (get-meta node :touched parse-touched)] - - (cond-> props - (some? stroke-color-ref-id) - (assoc :stroke-color-ref-id stroke-color-ref-id - :stroke-color-ref-file stroke-color-ref-file) - - (some? component-id) - (assoc :component-id component-id - :component-file component-file) - - component-root? - (assoc :component-root component-root?) - - main-instance? - (assoc :main-instance main-instance?) - - (some? shape-ref) - (assoc :shape-ref shape-ref) - - (seq touched) - (assoc :touched touched)))) - -(defn add-fill - [props node svg-data] - - (let [fill (:fill svg-data) - fill-color-ref-id (get-meta node :fill-color-ref-id uuid/parse) - fill-color-ref-file (get-meta node :fill-color-ref-file uuid/parse) - meta-fill-color (get-meta node :fill-color) - meta-fill-opacity (get-meta node :fill-opacity) - meta-fill-color-gradient (if (str/starts-with? meta-fill-color "url#fill-color-gradient") - (parse-gradient node meta-fill-color) - (get-meta node :fill-color-gradient)) - gradient (when (str/starts-with? fill "url") - (parse-gradient node fill))] - - (cond-> props - :always - (assoc :fill-color nil - :fill-opacity nil) - - (some? meta-fill-color) - (assoc :fill-color meta-fill-color - :fill-opacity (d/parse-double meta-fill-opacity)) - - (some? meta-fill-color-gradient) - (assoc :fill-color-gradient meta-fill-color-gradient - :fill-color nil - :fill-opacity nil) - - (some? gradient) - (assoc :fill-color-gradient gradient - :fill-color nil - :fill-opacity nil) - - (cc/valid-hex-color? fill) - (assoc :fill-color fill - :fill-opacity (-> svg-data (:fill-opacity "1") d/parse-double)) - - (some? fill-color-ref-id) - (assoc :fill-color-ref-id fill-color-ref-id - :fill-color-ref-file fill-color-ref-file)))) - -(defn add-stroke - [props node svg-data] - - (let [stroke-style (get-meta node :stroke-style keyword) - stroke-alignment (get-meta node :stroke-alignment keyword) - stroke (:stroke svg-data) - gradient (when (str/starts-with? stroke "url(#stroke-color-gradient") - (parse-gradient node stroke)) - - stroke-cap-start (get-meta node :stroke-cap-start keyword) - stroke-cap-end (get-meta node :stroke-cap-end keyword)] - - (cond-> props - :always - (assoc :stroke-alignment stroke-alignment - :stroke-style stroke-style - :stroke-color (-> svg-data :stroke) - :stroke-opacity (-> svg-data :stroke-opacity d/parse-double) - :stroke-width (-> svg-data :stroke-width d/parse-double)) - - (some? gradient) - (assoc :stroke-color-gradient gradient - :stroke-color nil - :stroke-opacity nil) - - (= stroke-alignment :inner) - (update :stroke-width / 2) - - (some? stroke-cap-start) - (assoc :stroke-cap-start stroke-cap-start) - - (some? stroke-cap-end) - (assoc :stroke-cap-end stroke-cap-end)))) - -(defn add-radius-data - [props node svg-data] - (let [r1 (get-meta node :r1 d/parse-double) - r2 (get-meta node :r2 d/parse-double) - r3 (get-meta node :r3 d/parse-double) - r4 (get-meta node :r4 d/parse-double) - - rx (-> (get svg-data :rx 0) d/parse-double)] - - (cond-> props - (some? r1) - (assoc :r1 r1 :r2 r2 :r3 r3 :r4 r4) - (and (nil? r1) (some? rx)) - (assoc :r1 rx :r2 rx :r3 rx :r4 rx)))) - -(defn add-image-data - [props type node] - (let [metadata {:id (get-meta node :media-id) - :width (get-meta node :media-width) - :height (get-meta node :media-height) - :mtype (get-meta node :media-mtype) - :keep-aspect-ratio (get-meta node :media-keep-aspect-ratio str->bool)}] - (cond-> props - (= type :image) - (assoc :metadata metadata) - - (not= type :image) - (assoc :fill-image metadata)))) - -(defn add-text-data - [props node] - (-> props - (assoc :grow-type (get-meta node :grow-type keyword)) - (assoc :content (get-meta node :content (comp string->uuid json/decode))) - (assoc :position-data (get-meta node :position-data (comp string->uuid json/decode))))) - -(defn add-group-data - [props node] - (let [mask? (get-meta node :masked-group str->bool)] - (cond-> props - mask? - (assoc :masked-group true)))) - -(defn add-bool-data - [props node] - (-> props - (assoc :bool-type (get-meta node :bool-type keyword)))) - -(defn parse-shadow [node] - {:id (uuid/next) - :style (get-meta node :shadow-type keyword) - :hidden (get-meta node :hidden str->bool) - :color {:color (get-meta node :color) - :opacity (get-meta node :opacity d/parse-double)} - :offset-x (get-meta node :offset-x d/parse-double) - :offset-y (get-meta node :offset-y d/parse-double) - :blur (get-meta node :blur d/parse-double) - :spread (get-meta node :spread d/parse-double)}) - -(defn parse-blur [node] - {:id (uuid/next) - :type (get-meta node :blur-type keyword) - :hidden (get-meta node :hidden str->bool) - :value (get-meta node :value d/parse-double)}) - -(defn parse-export [node] - {:type (get-meta node :type keyword) - :suffix (get-meta node :suffix) - :scale (get-meta node :scale d/parse-double)}) - -(defn parse-grid-node [node] - (let [attrs (-> node :attrs remove-penpot-prefix) - color {:color (:color attrs) - :opacity (-> attrs :opacity d/parse-double)} - - params (-> (dissoc attrs :color :opacity :display :type) - (d/update-when :size d/parse-double) - (d/update-when :item-length d/parse-double) - (d/update-when :gutter d/parse-double) - (d/update-when :margin d/parse-double) - (assoc :color color))] - {:type (-> attrs :type keyword) - :display (-> attrs :display str->bool) - :params params})) - -(defn parse-grids [node] - (let [grids-node (get-data node :penpot:grids)] - (->> grids-node :content (mapv parse-grid-node)))) - -(defn parse-flow-node [node] - (let [attrs (-> node :attrs remove-penpot-prefix)] - {:id (uuid/next) - :name (-> attrs :name) - :starting-frame (-> attrs :starting-frame uuid/parse)})) - -(defn parse-flows [node] - (let [flows-node (get-data node :penpot:flows)] - (->> flows-node :content (mapv parse-flow-node)))) - -(defn parse-guide-node [node] - (let [attrs (-> node :attrs remove-penpot-prefix) - id (uuid/next)] - [id - {:id id - :frame-id (when (:frame-id attrs) (-> attrs :frame-id uuid/parse)) - :axis (-> attrs :axis keyword) - :position (-> attrs :position d/parse-double)}])) - -(defn parse-guides [node] - (let [guides-node (get-data node :penpot:guides)] - (->> guides-node :content (map parse-guide-node) (into {})))) - -(defn extract-from-data - ([node tag] - (extract-from-data node tag identity)) - - ([node tag parse-fn] - (let [shape-data (get-data node)] - (->> shape-data - (node-seq) - (filter #(= (:tag %) tag)) - (mapv parse-fn))))) - -(defn add-shadows - [props node] - (let [shadows (extract-from-data node :penpot:shadow parse-shadow)] - (cond-> props - (d/not-empty? shadows) - (assoc :shadow shadows)))) - -(defn add-blur - [props node] - (let [blur (->> (extract-from-data node :penpot:blur parse-blur) (first))] - (cond-> props - (some? blur) - (assoc :blur blur)))) - -(defn add-exports - [props node] - (let [exports (extract-from-data node :penpot:export parse-export)] - (cond-> props - (d/not-empty? exports) - (assoc :exports exports)))) - -(defn add-layer-options - [props svg-data] - (let [blend-mode (get svg-data :mix-blend-mode) - opacity (-> (get svg-data :opacity) d/parse-double)] - - (cond-> props - (some? blend-mode) - (assoc :blend-mode (keyword blend-mode)) - - (some? opacity) - (assoc :opacity opacity)))) - -(defn remove-prefix [s] - (cond-> s - (string? s) - (str/replace uuid-regex-prefix ""))) - -(defn get-svg-attrs - [svg-import svg-data svg-attrs] - (let [process-attr - (fn [acc prop] - (cond - (and (= prop "style") - (contains? (:attrs svg-import) :penpot:svg-style)) - (let [style (get-in svg-import [:attrs :penpot:svg-style])] - (assoc acc :style (parse-style style))) - - (and (= prop "filter") - (contains? (:attrs svg-import) :penpot:svg-filter)) - (let [style (get-in svg-import [:attrs :penpot:svg-filter])] - (assoc acc :filter (parse-style style))) - - :else - (let [key (keyword prop)] - (if-let [v (or (get svg-data key) - (get-in svg-data [:attrs key]))] - (assoc acc key (remove-prefix v)) - acc))))] - (->> (str/split svg-attrs ",") - (reduce process-attr {})))) - -(defn get-svg-defs - [node] - - (let [svg-import (get-data node :penpot:svg-import)] - (->> svg-import - :content - (filter #(= (:tag %) :penpot:svg-def)) - (map #(vector (-> % :attrs :def-id) - (-> % :content first))) - (into {})))) - -(defn add-svg-attrs - [props node svg-data] - - (let [svg-import (get-data node :penpot:svg-import)] - (if (some? svg-import) - (let [svg-attrs (get-in svg-import [:attrs :penpot:svg-attrs]) - svg-defs (get-in svg-import [:attrs :penpot:svg-defs]) - svg-transform (get-in svg-import [:attrs :penpot:svg-transform]) - viewbox-x (get-in svg-import [:attrs :penpot:svg-viewbox-x]) - viewbox-y (get-in svg-import [:attrs :penpot:svg-viewbox-y]) - viewbox-width (get-in svg-import [:attrs :penpot:svg-viewbox-width]) - viewbox-height (get-in svg-import [:attrs :penpot:svg-viewbox-height])] - - (cond-> props - :true - (assoc :svg-attrs (get-svg-attrs svg-import svg-data svg-attrs)) - - (some? viewbox-x) - (assoc :svg-viewbox {:x (d/parse-double viewbox-x) - :y (d/parse-double viewbox-y) - :width (d/parse-double viewbox-width) - :height (d/parse-double viewbox-height)}) - - (some? svg-transform) - (assoc :svg-transform (gmt/str->matrix svg-transform)) - - - (some? svg-defs) - (assoc :svg-defs (get-svg-defs node)))) - - props))) - -(defn parse-fills - [node svg-data] - (let [fills-node (get-data node :penpot:fills) - images (:images node) - fills (->> (find-all-nodes fills-node :penpot:fill) - (mapv (fn [fill-node] - (let [fill-image-id (get-meta fill-node :fill-image-id)] - {:fill-color (when (not (str/starts-with? (get-meta fill-node :fill-color) "url")) - (get-meta fill-node :fill-color)) - :fill-color-gradient (when (str/starts-with? (get-meta fill-node :fill-color) "url(#fill-color-gradient") - (parse-gradient node (get-meta fill-node :fill-color))) - :fill-image (when fill-image-id - (get images fill-image-id)) - :fill-color-ref-file (get-meta fill-node :fill-color-ref-file uuid/parse) - :fill-color-ref-id (get-meta fill-node :fill-color-ref-id uuid/parse) - :fill-opacity (get-meta fill-node :fill-opacity d/parse-double)}))) - (mapv d/without-nils) - (filterv #(not= (:fill-color %) "none")))] - - (if (seq fills) - fills - (->> [(-> (add-fill {} node svg-data) - (d/without-nils))] - (filterv #(and (not-empty %) (not= (:fill-color %) "none"))))))) - -(defn parse-strokes - [node svg-data] - (let [strokes-node (get-data node :penpot:strokes) - images (:images node) - strokes (->> (find-all-nodes strokes-node :penpot:stroke) - (mapv (fn [stroke-node] - (let [stroke-image-id (get-meta stroke-node :stroke-image-id)] - {:stroke-color (when (not (str/starts-with? (get-meta stroke-node :stroke-color) "url")) - (get-meta stroke-node :stroke-color)) - :stroke-color-gradient (when (str/starts-with? (get-meta stroke-node :stroke-color) "url(#stroke-color-gradient") - (parse-gradient node (get-meta stroke-node :stroke-color))) - :stroke-image (when stroke-image-id - (get images stroke-image-id)) - :stroke-color-ref-file (get-meta stroke-node :stroke-color-ref-file uuid/parse) - :stroke-color-ref-id (get-meta stroke-node :stroke-color-ref-id uuid/parse) - :stroke-opacity (get-meta stroke-node :stroke-opacity d/parse-double) - :stroke-style (get-meta stroke-node :stroke-style keyword) - :stroke-width (get-meta stroke-node :stroke-width d/parse-double) - :stroke-alignment (get-meta stroke-node :stroke-alignment keyword) - :stroke-cap-start (get-meta stroke-node :stroke-cap-start keyword) - :stroke-cap-end (get-meta stroke-node :stroke-cap-end keyword)}))) - (mapv d/without-nils) - (filterv #(not= (:stroke-color %) "none")))] - - (if (seq strokes) - strokes - (->> [(-> (add-stroke {} node svg-data) - (d/without-nils))] - (filterv #(and (not-empty %) (not= (:stroke-color %) "none") (not= (:stroke-style %) :none))))))) - -(defn add-svg-content - [props node] - (let [svg-content (get-data node :penpot:svg-content) - attrs (-> (:attrs svg-content) (without-penpot-prefix) - (d/update-when :style parse-style)) - tag (-> svg-content :attrs :penpot:tag keyword) - - node-content - (cond - (= tag :svg) - (->> node :content last :content last :content fix-style-attr) - - (some? (:content svg-content)) - (->> (:content svg-content) - (filter #(= :penpot:svg-child (:tag %))) - (mapv :content) - (first)))] - (-> props - (assoc - :content - {:attrs attrs - :tag tag - :content node-content})))) - -(defn add-frame-data [props node] - (let [grids (parse-grids node) - show-content (get-meta node :show-content str->bool) - hide-in-viewer (get-meta node :hide-in-viewer str->bool) - use-for-thumbnail (get-meta node :use-for-thumbnail str->bool)] - (-> props - (assoc :show-content show-content) - (assoc :hide-in-viewer hide-in-viewer) - (cond-> use-for-thumbnail - (assoc :use-for-thumbnail use-for-thumbnail)) - (cond-> (d/not-empty? grids) - (assoc :grids grids))))) - -(defn get-stroke-images-data - [node] - (let [strokes - (-> node - (find-node :penpot:shape) - (find-node :penpot:strokes))] - (->> (find-all-nodes strokes :penpot:stroke) - (mapv (fn [stroke-node] - (let [id (get-in stroke-node [:attrs :penpot:stroke-image-id]) - image-node (->> node (node-seq) (find-node-by-id id))] - {:id id - :href (get-in image-node [:attrs :href])}))) - (filterv #(some? (:id %)))))) - -(defn has-stroke-images? - [node] - (let [stroke-images (get-stroke-images-data node)] - (> (count stroke-images) 0))) - -(defn has-image? - [node] - (let [type (get-type node) - pattern-image - (-> node - (find-node :defs) - (find-node :pattern) - (find-node :g) - (find-node :g) - (find-node :image))] - - (or (= type :image) - (some? pattern-image)))) - -(defn get-image-name - [node] - (get-meta node :name)) - -(defn get-image-data - [node] - (let [pattern-data - (-> node - (find-node :defs) - (find-node :pattern) - (find-node :g) - (find-node :g) - (find-node :image) - :attrs) - image-data (get-svg-data :image node) - svg-data (or pattern-data image-data)] - (or (:href svg-data) (:xlink:href svg-data)))) - -(defn get-fill-images-data - [node] - (let [fills - (-> node - (find-node :penpot:shape) - (find-node :penpot:fills))] - (->> (find-all-nodes fills :penpot:fill) - (mapv (fn [fill-node] - (let [id (get-in fill-node [:attrs :penpot:fill-image-id]) - image-node (->> node (node-seq) (find-node-by-id id))] - {:id id - :href (get-in image-node [:attrs :href]) - :keep-aspect-ratio (not= (get-in image-node [:attrs :preserveAspectRatio]) "none")}))) - (filterv #(some? (:id %)))))) - -(defn has-fill-images? - [node] - (let [fill-images (get-fill-images-data node)] - (> (count fill-images) 0))) - -(defn get-image-fill - [node] - (let [linear-gradient-node (-> node - (find-node :defs) - (find-node :linearGradient)) - radial-gradient-node (-> node - (find-node :defs) - (find-node :radialGradient)) - gradient-node (or linear-gradient-node radial-gradient-node) - stops (parse-stops gradient-node) - gradient (cond-> {:stops stops} - (some? linear-gradient-node) - (assoc :type :linear - :start-x (-> linear-gradient-node :attrs :x1 d/parse-double) - :start-y (-> linear-gradient-node :attrs :y1 d/parse-double) - :end-x (-> linear-gradient-node :attrs :x2 d/parse-double) - :end-y (-> linear-gradient-node :attrs :y2 d/parse-double) - :width 1) - - (some? radial-gradient-node) - (assoc :type :linear - :start-x (get-meta radial-gradient-node :start-x d/parse-double) - :start-y (get-meta radial-gradient-node :start-y d/parse-double) - :end-x (get-meta radial-gradient-node :end-x d/parse-double) - :end-y (get-meta radial-gradient-node :end-y d/parse-double) - :width (get-meta radial-gradient-node :width d/parse-double)))] - - (if (some? (or linear-gradient-node radial-gradient-node)) - {:fill-color-gradient gradient} - (-> node - (find-node :defs) - (find-node :pattern) - (find-node :g) - (find-node :rect) - :attrs - :style - parse-style)))) - -(defn parse-grid-tracks - [node label] - (let [node (-> (get-data node :penpot:layout) - (find-node label))] - (->> node - :content - (mapv - (fn [track-node] - (let [{:keys [type value]} (-> track-node :attrs remove-penpot-prefix)] - {:type (keyword type) - :value (d/parse-double value)})))))) - -(defn parse-grid-cells - [node] - (let [node (-> (get-data node :penpot:layout) - (find-node :penpot:grid-cells))] - (->> node - :content - (mapv - (fn [cell-node] - (let [{:keys [id - area-name - row - row-span - column - column-span - position - align-self - justify-self - shapes]} (-> cell-node :attrs remove-penpot-prefix) - id (uuid/parse id)] - [id (d/without-nils - {:id id - :area-name area-name - :row (d/parse-integer row) - :row-span (d/parse-integer row-span) - :column (d/parse-integer column) - :column-span (d/parse-integer column-span) - :position (keyword position) - :align-self (keyword align-self) - :justify-self (keyword justify-self) - :shapes (if (and (some? shapes) (d/not-empty? shapes)) - (->> (str/split shapes " ") - (mapv uuid/parse)) - [])})]))) - (into {})))) - -(defn add-layout-container-data [props node] - (if-let [data (get-data node :penpot:layout)] - (let [layout-grid-rows (parse-grid-tracks node :penpot:grid-rows) - layout-grid-columns (parse-grid-tracks node :penpot:grid-columns) - layout-grid-cells (parse-grid-cells node)] - (-> props - (merge - (d/without-nils - {:layout (get-meta data :layout keyword) - :layout-flex-dir (get-meta data :layout-flex-dir keyword) - :layout-grid-dir (get-meta data :layout-grid-dir keyword) - :layout-wrap-type (get-meta data :layout-wrap-type keyword) - - :layout-gap-type (get-meta data :layout-gap-type keyword) - :layout-gap - (d/without-nils - {:row-gap (get-meta data :layout-gap-row d/parse-double) - :column-gap (get-meta data :layout-gap-column d/parse-double)}) - - :layout-padding-type (get-meta data :layout-padding-type keyword) - :layout-padding - (d/without-nils - {:p1 (get-meta data :layout-padding-p1 d/parse-double) - :p2 (get-meta data :layout-padding-p2 d/parse-double) - :p3 (get-meta data :layout-padding-p3 d/parse-double) - :p4 (get-meta data :layout-padding-p4 d/parse-double)}) - - :layout-justify-items (get-meta data :layout-justify-items keyword) - :layout-justify-content (get-meta data :layout-justify-content keyword) - :layout-align-items (get-meta data :layout-align-items keyword) - :layout-align-content (get-meta data :layout-align-content keyword)})) - - (cond-> (d/not-empty? layout-grid-rows) - (assoc :layout-grid-rows layout-grid-rows)) - (cond-> (d/not-empty? layout-grid-columns) - (assoc :layout-grid-columns layout-grid-columns)) - (cond-> (d/not-empty? layout-grid-cells) - (assoc :layout-grid-cells layout-grid-cells)))) - props)) - -(defn add-layout-item-data [props node] - (if-let [data (get-data node :penpot:layout-item)] - (merge props - (d/without-nils - {:layout-item-margin - (d/without-nils - {:m1 (get-meta data :layout-item-margin-m1 d/parse-double) - :m2 (get-meta data :layout-item-margin-m2 d/parse-double) - :m3 (get-meta data :layout-item-margin-m3 d/parse-double) - :m4 (get-meta data :layout-item-margin-m4 d/parse-double)}) - - :layout-item-margin-type (get-meta data :layout-item-margin-type keyword) - :layout-item-h-sizing (get-meta data :layout-item-h-sizing keyword) - :layout-item-v-sizing (get-meta data :layout-item-v-sizing keyword) - :layout-item-max-h (get-meta data :layout-item-max-h d/parse-double) - :layout-item-min-h (get-meta data :layout-item-min-h d/parse-double) - :layout-item-max-w (get-meta data :layout-item-max-w d/parse-double) - :layout-item-min-w (get-meta data :layout-item-min-w d/parse-double) - :layout-item-align-self (get-meta data :layout-item-align-self keyword) - :layout-item-align-absolute (get-meta data :layout-item-align-absolute str->bool) - :layout-item-align-index (get-meta data :layout-item-align-index d/parse-double)})) - props)) - -(defn parse-data - [type node] - - (when-not (close? node) - (let [svg-data (get-svg-data type node)] - (-> {} - (add-common-data node) - (add-position type node svg-data) - - (add-layer-options svg-data) - (add-shadows node) - (add-blur node) - (add-exports node) - (add-svg-attrs node svg-data) - (add-library-refs node) - - (assoc :fills (parse-fills node svg-data)) - (assoc :strokes (parse-strokes node svg-data)) - - (assoc :hide-fill-on-export (get-meta node :hide-fill-on-export str->bool)) - - (cond-> (= :svg-raw type) - (add-svg-content node)) - - (cond-> (= :frame type) - (-> (add-frame-data node) - (add-layout-container-data node))) - - (add-layout-item-data node) - - (cond-> (= :group type) - (add-group-data node)) - - (cond-> (or (= :frame type) (= :rect type)) - (add-radius-data node svg-data)) - - (cond-> (some? (get-in node [:attrs :penpot:media-id])) - (-> - (add-radius-data node svg-data) - (add-image-data type node))) - - (cond-> (= :text type) - (add-text-data node)) - - (cond-> (= :bool type) - (add-bool-data node)))))) - -(defn parse-page-data - [node] - (let [style (parse-style (get-in node [:attrs :style])) - background (:background style) - grids (->> (parse-grids node) - (group-by :type) - (d/mapm (fn [_ v] (-> v first :params)))) - flows (parse-flows node) - guides (parse-guides node)] - (cond-> {} - (some? background) - (assoc :background background) - - (d/not-empty? grids) - (assoc :default-grids grids) - - (d/not-empty? flows) - (assoc :flows flows) - - (d/not-empty? guides) - (assoc :guides guides)))) - -(defn parse-interactions - [node] - (let [interactions-node (get-data node :penpot:interactions)] - (->> (find-all-nodes interactions-node :penpot:interaction) - (mapv (fn [node] - (let [interaction {:event-type (get-meta node :event-type keyword) - :action-type (get-meta node :action-type keyword)}] - (cond-> interaction - (ctsi/has-delay interaction) - (assoc :delay (get-meta node :delay d/parse-double)) - - (ctsi/has-destination interaction) - (assoc :destination (get-meta node :destination uuid/parse) - :preserve-scroll (get-meta node :preserve-scroll str->bool)) - - (ctsi/has-url interaction) - (assoc :url (get-meta node :url str)) - - (ctsi/has-overlay-opts interaction) - (assoc :overlay-pos-type (get-meta node :overlay-pos-type keyword) - :overlay-position (gpt/point - (get-meta node :overlay-position-x d/parse-double) - (get-meta node :overlay-position-y d/parse-double)) - :close-click-outside (get-meta node :close-click-outside str->bool) - :background-overlay (get-meta node :background-overlay str->bool))))))))) - From 1bcfa4b8dc2413505c141620e127f7792a308942 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 7 May 2025 11:41:26 +0200 Subject: [PATCH 3/7] :tada: Add facility to define custom js class --- frontend/src/app/util/object.cljc | 45 +++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/frontend/src/app/util/object.cljc b/frontend/src/app/util/object.cljc index a04e7e4b7..072d2a5f9 100644 --- a/frontend/src/app/util/object.cljc +++ b/frontend/src/app/util/object.cljc @@ -93,6 +93,51 @@ (when (some? obj) (js* "Object.entries(~{}).reduce((a, [k,v]) => (v == null ? a : (a[k]=v, a)), {}) " obj)))) +#?(:cljs + (defn plain-object? + ^boolean + [o] + (and (some? o) + (identical? (.getPrototypeOf js/Object o) + (.-prototype js/Object))))) + +;; EXPERIMENTAL: unsafe, does not checks and not validates the input, +;; should be improved over time, for now it works for define a class +;; extending js/Error that is more than enought for a first, quick and +;; dirty macro impl for generating classes. + +(defmacro class + "Create a class instance" + [& {:keys [name extends constructor]}] + + (let [params + (if (and constructor (= 'fn (first constructor))) + (into [] (drop 1) (second constructor)) + []) + + constructor-sym + (symbol name) + + constructor + (if constructor + constructor + `(fn ~name [~'this] + (.call ~extends ~'this)))] + + `(let [konstructor# ~constructor + extends# ~extends + ~constructor-sym + (fn ~constructor-sym ~params + (cljs.core/this-as ~'this + (konstructor# ~'this ~@params)))] + + (set! (.-prototype ~constructor-sym) + (js/Object.create (.-prototype extends#))) + (set! (.-constructor (.-prototype ~constructor-sym)) + konstructor#) + + ~constructor-sym))) + (defmacro add-properties! "Adds properties to an object using `.defineProperty`" [rsym & properties] From ffd7bc883df819916dd92ddc8067cd96183e7fcd Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 7 May 2025 11:37:08 +0200 Subject: [PATCH 4/7] :arrow_up: Update shadow-cljs to 3.0.3 on common and frontend --- common/deps.edn | 4 +- common/package.json | 2 +- frontend/deps.edn | 2 +- frontend/package.json | 4 +- frontend/yarn.lock | 676 ++++-------------------------------------- 5 files changed, 66 insertions(+), 622 deletions(-) diff --git a/common/deps.edn b/common/deps.edn index ae1a8868d..05aee308f 100644 --- a/common/deps.edn +++ b/common/deps.edn @@ -2,7 +2,7 @@ {org.clojure/clojure {:mvn/version "1.12.0"} org.clojure/data.json {:mvn/version "2.5.1"} org.clojure/tools.cli {:mvn/version "1.1.230"} - org.clojure/clojurescript {:mvn/version "1.11.132"} + org.clojure/clojurescript {:mvn/version "1.12.38"} org.clojure/test.check {:mvn/version "1.1.1"} org.clojure/data.fressian {:mvn/version "1.1.0"} @@ -59,7 +59,7 @@ {:dev {:extra-deps {org.clojure/tools.namespace {:mvn/version "RELEASE"} - thheller/shadow-cljs {:mvn/version "2.28.20"} + thheller/shadow-cljs {:mvn/version "3.0.3"} com.clojure-goes-fast/clj-async-profiler {:mvn/version "RELEASE"} com.bhauman/rebel-readline {:mvn/version "RELEASE"} criterium/criterium {:mvn/version "RELEASE"} diff --git a/common/package.json b/common/package.json index 5a449d128..29f1460df 100644 --- a/common/package.json +++ b/common/package.json @@ -17,7 +17,7 @@ "devDependencies": { "concurrently": "^9.0.1", "nodemon": "^3.1.7", - "shadow-cljs": "2.28.20", + "shadow-cljs": "3.0.3", "source-map-support": "^0.5.21", "ws": "^8.17.0" }, diff --git a/frontend/deps.edn b/frontend/deps.edn index 24ea5efa5..1641de2c7 100644 --- a/frontend/deps.edn +++ b/frontend/deps.edn @@ -42,7 +42,7 @@ :dev {:extra-paths ["dev"] :extra-deps - {thheller/shadow-cljs {:mvn/version "2.28.18"} + {thheller/shadow-cljs {:mvn/version "3.0.3"} com.bhauman/rebel-readline {:mvn/version "RELEASE"} org.clojure/tools.namespace {:mvn/version "RELEASE"} criterium/criterium {:mvn/version "RELEASE"} diff --git a/frontend/package.json b/frontend/package.json index 983eb966c..5d5e1554b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -25,6 +25,7 @@ "build:app:libs": "node ./scripts/build-libs.js", "build:app:main": "clojure -M:dev:shadow-cljs release main worker", "build:app": "yarn run clear:shadow-cache && yarn run build:app:main && yarn run build:app:libs", + "build:library": "yarn run clear:shadow-cache && clojure -M:dev:shadow-cljs release library", "e2e:server": "node ./scripts/e2e-server.js", "fmt:clj": "cljfmt fix --parallel=true src/ test/", "fmt:clj:check": "cljfmt check --parallel=false src/ test/", @@ -44,6 +45,7 @@ "watch:app:main": "clojure -M:dev:shadow-cljs watch main worker storybook", "clear:shadow-cache": "rm -rf .shadow-cljs", "watch:app": "yarn run clear:shadow-cache && concurrently \"yarn run watch:app:main\" \"yarn run watch:app:libs\"", + "watch:library": "yarn run clear:shadow-cache && clojure -M:dev:shadow-cljs watch library", "watch": "yarn run watch:app:assets", "watch:storybook": "concurrently \"storybook dev -p 6006 --no-open\" \"yarn run watch:storybook:assets\"", "watch:storybook:assets": "node ./scripts/watch-storybook.js" @@ -89,7 +91,7 @@ "rimraf": "^6.0.1", "sass": "^1.83.4", "sass-embedded": "^1.83.4", - "shadow-cljs": "2.28.20", + "shadow-cljs": "3.0.3", "storybook": "^8.5.2", "svg-sprite": "^2.0.4", "typescript": "^5.7.3", diff --git a/frontend/yarn.lock b/frontend/yarn.lock index b4d4e2318..d6d81559a 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -3089,27 +3089,6 @@ __metadata: languageName: node linkType: hard -"asn1.js@npm:^4.10.1": - version: 4.10.1 - resolution: "asn1.js@npm:4.10.1" - dependencies: - bn.js: "npm:^4.0.0" - inherits: "npm:^2.0.1" - minimalistic-assert: "npm:^1.0.0" - checksum: 10c0/afa7f3ab9e31566c80175a75b182e5dba50589dcc738aa485be42bdd787e2a07246a4b034d481861123cbe646a7656f318f4f1cad2e9e5e808a210d5d6feaa88 - languageName: node - linkType: hard - -"assert@npm:^1.1.1": - version: 1.5.1 - resolution: "assert@npm:1.5.1" - dependencies: - object.assign: "npm:^4.1.4" - util: "npm:^0.10.4" - checksum: 10c0/836688b928b68b7fc5bbc165443e16a62623d57676a1e8a980a0316f9ae86e5e0a102c63470491bf55a8545e75766303640c0c7ad1cf6bfa5450130396043bbd - languageName: node - linkType: hard - "assert@npm:^2.1.0": version: 2.1.0 resolution: "assert@npm:2.1.0" @@ -3309,7 +3288,7 @@ __metadata: languageName: node linkType: hard -"base64-js@npm:^1.0.2, base64-js@npm:^1.3.1": +"base64-js@npm:^1.3.1": version: 1.5.1 resolution: "base64-js@npm:1.5.1" checksum: 10c0/f23823513b63173a001030fae4f2dabe283b99a9d324ade3ad3d148e218134676f1ee8568c877cd79ec1c53158dcf2d2ba527a97c606618928ba99dd930102bf @@ -3350,20 +3329,6 @@ __metadata: languageName: node linkType: hard -"bn.js@npm:^4.0.0, bn.js@npm:^4.1.0, bn.js@npm:^4.11.9": - version: 4.12.0 - resolution: "bn.js@npm:4.12.0" - checksum: 10c0/9736aaa317421b6b3ed038ff3d4491935a01419ac2d83ddcfebc5717385295fcfcf0c57311d90fe49926d0abbd7a9dbefdd8861e6129939177f7e67ebc645b21 - languageName: node - linkType: hard - -"bn.js@npm:^5.2.1": - version: 5.2.1 - resolution: "bn.js@npm:5.2.1" - checksum: 10c0/bed3d8bd34ec89dbcf9f20f88bd7d4a49c160fda3b561c7bb227501f974d3e435a48fb9b61bc3de304acab9215a3bda0803f7017ffb4d0016a0c3a740a283caa - languageName: node - linkType: hard - "body-parser@npm:1.20.3": version: 1.20.3 resolution: "body-parser@npm:1.20.3" @@ -3419,13 +3384,6 @@ __metadata: languageName: node linkType: hard -"brorand@npm:^1.0.1, brorand@npm:^1.1.0": - version: 1.1.0 - resolution: "brorand@npm:1.1.0" - checksum: 10c0/6f366d7c4990f82c366e3878492ba9a372a73163c09871e80d82fb4ae0d23f9f8924cb8a662330308206e6b3b76ba1d528b4601c9ef73c2166b440b2ea3b7571 - languageName: node - linkType: hard - "browser-assert@npm:^1.2.1": version: 1.2.1 resolution: "browser-assert@npm:1.2.1" @@ -3433,81 +3391,6 @@ __metadata: languageName: node linkType: hard -"browserify-aes@npm:^1.0.4, browserify-aes@npm:^1.2.0": - version: 1.2.0 - resolution: "browserify-aes@npm:1.2.0" - dependencies: - buffer-xor: "npm:^1.0.3" - cipher-base: "npm:^1.0.0" - create-hash: "npm:^1.1.0" - evp_bytestokey: "npm:^1.0.3" - inherits: "npm:^2.0.1" - safe-buffer: "npm:^5.0.1" - checksum: 10c0/967f2ae60d610b7b252a4cbb55a7a3331c78293c94b4dd9c264d384ca93354c089b3af9c0dd023534efdc74ffbc82510f7ad4399cf82bc37bc07052eea485f18 - languageName: node - linkType: hard - -"browserify-cipher@npm:^1.0.1": - version: 1.0.1 - resolution: "browserify-cipher@npm:1.0.1" - dependencies: - browserify-aes: "npm:^1.0.4" - browserify-des: "npm:^1.0.0" - evp_bytestokey: "npm:^1.0.0" - checksum: 10c0/aa256dcb42bc53a67168bbc94ab85d243b0a3b56109dee3b51230b7d010d9b78985ffc1fb36e145c6e4db151f888076c1cfc207baf1525d3e375cbe8187fe27d - languageName: node - linkType: hard - -"browserify-des@npm:^1.0.0": - version: 1.0.2 - resolution: "browserify-des@npm:1.0.2" - dependencies: - cipher-base: "npm:^1.0.1" - des.js: "npm:^1.0.0" - inherits: "npm:^2.0.1" - safe-buffer: "npm:^5.1.2" - checksum: 10c0/943eb5d4045eff80a6cde5be4e5fbb1f2d5002126b5a4789c3c1aae3cdddb1eb92b00fb92277f512288e5c6af330730b1dbabcf7ce0923e749e151fcee5a074d - languageName: node - linkType: hard - -"browserify-rsa@npm:^4.0.0, browserify-rsa@npm:^4.1.0": - version: 4.1.1 - resolution: "browserify-rsa@npm:4.1.1" - dependencies: - bn.js: "npm:^5.2.1" - randombytes: "npm:^2.1.0" - safe-buffer: "npm:^5.2.1" - checksum: 10c0/b650ee1192e3d7f3d779edc06dd96ed8720362e72ac310c367b9d7fe35f7e8dbb983c1829142b2b3215458be8bf17c38adc7224920843024ed8cf39e19c513c0 - languageName: node - linkType: hard - -"browserify-sign@npm:^4.2.3": - version: 4.2.3 - resolution: "browserify-sign@npm:4.2.3" - dependencies: - bn.js: "npm:^5.2.1" - browserify-rsa: "npm:^4.1.0" - create-hash: "npm:^1.2.0" - create-hmac: "npm:^1.1.7" - elliptic: "npm:^6.5.5" - hash-base: "npm:~3.0" - inherits: "npm:^2.0.4" - parse-asn1: "npm:^5.1.7" - readable-stream: "npm:^2.3.8" - safe-buffer: "npm:^5.2.1" - checksum: 10c0/30c0eba3f5970a20866a4d3fbba2c5bd1928cd24f47faf995f913f1499214c6f3be14bb4d6ec1ab5c6cafb1eca9cb76ba1c2e1c04ed018370634d4e659c77216 - languageName: node - linkType: hard - -"browserify-zlib@npm:^0.2.0": - version: 0.2.0 - resolution: "browserify-zlib@npm:0.2.0" - dependencies: - pako: "npm:~1.0.5" - checksum: 10c0/9ab10b6dc732c6c5ec8ebcbe5cb7fe1467f97402c9b2140113f47b5f187b9438f93a8e065d8baf8b929323c18324fbf1105af479ee86d9d36cab7d7ef3424ad9 - languageName: node - linkType: hard - "browserslist@npm:^4.23.3, browserslist@npm:^4.24.0": version: 4.24.2 resolution: "browserslist@npm:4.24.2" @@ -3545,24 +3428,6 @@ __metadata: languageName: node linkType: hard -"buffer-xor@npm:^1.0.3": - version: 1.0.3 - resolution: "buffer-xor@npm:1.0.3" - checksum: 10c0/fd269d0e0bf71ecac3146187cfc79edc9dbb054e2ee69b4d97dfb857c6d997c33de391696d04bdd669272751fa48e7872a22f3a6c7b07d6c0bc31dbe02a4075c - languageName: node - linkType: hard - -"buffer@npm:^4.3.0": - version: 4.9.2 - resolution: "buffer@npm:4.9.2" - dependencies: - base64-js: "npm:^1.0.2" - ieee754: "npm:^1.1.4" - isarray: "npm:^1.0.0" - checksum: 10c0/dc443d7e7caab23816b58aacdde710b72f525ad6eecd7d738fcaa29f6d6c12e8d9c13fed7219fd502be51ecf0615f5c077d4bdc6f9308dde2e53f8e5393c5b21 - languageName: node - linkType: hard - "buffer@npm:^6.0.3": version: 6.0.3 resolution: "buffer@npm:6.0.3" @@ -3573,13 +3438,6 @@ __metadata: languageName: node linkType: hard -"builtin-status-codes@npm:^3.0.0": - version: 3.0.0 - resolution: "builtin-status-codes@npm:3.0.0" - checksum: 10c0/c37bbba11a34c4431e56bd681b175512e99147defbe2358318d8152b3a01df7bf25e0305873947e5b350073d5ef41a364a22b37e48f1fb6d2fe6d5286a0f348c - languageName: node - linkType: hard - "bytes@npm:3.1.2, bytes@npm:^3.0.0": version: 3.1.2 resolution: "bytes@npm:3.1.2" @@ -3795,16 +3653,6 @@ __metadata: languageName: node linkType: hard -"cipher-base@npm:^1.0.0, cipher-base@npm:^1.0.1, cipher-base@npm:^1.0.3": - version: 1.0.4 - resolution: "cipher-base@npm:1.0.4" - dependencies: - inherits: "npm:^2.0.1" - safe-buffer: "npm:^5.0.1" - checksum: 10c0/d8d005f8b64d8a77b3d3ce531301ae7b45902c9cab4ec8b66bdbd2bf2a1d9fceb9a2133c293eb3c060b2d964da0f14c47fb740366081338aa3795dd1faa8984b - languageName: node - linkType: hard - "cjs-module-lexer@npm:^1.0.0": version: 1.4.1 resolution: "cjs-module-lexer@npm:1.4.1" @@ -4107,20 +3955,6 @@ __metadata: languageName: node linkType: hard -"console-browserify@npm:^1.1.0": - version: 1.2.0 - resolution: "console-browserify@npm:1.2.0" - checksum: 10c0/89b99a53b7d6cee54e1e64fa6b1f7ac24b844b4019c5d39db298637e55c1f4ffa5c165457ad984864de1379df2c8e1886cbbdac85d9dbb6876a9f26c3106f226 - languageName: node - linkType: hard - -"constants-browserify@npm:^1.0.0": - version: 1.0.0 - resolution: "constants-browserify@npm:1.0.0" - checksum: 10c0/ab49b1d59a433ed77c964d90d19e08b2f77213fb823da4729c0baead55e3c597f8f97ebccfdfc47bd896d43854a117d114c849a6f659d9986420e97da0f83ac5 - languageName: node - linkType: hard - "content-disposition@npm:0.5.4": version: 0.5.4 resolution: "content-disposition@npm:0.5.4" @@ -4179,43 +4013,6 @@ __metadata: languageName: node linkType: hard -"create-ecdh@npm:^4.0.4": - version: 4.0.4 - resolution: "create-ecdh@npm:4.0.4" - dependencies: - bn.js: "npm:^4.1.0" - elliptic: "npm:^6.5.3" - checksum: 10c0/77b11a51360fec9c3bce7a76288fc0deba4b9c838d5fb354b3e40c59194d23d66efe6355fd4b81df7580da0661e1334a235a2a5c040b7569ba97db428d466e7f - languageName: node - linkType: hard - -"create-hash@npm:^1.1.0, create-hash@npm:^1.1.2, create-hash@npm:^1.2.0": - version: 1.2.0 - resolution: "create-hash@npm:1.2.0" - dependencies: - cipher-base: "npm:^1.0.1" - inherits: "npm:^2.0.1" - md5.js: "npm:^1.3.4" - ripemd160: "npm:^2.0.1" - sha.js: "npm:^2.4.0" - checksum: 10c0/d402e60e65e70e5083cb57af96d89567954d0669e90550d7cec58b56d49c4b193d35c43cec8338bc72358198b8cbf2f0cac14775b651e99238e1cf411490f915 - languageName: node - linkType: hard - -"create-hmac@npm:^1.1.4, create-hmac@npm:^1.1.7": - version: 1.1.7 - resolution: "create-hmac@npm:1.1.7" - dependencies: - cipher-base: "npm:^1.0.3" - create-hash: "npm:^1.1.0" - inherits: "npm:^2.0.1" - ripemd160: "npm:^2.0.0" - safe-buffer: "npm:^5.0.1" - sha.js: "npm:^2.4.8" - checksum: 10c0/24332bab51011652a9a0a6d160eed1e8caa091b802335324ae056b0dcb5acbc9fcf173cf10d128eba8548c3ce98dfa4eadaa01bd02f44a34414baee26b651835 - languageName: node - linkType: hard - "create-jest@npm:^29.7.0": version: 29.7.0 resolution: "create-jest@npm:29.7.0" @@ -4266,26 +4063,6 @@ __metadata: languageName: node linkType: hard -"crypto-browserify@npm:^3.11.0": - version: 3.12.1 - resolution: "crypto-browserify@npm:3.12.1" - dependencies: - browserify-cipher: "npm:^1.0.1" - browserify-sign: "npm:^4.2.3" - create-ecdh: "npm:^4.0.4" - create-hash: "npm:^1.2.0" - create-hmac: "npm:^1.1.7" - diffie-hellman: "npm:^5.0.3" - hash-base: "npm:~3.0.4" - inherits: "npm:^2.0.4" - pbkdf2: "npm:^3.1.2" - public-encrypt: "npm:^4.0.3" - randombytes: "npm:^2.1.0" - randomfill: "npm:^1.0.4" - checksum: 10c0/184a2def7b16628e79841243232ab5497f18d8e158ac21b7ce90ab172427d0a892a561280adc08f9d4d517bce8db2a5b335dc21abb970f787f8e874bd7b9db7d - languageName: node - linkType: hard - "css-select@npm:^4.1.3": version: 4.3.0 resolution: "css-select@npm:4.3.0" @@ -4653,16 +4430,6 @@ __metadata: languageName: node linkType: hard -"des.js@npm:^1.0.0": - version: 1.1.0 - resolution: "des.js@npm:1.1.0" - dependencies: - inherits: "npm:^2.0.1" - minimalistic-assert: "npm:^1.0.0" - checksum: 10c0/671354943ad67493e49eb4c555480ab153edd7cee3a51c658082fcde539d2690ed2a4a0b5d1f401f9cde822edf3939a6afb2585f32c091f2d3a1b1665cd45236 - languageName: node - linkType: hard - "destroy@npm:1.2.0": version: 1.2.0 resolution: "destroy@npm:1.2.0" @@ -4723,17 +4490,6 @@ __metadata: languageName: node linkType: hard -"diffie-hellman@npm:^5.0.3": - version: 5.0.3 - resolution: "diffie-hellman@npm:5.0.3" - dependencies: - bn.js: "npm:^4.1.0" - miller-rabin: "npm:^4.0.0" - randombytes: "npm:^2.0.0" - checksum: 10c0/ce53ccafa9ca544b7fc29b08a626e23a9b6562efc2a98559a0c97b4718937cebaa9b5d7d0a05032cc9c1435e9b3c1532b9e9bf2e0ede868525922807ad6e1ecf - languageName: node - linkType: hard - "doctrine@npm:^3.0.0": version: 3.0.0 resolution: "doctrine@npm:3.0.0" @@ -4799,13 +4555,6 @@ __metadata: languageName: node linkType: hard -"domain-browser@npm:^1.1.1": - version: 1.2.0 - resolution: "domain-browser@npm:1.2.0" - checksum: 10c0/a955f482f4b4710fbd77c12a33e77548d63603c30c80f61a80519f27e3db1ba8530b914584cc9e9365d2038753d6b5bd1f4e6c81e432b007b0ec95b8b5e69b1b - languageName: node - linkType: hard - "domelementtype@npm:1, domelementtype@npm:^1.3.1": version: 1.3.1 resolution: "domelementtype@npm:1.3.1" @@ -4928,21 +4677,6 @@ __metadata: languageName: node linkType: hard -"elliptic@npm:^6.5.3, elliptic@npm:^6.5.5": - version: 6.6.0 - resolution: "elliptic@npm:6.6.0" - dependencies: - bn.js: "npm:^4.11.9" - brorand: "npm:^1.1.0" - hash.js: "npm:^1.0.0" - hmac-drbg: "npm:^1.0.1" - inherits: "npm:^2.0.4" - minimalistic-assert: "npm:^1.0.1" - minimalistic-crypto-utils: "npm:^1.0.1" - checksum: 10c0/42eb3492e218017bf8923a5d14a86f414952f2f771361805b3ae9f380923b5da53e203d0d92be95cb0a248858a78db7db5934a346e268abb757e6fe561d401c9 - languageName: node - linkType: hard - "emittery@npm:^0.13.1": version: 0.13.1 resolution: "emittery@npm:0.13.1" @@ -5475,7 +5209,7 @@ __metadata: languageName: node linkType: hard -"events@npm:^3.0.0, events@npm:^3.3.0": +"events@npm:^3.3.0": version: 3.3.0 resolution: "events@npm:3.3.0" checksum: 10c0/d6b6f2adbccbcda74ddbab52ed07db727ef52e31a61ed26db9feb7dc62af7fc8e060defa65e5f8af9449b86b52cc1a1f6a79f2eafcf4e62add2b7a1fa4a432f6 @@ -5489,17 +5223,6 @@ __metadata: languageName: node linkType: hard -"evp_bytestokey@npm:^1.0.0, evp_bytestokey@npm:^1.0.3": - version: 1.0.3 - resolution: "evp_bytestokey@npm:1.0.3" - dependencies: - md5.js: "npm:^1.3.4" - node-gyp: "npm:latest" - safe-buffer: "npm:^5.1.1" - checksum: 10c0/77fbe2d94a902a80e9b8f5a73dcd695d9c14899c5e82967a61b1fc6cbbb28c46552d9b127cff47c45fcf684748bdbcfa0a50410349109de87ceb4b199ef6ee99 - languageName: node - linkType: hard - "execa@npm:^5.0.0": version: 5.1.1 resolution: "execa@npm:5.1.1" @@ -5946,7 +5669,7 @@ __metadata: sass: "npm:^1.83.4" sass-embedded: "npm:^1.83.4" sax: "npm:^1.4.1" - shadow-cljs: "npm:2.28.20" + shadow-cljs: "npm:3.0.3" source-map-support: "npm:^0.5.21" storybook: "npm:^8.5.2" style-dictionary: "npm:5.0.0-rc.1" @@ -6403,37 +6126,6 @@ __metadata: languageName: node linkType: hard -"hash-base@npm:^3.0.0": - version: 3.1.0 - resolution: "hash-base@npm:3.1.0" - dependencies: - inherits: "npm:^2.0.4" - readable-stream: "npm:^3.6.0" - safe-buffer: "npm:^5.2.0" - checksum: 10c0/663eabcf4173326fbb65a1918a509045590a26cc7e0964b754eef248d281305c6ec9f6b31cb508d02ffca383ab50028180ce5aefe013e942b44a903ac8dc80d0 - languageName: node - linkType: hard - -"hash-base@npm:~3.0, hash-base@npm:~3.0.4": - version: 3.0.4 - resolution: "hash-base@npm:3.0.4" - dependencies: - inherits: "npm:^2.0.1" - safe-buffer: "npm:^5.0.1" - checksum: 10c0/a13357dccb3827f0bb0b56bf928da85c428dc8670f6e4a1c7265e4f1653ce02d69030b40fd01b0f1d218a995a066eea279cded9cec72d207b593bcdfe309c2f0 - languageName: node - linkType: hard - -"hash.js@npm:^1.0.0, hash.js@npm:^1.0.3": - version: 1.1.7 - resolution: "hash.js@npm:1.1.7" - dependencies: - inherits: "npm:^2.0.3" - minimalistic-assert: "npm:^1.0.1" - checksum: 10c0/41ada59494eac5332cfc1ce6b7ebdd7b88a3864a6d6b08a3ea8ef261332ed60f37f10877e0c825aaa4bddebf164fbffa618286aeeec5296675e2671cbfa746c4 - languageName: node - linkType: hard - "hasha@npm:^5.0.0": version: 5.2.2 resolution: "hasha@npm:5.2.2" @@ -6460,17 +6152,6 @@ __metadata: languageName: node linkType: hard -"hmac-drbg@npm:^1.0.1": - version: 1.0.1 - resolution: "hmac-drbg@npm:1.0.1" - dependencies: - hash.js: "npm:^1.0.3" - minimalistic-assert: "npm:^1.0.0" - minimalistic-crypto-utils: "npm:^1.0.1" - checksum: 10c0/f3d9ba31b40257a573f162176ac5930109816036c59a09f901eb2ffd7e5e705c6832bedfff507957125f2086a0ab8f853c0df225642a88bf1fcaea945f20600d - languageName: node - linkType: hard - "homedir-polyfill@npm:^1.0.0": version: 1.0.3 resolution: "homedir-polyfill@npm:1.0.3" @@ -6547,13 +6228,6 @@ __metadata: languageName: node linkType: hard -"https-browserify@npm:^1.0.0": - version: 1.0.0 - resolution: "https-browserify@npm:1.0.0" - checksum: 10c0/e17b6943bc24ea9b9a7da5714645d808670af75a425f29baffc3284962626efdc1eb3aa9bbffaa6e64028a6ad98af5b09fabcb454a8f918fb686abfdc9e9b8ae - languageName: node - linkType: hard - "https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.5": version: 7.0.5 resolution: "https-proxy-agent@npm:7.0.5" @@ -6605,7 +6279,7 @@ __metadata: languageName: node linkType: hard -"ieee754@npm:^1.1.4, ieee754@npm:^1.2.1": +"ieee754@npm:^1.2.1": version: 1.2.1 resolution: "ieee754@npm:1.2.1" checksum: 10c0/b0782ef5e0935b9f12883a2e2aa37baa75da6e66ce6515c168697b42160807d9330de9a32ec1ed73149aea02e0d822e572bca6f1e22bdcbd2149e13b050b17bb @@ -6676,7 +6350,7 @@ __metadata: languageName: node linkType: hard -"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.1, inherits@npm:~2.0.3": +"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.3": version: 2.0.4 resolution: "inherits@npm:2.0.4" checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2 @@ -7063,13 +6737,6 @@ __metadata: languageName: node linkType: hard -"isarray@npm:^1.0.0, isarray@npm:~1.0.0": - version: 1.0.0 - resolution: "isarray@npm:1.0.0" - checksum: 10c0/18b5be6669be53425f0b84098732670ed4e727e3af33bc7f948aac01782110eb9a18b3b329c5323bcdd3acdaae547ee077d3951317e7f133bff7105264b3003d - languageName: node - linkType: hard - "isarray@npm:^2.0.5": version: 2.0.5 resolution: "isarray@npm:2.0.5" @@ -7077,6 +6744,13 @@ __metadata: languageName: node linkType: hard +"isarray@npm:~1.0.0": + version: 1.0.0 + resolution: "isarray@npm:1.0.0" + checksum: 10c0/18b5be6669be53425f0b84098732670ed4e727e3af33bc7f948aac01782110eb9a18b3b329c5323bcdd3acdaae547ee077d3951317e7f133bff7105264b3003d + languageName: node + linkType: hard + "isexe@npm:^2.0.0": version: 2.0.0 resolution: "isexe@npm:2.0.0" @@ -8248,17 +7922,6 @@ __metadata: languageName: node linkType: hard -"md5.js@npm:^1.3.4": - version: 1.3.5 - resolution: "md5.js@npm:1.3.5" - dependencies: - hash-base: "npm:^3.0.0" - inherits: "npm:^2.0.1" - safe-buffer: "npm:^5.1.2" - checksum: 10c0/b7bd75077f419c8e013fc4d4dada48be71882e37d69a44af65a2f2804b91e253441eb43a0614423a1c91bb830b8140b0dc906bc797245e2e275759584f4efcc5 - languageName: node - linkType: hard - "mdn-data@npm:2.0.14": version: 2.0.14 resolution: "mdn-data@npm:2.0.14" @@ -8362,18 +8025,6 @@ __metadata: languageName: node linkType: hard -"miller-rabin@npm:^4.0.0": - version: 4.0.1 - resolution: "miller-rabin@npm:4.0.1" - dependencies: - bn.js: "npm:^4.0.0" - brorand: "npm:^1.0.1" - bin: - miller-rabin: bin/miller-rabin - checksum: 10c0/26b2b96f6e49dbcff7faebb78708ed2f5f9ae27ac8cbbf1d7c08f83cf39bed3d418c0c11034dce997da70d135cc0ff6f3a4c15dc452f8e114c11986388a64346 - languageName: node - linkType: hard - "mime-db@npm:1.52.0": version: 1.52.0 resolution: "mime-db@npm:1.52.0" @@ -8420,20 +8071,6 @@ __metadata: languageName: node linkType: hard -"minimalistic-assert@npm:^1.0.0, minimalistic-assert@npm:^1.0.1": - version: 1.0.1 - resolution: "minimalistic-assert@npm:1.0.1" - checksum: 10c0/96730e5601cd31457f81a296f521eb56036e6f69133c0b18c13fe941109d53ad23a4204d946a0d638d7f3099482a0cec8c9bb6d642604612ce43ee536be3dddd - languageName: node - linkType: hard - -"minimalistic-crypto-utils@npm:^1.0.1": - version: 1.0.1 - resolution: "minimalistic-crypto-utils@npm:1.0.1" - checksum: 10c0/790ecec8c5c73973a4fbf2c663d911033e8494d5fb0960a4500634766ab05d6107d20af896ca2132e7031741f19888154d44b2408ada0852446705441383e9f8 - languageName: node - linkType: hard - "minimatch@npm:9.0.1": version: 9.0.1 resolution: "minimatch@npm:9.0.1" @@ -8696,37 +8333,6 @@ __metadata: languageName: node linkType: hard -"node-libs-browser@npm:^2.2.1": - version: 2.2.1 - resolution: "node-libs-browser@npm:2.2.1" - dependencies: - assert: "npm:^1.1.1" - browserify-zlib: "npm:^0.2.0" - buffer: "npm:^4.3.0" - console-browserify: "npm:^1.1.0" - constants-browserify: "npm:^1.0.0" - crypto-browserify: "npm:^3.11.0" - domain-browser: "npm:^1.1.1" - events: "npm:^3.0.0" - https-browserify: "npm:^1.0.0" - os-browserify: "npm:^0.3.0" - path-browserify: "npm:0.0.1" - process: "npm:^0.11.10" - punycode: "npm:^1.2.4" - querystring-es3: "npm:^0.2.0" - readable-stream: "npm:^2.3.3" - stream-browserify: "npm:^2.0.1" - stream-http: "npm:^2.7.2" - string_decoder: "npm:^1.0.0" - timers-browserify: "npm:^2.0.4" - tty-browserify: "npm:0.0.0" - url: "npm:^0.11.0" - util: "npm:^0.11.0" - vm-browserify: "npm:^1.0.1" - checksum: 10c0/0e05321a6396408903ed642231d2bca7dd96492d074c7af161ba06a63c95378bd3de50b4105eccbbc02d93ba3da69f0ff5e624bc2a8c92ca462ceb6a403e7986 - languageName: node - linkType: hard - "node-preload@npm:^0.2.1": version: 0.2.1 resolution: "node-preload@npm:0.2.1" @@ -9011,13 +8617,6 @@ __metadata: languageName: node linkType: hard -"os-browserify@npm:^0.3.0": - version: 0.3.0 - resolution: "os-browserify@npm:0.3.0" - checksum: 10c0/6ff32cb1efe2bc6930ad0fd4c50e30c38010aee909eba8d65be60af55efd6cbb48f0287e3649b4e3f3a63dce5a667b23c187c4293a75e557f0d5489d735bcf52 - languageName: node - linkType: hard - "os-homedir@npm:^1.0.1": version: 1.0.2 resolution: "os-homedir@npm:1.0.2" @@ -9121,27 +8720,13 @@ __metadata: languageName: node linkType: hard -"pako@npm:~1.0.2, pako@npm:~1.0.5": +"pako@npm:~1.0.2": version: 1.0.11 resolution: "pako@npm:1.0.11" checksum: 10c0/86dd99d8b34c3930345b8bbeb5e1cd8a05f608eeb40967b293f72fe469d0e9c88b783a8777e4cc7dc7c91ce54c5e93d88ff4b4f060e6ff18408fd21030d9ffbe languageName: node linkType: hard -"parse-asn1@npm:^5.0.0, parse-asn1@npm:^5.1.7": - version: 5.1.7 - resolution: "parse-asn1@npm:5.1.7" - dependencies: - asn1.js: "npm:^4.10.1" - browserify-aes: "npm:^1.2.0" - evp_bytestokey: "npm:^1.0.3" - hash-base: "npm:~3.0" - pbkdf2: "npm:^3.1.2" - safe-buffer: "npm:^5.2.1" - checksum: 10c0/05eb5937405c904eb5a7f3633bab1acc11f4ae3478a07ef5c6d81ce88c3c0e505ff51f9c7b935ebc1265c868343793698fc91025755a895d0276f620f95e8a82 - languageName: node - linkType: hard - "parse-json@npm:^4.0.0": version: 4.0.0 resolution: "parse-json@npm:4.0.0" @@ -9219,13 +8804,6 @@ __metadata: languageName: node linkType: hard -"path-browserify@npm:0.0.1": - version: 0.0.1 - resolution: "path-browserify@npm:0.0.1" - checksum: 10c0/3d59710cddeea06509d91935196185900f3d9d29376dff68ff0e146fbd41d0fb304e983d0158f30cabe4dd2ffcc6a7d3d977631994ee984c88e66aed50a1ccd3 - languageName: node - linkType: hard - "path-exists@npm:^4.0.0": version: 4.0.0 resolution: "path-exists@npm:4.0.0" @@ -9328,19 +8906,6 @@ __metadata: languageName: node linkType: hard -"pbkdf2@npm:^3.1.2": - version: 3.1.2 - resolution: "pbkdf2@npm:3.1.2" - dependencies: - create-hash: "npm:^1.1.2" - create-hmac: "npm:^1.1.4" - ripemd160: "npm:^2.0.1" - safe-buffer: "npm:^5.0.1" - sha.js: "npm:^2.4.8" - checksum: 10c0/5a30374e87d33fa080a92734d778cf172542cc7e41b96198c4c88763997b62d7850de3fbda5c3111ddf79805ee7c1da7046881c90ac4920b5e324204518b05fd - languageName: node - linkType: hard - "picocolors@npm:^0.2.1": version: 0.2.1 resolution: "picocolors@npm:0.2.1" @@ -9815,21 +9380,7 @@ __metadata: languageName: node linkType: hard -"public-encrypt@npm:^4.0.3": - version: 4.0.3 - resolution: "public-encrypt@npm:4.0.3" - dependencies: - bn.js: "npm:^4.1.0" - browserify-rsa: "npm:^4.0.0" - create-hash: "npm:^1.1.0" - parse-asn1: "npm:^5.0.0" - randombytes: "npm:^2.0.1" - safe-buffer: "npm:^5.1.2" - checksum: 10c0/6c2cc19fbb554449e47f2175065d6b32f828f9b3badbee4c76585ac28ae8641aafb9bb107afc430c33c5edd6b05dbe318df4f7d6d7712b1093407b11c4280700 - languageName: node - linkType: hard - -"punycode@npm:^1.2.4, punycode@npm:^1.4.1": +"punycode@npm:^1.4.1": version: 1.4.1 resolution: "punycode@npm:1.4.1" checksum: 10c0/354b743320518aef36f77013be6e15da4db24c2b4f62c5f1eb0529a6ed02fbaf1cb52925785f6ab85a962f2b590d9cd5ad730b70da72b5f180e2556b8bd3ca08 @@ -9859,22 +9410,6 @@ __metadata: languageName: node linkType: hard -"querystring-es3@npm:^0.2.0": - version: 0.2.1 - resolution: "querystring-es3@npm:0.2.1" - checksum: 10c0/476938c1adb45c141f024fccd2ffd919a3746e79ed444d00e670aad68532977b793889648980e7ca7ff5ffc7bfece623118d0fbadcaf217495eeb7059ae51580 - languageName: node - linkType: hard - -"randombytes@npm:^2.0.0, randombytes@npm:^2.0.1, randombytes@npm:^2.0.5, randombytes@npm:^2.1.0": - version: 2.1.0 - resolution: "randombytes@npm:2.1.0" - dependencies: - safe-buffer: "npm:^5.1.0" - checksum: 10c0/50395efda7a8c94f5dffab564f9ff89736064d32addf0cc7e8bf5e4166f09f8ded7a0849ca6c2d2a59478f7d90f78f20d8048bca3cdf8be09d8e8a10790388f3 - languageName: node - linkType: hard - "randomcolor@npm:^0.6.2": version: 0.6.2 resolution: "randomcolor@npm:0.6.2" @@ -9882,16 +9417,6 @@ __metadata: languageName: node linkType: hard -"randomfill@npm:^1.0.4": - version: 1.0.4 - resolution: "randomfill@npm:1.0.4" - dependencies: - randombytes: "npm:^2.0.5" - safe-buffer: "npm:^5.1.0" - checksum: 10c0/11aeed35515872e8f8a2edec306734e6b74c39c46653607f03c68385ab8030e2adcc4215f76b5e4598e028c4750d820afd5c65202527d831d2a5f207fe2bc87c - languageName: node - linkType: hard - "range-parser@npm:~1.2.1": version: 1.2.1 resolution: "range-parser@npm:1.2.1" @@ -10026,7 +9551,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:2 || 3, readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.6.0": +"readable-stream@npm:2 || 3, readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0": version: 3.6.2 resolution: "readable-stream@npm:3.6.2" dependencies: @@ -10037,7 +9562,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^2.0.2, readable-stream@npm:^2.3.3, readable-stream@npm:^2.3.5, readable-stream@npm:^2.3.6, readable-stream@npm:^2.3.8, readable-stream@npm:~2.3.6": +"readable-stream@npm:^2.3.5, readable-stream@npm:~2.3.6": version: 2.3.8 resolution: "readable-stream@npm:2.3.8" dependencies: @@ -10081,7 +9606,7 @@ __metadata: languageName: node linkType: hard -"readline-sync@npm:^1.4.7": +"readline-sync@npm:^1.4.10": version: 1.4.10 resolution: "readline-sync@npm:1.4.10" checksum: 10c0/0a4d0fe4ad501f8f005a3c9cbf3cc0ae6ca2ced93e9a1c7c46f226bdfcb6ef5d3f437ae7e9d2e1098ee13524a3739c830e4c8dbc7f543a693eecd293e41093a3 @@ -10293,16 +9818,6 @@ __metadata: languageName: node linkType: hard -"ripemd160@npm:^2.0.0, ripemd160@npm:^2.0.1": - version: 2.0.2 - resolution: "ripemd160@npm:2.0.2" - dependencies: - hash-base: "npm:^3.0.0" - inherits: "npm:^2.0.1" - checksum: 10c0/f6f0df78817e78287c766687aed4d5accbebc308a8e7e673fb085b9977473c1f139f0c5335d353f172a915bb288098430755d2ad3c4f30612f4dd0c901cd2c3a - languageName: node - linkType: hard - "rollup@npm:^4.23.0": version: 4.32.1 resolution: "rollup@npm:4.32.1" @@ -10410,7 +9925,7 @@ __metadata: languageName: node linkType: hard -"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.0, safe-buffer@npm:^5.2.1, safe-buffer@npm:~5.2.0": +"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.2.1, safe-buffer@npm:~5.2.0": version: 5.2.1 resolution: "safe-buffer@npm:5.2.1" checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3 @@ -10803,7 +10318,7 @@ __metadata: languageName: node linkType: hard -"setimmediate@npm:^1.0.4, setimmediate@npm:^1.0.5": +"setimmediate@npm:^1.0.5": version: 1.0.5 resolution: "setimmediate@npm:1.0.5" checksum: 10c0/5bae81bfdbfbd0ce992893286d49c9693c82b1bcc00dcaaf3a09c8f428fdeacf4190c013598b81875dfac2b08a572422db7df779a99332d0fce186d15a3e4d49 @@ -10817,18 +10332,6 @@ __metadata: languageName: node linkType: hard -"sha.js@npm:^2.4.0, sha.js@npm:^2.4.8": - version: 2.4.11 - resolution: "sha.js@npm:2.4.11" - dependencies: - inherits: "npm:^2.0.1" - safe-buffer: "npm:^5.0.1" - bin: - sha.js: ./bin.js - checksum: 10c0/b7a371bca8821c9cc98a0aeff67444a03d48d745cb103f17228b96793f455f0eb0a691941b89ea1e60f6359207e36081d9be193252b0f128e0daf9cfea2815a5 - languageName: node - linkType: hard - "shadow-cljs-jar@npm:1.3.4": version: 1.3.4 resolution: "shadow-cljs-jar@npm:1.3.4" @@ -10836,19 +10339,18 @@ __metadata: languageName: node linkType: hard -"shadow-cljs@npm:2.28.20": - version: 2.28.20 - resolution: "shadow-cljs@npm:2.28.20" +"shadow-cljs@npm:3.0.3": + version: 3.0.3 + resolution: "shadow-cljs@npm:3.0.3" dependencies: - node-libs-browser: "npm:^2.2.1" - readline-sync: "npm:^1.4.7" + readline-sync: "npm:^1.4.10" shadow-cljs-jar: "npm:1.3.4" - source-map-support: "npm:^0.4.15" - which: "npm:^1.3.1" - ws: "npm:^7.4.6" + source-map-support: "npm:^0.5.21" + which: "npm:^5.0.0" + ws: "npm:^8.18.1" bin: shadow-cljs: cli/runner.js - checksum: 10c0/f4aaa5127ffd7b717f00fec0b3e1d01df5f31effbaa741e4980a87432783d09a400807c39e3dcaeb4d7b3c13f432f55e96ead22291a9b90d437c8994d72fb2c2 + checksum: 10c0/ba592ba854b00f8065f2443f1f6cf9f1611eedb977ae8f77f0353e6c8e9fa77fa99fee2473d934c51195212f68942dcdf780a3b82b9d619b05a43dcbff13a29d languageName: node linkType: hard @@ -11025,15 +10527,6 @@ __metadata: languageName: node linkType: hard -"source-map-support@npm:^0.4.15": - version: 0.4.18 - resolution: "source-map-support@npm:0.4.18" - dependencies: - source-map: "npm:^0.5.6" - checksum: 10c0/cd9f0309c1632b1e01a7715a009e0b036d565f3af8930fa8cda2a06aeec05ad1d86180e743b7e1f02cc3c97abe8b6d8de7c3878c2d8e01e86e17f876f7ecf98e - languageName: node - linkType: hard - "source-map-support@npm:^0.5.21": version: 0.5.21 resolution: "source-map-support@npm:0.5.21" @@ -11044,7 +10537,7 @@ __metadata: languageName: node linkType: hard -"source-map@npm:^0.5.1, source-map@npm:^0.5.6": +"source-map@npm:^0.5.1": version: 0.5.7 resolution: "source-map@npm:0.5.7" checksum: 10c0/904e767bb9c494929be013017380cbba013637da1b28e5943b566031e29df04fba57edf3f093e0914be094648b577372bd8ad247fa98cfba9c600794cd16b599 @@ -11203,29 +10696,6 @@ __metadata: languageName: node linkType: hard -"stream-browserify@npm:^2.0.1": - version: 2.0.2 - resolution: "stream-browserify@npm:2.0.2" - dependencies: - inherits: "npm:~2.0.1" - readable-stream: "npm:^2.0.2" - checksum: 10c0/485562bd5d962d633ae178449029c6fa2611052e356bdb5668f768544aa4daa94c4f9a97de718f3f30ad98f3cb98a5f396252bb3855aff153c138f79c0e8f6ac - languageName: node - linkType: hard - -"stream-http@npm:^2.7.2": - version: 2.8.3 - resolution: "stream-http@npm:2.8.3" - dependencies: - builtin-status-codes: "npm:^3.0.0" - inherits: "npm:^2.0.1" - readable-stream: "npm:^2.3.6" - to-arraybuffer: "npm:^1.0.0" - xtend: "npm:^4.0.0" - checksum: 10c0/fbe7d327a29216bbabe88d3819bb8f7a502f11eeacf3212579e5af1f76fa7283f6ffa66134ab7d80928070051f571d1029e85f65ce3369fffd4c4df3669446c4 - languageName: node - linkType: hard - "stream-to-array@npm:^2.3.0": version: 2.3.0 resolution: "stream-to-array@npm:2.3.0" @@ -11346,7 +10816,7 @@ __metadata: languageName: node linkType: hard -"string_decoder@npm:^1.0.0, string_decoder@npm:^1.1.1, string_decoder@npm:^1.3.0": +"string_decoder@npm:^1.1.1, string_decoder@npm:^1.3.0": version: 1.3.0 resolution: "string_decoder@npm:1.3.0" dependencies: @@ -11644,15 +11114,6 @@ __metadata: languageName: node linkType: hard -"timers-browserify@npm:^2.0.4": - version: 2.0.12 - resolution: "timers-browserify@npm:2.0.12" - dependencies: - setimmediate: "npm:^1.0.4" - checksum: 10c0/98e84db1a685bc8827c117a8bc62aac811ad56a995d07938fc7ed8cdc5bf3777bfe2d4e5da868847194e771aac3749a20f6cdd22091300fe889a76fe214a4641 - languageName: node - linkType: hard - "timers-ext@npm:^0.1.7": version: 0.1.8 resolution: "timers-ext@npm:0.1.8" @@ -11769,13 +11230,6 @@ __metadata: languageName: node linkType: hard -"to-arraybuffer@npm:^1.0.0": - version: 1.0.1 - resolution: "to-arraybuffer@npm:1.0.1" - checksum: 10c0/2460bd95524f4845a751e4f8bf9937f9f3dcd1651f104e1512868782f858f8302c1cf25bbc30794bc1b3ff65c4e135158377302f2abaff43a2d8e3c38dfe098c - languageName: node - linkType: hard - "to-regex-range@npm:^5.0.1": version: 5.0.1 resolution: "to-regex-range@npm:5.0.1" @@ -11876,13 +11330,6 @@ __metadata: languageName: node linkType: hard -"tty-browserify@npm:0.0.0": - version: 0.0.0 - resolution: "tty-browserify@npm:0.0.0" - checksum: 10c0/c0c68206565f1372e924d5cdeeff1a0d9cc729833f1da98c03d78be8f939e5f61a107bd0ab77d1ef6a47d62bb0e48b1081fbea273acf404959e22fd3891439c5 - languageName: node - linkType: hard - "type-detect@npm:4.0.8": version: 4.0.8 resolution: "type-detect@npm:4.0.8" @@ -12132,7 +11579,7 @@ __metadata: languageName: node linkType: hard -"url@npm:^0.11.0, url@npm:^0.11.3": +"url@npm:^0.11.3": version: 0.11.4 resolution: "url@npm:0.11.4" dependencies: @@ -12149,7 +11596,7 @@ __metadata: languageName: node linkType: hard -"util@npm:^0.10.3, util@npm:^0.10.4": +"util@npm:^0.10.3": version: 0.10.4 resolution: "util@npm:0.10.4" dependencies: @@ -12158,15 +11605,6 @@ __metadata: languageName: node linkType: hard -"util@npm:^0.11.0": - version: 0.11.1 - resolution: "util@npm:0.11.1" - dependencies: - inherits: "npm:2.0.3" - checksum: 10c0/8e9d1a85e661c8a8d9883d821aedbff3f8d9c3accd85357020905386ada5653b20389fc3591901e2a0bde64f8dc86b28c3f990114aa5a38eaaf30b455fa3cdf6 - languageName: node - linkType: hard - "util@npm:^0.12.5": version: 0.12.5 resolution: "util@npm:0.12.5" @@ -12383,13 +11821,6 @@ __metadata: languageName: node linkType: hard -"vm-browserify@npm:^1.0.1": - version: 1.1.2 - resolution: "vm-browserify@npm:1.1.2" - checksum: 10c0/0cc1af6e0d880deb58bc974921320c187f9e0a94f25570fca6b1bd64e798ce454ab87dfd797551b1b0cc1849307421aae0193cedf5f06bdb5680476780ee344b - languageName: node - linkType: hard - "w3c-xmlserializer@npm:^5.0.0": version: 5.0.0 resolution: "w3c-xmlserializer@npm:5.0.0" @@ -12548,7 +11979,7 @@ __metadata: languageName: node linkType: hard -"which@npm:^1.2.12, which@npm:^1.2.9, which@npm:^1.3.1": +"which@npm:^1.2.12, which@npm:^1.2.9": version: 1.3.1 resolution: "which@npm:1.3.1" dependencies: @@ -12581,6 +12012,17 @@ __metadata: languageName: node linkType: hard +"which@npm:^5.0.0": + version: 5.0.0 + resolution: "which@npm:5.0.0" + dependencies: + isexe: "npm:^3.1.1" + bin: + node-which: bin/which.js + checksum: 10c0/e556e4cd8b7dbf5df52408c9a9dd5ac6518c8c5267c8953f5b0564073c66ed5bf9503b14d876d0e9c7844d4db9725fb0dcf45d6e911e17e26ab363dc3965ae7b + languageName: node + linkType: hard + "why-is-node-running@npm:^2.3.0": version: 2.3.0 resolution: "why-is-node-running@npm:2.3.0" @@ -12692,21 +12134,6 @@ __metadata: languageName: node linkType: hard -"ws@npm:^7.4.6": - version: 7.5.10 - resolution: "ws@npm:7.5.10" - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - checksum: 10c0/bd7d5f4aaf04fae7960c23dcb6c6375d525e00f795dd20b9385902bd008c40a94d3db3ce97d878acc7573df852056ca546328b27b39f47609f80fb22a0a9b61d - languageName: node - linkType: hard - "ws@npm:^8.18.0, ws@npm:^8.2.3": version: 8.18.0 resolution: "ws@npm:8.18.0" @@ -12722,6 +12149,21 @@ __metadata: languageName: node linkType: hard +"ws@npm:^8.18.1": + version: 8.18.2 + resolution: "ws@npm:8.18.2" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 10c0/4b50f67931b8c6943c893f59c524f0e4905bbd183016cfb0f2b8653aa7f28dad4e456b9d99d285bbb67cca4fedd9ce90dfdfaa82b898a11414ebd66ee99141e4 + languageName: node + linkType: hard + "xml-name-validator@npm:^5.0.0": version: 5.0.0 resolution: "xml-name-validator@npm:5.0.0" @@ -12759,7 +12201,7 @@ __metadata: languageName: node linkType: hard -"xtend@npm:^4.0.0, xtend@npm:~4.0.1": +"xtend@npm:~4.0.1": version: 4.0.2 resolution: "xtend@npm:4.0.2" checksum: 10c0/366ae4783eec6100f8a02dff02ac907bf29f9a00b82ac0264b4d8b832ead18306797e283cf19de776538babfdcb2101375ec5646b59f08c52128ac4ab812ed0e From 66ee9edaf840fd9575b741ff7b677280009e6bef Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 7 May 2025 11:32:46 +0200 Subject: [PATCH 5/7] :sparkles: Add minor enhacements and naming fixes on schemas --- common/src/app/common/geom/matrix.cljc | 29 ++- common/src/app/common/geom/point.cljc | 32 ++- common/src/app/common/types/color.cljc | 43 ++-- common/src/app/common/types/component.cljc | 26 +- common/src/app/common/types/container.cljc | 18 +- common/src/app/common/types/file.cljc | 11 +- common/src/app/common/types/page.cljc | 11 +- common/src/app/common/types/plugins.cljc | 13 +- common/src/app/common/types/shape.cljc | 233 +++++++++--------- common/src/app/common/types/shape/layout.cljc | 37 ++- common/src/app/common/types/shape_tree.cljc | 2 + common/src/app/common/types/typography.cljc | 36 +-- common/src/app/common/types/variant.cljc | 9 +- frontend/src/app/main/data/workspace.cljs | 19 +- .../app/main/data/workspace/libraries.cljs | 51 ++-- .../src/app/main/data/workspace/shapes.cljs | 46 ++-- .../data/workspace/tokens/library_edit.cljs | 14 +- frontend/src/app/main/fonts.cljs | 6 +- 18 files changed, 312 insertions(+), 324 deletions(-) diff --git a/common/src/app/common/geom/matrix.cljc b/common/src/app/common/geom/matrix.cljc index d6e545cd9..7fcabd192 100644 --- a/common/src/app/common/geom/matrix.cljc +++ b/common/src/app/common/geom/matrix.cljc @@ -126,21 +126,20 @@ o))) (def schema:matrix - {:type :map - :pred valid-matrix? - :type-properties - {:title "matrix" - :description "Matrix instance" - :error/message "expected a valid matrix instance" - :gen/gen (matrix-generator) - :decode/json decode-matrix - :decode/string decode-matrix - :encode/json matrix->json - :encode/string matrix->str - ::oapi/type "string" - ::oapi/format "matrix"}}) - -(sm/register! ::matrix schema:matrix) + (sm/register! + {:type ::matrix + :pred valid-matrix? + :type-properties + {:title "matrix" + :description "Matrix instance" + :error/message "expected a valid matrix instance" + :gen/gen (matrix-generator) + :decode/json decode-matrix + :decode/string decode-matrix + :encode/json matrix->json + :encode/string matrix->str + ::oapi/type "string" + ::oapi/format "matrix"}})) ;; FIXME: deprecated (s/def ::a ::us/safe-float) diff --git a/common/src/app/common/geom/point.cljc b/common/src/app/common/geom/point.cljc index a7cf2d412..eb9b3b90a 100644 --- a/common/src/app/common/geom/point.cljc +++ b/common/src/app/common/geom/point.cljc @@ -85,24 +85,22 @@ (into {} p) p)) -;; FIXME: make like matrix (def schema:point - {:type ::point - :pred valid-point? - :type-properties - {:title "point" - :description "Point" - :error/message "expected a valid point" - :gen/gen (->> (sg/tuple (sg/small-int) (sg/small-int)) - (sg/fmap #(apply pos->Point %))) - ::oapi/type "string" - ::oapi/format "point" - :decode/json decode-point - :decode/string decode-point - :encode/json point->json - :encode/string point->str}}) - -(sm/register! schema:point) + (sm/register! + {:type ::point + :pred valid-point? + :type-properties + {:title "point" + :description "Point" + :error/message "expected a valid point" + :gen/gen (->> (sg/tuple (sg/small-int) (sg/small-int)) + (sg/fmap #(apply pos->Point %))) + ::oapi/type "string" + ::oapi/format "point" + :decode/json decode-point + :decode/string decode-point + :encode/json point->json + :encode/string point->str}})) (defn point-like? [{:keys [x y] :as v}] diff --git a/common/src/app/common/types/color.cljc b/common/src/app/common/types/color.cljc index f93f75617..eb2018fad 100644 --- a/common/src/app/common/types/color.cljc +++ b/common/src/app/common/types/color.cljc @@ -41,17 +41,18 @@ [o] (and (string? o) (some? (re-matches rgb-color-re o)))) -(def ^:private type:rgb-color - {:type :string - :pred rgb-color-string? - :type-properties - {:title "rgb-color" - :description "RGB Color String" - :error/message "expected a valid RGB color" - :error/code "errors.invalid-rgb-color" - :gen/gen (generate-rgb-color) - ::oapi/type "integer" - ::oapi/format "int64"}}) +(def schema:rgb-color + (sm/register! + {:type ::rgb-color + :pred rgb-color-string? + :type-properties + {:title "rgb-color" + :description "RGB Color String" + :error/message "expected a valid RGB color" + :error/code "errors.invalid-rgb-color" + :gen/gen (generate-rgb-color) + ::oapi/type "integer" + ::oapi/format "int64"}})) (def schema:image-color [:map {:title "ImageColor"} @@ -76,7 +77,7 @@ [:stops [:vector {:min 1 :gen/max 2} [:map {:title "GradientStop"} - [:color ::rgb-color] + [:color schema:rgb-color] [:opacity {:optional true} [:maybe ::sm/safe-number]] [:offset ::sm/safe-number]]]]]) @@ -86,7 +87,7 @@ [:name {:optional true} :string] [:path {:optional true} [:maybe :string]] [:value {:optional true} [:maybe :string]] - [:color {:optional true} [:maybe ::rgb-color]] + [: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] @@ -103,12 +104,17 @@ [:and [:map {:title "RecentColor"} [:opacity {:optional true} [:maybe ::sm/safe-number]] - [:color {:optional true} [:maybe ::rgb-color]] + [:color {:optional true} [:maybe schema:rgb-color]] [:gradient {:optional true} [:maybe schema:gradient]] [:image {:optional true} [:maybe schema:image-color]]] [::sm/contains-any {:strict true} [:color :gradient :image]]]) -(sm/register! ::rgb-color type:rgb-color) +;; Same as color but with :id prop required +(def schema:library-color + [:and + (sm/required-keys schema:color-attrs [:id]) + [::sm/contains-any {:strict true} [:color :gradient :image]]]) + (sm/register! ::color schema:color) (sm/register! ::gradient schema:gradient) (sm/register! ::image-color schema:image-color) @@ -119,10 +125,13 @@ (sm/lazy-validator schema:color)) (def check-color - (sm/check-fn schema:color :hint "expected valid color struct")) + (sm/check-fn schema:color :hint "expected valid color")) + +(def check-library-color + (sm/check-fn schema:library-color :hint "expected valid library color")) (def check-recent-color - (sm/check-fn schema:recent-color)) + (sm/check-fn schema:recent-color :hint "expected valid recent color")) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; HELPERS diff --git a/common/src/app/common/types/component.cljc b/common/src/app/common/types/component.cljc index 93fabc973..2ffbc70d2 100644 --- a/common/src/app/common/types/component.cljc +++ b/common/src/app/common/types/component.cljc @@ -18,19 +18,19 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (def schema:component - [:merge - [:map - [:id ::sm/uuid] - [:name :string] - [:path {:optional true} [:maybe :string]] - [:modified-at {:optional true} ::sm/inst] - [:objects {:gen/max 10 :optional true} ::ctp/objects] - [:main-instance-id ::sm/uuid] - [:main-instance-page ::sm/uuid] - [:plugin-data {:optional true} ::ctpg/plugin-data]] - ::ctv/variant-component]) - -(sm/register! ::component schema:component) + (sm/register! + ^{::sm/type ::component} + [:merge + [:map + [:id ::sm/uuid] + [:name :string] + [:path {:optional true} [:maybe :string]] + [:modified-at {:optional true} ::sm/inst] + [:objects {:gen/max 10 :optional true} ctp/schema:objects] + [:main-instance-id ::sm/uuid] + [:main-instance-page ::sm/uuid] + [:plugin-data {:optional true} ctpg/schema:plugin-data]] + ctv/schema:variant-component])) (def check-component (sm/check-fn schema:component)) diff --git a/common/src/app/common/types/container.cljc b/common/src/app/common/types/container.cljc index bfb6d09e2..f087088a6 100644 --- a/common/src/app/common/types/container.cljc +++ b/common/src/app/common/types/container.cljc @@ -41,7 +41,7 @@ [:map-of {:gen/max 10} ::sm/uuid :map]] [:plugin-data {:optional true} ::ctpg/plugin-data]]) -(def check-container! +(def check-container (sm/check-fn ::container)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -62,9 +62,9 @@ (defn get-container [file type id] - (dm/assert! (map? file)) - (dm/assert! (contains? valid-container-types type)) - (dm/assert! (uuid? id)) + (assert (map? file)) + (assert (contains? valid-container-types type)) + (assert (uuid? id)) (-> (if (= type :page) (ctpl/get-page file id) @@ -74,13 +74,9 @@ (defn get-shape [container shape-id] - (dm/assert! - "expected valid container" - (check-container! container)) - - (dm/assert! - "expected valid uuid for `shape-id`" - (uuid? shape-id)) + (assert (check-container container)) + (assert (uuid? shape-id) + "expected valid uuid for `shape-id`") (-> container (get :objects) diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index 55725bb69..93e41924d 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -83,6 +83,7 @@ because sometimes we want to validate file without the data." [:map {:title "file"} [:id ::sm/uuid] + [:name :string] [:revn {:optional true} :int] [:vern {:optional true} :int] [:created-at {:optional true} ::sm/inst] @@ -101,13 +102,15 @@ (sm/register! ::media schema:media) (sm/register! ::colors schema:colors) (sm/register! ::typographies schema:typographies) - (sm/register! ::media-object schema:media) -(def check-file-data! - (sm/check-fn ::data)) +(def check-file + (sm/check-fn schema:file :hint "check error on validating file")) -(def check-media-object! +(def check-file-data + (sm/check-fn schema:data)) + +(def check-media-object (sm/check-fn schema:media)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/common/src/app/common/types/page.cljc b/common/src/app/common/types/page.cljc index cb0d0e9e0..85bb02bb0 100644 --- a/common/src/app/common/types/page.cljc +++ b/common/src/app/common/types/page.cljc @@ -70,7 +70,7 @@ (def valid-guide? (sm/lazy-validator schema:guide)) -(def check-page! +(def check-page (sm/check-fn schema:page)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -82,8 +82,7 @@ (def root uuid/zero) (def empty-page-data - {:options {} - :objects {root + {:objects {root (cts/setup-shape {:id root :type :frame :parent-id root @@ -91,10 +90,12 @@ :name "Root Frame"})}}) (defn make-empty-page - [{:keys [id name]}] + [{:keys [id name background]}] (-> empty-page-data (assoc :id (or id (uuid/next))) - (assoc :name (or name "Page 1")))) + (assoc :name (d/nilv name "Page 1")) + (cond-> background + (assoc :background background)))) (defn get-frame-flow [flows frame-id] diff --git a/common/src/app/common/types/plugins.cljc b/common/src/app/common/types/plugins.cljc index 128c90f7d..c4402591a 100644 --- a/common/src/app/common/types/plugins.cljc +++ b/common/src/app/common/types/plugins.cljc @@ -22,14 +22,13 @@ :keyword]) (def schema:plugin-data - [:map-of {:gen/max 5} - schema:keyword + (sm/register! + ^{::sm/type ::plugin-data} [:map-of {:gen/max 5} - schema:string - schema:string]]) - -(sm/register! ::plugin-data schema:plugin-data) - + schema:keyword + [:map-of {:gen/max 5} + schema:string + schema:string]])) (def ^:private schema:registry-entry [:map diff --git a/common/src/app/common/types/shape.cljc b/common/src/app/common/types/shape.cljc index c367d22ef..63caf358f 100644 --- a/common/src/app/common/types/shape.cljc +++ b/common/src/app/common/types/shape.cljc @@ -120,35 +120,35 @@ [:vector {:gen/max 4 :gen/min 4} ::gpt/point]) (def schema:fill - [:map {:title "Fill"} - [:fill-color {:optional true} ::ctc/rgb-color] - [:fill-opacity {:optional true} ::sm/safe-number] - [:fill-color-gradient {:optional true} [:maybe ::ctc/gradient]] - [:fill-color-ref-file {:optional true} [:maybe ::sm/uuid]] - [:fill-color-ref-id {:optional true} [:maybe ::sm/uuid]] - [:fill-image {:optional true} ::ctc/image-color]]) + (sm/register! + ^{::sm/type ::fill} + [:map {:title "Fill"} + [:fill-color {:optional true} ::ctc/rgb-color] + [:fill-opacity {:optional true} ::sm/safe-number] + [:fill-color-gradient {:optional true} [:maybe ::ctc/gradient]] + [:fill-color-ref-file {:optional true} [:maybe ::sm/uuid]] + [:fill-color-ref-id {:optional true} [:maybe ::sm/uuid]] + [:fill-image {:optional true} ::ctc/image-color]])) -(sm/register! ::fill schema:fill) - -(def ^:private schema:stroke - [:map {:title "Stroke"} - [:stroke-color {:optional true} :string] - [:stroke-color-ref-file {:optional true} ::sm/uuid] - [:stroke-color-ref-id {:optional true} ::sm/uuid] - [:stroke-opacity {:optional true} ::sm/safe-number] - [:stroke-style {:optional true} - [::sm/one-of #{:solid :dotted :dashed :mixed :none :svg}]] - [:stroke-width {:optional true} ::sm/safe-number] - [:stroke-alignment {:optional true} - [::sm/one-of #{:center :inner :outer}]] - [:stroke-cap-start {:optional true} - [::sm/one-of stroke-caps]] - [:stroke-cap-end {:optional true} - [::sm/one-of stroke-caps]] - [:stroke-color-gradient {:optional true} ::ctc/gradient] - [:stroke-image {:optional true} ::ctc/image-color]]) - -(sm/register! ::stroke schema:stroke) +(def schema:stroke + (sm/register! + ^{::sm/type ::stroke} + [:map {:title "Stroke"} + [:stroke-color {:optional true} :string] + [:stroke-color-ref-file {:optional true} ::sm/uuid] + [:stroke-color-ref-id {:optional true} ::sm/uuid] + [:stroke-opacity {:optional true} ::sm/safe-number] + [:stroke-style {:optional true} + [::sm/one-of #{:solid :dotted :dashed :mixed :none :svg}]] + [:stroke-width {:optional true} ::sm/safe-number] + [:stroke-alignment {:optional true} + [::sm/one-of #{:center :inner :outer}]] + [:stroke-cap-start {:optional true} + [::sm/one-of stroke-caps]] + [:stroke-cap-end {:optional true} + [::sm/one-of stroke-caps]] + [:stroke-color-gradient {:optional true} ::ctc/gradient] + [:stroke-image {:optional true} ::ctc/image-color]])) (def check-stroke (sm/check-fn schema:stroke)) @@ -172,8 +172,7 @@ [:width ::sm/safe-number] [:height ::sm/safe-number]]) -;; FIXME: rename to shape-generic-attrs -(def schema:shape-attrs +(def schema:shape-generic-attrs [:map {:title "ShapeAttrs"} [:page-id {:optional true} ::sm/uuid] [:component-id {:optional true} ::sm/uuid] @@ -277,7 +276,7 @@ [] (->> (sg/generator schema:shape-base-attrs) (sg/mcat (fn [{:keys [type] :as shape}] - (sg/let [attrs1 (sg/generator schema:shape-attrs) + (sg/let [attrs1 (sg/generator schema:shape-generic-attrs) attrs2 (sg/generator schema:shape-geom-attrs) attrs3 (case type :text (sg/generator schema:text-attrs) @@ -295,94 +294,100 @@ (merge attrs1 shape attrs2 attrs3))))) (sg/fmap create-shape))) +(def schema:shape-attrs + [:multi {:dispatch :type + :decode/json (fn [shape] + (update shape :type keyword)) + :title "Shape"} + [:group + [:merge {:title "GroupShape"} + ctsl/schema:layout-attrs + schema:group-attrs + schema:shape-generic-attrs + schema:shape-geom-attrs + schema:shape-base-attrs]] + + [:frame + [:merge {:title "FrameShape"} + ctsl/schema:layout-attrs + ::ctsl/layout-attrs + schema:frame-attrs + schema:shape-generic-attrs + schema:shape-geom-attrs + schema:shape-base-attrs + ::ctv/variant-shape + ::ctv/variant-container]] + + [:bool + [:merge {:title "BoolShape"} + ctsl/schema:layout-attrs + schema:bool-attrs + schema:shape-generic-attrs + schema:shape-base-attrs]] + + [:rect + [:merge {:title "RectShape"} + ctsl/schema:layout-attrs + schema:rect-attrs + schema:shape-generic-attrs + schema:shape-geom-attrs + schema:shape-base-attrs]] + + [:circle + [:merge {:title "CircleShape"} + ctsl/schema:layout-attrs + schema:circle-attrs + schema:shape-generic-attrs + schema:shape-geom-attrs + schema:shape-base-attrs]] + + [:image + [:merge {:title "ImageShape"} + ctsl/schema:layout-attrs + schema:image-attrs + schema:shape-generic-attrs + schema:shape-geom-attrs + schema:shape-base-attrs]] + + [:svg-raw + [:merge {:title "SvgRawShape"} + ctsl/schema:layout-attrs + schema:svg-raw-attrs + schema:shape-generic-attrs + schema:shape-geom-attrs + schema:shape-base-attrs]] + + [:path + [:merge {:title "PathShape"} + ctsl/schema:layout-attrs + schema:path-attrs + schema:shape-generic-attrs + schema:shape-base-attrs]] + + [:text + [:merge {:title "TextShape"} + ctsl/schema:layout-attrs + schema:text-attrs + schema:shape-generic-attrs + schema:shape-geom-attrs + schema:shape-base-attrs]]]) + (def schema:shape - [:and {:title "Shape" - :gen/gen (shape-generator) - :decode/json {:leave decode-shape}} - [:fn shape?] - [:multi {:dispatch :type - :decode/json (fn [shape] - (update shape :type keyword)) - :title "Shape"} - [:group - [:merge {:title "GroupShape"} - ::ctsl/layout-child-attrs - schema:group-attrs - schema:shape-attrs - schema:shape-geom-attrs - schema:shape-base-attrs]] + (sm/register! + ^{::sm/type ::shape} + [:and {:title "Shape" + :gen/gen (shape-generator) + :decode/json {:leave decode-shape}} + [:fn shape?] + schema:shape-attrs])) - [:frame - [:merge {:title "FrameShape"} - ::ctsl/layout-child-attrs - ::ctsl/layout-attrs - schema:frame-attrs - schema:shape-attrs - schema:shape-geom-attrs - schema:shape-base-attrs - ::ctv/variant-shape - ::ctv/variant-container]] +(def check-shape-generic-attrs + (sm/check-fn schema:shape-generic-attrs)) - [:bool - [:merge {:title "BoolShape"} - ::ctsl/layout-child-attrs - schema:bool-attrs - schema:shape-attrs - schema:shape-base-attrs]] - - [:rect - [:merge {:title "RectShape"} - ::ctsl/layout-child-attrs - schema:rect-attrs - schema:shape-attrs - schema:shape-geom-attrs - schema:shape-base-attrs]] - - [:circle - [:merge {:title "CircleShape"} - ::ctsl/layout-child-attrs - schema:circle-attrs - schema:shape-attrs - schema:shape-geom-attrs - schema:shape-base-attrs]] - - [:image - [:merge {:title "ImageShape"} - ::ctsl/layout-child-attrs - schema:image-attrs - schema:shape-attrs - schema:shape-geom-attrs - schema:shape-base-attrs]] - - [:svg-raw - [:merge {:title "SvgRawShape"} - ::ctsl/layout-child-attrs - schema:svg-raw-attrs - schema:shape-attrs - schema:shape-geom-attrs - schema:shape-base-attrs]] - - [:path - [:merge {:title "PathShape"} - ::ctsl/layout-child-attrs - schema:path-attrs - schema:shape-attrs - schema:shape-base-attrs]] - - [:text - [:merge {:title "TextShape"} - ::ctsl/layout-child-attrs - schema:text-attrs - schema:shape-attrs - schema:shape-geom-attrs - schema:shape-base-attrs]]]]) - -(sm/register! ::shape schema:shape) - -(def check-shape-attrs! +(def check-shape-attrs (sm/check-fn schema:shape-attrs)) -(def check-shape! +(def check-shape (sm/check-fn schema:shape :hint "expected valid shape")) diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index 6803b2a60..e66016502 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -168,25 +168,24 @@ (def item-align-self-types #{:start :end :center :stretch}) -(sm/register! - ^{::sm/type ::layout-child-attrs} - [:map {:title "LayoutChildAttrs"} - [:layout-item-margin-type {:optional true} [::sm/one-of item-margin-types]] - [:layout-item-margin {:optional true} - [:map - [:m1 {:optional true} ::sm/safe-number] - [:m2 {:optional true} ::sm/safe-number] - [:m3 {:optional true} ::sm/safe-number] - [:m4 {:optional true} ::sm/safe-number]]] - [:layout-item-max-h {:optional true} ::sm/safe-number] - [:layout-item-min-h {:optional true} ::sm/safe-number] - [:layout-item-max-w {:optional true} ::sm/safe-number] - [:layout-item-min-w {:optional true} ::sm/safe-number] - [:layout-item-h-sizing {:optional true} [::sm/one-of item-h-sizing-types]] - [:layout-item-v-sizing {:optional true} [::sm/one-of item-v-sizing-types]] - [:layout-item-align-self {:optional true} [::sm/one-of item-align-self-types]] - [:layout-item-absolute {:optional true} :boolean] - [:layout-item-z-index {:optional true} ::sm/safe-number]]) +(def schema:layout-attrs + [:map {:title "LayoutChildAttrs"} + [:layout-item-margin-type {:optional true} [::sm/one-of item-margin-types]] + [:layout-item-margin {:optional true} + [:map + [:m1 {:optional true} ::sm/safe-number] + [:m2 {:optional true} ::sm/safe-number] + [:m3 {:optional true} ::sm/safe-number] + [:m4 {:optional true} ::sm/safe-number]]] + [:layout-item-max-h {:optional true} ::sm/safe-number] + [:layout-item-min-h {:optional true} ::sm/safe-number] + [:layout-item-max-w {:optional true} ::sm/safe-number] + [:layout-item-min-w {:optional true} ::sm/safe-number] + [:layout-item-h-sizing {:optional true} [::sm/one-of item-h-sizing-types]] + [:layout-item-v-sizing {:optional true} [::sm/one-of item-v-sizing-types]] + [:layout-item-align-self {:optional true} [::sm/one-of item-align-self-types]] + [:layout-item-absolute {:optional true} :boolean] + [:layout-item-z-index {:optional true} ::sm/safe-number]]) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; SCHEMAS diff --git a/common/src/app/common/types/shape_tree.cljc b/common/src/app/common/types/shape_tree.cljc index c7a301ca4..492699ea3 100644 --- a/common/src/app/common/types/shape_tree.cljc +++ b/common/src/app/common/types/shape_tree.cljc @@ -16,6 +16,8 @@ [app.common.types.shape.layout :as ctl] [app.common.uuid :as uuid])) + +;; FIXME: the order of arguments seems arbitrary, container should be a first artgument (defn add-shape "Insert a shape in the tree, at the given index below the given parent or frame. Update the parent as needed." diff --git a/common/src/app/common/types/typography.cljc b/common/src/app/common/types/typography.cljc index 74f66b4e3..155c958b1 100644 --- a/common/src/app/common/types/typography.cljc +++ b/common/src/app/common/types/typography.cljc @@ -17,25 +17,25 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (def schema:typography - [:map {:title "Typography"} - [:id ::sm/uuid] - [:name :string] - [:font-id :string] - [:font-family :string] - [:font-variant-id :string] - [:font-size :string] - [:font-weight :string] - [:font-style :string] - [:line-height :string] - [:letter-spacing :string] - [:text-transform :string] - [:modified-at {:optional true} ::sm/inst] - [:path {:optional true} [:maybe :string]] - [:plugin-data {:optional true} ::ctpg/plugin-data]]) + (sm/register! + ^{::sm/type ::typography} + [:map {:title "Typography"} + [:id ::sm/uuid] + [:name :string] + [:font-id :string] + [:font-family :string] + [:font-variant-id :string] + [:font-size :string] + [:font-weight :string] + [:font-style :string] + [:line-height :string] + [:letter-spacing :string] + [:text-transform :string] + [:modified-at {:optional true} ::sm/inst] + [:path {:optional true} [:maybe :string]] + [:plugin-data {:optional true} ::ctpg/plugin-data]])) -(sm/register! ::typography schema:typography) - -(def check-typography! +(def check-typography (sm/check-fn ::typography)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/common/src/app/common/types/variant.cljc b/common/src/app/common/types/variant.cljc index 3fe90b92e..552449eda 100644 --- a/common/src/app/common/types/variant.cljc +++ b/common/src/app/common/types/variant.cljc @@ -23,9 +23,11 @@ (def schema:variant-component ;; A component that is part of a variant set. - [:map - [:variant-id {:optional true} ::sm/uuid] - [:variant-properties {:optional true} [:vector schema:variant-property]]]) + (sm/register! + ^{::sm/type ::variant-component} + [:map + [:variant-id {:optional true} ::sm/uuid] + [:variant-properties {:optional true} [:vector schema:variant-property]]])) (def schema:variant-shape ;; The root shape of the main instance of a variant component. @@ -40,7 +42,6 @@ [:is-variant-container {:optional true} :boolean]]) (sm/register! ::variant-property schema:variant-property) -(sm/register! ::variant-component schema:variant-component) (sm/register! ::variant-shape schema:variant-shape) (sm/register! ::variant-container schema:variant-container) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index d380e7bbe..9221356ec 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -771,17 +771,16 @@ ;; --- Update Shape Attrs +;; FIXME: revisit this before merge (defn update-shape [id attrs] - (dm/assert! - "expected valid parameters" - (and (cts/check-shape-attrs! attrs) - (uuid? id))) + (assert (uuid? id) "expected valid uuid for `id`") - (ptk/reify ::update-shape - ptk/WatchEvent - (watch [_ _ _] - (rx/of (dwsh/update-shapes [id] #(merge % attrs)))))) + (let [attrs (cts/check-shape-attrs attrs)] + (ptk/reify ::update-shape + ptk/WatchEvent + (watch [_ _ _] + (rx/of (dwsh/update-shapes [id] #(merge % attrs))))))) (defn start-rename-shape "Start shape renaming process" @@ -832,10 +831,6 @@ (defn update-selected-shapes [attrs] - (dm/assert! - "expected valid shape attrs" - (cts/check-shape-attrs! attrs)) - (ptk/reify ::update-selected-shapes ptk/WatchEvent (watch [_ state _] diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 777c63b11..09c815da0 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -254,20 +254,17 @@ (defn add-media [media] - (dm/assert! - "expected valid media object" - (ctf/check-media-object! media)) + (let [media (ctf/check-media-object media)] + (ptk/reify ::add-media + ev/Event + (-data [_] media) - (ptk/reify ::add-media - ev/Event - (-data [_] media) - - ptk/WatchEvent - (watch [it _ _] - (let [obj (select-keys media [:id :name :width :height :mtype]) - changes (-> (pcb/empty-changes it) - (pcb/add-media obj))] - (rx/of (dch/commit-changes changes)))))) + ptk/WatchEvent + (watch [it _ _] + (let [obj (select-keys media [:id :name :width :height :mtype]) + changes (-> (pcb/empty-changes it) + (pcb/add-media obj))] + (rx/of (dch/commit-changes changes))))))) (defn rename-media [id new-name] @@ -297,10 +294,7 @@ (defn delete-media [{:keys [id]}] - (dm/assert! - "expected valid uuid for `id`" - (uuid? id)) - + (assert (uuid? id) "expected valid uuid for `id`") (ptk/reify ::delete-media ev/Event (-data [_] {:id id}) @@ -316,11 +310,8 @@ (defn add-typography ([typography] (add-typography typography true)) ([typography edit?] - (let [typography (update typography :id #(or % (uuid/next)))] - (dm/assert! - "expected valid typography" - (ctt/check-typography! typography)) - + (let [typography (-> (update typography :id #(or % (uuid/next))) + (ctt/check-typography))] (ptk/reify ::add-typography ev/Event (-data [_] typography) @@ -349,16 +340,12 @@ (defn update-typography [typography file-id] - - (dm/assert! - "expected valid typography and file-id" - (and (ctt/check-typography! typography) - (uuid? file-id))) - - (ptk/reify ::update-typography - ptk/WatchEvent - (watch [it state _] - (do-update-tipography it state typography file-id)))) + (assert (uuid? file-id) "expected valid uuid for `file-id`") + (let [typography (ctt/check-typography typography)] + (ptk/reify ::update-typography + ptk/WatchEvent + (watch [it state _] + (do-update-tipography it state typography file-id))))) (defn rename-typography [file-id id new-name] diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs index d70a0b30b..27cb972a6 100644 --- a/frontend/src/app/main/data/workspace/shapes.cljs +++ b/frontend/src/app/main/data/workspace/shapes.cljs @@ -110,9 +110,7 @@ (add-shape shape {})) ([shape {:keys [no-select? no-update-layout?]}] - (dm/assert! - "expected valid shape" - (cts/check-shape! shape)) + (cts/check-shape shape) (ptk/reify ::add-shape ptk/WatchEvent @@ -293,30 +291,28 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defn update-shape-flags - [ids {:keys [blocked hidden undo-group] :as flags}] - (dm/assert! - "expected valid coll of uuids" - (every? uuid? ids)) + [ids flags] + (assert (every? uuid? ids) + "expected valid coll of uuids") - (dm/assert! - "expected valid shape-attrs value for `flags`" - (cts/check-shape-attrs! flags)) + (let [{:keys [blocked hidden undo-group]} + (cts/check-shape-generic-attrs flags)] - (ptk/reify ::update-shape-flags - ptk/WatchEvent - (watch [_ state _] - (let [update-fn - (fn [obj] - (cond-> obj - (boolean? blocked) (assoc :blocked blocked) - (boolean? hidden) (assoc :hidden hidden))) - objects (dsh/lookup-page-objects state) - ;; We have change only the hidden behaviour, to hide only the - ;; selected shape, block behaviour remains the same. - ids (if (boolean? blocked) - (into ids (->> ids (mapcat #(cfh/get-children-ids objects %)))) - ids)] - (rx/of (update-shapes ids update-fn {:attrs #{:blocked :hidden} :undo-group undo-group})))))) + (ptk/reify ::update-shape-flags + ptk/WatchEvent + (watch [_ state _] + (let [update-fn + (fn [obj] + (cond-> obj + (boolean? blocked) (assoc :blocked blocked) + (boolean? hidden) (assoc :hidden hidden))) + objects (dsh/lookup-page-objects state) + ;; We have change only the hidden behaviour, to hide only the + ;; selected shape, block behaviour remains the same. + ids (if (boolean? blocked) + (into ids (->> ids (mapcat #(cfh/get-children-ids objects %)))) + ids)] + (rx/of (update-shapes ids update-fn {:attrs #{:blocked :hidden} :undo-group undo-group}))))))) (defn toggle-visibility-selected [] diff --git a/frontend/src/app/main/data/workspace/tokens/library_edit.cljs b/frontend/src/app/main/data/workspace/tokens/library_edit.cljs index cc3a345cf..67c410431 100644 --- a/frontend/src/app/main/data/workspace/tokens/library_edit.cljs +++ b/frontend/src/app/main/data/workspace/tokens/library_edit.cljs @@ -51,15 +51,13 @@ ;; TODO HYMA: Copied over from workspace.cljs (defn update-shape [id attrs] - (dm/assert! - "expected valid parameters" - (and (cts/check-shape-attrs! attrs) - (uuid? id))) + (assert (uuid? id) "expected valid uuid for `id`") - (ptk/reify ::update-shape - ptk/WatchEvent - (watch [_ _ _] - (rx/of (dwsh/update-shapes [id] #(merge % attrs)))))) + (let [attrs (cts/check-shape-attrs attrs)] + (ptk/reify ::update-shape + ptk/WatchEvent + (watch [_ _ _] + (rx/of (dwsh/update-shapes [id] #(merge % attrs))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; TOKENS Actions diff --git a/frontend/src/app/main/fonts.cljs b/frontend/src/app/main/fonts.cljs index 4ee3c80b3..13c7f0cc1 100644 --- a/frontend/src/app/main/fonts.cljs +++ b/frontend/src/app/main/fonts.cljs @@ -214,15 +214,15 @@ (nil? font) (p/resolved font-id) - ;; Font already loaded, we just continue + ;; Font already loaded, we just continue (contains? @loaded font-id) (p/resolved font-id) - ;; Font is currently downloading. We attach the caller to the promise + ;; Font is currently downloading. We attach the caller to the promise (contains? @loading font-id) (get @loading font-id) - ;; First caller, we create the promise and then wait + ;; First caller, we create the promise and then wait :else (let [on-load (fn [resolve] (swap! loaded conj font-id) From 8bdec6692770ba57dfb4394a31fad4963cc3976a Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 7 May 2025 11:38:52 +0200 Subject: [PATCH 6/7] :sparkles: Remove the ILazySchema internal abstraction from schema ns --- common/src/app/common/schema.cljc | 156 +++++++++--------------------- 1 file changed, 47 insertions(+), 109 deletions(-) diff --git a/common/src/app/common/schema.cljc b/common/src/app/common/schema.cljc index 557dc8b3d..b9f662983 100644 --- a/common/src/app/common/schema.cljc +++ b/common/src/app/common/schema.cljc @@ -28,10 +28,6 @@ [malli.transform :as mt] [malli.util :as mu])) -(defprotocol ILazySchema - (-validate [_ o]) - (-explain [_ o])) - (def default-options {:registry sr/default-registry}) @@ -51,10 +47,6 @@ [s] (m/type-properties s)) -(defn- lazy-schema? - [s] - (satisfies? ILazySchema s)) - (defn schema [s] (if (schema? s) @@ -111,12 +103,16 @@ (malli.error/error-value exp {:malli.error/mask-valid-values '...})) (defn optional-keys - [schema] - (mu/optional-keys schema default-options)) + ([schema] + (mu/optional-keys schema nil default-options)) + ([schema keys] + (mu/optional-keys schema keys default-options))) (defn required-keys - [schema] - (mu/required-keys schema default-options)) + ([schema] + (mu/required-keys schema nil default-options)) + ([schema keys] + (mu/required-keys schema keys default-options))) (defn transformer [& transformers] @@ -229,6 +225,11 @@ (let [vfn (delay (decoder (if (delay? s) (deref s) s) transformer))] (fn [v] (@vfn v)))) +(defn decode-fn + [s transformer] + (let [vfn (delay (decoder (if (delay? s) (deref s) s) transformer))] + (fn [v] (@vfn v)))) + (defn humanize-explain "Returns a string representation of the explain data structure" [{:keys [errors value]} & {:keys [length level]}] @@ -274,38 +275,36 @@ ([s] (lookup sr/default-registry s)) ([registry s] (schema (mr/schema registry s)))) -(defn- fast-check - "A fast path for checking process, assumes the ILazySchema protocol - implemented on the provided `s` schema. Sould not be used directly." - [s type code hint value] - (when-not ^boolean (-validate s value) - (let [explain (-explain s value)] - (throw (ex-info hint {:type type - :code code - :hint hint - ::explain explain})))) - value) - -(declare ^:private lazy-schema) - (defn check-fn "Create a predefined check function" [s & {:keys [hint type code]}] - (let [schema (if (lazy-schema? s) s (lazy-schema s)) - hint (or ^boolean hint "check error") - type (or ^boolean type :assertion) - code (or ^boolean code :data-validation)] - (partial fast-check schema type code hint))) + (let [s (schema s) + validator* (delay (m/validator s)) + explainer* (delay (m/explainer s)) + hint (or ^boolean hint "check error") + type (or ^boolean type :assertion) + code (or ^boolean code :data-validation)] + + (fn [value] + (let [validate-fn @validator*] + (when-not ^boolean (validate-fn value) + (let [explain-fn @explainer* + explain (explain-fn value)] + (throw (ex-info hint {:type type + :code code + :hint hint + ::explain explain})))) + value)))) (defn check "A helper intended to be used on assertions for validate/check the - schema over provided data. Raises an assertion exception." - [s value & {:keys [hint type code]}] - (let [s (if (lazy-schema? s) s (lazy-schema s)) - hint (or ^boolean hint "check error") - type (or ^boolean type :assertion) - code (or ^boolean code :data-validation)] - (fast-check s type code hint value))) + schema over provided data. Raises an assertion exception. + + Use only on non-performance sensitive code, because it creates the + check-fn instance all the time it is invoked." + [s value & {:as opts}] + (let [check-fn (check-fn s opts)] + (check-fn value))) (defn type-schema [& {:as params}] @@ -319,11 +318,14 @@ ([params] (cond (map? params) - (let [type (get params :type)] + (let [mdata (meta params) + type (or (get mdata ::id) + (get mdata ::type) + (get params :type))] (assert (qualified-keyword? type) "expected qualified keyword for `type`") (let [s (m/-simple-schema params)] (swap! sr/registry assoc type s) - nil)) + s)) (vector? params) (let [mdata (meta params) @@ -331,83 +333,19 @@ (get mdata ::type))] (assert (qualified-keyword? type) "expected qualified keyword to be on metadata") (swap! sr/registry assoc type params) - nil) + params) (m/into-schema? params) (let [type (m/-type params)] - (swap! sr/registry assoc type params)) + (swap! sr/registry assoc type params) + params) :else (throw (ex-info "Invalid Arguments" {})))) ([type params] - (let [s (if (map? params) - (cond - (= :set (:type params)) - (m/-collection-schema params) - - (= :vector (:type params)) - (m/-collection-schema params) - - :else - (m/-simple-schema params)) - params)] - - (swap! sr/registry assoc type s) - nil))) - -(defn- lazy-schema - "Create ans instance of ILazySchema" - [s] - (let [schema (schema s) - validator (delay (m/validator schema)) - explainer (delay (m/explainer schema))] - - (reify - m/AST - (-to-ast [_ options] (m/-to-ast schema options)) - - m/EntrySchema - (-entries [_] (m/-entries schema)) - (-entry-parser [_] (m/-entry-parser schema)) - - m/Cached - (-cache [_] (m/-cache schema)) - - m/LensSchema - (-keep [_] (m/-keep schema)) - (-get [_ key default] (m/-get schema key default)) - (-set [_ key value] (m/-set schema key value)) - - m/Schema - (-validator [_] - (m/-validator schema)) - (-explainer [_ path] - (m/-explainer schema path)) - (-parser [_] - (m/-parser schema)) - (-unparser [_] - (m/-unparser schema)) - (-transformer [_ transformer method options] - (m/-transformer schema transformer method options)) - (-walk [_ walker path options] - (m/-walk schema walker path options)) - (-properties [_] - (m/-properties schema)) - (-options [_] - (m/-options schema)) - (-children [_] - (m/-children schema)) - (-parent [_] - (m/-parent schema)) - (-form [_] - (m/-form schema)) - - ILazySchema - (-validate [_ o] - (@validator o)) - (-explain [_ o] - (@explainer o))))) + (swap! sr/registry assoc type params) + params)) ;; --- BUILTIN SCHEMAS From 0b7b6e2c2391b219dbdb2968d789cb6dd4b173fa Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 8 May 2025 09:51:25 +0200 Subject: [PATCH 7/7] :recycle: Refactor penpot library --- CHANGES.md | 30 + backend/src/app/rpc/commands/files_create.clj | 4 +- common/src/app/common/files/builder.cljc | 904 +++++++----------- common/src/app/common/files/changes.cljc | 10 +- .../src/app/common/files/changes_builder.cljc | 6 +- .../app/common/geom/shapes/transforms.cljc | 6 +- common/src/app/common/test_helpers/files.cljc | 38 +- common/src/app/common/types/file.cljc | 39 +- frontend/shadow-cljs.edn | 13 +- frontend/src/app/libs/file_builder.cljs | 281 ------ frontend/src/app/libs/render.cljs | 28 - frontend/src/app/main/data/workspace.cljs | 6 +- .../src/app/main/data/workspace/bool.cljs | 19 +- frontend/src/app/util/object.cljc | 2 +- frontend/src/lib/file_builder.cljs | 250 +++++ frontend/src/lib/playground/sample1.js | 30 + .../plugins/context_shapes_test.cljs | 3 +- .../frontend_tests/util_snap_data_test.cljs | 182 ++-- 18 files changed, 817 insertions(+), 1034 deletions(-) delete mode 100644 frontend/src/app/libs/file_builder.cljs delete mode 100644 frontend/src/app/libs/render.cljs create mode 100644 frontend/src/lib/file_builder.cljs create mode 100644 frontend/src/lib/playground/sample1.js diff --git a/CHANGES.md b/CHANGES.md index ae4ad9f1b..2bacc91b4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,36 @@ ### :boom: Breaking changes & Deprecations +**Breaking changes on penpot library:** + +- Change the signature of the `addPage` method: it now accepts an object (as a single argument) where you can pass `id`, + `name`, and `background` props (instead of the previous positional arguments) +- Rename the `file.createRect` method to `file.addRect` +- Rename the `file.createCircle` method to `file.addCircle` +- Rename the `file.createPath` method to `file.addPath` +- Rename the `file.createText` method to `file.addText` +- Rename `file.startComponent` to `file.addComponent` (to preserve the naming style) +- Rename `file.createComponentInstance` to `file.addComponentInstance` (to preserve the naming style) +- Rename `file.lookupShape` to `file.getShape` +- Rename `file.asMap` to `file.toMap` +- Remove `file.updateLibraryColor` (use `file.addLibraryColor` if you just need to replace a color) +- Remove `file.deleteLibraryColor` (this library is intended to build files) +- Remove `file.updateLibraryTypography` (use `file.addLibraryTypography` if you just need to replace a typography) +- Remove `file.deleteLibraryTypography` (this library is intended to build files) +- Remove `file.add/update/deleteLibraryMedia` (they are no longer supported by Penpot and have been replaced by components) +- Remove `file.deleteObject` (this library is intended to build files) +- Remove `file.updateObject` (this library is intended to build files) +- Remove `file.finishComponent` (it is no longer necessary; see below for more details on component creation changes) +- Change the `file.getCurrentPageId` function to a read-only `file.currentPageId` property +- Add `file.currentFrameId` read-only property +- Add `file.lastId` read-only property + +There are also relevant semantic changes in how components should be created: this refactor removes +all notions of the old components (v1). Since v2, the shapes that are part of a component live on a +page. So, from now on, to create a component, you should first create a frame, then add shapes +and/or groups to that frame, and then create a component by declaring that frame as the component +root. + ### :heart: Community contributions (Thank you!) ### :sparkles: New features diff --git a/backend/src/app/rpc/commands/files_create.clj b/backend/src/app/rpc/commands/files_create.clj index eeafddd0c..fc9dcb7fe 100644 --- a/backend/src/app/rpc/commands/files_create.clj +++ b/backend/src/app/rpc/commands/files_create.clj @@ -55,8 +55,8 @@ :features features :ignore-sync-until ignore-sync-until :modified-at modified-at - :deleted-at deleted-at - :create-page create-page + :deleted-at deleted-at} + {:create-page create-page :page-id page-id}) file (-> (bfc/insert-file! cfg file) (bfc/decode-row))] diff --git a/common/src/app/common/files/builder.cljc b/common/src/app/common/files/builder.cljc index b23326710..c62b098dd 100644 --- a/common/src/app/common/files/builder.cljc +++ b/common/src/app/common/files/builder.cljc @@ -5,86 +5,86 @@ ;; Copyright (c) KALEIDOS INC (ns app.common.files.builder + "Internal implementation of file builder. Mainly used as base impl + for penpot library" (:require [app.common.data :as d] [app.common.data.macros :as dm] - [app.common.exceptions :as ex] + [app.common.features :as cfeat] [app.common.files.changes :as ch] + [app.common.files.migrations :as fmig] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] - [app.common.pprint :as pp] [app.common.schema :as sm] [app.common.svg :as csvg] - [app.common.text :as txt] - [app.common.types.components-list :as ctkl] - [app.common.types.container :as ctn] - [app.common.types.file :as ctf] - [app.common.types.page :as ctp] - [app.common.types.pages-list :as ctpl] - [app.common.types.shape :as cts] + [app.common.types.color :as types.color] + [app.common.types.component :as types.component] + [app.common.types.components-list :as types.components-list] + [app.common.types.container :as types.container] + [app.common.types.file :as types.file] + [app.common.types.page :as types.page] + [app.common.types.pages-list :as types.pages-list] + [app.common.types.shape :as types.shape] + [app.common.types.typography :as types.typography] [app.common.uuid :as uuid] [cuerdas.core :as str])) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; IMPL & HELPERS +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (def ^:private root-id uuid/zero) (def ^:private conjv (fnil conj [])) (def ^:private conjs (fnil conj #{})) +(defn default-uuid + [v] + (or v (uuid/next))) + +(defn- track-used-name + [file name] + (let [container-id (::current-page-id file)] + (update-in file [::unames container-id] conjs name))) + (defn- commit-change - ([file change] - (commit-change file change nil)) + [file change & {:keys [add-container] + :or {add-container false}}] - ([file change {:keys [add-container? - fail-on-spec?] - :or {add-container? false - fail-on-spec? false}}] - (let [change (cond-> change - add-container? - (assoc :page-id (:current-page-id file) - :frame-id (:current-frame-id file))) - - valid? (or (and (nil? (:component-id change)) - (nil? (:page-id change))) - (ch/valid-change? change))] - - (when-not valid? - (let [explain (sm/explain ::ch/change change)] - (pp/pprint (sm/humanize-explain explain)) - (when fail-on-spec? - (ex/raise :type :assertion - :code :data-validation - :hint "invalid change" - ::sm/explain explain)))) - - (cond-> file - (and valid? (or (not add-container?) (some? (:component-id change)) (some? (:page-id change)))) - (-> (update :changes conjv change) - (update :data ch/process-changes [change] false)) - - (not valid?) - (update :errors conjv change))))) + (let [change (cond-> change + add-container + (assoc :page-id (::current-page-id file) + :frame-id (::current-frame-id file)))] + (-> file + (update ::changes conjv change) + (update :data ch/process-changes [change] false)))) (defn- lookup-objects - ([file] - (if (some? (:current-component-id file)) - (dm/get-in file [:data :components (:current-component-id file) :objects]) - (dm/get-in file [:data :pages-index (:current-page-id file) :objects])))) + [file] + (dm/get-in file [:data :pages-index (::current-page-id file) :objects])) -(defn lookup-shape [file shape-id] - (-> (lookup-objects file) - (get shape-id))) +(defn- commit-shape + [file shape] + (let [parent-id + (-> file ::parent-stack peek) -(defn- commit-shape [file obj] - (let [parent-id (-> file :parent-stack peek) - change {:type :add-obj - :id (:id obj) - :ignore-touched true - :obj obj - :parent-id parent-id} + frame-id + (::current-frame-id file) - fail-on-spec? (or (= :group (:type obj)) - (= :frame (:type obj)))] + page-id + (::current-page-id file) - (commit-change file change {:add-container? true :fail-on-spec? fail-on-spec?}))) + change + {:type :add-obj + :id (:id shape) + :ignore-touched true + :obj shape + :parent-id parent-id + :frame-id frame-id + :page-id page-id}] + + (-> file + (commit-change change) + (track-used-name (:name shape))))) (defn- generate-name [type data] @@ -96,24 +96,16 @@ :else (str tag)))) (str/capital (d/name type)))) -(defn- add-name - [file name] - (let [container-id (or (:current-component-id file) - (:current-page-id file))] - (-> file - (update-in [:unames container-id] conjs name)))) - (defn- unique-name [name file] - (let [container-id (or (:current-component-id file) - (:current-page-id file)) + (let [container-id (::current-page-id file) unames (dm/get-in file [:unames container-id])] (d/unique-name name (or unames #{})))) -(defn clear-names [file] - (dissoc file :unames)) +(defn- clear-names [file] + (dissoc file ::unames)) -(defn- check-name +(defn- assign-name "Given a tag returns its layer name" [data file type] @@ -124,592 +116,356 @@ :always (update :name unique-name file))) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; SCHEMAS +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(def decode-file + (sm/decode-fn types.file/schema:file sm/json-transformer)) + +(def decode-page + (sm/decode-fn types.page/schema:page sm/json-transformer)) + +(def decode-shape + (sm/decode-fn types.shape/schema:shape-attrs sm/json-transformer)) + +(def decode-library-color + (sm/decode-fn types.color/schema:color sm/json-transformer)) + +(def decode-library-typography + (sm/decode-fn types.typography/schema:typography sm/json-transformer)) + +(def decode-component + (sm/decode-fn types.component/schema:component sm/json-transformer)) + +(def schema:add-component-instance + [:map + [:component-id ::sm/uuid] + [:x ::sm/safe-number] + [:y ::sm/safe-number]]) + +(def check-add-component-instance + (sm/check-fn schema:add-component-instance)) + +(def decode-add-component-instance + (sm/decode-fn schema:add-component-instance sm/json-transformer)) + +(def schema:add-bool + [:map + [:group-id ::sm/uuid] + [:type [::sm/one-of types.shape/bool-types]]]) + +(def decode-add-bool + (sm/decode-fn schema:add-bool sm/json-transformer)) + +(def check-add-bool + (sm/check-fn schema:add-bool)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; PUBLIC API +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn lookup-shape [file shape-id] + (-> (lookup-objects file) + (get shape-id))) + +(defn get-current-page + [file] + (let [page-id (::current-page-id file)] + (dm/get-in file [:data :pages-index page-id]))) (defn create-file - ([name] - (create-file (uuid/next) name)) - - ([id name] - (-> (ctf/make-file {:id id :name name :create-page false}) - (assoc :changes [])))) ;; We keep the changes so we can send them to the backend + [params] + (let [params (-> params + (assoc :features cfeat/default-features) + (assoc :migrations fmig/available-migrations))] + (types.file/make-file params :create-page false))) (defn add-page - [file data] - (dm/assert! (nil? (:current-component-id file))) - (let [page-id (or (:id data) (uuid/next)) - page (-> (ctp/make-empty-page {:id page-id :name "Page 1"}) - (d/deep-merge data))] + [file params] + (let [page (-> (types.page/make-empty-page params) + (types.page/check-page)) + change {:type :add-page + :page page}] + (-> file - (commit-change - {:type :add-page - :page page}) + (commit-change change) ;; Current page being edited - (assoc :current-page-id page-id) + (assoc ::current-page-id (:id page)) ;; Current frame-id - (assoc :current-frame-id root-id) + (assoc ::current-frame-id root-id) ;; Current parent stack we'll be nesting - (assoc :parent-stack [root-id]) + (assoc ::parent-stack [root-id]) ;; Last object id added - (assoc :last-id nil)))) + (assoc ::last-id nil)))) (defn close-page [file] - (dm/assert! (nil? (:current-component-id file))) (-> file - (dissoc :current-page-id) - (dissoc :parent-stack) - (dissoc :last-id) + (dissoc ::current-page-id) + (dissoc ::parent-stack) + (dissoc ::last-id) (clear-names))) -(defn add-artboard [file data] - (let [obj (-> (cts/setup-shape (assoc data :type :frame)) - (check-name file :frame))] - (-> file - (commit-shape obj) - (assoc :current-frame-id (:id obj)) - (assoc :last-id (:id obj)) - (add-name (:name obj)) - (update :parent-stack conjv (:id obj))))) +(defn add-artboard + [file data] + (let [{:keys [id] :as shape} + (-> data + (update :id default-uuid) + (assoc :type :frame) + (assign-name file :frame) + (types.shape/setup-shape) + (types.shape/check-shape))] -(defn close-artboard [file] - (let [parent-id (-> file :parent-stack peek) - parent (lookup-shape file parent-id) - current-frame-id (or (:frame-id parent) - root-id)] (-> file - (assoc :current-frame-id current-frame-id) - (update :parent-stack pop)))) + (commit-shape shape) + (update ::parent-stack conjv id) + (assoc ::current-frame-id id) + (assoc ::last-id id)))) -(defn add-group [file data] - (let [frame-id (:current-frame-id file) - obj (-> (cts/setup-shape (assoc data :type :group :frame-id frame-id)) - (check-name file :group))] +(defn close-artboard + [file] + (let [parent-id (-> file ::parent-stack peek) + parent (lookup-shape file parent-id)] (-> file - (commit-shape obj) - (assoc :last-id (:id obj)) - (add-name (:name obj)) - (update :parent-stack conjv (:id obj))))) + (assoc ::current-frame-id (or (:frame-id parent) root-id)) + (update ::parent-stack pop)))) -(defn close-group [file] +(defn add-group + [file params] + (let [{:keys [id] :as shape} + (-> params + (update :id default-uuid) + (assoc :type :group) + (assign-name file :group) + (types.shape/setup-shape) + (types.shape/check-shape))] + (-> file + (commit-shape shape) + (assoc ::last-id id) + (update ::parent-stack conjv id)))) + +(defn close-group + [file] (let [group-id (-> file :parent-stack peek) group (lookup-shape file group-id) - children (->> group :shapes (mapv #(lookup-shape file %))) + children (->> (get group :shapes) + (into [] (keep (partial lookup-shape file))) + (not-empty))] - file - (cond - (empty? children) - (commit-change - file - {:type :del-obj - :ignore-touched true - :id group-id} - {:add-container? true}) + (assert (some? children) "group expect to have at least 1 children") - (:masked-group group) - (let [mask (first children)] - (commit-change - file - {:type :mod-obj - :id group-id - :operations - [{:type :set :attr :x :val (-> mask :selrect :x) :ignore-touched true} - {:type :set :attr :y :val (-> mask :selrect :y) :ignore-touched true} - {:type :set :attr :width :val (-> mask :selrect :width) :ignore-touched true} - {:type :set :attr :height :val (-> mask :selrect :height) :ignore-touched true} - {:type :set :attr :flip-x :val (-> mask :flip-x) :ignore-touched true} - {:type :set :attr :flip-y :val (-> mask :flip-y) :ignore-touched true} - {:type :set :attr :selrect :val (-> mask :selrect) :ignore-touched true} - {:type :set :attr :points :val (-> mask :points) :ignore-touched true}]} - {:add-container? true})) + (let [file (if (:masked-group group) + (let [mask (first children) + change {:type :mod-obj + :id group-id + :operations + [{:type :set :attr :x :val (-> mask :selrect :x) :ignore-touched true} + {:type :set :attr :y :val (-> mask :selrect :y) :ignore-touched true} + {:type :set :attr :width :val (-> mask :selrect :width) :ignore-touched true} + {:type :set :attr :height :val (-> mask :selrect :height) :ignore-touched true} + {:type :set :attr :flip-x :val (-> mask :flip-x) :ignore-touched true} + {:type :set :attr :flip-y :val (-> mask :flip-y) :ignore-touched true} + {:type :set :attr :selrect :val (-> mask :selrect) :ignore-touched true} + {:type :set :attr :points :val (-> mask :points) :ignore-touched true}]}] + (commit-change file change :add-container true)) + (let [group (gsh/update-group-selrect group children) + change {:type :mod-obj + :id group-id + :operations + [{:type :set :attr :selrect :val (:selrect group) :ignore-touched true} + {:type :set :attr :points :val (:points group) :ignore-touched true} + {:type :set :attr :x :val (-> group :selrect :x) :ignore-touched true} + {:type :set :attr :y :val (-> group :selrect :y) :ignore-touched true} + {:type :set :attr :width :val (-> group :selrect :width) :ignore-touched true} + {:type :set :attr :height :val (-> group :selrect :height) :ignore-touched true}]}] - :else - (let [group' (gsh/update-group-selrect group children)] - (commit-change - file - {:type :mod-obj - :id group-id - :operations - [{:type :set :attr :selrect :val (:selrect group') :ignore-touched true} - {:type :set :attr :points :val (:points group') :ignore-touched true} - {:type :set :attr :x :val (-> group' :selrect :x) :ignore-touched true} - {:type :set :attr :y :val (-> group' :selrect :y) :ignore-touched true} - {:type :set :attr :width :val (-> group' :selrect :width) :ignore-touched true} - {:type :set :attr :height :val (-> group' :selrect :height) :ignore-touched true}]} + (commit-change file change :add-container true)))] + (update file ::parent-stack pop)))) - {:add-container? true})))] +(defn add-bool + [file params] + (let [{:keys [group-id type]} + (check-add-bool params) - (-> file - (update :parent-stack pop)))) + group + (lookup-shape file group-id) -(defn add-bool [file data] - (let [frame-id (:current-frame-id file) - obj (-> (cts/setup-shape (assoc data :type :bool :frame-id frame-id)) - (check-name file :bool))] + children + (->> (get group :shapes) + (not-empty))] + + (assert (some? children) "expect group to have at least 1 element") + + (let [objects (lookup-objects file) + bool (-> group + (assoc :type :bool) + (gsh/update-bool objects)) + change {:type :mod-obj + :id (:id bool) + :operations + [{:type :set :attr :content :val (:content bool) :ignore-touched true} + {:type :set :attr :type :val :bool :ignore-touched true} + {:type :set :attr :bool-type :val type :ignore-touched true} + {:type :set :attr :selrect :val (:selrect bool) :ignore-touched true} + {:type :set :attr :points :val (:points bool) :ignore-touched true} + {:type :set :attr :x :val (-> bool :selrect :x) :ignore-touched true} + {:type :set :attr :y :val (-> bool :selrect :y) :ignore-touched true} + {:type :set :attr :width :val (-> bool :selrect :width) :ignore-touched true} + {:type :set :attr :height :val (-> bool :selrect :height) :ignore-touched true}]}] + + (-> file + (commit-change change :add-container true) + (assoc ::last-id group-id))))) + +(defn add-shape + [file params] + (let [obj (-> params + (d/update-when :svg-attrs csvg/attrs->props) + (types.shape/setup-shape) + (assign-name file :type))] (-> file (commit-shape obj) - (assoc :last-id (:id obj)) - (add-name (:name obj)) - (update :parent-stack conjv (:id obj))))) - -(defn close-bool [file] - (let [bool-id (-> file :parent-stack peek) - bool (lookup-shape file bool-id) - children (->> bool :shapes (mapv #(lookup-shape file %))) - - file - (cond - (empty? children) - (commit-change - file - {:type :del-obj - :ignore-touched true - :id bool-id} - {:add-container? true}) - - :else - (let [objects (lookup-objects file) - bool' (gsh/update-bool bool children objects)] - (commit-change - file - {:type :mod-obj - :id bool-id - :operations - [{:type :set :attr :content :val (:content bool') :ignore-touched true} - {:type :set :attr :selrect :val (:selrect bool') :ignore-touched true} - {:type :set :attr :points :val (:points bool') :ignore-touched true} - {:type :set :attr :x :val (-> bool' :selrect :x) :ignore-touched true} - {:type :set :attr :y :val (-> bool' :selrect :y) :ignore-touched true} - {:type :set :attr :width :val (-> bool' :selrect :width) :ignore-touched true} - {:type :set :attr :height :val (-> bool' :selrect :height) :ignore-touched true}]} - - {:add-container? true})))] - - (-> file - (update :parent-stack pop)))) - -(defn create-shape - [file type data] - (let [obj (-> (assoc data :type type) - (update :svg-attrs csvg/attrs->props) - (cts/setup-shape) - (check-name file :type))] - - (-> file - (commit-shape obj) - (assoc :last-id (:id obj)) - (add-name (:name obj))))) - -(defn create-rect [file data] - (create-shape file :rect data)) - -(defn create-circle [file data] - (create-shape file :circle data)) - -(defn create-path [file data] - (create-shape file :path data)) - -(defn- clean-text-content - "Clean the content data so it doesn't break the validation" - [content] - (letfn [(update-fill [fill] - (d/update-in-when fill [:fill-color-gradient :type] keyword))] - (txt/transform-nodes - (fn [node] - (d/update-when node :fills #(mapv update-fill %))) - content))) - -(defn create-text [file data] - (let [data (d/update-when data :content clean-text-content)] - (create-shape file :text data))) - -(defn create-image [file data] - (create-shape file :image data)) - -(declare close-svg-raw) - -(defn create-svg-raw [file data] - (let [file (as-> file $ - (create-shape $ :svg-raw data) - (update $ :parent-stack conjv (:last-id $))) - - create-child - (fn [file child] - (-> file - (create-svg-raw (assoc data - :id (uuid/next) - :content child)) - (close-svg-raw)))] - - ;; First :content is the the shape attribute, the other content is the - ;; XML children - (reduce create-child file (dm/get-in data [:content :content])))) - -(defn close-svg-raw [file] - (-> file - (update :parent-stack pop))) - -(defn- read-classifier - [interaction-src] - (select-keys interaction-src [:event-type :action-type])) - -(defmulti read-event-opts :event-type) - -(defmethod read-event-opts :after-delay - [interaction-src] - (select-keys interaction-src [:delay])) - -(defmethod read-event-opts :default - [_] - {}) - -(defmulti read-action-opts :action-type) - -(defmethod read-action-opts :navigate - [interaction-src] - (select-keys interaction-src [:destination - :preserve-scroll])) - -(defmethod read-action-opts :open-overlay - [interaction-src] - (select-keys interaction-src [:destination - :overlay-position - :overlay-pos-type - :close-click-outside - :background-overlay])) - -(defmethod read-action-opts :toggle-overlay - [interaction-src] - (select-keys interaction-src [:destination - :overlay-position - :overlay-pos-type - :close-click-outside - :background-overlay])) - -(defmethod read-action-opts :close-overlay - [interaction-src] - (select-keys interaction-src [:destination])) - -(defmethod read-action-opts :prev-screen - [_] - {}) - -(defmethod read-action-opts :open-url - [interaction-src] - (select-keys interaction-src [:url])) - -(defn add-interaction - [file from-id interaction-src] - - (let [shape (lookup-shape file from-id)] - (if (nil? shape) - file - (let [{:keys [event-type action-type]} (read-classifier interaction-src) - {:keys [delay]} (read-event-opts interaction-src) - {:keys [destination overlay-pos-type overlay-position url - close-click-outside background-overlay preserve-scroll]} - (read-action-opts interaction-src) - - interactions (-> shape - :interactions - (conjv - (d/without-nils {:event-type event-type - :action-type action-type - :delay delay - :destination destination - :overlay-pos-type overlay-pos-type - :overlay-position overlay-position - :url url - :close-click-outside close-click-outside - :background-overlay background-overlay - :preserve-scroll preserve-scroll})))] - (commit-change - file - {:type :mod-obj - :page-id (:current-page-id file) - :id from-id - - :operations - [{:type :set :attr :interactions :val interactions :ignore-touched true}]}))))) - -(defn generate-changes - [file] - (:changes file)) + (assoc ::last-id (:id obj))))) (defn add-library-color [file color] - (let [id (or (:id color) (uuid/next))] + (let [color (-> color + (update :id default-uuid) + (types.color/check-library-color color)) + change {:type :add-color + :color color}] (-> file - (commit-change - {:type :add-color - :color (assoc color :id id)}) - (assoc :last-id id)))) - -(defn update-library-color - [file color] - (let [id (uuid/uuid (:id color))] - (-> file - (commit-change - {:type :mod-color - :color (assoc color :id id)}) - (assoc :last-id (:id color))))) - -(defn delete-library-color - [file color-id] - (let [id (uuid/uuid color-id)] - (-> file - (commit-change - {:type :del-color - :id id})))) + (commit-change change) + (assoc ::last-id (:id color))))) (defn add-library-typography [file typography] - (let [id (or (:id typography) (uuid/next))] + (let [typography (-> typography + (update :id default-uuid) + (d/without-nils)) + change {:type :add-typography + :id (:id typography) + :typography typography}] (-> file - (commit-change - {:type :add-typography - :id id - :typography (assoc typography :id id)}) - (assoc :last-id id)))) + (commit-change change) + (assoc ::last-id (:id typography))))) -(defn delete-library-typography - [file typography-id] - (let [id (uuid/uuid typography-id)] +(defn add-component + [file params] + (let [change1 {:type :add-component + :id (or (:id params) (uuid/next)) + :name (:name params) + :path (:path params) + :main-instance-id (:main-instance-id params) + :main-instance-page (:main-instance-page params)} + + comp-id (get change1 :id) + + change2 {:type :mod-obj + :id (:main-instance-id params) + :operations + [{:type :set :attr :component-root :val true} + {:type :set :attr :component-id :val comp-id} + {:type :set :attr :component-file :val (:id file)}]}] (-> file - (commit-change - {:type :del-typography - :id id})))) + (commit-change change1) + (commit-change change2) + (assoc ::last-id comp-id) + (assoc ::current-frame-id comp-id)))) -(defn add-library-media - [file media] - (let [id (or (:id media) (uuid/next))] - (-> file - (commit-change - {:type :add-media - :object (assoc media :id id)}) - (assoc :last-id id)))) +(defn add-component-instance + [{:keys [id data] :as file} params] -(defn delete-library-media - [file media-id] - (let [id (uuid/uuid media-id)] - (-> file - (commit-change - {:type :del-media - :id id})))) + (let [{:keys [component-id x y]} + (check-add-component-instance params) -(defn start-component - ([file data] - (start-component file data :frame)) + component + (types.components-list/get-component data component-id) - ([file data root-type] - (let [name (:name data) - path (:path data) - main-instance-id (:main-instance-id data) - main-instance-page (:main-instance-page data) + page-id + (get file ::current-page-id)] - obj-id (or (:id data) (uuid/next))] + (assert (uuid? page-id) "page-id is expected to be set") + (assert (uuid? component) "component is expected to exist") - (-> file - (commit-change - {:type :add-component - :id obj-id - :name name - :path path - :main-instance-id main-instance-id - :main-instance-page main-instance-page}) + ;; FIXME: this should be on files and not in pages-list + (let [page (types.pages-list/get-page (:data file) page-id) + pos (gpt/point x y) - (assoc :last-id obj-id) - (assoc :parent-stack [obj-id]) - (assoc :current-component-id obj-id) - (assoc :current-frame-id (if (= root-type :frame) obj-id uuid/zero)))))) + [shape shapes] + (types.container/make-component-instance page component id pos) -(defn start-deleted-component - [file data] - (let [attrs (-> data - (assoc :id (:main-instance-id data)) - (assoc :component-file (:id file)) - (assoc :component-id (:id data)) - (assoc :x (:main-instance-x data)) - (assoc :y (:main-instance-y data)) - (dissoc :path) - (dissoc :main-instance-id) - (dissoc :main-instance-page) - (dissoc :main-instance-x) - (dissoc :main-instance-y) - (dissoc :main-instance-parent) - (dissoc :main-instance-frame))] - ;; To create a deleted component, first we add all shapes of the main instance - ;; in the main instance page, and in the finish event we delete it. - (-> file - (update :parent-stack conjv (:main-instance-parent data)) - (assoc :current-page-id (:main-instance-page data)) - (assoc :current-frame-id (:main-instance-frame data)) - (add-artboard attrs)))) + file + (reduce #(commit-change %1 + {:type :add-obj + :id (:id %2) + :page-id (:id page) + :parent-id (:parent-id %2) + :frame-id (:frame-id %2) + :ignore-touched true + :obj %2}) + file + shapes)] -(defn finish-component - [file] - (let [component-id (:current-component-id file) - component-data (ctkl/get-component (:data file) component-id) + (assoc file ::last-id (:id shape))))) - component (lookup-shape file component-id) - children (->> component :shapes (mapv #(lookup-shape file %))) - - file - (cond - component-data - (update file :data - (fn [data] - (ctkl/update-component data component-id dissoc :objects))) - - (empty? children) - (commit-change - file - {:type :del-component - :id component-id - :skip-undelete? true}) - - (:masked-group component) - (let [mask (first children)] - (commit-change - file - {:type :mod-obj - :id component-id - :operations - [{:type :set :attr :x :val (-> mask :selrect :x) :ignore-touched true} - {:type :set :attr :y :val (-> mask :selrect :y) :ignore-touched true} - {:type :set :attr :width :val (-> mask :selrect :width) :ignore-touched true} - {:type :set :attr :height :val (-> mask :selrect :height) :ignore-touched true} - {:type :set :attr :flip-x :val (-> mask :flip-x) :ignore-touched true} - {:type :set :attr :flip-y :val (-> mask :flip-y) :ignore-touched true} - {:type :set :attr :selrect :val (-> mask :selrect) :ignore-touched true} - {:type :set :attr :points :val (-> mask :points) :ignore-touched true}]} - - {:add-container? true})) - - (= (:type component) :group) - (let [component' (gsh/update-group-selrect component children)] - (commit-change - file - {:type :mod-obj - :id component-id - :operations - [{:type :set :attr :selrect :val (:selrect component') :ignore-touched true} - {:type :set :attr :points :val (:points component') :ignore-touched true} - {:type :set :attr :x :val (-> component' :selrect :x) :ignore-touched true} - {:type :set :attr :y :val (-> component' :selrect :y) :ignore-touched true} - {:type :set :attr :width :val (-> component' :selrect :width) :ignore-touched true} - {:type :set :attr :height :val (-> component' :selrect :height) :ignore-touched true}]} - {:add-container? true})) - - :else file)] - - (-> file - (dissoc :current-component-id) - (dissoc :current-frame-id) - (update :parent-stack pop)))) - -(defn finish-deleted-component - [component-id file] - (let [file (assoc file :current-component-id component-id) - component (ctkl/get-component (:data file) component-id)] - (-> file - (close-artboard) - (commit-change {:type :del-component - :id component-id}) - (commit-change {:type :del-obj - :page-id (:main-instance-page component) - :id (:main-instance-id component) - :ignore-touched true}) - (dissoc :current-page-id)))) - -(defn create-component-instance - [file data] - (let [component-id (uuid/uuid (:component-id data)) - x (:x data) - y (:y data) - file (assoc file :current-component-id component-id) - page-id (:current-page-id file) - page (ctpl/get-page (:data file) page-id) - component (ctkl/get-component (:data file) component-id) - - [shape shapes] - (ctn/make-component-instance page - component - (:id file) - (gpt/point x - y))] - - (as-> file $ - (reduce #(commit-change %1 - {:type :add-obj - :id (:id %2) - :page-id (:id page) - :parent-id (:parent-id %2) - :frame-id (:frame-id %2) - :ignore-touched true - :obj %2}) - $ - shapes) - - (assoc $ :last-id (:id shape)) - (dissoc $ :current-component-id)))) - -(defn delete-object +(defn delete-shape [file id] - (let [page-id (:current-page-id file)] - (commit-change - file - {:type :del-obj - :page-id page-id - :ignore-touched true - :id id}))) + (commit-change + file + {:type :del-obj + :page-id (::current-page-id file) + :ignore-touched true + :id id})) + +(defn update-shape + [file shape-id f] + (let [page-id (::current-page-id file) + objects (lookup-objects file) + old-shape (get objects shape-id) + new-shape (f old-shape) + attrs (d/concat-set + (keys old-shape) + (keys new-shape)) -(defn update-object - [file old-obj new-obj] - (let [page-id (:current-page-id file) - new-obj (cts/setup-shape new-obj) - attrs (d/concat-set (keys old-obj) (keys new-obj)) generate-operation (fn [changes attr] - (let [old-val (get old-obj attr) - new-val (get new-obj attr)] + (let [old-val (get old-shape attr) + new-val (get new-shape attr)] (if (= old-val new-val) changes (conj changes {:type :set :attr attr :val new-val :ignore-touched true}))))] + (-> file (commit-change {:type :mod-obj :operations (reduce generate-operation [] attrs) :page-id page-id - :id (:id old-obj)}) - (assoc :last-id (:id old-obj))))) - -(defn get-current-page - [file] - (let [page-id (:current-page-id file)] - (dm/get-in file [:data :pages-index page-id]))) + :id (:id old-shape)}) + (assoc ::last-id shape-id)))) (defn add-guide [file guide] (let [guide (cond-> guide (nil? (:id guide)) (assoc :id (uuid/next))) - page-id (:current-page-id file)] + page-id (::current-page-id file)] (-> file (commit-change {:type :set-guide :page-id page-id :id (:id guide) :params guide}) - (assoc :last-id (:id guide))))) + (assoc ::last-id (:id guide))))) (defn delete-guide [file id] - (let [page-id (:current-page-id file)] + (let [page-id (::current-page-id file)] (commit-change file {:type :set-guide :page-id page-id @@ -718,7 +474,7 @@ (defn update-guide [file guide] - (let [page-id (:current-page-id file)] + (let [page-id (::current-page-id file)] (commit-change file {:type :set-guide :page-id page-id diff --git a/common/src/app/common/files/changes.cljc b/common/src/app/common/files/changes.cljc index 61b08e6b0..23dda4ac1 100644 --- a/common/src/app/common/files/changes.cljc +++ b/common/src/app/common/files/changes.cljc @@ -732,20 +732,22 @@ (update-group [group objects] (let [lookup (d/getf objects) - children (->> group :shapes (map lookup))] + children (get group :shapes)] (cond ;; If the group is empty we don't make any changes. Will be removed by a later process (empty? children) group (= :bool (:type group)) - (gsh/update-bool group children objects) + (gsh/update-bool group objects) (:masked-group group) - (set-mask-selrect group children) + (->> (map lookup children) + (set-mask-selrect group)) :else - (gsh/update-group-selrect group children))))] + (->> (map lookup children) + (gsh/update-group-selrect group)))))] (if page-id (d/update-in-when data [:pages-index page-id :objects] reg-objects) diff --git a/common/src/app/common/files/changes_builder.cljc b/common/src/app/common/files/changes_builder.cljc index 9028c04d9..af9a1379a 100644 --- a/common/src/app/common/files/changes_builder.cljc +++ b/common/src/app/common/files/changes_builder.cljc @@ -660,9 +660,13 @@ nil ;; so it does not need resize (= (:type parent) :bool) - (gsh/update-bool parent children objects) + (gsh/update-bool parent objects) (= (:type parent) :group) + ;; FIXME: this functions should be + ;; normalized in the same way as + ;; update-bool in order to make all + ;; this code consistent (if (:masked-group parent) (gsh/update-mask-selrect parent children) (gsh/update-group-selrect parent children)))] diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index 46031a54a..01dd329dd 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -455,12 +455,12 @@ (defn update-bool "Calculates the selrect+points for the boolean shape" - [shape _children objects] - + [shape objects] (let [content (path/calc-bool-content shape objects) shape (assoc shape :content content)] (path/update-geometry shape))) +;; FIXME: revisit (defn update-shapes-geometry [objects ids] (->> ids @@ -474,7 +474,7 @@ (update-mask-selrect shape children) (cfh/bool-shape? shape) - (update-bool shape children objects) + (update-bool shape objects) (cfh/group-shape? shape) (update-group-selrect shape children) diff --git a/common/src/app/common/test_helpers/files.cljc b/common/src/app/common/test_helpers/files.cljc index d7c18dcd9..5420a0c37 100644 --- a/common/src/app/common/test_helpers/files.cljc +++ b/common/src/app/common/test_helpers/files.cljc @@ -23,28 +23,32 @@ (defn sample-file [label & {:keys [page-label name view-only?] :as params}] - (binding [ffeat/*current* #{"components/v2"}] - (let [params (cond-> params - label - (assoc :id (thi/new-id! label)) + (let [params + (cond-> params + label + (assoc :id (thi/new-id! label)) - page-label - (assoc :page-id (thi/new-id! page-label)) + (nil? name) + (assoc :name "Test file") - (nil? name) - (assoc :name "Test file")) + :always + (assoc :features ffeat/default-features)) - file (-> (ctf/make-file (dissoc params :page-label)) - (assoc :features #{"components/v2"}) - (assoc :permissions {:can-edit (not (true? view-only?))})) + opts + (cond-> {} + page-label + (assoc :page-id (thi/new-id! page-label))) - page (-> file - :data - (ctpl/pages-seq) - (first))] + file (-> (ctf/make-file params opts) + (assoc :permissions {:can-edit (not (true? view-only?))})) - (with-meta file - {:current-page-id (:id page)})))) + page (-> file + :data + (ctpl/pages-seq) + (first))] + + (with-meta file + {:current-page-id (:id page)}))) (defn validate-file! ([file] (validate-file! file {})) diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index 93e41924d..e4ee9ceb9 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -137,33 +137,36 @@ (update :options assoc :components-v2 true))))) (defn make-file - [{:keys [id project-id name revn is-shared features - ignore-sync-until modified-at deleted-at - create-page page-id] - :or {is-shared false revn 0 create-page true}}] + [{:keys [id project-id name revn is-shared features migrations + ignore-sync-until modified-at deleted-at] + :or {is-shared false revn 0}} + + & {:keys [create-page page-id] + :or {create-page true}}] (let [id (or id (uuid/next)) - data (if create-page (if page-id (make-file-data id page-id) (make-file-data id)) (make-file-data id nil)) - file {:id id - :project-id project-id - :name name - :revn revn - :vern 0 - :is-shared is-shared - :version version - :data data - :features features - :ignore-sync-until ignore-sync-until - :modified-at modified-at - :deleted-at deleted-at}] + file (d/without-nils + {:id id + :project-id project-id + :name name + :revn revn + :vern 0 + :is-shared is-shared + :version version + :data data + :features features + :migrations migrations + :ignore-sync-until ignore-sync-until + :modified-at modified-at + :deleted-at deleted-at})] - (d/without-nils file))) + (check-file file))) ;; Helpers diff --git a/frontend/shadow-cljs.edn b/frontend/shadow-cljs.edn index 54a0c98e7..2cd8a48b8 100644 --- a/frontend/shadow-cljs.edn +++ b/frontend/shadow-cljs.edn @@ -149,13 +149,16 @@ {:test {:init-fn frontend-tests.runner/init :prepend-js ";if (typeof globalThis.navigator?.userAgent === 'undefined') { globalThis.navigator = {userAgent: ''}; };"}}} - :lib-penpot + :library {:target :esm - :output-dir "resources/public/libs" + :runtime :custom + :output-dir "target/library" + :devtools {:autoload false} :modules - {:penpot {:exports {:renderPage app.libs.render/render-page-export - :createFile app.libs.file-builder/create-file-export}}} + {:penpot + {:exports {BuilderError lib.file-builder/BuilderError + createFile lib.file-builder/create-file}}} :compiler-options {:output-feature-set :es2020 @@ -165,6 +168,8 @@ :release {:compiler-options {:fn-invoke-direct true + :optimizations #shadow/env ["PENPOT_BUILD_OPTIMIZATIONS" :as :keyword :default :advanced] + :pretty-print false :source-map true :elide-asserts true :anon-fn-naming-policy :off diff --git a/frontend/src/app/libs/file_builder.cljs b/frontend/src/app/libs/file_builder.cljs deleted file mode 100644 index 36e22caef..000000000 --- a/frontend/src/app/libs/file_builder.cljs +++ /dev/null @@ -1,281 +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.libs.file-builder - (:require - [app.common.data :as d] - [app.common.features :as cfeat] - [app.common.files.builder :as fb] - [app.common.media :as cm] - [app.common.types.components-list :as ctkl] - [app.common.uuid :as uuid] - [app.util.json :as json] - [app.util.webapi :as wapi] - [app.util.zip :as uz] - [app.worker.export :as e] - [beicon.v2.core :as rx] - [cuerdas.core :as str] - [promesa.core :as p])) - -(defn parse-data [data] - (as-> data $ - (js->clj $ :keywordize-keys true) - ;; Transforms camelCase to kebab-case - (d/deep-mapm - (fn [[key value]] - (let [value (if (= (type value) js/Symbol) - (keyword (js/Symbol.keyFor value)) - value) - key (-> key d/name str/kebab keyword)] - [key value])) $))) - -(defn data-uri->blob - [data-uri] - (let [[mtype b64-data] (str/split data-uri ";base64,") - mtype (subs mtype (inc (str/index-of mtype ":"))) - decoded (.atob js/window b64-data) - size (.-length ^js decoded) - content (js/Uint8Array. size)] - - (doseq [i (range 0 size)] - (aset content i (.charCodeAt decoded i))) - - (wapi/create-blob content mtype))) - -(defn parse-library-media - [[file-id media]] - (rx/merge - (let [markup - (->> (vals media) - (reduce e/collect-media {}) - (json/encode))] - (rx/of (vector (str file-id "/media.json") markup))) - - (->> (rx/from (vals media)) - (rx/map #(assoc % :file-id file-id)) - (rx/merge-map - (fn [media] - (let [file-path (str/concat file-id "/media/" (:id media) (cm/mtype->extension (:mtype media))) - blob (data-uri->blob (:uri media))] - (rx/of (vector file-path blob)))))))) - -(defn export-file - [file] - (let [file (assoc file - :name (:name file) - :file-name (:name file) - :is-shared false) - - files-stream (->> (rx/of {(:id file) file}) - (rx/share)) - - manifest-stream - (->> files-stream - (rx/map #(e/create-manifest (uuid/next) (:id file) :all % cfeat/default-features)) - (rx/map (fn [a] - (vector "manifest.json" a)))) - - render-stream - (->> files-stream - (rx/merge-map vals) - (rx/merge-map e/process-pages) - (rx/observe-on :async) - (rx/merge-map e/get-page-data) - (rx/share)) - - colors-stream - (->> files-stream - (rx/merge-map vals) - (rx/map #(vector (:id %) (get-in % [:data :colors]))) - (rx/filter #(d/not-empty? (second %))) - (rx/map e/parse-library-color)) - - typographies-stream - (->> files-stream - (rx/merge-map vals) - (rx/map #(vector (:id %) (get-in % [:data :typographies]))) - (rx/filter #(d/not-empty? (second %))) - (rx/map e/parse-library-typographies)) - - media-stream - (->> files-stream - (rx/merge-map vals) - (rx/map #(vector (:id %) (get-in % [:data :media]))) - (rx/filter #(d/not-empty? (second %))) - (rx/merge-map parse-library-media)) - - components-stream - (->> files-stream - (rx/merge-map vals) - (rx/filter #(d/not-empty? (ctkl/components-seq (:data %)))) - (rx/merge-map e/parse-library-components)) - - pages-stream - (->> render-stream - (rx/map e/collect-page))] - - (rx/merge - (->> render-stream - (rx/map #(hash-map - :type :progress - :file (:id file) - :data (str "Render " (:file-name %) " - " (:name %))))) - - (->> (rx/merge - manifest-stream - pages-stream - components-stream - media-stream - colors-stream - typographies-stream) - (rx/reduce conj []) - (rx/with-latest-from files-stream) - (rx/merge-map (fn [[data _]] - (->> (uz/compress-files data) - (rx/map #(vector file %))))))))) - -(deftype File [^:mutable file] - Object - - (addPage [_ name] - (set! file (fb/add-page file {:name name})) - (str (:current-page-id file))) - - (addPage [_ name options] - (set! file (fb/add-page file {:name name :options (parse-data options)})) - (str (:current-page-id file))) - - (closePage [_] - (set! file (fb/close-page file))) - - (addArtboard [_ data] - (set! file (fb/add-artboard file (parse-data data))) - (str (:last-id file))) - - (closeArtboard [_] - (set! file (fb/close-artboard file))) - - (addGroup [_ data] - (set! file (fb/add-group file (parse-data data))) - (str (:last-id file))) - - (closeGroup [_] - (set! file (fb/close-group file))) - - (addBool [_ data] - (set! file (fb/add-bool file (parse-data data))) - (str (:last-id file))) - - (closeBool [_] - (set! file (fb/close-bool file))) - - (createRect [_ data] - (set! file (fb/create-rect file (parse-data data))) - (str (:last-id file))) - - (createCircle [_ data] - (set! file (fb/create-circle file (parse-data data))) - (str (:last-id file))) - - (createPath [_ data] - (set! file (fb/create-path file (parse-data data))) - (str (:last-id file))) - - (createText [_ data] - (set! file (fb/create-text file (parse-data data))) - (str (:last-id file))) - - (createImage [_ data] - (set! file (fb/create-image file (parse-data data))) - (str (:last-id file))) - - (createSVG [_ data] - (set! file (fb/create-svg-raw file (parse-data data))) - (str (:last-id file))) - - (closeSVG [_] - (set! file (fb/close-svg-raw file))) - - (addLibraryColor [_ data] - (set! file (fb/add-library-color file (parse-data data))) - (str (:last-id file))) - - (updateLibraryColor [_ data] - (set! file (fb/update-library-color file (parse-data data))) - (str (:last-id file))) - - (deleteLibraryColor [_ data] - (set! file (fb/delete-library-color file (parse-data data))) - (str (:last-id file))) - - (addLibraryMedia [_ data] - (set! file (fb/add-library-media file (parse-data data))) - (str (:last-id file))) - - (deleteLibraryMedia [_ data] - (set! file (fb/delete-library-media file (parse-data data))) - (str (:last-id file))) - - (addLibraryTypography [_ data] - (set! file (fb/add-library-typography file (parse-data data))) - (str (:last-id file))) - - (deleteLibraryTypography [_ data] - (set! file (fb/delete-library-typography file (parse-data data))) - (str (:last-id file))) - - (startComponent [_ data] - (set! file (fb/start-component file (parse-data data))) - (str (:current-component-id file))) - - (finishComponent [_] - (set! file (fb/finish-component file))) - - (createComponentInstance [_ data] - (set! file (fb/create-component-instance file (parse-data data))) - (str (:last-id file))) - - (lookupShape [_ shape-id] - (clj->js (fb/lookup-shape file (uuid/parse shape-id)))) - - (updateObject [_ id new-obj] - (let [old-obj (fb/lookup-shape file (uuid/parse id)) - new-obj (d/deep-merge old-obj (parse-data new-obj))] - (set! file (fb/update-object file old-obj new-obj)))) - - (deleteObject [_ id] - (set! file (fb/delete-object file (uuid/parse id)))) - - (getId [_] - (:id file)) - - (getCurrentPageId [_] - (:current-page-id file)) - - (asMap [_] - (clj->js file)) - - (newId [_] - (uuid/next)) - - (export [_] - (p/create - (fn [resolve reject] - (->> (export-file file) - (rx/filter #(not= (:type %) :progress)) - (rx/take 1) - (rx/subs! - (fn [value] - (let [[_ export-blob] value] - (resolve export-blob))) - reject)))))) - -(defn create-file-export [^string name] - (binding [cfeat/*current* cfeat/default-features] - (File. (fb/create-file name)))) - -(defn exports [] - #js {:createFile create-file-export}) diff --git a/frontend/src/app/libs/render.cljs b/frontend/src/app/libs/render.cljs deleted file mode 100644 index 7ece057c0..000000000 --- a/frontend/src/app/libs/render.cljs +++ /dev/null @@ -1,28 +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.libs.render - (:require - [app.common.uuid :as uuid] - [app.main.render :as r] - [beicon.v2.core :as rx] - [promesa.core :as p])) - -(defn render-page-export - [file ^string page-id] - - ;; Better to expose the api as a promise to be consumed from JS - (let [page-id (uuid/parse page-id) - file-data (.-file file) - data (get-in file-data [:data :pages-index page-id])] - (p/create - (fn [resolve reject] - (->> (r/render-page data) - (rx/take 1) - (rx/subs! resolve reject)))))) - -(defn exports [] - #js {:renderPage render-page-export}) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 9221356ec..48b67fe39 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -771,12 +771,12 @@ ;; --- Update Shape Attrs -;; FIXME: revisit this before merge +;; FIXME: rename to update-shape-generic-attrs because on the end we +;; only allow here to update generic attrs (defn update-shape [id attrs] (assert (uuid? id) "expected valid uuid for `id`") - - (let [attrs (cts/check-shape-attrs attrs)] + (let [attrs (cts/check-shape-generic-attrs attrs)] (ptk/reify ::update-shape ptk/WatchEvent (watch [_ _ _] diff --git a/frontend/src/app/main/data/workspace/bool.cljs b/frontend/src/app/main/data/workspace/bool.cljs index 858239b75..e91e66ca6 100644 --- a/frontend/src/app/main/data/workspace/bool.cljs +++ b/frontend/src/app/main/data/workspace/bool.cljs @@ -12,7 +12,6 @@ [app.common.geom.shapes :as gsh] [app.common.types.component :as ctc] [app.common.types.container :as ctn] - [app.common.types.path :as path] [app.common.types.path.bool :as bool] [app.common.types.shape :as cts] [app.common.types.shape.layout :as ctl] @@ -30,9 +29,6 @@ (let [shape-id (or id (uuid/next)) - shapes - (mapv #(path/convert-to-path % objects) shapes) - head (if (= type :difference) (first shapes) (last shapes)) @@ -48,13 +44,13 @@ :frame-id (:frame-id head) :parent-id (:parent-id head) :name name - :shapes (mapv :id shapes)} + :shapes (vec shapes)} shape (-> shape (merge (select-keys head bool/style-properties)) (cts/setup-shape) - (gsh/update-bool shapes objects))] + (gsh/update-bool objects))] [shape (cph/get-position-on-parent objects (:id head))])) @@ -108,19 +104,16 @@ (defn group->bool [type group objects] (let [shapes (->> (:shapes group) - (map #(get objects %)) - (mapv #(path/convert-to-path % objects))) + (map (d/getf objects))) head (if (= type :difference) (first shapes) (last shapes)) head (cond-> head (and (contains? head :svg-attrs) (empty? (:fills head))) - (assoc :fills bool/default-fills)) - head-data (select-keys head bool/style-properties)] - + (assoc :fills bool/default-fills))] (-> group (assoc :type :bool) (assoc :bool-type type) - (merge head-data) - (gsh/update-bool shapes objects)))) + (merge (select-keys head bool/style-properties)) + (gsh/update-bool objects)))) (defn group-to-bool [shape-id type] diff --git a/frontend/src/app/util/object.cljc b/frontend/src/app/util/object.cljc index 072d2a5f9..d7404b870 100644 --- a/frontend/src/app/util/object.cljc +++ b/frontend/src/app/util/object.cljc @@ -6,7 +6,7 @@ (ns app.util.object "A collection of helpers for work with javascript objects." - (:refer-clojure :exclude [set! new get merge clone contains? array? into-array reify]) + (:refer-clojure :exclude [set! new get merge clone contains? array? into-array reify class]) #?(:cljs (:require-macros [app.util.object])) (:require [clojure.core :as c])) diff --git a/frontend/src/lib/file_builder.cljs b/frontend/src/lib/file_builder.cljs new file mode 100644 index 000000000..cf09d7ee2 --- /dev/null +++ b/frontend/src/lib/file_builder.cljs @@ -0,0 +1,250 @@ +;; 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 lib.file-builder + (:require + [app.common.data :as d] + [app.common.data.macros :as dm] + [app.common.files.builder :as fb] + [app.common.json :as json] + [app.common.schema :as sm] + [app.common.uuid :as uuid] + [app.util.object :as obj])) + +(def BuilderError + (obj/class + :name "BuilderError" + :extends js/Error + :constructor + (fn [this type code hint cause] + (.call js/Error this hint) + (set! (.-name this) (str "Exception: " hint)) + (set! (.-type this) type) + (set! (.-code this) code) + (set! (.-hint this) hint) + + (when (exists? js/Error.captureStackTrace) + (.captureStackTrace js/Error this)) + + (obj/add-properties! + this + {:name "cause" + :enumerable true + :this false + :get (fn [] cause)} + {:name "data" + :enumerable true + :this false + :get (fn [] + (let [data (ex-data cause)] + (when-let [explain (::sm/explain data)] + (json/->js (sm/simplify explain)))))})))) + +(defn- handle-exception + [cause] + (let [data (ex-data cause)] + (throw (new BuilderError + (d/name (get data :type :unknown)) + (d/name (get data :code :unknown)) + (or (get data :hint) (ex-message cause)) + cause)))) + +(defn- decode-params + [params] + (if (obj/plain-object? params) + (json/->js params) + params)) + +(defn- create-file* + [file] + (let [state* (volatile! file)] + (obj/reify {:name "File"} + :id + {:get #(dm/str (:id @state*))} + + :currentFrameId + {:get #(dm/str (::fb/current-frame-id @state*))} + + :currentPageId + {:get #(dm/str (::fb/current-page-id @state*))} + + :lastId + {:get #(dm/str (::fb/last-id @state*))} + + :addPage + (fn [params] + (try + (let [params (-> params + (decode-params) + (fb/decode-page))] + (vswap! state* fb/add-page params) + (dm/str (::fb/current-page-id @state*))) + (catch :default cause + (handle-exception cause)))) + + :closePage + (fn [] + (vswap! state* fb/close-page)) + + :addArtboard + (fn [params] + (try + (let [params (-> params + (json/->clj) + (assoc :type :frame) + (fb/decode-shape))] + (vswap! state* fb/add-artboard params) + (dm/str (::fb/last-id @state*))) + (catch :default cause + (handle-exception cause)))) + + :closeArtboard + (fn [] + (vswap! state* fb/close-artboard)) + + :addGroup + (fn [params] + (try + (let [params (-> params + (json/->clj) + (assoc :type :group) + (fb/decode-shape))] + (vswap! state* fb/add-group params) + (dm/str (::fb/last-id @state*))) + (catch :default cause + (handle-exception cause)))) + + :closeGroup + (fn [] + (vswap! state* fb/close-group)) + + :addBool + (fn [params] + (try + (let [params (-> params + (json/->clj) + (fb/decode-add-bool))] + (vswap! state* fb/add-bool params) + (dm/str (::fb/last-id @state*))) + (catch :default cause + (handle-exception cause)))) + + :addRect + (fn [params] + (try + (let [params (-> params + (json/->clj) + (assoc :type :rect) + (fb/decode-shape))] + (vswap! state* fb/add-shape params) + (dm/str (::fb/last-id @state*))) + (catch :default cause + (handle-exception cause)))) + + :addCircle + (fn [params] + (try + (let [params (-> params + (json/->clj) + (assoc :type :circle) + (fb/decode-shape))] + (vswap! state* fb/add-shape params) + (dm/str (::fb/last-id @state*))) + (catch :default cause + (handle-exception cause)))) + + :addPath + (fn [params] + (try + (let [params (-> params + (json/->clj) + (assoc :type :path) + (fb/decode-shape))] + (vswap! state* fb/add-shape params) + (dm/str (::fb/last-id @state*))) + (catch :default cause + (handle-exception cause)))) + + :addText + (fn [params] + (try + (let [params (-> params + (json/->clj) + (assoc :type :text) + (fb/decode-shape))] + (vswap! state* fb/add-shape params) + (dm/str (::fb/last-id @state*))) + (catch :default cause + (handle-exception cause)))) + + :addLibraryColor + (fn [params] + (try + (let [params (-> params + (json/->clj) + (fb/decode-library-color) + (d/without-nils))] + (vswap! state* fb/add-library-color params) + (dm/str (::fb/last-id @state*))) + (catch :default cause + (handle-exception cause)))) + + :addLibraryTypography + (fn [params] + (try + (let [params (-> params + (json/->clj) + (fb/decode-library-typography) + (d/without-nils))] + (vswap! state* fb/add-library-typography params) + (dm/str (::fb/last-id @state*))) + (catch :default cause + (handle-exception cause)))) + + :addComponent + (fn [params] + (try + (let [params (-> params + (json/->clj) + (fb/decode-component) + (d/without-nils))] + (vswap! state* fb/add-component params) + (dm/str (::fb/last-id @state*))) + (catch :default cause + (handle-exception cause)))) + + :addComponentInstance + (fn [params] + (try + (let [params (-> params + (json/->clj) + (fb/decode-add-component-instance) + (d/without-nils))] + (vswap! state* fb/add-component-instance params) + (dm/str (::fb/last-id @state*))) + (catch :default cause + (handle-exception cause)))) + + :getShape + (fn [shape-id] + (let [shape-id (uuid/parse shape-id)] + (some-> (fb/lookup-shape @state* shape-id) + (json/->js)))) + + :toMap + (fn [] + (-> @state* + (d/without-qualified) + (json/->js)))))) + +(defn create-file + [params] + (try + (let [params (-> params json/->clj fb/decode-file) + file (fb/create-file params)] + (create-file* file)) + (catch :default cause + (handle-exception cause)))) diff --git a/frontend/src/lib/playground/sample1.js b/frontend/src/lib/playground/sample1.js new file mode 100644 index 000000000..8dd7c8799 --- /dev/null +++ b/frontend/src/lib/playground/sample1.js @@ -0,0 +1,30 @@ +import * as penpot from "../../../target/library/penpot.js"; + +console.log(penpot); + +try { + const file = penpot.createFile({name: "Test"}); + file.addPage({name: "Foo Page"}) + const boardId = file.addArtboard({name: "Foo Board"}) + const rectId = file.addRect({name: "Foo Rect", width:100, height: 200}) + + file.addLibraryColor({color: "#fabada", opacity: 0.5}) + + console.log("created board", boardId); + console.log("created rect", rectId); + + const board = file.getShape(boardId); + console.log("=========== BOARD =============") + console.dir(board, {depth: 10}); + + const rect = file.getShape(rectId); + console.log("=========== RECT =============") + console.dir(rect, {depth: 10}); + + // console.dir(file.toMap(), {depth:10}); +} catch (e) { + console.log(e); + // console.log(e.data); +} + +process.exit(0); diff --git a/frontend/test/frontend_tests/plugins/context_shapes_test.cljs b/frontend/test/frontend_tests/plugins/context_shapes_test.cljs index 6afbf63bd..590162203 100644 --- a/frontend/test/frontend_tests/plugins/context_shapes_test.cljs +++ b/frontend/test/frontend_tests/plugins/context_shapes_test.cljs @@ -16,8 +16,7 @@ (t/deftest test-common-shape-properties (let [;; ==== Setup - store (ths/setup-store - (cthf/sample-file :file1 :page-label :page1)) + store (ths/setup-store (cthf/sample-file :file1 :page-label :page1)) ^js context (api/create-context "TEST") diff --git a/frontend/test/frontend_tests/util_snap_data_test.cljs b/frontend/test/frontend_tests/util_snap_data_test.cljs index d988c4e9a..f46fba7d6 100644 --- a/frontend/test/frontend_tests/util_snap_data_test.cljs +++ b/frontend/test/frontend_tests/util_snap_data_test.cljs @@ -13,13 +13,21 @@ [cljs.pprint :refer [pprint]] [cljs.test :as t :include-macros true])) +(def uuid-counter 1) + +(defn get-mocked-uuid + [] + (let [counter (atom 0)] + (fn [] + (uuid/custom 123456789 (swap! counter inc))))) + (t/deftest test-create-index (t/testing "Create empty data" (let [data (sd/make-snap-data)] (t/is (some? data)))) (t/testing "Add empty page (only root-frame)" - (let [page (-> (fb/create-file "Test") + (let [page (-> (fb/create-file {:name "Test"}) (fb/add-page {:name "Page 1"}) (fb/get-current-page)) @@ -28,10 +36,11 @@ (t/is (some? data)))) (t/testing "Create simple shape on root" - (let [file (-> (fb/create-file "Test") + (let [file (-> (fb/create-file {:name "Test"}) (fb/add-page {:name "Page 1"}) - (fb/create-rect - {:x 0 + (fb/add-shape + {:type :rect + :x 0 :y 0 :width 100 :height 100})) @@ -57,7 +66,7 @@ (t/is (= (first (nth result-x 2)) 100)))) (t/testing "Add page with single empty frame" - (let [file (-> (fb/create-file "Test") + (let [file (-> (fb/create-file {:name "Test"}) (fb/add-page {:name "Page 1"}) (fb/add-artboard {:x 0 @@ -66,10 +75,10 @@ :height 100}) (fb/close-artboard)) - frame-id (:last-id file) + frame-id (::fb/last-id file) page (fb/get-current-page file) - ;; frame-id (:last-id file) + ;; frame-id (::fb/last-id file) data (-> (sd/make-snap-data) (sd/add-page page)) @@ -81,47 +90,49 @@ (t/is (= (count result-frame-x) 3)))) (t/testing "Add page with some shapes inside frames" - (let [file (-> (fb/create-file "Test") - (fb/add-page {:name "Page 1"}) - (fb/add-artboard - {:x 0 - :y 0 - :width 100 - :height 100})) - frame-id (:last-id file) + (with-redefs [uuid/next (get-mocked-uuid)] + (let [file (-> (fb/create-file {:name "Test"}) + (fb/add-page {:name "Page 1"}) + (fb/add-artboard + {:x 0 + :y 0 + :width 100 + :height 100})) - file (-> file - (fb/create-rect - {:x 25 - :y 25 - :width 50 - :height 50}) - (fb/close-artboard)) + frame-id (::fb/last-id file) - page (fb/get-current-page file) + file (-> file + (fb/add-shape + {:type :rect + :x 25 + :y 25 + :width 50 + :height 50}) + (fb/close-artboard)) - ;; frame-id (:last-id file) - data (-> (sd/make-snap-data) - (sd/add-page page)) + page (fb/get-current-page file) - result-zero-x (sd/query data (:id page) uuid/zero :x [0 100]) - result-frame-x (sd/query data (:id page) frame-id :x [0 100])] + data (-> (sd/make-snap-data) + (sd/add-page page)) - (t/is (some? data)) - (t/is (= (count result-zero-x) 3)) - (t/is (= (count result-frame-x) 5)))) + result-zero-x (sd/query data (:id page) uuid/zero :x [0 100]) + result-frame-x (sd/query data (:id page) frame-id :x [0 100])] + + (t/is (some? data)) + (t/is (= (count result-zero-x) 3)) + (t/is (= (count result-frame-x) 5))))) (t/testing "Add a global guide" - (let [file (-> (fb/create-file "Test") + (let [file (-> (fb/create-file {:name "Test"}) (fb/add-page {:name "Page 1"}) (fb/add-guide {:position 50 :axis :x}) (fb/add-artboard {:x 200 :y 200 :width 100 :height 100}) (fb/close-artboard)) - frame-id (:last-id file) + frame-id (::fb/last-id file) page (fb/get-current-page file) - ;; frame-id (:last-id file) + ;; frame-id (::fb/last-id file) data (-> (sd/make-snap-data) (sd/add-page page)) @@ -140,26 +151,26 @@ (t/is (= (count result-frame-y) 0)))) (t/testing "Add a frame guide" - (let [file (-> (fb/create-file "Test") + (let [file (-> (fb/create-file {:name "Test"}) (fb/add-page {:name "Page 1"}) (fb/add-artboard {:x 200 :y 200 :width 100 :height 100}) (fb/close-artboard)) - frame-id (:last-id file) + frame-id (::fb/last-id file) file (-> file (fb/add-guide {:position 50 :axis :x :frame-id frame-id})) page (fb/get-current-page file) - ;; frame-id (:last-id file) - data (-> (sd/make-snap-data) - (sd/add-page page)) + data (-> (sd/make-snap-data) + (sd/add-page page)) result-zero-x (sd/query data (:id page) uuid/zero :x [0 100]) result-zero-y (sd/query data (:id page) uuid/zero :y [0 100]) result-frame-x (sd/query data (:id page) frame-id :x [0 100]) result-frame-y (sd/query data (:id page) frame-id :y [0 100])] + (t/is (some? data)) ;; We can snap in the root (t/is (= (count result-zero-x) 0)) @@ -171,7 +182,7 @@ (t/deftest test-update-index (t/testing "Create frame on root and then remove it." - (let [file (-> (fb/create-file "Test") + (let [file (-> (fb/create-file {:name "Test"}) (fb/add-page {:name "Page 1"}) (fb/add-artboard {:x 0 @@ -180,15 +191,15 @@ :height 100}) (fb/close-artboard)) - shape-id (:last-id file) + shape-id (::fb/last-id file) page (fb/get-current-page file) - ;; frame-id (:last-id file) + ;; frame-id (::fb/last-id file) data (-> (sd/make-snap-data) (sd/add-page page)) file (-> file - (fb/delete-object shape-id)) + (fb/delete-shape shape-id)) new-page (fb/get-current-page file) data (sd/update-page data page new-page) @@ -201,22 +212,23 @@ (t/is (= (count result-y) 0)))) (t/testing "Create simple shape on root. Then remove it" - (let [file (-> (fb/create-file "Test") + (let [file (-> (fb/create-file {:name "Test"}) (fb/add-page {:name "Page 1"}) - (fb/create-rect - {:x 0 + (fb/add-shape + {:type :rect + :x 0 :y 0 :width 100 :height 100})) - shape-id (:last-id file) + shape-id (::fb/last-id file) page (fb/get-current-page file) - ;; frame-id (:last-id file) + ;; frame-id (::fb/last-id file) data (-> (sd/make-snap-data) (sd/add-page page)) - file (fb/delete-object file shape-id) + file (fb/delete-shape file shape-id) new-page (fb/get-current-page file) data (sd/update-page data page new-page) @@ -229,17 +241,17 @@ (t/is (= (count result-y) 0)))) (t/testing "Create shape inside frame, then remove it" - (let [file (-> (fb/create-file "Test") + (let [file (-> (fb/create-file {:name "Test"}) (fb/add-page {:name "Page 1"}) (fb/add-artboard {:x 0 :y 0 :width 100 :height 100})) - frame-id (:last-id file) + frame-id (::fb/last-id file) - file (fb/create-rect file {:x 25 :y 25 :width 50 :height 50}) - shape-id (:last-id file) + file (fb/add-shape file {:type :rect :x 25 :y 25 :width 50 :height 50}) + shape-id (::fb/last-id file) file (fb/close-artboard file) @@ -247,7 +259,7 @@ data (-> (sd/make-snap-data) (sd/add-page page)) - file (fb/delete-object file shape-id) + file (fb/delete-shape file shape-id) new-page (fb/get-current-page file) data (sd/update-page data page new-page) @@ -260,16 +272,16 @@ (t/is (= (count result-frame-x) 3)))) (t/testing "Create global guide then remove it" - (let [file (-> (fb/create-file "Test") + (let [file (-> (fb/create-file {:name "Test"}) (fb/add-page {:name "Page 1"}) (fb/add-guide {:position 50 :axis :x})) - guide-id (:last-id file) + guide-id (::fb/last-id file) file (-> (fb/add-artboard file {:x 200 :y 200 :width 100 :height 100}) (fb/close-artboard)) - frame-id (:last-id file) + frame-id (::fb/last-id file) page (fb/get-current-page file) data (-> (sd/make-snap-data) (sd/add-page page)) @@ -293,14 +305,14 @@ (t/is (= (count result-frame-y) 0)))) (t/testing "Create frame guide then remove it" - (let [file (-> (fb/create-file "Test") + (let [file (-> (fb/create-file {:name "Test"}) (fb/add-page {:name "Page 1"}) (fb/add-artboard {:x 200 :y 200 :width 100 :height 100}) (fb/close-artboard)) - frame-id (:last-id file) + frame-id (::fb/last-id file) file (fb/add-guide file {:position 50 :axis :x :frame-id frame-id}) - guide-id (:last-id file) + guide-id (::fb/last-id file) page (fb/get-current-page file) data (-> (sd/make-snap-data) (sd/add-page page)) @@ -324,7 +336,7 @@ (t/is (= (count result-frame-y) 0)))) (t/testing "Update frame coordinates" - (let [file (-> (fb/create-file "Test") + (let [file (-> (fb/create-file {:name "Test"}) (fb/add-page {:name "Page 1"}) (fb/add-artboard {:x 0 @@ -333,17 +345,18 @@ :height 100}) (fb/close-artboard)) - frame-id (:last-id file) + frame-id (::fb/last-id file) page (fb/get-current-page file) data (-> (sd/make-snap-data) (sd/add-page page)) - frame (fb/lookup-shape file frame-id) - new-frame (-> frame - (dissoc :selrect :points) - (assoc :x 200 :y 200) - (cts/setup-shape)) + file (fb/update-shape file frame-id + (fn [shape] + (-> shape + (dissoc :selrect :points) + (assoc :x 200 :y 200) + (cts/setup-shape)))) + - file (fb/update-object file frame new-frame) new-page (fb/get-current-page file) data (sd/update-page data page new-page) @@ -360,27 +373,30 @@ (t/is (= (count result-frame-x-2) 3)))) (t/testing "Update shape coordinates" - (let [file (-> (fb/create-file "Test") + (let [file (-> (fb/create-file {:name "Test"}) (fb/add-page {:name "Page 1"}) - (fb/create-rect - {:x 0 + (fb/add-shape + {:type :rect + :x 0 :y 0 :width 100 :height 100})) - shape-id (:last-id file) + shape-id (::fb/last-id file) page (fb/get-current-page file) - data (-> (sd/make-snap-data) (sd/add-page page)) + data (-> (sd/make-snap-data) + (sd/add-page page)) - shape (fb/lookup-shape file shape-id) - new-shape (-> shape - (dissoc :selrect :points) - (assoc :x 200 :y 200)) + file (fb/update-shape file shape-id + (fn [shape] + (-> shape + (dissoc :selrect :points) + (assoc :x 200 :y 200) + (cts/setup-shape)))) - file (fb/update-object file shape new-shape) new-page (fb/get-current-page file) - - data (sd/update-page data page new-page) + ;; FIXME: update + data (sd/update-page data page new-page) result-zero-x-1 (sd/query data (:id page) uuid/zero :x [0 100]) result-zero-x-2 (sd/query data (:id page) uuid/zero :x [200 300])] @@ -391,17 +407,17 @@ (t/testing "Update global guide" (let [guide {:position 50 :axis :x} - file (-> (fb/create-file "Test") + file (-> (fb/create-file {:name "Test"}) (fb/add-page {:name "Page 1"}) (fb/add-guide guide)) - guide-id (:last-id file) + guide-id (::fb/last-id file) guide (assoc guide :id guide-id) file (-> (fb/add-artboard file {:x 500 :y 500 :width 100 :height 100}) (fb/close-artboard)) - frame-id (:last-id file) + frame-id (::fb/last-id file) page (fb/get-current-page file) data (-> (sd/make-snap-data) (sd/add-page page))