Merge remote-tracking branch 'origin/staging' into develop

This commit is contained in:
Andrey Antukh 2022-01-20 14:30:16 +01:00
commit c27d709b6b
27 changed files with 427 additions and 219 deletions

View file

@ -23,6 +23,7 @@
- Add actions to go to main component context menu option [Taiga #2053](https://tree.taiga.io/project/penpot/us/2053). - Add actions to go to main component context menu option [Taiga #2053](https://tree.taiga.io/project/penpot/us/2053).
- Add contrast between component select color and shape select color [Taiga #2121](https://tree.taiga.io/project/penpot/issue/2121). - Add contrast between component select color and shape select color [Taiga #2121](https://tree.taiga.io/project/penpot/issue/2121).
- Add animations in interactions [Taiga #2244](https://tree.taiga.io/project/penpot/us/2244). - Add animations in interactions [Taiga #2244](https://tree.taiga.io/project/penpot/us/2244).
- Add performance improvements on .penpot file import process [Taiga 2497](https://tree.taiga.io/project/penpot/us/2497).
### :bug: Bugs fixed ### :bug: Bugs fixed
@ -69,6 +70,7 @@
- Avoid modifying component when moving into a group [Taiga #2534](https://tree.taiga.io/project/penpot/issue/2534) - Avoid modifying component when moving into a group [Taiga #2534](https://tree.taiga.io/project/penpot/issue/2534)
- Show correctly group types label in handoff [Taiga #2482](https://tree.taiga.io/project/penpot/issue/2482) - Show correctly group types label in handoff [Taiga #2482](https://tree.taiga.io/project/penpot/issue/2482)
- Display view mode buttons always centered in viewer [#Taiga 2466](https://tree.taiga.io/project/penpot/issue/2466) - Display view mode buttons always centered in viewer [#Taiga 2466](https://tree.taiga.io/project/penpot/issue/2466)
- Fix default profile image generation issue [Taiga #2601](https://tree.taiga.io/project/penpot/issue/2601)
### :arrow_up: Deps updates ### :arrow_up: Deps updates

View file

@ -8,7 +8,7 @@ export OPTIONS="
-J-Dlog4j2.configurationFile=log4j2-devenv.xml \ -J-Dlog4j2.configurationFile=log4j2-devenv.xml \
-J-XX:+UseZGC \ -J-XX:+UseZGC \
-J-XX:-OmitStackTraceInFastThrow \ -J-XX:-OmitStackTraceInFastThrow \
-J-Xms50m -J-Xmx512m \ -J-Xms50m -J-Xmx1024m \
-J-Djdk.attach.allowAttachSelf \ -J-Djdk.attach.allowAttachSelf \
-J-XX:+UnlockDiagnosticVMOptions \ -J-XX:+UnlockDiagnosticVMOptions \
-J-XX:+DebugNonSafepoints"; -J-XX:+DebugNonSafepoints";

View file

@ -414,7 +414,9 @@
[conn project-id] [conn project-id]
(:team-id (db/get-by-id conn :project project-id {:columns [:team-id]}))) (:team-id (db/get-by-id conn :project project-id {:columns [:team-id]})))
;; TEMPORARY FILE CREATION ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TEMPORARY FILES (behaves differently)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(s/def ::create-temp-file ::create-file) (s/def ::create-temp-file ::create-file)
@ -424,6 +426,23 @@
(proj/check-edition-permissions! conn profile-id project-id) (proj/check-edition-permissions! conn profile-id project-id)
(create-file conn (assoc params :deleted-at (dt/in-future {:days 1}))))) (create-file conn (assoc params :deleted-at (dt/in-future {:days 1})))))
(s/def ::update-temp-file
(s/keys :req-un [::changes ::revn ::session-id ::id]))
(sv/defmethod ::update-temp-file
[{:keys [pool] :as cfg} {:keys [profile-id session-id id revn changes] :as params}]
(db/with-atomic [conn pool]
(db/insert! conn :file-change
{:id (uuid/next)
:session-id session-id
:profile-id profile-id
:created-at (dt/now)
:file-id id
:revn revn
:data nil
:changes (blob/encode changes)})
nil))
(s/def ::persist-temp-file (s/def ::persist-temp-file
(s/keys :req-un [::id ::profile-id])) (s/keys :req-un [::id ::profile-id]))
@ -431,6 +450,25 @@
[{:keys [pool] :as cfg} {:keys [id profile-id] :as params}] [{:keys [pool] :as cfg} {:keys [id profile-id] :as params}]
(db/with-atomic [conn pool] (db/with-atomic [conn pool]
(files/check-edition-permissions! conn profile-id id) (files/check-edition-permissions! conn profile-id id)
(db/update! conn :file (let [file (db/get-by-id conn :file id)
{:deleted-at nil} revs (db/query conn :file-change
{:id id}))) {:file-id id}
{:order-by [[:revn :asc]]})
revn (count revs)]
(when (nil? (:deleted-at file))
(ex/raise :type :validation
:code :cant-persist-already-persisted-file))
(loop [revs (seq revs)
data (blob/decode (:data file))]
(if-let [rev (first revs)]
(recur (rest revs)
(->> rev :changes blob/decode (cp/process-changes data)))
(db/update! conn :file
{:deleted-at nil
:revn revn
:data (blob/encode data)}
{:id id})))
nil)))

View file

@ -6,6 +6,8 @@
(ns app.rpc.queries.files (ns app.rpc.queries.files
(:require (:require
[app.common.data :as d]
[app.common.pages :as cp]
[app.common.pages.migrations :as pmg] [app.common.pages.migrations :as pmg]
[app.common.spec :as us] [app.common.spec :as us]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
@ -215,8 +217,66 @@
(some-> (retrieve-file cfg id) (some-> (retrieve-file cfg id)
(assoc :permissions perms))))) (assoc :permissions perms)))))
(defn remove-thumbnails-frames (declare trim-file-data)
"Removes from data the children for frames that have a thumbnail set up"
(s/def ::page-id ::us/uuid)
(s/def ::object-id ::us/uuid)
(s/def ::trimmed-file
(s/keys :req-un [::profile-id ::id ::object-id ::page-id]))
(sv/defmethod ::trimmed-file
"Retrieve a file by its ID and trims all unnecesary content from
it. It is mainly used for rendering a concrete object, so we don't
need force download all shapes when only a small subset is
necesseary."
[{:keys [pool] :as cfg} {:keys [profile-id id] :as params}]
(db/with-atomic [conn pool]
(let [cfg (assoc cfg :conn conn)
perms (get-permissions conn profile-id id)]
(check-read-permissions! perms)
(some-> (retrieve-file cfg id)
(trim-file-data params)
(assoc :permissions perms)))))
(defn- trim-file-data
[file {:keys [page-id object-id]}]
(let [page (get-in file [:data :pages-index page-id])
objects (->> (:objects page)
(cp/get-object-with-children object-id)
(map #(dissoc % :thumbnail)))
objects (d/index-by :id objects)
page (assoc page :objects objects)]
(-> file
(update :data assoc :pages-index {page-id page})
(assoc :pages [page-id]))))
(declare strip-frames-with-thumbnails)
(s/def ::strip-frames-with-thumbnails ::us/boolean)
(s/def ::page
(s/keys :req-un [::profile-id ::file-id]
:opt-un [::strip-frames-with-thumbnails]))
(sv/defmethod ::page
"Retrieves the first page of the file. Used mainly for render
thumbnails on dashboard."
[{:keys [pool] :as cfg} {:keys [profile-id file-id] :as props}]
(db/with-atomic [conn pool]
(check-read-permissions! conn profile-id file-id)
(let [cfg (assoc cfg :conn conn)
file (retrieve-file cfg file-id)
page-id (get-in file [:data :pages 0])]
(cond-> (get-in file [:data :pages-index page-id])
(true? (:strip-frames-with-thumbnails props))
(strip-frames-with-thumbnails)))))
(defn strip-frames-with-thumbnails
"Remove unnecesary shapes from frames that have thumbnail."
[data] [data]
(let [filter-shape? (let [filter-shape?
(fn [objects [id shape]] (fn [objects [id shape]]
@ -242,22 +302,6 @@
(update data :objects update-objects))) (update data :objects update-objects)))
(s/def ::page
(s/keys :req-un [::profile-id ::file-id]))
(sv/defmethod ::page
"Retrieves the first page of the file. Used mainly for render
thumbnails on dashboard."
[{:keys [pool] :as cfg} {:keys [profile-id file-id strip-thumbnails]}]
(db/with-atomic [conn pool]
(check-read-permissions! conn profile-id file-id)
(let [cfg (assoc cfg :conn conn)
file (retrieve-file cfg file-id)
page-id (get-in file [:data :pages 0])]
(cond-> (get-in file [:data :pages-index page-id])
strip-thumbnails
(remove-thumbnails-frames)))))
;; --- Query: Shared Library Files ;; --- Query: Shared Library Files

View file

@ -6,7 +6,9 @@
(ns app.common.geom.align (ns app.common.geom.align
(:require (:require
[app.common.data :as d]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.pages.helpers :refer [get-children]]
[clojure.spec.alpha :as s])) [clojure.spec.alpha :as s]))
;; --- Alignment ;; --- Alignment
@ -15,23 +17,13 @@
(declare calc-align-pos) (declare calc-align-pos)
;; TODO: revisit on how to reuse code and dont have this function
;; duplicated because the implementation right now differs from the
;; original function.
;; Duplicated from pages/helpers to remove cyclic dependencies
(defn- get-children [id objects]
(let [shapes (vec (get-in objects [id :shapes]))]
(if shapes
(into shapes (mapcat #(get-children % objects)) shapes)
[])))
(defn- recursive-move (defn- recursive-move
"Move the shape and all its recursive children." "Move the shape and all its recursive children."
[shape dpoint objects] [shape dpoint objects]
(let [children-ids (get-children (:id shape) objects) (->> (get-children (:id shape) objects)
children (map #(get objects %) children-ids)] (map (d/getf objects))
(map #(gsh/move % dpoint) (cons shape children)))) (cons shape)
(map #(gsh/move % dpoint))))
(defn align-to-rect (defn align-to-rect
"Move the shape so that it is aligned with the given rectangle "Move the shape so that it is aligned with the given rectangle

View file

@ -412,7 +412,7 @@
{:rotation angle {:rotation angle
:displacement displacement})) :displacement displacement}))
(defn merge-modifiers* (defn merge-modifiers
[objects modifiers] [objects modifiers]
(let [set-modifier (let [set-modifier
@ -422,8 +422,6 @@
(->> modifiers (->> modifiers
(reduce set-modifier objects)))) (reduce set-modifier objects))))
(def merge-modifiers (memoize merge-modifiers*))
(defn modifiers->transform (defn modifiers->transform
([modifiers] ([modifiers]
(modifiers->transform nil modifiers)) (modifiers->transform nil modifiers))

View file

@ -168,7 +168,7 @@
`(write-log! ~(or logger (str *ns*)) `(write-log! ~(or logger (str *ns*))
~level ~level
~cause ~cause
~(dissoc props :level :cause ::logger ::raw)) (or ~raw ~(dissoc props :level :cause ::logger ::raw)))
(let [props (dissoc props :level :cause ::logger ::async ::raw) (let [props (dissoc props :level :cause ::logger ::async ::raw)
logger (or logger (str *ns*)) logger (or logger (str *ns*))
logger-sym (gensym "log") logger-sym (gensym "log")

View file

@ -99,37 +99,10 @@
[component] [component]
(get-in component [:objects (:id component)])) (get-in component [:objects (:id component)]))
;; Implemented with transient for performance (defn get-children [id objects]
(defn get-children* (if-let [shapes (-> (get objects id) :shapes (some-> vec))]
"Retrieve all children ids recursively for a given object. The (into shapes (mapcat #(get-children % objects)) shapes)
children's order will be breadth first." []))
[id objects]
(loop [result (transient [])
pending (transient [])
next id]
(let [children (get-in objects [next :shapes] [])
[result pending]
;; Iterate through children and add them to the result
;; also add them in pending to check for their children
(loop [result result
pending pending
current (first children)
children (rest children)]
(if current
(recur (conj! result current)
(conj! pending current)
(first children)
(rest children))
[result pending]))
;; If we have still pending, advance the iterator
length (count pending)]
(if (pos? length)
(let [next (get pending (dec length))]
(recur result (pop! pending) next))
(persistent! result)))))
(def get-children (memoize get-children*))
(defn get-children-objects (defn get-children-objects
"Retrieve all children objects recursively for a given object" "Retrieve all children objects recursively for a given object"
@ -175,7 +148,7 @@
shape shape
(get objects (:frame-id shape)))) (get objects (:frame-id shape))))
(defn clean-loops* (defn clean-loops
"Clean a list of ids from circular references." "Clean a list of ids from circular references."
[objects ids] [objects ids]
@ -192,8 +165,6 @@
(reduce add-element (d/ordered-set) ids))) (reduce add-element (d/ordered-set) ids)))
(def clean-loops (memoize clean-loops*))
(defn calculate-invalid-targets (defn calculate-invalid-targets
[shape-id objects] [shape-id objects]
(let [result #{shape-id} (let [result #{shape-id}

View file

@ -2,17 +2,21 @@
set -ex set -ex
CURRENT_VERSION=$1;
yarn install yarn install
rm -rf target rm -rf target
export NODE_ENV=production; export NODE_ENV=production;
# Build the application # Build the application
clojure -M:dev:shadow-cljs release main clojure -M:dev:shadow-cljs release main;
# Remove source # Remove source
rm -rf target/app rm -rf target/app;
# Copy package*.json files # Copy package*.json files
cp yarn.lock target/ cp yarn.lock target/;
cp package.json target/ cp package.json target/;
sed -i -re "s/\%version\%/$CURRENT_VERSION/g" ./target/app.js;

View file

@ -12,9 +12,9 @@
[app.common.logging :as l] [app.common.logging :as l]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.config :as cf] [app.config :as cf]
[app.util.object :as obj]
[promesa.core :as p])) [promesa.core :as p]))
(l/set-level! :trace) (l/set-level! :trace)
;; --- BROWSER API ;; --- BROWSER API
@ -35,7 +35,8 @@
[page {:keys [timeout cookie user-agent viewport]}] [page {:keys [timeout cookie user-agent viewport]}]
(let [timeout (or timeout default-timeout) (let [timeout (or timeout default-timeout)
user-agent (or user-agent default-user-agent) user-agent (or user-agent default-user-agent)
viewport (d/merge default-viewport viewport)] viewport (merge default-viewport viewport)]
(p/do! (p/do!
(.setViewport ^js page #js {:width (:width viewport) (.setViewport ^js page #js {:width (:width viewport)
:height (:height viewport) :height (:height viewport)
@ -63,29 +64,27 @@
(defn screenshot (defn screenshot
([frame] (screenshot frame nil)) ([frame] (screenshot frame nil))
([frame {:keys [full-page? omit-background? type] ([frame {:keys [full-page? omit-background? type]
:or {full-page? false :or {type "png"
type "png" full-page? false
omit-background? false}}] omit-background? false}}]
(.screenshot ^js frame #js {:fullPage full-page? (let [options (-> (obj/new)
:type (name type) (obj/set! "type" (name type))
:omitBackground omit-background?}))) (obj/set! "omitBackground" omit-background?)
(cond-> full-page? (-> (obj/set! "fullPage" true)
(obj/set! "clip" nil))))]
(.screenshot ^js frame options))))
(defn pdf (defn pdf
([page] (pdf page nil)) ([page] (pdf page nil))
([page {:keys [viewport omit-background? prefer-css-page-size? save-path] ([page {:keys [viewport save-path]}]
:or {viewport {} (p/let [viewport (d/merge default-viewport viewport)]
omit-background? true (.emulateMediaType ^js page "screen")
prefer-css-page-size? true
save-path nil}}]
(let [viewport (d/merge default-viewport viewport)]
(.pdf ^js page #js {:path save-path (.pdf ^js page #js {:path save-path
:width (:width viewport) :width (:width viewport)
:height (:height viewport) :height (:height viewport)
:scale (:scale viewport) :scale (:scale viewport)
:omitBackground omit-background? :printBackground true
:printBackground (not omit-background?) :preferCSSPageSize true}))))
:preferCSSPageSize prefer-css-page-size?}))))
(defn eval! (defn eval!
[frame f] [frame f]
(.evaluate ^js frame f)) (.evaluate ^js frame f))
@ -104,10 +103,17 @@
(defonce pool (atom nil)) (defonce pool (atom nil))
(defonce pool-browser-id (atom 1)) (defonce pool-browser-id (atom 1))
(def default-chrome-args
#js ["--no-sandbox"
"--font-render-hinting=none"
"--disable-setuid-sandbox"
"--disable-accelerated-2d-canvas"
"--disable-gpu"])
(def browser-pool-factory (def browser-pool-factory
(letfn [(create [] (letfn [(create []
(let [path (cf/get :browser-executable-path "/usr/bin/google-chrome")] (let [path (cf/get :browser-executable-path "/usr/bin/google-chrome")]
(-> (pp/launch #js {:executablePath path :args #js ["--no-sandbox" "--font-render-hinting=none"]}) (-> (pp/launch #js {:executablePath path :args default-chrome-args})
(p/then (fn [browser] (p/then (fn [browser]
(let [id (deref pool-browser-id)] (let [id (deref pool-browser-id)]
(l/info :origin "factory" :action "create" :browser-id id) (l/info :origin "factory" :action "create" :browser-id id)

View file

@ -71,9 +71,7 @@
(atom (prepare-config))) (atom (prepare-config)))
(def version (def version
(atom (v/parse (or (some-> (ex/ignoring (fs/readFileSync "version.txt")) (atom (v/parse "%version%")))
(str/trim))
"%version%"))))
(defn get (defn get
"A configuration getter." "A configuration getter."

View file

@ -31,20 +31,26 @@
(let [path (str "/render-object/" file-id "/" page-id "/" object-id) (let [path (str "/render-object/" file-id "/" page-id "/" object-id)
uri (-> (u/uri (cf/get :public-uri)) uri (-> (u/uri (cf/get :public-uri))
(assoc :path "/") (assoc :path "/")
(assoc :query "essential=t")
(assoc :fragment path)) (assoc :fragment path))
cookie (create-cookie uri token)] cookie (create-cookie uri token)]
(pdf-from page (str uri) cookie))) (pdf-from page (str uri) cookie)))
(pdf-from [page uri cookie] (pdf-from [page uri cookie]
(l/info :uri uri) (l/info :uri uri)
(let [options {:cookie cookie}] (p/let [options {:cookie cookie}]
(p/do! (bw/configure-page! page options)
(bw/configure-page! page options) (bw/navigate! page uri)
(bw/navigate! page uri) (bw/wait-for page "#screenshot")
(bw/wait-for page "#screenshot") ;; taking png screenshot before pdf, helps to make the
(if save-path ;; pdf rendering works as expected.
(bw/pdf page {:save-path save-path}) (p/let [dom (bw/select page "#screenshot")]
(bw/pdf page)))))] (bw/screenshot dom {:full-page? true}))
(if save-path
(bw/pdf page {:save-path save-path})
(bw/pdf page))))]
(bw/exec! handle))) (bw/exec! handle)))

View file

@ -0,0 +1,86 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) UXBOX Labs SL
(ns app.util.object
"A collection of helpers for work with javascript objects."
(:refer-clojure :exclude [set! get get-in merge clone contains?])
(:require
[cuerdas.core :as str]))
(defn new [] #js {})
(defn get
([obj k]
(when-not (nil? obj)
(unchecked-get obj k)))
([obj k default]
(let [result (get obj k)]
(if (undefined? result) default result))))
(defn contains?
[obj k]
(some? (unchecked-get obj k)))
(defn get-keys
[obj]
(js/Object.keys ^js obj))
(defn get-in
([obj keys]
(get-in obj keys nil))
([obj keys default]
(loop [key (first keys)
keys (rest keys)
res obj]
(if (or (nil? key) (nil? res))
(or res default)
(recur (first keys)
(rest keys)
(unchecked-get res key))))))
(defn clone
[a]
(js/Object.assign #js {} a))
(defn merge!
([a b]
(js/Object.assign a b))
([a b & more]
(reduce merge! (merge! a b) more)))
(defn merge
([a b]
(js/Object.assign #js {} a b))
([a b & more]
(reduce merge! (merge a b) more)))
(defn set!
[obj key value]
(unchecked-set obj key value)
obj)
(defn update!
[obj key f & args]
(let [found (get obj key ::not-found)]
(if-not (identical? ::not-found found)
(do (unchecked-set obj key (apply f found args))
obj)
obj)))
(defn- props-key-fn
[key]
(if (or (= key :class) (= key :class-name))
"className"
(str/camel (name key))))
(defn clj->props
[props]
(clj->js props :keyword-fn props-key-fn))
(defn ^boolean in?
[obj prop]
(js* "~{} in ~{}" prop obj))

View file

@ -20,9 +20,11 @@
[app.main.ui.routes :as rt] [app.main.ui.routes :as rt]
[app.main.worker :as worker] [app.main.worker :as worker]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.globals :as glob]
[app.util.i18n :as i18n] [app.util.i18n :as i18n]
[app.util.theme :as theme] [app.util.theme :as theme]
[beicon.core :as rx] [beicon.core :as rx]
[cuerdas.core :as str]
[debug] [debug]
[potok.core :as ptk] [potok.core :as ptk]
[rumext.alpha :as mf])) [rumext.alpha :as mf]))
@ -58,12 +60,18 @@
(rx/take 1) (rx/take 1)
(rx/map #(rt/init-routes))))))) (rx/map #(rt/init-routes)))))))
(def essential-only?
(let [href (.-href ^js glob/location)]
(str/includes? href "essential=t")))
(defn ^:export init (defn ^:export init
[] []
(worker/init!) (when-not essential-only?
(sentry/init!) (worker/init!)
(i18n/init! cf/translations) (sentry/init!)
(theme/init! cf/themes) (i18n/init! cf/translations)
(theme/init! cf/themes))
(init-ui) (init-ui)
(st/emit! (initialize))) (st/emit! (initialize)))

View file

@ -101,10 +101,12 @@
bool-shape (bool/bool-shape shape-wrapper)] bool-shape (bool/bool-shape shape-wrapper)]
(mf/fnc bool-wrapper (mf/fnc bool-wrapper
[{:keys [shape] :as props}] [{:keys [shape] :as props}]
(let [childs (->> (cp/get-children (:id shape) objects) (let [childs (mf/use-memo
(select-keys objects))] (mf/deps (:id shape) objects)
[:& bool-shape {:shape shape (fn []
:childs childs}])))) (->> (cp/get-children (:id shape) objects)
(select-keys objects))))]
[:& bool-shape {:shape shape :childs childs}]))))
(defn svg-raw-wrapper-factory (defn svg-raw-wrapper-factory
[objects] [objects]
@ -221,26 +223,39 @@
(mf/defc frame-svg (mf/defc frame-svg
{::mf/wrap [mf/memo]} {::mf/wrap [mf/memo]}
[{:keys [objects frame zoom] :or {zoom 1} :as props}] [{:keys [objects frame zoom] :or {zoom 1} :as props}]
(let [modifier (-> (gpt/point (:x frame) (:y frame)) (let [frame-id (:id frame)
(gpt/negate)
(gmt/translate-matrix))
frame-id (:id frame)
include-metadata? (mf/use-ctx export/include-metadata-ctx) include-metadata? (mf/use-ctx export/include-metadata-ctx)
modifier-ids (concat [frame-id] (cp/get-children frame-id objects)) modifier
update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier) (mf/use-memo
objects (reduce update-fn objects modifier-ids) (mf/deps (:x frame) (:y frame))
frame (assoc-in frame [:modifiers :displacement] modifier) (fn []
(-> (gpt/point (:x frame) (:y frame))
(gpt/negate)
(gmt/translate-matrix))))
objects
(mf/use-memo
(mf/deps frame-id objects modifier)
(fn []
(let [update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier)]
(->> (cp/get-children frame-id objects)
(into [frame-id])
(reduce update-fn objects)))))
frame
(mf/use-memo
(mf/deps modifier)
(fn [] (assoc-in frame [:modifiers :displacement] modifier)))
wrapper
(mf/use-memo
(mf/deps objects)
(fn [] (frame-wrapper-factory objects)))
width (* (:width frame) zoom) width (* (:width frame) zoom)
height (* (:height frame) zoom) height (* (:height frame) zoom)
vbox (format-viewbox {:width (:width frame 0) :height (:height frame 0)}) vbox (format-viewbox {:width (:width frame 0) :height (:height frame 0)})]
wrapper (mf/use-memo
(mf/deps objects)
#(frame-wrapper-factory objects))]
[:svg {:view-box vbox [:svg {:view-box vbox
:width (ust/format-precision width viewbox-decimal-precision) :width (ust/format-precision width viewbox-decimal-precision)
@ -254,19 +269,25 @@
(mf/defc component-svg (mf/defc component-svg
{::mf/wrap [mf/memo #(mf/deferred % ts/idle-then-raf)]} {::mf/wrap [mf/memo #(mf/deferred % ts/idle-then-raf)]}
[{:keys [objects group zoom] :or {zoom 1} :as props}] [{:keys [objects group zoom] :or {zoom 1} :as props}]
(let [modifier (-> (gpt/point (:x group) (:y group)) (let [group-id (:id group)
(gpt/negate)
(gmt/translate-matrix))
group-id (:id group)
include-metadata? (mf/use-ctx export/include-metadata-ctx) include-metadata? (mf/use-ctx export/include-metadata-ctx)
modifier-ids (concat [group-id] (cp/get-children group-id objects)) modifier
(mf/use-memo
(mf/deps (:x group) (:y group))
(fn []
(-> (gpt/point (:x group) (:y group))
(gpt/negate)
(gmt/translate-matrix))))
update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier) objects
modifiers (reduce update-fn {} modifier-ids) (mf/use-memo
objects (gsh/merge-modifiers objects modifiers) (mf/deps modifier objects group-id)
(fn []
(let [modifier-ids (concat [group-id] (cp/get-children group-id objects))
update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier)
modifiers (reduce update-fn {} modifier-ids)]
(gsh/merge-modifiers objects modifiers))))
group (get objects group-id) group (get objects group-id)
width (* (:width group) zoom) width (* (:width group) zoom)
@ -276,7 +297,7 @@
group-wrapper group-wrapper
(mf/use-memo (mf/use-memo
(mf/deps objects) (mf/deps objects)
#(group-wrapper-factory objects))] (fn [] (group-wrapper-factory objects)))]
[:svg {:view-box vbox [:svg {:view-box vbox
:width (ust/format-precision width viewbox-decimal-precision) :width (ust/format-precision width viewbox-decimal-precision)
@ -285,31 +306,51 @@
:xmlns "http://www.w3.org/2000/svg" :xmlns "http://www.w3.org/2000/svg"
:xmlnsXlink "http://www.w3.org/1999/xlink" :xmlnsXlink "http://www.w3.org/1999/xlink"
:xmlns:penpot (when include-metadata? "https://penpot.app/xmlns")} :xmlns:penpot (when include-metadata? "https://penpot.app/xmlns")}
[:> shape-container {:shape group} [:> shape-container {:shape group}
[:& group-wrapper {:shape group :view-box vbox}]]])) [:& group-wrapper {:shape group :view-box vbox}]]]))
(mf/defc component-symbol (mf/defc component-symbol
[{:keys [id data] :as props}] {::mf/wrap-props false}
[props]
(let [id (obj/get props "id")
data (obj/get props "data")
name (:name data)
path (:path data)
objects (:objects data)
root (get objects id)
selrect (:selrect root)
(let [{:keys [name path objects]} data vbox
root (get objects id) (format-viewbox
{:width (:width selrect)
:height (:height selrect)})
{:keys [width height]} (:selrect root) modifier
vbox (format-viewbox {:width width :height height}) (mf/use-memo
(mf/deps (:x root) (:y root))
(fn []
(-> (gpt/point (:x root) (:y root))
(gpt/negate)
(gmt/translate-matrix))))
modifier (-> (gpt/point (:x root) (:y root)) objects
(gpt/negate) (mf/use-memo
(gmt/translate-matrix)) (mf/deps modifier id objects)
(fn []
(let [modifier-ids (concat [id] (cp/get-children id objects))
update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier)]
(reduce update-fn objects modifier-ids))))
modifier-ids (concat [id] (cp/get-children id objects)) root
update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier) (mf/use-memo
objects (reduce update-fn objects modifier-ids) (mf/deps modifier root)
root (assoc-in root [:modifiers :displacement] modifier) (fn [] (assoc-in root [:modifiers :displacement] modifier)))
group-wrapper group-wrapper
(mf/use-memo (mf/use-memo
(mf/deps objects) (mf/deps objects)
#(group-wrapper-factory objects))] (fn [] (group-wrapper-factory objects)))]
[:> "symbol" #js {:id (str id) [:> "symbol" #js {:id (str id)
:viewBox vbox :viewBox vbox
@ -321,10 +362,9 @@
(mf/defc components-sprite-svg (mf/defc components-sprite-svg
{::mf/wrap-props false} {::mf/wrap-props false}
[props] [props]
(let [data (obj/get props "data")
(let [data (obj/get props "data") children (obj/get props "children")
children (obj/get props "children") embed? (obj/get props "embed?")
embed? (obj/get props "embed?")
include-metadata? (obj/get props "include-metadata?")] include-metadata? (obj/get props "include-metadata?")]
[:& (mf/provider embed/context) {:value embed?} [:& (mf/provider embed/context) {:value embed?}
[:& (mf/provider export/include-metadata-ctx) {:value include-metadata?} [:& (mf/provider export/include-metadata-ctx) {:value include-metadata?}

View file

@ -7,7 +7,6 @@
(ns app.main.ui.dashboard.export (ns app.main.ui.dashboard.export
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.main.data.events :as ev]
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
@ -15,7 +14,6 @@
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
[beicon.core :as rx] [beicon.core :as rx]
[potok.core :as ptk]
[rumext.alpha :as mf])) [rumext.alpha :as mf]))
(def ^:const options [:all :merge :detach]) (def ^:const options [:all :merge :detach])
@ -60,10 +58,6 @@
start-export start-export
(fn [] (fn []
(st/emit! (ptk/event ::ev/event {::ev/name "export-files"
:num-files (count (:files @state))
:option @selected-option}))
(swap! state assoc :status :exporting) (swap! state assoc :status :exporting)
(->> (uw/ask-many! (->> (uw/ask-many!
{:cmd :export-file {:cmd :export-file

View file

@ -162,6 +162,7 @@
(mf/deps files current-team-id) (mf/deps files current-team-id)
(fn [_] (fn [_]
(st/emit! (ptk/event ::ev/event {::ev/name "export-files" (st/emit! (ptk/event ::ev/event {::ev/name "export-files"
::ev/origin "dashboard"
:num-files (count files)})) :num-files (count files)}))
(->> (rx/from files) (->> (rx/from files)
(rx/flat-map (rx/flat-map

View file

@ -153,7 +153,7 @@
(fn [] (fn []
(->> (rx/zip (->> (rx/zip
(repo/query! :font-variants {:file-id file-id}) (repo/query! :font-variants {:file-id file-id})
(repo/query! :file {:id file-id})) (repo/query! :trimmed-file {:id file-id :page-id page-id :object-id object-id}))
(rx/subs (rx/subs
(fn [[fonts {:keys [data]}]] (fn [[fonts {:keys [data]}]]
(when (seq fonts) (when (seq fonts)

View file

@ -199,6 +199,18 @@
:penpot:suffix suffix :penpot:suffix suffix
:penpot:scale (str scale)}]))) :penpot:scale (str scale)}])))
(defn str->style
[style-str]
(if (string? style-str)
(->> (str/split style-str ";")
(map str/trim)
(map #(str/split % ":"))
(group-by first)
(map (fn [[key val]]
(vector (keyword key) (second (first val)))))
(into {}))
style-str))
(defn style->str (defn style->str
[style] [style]
(->> style (->> style
@ -229,7 +241,8 @@
[:& render-xml {:xml def-xml}]])])) [:& render-xml {:xml def-xml}]])]))
(when (= (:type shape) :svg-raw) (when (= (:type shape) :svg-raw)
(let [props (let [shape (-> shape (d/update-in-when [:content :attrs :style] str->style))
props
(-> (obj/new) (-> (obj/new)
(obj/set! "penpot:x" (:x shape)) (obj/set! "penpot:x" (:x shape))
(obj/set! "penpot:y" (:y shape)) (obj/set! "penpot:y" (:y shape))
@ -263,7 +276,6 @@
(mf/defc export-data (mf/defc export-data
[{:keys [shape]}] [{:keys [shape]}]
(let [props (-> (obj/new) (add-data shape) (add-library-refs shape))] (let [props (-> (obj/new) (add-data shape) (add-library-refs shape))]
(js/console.log props)
[:> "penpot:shape" props [:> "penpot:shape" props
(export-shadow-data shape) (export-shadow-data shape)
(export-blur-data shape) (export-blur-data shape)

View file

@ -9,6 +9,7 @@
[app.common.data :as d] [app.common.data :as d]
[app.common.math :as mth] [app.common.math :as mth]
[app.config :as cf] [app.config :as cf]
[app.main.data.events :as ev]
[app.main.data.messages :as dm] [app.main.data.messages :as dm]
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
[app.main.data.workspace :as dw] [app.main.data.workspace :as dw]
@ -25,6 +26,7 @@
[app.util.router :as rt] [app.util.router :as rt]
[beicon.core :as rx] [beicon.core :as rx]
[okulary.core :as l] [okulary.core :as l]
[potok.core :as ptk]
[rumext.alpha :as mf])) [rumext.alpha :as mf]))
;; --- Zoom Widget ;; --- Zoom Widget
@ -152,6 +154,10 @@
(mf/use-callback (mf/use-callback
(mf/deps file team-id) (mf/deps file team-id)
(fn [_] (fn [_]
(st/emit! (ptk/event ::ev/event {::ev/name "export-files"
::ev/origin "workspace"
:num-files 1}))
(->> (rx/of file) (->> (rx/of file)
(rx/flat-map (rx/flat-map
(fn [file] (fn [file]
@ -251,7 +257,7 @@
(tr "workspace.header.menu.hide-palette") (tr "workspace.header.menu.hide-palette")
(tr "workspace.header.menu.show-palette"))] (tr "workspace.header.menu.show-palette"))]
[:span.shortcut (sc/get-tooltip :toggle-palette)]] [:span.shortcut (sc/get-tooltip :toggle-palette)]]
[:li {:on-click #(st/emit! (dw/toggle-layout-flags :display-artboard-names))} [:li {:on-click #(st/emit! (dw/toggle-layout-flags :display-artboard-names))}
[:span [:span
(if (contains? layout :display-artboard-names) (if (contains? layout :display-artboard-names)

View file

@ -96,18 +96,21 @@
::mf/wrap-props false} ::mf/wrap-props false}
[props] [props]
(let [shape (unchecked-get props "shape") (when-let [shape (unchecked-get props "shape")]
objects (unchecked-get props "objects") (let [objects (unchecked-get props "objects")
thumbnail? (unchecked-get props "thumbnail?") thumbnail? (unchecked-get props "thumbnail?")
children (-> (mapv (d/getf objects) (:shapes shape)) children
(hooks/use-equal-memo)) (-> (mapv (d/getf objects) (:shapes shape))
all-children (-> (cp/get-children-objects (:id shape) objects) (hooks/use-equal-memo))
(hooks/use-equal-memo))
show-thumbnail? (and thumbnail? (some? (:thumbnail shape)))] all-children
(-> (cp/get-children-objects (:id shape) objects)
(hooks/use-equal-memo))
show-thumbnail?
(and thumbnail? (some? (:thumbnail shape)))]
(when (some? shape)
[:g.frame-wrapper {:display (when (:hidden shape) "none")} [:g.frame-wrapper {:display (when (:hidden shape) "none")}
[:> shape-container {:shape shape} [:> shape-container {:shape shape}
[:& ff/fontfaces-style {:shapes all-children}] [:& ff/fontfaces-style {:shapes all-children}]

View file

@ -11,7 +11,7 @@
(defn on-error (defn on-error
[error] [error]
(js/console.error "Error on worker" error)) (js/console.error "Error on worker" (pr-str error)))
(defonce instance (atom nil)) (defonce instance (atom nil))

View file

@ -11,7 +11,8 @@
(defn generate* (defn generate*
[{:keys [name color size] [{:keys [name color size]
:or {color "var(--color-black)" size 128}}] :or {color "#000000" size 128}}]
(let [parts (str/words (str/upper name)) (let [parts (str/words (str/upper name))
letters (if (= 1 (count parts)) letters (if (= 1 (count parts))
(ffirst parts) (ffirst parts)
@ -27,7 +28,7 @@
(obj/set! context "font" (str (/ size 2) "px Arial")) (obj/set! context "font" (str (/ size 2) "px Arial"))
(obj/set! context "textAlign" "center") (obj/set! context "textAlign" "center")
(obj/set! context "fillStyle" "var(--color-white)") (obj/set! context "fillStyle" "#ffffff")
(.fillText context letters (/ size 2) (/ size 1.5)) (.fillText context letters (/ size 2) (/ size 1.5))
(.toDataURL canvas))) (.toDataURL canvas)))

View file

@ -210,7 +210,8 @@
svg-node (if (= :svg tag) svg-node (if (= :svg tag)
(->> node :content last :content last) (->> node :content last :content last)
(->> node :content last))] (->> node :content last))
svg-node (d/update-in-when svg-node [:attrs :style] parse-style)]
(merge (add-attrs {} (:attrs svg-node)) node-attrs)) (merge (add-attrs {} (:attrs svg-node)) node-attrs))
(= type :bool) (= type :bool)
@ -633,7 +634,8 @@
(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)
attrs (-> (:attrs svg-content) (without-penpot-prefix)) attrs (-> (:attrs svg-content) (without-penpot-prefix)
(d/update-when :style parse-style))
tag (-> svg-content :attrs :penpot:tag keyword) tag (-> svg-content :attrs :penpot:tag keyword)
node-content node-content
@ -641,13 +643,17 @@
(= tag :svg) (= tag :svg)
(->> node :content last :content last :content fix-style-attr) (->> node :content last :content last :content fix-style-attr)
(= tag :text) (some? (:content svg-content))
(-> node :content last :content))] (->> (:content svg-content)
(assoc (filter #(= :penpot:svg-child (:tag %)))
props :content (mapv :content)
{:attrs attrs (first)))]
:tag tag (-> props
:content node-content}))) (assoc
:content
{:attrs attrs
:tag tag
:content node-content}))))
(defn add-frame-data [props node] (defn add-frame-data [props node]
(let [grids (parse-grids node)] (let [grids (parse-grids node)]

View file

@ -53,7 +53,7 @@
(post {:payload result})) (post {:payload result}))
(reply-error [err] (reply-error [err]
(.error js/console "error" err) (.error js/console "error" (pr-str err))
(post {:error {:data (ex-data err) (post {:error {:data (ex-data err)
:message (ex-message err)}})) :message (ex-message err)}}))

View file

@ -141,38 +141,30 @@
(rx/map #(hash-map :file-id file-id :library-id %)) (rx/map #(hash-map :file-id file-id :library-id %))
(rx/flat-map (partial rp/mutation :link-file-to-library))))) (rx/flat-map (partial rp/mutation :link-file-to-library)))))
(defn persist-file [file]
(rp/mutation :persist-temp-file {:id (:id file)}))
(defn send-changes (defn send-changes
"Creates batches of changes to be sent to the backend" "Creates batches of changes to be sent to the backend"
[context file] [context file]
(let [revn (atom (:revn file)) (let [file-id (:id file)
file-id (:id file)
session-id (uuid/next) session-id (uuid/next)
changes-batches batches (->> (fb/generate-changes file)
(->> (fb/generate-changes file) (partition change-batch-size change-batch-size nil)
(partition change-batch-size change-batch-size nil) (mapv vec))
(mapv vec))
current (atom 0) processed (atom 0)
total (count changes-batches)] total (count batches)]
(rx/concat (rx/concat
(->> (rx/from changes-batches) (->> (rx/from (d/enumerate batches))
(rx/mapcat (rx/merge-map
(fn [change-batch] (fn [[i change-batch]]
(->> (rp/mutation :update-file (->> (rp/mutation :update-temp-file
{:id file-id {:id file-id
:session-id session-id :session-id session-id
:revn @revn :revn i
:changes change-batch}) :changes change-batch})
(rx/tap #(do (swap! current inc) (rx/tap #(do (swap! processed inc)
(progress! context (progress! context :upload-data @processed total))))))
:upload-data @current total))))))
(rx/map first) (rx/map first)
(rx/tap #(reset! revn (:revn %)))
(rx/ignore)) (rx/ignore))
(->> (rp/mutation :persist-temp-file {:id file-id}) (->> (rp/mutation :persist-temp-file {:id file-id})
@ -340,7 +332,7 @@
file (-> file (fb/add-page page-data))] file (-> file (fb/add-page page-data))]
(->> (rx/from nodes) (->> (rx/from nodes)
(rx/filter cip/shape?) (rx/filter cip/shape?)
(rx/mapcat (partial resolve-media context file-id)) (rx/merge-map (partial resolve-media context file-id))
(rx/reduce (partial process-import-node context) file) (rx/reduce (partial process-import-node context) file)
(rx/map (comp fb/close-page setup-interactions))))) (rx/map (comp fb/close-page setup-interactions)))))
@ -362,7 +354,7 @@
(rx/filter cip/shape?) (rx/filter cip/shape?)
(rx/skip 1) (rx/skip 1)
(rx/skip-last 1) (rx/skip-last 1)
(rx/mapcat (partial resolve-media context file-id)) (rx/merge-map (partial resolve-media context file-id))
(rx/reduce (partial process-import-node context) file) (rx/reduce (partial process-import-node context) file)
(rx/map fb/finish-component)))) (rx/map fb/finish-component))))
@ -382,6 +374,7 @@
(fn [[page-id page-name]] (fn [[page-id page-name]]
(->> (get-file context :page page-id) (->> (get-file context :page page-id)
(rx/map (fn [page-data] [page-id page-name page-data]))))) (rx/map (fn [page-data] [page-id page-name page-data])))))
(rx/concat-reduce (partial import-page context) file)))) (rx/concat-reduce (partial import-page context) file))))
(defn process-library-colors (defn process-library-colors
@ -420,7 +413,7 @@
(let [resolve (:resolve context)] (let [resolve (:resolve context)]
(->> (get-file context :media-list) (->> (get-file context :media-list)
(rx/flat-map (comp d/kebab-keys cip/string->uuid)) (rx/flat-map (comp d/kebab-keys cip/string->uuid))
(rx/mapcat (rx/merge-map
(fn [[id media]] (fn [[id media]]
(let [media (assoc media :id (resolve id))] (let [media (assoc media :id (resolve id))]
(->> (get-file context :media id media) (->> (get-file context :media id media)
@ -432,12 +425,11 @@
:content content :content content
:is-local false}))) :is-local false})))
(rx/tap #(progress! context :upload-media (:name %))) (rx/tap #(progress! context :upload-media (:name %)))
(rx/flat-map #(rp/mutation! :upload-file-media-object %)) (rx/merge-map #(rp/mutation! :upload-file-media-object %))
(rx/map (constantly media)) (rx/map (constantly media))
(rx/catch #(do (.error js/console (str "Error uploading media: " (:name media)) ) (rx/catch #(do (.error js/console (str "Error uploading media: " (:name media)) )
(rx/empty))))))) (rx/empty)))))))
(rx/reduce fb/add-library-media file))) (rx/reduce fb/add-library-media file)))
(rx/of file))) (rx/of file)))
(defn process-library-components (defn process-library-components
@ -511,7 +503,7 @@
(fn [[file data]] (fn [[file data]]
(->> (uz/load-from-url (:uri data)) (->> (uz/load-from-url (:uri data))
(rx/map #(-> context (assoc :zip %) (merge data))) (rx/map #(-> context (assoc :zip %) (merge data)))
(rx/flat-map (rx/merge-map
(fn [context] (fn [context]
;; process file retrieves a stream that will emit progress notifications ;; process file retrieves a stream that will emit progress notifications
;; and other that will emit the files once imported ;; and other that will emit the files once imported

View file

@ -34,7 +34,7 @@
(let [uri (u/join (cfg/get-public-uri) "api/rpc/query/page") (let [uri (u/join (cfg/get-public-uri) "api/rpc/query/page")
params {:file-id file-id params {:file-id file-id
:id page-id :id page-id
:strip-thumbnails true}] :strip-frames-with-thumbnails true}]
(->> (http/send! (->> (http/send!
{:method :get {:method :get
:uri uri :uri uri