mirror of
https://github.com/penpot/penpot.git
synced 2025-05-03 16:05:54 +02:00
🎉 Basic changes to use penpot as a library
This commit is contained in:
parent
10e0cf121b
commit
369dc8ffb5
11 changed files with 96 additions and 43 deletions
|
@ -21,6 +21,7 @@
|
||||||
[app.common.types.pages-list :as ctpl]
|
[app.common.types.pages-list :as ctpl]
|
||||||
[app.common.types.shape :as cts]
|
[app.common.types.shape :as cts]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
|
[clojure.spec.alpha :as spec]
|
||||||
[cuerdas.core :as str]))
|
[cuerdas.core :as str]))
|
||||||
|
|
||||||
(def root-frame uuid/zero)
|
(def root-frame uuid/zero)
|
||||||
|
@ -52,9 +53,18 @@
|
||||||
(when fail-on-spec?
|
(when fail-on-spec?
|
||||||
(us/verify ::pcs/change change))
|
(us/verify ::pcs/change change))
|
||||||
|
|
||||||
(let [valid? (us/valid? ::pcs/change change)]
|
(let [valid? (us/valid? ::pcs/change change)
|
||||||
|
explain (spec/explain-str ::pcs/change change)]
|
||||||
#?(:cljs
|
#?(:cljs
|
||||||
(when-not valid? (.warn js/console "Invalid shape" (clj->js change))))
|
(when-not valid?
|
||||||
|
(do
|
||||||
|
(.warn js/console "Invalid shape" (clj->js change))
|
||||||
|
(.warn js/console explain)))
|
||||||
|
:clj
|
||||||
|
(when-not valid?
|
||||||
|
(do
|
||||||
|
(prn "Invalid shape" change)
|
||||||
|
(prn explain))))
|
||||||
|
|
||||||
(cond-> file
|
(cond-> file
|
||||||
valid?
|
valid?
|
||||||
|
|
|
@ -13,9 +13,10 @@
|
||||||
funcool/tubax {:mvn/version "2021.05.20-0"}
|
funcool/tubax {:mvn/version "2021.05.20-0"}
|
||||||
|
|
||||||
funcool/rumext
|
funcool/rumext
|
||||||
{:git/tag "v2.0"
|
{:git/tag "v2.1"
|
||||||
:git/sha "fc617a8"
|
:git/sha "6343102"
|
||||||
:git/url "https://github.com/funcool/rumext.git"}
|
:git/url "https://github.com/funcool/rumext.git"
|
||||||
|
}
|
||||||
|
|
||||||
instaparse/instaparse {:mvn/version "1.4.12"}
|
instaparse/instaparse {:mvn/version "1.4.12"}
|
||||||
garden/garden {:git/url "https://github.com/noprompt/garden"
|
garden/garden {:git/url "https://github.com/noprompt/garden"
|
||||||
|
|
|
@ -53,7 +53,7 @@
|
||||||
:createFile app.libs.file-builder/create-file-export}}}
|
:createFile app.libs.file-builder/create-file-export}}}
|
||||||
|
|
||||||
:compiler-options
|
:compiler-options
|
||||||
{:output-feature-set :es8
|
{:output-feature-set :es2020
|
||||||
:output-wrapper false
|
:output-wrapper false
|
||||||
:warnings {:fn-deprecated false}}
|
:warnings {:fn-deprecated false}}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,11 @@
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.file-builder :as fb]
|
[app.common.file-builder :as fb]
|
||||||
|
[app.common.uuid :as uuid]
|
||||||
|
[app.util.dom :as dom]
|
||||||
|
[app.util.zip :as uz]
|
||||||
|
[app.worker.export :as e]
|
||||||
|
[beicon.core :as rx]
|
||||||
[cuerdas.core :as str]))
|
[cuerdas.core :as str]))
|
||||||
|
|
||||||
(defn parse-data [data]
|
(defn parse-data [data]
|
||||||
|
@ -22,6 +27,50 @@
|
||||||
key (-> key d/name str/kebab keyword)]
|
key (-> key d/name str/kebab keyword)]
|
||||||
[key value])) $)))
|
[key value])) $)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn export-file
|
||||||
|
[file]
|
||||||
|
(let [file (assoc file
|
||||||
|
:name (:name file)
|
||||||
|
:file-name (:name file)
|
||||||
|
:is-shared false)
|
||||||
|
|
||||||
|
files-stream (->> (rx/of {(:id file) file})
|
||||||
|
(rx/share))
|
||||||
|
|
||||||
|
manifest-stream
|
||||||
|
(->> files-stream
|
||||||
|
(rx/map #(e/create-manifest (uuid/next) (:id file) :all %))
|
||||||
|
(rx/map (fn [a]
|
||||||
|
(vector "manifest.json" a))))
|
||||||
|
|
||||||
|
render-stream
|
||||||
|
(->> files-stream
|
||||||
|
(rx/flat-map vals)
|
||||||
|
(rx/flat-map e/process-pages)
|
||||||
|
(rx/observe-on :async)
|
||||||
|
(rx/flat-map e/get-page-data)
|
||||||
|
(rx/share))
|
||||||
|
pages-stream
|
||||||
|
(->> render-stream
|
||||||
|
(rx/map e/collect-page))]
|
||||||
|
|
||||||
|
(rx/merge
|
||||||
|
(->> render-stream
|
||||||
|
(rx/map #(hash-map
|
||||||
|
:type :progress
|
||||||
|
:file (:id file)
|
||||||
|
:data (str "Render " (:file-name %) " - " (:name %)))))
|
||||||
|
|
||||||
|
(->> (rx/merge
|
||||||
|
manifest-stream
|
||||||
|
pages-stream)
|
||||||
|
(rx/reduce conj [])
|
||||||
|
(rx/with-latest-from files-stream)
|
||||||
|
(rx/flat-map (fn [[data _]]
|
||||||
|
(->> (uz/compress-files data)
|
||||||
|
(rx/map #(vector file %)))))))))
|
||||||
|
|
||||||
(deftype File [^:mutable file]
|
(deftype File [^:mutable file]
|
||||||
Object
|
Object
|
||||||
|
|
||||||
|
@ -50,6 +99,13 @@
|
||||||
(closeGroup [_]
|
(closeGroup [_]
|
||||||
(set! file (fb/close-group file)))
|
(set! file (fb/close-group file)))
|
||||||
|
|
||||||
|
(addBool [_ data]
|
||||||
|
(set! file (fb/add-bool file (parse-data data)))
|
||||||
|
(str (:last-id file)))
|
||||||
|
|
||||||
|
(closeBool [_]
|
||||||
|
(set! file (fb/close-bool file)))
|
||||||
|
|
||||||
(createRect [_ data]
|
(createRect [_ data]
|
||||||
(set! file (fb/create-rect file (parse-data data)))
|
(set! file (fb/create-rect file (parse-data data)))
|
||||||
(str (:last-id file)))
|
(str (:last-id file)))
|
||||||
|
@ -78,7 +134,15 @@
|
||||||
(set! file (fb/close-svg-raw file)))
|
(set! file (fb/close-svg-raw file)))
|
||||||
|
|
||||||
(asMap [_]
|
(asMap [_]
|
||||||
(clj->js file)))
|
(clj->js file))
|
||||||
|
|
||||||
|
(export [_]
|
||||||
|
(->> (export-file file)
|
||||||
|
(rx/subs
|
||||||
|
(fn [value]
|
||||||
|
(when (not (contains? value :type))
|
||||||
|
(let [[file export-blob] value]
|
||||||
|
(dom/trigger-download (:name file) export-blob))))))))
|
||||||
|
|
||||||
(defn create-file-export [^string name]
|
(defn create-file-export [^string name]
|
||||||
(File. (fb/create-file name)))
|
(File. (fb/create-file name)))
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
(ns app.main.broadcast
|
(ns app.main.broadcast
|
||||||
"BroadcastChannel API."
|
"BroadcastChannel API."
|
||||||
(:require
|
(:require
|
||||||
|
[app.common.exceptions :as ex]
|
||||||
[app.common.transit :as t]
|
[app.common.transit :as t]
|
||||||
[beicon.core :as rx]
|
[beicon.core :as rx]
|
||||||
[potok.core :as ptk]))
|
[potok.core :as ptk]))
|
||||||
|
@ -18,9 +19,12 @@
|
||||||
(def ^:const default-topic "penpot")
|
(def ^:const default-topic "penpot")
|
||||||
|
|
||||||
;; The main broadcast channel instance, used for emit data
|
;; The main broadcast channel instance, used for emit data
|
||||||
|
;; If used as a library may be we can't access js/BroadcastChannel.
|
||||||
|
;; and even if it exists we can receive an exception like:
|
||||||
|
;; Failed to construct 'BroadcastChannel': Can't create BroadcastChannel in an opaque origin
|
||||||
(defonce default-channel
|
(defonce default-channel
|
||||||
(when (exists? js/BroadcastChannel)
|
(when (exists? js/BroadcastChannel)
|
||||||
(js/BroadcastChannel. default-topic)))
|
(ex/ignoring (js/BroadcastChannel. default-topic))))
|
||||||
|
|
||||||
(defonce stream
|
(defonce stream
|
||||||
(if (exists? js/BroadcastChannel)
|
(if (exists? js/BroadcastChannel)
|
||||||
|
|
|
@ -7,9 +7,7 @@
|
||||||
(ns app.main.ui.hooks
|
(ns app.main.ui.hooks
|
||||||
"A collection of general purpose react hooks."
|
"A collection of general purpose react hooks."
|
||||||
(:require
|
(:require
|
||||||
[app.common.data.macros :as dm]
|
|
||||||
[app.common.pages :as cp]
|
[app.common.pages :as cp]
|
||||||
[app.common.uuid :as uuid]
|
|
||||||
[app.main.broadcast :as mbc]
|
[app.main.broadcast :as mbc]
|
||||||
[app.main.data.shortcuts :as dsc]
|
[app.main.data.shortcuts :as dsc]
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
|
@ -22,11 +20,6 @@
|
||||||
[goog.functions :as f]
|
[goog.functions :as f]
|
||||||
[rumext.v2 :as mf]))
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
(defn use-id
|
|
||||||
"Get a stable id value across rerenders."
|
|
||||||
[]
|
|
||||||
(mf/use-memo #(dm/str (uuid/next))))
|
|
||||||
|
|
||||||
(defn use-rxsub
|
(defn use-rxsub
|
||||||
[ob]
|
[ob]
|
||||||
(let [[state reset-state!] (mf/useState #(if (satisfies? IDeref ob) @ob nil))]
|
(let [[state reset-state!] (mf/useState #(if (satisfies? IDeref ob) @ob nil))]
|
||||||
|
@ -253,24 +246,6 @@
|
||||||
(mf/set-ref-val! ref val))
|
(mf/set-ref-val! ref val))
|
||||||
(mf/ref-val ref)))
|
(mf/ref-val ref)))
|
||||||
|
|
||||||
(defn- ssr?
|
|
||||||
"Checks if the current environment is run under a SSR context"
|
|
||||||
[]
|
|
||||||
(try
|
|
||||||
(not js/window)
|
|
||||||
(catch :default _e
|
|
||||||
;; When exception accessing window we're in ssr
|
|
||||||
true)))
|
|
||||||
|
|
||||||
(defn use-effect-ssr
|
|
||||||
"Use effect that handles SSR"
|
|
||||||
[deps effect-fn]
|
|
||||||
|
|
||||||
(if (ssr?)
|
|
||||||
(let [ret (effect-fn)]
|
|
||||||
(when (fn? ret) (ret)))
|
|
||||||
(mf/use-effect deps effect-fn)))
|
|
||||||
|
|
||||||
(defn with-focus-objects
|
(defn with-focus-objects
|
||||||
([objects]
|
([objects]
|
||||||
(let [focus (mf/deref refs/workspace-focus-selected)]
|
(let [focus (mf/deref refs/workspace-focus-selected)]
|
||||||
|
@ -298,7 +273,7 @@
|
||||||
localStorage. And it will keep watching events with type equals to
|
localStorage. And it will keep watching events with type equals to
|
||||||
`key` for new values."
|
`key` for new values."
|
||||||
[key default]
|
[key default]
|
||||||
(let [id (use-id)
|
(let [id (mf/use-id)
|
||||||
state (mf/use-state (get @storage key default))
|
state (mf/use-state (get @storage key default))
|
||||||
stream (mf/with-memo [id]
|
stream (mf/with-memo [id]
|
||||||
(->> mbc/stream
|
(->> mbc/stream
|
||||||
|
@ -311,7 +286,6 @@
|
||||||
(swap! storage assoc key @state))
|
(swap! storage assoc key @state))
|
||||||
|
|
||||||
(use-stream stream (partial reset! state))
|
(use-stream stream (partial reset! state))
|
||||||
|
|
||||||
state))
|
state))
|
||||||
|
|
||||||
(defonce ^:private intersection-subject (rx/subject))
|
(defonce ^:private intersection-subject (rx/subject))
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
uri-data (mf/use-ref {})
|
uri-data (mf/use-ref {})
|
||||||
state (mf/use-state 0)]
|
state (mf/use-state 0)]
|
||||||
|
|
||||||
(hooks/use-effect-ssr
|
(mf/use-ssr-effect
|
||||||
(mf/deps embed? urls)
|
(mf/deps embed? urls)
|
||||||
(fn []
|
(fn []
|
||||||
(let [;; When not active the embedding we return the URI
|
(let [;; When not active the embedding we return the URI
|
||||||
|
|
|
@ -80,7 +80,7 @@
|
||||||
(obj/set! "height" height))])
|
(obj/set! "height" height))])
|
||||||
|
|
||||||
(when has-image?
|
(when has-image?
|
||||||
[:image {:href (get embed uri uri)
|
[:image {:href (or (:data-uri shape) (get embed uri uri))
|
||||||
:preserveAspectRatio "none"
|
:preserveAspectRatio "none"
|
||||||
:width width
|
:width width
|
||||||
:height height}])]])])))))
|
:height height}])]])])))))
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
[app.common.pages.helpers :as cph]
|
[app.common.pages.helpers :as cph]
|
||||||
[app.main.ui.context :as muc]
|
[app.main.ui.context :as muc]
|
||||||
[app.main.ui.hooks :as h]
|
|
||||||
[app.main.ui.shapes.attrs :as attrs]
|
[app.main.ui.shapes.attrs :as attrs]
|
||||||
[app.main.ui.shapes.export :as ed]
|
[app.main.ui.shapes.export :as ed]
|
||||||
[app.main.ui.shapes.fills :as fills]
|
[app.main.ui.shapes.fills :as fills]
|
||||||
|
@ -56,7 +55,7 @@
|
||||||
disable-shadows? (unchecked-get props "disable-shadows?")
|
disable-shadows? (unchecked-get props "disable-shadows?")
|
||||||
|
|
||||||
type (:type shape)
|
type (:type shape)
|
||||||
render-id (h/use-id)
|
render-id (mf/use-id)
|
||||||
filter-id (dm/str "filter_" render-id)
|
filter-id (dm/str "filter_" render-id)
|
||||||
styles (-> (obj/create)
|
styles (-> (obj/create)
|
||||||
(obj/set! "pointerEvents" pointer-events)
|
(obj/set! "pointerEvents" pointer-events)
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.pages.helpers :as cph]
|
[app.common.pages.helpers :as cph]
|
||||||
[app.main.fonts :as fonts]
|
[app.main.fonts :as fonts]
|
||||||
[app.main.ui.hooks :as hooks]
|
|
||||||
[app.main.ui.shapes.embed :as embed]
|
[app.main.ui.shapes.embed :as embed]
|
||||||
[app.util.object :as obj]
|
[app.util.object :as obj]
|
||||||
[beicon.core :as rx]
|
[beicon.core :as rx]
|
||||||
|
@ -32,7 +31,7 @@
|
||||||
(let [fonts-css-ref (mf/use-ref "")
|
(let [fonts-css-ref (mf/use-ref "")
|
||||||
redraw (mf/use-state 0)]
|
redraw (mf/use-state 0)]
|
||||||
|
|
||||||
(hooks/use-effect-ssr
|
(mf/use-ssr-effect
|
||||||
(mf/deps fonts)
|
(mf/deps fonts)
|
||||||
(fn []
|
(fn []
|
||||||
(let [sub
|
(let [sub
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
(ns app.util.storage
|
(ns app.util.storage
|
||||||
(:require
|
(:require
|
||||||
|
[app.common.exceptions :as ex]
|
||||||
[app.common.transit :as t]
|
[app.common.transit :as t]
|
||||||
[app.util.globals :as g]
|
[app.util.globals :as g]
|
||||||
[app.util.timers :as tm]))
|
[app.util.timers :as tm]))
|
||||||
|
@ -38,7 +39,8 @@
|
||||||
{}
|
{}
|
||||||
(range len)))))
|
(range len)))))
|
||||||
|
|
||||||
|
;; Using ex/ignoring because can receive a DOMException like this when importing the code as a library:
|
||||||
(defonce storage (atom (load (unchecked-get g/global "localStorage"))))
|
;; Failed to read the 'localStorage' property from 'Window': Storage is disabled inside 'data:' URLs.
|
||||||
|
(defonce storage (atom (load (ex/ignoring (unchecked-get g/global "localStorage")))))
|
||||||
|
|
||||||
(add-watch storage :persistence #(persist js/localStorage %3 %4))
|
(add-watch storage :persistence #(persist js/localStorage %3 %4))
|
||||||
|
|
Loading…
Add table
Reference in a new issue