mirror of
https://github.com/penpot/penpot.git
synced 2025-05-08 08:15:54 +02:00
✨ Adds flip,proportion and rotation
This commit is contained in:
parent
a106c728ba
commit
d6e009ce78
7 changed files with 189 additions and 40 deletions
|
@ -87,15 +87,15 @@
|
||||||
:changes []})))
|
:changes []})))
|
||||||
|
|
||||||
(defn add-page
|
(defn add-page
|
||||||
[file name]
|
[file data]
|
||||||
(let [page-id (uuid/next)]
|
(let [page-id (uuid/next)
|
||||||
|
page (-> init/empty-page-data
|
||||||
|
(assoc :id page-id)
|
||||||
|
(d/deep-merge data))]
|
||||||
(-> file
|
(-> file
|
||||||
(commit-change
|
(commit-change
|
||||||
{:type :add-page
|
{:type :add-page
|
||||||
:id page-id
|
:page page})
|
||||||
:name name
|
|
||||||
:page (-> init/empty-page-data
|
|
||||||
(assoc :name name))})
|
|
||||||
|
|
||||||
;; Current page being edited
|
;; Current page being edited
|
||||||
(assoc :current-page-id page-id)
|
(assoc :current-page-id page-id)
|
||||||
|
|
|
@ -7,14 +7,65 @@
|
||||||
(ns app.libs.file-builder
|
(ns app.libs.file-builder
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[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 [[k v]]
|
||||||
|
[(-> k d/name str/kebab keyword) v]) $)))
|
||||||
|
|
||||||
(deftype File [^:mutable file]
|
(deftype File [^:mutable file]
|
||||||
Object
|
Object
|
||||||
(addPage [self name]
|
|
||||||
(set! file (fb/add-page file name))
|
|
||||||
(str (:current-page-id file))))
|
|
||||||
|
|
||||||
|
(addPage
|
||||||
|
([self name]
|
||||||
|
(addPage self name nil))
|
||||||
|
|
||||||
|
([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))))
|
||||||
|
|
||||||
(defn create-file-export [^string name]
|
(defn create-file-export [^string name]
|
||||||
(File. (fb/create-file name)))
|
(File. (fb/create-file name)))
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[app.main.ui.shapes.circle :as circle]
|
[app.main.ui.shapes.circle :as circle]
|
||||||
[app.main.ui.shapes.embed :as embed]
|
[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.filters :as filters]
|
||||||
[app.main.ui.shapes.frame :as frame]
|
[app.main.ui.shapes.frame :as frame]
|
||||||
[app.main.ui.shapes.group :as group]
|
[app.main.ui.shapes.group :as group]
|
||||||
|
@ -158,6 +159,9 @@
|
||||||
:style {:width "100%"
|
:style {:width "100%"
|
||||||
:height "100%"
|
:height "100%"
|
||||||
:background background-color}}
|
:background background-color}}
|
||||||
|
|
||||||
|
[:& use/export-page {:options (:options data)}]
|
||||||
|
|
||||||
(for [item shapes]
|
(for [item shapes]
|
||||||
(let [frame? (= (:type item) :frame)]
|
(let [frame? (= (:type item) :frame)]
|
||||||
(cond
|
(cond
|
||||||
|
|
|
@ -83,6 +83,37 @@
|
||||||
(cond-> mask?
|
(cond-> mask?
|
||||||
(obj/set! "penpot:masked-group" "true"))))))
|
(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]}]
|
||||||
|
[:> "penpot:page" #js {}
|
||||||
|
(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))]
|
||||||
|
[:& export-grid-data {:grids grids}])))])
|
||||||
|
|
||||||
(mf/defc export-data
|
(mf/defc export-data
|
||||||
[{:keys [shape]}]
|
[{:keys [shape]}]
|
||||||
(let [props (-> (obj/new)
|
(let [props (-> (obj/new)
|
||||||
|
@ -135,5 +166,10 @@
|
||||||
(clj->js))))]
|
(clj->js))))]
|
||||||
[:> "penpot:svg-content" props
|
[:> "penpot:svg-content" props
|
||||||
(for [leaf (->> shape :content :content (filter string?))]
|
(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)}])]))
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
[app.main.ui.context :as muc]
|
[app.main.ui.context :as muc]
|
||||||
[app.main.ui.measurements :as msr]
|
[app.main.ui.measurements :as msr]
|
||||||
[app.main.ui.shapes.embed :as embed]
|
[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 :as shapes]
|
||||||
[app.main.ui.workspace.shapes.text.editor :as editor]
|
[app.main.ui.workspace.shapes.text.editor :as editor]
|
||||||
[app.main.ui.workspace.viewport.actions :as actions]
|
[app.main.ui.workspace.viewport.actions :as actions]
|
||||||
|
@ -188,6 +189,8 @@
|
||||||
:style {:background-color (get options :background "#E8E9EA")
|
:style {:background-color (get options :background "#E8E9EA")
|
||||||
:pointer-events "none"}}
|
:pointer-events "none"}}
|
||||||
|
|
||||||
|
[:& use/export-page {:options options}]
|
||||||
|
|
||||||
[:& (mf/provider embed/context) {:value true}
|
[:& (mf/provider embed/context) {:value true}
|
||||||
;; Render root shape
|
;; Render root shape
|
||||||
[:& shapes/root-shape {:key page-id
|
[:& shapes/root-shape {:key page-id
|
||||||
|
|
|
@ -32,7 +32,8 @@
|
||||||
|
|
||||||
(defn get-data
|
(defn get-data
|
||||||
([node]
|
([node]
|
||||||
(->> node :content (d/seek #(= :penpot:shape (:tag %)))))
|
(->> node :content (d/seek #(or (= :penpot:shape (:tag %))
|
||||||
|
(= :penpot:page (:tag %))))))
|
||||||
([node tag]
|
([node tag]
|
||||||
(->> (get-data node)
|
(->> (get-data node)
|
||||||
:content
|
:content
|
||||||
|
@ -98,6 +99,41 @@
|
||||||
m
|
m
|
||||||
attrs))
|
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})
|
(def search-data-node? #{:rect :image :path :text :circle})
|
||||||
|
|
||||||
(defn get-svg-data
|
(defn get-svg-data
|
||||||
|
@ -372,6 +408,26 @@
|
||||||
:suffix (get-meta node :suffix)
|
:suffix (get-meta node :suffix)
|
||||||
:scale (get-meta node :scale d/parse-double)})
|
: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
|
(defn extract-from-data
|
||||||
([node tag]
|
([node tag]
|
||||||
(extract-from-data node tag identity))
|
(extract-from-data node tag identity))
|
||||||
|
@ -477,32 +533,6 @@
|
||||||
|
|
||||||
props)))
|
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
|
(defn add-svg-content
|
||||||
[props node]
|
[props node]
|
||||||
(let [svg-content (get-data node :penpot:svg-content)
|
(let [svg-content (get-data node :penpot:svg-content)
|
||||||
|
@ -522,6 +552,12 @@
|
||||||
:tag tag
|
:tag tag
|
||||||
:content node-content})))
|
:content node-content})))
|
||||||
|
|
||||||
|
(defn add-frame-data [props node]
|
||||||
|
(let [grids (parse-grids node)]
|
||||||
|
(cond-> props
|
||||||
|
(not (empty? grids))
|
||||||
|
(assoc :grids grids))))
|
||||||
|
|
||||||
(defn get-image-name
|
(defn get-image-name
|
||||||
[node]
|
[node]
|
||||||
(get-in node [:attrs :penpot:name]))
|
(get-in node [:attrs :penpot:name]))
|
||||||
|
@ -550,6 +586,9 @@
|
||||||
(cond-> (= :svg-raw type)
|
(cond-> (= :svg-raw type)
|
||||||
(add-svg-content node))
|
(add-svg-content node))
|
||||||
|
|
||||||
|
(cond-> (= :frame type)
|
||||||
|
(add-frame-data node))
|
||||||
|
|
||||||
(cond-> (= :group type)
|
(cond-> (= :group type)
|
||||||
(add-group-data node))
|
(add-group-data node))
|
||||||
|
|
||||||
|
@ -561,3 +600,17 @@
|
||||||
|
|
||||||
(cond-> (= :text type)
|
(cond-> (= :text type)
|
||||||
(add-text-data node))))))
|
(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))))
|
||||||
|
|
|
@ -137,11 +137,13 @@
|
||||||
[file [page-name content]]
|
[file [page-name content]]
|
||||||
(if (cip/valid? content)
|
(if (cip/valid? content)
|
||||||
(let [nodes (->> content cip/node-seq)
|
(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/from nodes)
|
||||||
(rx/filter cip/shape?)
|
(rx/filter cip/shape?)
|
||||||
(rx/mapcat (partial resolve-images file-id))
|
(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/map fb/close-page)))
|
||||||
(rx/empty)))
|
(rx/empty)))
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue