diff --git a/common/src/app/common/file_builder.cljc b/common/src/app/common/file_builder.cljc index 64ae5dff46..c2cd12a07b 100644 --- a/common/src/app/common/file_builder.cljc +++ b/common/src/app/common/file_builder.cljc @@ -87,15 +87,15 @@ :changes []}))) (defn add-page - [file name] - (let [page-id (uuid/next)] + [file data] + (let [page-id (uuid/next) + page (-> init/empty-page-data + (assoc :id page-id) + (d/deep-merge data))] (-> file (commit-change {:type :add-page - :id page-id - :name name - :page (-> init/empty-page-data - (assoc :name name))}) + :page page}) ;; Current page being edited (assoc :current-page-id page-id) diff --git a/frontend/src/app/libs/file_builder.cljs b/frontend/src/app/libs/file_builder.cljs index 4f1c6a207c..8d64cbb3a8 100644 --- a/frontend/src/app/libs/file_builder.cljs +++ b/frontend/src/app/libs/file_builder.cljs @@ -7,14 +7,72 @@ (ns app.libs.file-builder (:require [app.common.data :as d] - [app.common.file-builder :as fb])) + [app.common.file-builder :as fb] + [cuerdas.core :as str])) + +(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])) $))) (deftype File [^:mutable file] Object - (addPage [self name] - (set! file (fb/add-page file name)) - (str (:current-page-id file)))) + (addPage [self name] + (set! file (fb/add-page file {:name name})) + (str (:current-page-id file))) + + (addPage [self name options] + (set! file (fb/add-page file {:name name :options options})) + (str (:current-page-id file))) + + (closePage [self] + (set! file (fb/close-page file))) + + (addArtboard [self data] + (set! file (fb/add-artboard file (parse-data data))) + (str (:last-id file))) + + (closeArtboard [self data] + (set! file (fb/close-artboard file))) + + (addGroup [self data] + (set! file (fb/add-group file (parse-data data))) + (str (:last-id file))) + + (closeGroup [self] + (set! file (fb/close-group file))) + + (createRect [self data] + (set! file (fb/create-rect file (parse-data data)))) + + (createCircle [self data] + (set! file (fb/create-circle file (parse-data data)))) + + (createPath [self data] + (set! file (fb/create-path file (parse-data data)))) + + (createText [self data] + (set! file (fb/create-text file (parse-data data)))) + + (createImage [self data] + (set! file (fb/create-image file (parse-data data)))) + + (createSVG [self data] + (set! file (fb/create-svg-raw file (parse-data data)))) + + (closeSVG [self] + (set! file (fb/close-svg-raw file))) + + (asMap [self] + (clj->js file))) (defn create-file-export [^string name] (File. (fb/create-file name))) diff --git a/frontend/src/app/main/exports.cljs b/frontend/src/app/main/exports.cljs index 2ee9d6db72..cb5a327d54 100644 --- a/frontend/src/app/main/exports.cljs +++ b/frontend/src/app/main/exports.cljs @@ -16,6 +16,7 @@ [app.common.uuid :as uuid] [app.main.ui.shapes.circle :as circle] [app.main.ui.shapes.embed :as embed] + [app.main.ui.shapes.export :as use] [app.main.ui.shapes.filters :as filters] [app.main.ui.shapes.frame :as frame] [app.main.ui.shapes.group :as group] @@ -158,6 +159,9 @@ :style {:width "100%" :height "100%" :background background-color}} + + [:& use/export-page {:options (:options data)}] + (for [item shapes] (let [frame? (= (:type item) :frame)] (cond diff --git a/frontend/src/app/main/ui/components/file_uploader.cljs b/frontend/src/app/main/ui/components/file_uploader.cljs index 54898407a7..1151976143 100644 --- a/frontend/src/app/main/ui/components/file_uploader.cljs +++ b/frontend/src/app/main/ui/components/file_uploader.cljs @@ -12,17 +12,21 @@ [app.util.dom :as dom])) (mf/defc file-uploader - [{:keys [accept multi label-text label-class input-id input-ref on-selected] :as props}] + {::mf/forward-ref true} + [{:keys [accept multi label-text label-class input-id on-selected] :as props} input-ref] (let [opt-pick-one #(if multi % (first %)) - on-files-selected (fn [event] - (let [target (dom/get-target event)] - (st/emit! - (some-> target - (dom/get-files) - (opt-pick-one) - (on-selected))) - (dom/clean-value! target)))] + on-files-selected + (mf/use-callback + (mf/deps opt-pick-one) + (fn [event] + (let [target (dom/get-target event)] + (st/emit! + (some-> target + (dom/get-files) + (opt-pick-one) + (on-selected))) + (dom/clean-value! target))))] [:* (when label-text [:label {:for input-id :class-name label-class} label-text]) diff --git a/frontend/src/app/main/ui/dashboard/file_menu.cljs b/frontend/src/app/main/ui/dashboard/file_menu.cljs index 6dcedd52ab..5de3b72a2e 100644 --- a/frontend/src/app/main/ui/dashboard/file_menu.cljs +++ b/frontend/src/app/main/ui/dashboard/file_menu.cljs @@ -14,6 +14,7 @@ [app.main.ui.components.context-menu :refer [context-menu]] [app.main.ui.context :as ctx] [app.main.worker :as uw] + [app.util.debug :as d] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] [app.util.router :as rt] @@ -192,7 +193,8 @@ [[(tr "dashboard.duplicate-multi" file-count) on-duplicate] (when (or (seq current-projects) (seq other-teams)) [(tr "dashboard.move-to-multi" file-count) nil sub-options]) - #_[(tr "dashboard.export-multi" file-count) on-export-files] + (when (d/debug? :export) + [(tr "dashboard.export-multi" file-count) on-export-files]) [:separator] [(tr "labels.delete-multi-files" file-count) on-delete]] @@ -204,7 +206,8 @@ (if (:is-shared file) [(tr "dashboard.remove-shared") on-del-shared] [(tr "dashboard.add-shared") on-add-shared]) - #_[(tr "dashboard.export-single") on-export-files] + (when (d/debug? :export) + [(tr "dashboard.export-single") on-export-files]) [:separator] [(tr "labels.delete") on-delete]])] diff --git a/frontend/src/app/main/ui/dashboard/fonts.cljs b/frontend/src/app/main/ui/dashboard/fonts.cljs index bda48caadf..9363698bf4 100644 --- a/frontend/src/app/main/ui/dashboard/fonts.cljs +++ b/frontend/src/app/main/ui/dashboard/fonts.cljs @@ -144,7 +144,7 @@ [:& file-uploader {:input-id "font-upload" :accept cm/str-font-types :multi true - :input-ref input-ref + :ref input-ref :on-selected on-selected}]]] [:* diff --git a/frontend/src/app/main/ui/dashboard/import.cljs b/frontend/src/app/main/ui/dashboard/import.cljs index c30e48f03f..dcec18c73a 100644 --- a/frontend/src/app/main/ui/dashboard/import.cljs +++ b/frontend/src/app/main/ui/dashboard/import.cljs @@ -39,17 +39,15 @@ (log/debug :action "import-end") (when on-finish-import (on-finish-import)))))))))) -(mf/defc import-button - [{:keys [project-id on-finish-import]}] +(mf/defc import-form + {::mf/forward-ref true} + [{:keys [project-id on-finish-import]} external-ref] - (let [file-input (mf/use-ref nil) - on-file-selected (use-import-file project-id on-finish-import)] + (let [on-file-selected (use-import-file project-id on-finish-import)] [:form.import-file - [:button.import-file-btn {:type "button" - :on-click #(dom/click (mf/ref-val file-input))} i/import] [:& file-uploader {:accept "application/zip" :multi true - :input-ref file-input + :ref external-ref :on-selected on-file-selected}]])) diff --git a/frontend/src/app/main/ui/dashboard/project_menu.cljs b/frontend/src/app/main/ui/dashboard/project_menu.cljs index 8ea8b4132b..68c166a326 100644 --- a/frontend/src/app/main/ui/dashboard/project_menu.cljs +++ b/frontend/src/app/main/ui/dashboard/project_menu.cljs @@ -14,6 +14,9 @@ [app.main.store :as st] [app.main.ui.components.context-menu :refer [context-menu]] [app.main.ui.context :as ctx] + [app.main.ui.dashboard.import :as udi] + [app.util.debug :as d] + [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] [app.util.router :as rt] [beicon.core :as rx] @@ -71,22 +74,43 @@ :title (tr "modals.delete-project-confirm.title") :message (tr "modals.delete-project-confirm.message") :accept-label (tr "modals.delete-project-confirm.accept") - :on-accept delete-fn}))] + :on-accept delete-fn})) - [:& context-menu {:on-close on-menu-close - :show show? - :fixed? (or (not= top 0) (not= left 0)) - :min-width? true - :top top - :left left - :options [(when-not (:is-default project) - [(tr "labels.rename") on-edit]) - [(tr "dashboard.duplicate") on-duplicate] - [(tr "dashboard.pin-unpin") toggle-pin] - (when (seq teams) - [(tr "dashboard.move-to") nil - (for [team teams] - [(:name team) (on-move (:id team))])]) - [:separator] - [(tr "labels.delete") on-delete]]}])) + + file-input (mf/use-ref nil) + + on-import-files + (mf/use-callback + (fn [] + (dom/click (mf/ref-val file-input)))) + + on-finish-import + (mf/use-callback + (fn [] + (st/emit! (dd/fetch-recent-files) + (dd/clear-selected-files))))] + + [:* + [:& udi/import-form {:ref file-input + :project-id (:id project) + :on-finish-import on-finish-import}] + [:& context-menu + {:on-close on-menu-close + :show show? + :fixed? (or (not= top 0) (not= left 0)) + :min-width? true + :top top + :left left + :options [(when-not (:is-default project) + [(tr "labels.rename") on-edit]) + [(tr "dashboard.duplicate") on-duplicate] + [(tr "dashboard.pin-unpin") toggle-pin] + (when (seq teams) + [(tr "dashboard.move-to") nil + (for [team teams] + [(:name team) (on-move (:id team))])]) + (when (d/debug? :import) + [(tr "dashboard.import") on-import-files]) + [:separator] + [(tr "labels.delete") on-delete]]}]])) diff --git a/frontend/src/app/main/ui/dashboard/projects.cljs b/frontend/src/app/main/ui/dashboard/projects.cljs index 8e6485b379..262fcaac62 100644 --- a/frontend/src/app/main/ui/dashboard/projects.cljs +++ b/frontend/src/app/main/ui/dashboard/projects.cljs @@ -21,8 +21,7 @@ [app.util.router :as rt] [app.util.time :as dt] [okulary.core :as l] - [rumext.alpha :as mf] - [app.main.ui.dashboard.import :refer [import-button]])) + [rumext.alpha :as mf])) (mf/defc header {::mf/wrap [mf/memo]} @@ -98,13 +97,7 @@ (fn [] (let [mdata {:on-success on-file-created} params {:project-id (:id project)}] - (st/emit! (dd/create-file (with-meta params mdata)))))) - - on-finish-import - (mf/use-callback - (fn [] - (st/emit! (dd/fetch-recent-files) - (dd/clear-selected-files))))] + (st/emit! (dd/create-file (with-meta params mdata))))))] [:div.dashboard-project-row {:class (when first? "first")} [:div.project @@ -117,13 +110,12 @@ (tr "labels.drafts") (:name project))]) - (when (:menu-open @local) - [:& project-menu {:project project - :show? (:menu-open @local) - :left (:x (:menu-pos @local)) - :top (:y (:menu-pos @local)) - :on-edit on-edit-open - :on-menu-close on-menu-close}]) + [:& project-menu {:project project + :show? (:menu-open @local) + :left (:x (:menu-pos @local)) + :top (:y (:menu-pos @local)) + :on-edit on-edit-open + :on-menu-close on-menu-close}] [:span.info (str file-count " files")] (when (> file-count 0) @@ -131,9 +123,6 @@ (dt/timeago {:locale locale}))] [:span.recent-files-row-title-info (str ", " time)])) - #_[:& import-button {:project-id (:id project) - :on-finish-import on-finish-import}] - (when-not (:is-default project) [:span.pin-icon.tooltip.tooltip-bottom {:class (when (:is-pinned project) "active") diff --git a/frontend/src/app/main/ui/dashboard/team.cljs b/frontend/src/app/main/ui/dashboard/team.cljs index 168a944c25..4eb028314c 100644 --- a/frontend/src/app/main/ui/dashboard/team.cljs +++ b/frontend/src/app/main/ui/dashboard/team.cljs @@ -285,7 +285,7 @@ [:img {:src (cfg/resolve-team-photo-url team)}] [:& file-uploader {:accept "image/jpeg,image/png" :multi false - :input-ref finput + :ref finput :on-selected on-file-selected}]]] [:div.block.owner-block diff --git a/frontend/src/app/main/ui/settings/profile.cljs b/frontend/src/app/main/ui/settings/profile.cljs index 2ed0cb1829..03a6ddda28 100644 --- a/frontend/src/app/main/ui/settings/profile.cljs +++ b/frontend/src/app/main/ui/settings/profile.cljs @@ -96,7 +96,7 @@ [:img {:src photo}] [:& file-uploader {:accept "image/jpeg,image/png" :multi false - :input-ref file-input + :ref file-input :on-selected on-file-selected}]]])) ;; --- Profile Page diff --git a/frontend/src/app/main/ui/shapes/export.cljs b/frontend/src/app/main/ui/shapes/export.cljs index 6cda48b11c..a74740040c 100644 --- a/frontend/src/app/main/ui/shapes/export.cljs +++ b/frontend/src/app/main/ui/shapes/export.cljs @@ -70,6 +70,11 @@ (obj/set! "penpot:center-x" (-> center :x str)) (obj/set! "penpot:center-y" (-> center :y str)) + ;; Constraints + (add! :constraints-h) + (add! :constraints-v) + (add! :fixed-scroll) + (cond-> (and rect? (some? (:r1 shape))) (-> (add! :r1) (add! :r2) @@ -83,6 +88,37 @@ (cond-> mask? (obj/set! "penpot:masked-group" "true")))))) +(defn prefix-keys [m] + (letfn [(prefix-entry [[k v]] + [(str "penpot:" (d/name k)) v])] + (into {} (map prefix-entry) m))) + +(mf/defc export-grid-data + [{:keys [grids]}] + [:> "penpot:grids" #js {} + (for [{:keys [type display params]} grids] + (let [props (->> (d/without-keys params [:color]) + (prefix-keys) + (clj->js))] + [:> "penpot:grid" + (-> props + (obj/set! "penpot:color" (get-in params [:color :color])) + (obj/set! "penpot:opacity" (get-in params [:color :opacity])) + (obj/set! "penpot:type" (d/name type)) + (cond-> (some? display) + (obj/set! "penpot:display" (str display))))]))]) + +(mf/defc export-page + [{:keys [options]}] + (let [saved-grids (get options :saved-grids)] + (when-not (empty? saved-grids) + (let [parse-grid + (fn [[type params]] + {:type type :params params}) + grids (->> saved-grids (mapv parse-grid))] + [:> "penpot:page" #js {} + [:& export-grid-data {:grids grids}]])))) + (mf/defc export-data [{:keys [shape]}] (let [props (-> (obj/new) @@ -135,5 +171,10 @@ (clj->js))))] [:> "penpot:svg-content" props (for [leaf (->> shape :content :content (filter string?))] - [:> "penpot:svg-child" {} leaf])]))])) + [:> "penpot:svg-child" {} leaf])])) + + + (when (and (= (:type shape) :frame) + (not (empty? (:grids shape)))) + [:& export-grid-data {:grids (:grids shape)}])])) diff --git a/frontend/src/app/main/ui/workspace/left_toolbar.cljs b/frontend/src/app/main/ui/workspace/left_toolbar.cljs index 1692a392f6..f7ed31957b 100644 --- a/frontend/src/app/main/ui/workspace/left_toolbar.cljs +++ b/frontend/src/app/main/ui/workspace/left_toolbar.cljs @@ -52,7 +52,7 @@ [:& file-uploader {:input-id "image-upload" :accept cm/str-image-types :multi true - :input-ref ref + :ref ref :on-selected on-files-selected}]]])) (mf/defc left-toolbar diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index 26fd4d30b9..2935531008 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -743,7 +743,7 @@ i/plus [:& file-uploader {:accept cm/str-image-types :multi true - :input-ref input-ref + :ref input-ref :on-selected on-file-selected}]]]) [:& asset-section-block {:role :content} diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 8ead981ca2..6a79f9c1e1 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -14,6 +14,7 @@ [app.main.ui.context :as muc] [app.main.ui.measurements :as msr] [app.main.ui.shapes.embed :as embed] + [app.main.ui.shapes.export :as use] [app.main.ui.workspace.shapes :as shapes] [app.main.ui.workspace.shapes.text.editor :as editor] [app.main.ui.workspace.viewport.actions :as actions] @@ -188,6 +189,8 @@ :style {:background-color (get options :background "#E8E9EA") :pointer-events "none"}} + [:& use/export-page {:options options}] + [:& (mf/provider embed/context) {:value true} ;; Render root shape [:& shapes/root-shape {:key page-id diff --git a/frontend/src/app/util/debug.cljs b/frontend/src/app/util/debug.cljs index d83a3e0543..cfffaf3f33 100644 --- a/frontend/src/app/util/debug.cljs +++ b/frontend/src/app/util/debug.cljs @@ -6,7 +6,7 @@ [app.common.math :as mth] [cljs.pprint :refer [pprint]])) -(def debug-options #{:bounding-boxes :group :events :rotation-handler :resize-handler :selection-center #_:simple-selection}) +(def debug-options #{:bounding-boxes :group :events :rotation-handler :resize-handler :selection-center :export :import #_:simple-selection}) ;; These events are excluded when we activate the :events flag (def debug-exclude-events diff --git a/frontend/src/app/util/import/parser.cljc b/frontend/src/app/util/import/parser.cljc index 97f8113214..ab5fc82db3 100644 --- a/frontend/src/app/util/import/parser.cljc +++ b/frontend/src/app/util/import/parser.cljc @@ -30,13 +30,28 @@ (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) + (->> node :content (filterv #(= (:tag %) :defs))))) + (defn get-data ([node] - (->> node :content (d/seek #(= :penpot:shape (:tag %))))) - ([node tag] - (->> (get-data node) - :content - (d/seek #(= tag (:tag %)))))) + (or (find-node node :penpot:shape) + (find-node node :penpot:page))) + + ([node tag] + (-> (get-data node) + (find-node tag)))) (defn get-type [node] @@ -98,6 +113,41 @@ m attrs)) +(defn without-penpot-prefix + [m] + (let [no-penpot-prefix? + (fn [[k v]] + (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))) + (def search-data-node? #{:rect :image :path :text :circle}) (defn get-svg-data @@ -148,6 +198,7 @@ selrect (gsh/content->selrect content-tr) points (-> (gsh/rect->points selrect) (gsh/transform-points center transform))] + (-> props (assoc :content content) (assoc :selrect selrect) @@ -166,9 +217,6 @@ (def url-regex #"url\(#([^\)]*)\)") -(defn seek-node - [id coll] - (->> coll (d/seek #(= id (-> % :attrs :id))))) (defn parse-stops [gradient-node] @@ -183,7 +231,7 @@ (defn parse-gradient [node ref-url] (let [[_ url] (re-find url-regex ref-url) - gradient-node (->> node (node-seq) (seek-node url)) + gradient-node (->> node (node-seq) (find-node-by-id url)) stops (parse-stops gradient-node)] (when (contains? (:attrs gradient-node) :penpot:gradient) @@ -231,19 +279,35 @@ 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)] + 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 :transform transform) - (assoc :transform-inverse transform-inverse) (assoc :flip-x flip-x) (assoc :flip-y flip-y) (assoc :proportion proportion) (assoc :proportion-lock proportion-lock) - (assoc :rotation rotation)))) + (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] @@ -330,12 +394,17 @@ (assoc :rx rx :ry ry)))) (defn add-image-data - [props node] - (-> props - (assoc-in [:metadata :id] (get-meta node :media-id)) - (assoc-in [:metadata :width] (get-meta node :media-width)) - (assoc-in [:metadata :height] (get-meta node :media-height)) - (assoc-in [:metadata :mtype] (get-meta node :media-mtype)))) + [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)}] + (cond-> props + (= type :image) + (assoc :metadata metadata) + + (not= type :image) + (assoc :fill-image metadata)))) (defn add-text-data [props node] @@ -372,6 +441,26 @@ :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 (-> (d/without-keys 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 [grid-node (get-data node :penpot:grids)] + (->> grid-node :content (mapv parse-grid-node)))) + (defn extract-from-data ([node tag] (extract-from-data node tag identity)) @@ -477,32 +566,6 @@ props))) -(defn without-penpot-prefix - [m] - (let [no-penpot-prefix? - (fn [[k v]] - (not (str/starts-with? (d/name k) "penpot:")))] - (into {} (filter no-penpot-prefix?) 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 add-svg-content [props node] (let [svg-content (get-data node :penpot:svg-content) @@ -522,13 +585,36 @@ :tag tag :content node-content}))) +(defn add-frame-data [props node] + (let [grids (parse-grids node)] + (cond-> props + (not (empty? grids)) + (assoc :grids grids)))) + +(defn has-image? + [type node] + (let [pattern-image + (-> node + (find-node :defs) + (find-node :pattern) + (find-node :image))] + (or (= type :image) + (some? pattern-image)))) + (defn get-image-name [node] (get-in node [:attrs :penpot:name])) (defn get-image-data [node] - (let [svg-data (get-svg-data :image node)] + (let [pattern-data + (-> node + (find-node :defs) + (find-node :pattern) + (find-node :image) + :attrs) + image-data (get-svg-data :image node) + svg-data (or image-data pattern-data)] (:xlink:href svg-data))) (defn parse-data @@ -550,14 +636,31 @@ (cond-> (= :svg-raw type) (add-svg-content node)) + (cond-> (= :frame type) + (add-frame-data node)) + (cond-> (= :group type) (add-group-data node)) (cond-> (= :rect type) (add-rect-data node svg-data)) - (cond-> (= :image type) - (add-image-data node)) + (cond-> (some? (get-in node [:attrs :penpot:media-id])) + (add-image-data type node)) (cond-> (= :text type) (add-text-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 [k v] (-> v first :params))))] + (cond-> {} + (some? background) + (assoc-in [:options :background] background) + + (not (empty? grids)) + (assoc-in [:options :saved-grids] grids)))) diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs index 6a1c338a9c..48fe14a153 100644 --- a/frontend/src/app/worker/import.cljs +++ b/frontend/src/app/worker/import.cljs @@ -117,7 +117,8 @@ (defn resolve-images [file-id node] - (if (and (cip/shape? node) (= (cip/get-type node) :image) (not (cip/close? node))) + (if (and (not (cip/close? node)) + (cip/has-image? type node)) (let [name (cip/get-image-name node) data-uri (cip/get-image-data node)] (->> (upload-media-files file-id name data-uri) @@ -137,11 +138,13 @@ [file [page-name content]] (if (cip/valid? content) (let [nodes (->> content cip/node-seq) - file-id (:id file)] + file-id (:id file) + page-data (-> (cip/parse-page-data content) + (assoc :name page-name))] (->> (rx/from nodes) (rx/filter cip/shape?) (rx/mapcat (partial resolve-images file-id)) - (rx/reduce add-shape-file (fb/add-page file page-name)) + (rx/reduce add-shape-file (fb/add-page file page-data)) (rx/map fb/close-page))) (rx/empty))) diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 6b383d7392..46916731f2 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -2694,5 +2694,8 @@ msgstr "Export file" msgid "dashboard.export-multi" msgstr "Export %s files" +msgid "dashboard.import" +msgstr "Import files" + msgid "dashboard.options" msgstr "Options"