diff --git a/CHANGES.md b/CHANGES.md index 263f75f922..c091a73daa 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,33 +8,65 @@ **Breaking changes on penpot library:** +The library entrypoint API object has been changed. From now you start creating a new +build context, from where you can add multiple files and attach media. This change add the +ability to build more than one file at same time and export them in an unique .penpot +file. + +```js +const context = penpot.createBuildContext() + +context.addFile({name:"aa"}) +context.addPage({name:"aa"}) +context.closePage() +context.closeFile() + +;; barray is instance of Uint8Array +const barray = penpot.exportAsBytes(context); +``` + +The previous `file.export()` method has been removed and several alternatives are +added as first level functions on penpot library API entrypoint: + +- `exportAsBytes(BuildContext context) -> Promise` +- `exportAsBlob(BuildContext context) -> Promise` +- `exportStream(BuildContext context, WritableStream stream) -> Promise` + +The stream variant allows writting data as it is generated to the stream, without the need +to store the generated output entirelly in the memory. + +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. + +A non exhaustive list of changes: + - 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 +- Rename the `createRect` method to `addRect` +- Rename the `createCircle` method to `addCircle` +- Rename the `createPath` method to `addPath` +- Rename the `createText` method to `addText` +- Rename the `addArtboard` method to `addBoard` +- Rename `startComponent` to `addComponent` (to preserve the naming style) +- Rename `createComponentInstance` to `addComponentInstance` (to preserve the naming style) +- Remove `lookupShape` +- Remove `asMap` +- Remove `updateLibraryColor` (use `addLibraryColor` if you just need to replace a color) +- Remove `deleteLibraryColor` (this library is intended to build files) +- Remove `updateLibraryTypography` (use `addLibraryTypography` if you just need to replace a typography) +- Remove `deleteLibraryTypography` (this library is intended to build files) +- Remove `add/update/deleteLibraryMedia` (they are no longer supported by Penpot and have been replaced by components) +- Remove `deleteObject` (this library is intended to build files) +- Remove `updateObject` (this library is intended to build files) +- Remove `finishComponent` (it is no longer necessary; see below for more details on component creation changes) -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. +- Change the `getCurrentPageId` function to a read-only `currentPageId` property +- Add `currentFileId` read-only property +- Add `currentFrameId` read-only property +- Add `lastId` read-only property ### :heart: Community contributions (Thank you!) diff --git a/backend/src/app/binfile/v3.clj b/backend/src/app/binfile/v3.clj index 961dd2f33f..7659ec5672 100644 --- a/backend/src/app/binfile/v3.clj +++ b/backend/src/app/binfile/v3.clj @@ -18,6 +18,7 @@ [app.common.files.migrations :as-alias fmg] [app.common.json :as json] [app.common.logging :as l] + [app.common.media :as cmedia] [app.common.schema :as sm] [app.common.thumbnails :as cth] [app.common.types.color :as ctcl] @@ -73,7 +74,7 @@ [:size ::sm/int] [:content-type :string] [:bucket [::sm/one-of {:format :string} sto/valid-buckets]] - [:hash :string]]) + [:hash {:optional true} :string]]) (def ^:private schema:file-thumbnail [:map {:title "FileThumbnail"} @@ -88,13 +89,19 @@ ctf/schema:file [:map [:options {:optional true} ctf/schema:options]]]) +;; --- HELPERS + +(defn- default-now + [o] + (or o (dt/now))) + ;; --- ENCODERS (def encode-file (sm/encoder schema:file sm/json-transformer)) (def encode-page - (sm/encoder ::ctp/page sm/json-transformer)) + (sm/encoder ctp/schema:page sm/json-transformer)) (def encode-shape (sm/encoder ::cts/shape sm/json-transformer)) @@ -129,7 +136,7 @@ (sm/decoder schema:manifest sm/json-transformer)) (def decode-media - (sm/decoder ::ctf/media sm/json-transformer)) + (sm/decoder ctf/schema:media sm/json-transformer)) (def decode-component (sm/decoder ::ctc/component sm/json-transformer)) @@ -229,27 +236,13 @@ :always (bfc/clean-file-features)))))) -(defn- resolve-extension - [mtype] - (case mtype - "image/png" ".png" - "image/jpeg" ".jpg" - "image/gif" ".gif" - "image/svg+xml" ".svg" - "image/webp" ".webp" - "font/woff" ".woff" - "font/woff2" ".woff2" - "font/ttf" ".ttf" - "font/otf" ".otf" - "application/octet-stream" ".bin")) - (defn- export-storage-objects [{:keys [::output] :as cfg}] (let [storage (sto/resolve cfg)] (doseq [id (-> bfc/*state* deref :storage-objects not-empty)] (let [sobject (sto/get-object storage id) smeta (meta sobject) - ext (resolve-extension (:content-type smeta)) + ext (cmedia/mtype->extension (:content-type smeta)) path (str "objects/" id ".json") params (-> (meta sobject) (assoc :id (:id sobject)) @@ -574,7 +567,14 @@ (let [object (->> (read-entry input entry) (decode-media) (validate-media)) - object (assoc object :file-id file-id)] + object (-> object + (assoc :file-id file-id) + (update :created-at default-now) + (update :modified-at default-now) + ;; FIXME: this is set default to true for + ;; setting a value, this prop is no longer + ;; relevant; + (assoc :is-local true))] (if (= id (:id object)) (conj result object) result))) @@ -800,7 +800,7 @@ :expected-id (str id) :found-id (str (:id object)))) - (let [ext (resolve-extension (:content-type object)) + (let [ext (cmedia/mtype->extension (:content-type object)) path (str "objects/" id ext) content (->> path (get-zip-entry input) @@ -814,13 +814,14 @@ :expected-size (:size object) :found-size (sto/get-size content))) - (when (not= (:hash object) (sto/get-hash content)) - (ex/raise :type :validation - :code :inconsistent-penpot-file - :hint "found corrupted storage object: hash does not match" - :path path - :expected-hash (:hash object) - :found-hash (sto/get-hash content))) + (when-let [hash (get object :hash)] + (when (not= hash (sto/get-hash content)) + (ex/raise :type :validation + :code :inconsistent-penpot-file + :hint "found corrupted storage object: hash does not match" + :path path + :expected-hash (:hash object) + :found-hash (sto/get-hash content)))) (let [params (-> object (dissoc :id :size) diff --git a/backend/test/backend_tests/rpc_file_test.clj b/backend/test/backend_tests/rpc_file_test.clj index da754ef966..951ee96a83 100644 --- a/backend/test/backend_tests/rpc_file_test.clj +++ b/backend/test/backend_tests/rpc_file_test.clj @@ -1712,6 +1712,7 @@ [{:fill-image {:id (:id fmedia) :name "test" + :mtype "image/jpeg" :width 200 :height 200}}]] diff --git a/common/src/app/common/files/builder.cljc b/common/src/app/common/files/builder.cljc index c62b098dda..c51dccde9a 100644 --- a/common/src/app/common/files/builder.cljc +++ b/common/src/app/common/files/builder.cljc @@ -13,17 +13,12 @@ [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.schema :as sm] [app.common.svg :as csvg] [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] @@ -37,41 +32,36 @@ (def ^:private conjv (fnil conj [])) (def ^:private conjs (fnil conj #{})) -(defn default-uuid +(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))) + [state name] + (let [container-id (::current-page-id state)] + (update-in state [::unames container-id] conjs name))) (defn- commit-change - [file change & {:keys [add-container] - :or {add-container false}}] + [state change & {:keys [add-container]}] + (let [file-id (get state ::current-file-id)] + (assert (uuid? file-id) "no current file id") - (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] - (dm/get-in file [:data :pages-index (::current-page-id file) :objects])) + (let [change (cond-> change + add-container + (assoc :page-id (::current-page-id state) + :frame-id (::current-frame-id state)))] + (update-in state [::files file-id :data] ch/process-changes [change] false)))) (defn- commit-shape - [file shape] + [state shape] (let [parent-id - (-> file ::parent-stack peek) + (-> state ::parent-stack peek) frame-id - (::current-frame-id file) + (get state ::current-frame-id) page-id - (::current-page-id file) + (get state ::current-page-id) change {:type :add-obj @@ -82,39 +72,31 @@ :frame-id frame-id :page-id page-id}] - (-> file + (-> state (commit-change change) (track-used-name (:name shape))))) -(defn- generate-name - [type data] - (if (= type :svg-raw) - (let [tag (dm/get-in data [:content :tag])] - (str "svg-" (cond (string? tag) tag - (keyword? tag) (d/name tag) - (nil? tag) "node" - :else (str tag)))) - (str/capital (d/name type)))) - (defn- unique-name - [name file] - (let [container-id (::current-page-id file) - unames (dm/get-in file [:unames container-id])] + [name state] + (let [container-id (::current-page-id state) + unames (dm/get-in state [:unames container-id])] (d/unique-name name (or unames #{})))) (defn- clear-names [file] (dissoc file ::unames)) -(defn- assign-name +(defn- assign-shape-name "Given a tag returns its layer name" - [data file type] - - (cond-> data - (nil? (:name data)) - (assoc :name (generate-name type data)) + [shape state] + (cond-> shape + (nil? (:name shape)) + (assoc :name (let [type (get shape :type)] + (case type + :frame "Board" + (str/capital (d/name type))))) :always - (update :name unique-name file))) + (update :name unique-name state))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; SCHEMAS @@ -135,16 +117,25 @@ (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 + [:map + [:component-id ::sm/uuid] + [:file-id {:optional true} ::sm/uuid] + [:name {:optional true} ::sm/text] + [:path {:optional true} ::sm/text]]) + +(def ^:private check-add-component + (sm/check-fn schema:add-component)) + +(def decode-add-component + (sm/decode-fn schema:add-component sm/json-transformer)) (def schema:add-component-instance [:map [:component-id ::sm/uuid] - [:x ::sm/safe-number] - [:y ::sm/safe-number]]) + [:file-id {:optional true} ::sm/uuid]]) -(def check-add-component-instance +(def ^:private check-add-component-instance (sm/check-fn schema:add-component-instance)) (def decode-add-component-instance @@ -158,37 +149,76 @@ (def decode-add-bool (sm/decode-fn schema:add-bool sm/json-transformer)) -(def check-add-bool +(def ^:private check-add-bool (sm/check-fn schema:add-bool)) +(def schema:add-file-media + [:map + [:id {:optional true} ::sm/uuid] + [:name ::sm/text] + [:width ::sm/int] + [:height ::sm/int]]) + +(def decode-add-file-media + (sm/decode-fn schema:add-file-media sm/json-transformer)) + +(def check-add-file-media + (sm/check-fn schema:add-file-media)) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; PUBLIC API ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defn lookup-shape [file shape-id] - (-> (lookup-objects file) - (get shape-id))) +(defn create-state + [] + {}) (defn get-current-page - [file] - (let [page-id (::current-page-id file)] - (dm/get-in file [:data :pages-index page-id]))) + [state] + (let [file-id (get state ::current-file-id) + page-id (get state ::current-page-id)] -(defn create-file - [params] + (assert (uuid? file-id) "expected current-file-id to be assigned") + (assert (uuid? page-id) "expected current-page-id to be assigned") + (dm/get-in state [::files file-id :data :pages-index page-id]))) + +(defn get-current-objects + [state] + (-> (get-current-page state) + (get :objects))) + +(defn get-shape + [state shape-id] + (-> (get-current-objects state) + (get shape-id))) + +(defn add-file + [state params] (let [params (-> params (assoc :features cfeat/default-features) - (assoc :migrations fmig/available-migrations))] - (types.file/make-file params :create-page false))) + (assoc :migrations fmig/available-migrations)) + file (types.file/make-file params :create-page false)] + (-> state + (update ::files assoc (:id file) file) + (assoc ::current-file-id (:id file))))) + +(declare close-page) + +(defn close-file + [state] + (let [state (-> state + (close-page) + (dissoc ::current-file-id))] + state)) (defn add-page - [file params] + [state params] (let [page (-> (types.page/make-empty-page params) (types.page/check-page)) change {:type :add-page :page page}] - (-> file + (-> state (commit-change change) ;; Current page being edited @@ -203,96 +233,96 @@ ;; Last object id added (assoc ::last-id nil)))) -(defn close-page [file] - (-> file +(defn close-page [state] + (-> state (dissoc ::current-page-id) (dissoc ::parent-stack) (dissoc ::last-id) (clear-names))) -(defn add-artboard - [file data] +(defn add-board + [state params] (let [{:keys [id] :as shape} - (-> data + (-> params (update :id default-uuid) (assoc :type :frame) - (assign-name file :frame) + (assign-shape-name state) (types.shape/setup-shape) (types.shape/check-shape))] - (-> file + (-> state (commit-shape shape) (update ::parent-stack conjv id) (assoc ::current-frame-id id) (assoc ::last-id id)))) -(defn close-artboard - [file] - (let [parent-id (-> file ::parent-stack peek) - parent (lookup-shape file parent-id)] - (-> file +(defn close-board + [state] + (let [parent-id (-> state ::parent-stack peek) + parent (get-shape state parent-id)] + (-> state (assoc ::current-frame-id (or (:frame-id parent) root-id)) (update ::parent-stack pop)))) (defn add-group - [file params] + [state params] (let [{:keys [id] :as shape} (-> params (update :id default-uuid) (assoc :type :group) - (assign-name file :group) + (assign-shape-name state) (types.shape/setup-shape) (types.shape/check-shape))] - (-> file + (-> state (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) + [state] + (let [group-id (-> state :parent-stack peek) + group (get-shape state group-id) children (->> (get group :shapes) - (into [] (keep (partial lookup-shape file))) + (into [] (keep (partial get-shape state))) (not-empty))] (assert (some? children) "group expect to have at least 1 children") - (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}]}] + (let [state (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 state 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}]}] - (commit-change file change :add-container true)))] - (update file ::parent-stack pop)))) + (commit-change state change :add-container true)))] + (update state ::parent-stack pop)))) (defn add-bool - [file params] + [state params] (let [{:keys [group-id type]} (check-add-bool params) group - (lookup-shape file group-id) + (get-shape state group-id) children (->> (get group :shapes) @@ -300,7 +330,7 @@ (assert (some? children) "expect group to have at least 1 element") - (let [objects (lookup-objects file) + (let [objects (get-current-objects state) bool (-> group (assoc :type :bool) (gsh/update-bool objects)) @@ -317,101 +347,102 @@ {:type :set :attr :width :val (-> bool :selrect :width) :ignore-touched true} {:type :set :attr :height :val (-> bool :selrect :height) :ignore-touched true}]}] - (-> file + (-> state (commit-change change :add-container true) (assoc ::last-id group-id))))) (defn add-shape - [file params] + [state params] (let [obj (-> params (d/update-when :svg-attrs csvg/attrs->props) (types.shape/setup-shape) - (assign-name file :type))] - (-> file + (assign-shape-name state))] + (-> state (commit-shape obj) (assoc ::last-id (:id obj))))) (defn add-library-color - [file color] + [state color] (let [color (-> color + (update :opacity d/nilv 1) (update :id default-uuid) (types.color/check-library-color color)) + change {:type :add-color :color color}] - (-> file + + (-> state (commit-change change) (assoc ::last-id (:id color))))) (defn add-library-typography - [file typography] + [state typography] (let [typography (-> typography (update :id default-uuid) (d/without-nils)) change {:type :add-typography :id (:id typography) :typography typography}] - (-> file + (-> state (commit-change change) (assoc ::last-id (:id typography))))) (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)} + [state params] + (let [{:keys [component-id file-id name path]} + (check-add-component 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 change1) - (commit-change change2) - (assoc ::last-id comp-id) - (assoc ::current-frame-id comp-id)))) - -(defn add-component-instance - [{:keys [id data] :as file} params] - - (let [{:keys [component-id x y]} - (check-add-component-instance params) - - component - (types.components-list/get-component data component-id) + frame-id + (get state ::current-frame-id) page-id - (get file ::current-page-id)] + (get state ::current-page-id) - (assert (uuid? page-id) "page-id is expected to be set") - (assert (uuid? component) "component is expected to exist") + component-id + (or component-id (uuid/next)) - ;; 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) + change1 + (d/without-nils + {:type :add-component + :id component-id + :name (or name "anonmous") + :path path + :main-instance-id frame-id + :main-instance-page page-id}) - [shape shapes] - (types.container/make-component-instance page component id pos) + change2 + {:type :mod-obj + :id frame-id + :operations + [{:type :set :attr :component-root :val true} + {:type :set :attr :component-id :val component-id} + {:type :set :attr :component-file :val file-id}]}] - 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)] + (-> state + (commit-change change1) + (commit-change change2)))) - (assoc file ::last-id (:id shape))))) +(defn add-component-instance + [state params] + + (let [{:keys [component-id file-id]} + (check-add-component-instance params) + + file-id + (or file-id (get state ::current-file-id)) + + frame-id + (get state ::current-frame-id) + + change + {:type :mod-obj + :id frame-id + :operations + [{:type :set :attr :component-root :val false} + {:type :set :attr :component-id :val component-id} + {:type :set :attr :component-file :val file-id}]}] + + (commit-change state change))) (defn delete-shape [file id] @@ -423,10 +454,12 @@ :id id})) (defn update-shape - [file shape-id f] - (let [page-id (::current-page-id file) - objects (lookup-objects file) + [state shape-id f] + (let [page-id (get state ::current-page-id) + + objects (get-current-objects state) old-shape (get objects shape-id) + new-shape (f old-shape) attrs (d/concat-set (keys old-shape) @@ -440,7 +473,7 @@ changes (conj changes {:type :set :attr attr :val new-val :ignore-touched true}))))] - (-> file + (-> state (commit-change {:type :mod-obj :operations (reduce generate-operation [] attrs) @@ -449,12 +482,12 @@ (assoc ::last-id shape-id)))) (defn add-guide - [file guide] + [state guide] (let [guide (cond-> guide (nil? (:id guide)) (assoc :id (uuid/next))) - page-id (::current-page-id file)] - (-> file + page-id (::current-page-id state)] + (-> state (commit-change {:type :set-guide :page-id page-id @@ -463,24 +496,54 @@ (assoc ::last-id (:id guide))))) (defn delete-guide - [file id] - - (let [page-id (::current-page-id file)] - (commit-change file + [state id] + (let [page-id (::current-page-id state)] + (commit-change state {:type :set-guide :page-id page-id :id id :params nil}))) (defn update-guide - [file guide] - (let [page-id (::current-page-id file)] - (commit-change file + [state guide] + (let [page-id (::current-page-id state)] + (commit-change state {:type :set-guide :page-id page-id :id (:id guide) :params guide}))) -(defn strip-image-extension [filename] - (let [image-extensions-re #"(\.png)|(\.jpg)|(\.jpeg)|(\.webp)|(\.gif)|(\.svg)$"] - (str/replace filename image-extensions-re ""))) +(defrecord BlobWrapper [mtype size blob]) + +(defn add-file-media + [state params blob] + (assert (instance? BlobWrapper blob) "expect blob to be wrapped") + + (let [media-id + (uuid/next) + + file-id + (get state ::current-file-id) + + {:keys [id width height name]} + (-> params + (update :id default-uuid) + (check-add-file-media params))] + + (-> state + (update ::blobs assoc media-id blob) + (update ::media assoc media-id + {:id media-id + :bucket "file-media-object" + :content-type (get blob :mtype) + :size (get blob :size)}) + (update ::file-media assoc id + {:id id + :name name + :width width + :height height + :file-id file-id + :media-id media-id + :mtype (get blob :mtype)}) + + (assoc ::last-id id)))) diff --git a/common/src/app/common/files/changes.cljc b/common/src/app/common/files/changes.cljc index 23dda4ac17..47409130fb 100644 --- a/common/src/app/common/files/changes.cljc +++ b/common/src/app/common/files/changes.cljc @@ -310,12 +310,12 @@ [:add-media [:map {:title "AddMediaChange"} [:type [:= :add-media]] - [:object ::ctf/media-object]]] + [:object ctf/schema:media]]] [:mod-media [:map {:title "ModMediaChange"} [:type [:= :mod-media]] - [:object ::ctf/media-object]]] + [:object ctf/schema:media]]] [:del-media [:map {:title "DelMediaChange"} diff --git a/common/src/app/common/media.cljc b/common/src/app/common/media.cljc index a342a227f0..b6f2311fb5 100644 --- a/common/src/app/common/media.cljc +++ b/common/src/app/common/media.cljc @@ -5,8 +5,8 @@ ;; Copyright (c) KALEIDOS INC (ns app.common.media + "Media assets helpers (images, fonts, etc)" (:require - [clojure.spec.alpha :as s] [cuerdas.core :as str])) ;; We have added ".ttf" as string to solve a problem with chrome input selector @@ -48,38 +48,28 @@ (defn mtype->extension [mtype] ;; https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types (case mtype - "image/apng" ".apng" - "image/avif" ".avif" - "image/gif" ".gif" - "image/jpeg" ".jpg" - "image/png" ".png" - "image/svg+xml" ".svg" - "image/webp" ".webp" - "application/zip" ".zip" - "application/penpot" ".penpot" - "application/pdf" ".pdf" - "text/plain" ".txt" + "image/apng" ".apng" + "image/avif" ".avif" + "image/gif" ".gif" + "image/jpeg" ".jpg" + "image/png" ".png" + "image/svg+xml" ".svg" + "image/webp" ".webp" + "application/zip" ".zip" + "application/penpot" ".penpot" + "application/pdf" ".pdf" + "text/plain" ".txt" + "font/woff" ".woff" + "font/woff2" ".woff2" + "font/ttf" ".ttf" + "font/otf" ".otf" + "application/octet-stream" ".bin" nil)) -(s/def ::id uuid?) -(s/def ::name string?) -(s/def ::width number?) -(s/def ::height number?) -(s/def ::created-at inst?) -(s/def ::modified-at inst?) -(s/def ::mtype string?) -(s/def ::uri string?) - -(s/def ::media-object - (s/keys :req-un [::id - ::name - ::width - ::height - ::mtype - ::created-at - ::modified-at - ::uri])) - +(defn strip-image-extension + [filename] + (let [image-extensions-re #"(\.png)|(\.jpg)|(\.jpeg)|(\.webp)|(\.gif)|(\.svg)$"] + (str/replace filename image-extensions-re ""))) (defn parse-font-weight [variant] diff --git a/common/src/app/common/types/color.cljc b/common/src/app/common/types/color.cljc index eb2018fad7..9fda10d36b 100644 --- a/common/src/app/common/types/color.cljc +++ b/common/src/app/common/types/color.cljc @@ -54,13 +54,13 @@ ::oapi/type "integer" ::oapi/format "int64"}})) -(def schema:image-color +(def schema:image [:map {:title "ImageColor"} - [:name {:optional true} :string] [:width ::sm/int] [:height ::sm/int] - [:mtype {:optional true} [:maybe :string]] + [:mtype ::sm/text] [:id ::sm/uuid] + [:name {:optional true} ::sm/text] [:keep-aspect-ratio {:optional true} :boolean]]) (def gradient-types @@ -93,7 +93,7 @@ [:ref-id {:optional true} ::sm/uuid] [:ref-file {:optional true} ::sm/uuid] [:gradient {:optional true} [:maybe schema:gradient]] - [:image {:optional true} [:maybe schema:image-color]] + [:image {:optional true} [:maybe schema:image]] [:plugin-data {:optional true} ::ctpg/plugin-data]]) (def schema:color @@ -106,7 +106,7 @@ [:opacity {:optional true} [:maybe ::sm/safe-number]] [:color {:optional true} [:maybe schema:rgb-color]] [:gradient {:optional true} [:maybe schema:gradient]] - [:image {:optional true} [:maybe schema:image-color]]] + [:image {:optional true} [:maybe schema:image]]] [::sm/contains-any {:strict true} [:color :gradient :image]]]) ;; Same as color but with :id prop required @@ -115,9 +115,10 @@ (sm/required-keys schema:color-attrs [:id]) [::sm/contains-any {:strict true} [:color :gradient :image]]]) +;; FIXME: revisit if we really need this all registers (sm/register! ::color schema:color) (sm/register! ::gradient schema:gradient) -(sm/register! ::image-color schema:image-color) +(sm/register! ::image-color schema:image) (sm/register! ::recent-color schema:recent-color) (sm/register! ::color-attrs schema:color-attrs) diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index e4ee9ceb9e..ca3a3e94f8 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -38,18 +38,18 @@ (def schema:media "A schema that represents the file media object" - [:map {:title "FileMediaObject"} + [:map {:title "FileMedia"} [:id ::sm/uuid] - [:created-at ::sm/inst] + [:created-at {:optional true} ::sm/inst] [:deleted-at {:optional true} ::sm/inst] [:name :string] [:width ::sm/safe-int] [:height ::sm/safe-int] [:mtype :string] - [:file-id {:optional true} ::sm/uuid] [:media-id ::sm/uuid] + [:file-id {:optional true} ::sm/uuid] [:thumbnail-id {:optional true} ::sm/uuid] - [:is-local :boolean]]) + [:is-local {:optional true} :boolean]]) (def schema:colors [:map-of {:gen/max 5} ::sm/uuid ::ctc/color]) @@ -102,7 +102,6 @@ (sm/register! ::media schema:media) (sm/register! ::colors schema:colors) (sm/register! ::typographies schema:typographies) -(sm/register! ::media-object schema:media) (def check-file (sm/check-fn schema:file :hint "check error on validating file")) @@ -110,7 +109,7 @@ (def check-file-data (sm/check-fn schema:data)) -(def check-media-object +(def check-file-media (sm/check-fn schema:media)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/frontend/src/app/main/data/exports/files.cljs b/frontend/src/app/main/data/exports/files.cljs index fd5bd28350..74001f1f9f 100644 --- a/frontend/src/app/main/data/exports/files.cljs +++ b/frontend/src/app/main/data/exports/files.cljs @@ -34,41 +34,35 @@ (defn export-files [files format] - (dm/assert! - "expected valid files param" - (check-export-files files)) + (assert (contains? valid-formats format) + "expected valid export format") - (dm/assert! - "expected valid format" - (contains? valid-formats format)) + (let [files (check-export-files files)] - (ptk/reify ::export-files - ptk/WatchEvent - (watch [_ state _] - (let [features (get state :features) - team-id (:current-team-id state) - evname (if (= format :legacy-zip) - "export-standard-files" - "export-binary-files")] - - (rx/merge - (rx/of (ptk/event ::ev/event {::ev/name evname - ::ev/origin "dashboard" - :format format - :num-files (count files)})) - (->> (rx/from files) - (rx/mapcat - (fn [file] - (->> (rp/cmd! :has-file-libraries {:file-id (:id file)}) - (rx/map #(assoc file :has-libraries %))))) - (rx/reduce conj []) - (rx/map (fn [files] - (modal/show - {:type ::export-files - :features features - :team-id team-id - :files files - :format format}))))))))) + (ptk/reify ::export-files + ptk/WatchEvent + (watch [_ state _] + (let [team-id (get state :current-team-id) + evname (if (= format :legacy-zip) + "export-standard-files" + "export-binary-files")] + (rx/merge + (rx/of (ptk/event ::ev/event {::ev/name evname + ::ev/origin "dashboard" + :format format + :num-files (count files)})) + (->> (rx/from files) + (rx/mapcat + (fn [file] + (->> (rp/cmd! :has-file-libraries {:file-id (:id file)}) + (rx/map #(assoc file :has-libraries %))))) + (rx/reduce conj []) + (rx/map (fn [files] + (modal/show + {:type ::export-files + :team-id team-id + :files files + :format format})))))))))) ;;;;;;;;;;;;;;;;;;;;;; ;; Team Request diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 67c9d18715..92a5f81a45 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -254,7 +254,7 @@ (defn add-media [media] - (let [media (ctf/check-media-object media)] + (let [media (ctf/check-file-media media)] (ptk/reify ::add-media ev/Event (-data [_] media) diff --git a/frontend/src/app/main/data/workspace/media.cljs b/frontend/src/app/main/data/workspace/media.cljs index 60c4ff25b0..57b3d491e3 100644 --- a/frontend/src/app/main/data/workspace/media.cljs +++ b/frontend/src/app/main/data/workspace/media.cljs @@ -10,11 +10,11 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.exceptions :as ex] - [app.common.files.builder :as fb] [app.common.files.changes-builder :as pcb] [app.common.files.shapes-builder :as sb] [app.common.logging :as log] [app.common.math :as mth] + [app.common.media :as media] [app.common.schema :as sm] [app.common.types.container :as ctn] [app.common.types.shape :as cts] @@ -137,7 +137,7 @@ (= (.-type blob) "image/svg+xml"))) (prepare-blob [blob] - (let [name (or name (if (dmm/file? blob) (fb/strip-image-extension (.-name blob)) "blob"))] + (let [name (or name (if (dmm/file? blob) (media/strip-image-extension (.-name blob)) "blob"))] {:file-id file-id :name name :is-local local? diff --git a/frontend/test/frontend_tests/util_snap_data_test.cljs b/frontend/test/frontend_tests/util_snap_data_test.cljs index f46fba7d6a..0ad546a672 100644 --- a/frontend/test/frontend_tests/util_snap_data_test.cljs +++ b/frontend/test/frontend_tests/util_snap_data_test.cljs @@ -27,7 +27,8 @@ (t/is (some? data)))) (t/testing "Add empty page (only root-frame)" - (let [page (-> (fb/create-file {:name "Test"}) + (let [page (-> (fb/create-state) + (fb/add-file {:name "Test"}) (fb/add-page {:name "Page 1"}) (fb/get-current-page)) @@ -36,18 +37,19 @@ (t/is (some? data)))) (t/testing "Create simple shape on root" - (let [file (-> (fb/create-file {:name "Test"}) - (fb/add-page {:name "Page 1"}) - (fb/add-shape - {:type :rect - :x 0 - :y 0 - :width 100 - :height 100})) - page (fb/get-current-page file) + (let [state (-> (fb/create-state) + (fb/add-file {:name "Test"}) + (fb/add-page {:name "Page 1"}) + (fb/add-shape + {:type :rect + :x 0 + :y 0 + :width 100 + :height 100})) + page (fb/get-current-page state) - data (-> (sd/make-snap-data) - (sd/add-page page)) + data (-> (sd/make-snap-data) + (sd/add-page page)) result-x (sd/query data (:id page) uuid/zero :x [0 100])] @@ -66,17 +68,18 @@ (t/is (= (first (nth result-x 2)) 100)))) (t/testing "Add page with single empty frame" - (let [file (-> (fb/create-file {:name "Test"}) - (fb/add-page {:name "Page 1"}) - (fb/add-artboard - {:x 0 - :y 0 - :width 100 - :height 100}) - (fb/close-artboard)) + (let [state (-> (fb/create-state) + (fb/add-file {:name "Test"}) + (fb/add-page {:name "Page 1"}) + (fb/add-board + {:x 0 + :y 0 + :width 100 + :height 100}) + (fb/close-board)) - frame-id (::fb/last-id file) - page (fb/get-current-page file) + frame-id (::fb/last-id state) + page (fb/get-current-page state) ;; frame-id (::fb/last-id file) data (-> (sd/make-snap-data) @@ -91,26 +94,27 @@ (t/testing "Add page with some shapes inside frames" (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})) + (let [state (-> (fb/create-state) + (fb/add-file {:name "Test"}) + (fb/add-page {:name "Page 1"}) + (fb/add-board + {:x 0 + :y 0 + :width 100 + :height 100})) - frame-id (::fb/last-id file) + frame-id (::fb/last-id state) - file (-> file - (fb/add-shape - {:type :rect - :x 25 - :y 25 - :width 50 - :height 50}) - (fb/close-artboard)) + state (-> state + (fb/add-shape + {:type :rect + :x 25 + :y 25 + :width 50 + :height 50}) + (fb/close-board)) - page (fb/get-current-page file) + page (fb/get-current-page state) data (-> (sd/make-snap-data) (sd/add-page page)) @@ -123,21 +127,21 @@ (t/is (= (count result-frame-x) 5))))) (t/testing "Add a global guide" - (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)) + (let [state (-> (fb/create-state) + (fb/add-file {:name "Test"}) + (fb/add-page {:name "Page 1"}) + (fb/add-guide {:position 50 :axis :x}) + (fb/add-board {:x 200 :y 200 :width 100 :height 100}) + (fb/close-board)) - frame-id (::fb/last-id file) - page (fb/get-current-page file) + frame-id (::fb/last-id state) + page (fb/get-current-page state) - ;; frame-id (::fb/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-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])] @@ -151,17 +155,18 @@ (t/is (= (count result-frame-y) 0)))) (t/testing "Add a frame guide" - (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)) + (let [state (-> (fb/create-state) + (fb/add-file {:name "Test"}) + (fb/add-page {:name "Page 1"}) + (fb/add-board {:x 200 :y 200 :width 100 :height 100}) + (fb/close-board)) - frame-id (::fb/last-id file) + frame-id (::fb/last-id state) - file (-> file - (fb/add-guide {:position 50 :axis :x :frame-id frame-id})) + state (-> state + (fb/add-guide {:position 50 :axis :x :frame-id frame-id})) - page (fb/get-current-page file) + page (fb/get-current-page state) data (-> (sd/make-snap-data) (sd/add-page page)) @@ -182,26 +187,26 @@ (t/deftest test-update-index (t/testing "Create frame on root and then remove it." - (let [file (-> (fb/create-file {:name "Test"}) - (fb/add-page {:name "Page 1"}) - (fb/add-artboard - {:x 0 - :y 0 - :width 100 - :height 100}) - (fb/close-artboard)) + (let [state (-> (fb/create-state) + (fb/add-file {:name "Test"}) + (fb/add-page {:name "Page 1"}) + (fb/add-board + {:x 0 + :y 0 + :width 100 + :height 100}) + (fb/close-board)) - shape-id (::fb/last-id file) - page (fb/get-current-page file) + shape-id (::fb/last-id state) + page (fb/get-current-page state) - ;; frame-id (::fb/last-id file) data (-> (sd/make-snap-data) (sd/add-page page)) - file (-> file - (fb/delete-shape shape-id)) + state (-> state + (fb/delete-shape shape-id)) - new-page (fb/get-current-page file) + new-page (fb/get-current-page state) data (sd/update-page data page new-page) result-x (sd/query data (:id page) uuid/zero :x [0 100]) @@ -212,25 +217,26 @@ (t/is (= (count result-y) 0)))) (t/testing "Create simple shape on root. Then remove it" - (let [file (-> (fb/create-file {:name "Test"}) - (fb/add-page {:name "Page 1"}) - (fb/add-shape - {:type :rect - :x 0 - :y 0 - :width 100 - :height 100})) + (let [state (-> (fb/create-state) + (fb/add-file {:name "Test"}) + (fb/add-page {:name "Page 1"}) + (fb/add-shape + {:type :rect + :x 0 + :y 0 + :width 100 + :height 100})) - shape-id (::fb/last-id file) - page (fb/get-current-page file) + shape-id (::fb/last-id state) + page (fb/get-current-page state) - ;; frame-id (::fb/last-id file) + ;; frame-id (::fb/last-id state) data (-> (sd/make-snap-data) (sd/add-page page)) - file (fb/delete-shape file shape-id) + state (fb/delete-shape state shape-id) - new-page (fb/get-current-page file) + new-page (fb/get-current-page state) data (sd/update-page data page new-page) result-x (sd/query data (:id page) uuid/zero :x [0 100]) @@ -241,30 +247,31 @@ (t/is (= (count result-y) 0)))) (t/testing "Create shape inside frame, then remove it" - (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 (::fb/last-id file) + (let [state (-> (fb/create-state) + (fb/add-file {:name "Test"}) + (fb/add-page {:name "Page 1"}) + (fb/add-board + {:x 0 + :y 0 + :width 100 + :height 100})) + frame-id (::fb/last-id state) - file (fb/add-shape file {:type :rect :x 25 :y 25 :width 50 :height 50}) - shape-id (::fb/last-id file) + state (fb/add-shape state {:type :rect :x 25 :y 25 :width 50 :height 50}) + shape-id (::fb/last-id state) - file (fb/close-artboard file) + state (fb/close-board state) - page (fb/get-current-page file) - data (-> (sd/make-snap-data) - (sd/add-page page)) + page (fb/get-current-page state) + data (-> (sd/make-snap-data) + (sd/add-page page)) - file (fb/delete-shape file shape-id) - new-page (fb/get-current-page file) + state (fb/delete-shape state shape-id) + new-page (fb/get-current-page state) - data (sd/update-page data page new-page) + data (sd/update-page data page new-page) - result-zero-x (sd/query data (:id page) uuid/zero :x [0 100]) + 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)) @@ -272,26 +279,28 @@ (t/is (= (count result-frame-x) 3)))) (t/testing "Create global guide then remove it" - (let [file (-> (fb/create-file {:name "Test"}) - (fb/add-page {:name "Page 1"}) - (fb/add-guide {:position 50 :axis :x})) + (let [state (-> (fb/create-state) + (fb/add-file {:name "Test"}) + (fb/add-page {:name "Page 1"}) + (fb/add-guide {:position 50 :axis :x})) - guide-id (::fb/last-id file) + guide-id (::fb/last-id state) - file (-> (fb/add-artboard file {:x 200 :y 200 :width 100 :height 100}) - (fb/close-artboard)) + state (-> (fb/add-board state {:x 200 :y 200 :width 100 :height 100}) + (fb/close-board)) - frame-id (::fb/last-id file) - page (fb/get-current-page file) - data (-> (sd/make-snap-data) (sd/add-page page)) + frame-id (::fb/last-id state) + page (fb/get-current-page state) + data (-> (sd/make-snap-data) + (sd/add-page page)) - new-page (-> (fb/delete-guide file guide-id) + new-page (-> (fb/delete-guide state guide-id) (fb/get-current-page)) - data (sd/update-page data page new-page) + data (sd/update-page data page new-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-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])] @@ -305,10 +314,11 @@ (t/is (= (count result-frame-y) 0)))) (t/testing "Create frame guide then remove it" - (let [file (-> (fb/create-file {:name "Test"}) + (let [file (-> (fb/create-state) + (fb/add-file {:name "Test"}) (fb/add-page {:name "Page 1"}) - (fb/add-artboard {:x 200 :y 200 :width 100 :height 100}) - (fb/close-artboard)) + (fb/add-board {:x 200 :y 200 :width 100 :height 100}) + (fb/close-board)) frame-id (::fb/last-id file) file (fb/add-guide file {:position 50 :axis :x :frame-id frame-id}) @@ -336,30 +346,31 @@ (t/is (= (count result-frame-y) 0)))) (t/testing "Update frame coordinates" - (let [file (-> (fb/create-file {:name "Test"}) - (fb/add-page {:name "Page 1"}) - (fb/add-artboard - {:x 0 - :y 0 - :width 100 - :height 100}) - (fb/close-artboard)) + (let [state (-> (fb/create-state) + (fb/add-file {:name "Test"}) + (fb/add-page {:name "Page 1"}) + (fb/add-board + {:x 0 + :y 0 + :width 100 + :height 100}) + (fb/close-board)) - frame-id (::fb/last-id file) - page (fb/get-current-page file) - data (-> (sd/make-snap-data) (sd/add-page page)) + frame-id (::fb/last-id state) + page (fb/get-current-page state) + data (-> (sd/make-snap-data) + (sd/add-page page)) - file (fb/update-shape file frame-id - (fn [shape] - (-> shape - (dissoc :selrect :points) - (assoc :x 200 :y 200) - (cts/setup-shape)))) + state (fb/update-shape state frame-id + (fn [shape] + (-> shape + (dissoc :selrect :points) + (assoc :x 200 :y 200) + (cts/setup-shape)))) - new-page (fb/get-current-page file) - - data (sd/update-page data page new-page) + new-page (fb/get-current-page state) + data (sd/update-page data page new-page) result-zero-x-1 (sd/query data (:id page) uuid/zero :x [0 100]) result-frame-x-1 (sd/query data (:id page) frame-id :x [0 100]) @@ -373,28 +384,29 @@ (t/is (= (count result-frame-x-2) 3)))) (t/testing "Update shape coordinates" - (let [file (-> (fb/create-file {:name "Test"}) - (fb/add-page {:name "Page 1"}) - (fb/add-shape - {:type :rect - :x 0 - :y 0 - :width 100 - :height 100})) + (let [state (-> (fb/create-state) + (fb/add-file {:name "Test"}) + (fb/add-page {:name "Page 1"}) + (fb/add-shape + {:type :rect + :x 0 + :y 0 + :width 100 + :height 100})) - shape-id (::fb/last-id file) - page (fb/get-current-page file) + shape-id (::fb/last-id state) + page (fb/get-current-page state) data (-> (sd/make-snap-data) (sd/add-page page)) - file (fb/update-shape file shape-id - (fn [shape] - (-> shape - (dissoc :selrect :points) - (assoc :x 200 :y 200) - (cts/setup-shape)))) + state (fb/update-shape state shape-id + (fn [shape] + (-> shape + (dissoc :selrect :points) + (assoc :x 200 :y 200) + (cts/setup-shape)))) - new-page (fb/get-current-page file) + new-page (fb/get-current-page state) ;; FIXME: update data (sd/update-page data page new-page) @@ -406,25 +418,26 @@ (t/is (= (count result-zero-x-2) 3)))) (t/testing "Update global guide" - (let [guide {:position 50 :axis :x} - file (-> (fb/create-file {:name "Test"}) - (fb/add-page {:name "Page 1"}) - (fb/add-guide guide)) + (let [guide {:position 50 :axis :x} + state (-> (fb/create-state) + (fb/add-file {:name "Test"}) + (fb/add-page {:name "Page 1"}) + (fb/add-guide guide)) - guide-id (::fb/last-id file) - guide (assoc guide :id guide-id) + guide-id (::fb/last-id state) + guide (assoc guide :id guide-id) - file (-> (fb/add-artboard file {:x 500 :y 500 :width 100 :height 100}) - (fb/close-artboard)) + state (-> (fb/add-board state {:x 500 :y 500 :width 100 :height 100}) + (fb/close-board)) - frame-id (::fb/last-id file) - page (fb/get-current-page file) + frame-id (::fb/last-id state) + page (fb/get-current-page state) data (-> (sd/make-snap-data) (sd/add-page page)) - new-page (-> (fb/update-guide file (assoc guide :position 150)) + new-page (-> (fb/update-guide state (assoc guide :position 150)) (fb/get-current-page)) - data (sd/update-page data page new-page) + data (sd/update-page data page new-page) result-zero-x-1 (sd/query data (:id page) uuid/zero :x [0 100]) result-zero-y-1 (sd/query data (:id page) uuid/zero :y [0 100]) diff --git a/library/package.json b/library/package.json index 21f9041937..f4664da6d1 100644 --- a/library/package.json +++ b/library/package.json @@ -33,7 +33,6 @@ "dependencies": { "@zip.js/zip.js": "patch:@zip.js/zip.js@npm%3A2.7.60#~/.yarn/patches/@zip.js-zip.js-npm-2.7.60-b6b814410b.patch", "luxon": "^3.6.1", - "sax": "^1.4.1", "source-map-support": "^0.5.21" } } diff --git a/library/playground/sample.jpg b/library/playground/sample.jpg new file mode 100644 index 0000000000..9476988529 Binary files /dev/null and b/library/playground/sample.jpg differ diff --git a/library/playground/sample1.js b/library/playground/sample1.js index f75d737693..db8b51016f 100644 --- a/library/playground/sample1.js +++ b/library/playground/sample1.js @@ -1,47 +1,87 @@ import * as penpot from "../target/library/penpot.js"; -import { writeFile } from 'fs/promises'; +import { writeFile, readFile } from 'fs/promises'; import { createWriteStream } from 'fs'; import { Writable } from "stream"; -console.log(penpot); +// console.log(penpot); (async function() { - 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}); + const context = penpot.createBuildContext(); { - let result = await penpot.exportAsBytes(file) + context.addFile({name: "Test File 1"}); + context.addPage({name: "Foo Page"}) + + // Add image media + const buffer = await readFile("./playground/sample.jpg"); + const blob = new Blob([buffer], { type: 'image/jpeg' }); + + const mediaId = context.addFileMedia({ + name: "avatar.jpg", + width: 512, + height: 512 + }, blob); + + // Add image color asset + const assetColorId = context.addLibraryColor({ + name: "Avatar", + opacity: 1, + image: { + ...context.getMediaAsImage(mediaId), + keepAspectRatio: true + } + }); + + const boardId = context.addBoard({ + name: "Foo Board", + x: 0, + y: 0, + width: 500, + height: 300, + }) + + const fill = { + fillColorRefId: assetColorId, + fillColorRefFile: context.currentFileId, + fillImage: { + ...context.getMediaAsImage(mediaId), + keepAspectRatio: true + } + }; + + context.addRect({ + name: "Rect 1", + x: 20, + y: 20, + width:100, + height:200, + fills: [fill] + }); + + context.closeBoard(); + context.closeFile(); + } + + { + let result = await penpot.exportAsBytes(context) await writeFile("sample-sync.zip", result); } - { - // Create a file stream to write the zip to - const output = createWriteStream('sample-stream.zip'); - - // Wrap Node's stream in a WHATWG WritableStream - const writable = Writable.toWeb(output); - - await penpot.exportStream(file, writable); - } + // { + // // Create a file stream to write the zip to + // const output = createWriteStream('sample-stream.zip'); + // // Wrap Node's stream in a WHATWG WritableStream + // const writable = Writable.toWeb(output); + // await penpot.exportStream(context, writable); + // } })().catch((cause) => { - console.log(cause); + console.error(cause); + + const innerCause = cause.cause; + if (innerCause) { + console.error("Inner cause:", innerCause); + } process.exit(-1); }).finally(() => { process.exit(0); diff --git a/library/shadow-cljs.edn b/library/shadow-cljs.edn index 862a53e39f..5eee3a6993 100644 --- a/library/shadow-cljs.edn +++ b/library/shadow-cljs.edn @@ -25,7 +25,7 @@ :modules {:penpot {:exports {BuilderError lib.builder/BuilderError - createFile lib.builder/create-file + createBuildContext lib.builder/create-build-context exportAsBytes lib.export/export-bytes exportAsBlob lib.export/export-blob exportStream lib.export/export-stream @@ -34,7 +34,7 @@ :js-options {:entry-keys ["module" "browser" "main"] :export-conditions ["module" "import", "browser" "require" "default"] - :js-provider :import + ;; :js-provider :import ;; :external-index "target/library/dependencies.js" ;; :external-index-format :esm } diff --git a/library/src/lib/builder.cljs b/library/src/lib/builder.cljs index d39469345a..4cd19dd639 100644 --- a/library/src/lib/builder.cljs +++ b/library/src/lib/builder.cljs @@ -55,201 +55,235 @@ (defn- decode-params [params] (if (obj/plain-object? params) - (json/->js params) + (json/->clj params) params)) -(defn- create-file-api - [file] - (let [state* (volatile! file) - api (obj/reify {:name "File"} - :id - {:get #(dm/str (:id @state*))} +(defn- get-current-page-id + [state] + (dm/str (get state ::fb/current-page-id))) - :currentFrameId - {:get #(dm/str (::fb/current-frame-id @state*))} +(defn- get-last-id + [state] + (dm/str (get state ::fb/last-id))) - :currentPageId - {:get #(dm/str (::fb/current-page-id @state*))} +(defn- create-builder-api + [state] + (obj/reify {:name "File"} + :currentFileId + {:get #(dm/str (get @state ::fb/current-file-id))} - :lastId - {:get #(dm/str (::fb/last-id @state*))} + :currentFrameId + {:get #(dm/str (get @state ::fb/current-frame-id))} - :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)))) + :currentPageId + {:get #(get-current-page-id @state)} - :closePage - (fn [] - (vswap! state* fb/close-page)) + :lastId + {:get #(get-last-id @state)} - :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)))) + :addFile + (fn [params] + (try + (let [params (-> params decode-params fb/decode-file)] + (-> (swap! state fb/add-file params) + (get ::fb/current-file-id))) + (catch :default cause + (handle-exception cause)))) - :closeArtboard - (fn [] - (vswap! state* fb/close-artboard)) + :closeFile + (fn [] + (swap! state fb/close-file) + nil) - :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)))) + :addPage + (fn [params] + (try + (let [params (-> (decode-params params) + (fb/decode-page))] - :closeGroup - (fn [] - (vswap! state* fb/close-group)) + (-> (swap! state fb/add-page params) + (get-current-page-id))) - :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)))) + (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)))) + :closePage + (fn [] + (swap! state fb/close-page) + nil) - :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)))) + :addBoard + (fn [params] + (try + (let [params (-> (decode-params params) + (assoc :type :frame) + (fb/decode-shape))] + (-> (swap! state fb/add-board params) + (get-last-id))) + (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)))) + :closeBoard + (fn [] + (swap! state fb/close-board) + nil) - :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)))) + :addGroup + (fn [params] + (try + (let [params (-> (decode-params params) + (assoc :type :group) + (fb/decode-shape))] + (-> (swap! state fb/add-group params) + (get-last-id))) + (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)))) + :closeGroup + (fn [] + (swap! state fb/close-group) + nil) - :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)))) + :addBool + (fn [params] + (try + (let [params (-> (decode-params params) + (fb/decode-add-bool))] + (-> (swap! state fb/add-bool params) + (get-last-id))) + (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)))) + :addRect + (fn [params] + (try + (let [params (-> (decode-params params) + (assoc :type :rect) + (fb/decode-shape))] + (-> (swap! state fb/add-shape params) + (get-last-id))) + (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)))) + :addCircle + (fn [params] + (try + (let [params (-> (decode-params params) + (assoc :type :circle) + (fb/decode-shape))] + (-> (swap! state fb/add-shape params) + (get-last-id))) + (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)))) + :addPath + (fn [params] + (try + (let [params (-> (decode-params params) + (assoc :type :path) + (fb/decode-shape))] + (-> (swap! state fb/add-shape params) + (get-last-id))) + (catch :default cause + (handle-exception cause)))) - :toMap - (fn [] - (-> @state* - (d/without-qualified) - (json/->js))))] + :addText + (fn [params] + (try + (let [params (-> (decode-params params) + (assoc :type :text) + (fb/decode-shape))] + (-> (swap! state fb/add-shape params) + (get-last-id))) + (catch :default cause + (handle-exception cause)))) + + :addLibraryColor + (fn [params] + (try + (let [params (-> (decode-params params) + (fb/decode-library-color) + (d/without-nils))] + (-> (swap! state fb/add-library-color params) + (get-last-id))) + (catch :default cause + (handle-exception cause)))) + + :addLibraryTypography + (fn [params] + (try + (let [params (-> (decode-params params) + (fb/decode-library-typography) + (d/without-nils))] + (-> (swap! state fb/add-library-typography params) + (get-last-id))) + (catch :default cause + (handle-exception cause)))) + + :addComponent + (fn [params] + (try + (let [params (-> (decode-params params) + (fb/decode-add-component))] + (-> (swap! state fb/add-component params) + (get-last-id))) + (catch :default cause + (handle-exception cause)))) + + :addComponentInstance + (fn [params] + (try + (let [params (-> (decode-params params) + (fb/decode-add-component-instance))] + (-> (swap! state fb/add-component-instance params) + (get-last-id))) + (catch :default cause + (handle-exception cause)))) + + :addFileMedia + (fn [params blob] + + (when-not (instance? js/Blob blob) + (throw (BuilderError. "validation" + "invalid-media" + "only Blob instance are soported"))) + (try + (let [blob (fb/map->BlobWrapper + {:size (.-size ^js blob) + :mtype (.-type ^js blob) + :blob blob}) + params + (-> (decode-params params) + (fb/decode-add-file-media))] + + (-> (swap! state fb/add-file-media params blob) + (get-last-id))) + + (catch :default cause + (handle-exception cause)))) + + :getMediaAsImage + (fn [id] + (let [id (uuid/parse id)] + (when-let [fmedia (get-in @state [::fb/file-media id])] + (let [image {:id (get fmedia :id) + :width (get fmedia :width) + :height (get fmedia :height) + :name (get fmedia :name) + :mtype (get fmedia :mtype)}] + (json/->js (d/without-nils image)))))) + + :genId + (fn [] + (dm/str (uuid/next))))) + + +(defn create-build-context + "Create an empty builder state context." + [] + (let [state (atom {}) + api (create-builder-api state)] (specify! api cljs.core/IDeref - (-deref [_] - (d/without-qualified @state*))))) - -(defn create-file - [params] - (try - (let [params (-> params json/->clj fb/decode-file) - file (fb/create-file params)] - (create-file-api file)) - (catch :default cause - (handle-exception cause)))) + (-deref [_] @state)))) diff --git a/library/src/lib/export.cljs b/library/src/lib/export.cljs index f777e14c60..f2b7fec266 100644 --- a/library/src/lib/export.cljs +++ b/library/src/lib/export.cljs @@ -8,12 +8,10 @@ "A .penpot export implementation" (: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.media :as media] [app.common.schema :as sm] - [app.common.uuid :as uuid] - [app.util.object :as obj] [app.common.types.color :as types.color] [app.common.types.component :as types.component] [app.common.types.file :as types.file] @@ -22,8 +20,8 @@ [app.common.types.shape :as types.shape] [app.common.types.tokens-lib :as types.tokens-lib] [app.common.types.typography :as types.typography] - [cuerdas.core :as str] [app.util.zip :as zip] + [cuerdas.core :as str] [promesa.core :as p])) (def ^:private schema:file @@ -43,9 +41,6 @@ (def ^:private encode-component (sm/encoder types.component/schema:component sm/json-transformer)) -;; (def encode-media -;; (sm/encoder ::ctf/media sm/json-transformer)) - (def encode-color (sm/encoder types.color/schema:color sm/json-transformer)) @@ -68,7 +63,6 @@ "file-data-fragment" "file-change"}) -;; FIXME: move to types (def ^:private schema:storage-object [:map {:title "StorageObject"} [:id ::sm/uuid] @@ -80,12 +74,6 @@ (def encode-storage-object (sm/encoder schema:storage-object sm/json-transformer)) -;; (def encode-file-thumbnail -;; (sm/encoder schema:file-thumbnail sm/json-transformer)) - - -;; FIXME: naming - (def ^:private file-attrs #{:id :name @@ -96,8 +84,6 @@ (defn- generate-file-export-procs [{:keys [id data] :as file}] - ;; (prn "generate-file-export-procs") - ;; (app.common.pprint/pprint file) (cons (let [file (cond-> (select-keys file file-attrs) (:options data) @@ -108,11 +94,11 @@ (concat (let [pages (get data :pages) pages-index (get data :pages-index)] + (->> (d/enumerate pages) (mapcat (fn [[index page-id]] - (let [path (str "files/" id "/pages/" page-id ".json") - page (get pages-index page-id) + (let [page (get pages-index page-id) objects (:objects page) page (-> page (dissoc :objects) @@ -155,45 +141,74 @@ (when-let [tokens-lib (get data :tokens-lib)] (list [(str "files/" id "/tokens.json") (delay (-> tokens-lib - (encode-tokens-lib tokens-lib) + encode-tokens-lib json/encode))]))))) -(defn generate-manifest-procs - [file] - (let [mdata {:id (:id file) - :name (:name file) - :features (:features file)} +(defn- generate-files-export-procs + [state] + (->> (vals (get state ::fb/files)) + (mapcat generate-file-export-procs))) + +(defn- generate-media-export-procs + [state] + (->> (get state ::fb/file-media) + (mapcat (fn [[file-media-id file-media]] + (let [media-id (get file-media :media-id) + media (get-in state [::fb/media media-id]) + blob (get-in state [::fb/blobs media-id])] + (list + [(str "objects/" media-id (media/mtype->extension (:content-type media))) + (delay (get blob :blob))] + + [(str "objects/" media-id ".json") + (delay (-> media + ;; FIXME: proper encode? + (json/encode)))] + [(str "files/" (:file-id file-media) "/media/" file-media-id ".json") + (delay (-> file-media + (dissoc :file-id) + (json/encode)))])))))) + +(defn- generate-manifest-procs + [state] + (let [files (->> (get state ::fb/files) + (mapv (fn [[file-id file]] + {:id file-id + :name (:name file) + :features (:features file)}))) params {:type "penpot/export-files" :version 1 + ;; FIXME: set proper placeholder for replacement on build :generated-by "penpot-lib/develop" - :files [mdata] + :files files :relations []}] - (list - ["manifest.json" (delay (json/encode params))]))) + ["manifest.json" (delay (json/encode params))])) (defn- export - [file writer] - (->> (p/reduce (fn [writer [path proc]] - (let [data (deref proc)] + [state writer] + (->> (p/reduce (fn [writer [path data]] + (let [data (if (delay? data) (deref data) data)] (js/console.log "export" path) (->> (zip/add writer path data) (p/fmap (constantly writer))))) writer - (concat - (generate-manifest-procs @file) - (generate-file-export-procs @file))) + (cons (generate-manifest-procs @state) + (concat + (generate-files-export-procs @state) + (generate-media-export-procs @state)))) + (p/mcat (fn [writer] (zip/close writer))))) (defn export-bytes - [file] - (export file (zip/writer (zip/bytes-writer)))) + [state] + (export state (zip/writer (zip/bytes-writer)))) (defn export-blob - [file] - (export file (zip/writer (zip/blob-writer)))) + [state] + (export state (zip/writer (zip/blob-writer)))) (defn export-stream - [file stream] - (export file (zip/writer stream))) + [state stream] + (export state (zip/writer stream))) diff --git a/library/yarn.lock b/library/yarn.lock index 67f82a9feb..44b445a74f 100644 --- a/library/yarn.lock +++ b/library/yarn.lock @@ -59,7 +59,6 @@ __metadata: concurrently: "npm:^9.1.2" luxon: "npm:^3.6.1" nodemon: "npm:^3.1.9" - sax: "npm:^1.4.1" shadow-cljs: "npm:3.0.5" source-map-support: "npm:^0.5.21" languageName: unknown @@ -73,11 +72,11 @@ __metadata: linkType: hard "@types/node@npm:^22.12.0": - version: 22.15.17 - resolution: "@types/node@npm:22.15.17" + version: 22.15.18 + resolution: "@types/node@npm:22.15.18" dependencies: undici-types: "npm:~6.21.0" - checksum: 10c0/fb92aa10b628683c5b965749f955bc2322485ecb0ea6c2f4cae5f2c7537a16834607e67083a9e9281faaae8d7dee9ada8d6a5c0de9a52c17d82912ef00c0fdd4 + checksum: 10c0/e23178c568e2dc6b93b6aa3b8dfb45f9556e527918c947fe7406a4c92d2184c7396558912400c3b1b8d0fa952ec63819aca2b8e4d3545455fc6f1e9623e09ca6 languageName: node linkType: hard @@ -318,14 +317,14 @@ __metadata: linkType: hard "debug@npm:4, debug@npm:^4, debug@npm:^4.3.4": - version: 4.4.0 - resolution: "debug@npm:4.4.0" + version: 4.4.1 + resolution: "debug@npm:4.4.1" dependencies: ms: "npm:^2.1.3" peerDependenciesMeta: supports-color: optional: true - checksum: 10c0/db94f1a182bf886f57b4755f85b3a74c39b5114b9377b7ab375dc2cfa3454f09490cc6c30f829df3fc8042bc8b8995f6567ce5cd96f3bc3688bd24027197d9de + checksum: 10c0/d2b44bc1afd912b49bb7ebb0d50a860dc93a4dd7d946e8de94abc957bb63726b7dd5aa48c18c2386c379ec024c46692e15ed3ed97d481729f929201e671fcd55 languageName: node linkType: hard @@ -962,13 +961,6 @@ __metadata: languageName: node linkType: hard -"sax@npm:^1.4.1": - version: 1.4.1 - resolution: "sax@npm:1.4.1" - checksum: 10c0/6bf86318a254c5d898ede6bd3ded15daf68ae08a5495a2739564eb265cd13bcc64a07ab466fb204f67ce472bb534eb8612dac587435515169593f4fffa11de7c - languageName: node - linkType: hard - "semver@npm:^7.3.5, semver@npm:^7.5.3": version: 7.7.2 resolution: "semver@npm:7.7.2" diff --git a/package.json b/package.json index 1ef10821c7..2c2b65f982 100644 --- a/package.json +++ b/package.json @@ -11,13 +11,14 @@ }, "type": "module", "scripts": { - "fmt:clj:check": "cljfmt check --parallel=true common/src/ common/test/ frontend/src/ frontend/test/ backend/src/ backend/test/ exporter/src/", - "fmt:clj": "cljfmt fix --parallel=true common/src/ common/test/ frontend/src/ frontend/test/ backend/src/ backend/test/ exporter/src/", + "fmt:clj:check": "cljfmt check --parallel=true common/src/ common/test/ frontend/src/ frontend/test/ backend/src/ backend/test/ exporter/src/ library/src", + "fmt:clj": "cljfmt fix --parallel=true common/src/ common/test/ frontend/src/ frontend/test/ backend/src/ backend/test/ exporter/src/ library/src", "lint:clj:common": "clj-kondo --parallel=true --lint common/src", "lint:clj:frontend": "clj-kondo --parallel=true --lint frontend/src", "lint:clj:backend": "clj-kondo --parallel=true --lint backend/src", "lint:clj:exporter": "clj-kondo --parallel=true --lint exporter/src", - "lint:clj": "yarn run lint:clj:common && yarn run lint:clj:frontend && yarn run lint:clj:backend && yarn run lint:clj:exporter" + "lint:clj:library": "clj-kondo --parallel=true --lint library/src", + "lint:clj": "yarn run lint:clj:common && yarn run lint:clj:frontend && yarn run lint:clj:backend && yarn run lint:clj:exporter && yarn run lint:clj:library" }, "devDependencies": { "@playwright/test": "^1.43.1",