diff --git a/frontend/src/app/main/exports.cljs b/frontend/src/app/main/exports.cljs index f3ae1e818..ed0907b09 100644 --- a/frontend/src/app/main/exports.cljs +++ b/frontend/src/app/main/exports.cljs @@ -25,6 +25,7 @@ [app.main.ui.shapes.shape :refer [shape-container]] [app.main.ui.shapes.svg-raw :as svg-raw] [app.main.ui.shapes.text :as text] + [app.util.object :as obj] [app.util.timers :as ts] [cuerdas.core :as str] [rumext.alpha :as mf])) @@ -73,10 +74,11 @@ (mf/fnc group-wrapper [{:keys [shape frame] :as props}] (let [childs (mapv #(get objects %) (:shapes shape))] - [:& group-shape {:frame frame - :shape shape - :is-child-selected? true - :childs childs}])))) + [:& shape-container {:shape shape} + [:& group-shape {:frame frame + :shape shape + :is-child-selected? true + :childs childs}]])))) (defn svg-raw-wrapper-factory [objects] @@ -207,7 +209,8 @@ :height height :version "1.1" :xmlnsXlink "http://www.w3.org/1999/xlink" - :xmlns "http://www.w3.org/2000/svg"} + :xmlns "http://www.w3.org/2000/svg" + :xmlns:penpot "https://penpot.app/xmlns"} [:& wrapper {:shape frame :view-box vbox}]])) (mf/defc component-svg @@ -238,5 +241,58 @@ :height height :version "1.1" :xmlnsXlink "http://www.w3.org/1999/xlink" - :xmlns "http://www.w3.org/2000/svg"} + :xmlns "http://www.w3.org/2000/svg" + :xmlns:penpot "https://penpot.app/xmlns"} [:& wrapper {:shape group :view-box vbox}]])) + +(mf/defc component-symbol + [{:keys [id data] :as props}] + + (let [{:keys [name objects]} data + root (get objects id) + + {:keys [width height]} (:selrect root) + vbox (str "0 0 " width " " height) + + modifier (-> (gpt/point (:x root) (:y root)) + (gpt/negate) + (gmt/translate-matrix)) + + modifier-ids (concat [id] (cp/get-children id objects)) + update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier) + objects (reduce update-fn objects modifier-ids) + root (assoc-in root [:modifiers :displacement] modifier) + + group-wrapper + (mf/use-memo + (mf/deps objects) + #(group-wrapper-factory objects))] + + [:symbol {:id (str id) + :viewBox vbox} + [:title name] + [:& group-wrapper {:shape root :view-box vbox}]])) + +(mf/defc components-sprite-svg + {::mf/wrap-props false} + [props] + + (let [data (obj/get props "data") + children (obj/get props "children") + embed? (obj/get props "embed?")] + [:& (mf/provider embed/context) {:value embed?} + [:svg {:version "1.1" + :xmlns "http://www.w3.org/2000/svg" + :xmlnsXlink "http://www.w3.org/1999/xlink" + :xmlns:penpot "https://penpot.app/xmlns" + :style {:width "100vw" + :height "100vh" + :display (when-not (some? children) "none")}} + + [:defs + (for [[component-id component-data] (:components data)] + [:& component-symbol {:id component-id + :key (str component-id) + :data component-data}])] + + children]])) diff --git a/frontend/src/app/main/render.cljs b/frontend/src/app/main/render.cljs index 26e62bacc..64b760cfa 100644 --- a/frontend/src/app/main/render.cljs +++ b/frontend/src/app/main/render.cljs @@ -66,5 +66,16 @@ (let [elem (mf/element exports/page-svg #js {:data data :embed? true})] (rds/renderToStaticMarkup elem))))))) +(defn render-components + [data] + (rx/concat + (->> (rx/merge + (populate-images-cache data) + (populate-fonts-cache data)) + (rx/ignore)) - + (->> (rx/of data) + (rx/map + (fn [data] + (let [elem (mf/element exports/components-sprite-svg #js {:data data :embed? true})] + (rds/renderToStaticMarkup elem))))))) diff --git a/frontend/src/app/main/ui.cljs b/frontend/src/app/main/ui.cljs index 8859dacd3..3bf450847 100644 --- a/frontend/src/app/main/ui.cljs +++ b/frontend/src/app/main/ui.cljs @@ -82,6 +82,7 @@ ;; Used for export ["/render-object/:file-id/:page-id/:object-id" :render-object] + ["/render-sprite/:file-id" :render-sprite] ["/dashboard/team/:team-id" ["/members" :dashboard-team-members] @@ -172,6 +173,14 @@ :page-id page-id :object-id object-id}])) + :render-sprite + (do + (let [file-id (uuid (get-in route [:path-params :file-id])) + component-id (get-in route [:query-params :component-id]) + component-id (when (some? component-id) (uuid component-id))] + [:& render/render-sprite {:file-id file-id + :component-id component-id}])) + :workspace (let [project-id (some-> params :path :project-id uuid) file-id (some-> params :path :file-id uuid) diff --git a/frontend/src/app/main/ui/render.cljs b/frontend/src/app/main/ui/render.cljs index cb09796fc..64fd56ec2 100644 --- a/frontend/src/app/main/ui/render.cljs +++ b/frontend/src/app/main/ui/render.cljs @@ -123,3 +123,30 @@ [:& object-svg {:objects @objects :object-id object-id :zoom 1}]))) + +(mf/defc render-sprite + [{:keys [file-id component-id] :as props}] + (let [file (mf/use-state nil)] + (mf/use-effect + (mf/deps file-id) + (fn [] + (->> (repo/query! :file {:id file-id}) + (rx/subs + (fn [result] + (reset! file result)))) + (constantly nil))) + + (when @file + [:* + [:& exports/components-sprite-svg {:data (:data @file) :embed true} + + (when (some? component-id) + [:use {:x 0 :y 0 + :xlinkHref (str "#" component-id)}])] + + (when-not (some? component-id) + [:ul + (for [[id data] (get-in @file [:data :components])] + (let [url (str "#/render-sprite/" (:id @file) "?component-id=" id)] + [:li [:a {:href url} (:name data)]]))])]))) + diff --git a/frontend/src/app/worker/export.cljs b/frontend/src/app/worker/export.cljs index 894d69c46..3e9ee110d 100644 --- a/frontend/src/app/worker/export.cljs +++ b/frontend/src/app/worker/export.cljs @@ -70,26 +70,77 @@ [{:keys [id file-id markup] :as page}] [(str file-id "/" id ".svg") markup]) -(defn collect-color - [result color] +(defn collect-entries [result data keys] (-> result - (assoc (str (:id color)) - (->> (select-keys color [:name :color :opacity :gradient]) + (assoc (str (:id data)) + (->> (select-keys data keys) (d/deep-mapm (fn [[k v]] [(-> k str/camel) v])))))) -(def ^:const typography-keys [:name :font-family :font-id :font-size - :font-style :font-variant-id :font-weight - :letter-spacing :line-height :text-transform]) +(def ^:const color-keys + [:name :color :opacity :gradient]) + +(def ^:const typography-keys + [:name :font-family :font-id :font-size :font-style :font-variant-id :font-weight + :letter-spacing :line-height :text-transform]) + +(def ^:const media-keys + [:name :mtype :width :height]) + +(defn collect-color + [result color] + (collect-entries result color color-keys)) + (defn collect-typography [result typography] - (-> result - (assoc (str (:id typography)) - (->> (select-keys typography typography-keys) - (d/deep-mapm - (fn [[k v]] - [(-> k str/camel) v])))))) + (collect-entries result typography typography-keys)) + +(defn collect-media + [result media] + (collect-entries result media media-keys)) + +(defn parse-library-color + [[file-id colors]] + (let [markup + (->> (vals colors) + (reduce collect-color {}) + (json/encode))] + [(str file-id "/colors.json") markup])) + +(defn parse-library-typographies + [[file-id typographies]] + (let [markup + (->> (vals typographies) + (reduce collect-typography {}) + (json/encode))] + [(str file-id "/typographies.json") markup])) + +(defn parse-library-media + [[file-id media]] + (rx/merge + (let [markup + (->> (vals media) + (reduce collect-media {}) + (json/encode))] + (rx/of (vector (str file-id "/images.json") markup))) + + (->> (rx/from (vals media)) + (rx/map #(assoc % :file-id file-id)) + (rx/flat-map + (fn [media] + (let [file-path (str file-id "/images/" (:id media) "." (dom/mtype->extension (:mtype media)))] + (->> (http/send! + {:uri (cfg/resolve-file-media media) + :response-type :blob + :method :get}) + (rx/map :body) + (rx/map #(vector file-path %))))))))) + +(defn parse-library-components + [file] + (->> (r/render-components (:data file)) + (rx/map #(vector (str (:id file) "/components.svg") %)))) (defn export-file [team-id file-id] @@ -120,42 +171,27 @@ (rx/flat-map vals) (rx/map #(vector (:id %) (get-in % [:data :colors]))) (rx/filter #(d/not-empty? (second %))) - (rx/map (fn [[file-id colors]] - (let [markup - (->> (vals colors) - (reduce collect-color {}) - (json/encode))] - [(str file-id "/colors.json") markup])))) + (rx/map parse-library-color)) typographies-stream (->> files-stream (rx/flat-map vals) (rx/map #(vector (:id %) (get-in % [:data :typographies]))) (rx/filter #(d/not-empty? (second %))) - (rx/map (fn [[file-id typographies]] - (let [markup - (->> (vals typographies) - (reduce collect-typography {}) - (json/encode))] - [(str file-id "/typographies.json") markup])))) + (rx/map parse-library-typographies)) media-stream (->> files-stream (rx/flat-map vals) (rx/map #(vector (:id %) (get-in % [:data :media]))) (rx/filter #(d/not-empty? (second %))) - (rx/flat-map - (fn [[file-id media]] - (->> media vals (mapv #(assoc % :file-id file-id))))) + (rx/flat-map parse-library-media)) - (rx/flat-map - (fn [media] - (->> (http/send! - {:uri (cfg/resolve-file-media media) - :response-type :blob - :method :get}) - (rx/map :body) - (rx/map #(vector (str file-id "/images/" (:id media)) %)))))) + components-stream + (->> files-stream + (rx/flat-map vals) + (rx/filter #(d/not-empty? (get-in % [:data :components]))) + (rx/flat-map parse-library-components)) pages-stream (->> render-stream @@ -171,7 +207,7 @@ (->> (rx/merge manifest-stream pages-stream - #_components-stream + components-stream media-stream colors-stream typographies-stream)