0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-03-13 16:21:57 -05:00

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

This commit is contained in:
Andrey Antukh 2024-04-08 14:07:43 +02:00
commit fb24918fd9
34 changed files with 410 additions and 367 deletions

View file

@ -91,8 +91,8 @@
:jmx-remote
{:jvm-opts ["-Dcom.sun.management.jmxremote"
"-Dcom.sun.management.jmxremote.port=9090"
"-Dcom.sun.management.jmxremote.rmi.port=9090"
"-Dcom.sun.management.jmxremote.port=9091"
"-Dcom.sun.management.jmxremote.rmi.port=9091"
"-Dcom.sun.management.jmxremote.local.only=false"
"-Dcom.sun.management.jmxremote.authenticate=false"
"-Dcom.sun.management.jmxremote.ssl=false"

View file

@ -1,30 +1,33 @@
[{:id "material-design-3"
:name "Material Design 3"
:file-uri "https://github.com/penpot/penpot-files/raw/main/Material%20Design%203.penpot"}
{:id "tutorial-for-beginners"
[{:id "tutorial-for-beginners"
:name "Tutorial for beginners"
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/tutorial-for-beginners.penpot"}
{:id "penpot-design-system"
:name "Penpot Design System"
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Penpot-Design-system.penpot"}
{:id "flex-layout-playground"
:name "Flex Layout Playground"
:file-uri "https://github.com/penpot/penpot-files/raw/main/Flex%20Layout%20Playground.penpot"}
{:id "lucide-icons"
:name "Lucide Icons"
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Lucide-icons.penpot"}
{:id "font-awesome"
:name "Font Awesome"
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Font-Awesome.penpot"}
{:id "plants-app"
:name "Plants app"
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Plants-app.penpot"}
{:id "wireframing-kit"
:name "Wireframing Kit"
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/wireframing-kit.penpot"}
{:id "ant-design"
:name "Ant Design UI Kit (lite)"
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Ant-Design-UI-Kit-Lite.penpot"}
{:id "cocomaterial"
:name "Cocomaterial"
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Cocomaterial.penpot"}
{:id "circum-icons"
:name "Circum Icons pack"
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/CircumIcons.penpot"}
{:id "coreui"
:name "CoreUI"
:file-uri "https://github.com/penpot/penpot-files/raw/main/CoreUI%20DesignSystem%20(DEMO).penpot"}
{:id "black-white-mobile-templates"
:name "Black & White Mobile Templates"
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Black-White-Mobile-Templates.penpot"}
{:id "avataaars"
:name "Avataaars"
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Avataaars-by-Pablo-Stanley.penpot"}
{:id "ux-notes"
:name "UX Notes"
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/UX-Notes.penpot"}
{:id "whiteboarding-kit"
:name "Whiteboarding Kit"
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Whiteboarding-mapping-kit.penpot"}]
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Whiteboarding-mapping-kit.penpot"}
{:id "open-color-scheme"
:name "Open Color Scheme"
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Open-Color-Scheme.penpot"}
{:id "flex-layout-playground"
:name "Flex Layout Playground"
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Flex-Layout-Playground.penpot"}]

View file

@ -6,7 +6,7 @@
alwaysWriteExceptions="true" />
</Console>
<RollingFile name="main" fileName="logs/main.log" filePattern="logs/main-%i.log">
<RollingFile name="main" fileName="logs/main-latest.log" filePattern="logs/main-%i.log">
<PatternLayout pattern="[%d{YYYY-MM-dd HH:mm:ss.SSS}] %level{length=1} %logger{36} - %msg%n"
alwaysWriteExceptions="true" />
<Policies>
@ -15,7 +15,7 @@
<DefaultRolloverStrategy max="9"/>
</RollingFile>
<RollingFile name="reports" fileName="logs/reports.log" filePattern="logs/reports-%i.log">
<RollingFile name="reports" fileName="logs/reports-latest.log" filePattern="logs/reports-%i.log">
<PatternLayout pattern="[%d{YYYY-MM-dd HH:mm:ss.SSS}] %level{length=1} %logger{36} - %msg%n"
alwaysWriteExceptions="true" />
<Policies>

View file

@ -1,6 +1,6 @@
#!/usr/bin/env bash
source /home/penpot/backend/environ
source /home/penpot/environ
export PENPOT_FLAGS="$PENPOT_FLAGS disable-backend-worker"
export OPTIONS="
@ -12,13 +12,13 @@ export OPTIONS="
-J-XX:+UnlockDiagnosticVMOptions \
-J-XX:+DebugNonSafepoints \
-J-Djdk.tracePinnedThreads=full \
-J-XX:+UseTransparentHugePages \
-J-XX:ReservedCodeCacheSize=1g \
-J-Dpolyglot.engine.WarnInterpreterOnly=false \
-J--enable-preview";
# Setup HEAP
#export OPTIONS="$OPTIONS -J-Xms900m -J-Xmx900m -J-XX:+AlwaysPreTouch"
export OPTIONS="$OPTIONS -J-Xms1g -J-Xmx25g"
#export OPTIONS="$OPTIONS -J-Xms900m -J-Xmx900m -J-XX:+AlwaysPreTouch"
export OPTIONS="$OPTIONS -J-Xms320g -J-Xmx320g -J-XX:+AlwaysPreTouch"
export PENPOT_HTTP_SERVER_IO_THREADS=2
export PENPOT_HTTP_SERVER_WORKER_THREADS=2
@ -33,11 +33,10 @@ export PENPOT_HTTP_SERVER_WORKER_THREADS=2
# export OPTIONS="$OPTIONS -J-Xint"
# Setup GC
export OPTIONS="$OPTIONS -J-XX:+UseG1GC -J-Xlog:gc:logs/gc.log"
export OPTIONS="$OPTIONS -J-XX:+UseG1GC -J-Xlog:gc:logs/gc.log"
# Setup GC
#export OPTIONS="$OPTIONS -J-XX:+UseZGC -J-XX:+ZGenerational -J-Xlog:gc:gc.log"
#export OPTIONS="$OPTIONS -J-XX:+UseZGC -J-XX:+ZGenerational -J-Xlog:gc:logs/gc.log"
# Enable ImageMagick v7.x support
# export OPTIONS="-J-Dim4java.useV7=true $OPTIONS";
@ -46,4 +45,4 @@ export OPTIONS_EVAL="nil"
# export OPTIONS_EVAL="(set! *warn-on-reflection* true)"
set -ex
exec clojure $OPTIONS -M -e "$OPTIONS_EVAL" -m rebel-readline.main
exec clojure $OPTIONS -M -e "$OPTIONS_EVAL" -m rebel-readline.main

View file

@ -16,6 +16,7 @@
[app.common.files.migrations :as fmg]
[app.common.files.shapes-helpers :as cfsh]
[app.common.files.validate :as cfv]
[app.common.fressian :as fres]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.geom.rect :as grc]
@ -48,18 +49,18 @@
[app.rpc.commands.files-snapshot :as fsnap]
[app.rpc.commands.media :as cmd.media]
[app.storage :as sto]
[app.storage.impl :as impl]
[app.storage.tmp :as tmp]
[app.svgo :as svgo]
[app.util.blob :as blob]
[app.util.cache :as cache]
[app.util.events :as events]
[app.util.pointer-map :as pmap]
[app.util.time :as dt]
[buddy.core.codecs :as bc]
[clojure.set :refer [rename-keys]]
[cuerdas.core :as str]
[datoteka.fs :as fs]
[datoteka.io :as io]
[promesa.exec :as px]
[promesa.util :as pu]))
(def ^:dynamic *stats*
@ -68,7 +69,7 @@
(def ^:dynamic *cache*
"A dynamic var for setting up a cache instance."
nil)
false)
(def ^:dynamic *skip-on-graphic-error*
"A dynamic var for setting up the default error behavior for graphics processing."
@ -100,6 +101,8 @@
(some? data)
(assoc :data (blob/decode data))))
(set! *warn-on-reflection* true)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; FILE PREPARATION BEFORE MIGRATION
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -872,10 +875,11 @@
(fix-shape [shape]
(if (or (nil? (:parent-id shape)) (ctk/instance-head? shape))
(let [frame? (= :frame (:type shape))]
(assoc shape
:type :frame ; Old groups must be converted
:fills (or (:fills shape) []) ; to frames and conform to spec
(let [frame? (= :frame (:type shape))
not-group? (not= :group (:type shape))]
(assoc shape ; Old groups must be converted
:type :frame ; to frames and conform to spec
:fills (if not-group? (d/nilv (:fills shape) []) []) ; Groups never should have fill
:shapes (or (:shapes shape) [])
:hide-in-viewer (if frame? (boolean (:hide-in-viewer shape)) true)
:show-content (if frame? (boolean (:show-content shape)) true)
@ -1254,7 +1258,7 @@
:frame-id frame-id
:parent-id frame-id})
(assoc
:proportion (/ width height)
:proportion (float (/ width height))
:proportion-lock true))
img-shape (cts/setup-shape
@ -1295,7 +1299,7 @@
(try
(let [item (if (str/starts-with? href "data:")
(let [[mtype data] (parse-datauri href)
size (alength data)
size (alength ^bytes data)
path (tmp/tempfile :prefix "penpot.media.download.")
written (io/write-to-file! data path :size size)]
@ -1364,27 +1368,49 @@
{::sql/columns [:media-id]})]
(:media-id fmobject)))
(defn- get-sobject-content
(defn get-sobject-content
[id]
(let [storage (::sto/storage *system*)
sobject (sto/get-object storage id)]
(when-not sobject
(throw (RuntimeException. "sobject is nil")))
(when (> (:size sobject) 1135899)
(throw (RuntimeException. "svg too big")))
(with-open [stream (sto/get-object-data storage sobject)]
(slurp stream))))
(defn get-optimized-svg
[sid]
(let [svg-text (get-sobject-content sid)
svg-text (svgo/optimize *system* svg-text)]
(csvg/parse svg-text)))
(def base-path "/data/cache")
(defn get-sobject-cache-path
[sid]
(let [path (impl/id->path sid)]
(fs/join base-path path)))
(defn get-cached-svg
[sid]
(let [path (get-sobject-cache-path sid)]
(if (fs/exists? path)
(with-open [^java.lang.AutoCloseable stream (io/input-stream path)]
(let [reader (fres/reader stream)]
(fres/read! reader)))
(get-optimized-svg sid))))
(defn- create-shapes-for-svg
[{:keys [id] :as mobj} file-id objects frame-id position]
(let [get-svg (fn [sid]
(let [svg-text (get-sobject-content sid)
svg-text (svgo/optimize *system* svg-text)]
(-> (csvg/parse svg-text)
(assoc :name (:name mobj)))))
sid (resolve-sobject-id id)
svg-data (if (cache/cache? *cache*)
(cache/get *cache* sid (px/wrap-bindings get-svg))
(get-svg sid))
svg-data (collect-and-persist-images svg-data file-id id)]
(let [sid (resolve-sobject-id id)
svg-data (if *cache*
(get-cached-svg sid)
(get-optimized-svg sid))
svg-data (collect-and-persist-images svg-data file-id id)
svg-data (assoc svg-data :name (:name mobj))]
(sbuilder/create-svg-shapes svg-data position objects frame-id frame-id #{} false)))
@ -1663,6 +1689,7 @@
(db/update! conn :file
{:data (blob/encode (:data file))
:features (db/create-array conn "text" (:features file))
:version (:version file)
:revn (:revn file)}
{:id (:id file)})))
@ -1712,7 +1739,7 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn migrate-file!
[system file-id & {:keys [validate? skip-on-graphic-error? label]}]
[system file-id & {:keys [validate? skip-on-graphic-error? label rown]}]
(let [tpoint (dt/tpoint)
err (volatile! false)]
@ -1752,24 +1779,14 @@
components (get @*file-stats* :processed-components 0)
graphics (get @*file-stats* :processed-graphics 0)]
(if (cache/cache? *cache*)
(let [cache-stats (cache/stats *cache*)]
(l/dbg :hint "migrate:file:end"
:file-id (str file-id)
:graphics graphics
:components components
:validate validate?
:crt (mth/to-fixed (:hit-rate cache-stats) 2)
:crq (str (:req-count cache-stats))
:error @err
:elapsed (dt/format-duration elapsed)))
(l/dbg :hint "migrate:file:end"
:file-id (str file-id)
:graphics graphics
:components components
:validate validate?
:error @err
:elapsed (dt/format-duration elapsed)))
(l/dbg :hint "migrate:file:end"
:file-id (str file-id)
:graphics graphics
:components components
:validate validate?
:rown rown
:error @err
:elapsed (dt/format-duration elapsed))
(some-> *stats* (swap! update :processed-files (fnil inc 0)))
(some-> *team-stats* (swap! update :processed-files (fnil inc 0)))))))))
@ -1831,21 +1848,9 @@
(when-not @err
(some-> *stats* (swap! update :processed-teams (fnil inc 0))))
(if (cache/cache? *cache*)
(let [cache-stats (cache/stats *cache*)]
(l/dbg :hint "migrate:team:end"
:team-id (dm/str team-id)
:files files
:components components
:graphics graphics
:crt (mth/to-fixed (:hit-rate cache-stats) 2)
:crq (str (:req-count cache-stats))
:error @err
:elapsed (dt/format-duration elapsed)))
(l/dbg :hint "migrate:team:end"
:team-id (dm/str team-id)
:files files
:components components
:graphics graphics
:elapsed (dt/format-duration elapsed)))))))))
(l/dbg :hint "migrate:team:end"
:team-id (dm/str team-id)
:files files
:components components
:graphics graphics
:elapsed (dt/format-duration elapsed))))))))

View file

@ -72,11 +72,11 @@
;; are not very friendly with virtual threads, and for avoid
;; unexpected blocking of other concurrent operations we
;; dispatch that operation to a dedicated executor.
(let [result (px/submit! executor (partial bf.v1/import-files! cfg input))]
(let [result (px/invoke! executor (partial bf.v1/import-files! cfg input))]
(db/update! conn :project
{:modified-at (dt/now)}
{:id project-id})
(deref result)))))
result))))
(def ^:private
schema:import-binfile

View file

@ -134,6 +134,9 @@
file))
file)]
;; Delete changes from the changes history
(db/delete! conn :file-change {:file-id id})
(db/update! conn :file
{:deleted-at nil
:revn 1

View file

@ -7,18 +7,19 @@
(ns app.srepl.components-v2
(:require
[app.common.data :as d]
[app.common.fressian :as fres]
[app.common.logging :as l]
[app.common.uuid :as uuid]
[app.db :as db]
[app.features.components-v2 :as feat]
[app.main :as main]
[app.srepl.helpers :as h]
[app.svgo :as svgo]
[app.util.cache :as cache]
[app.util.events :as events]
[app.util.time :as dt]
[app.worker :as-alias wrk]
[cuerdas.core :as str]
[datoteka.fs :as fs]
[datoteka.io :as io]
[promesa.exec :as px]
[promesa.exec.semaphore :as ps]
[promesa.util :as pu]))
@ -68,7 +69,8 @@
(def ^:private sql:get-teams-by-created-at
"WITH teams AS (
SELECT id, features
SELECT id, features,
row_number() OVER (ORDER BY created_at) AS rown
FROM team
WHERE deleted_at IS NULL
ORDER BY created_at DESC
@ -77,6 +79,7 @@
(def ^:private sql:get-teams-by-graphics
"WITH teams AS (
SELECT t.id, t.features,
row_number() OVER (ORDER BY t.created_at) AS rown,
(SELECT count(*)
FROM file_media_object AS fmo
JOIN file AS f ON (f.id = fmo.file_id)
@ -93,6 +96,7 @@
(def ^:private sql:get-teams-by-activity
"WITH teams AS (
SELECT t.id, t.features,
row_number() OVER (ORDER BY t.created_at) AS rown,
(SELECT coalesce(max(date_trunc('month', f.modified_at)), date_trunc('month', t.modified_at))
FROM file AS f
JOIN project AS p ON (f.project_id = p.id)
@ -107,24 +111,16 @@
)
SELECT * FROM teams %(pred)s")
(def ^:private sql:get-teams-by-report
"WITH teams AS (
SELECT t.id t.features, mr.name
FROM migration_team_report AS mr
JOIN team AS t ON (t.id = mr.team_id)
WHERE t.deleted_at IS NULL
AND mr.error IS NOT NULL
ORDER BY mr.created_at
) SELECT id, features FROM teams %(pred)s")
(def ^:private sql:get-files-by-created-at
"SELECT id, features
"SELECT id, features,
row_number() OVER (ORDER BY created_at DESC) AS rown
FROM file
WHERE deleted_at IS NULL
ORDER BY created_at DESC")
(def ^:private sql:get-files-by-modified-at
"SELECT id, features
row_number() OVER (ORDER BY modified_at DESC) AS rown
FROM file
WHERE deleted_at IS NULL
ORDER BY modified_at DESC")
@ -132,6 +128,7 @@
(def ^:private sql:get-files-by-graphics
"WITH files AS (
SELECT f.id, f.features,
row_number() OVER (ORDER BY modified_at) AS rown,
(SELECT count(*) FROM file_media_object AS fmo
WHERE fmo.mtype = 'image/svg+xml'
AND fmo.is_local = false
@ -141,16 +138,6 @@
ORDER BY 3 ASC
) SELECT * FROM files %(pred)s")
(def ^:private sql:get-files-by-report
"WITH files AS (
SELECT f.id, f.features, mr.label
FROM migration_file_report AS mr
JOIN file AS f ON (f.id = mr.file_id)
WHERE f.deleted_at IS NULL
AND mr.error IS NOT NULL
ORDER BY mr.created_at
) SELECT id, features FROM files %(pred)s")
(defn- read-pred
[entries]
(let [entries (if (and (vector? entries)
@ -181,8 +168,7 @@
sql (case query
:created-at sql:get-teams-by-created-at
:activity sql:get-teams-by-activity
:graphics sql:get-teams-by-graphics
:report sql:get-teams-by-report)
:graphics sql:get-teams-by-graphics)
sql (if pred
(let [[pred-sql & pred-params] (read-pred pred)]
(apply vector
@ -193,8 +179,7 @@
(->> (db/cursor conn sql {:chunk-size 500})
(map feat/decode-row)
(remove (fn [{:keys [features]}]
(contains? features "components/v2")))
(map :id))))
(contains? features "components/v2"))))))
(defn- get-files
[conn query pred]
@ -202,8 +187,7 @@
sql (case query
:created-at sql:get-files-by-created-at
:modified-at sql:get-files-by-modified-at
:graphics sql:get-files-by-graphics
:report sql:get-files-by-report)
:graphics sql:get-files-by-graphics)
sql (if pred
(let [[pred-sql & pred-params] (read-pred pred)]
(apply vector
@ -214,60 +198,7 @@
(->> (db/cursor conn sql {:chunk-size 500})
(map feat/decode-row)
(remove (fn [{:keys [features]}]
(contains? features "components/v2")))
(map :id))))
(def ^:private sql:team-report-table
"CREATE UNLOGGED TABLE IF NOT EXISTS migration_team_report (
id bigserial NOT NULL,
label text NOT NULL,
team_id UUID NOT NULL,
error text NULL,
created_at timestamptz NOT NULL DEFAULT now(),
elapsed bigint NOT NULL,
PRIMARY KEY (label, created_at, id))")
(def ^:private sql:file-report-table
"CREATE UNLOGGED TABLE IF NOT EXISTS migration_file_report (
id bigserial NOT NULL,
label text NOT NULL,
file_id UUID NOT NULL,
error text NULL,
created_at timestamptz NOT NULL DEFAULT now(),
elapsed bigint NOT NULL,
PRIMARY KEY (label, created_at, id))")
(defn- create-report-tables!
[system]
(db/exec-one! system [sql:team-report-table])
(db/exec-one! system [sql:file-report-table]))
(defn- clean-team-reports!
[system label]
(db/delete! system :migration-team-report {:label label}))
(defn- team-report!
[system team-id label elapsed error]
(db/insert! system :migration-team-report
{:label label
:team-id team-id
:elapsed (inst-ms elapsed)
:error error}
{::db/return-keys false}))
(defn- clean-file-reports!
[system label]
(db/delete! system :migration-file-report {:label label}))
(defn- file-report!
[system file-id label elapsed error]
(db/insert! system :migration-file-report
{:label label
:file-id file-id
:elapsed (inst-ms elapsed)
:error error}
{::db/return-keys false}))
(contains? features "components/v2"))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; PUBLIC API
@ -280,11 +211,7 @@
skip-on-graphic-error? true}}]
(l/dbg :hint "migrate:start" :rollback rollback?)
(let [tpoint (dt/tpoint)
file-id (h/parse-uuid file-id)
cache (if (int? cache)
(cache/create :executor (::wrk/executor main/system)
:max-items cache)
nil)]
file-id (h/parse-uuid file-id)]
(binding [feat/*stats* (atom {})
feat/*cache* cache]
@ -315,12 +242,7 @@
(let [team-id (h/parse-uuid team-id)
stats (atom {})
tpoint (dt/tpoint)
cache (if (int? cache)
(cache/create :executor (::wrk/executor main/system)
:max-items cache)
nil)]
tpoint (dt/tpoint)]
(add-watch stats :progress-report (report-progress-files tpoint))
@ -347,7 +269,7 @@
"A REPL helper for migrate all teams.
This function starts multiple concurrent team migration processes
until thw maximum number of jobs is reached which by default has the
until the maximum number of jobs is reached which by default has the
value of `1`. This is controled with the `:max-jobs` option.
If you want to run this on multiple machines you will need to specify
@ -383,41 +305,30 @@
sjobs (ps/create :permits max-jobs)
sprocs (ps/create :permits max-procs)
cache (if (int? cache)
(cache/create :executor (::wrk/executor main/system)
:max-items cache)
nil)
migrate-team
(fn [team-id]
(let [tpoint (dt/tpoint)]
(try
(db/tx-run! (assoc main/system ::db/rollback rollback?)
(fn [system]
(db/exec-one! system ["SET idle_in_transaction_session_timeout = 0"])
(feat/migrate-team! system team-id
:label label
:validate? validate?
:skip-on-graphic-error? skip-on-graphic-error?)))
(try
(db/tx-run! (assoc main/system ::db/rollback rollback?)
(fn [system]
(db/exec-one! system ["SET LOCAL idle_in_transaction_session_timeout = 0"])
(feat/migrate-team! system team-id
:label label
:validate? validate?
:skip-on-graphic-error? skip-on-graphic-error?)))
(when (string? label)
(team-report! main/system team-id label (tpoint) nil))
(catch Throwable cause
(l/wrn :hint "unexpected error on processing team (skiping)"
:team-id (str team-id))
(catch Throwable cause
(l/wrn :hint "unexpected error on processing team (skiping)"
:team-id (str team-id))
(events/tap :error
(ex-info "unexpected error on processing team (skiping)"
{:team-id team-id}
cause))
(events/tap :error
(ex-info "unexpected error on processing team (skiping)"
{:team-id team-id}
cause))
(swap! stats update :errors (fnil inc 0)))
(swap! stats update :errors (fnil inc 0))
(when (string? label)
(team-report! main/system team-id label (tpoint) (ex-message cause))))
(finally
(ps/release! sjobs)))))
(finally
(ps/release! sjobs))))
process-team
(fn [team-id]
@ -445,23 +356,18 @@
feat/*cache* cache
svgo/*semaphore* sprocs]
(try
(when (string? label)
(create-report-tables! main/system)
(clean-team-reports! main/system label))
(db/tx-run! main/system
(fn [{:keys [::db/conn] :as system}]
(db/exec! conn ["SET statement_timeout = 0"])
(db/exec! conn ["SET idle_in_transaction_session_timeout = 0"])
(db/exec! conn ["SET LOCAL statement_timeout = 0"])
(db/exec! conn ["SET LOCAL idle_in_transaction_session_timeout = 0"])
(run! process-team
(->> (get-teams conn query pred)
(filter (fn [team-id]
(filter (fn [{:keys [rown]}]
(if (int? partitions)
(= current-partition (-> (uuid/hash-int team-id)
(mod partitions)
(inc)))
(= current-partition (inc (mod rown partitions)))
true)))
(map :id)
(take max-items)))
;; Close and await tasks
@ -480,7 +386,6 @@
:rollback rollback?
:elapsed elapsed)))))))
(defn migrate-files!
"A REPL helper for migrate all files.
@ -521,56 +426,45 @@
sjobs (ps/create :permits max-jobs)
sprocs (ps/create :permits max-procs)
cache (if (int? cache)
(cache/create :executor (::wrk/executor main/system)
:max-items cache)
nil)
migrate-file
(fn [file-id]
(let [tpoint (dt/tpoint)]
(try
(db/tx-run! (assoc main/system ::db/rollback rollback?)
(fn [system]
(db/exec-one! system ["SET idle_in_transaction_session_timeout = 0"])
(feat/migrate-file! system file-id
:label label
:validate? validate?
:skip-on-graphic-error? skip-on-graphic-error?)))
(fn [file-id rown]
(try
(db/tx-run! (assoc main/system ::db/rollback rollback?)
(fn [system]
(db/exec-one! system ["SET LOCAL idle_in_transaction_session_timeout = 0"])
(feat/migrate-file! system file-id
:rown rown
:label label
:validate? validate?
:skip-on-graphic-error? skip-on-graphic-error?)))
(when (string? label)
(file-report! main/system file-id label (tpoint) nil))
(catch Throwable cause
(l/wrn :hint "unexpected error on processing file (skiping)"
:file-id (str file-id))
(catch Throwable cause
(l/wrn :hint "unexpected error on processing file (skiping)"
:file-id (str file-id))
(events/tap :error
(ex-info "unexpected error on processing file (skiping)"
{:file-id file-id}
cause))
(events/tap :error
(ex-info "unexpected error on processing file (skiping)"
{:file-id file-id}
cause))
(swap! stats update :errors (fnil inc 0)))
(swap! stats update :errors (fnil inc 0))
(when (string? label)
(file-report! main/system file-id label (tpoint) (ex-message cause))))
(finally
(ps/release! sjobs)))))
(finally
(ps/release! sjobs))))
process-file
(fn [file-id]
(fn [{:keys [id rown]}]
(ps/acquire! sjobs)
(let [ts (tpoint)]
(if (and mtime (neg? (compare mtime ts)))
(do
(l/inf :hint "max time constraint reached"
:file-id (str file-id)
:file-id (str id)
:elapsed (dt/format-duration ts))
(ps/release! sjobs)
(reduced nil))
(px/run! executor (partial migrate-file file-id)))))]
(px/run! executor (partial migrate-file id rown)))))]
(l/dbg :hint "migrate:start"
:label label
@ -584,22 +478,16 @@
feat/*cache* cache
svgo/*semaphore* sprocs]
(try
(when (string? label)
(create-report-tables! main/system)
(clean-file-reports! main/system label))
(db/tx-run! main/system
(fn [{:keys [::db/conn] :as system}]
(db/exec! conn ["SET statement_timeout = 0"])
(db/exec! conn ["SET idle_in_transaction_session_timeout = 0"])
(db/exec! conn ["SET LOCAL statement_timeout = 0"])
(db/exec! conn ["SET LOCAL idle_in_transaction_session_timeout = 0"])
(run! process-file
(->> (get-files conn query pred)
(filter (fn [file-id]
(filter (fn [{:keys [rown] :as row}]
(if (int? partitions)
(= current-partition (-> (uuid/hash-int file-id)
(mod partitions)
(inc)))
(= current-partition (inc (mod rown partitions)))
true)))
(take max-items)))
@ -619,6 +507,100 @@
:rollback rollback?
:elapsed elapsed)))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; CACHE POPULATE
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def sql:sobjects-for-cache
"SELECT id,
row_number() OVER (ORDER BY created_at) AS index
FROM storage_object
WHERE (metadata->>'~:bucket' = 'file-media-object' OR
metadata->>'~:bucket' IS NULL)
AND metadata->>'~:content-type' = 'image/svg+xml'
AND deleted_at IS NULL
AND size < 1135899
ORDER BY created_at ASC")
(defn populate-cache!
"A REPL helper for migrate all files.
This function starts multiple concurrent file migration processes
until thw maximum number of jobs is reached which by default has the
value of `1`. This is controled with the `:max-jobs` option.
If you want to run this on multiple machines you will need to specify
the total number of partitions and the current partition.
In order to get the report table populated, you will need to provide
a correct `:label`. That label is also used for persist a file
snaphot before continue with the migration."
[& {:keys [max-jobs] :or {max-jobs 1}}]
(let [tpoint (dt/tpoint)
factory (px/thread-factory :virtual false :prefix "penpot/cache/")
executor (px/cached-executor :factory factory)
sjobs (ps/create :permits max-jobs)
retrieve-sobject
(fn [id index]
(let [path (feat/get-sobject-cache-path id)
parent (fs/parent path)]
(try
(when-not (fs/exists? parent)
(fs/create-dir parent))
(if (fs/exists? path)
(l/inf :hint "create cache entry" :status "exists" :index index :id (str id) :path (str path))
(let [svg-data (feat/get-optimized-svg id)]
(with-open [^java.lang.AutoCloseable stream (io/output-stream path)]
(let [writer (fres/writer stream)]
(fres/write! writer svg-data)))
(l/inf :hint "create cache entry" :status "created"
:index index
:id (str id)
:path (str path))))
(catch Throwable cause
(l/wrn :hint "create cache entry"
:status "error"
:index index
:id (str id)
:path (str path)
:cause cause))
(finally
(ps/release! sjobs)))))
process-sobject
(fn [{:keys [id index]}]
(ps/acquire! sjobs)
(px/run! executor (partial retrieve-sobject id index)))]
(l/dbg :hint "migrate:start"
:max-jobs max-jobs)
(try
(binding [feat/*system* main/system]
(run! process-sobject
(db/exec! main/system [sql:sobjects-for-cache]))
;; Close and await tasks
(pu/close! executor))
{:elapsed (dt/format-duration (tpoint))}
(catch Throwable cause
(l/dbg :hint "populate:error" :cause cause))
(finally
(let [elapsed (dt/format-duration (tpoint))]
(l/dbg :hint "populate:end"
:elapsed elapsed))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; FILE PROCESS HELPERS

View file

@ -16,6 +16,8 @@
[clojure.set :as set]
[cuerdas.core :as str]))
#?(:clj (set! *warn-on-reflection* true))
(declare reduce-objects)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -327,12 +329,9 @@
"Selects the shape that will be the base to add the shapes over"
[objects selected]
(let [;; Gets the tree-index for all the shapes
indexed-shapes (indexed-shapes objects)
indexed-shapes (indexed-shapes objects selected)
;; Filters the selected and retrieve a list of ids
sorted-ids (->> indexed-shapes
(filter (comp selected second))
(map second))]
sorted-ids (map val indexed-shapes)]
;; The first id will be the top-most
(get objects (first sorted-ids))))
@ -486,43 +485,62 @@
(reduce add-element (d/ordered-set) ids)))
(defn indexed-shapes
"Retrieves a list with the indexes for each element in the layer tree.
This will be used for shift+selection."
[objects]
(letfn [(red-fn [cur-idx id]
(let [[prev-idx _] (first cur-idx)
prev-idx (or prev-idx 0)
cur-idx (conj cur-idx (d/vec2 (inc prev-idx) id))]
(rec-index cur-idx id)))
(rec-index [cur-idx id]
(let [object (get objects id)]
(reduce red-fn cur-idx (reverse (:shapes object)))))]
(into {} (rec-index '() uuid/zero))))
(defn- indexed-shapes
"Retrieves a vector with the indexes for each element in the layer
tree. This will be used for shift+selection."
[objects selected]
(loop [index 1
result (transient [])
;; Flag to start adding elements to the index
add? false
;; Only add elements while we're in the selection, we finish when the selection is over
pending (set selected)
shapes (-> objects
(get uuid/zero)
(get :shapes)
(rseq))]
(let [shape-id (first shapes)]
(if (and (d/not-empty? pending) shape-id)
(let [shape (get objects shape-id)
add? (or add? (contains? selected shape-id))
pending (disj pending shape-id)
result (if add?
(conj! result (d/vec2 index shape-id))
result)]
(if-let [children (get shape :shapes)]
(recur (inc index)
result
add?
pending
(concat (rseq children) (rest shapes)))
(recur (inc index)
result
add?
pending
(rest shapes))))
(persistent! result)))))
(defn expand-region-selection
"Given a selection selects all the shapes between the first and last in
an indexed manner (shift selection)"
[objects selection]
(let [indexed-shapes (indexed-shapes objects)
filter-indexes (->> indexed-shapes
(filter (comp selection second))
(map first))
from (apply min filter-indexes)
to (apply max filter-indexes)]
(->> indexed-shapes
(filter (fn [[idx _]] (and (>= idx from) (<= idx to))))
(map second)
(into #{}))))
(let [selection (if (set? selection) selection (set selection))
indexed-shapes (indexed-shapes objects selection)
indexes (map key indexed-shapes)
from (apply min indexes)
to (apply max indexes)
xform (comp
(filter (fn [[idx _]] (and (>= idx from) (<= idx to))))
(map val))]
(into #{} xform indexed-shapes)))
(defn order-by-indexed-shapes
[objects ids]
(let [ids (if (set? ids) ids (set ids))]
(->> (indexed-shapes objects)
(filter (fn [o] (contains? ids (val o))))
(sort-by key)
(map val))))
"Retrieves a ordered vector for each element in the layer tree and
filted by selected set"
[objects selected]
(let [selected (if (set? selected) selected (set selected))]
(sequence (map val) (indexed-shapes objects selected))))
(defn get-index-replacement
"Given a collection of shapes, calculate their positions

View file

@ -13,27 +13,27 @@
(defn assign-proportions
[shape]
(let [{:keys [width height]} (:selrect shape)]
(assoc shape :proportion (/ width height))))
;; --- Setup Proportions
(assoc shape :proportion (float (/ width height))))) ; Note: we need to convert explicitly to float.
; In Clojure (not clojurescript) when we divide
;; --- Setup Proportions ; two integers it does not create a float, but
; a clojure.lang.Ratio object.
(defn setup-proportions-image
[{:keys [metadata] :as shape}]
(let [{:keys [width height]} metadata]
(assoc shape
:proportion (/ width height)
:proportion (float (/ width height))
:proportion-lock true)))
(defn setup-proportions-size
[{{:keys [width height]} :selrect :as shape}]
(assoc shape
:proportion (/ width height)
:proportion (float (/ width height))
:proportion-lock true))
(defn setup-proportions-const
[shape]
(assoc shape
:proportion 1
:proportion 1.0
:proportion-lock false))
(defn setup-proportions

View file

@ -483,8 +483,8 @@
(defn- setup-image
[{:keys [metadata] :as shape}]
(-> shape
(assoc :proportion (/ (:width metadata)
(:height metadata)))
(assoc :proportion (float (/ (:width metadata)
(:height metadata))))
(assoc :proportion-lock true)))
(defn setup-shape

View file

@ -543,19 +543,21 @@
(or (:layout-item-z-index shape) 0)))
(defn- comparator-layout-z-index
[[idx-a child-a] [idx-b child-b]]
[reverse? [idx-a child-a] [idx-b child-b]]
(cond
(> (layout-z-index child-a) (layout-z-index child-b)) 1
(< (layout-z-index child-a) (layout-z-index child-b)) -1
(and (< idx-a idx-b) reverse?) -1
(and (> idx-a idx-b) reverse?) 1
(< idx-a idx-b) 1
(> idx-a idx-b) -1
:else 0))
(defn sort-layout-children-z-index
[children]
[children reverse?]
(->> children
(d/enumerate)
(sort comparator-layout-z-index)
(sort (partial comparator-layout-z-index reverse?))
(mapv second)))
(defn change-h-sizing?
@ -1292,11 +1294,14 @@
(->> (range start-index (inc to-index))
(map vector shape-ids)
(reduce (fn [[parent cells] [shape-id idx]]
(let [[parent cells] (free-cell-push parent cells idx)]
[(update-in parent [:layout-grid-cells (get-in cells [idx :id])]
assoc :position :manual
:shapes [shape-id])
cells]))
;; If the shape to put in a cell is the same that is already in the cell we do nothing
(if (= shape-id (get-in parent [:layout-grid-cells (get-in cells [idx :id]) :shapes 0]))
[parent cells]
(let [[parent cells] (free-cell-push parent cells idx)]
[(update-in parent [:layout-grid-cells (get-in cells [idx :id])]
assoc :position :manual
:shapes [shape-id])
cells])))
[parent cells])
(first)))
parent)))

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 176 KiB

View file

@ -273,7 +273,8 @@
;; color
attrs (cond-> attrs
(:gradient attrs) (get-in [:gradient :stops 0]))
new-attrs (merge (get-in shape [:shadow index :color]) attrs)]
new-attrs (-> (merge (get-in shape [:shadow index :color]) attrs)
(d/without-nils))]
(assoc-in shape [:shadow index :color] new-attrs))))))))
(defn add-shadow
@ -602,7 +603,8 @@
(merge data)
(materialize-color-components))))
(-> state
(dissoc :gradient :stops :editing-stop)))))))
(dissoc :gradient :stops :editing-stop)
(assoc :type :color)))))))
ptk/WatchEvent
(watch [_ state _]
(when add-recent?

View file

@ -60,8 +60,7 @@
(let [local (:comments-local state)]
(cond
(:draft local) (rx/of (dcm/close-thread))
(:open local) (rx/of (dcm/close-thread))
:else (rx/of #(dissoc % :workspace-drawing)))))))
(:open local) (rx/of (dcm/close-thread)))))))
;; Event responsible of the what should be executed when user clicked
;; on the comments layer. An option can be create a new draft thread,

View file

@ -28,6 +28,10 @@
ptk/UpdateEvent
(update [_ state]
(-> state
(update :workspace-layout (fn [workspace-layout]
(if (= tool :comments)
(disj workspace-layout :document-history)
workspace-layout)))
(update :workspace-drawing assoc :tool tool)
;; When changing drawing tool disable "scale text" mode
;; automatically, to help users that ignore how this

View file

@ -585,7 +585,6 @@
:else
[move-vector nil])]
(-> (dwm/create-modif-tree ids (ctm/move-modifiers move-vector))
(dwm/build-change-frame-modifiers objects selected target-frame drop-index cell-data)
(dwm/set-modifiers false false {:snap-ignore-axis snap-ignore-axis}))))))
@ -608,11 +607,11 @@
(->> move-stream
(rx/last)
(rx/mapcat
(fn [[_ target-frame drop-index cell-data]]
(fn [[_ target-frame drop-index drop-cell]]
(let [undo-id (js/Symbol)]
(rx/of (dwu/start-undo-transaction undo-id)
(dwm/apply-modifiers {:undo-transation? false})
(move-shapes-to-frame ids target-frame drop-index cell-data)
(move-shapes-to-frame ids target-frame drop-index drop-cell)
(finish-transform)
(dwu/commit-undo-transaction undo-id))))))))))))))

View file

@ -239,6 +239,7 @@
(let [form (or (unchecked-get props "form")
(mf/use-ctx form-ctx))
name (unchecked-get props "name")
image (unchecked-get props "image")
current-value (or (dm/get-in @form [:data name] "")
(unchecked-get props "value"))
@ -260,7 +261,9 @@
(when (fn? on-change)
(on-change name value)))))]
[:div {:class (dm/str class " " (stl/css :custom-radio))}
[:div {:class (if image
class
(dm/str class " " (stl/css :custom-radio)))}
(for [{:keys [image icon value label area]} options]
(let [image? (some? image)
icon? (some? icon)

View file

@ -167,6 +167,7 @@
{:label (tr "questions.never-used-one") :area "image6" :value "never-used-a-tool" :icon i/curve}
{:label (tr "questions.other") :value "other" :area "other"}]
:name :experience-design-tool
:image true
:class (stl/css :image-radio)
:on-change on-design-tool-change}]

View file

@ -168,9 +168,10 @@
[props]
(let [shape (unchecked-get props "shape")
childs (unchecked-get props "childs")
reverse? (and (ctl/flex-layout? shape) (ctl/reverse? shape))
childs (cond-> childs
(ctl/any-layout? shape)
(ctl/sort-layout-children-z-index))]
(ctl/sort-layout-children-z-index reverse?))]
[:> frame-container props
[:g.frame-children

View file

@ -92,7 +92,8 @@
(fn []
(if from-viewer
(st/emit! (dcm/update-options {:show-sidebar? false}))
(st/emit! :interrupt (dw/deselect-all true)))))
(st/emit! (dw/clear-edition-mode)
(dw/deselect-all true)))))
tgroups (->> threads
(dcm/group-threads-by-page))

View file

@ -10,6 +10,7 @@
[app.main.data.events :as ev]
[app.main.data.shortcuts :as scd]
[app.main.data.workspace :as dw]
[app.main.data.workspace.drawing.common :as dwc]
[app.main.data.workspace.shortcuts :as sc]
[app.main.refs :as refs]
[app.main.store :as st]
@ -21,7 +22,6 @@
[app.main.ui.workspace.presence :refer [active-sessions]]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[app.util.timers :as ts]
[okulary.core :as l]
[rumext.v2 :as mf]))
@ -141,7 +141,7 @@
(mf/defc right-header
{::mf/wrap-props false}
[{:keys [file layout page-id]}]
(let [file-id (:id file)
(let [file-id (:id file)
zoom (mf/deref refs/selected-zoom)
read-only? (mf/use-ctx ctx/workspace-read-only?)
@ -169,33 +169,29 @@
active-comments
(mf/use-fn
(mf/deps layout)
(fn []
(st/emit! :interrupt
(dw/clear-edition-mode))
;; Delay so anything that launched :interrupt can finish
(ts/schedule 100 #(st/emit! (dw/select-for-drawing :comments)))))
(dw/clear-edition-mode)
(-> (dw/remove-layout-flag :document-history)
(vary-meta assoc ::ev/origin "workspace-header"))
(dw/select-for-drawing :comments))))
toggle-comments
(mf/use-fn
(mf/deps selected-drawtool)
(fn [_]
(when (contains? layout :document-history)
(st/emit! (-> (dw/remove-layout-flag :document-history)
(vary-meta assoc ::ev/origin "workspace-header"))))
(if (= :comments selected-drawtool)
(st/emit! :interrupt)
(if (= selected-drawtool :comments)
(st/emit! (dwc/clear-drawing))
(active-comments))))
toggle-history
(mf/use-fn
(mf/deps selected-drawtool)
(fn []
(when (= :comments selected-drawtool)
(st/emit! :interrupt
(-> (dw/toggle-layout-flag :comments)
(vary-meta assoc ::ev/origin "workspace-header"))))
(dw/clear-edition-mode)))
(st/emit! (-> (dw/toggle-layout-flag :document-history)
(vary-meta assoc ::ev/origin "workspace-header")))))]

View file

@ -266,7 +266,7 @@
rule-area-size (/ rulers/ruler-area-size zoom)]
(hooks/setup-dom-events zoom disable-paste in-viewport? workspace-read-only?)
(hooks/setup-dom-events zoom disable-paste in-viewport? workspace-read-only? drawing-tool drawing-path?)
(hooks/setup-viewport-size vport viewport-ref)
(hooks/setup-cursor cursor alt? mod? space? panning drawing-tool drawing-path? node-editing? z? workspace-read-only?)
(hooks/setup-keyboard alt? mod? space? z? shift?)

View file

@ -17,6 +17,7 @@
[app.common.uuid :as uuid]
[app.main.data.shortcuts :as dsc]
[app.main.data.workspace :as dw]
[app.main.data.workspace.edition :as dwe]
[app.main.data.workspace.grid-layout.shortcuts :as gsc]
[app.main.data.workspace.path.shortcuts :as psc]
[app.main.data.workspace.shortcuts :as wsc]
@ -39,13 +40,26 @@
[rumext.v2 :as mf])
(:import goog.events.EventType))
(defn setup-dom-events [zoom disable-paste in-viewport? workspace-read-only?]
(defn setup-dom-events [zoom disable-paste in-viewport? workspace-read-only? drawing-tool drawing-path?]
(let [on-key-down (actions/on-key-down)
on-key-up (actions/on-key-up)
on-mouse-wheel (actions/on-mouse-wheel zoom)
on-paste (actions/on-paste disable-paste in-viewport? workspace-read-only?)
on-pointer-down (mf/use-fn
(mf/deps drawing-tool drawing-path?)
(fn [_]
(when drawing-path?
(st/emit! (dwe/clear-edition-mode)))))
on-blur (mf/use-fn #(st/emit! (mse/->BlurEvent)))]
(mf/use-effect
(mf/deps drawing-tool drawing-path?)
(fn []
(let [keys [(events/listen js/window EventType.POINTERDOWN on-pointer-down)]]
(fn []
(doseq [key keys]
(events/unlistenByKey key))))))
(mf/use-layout-effect
(mf/deps on-key-down on-key-up on-mouse-wheel on-paste workspace-read-only?)
(fn []

View file

@ -197,6 +197,12 @@
bs (* 4 zoom-inverse)]
[:*
[:g.viewport-frame-background
;; Fix for a Firefox bug that shows some strange artifacts when creating shape
[:rect {:x 0 :y 0 :width 1 :height 1
:fill "none"
:stroke-width 0.1
:stroke "rgba(0,0,0,0)"}]
;; This goes behind because if it goes in front the background bleeds through
[:path {:d (rulers-inside-path x1 y1 x2 y2 br bw)
:fill "none"

View file

@ -25,7 +25,6 @@
([objects shape level]
(when (and (some? shape) (some? (:selrect shape)))
(let [indent (str/repeat " " level)
maybe-reverse (if (ctl/any-layout? shape) reverse identity)
shape-html
(cond
@ -65,15 +64,18 @@
indent)
:else
(dm/fmt "%<div class=\"%\">\n%\n%</div>"
indent
(dm/str (d/name (:type shape)) " "
(cgc/shape->selector shape))
(->> (:shapes shape)
(maybe-reverse)
(map #(generate-html objects (get objects %) (inc level)))
(str/join "\n"))
indent))
(let [children (->> shape :shapes (map #(get objects %)))
reverse? (ctl/any-layout? shape)
;; The order for layout elements is the reverse of SVG order
children (cond-> children reverse? reverse)]
(dm/fmt "%<div class=\"%\">\n%\n%</div>"
indent
(dm/str (d/name (:type shape)) " "
(cgc/shape->selector shape))
(->> children
(map #(generate-html objects % (inc level)))
(str/join "\n"))
indent)))
shape-html
(if (cgc/has-wrapper? objects shape)

View file

@ -614,14 +614,14 @@
(rx/tap #(rx/end! progress-str)))]))
(defn create-files
[{:keys [features] :as context} files]
[{:keys [system-features] :as context} files]
(let [data (group-by :file-id files)]
(rx/concat
(->> (rx/from files)
(rx/map #(merge context %))
(rx/merge-map (fn [context]
(->> (create-file context features)
(->> (create-file context system-features)
(rx/map #(vector % (first (get data (:file-id context)))))))))
(->> (rx/from files)
@ -694,7 +694,7 @@
(let [context {:project-id project-id
:resolve (resolve-factory)
:features features}
:system-features features}
zip-files (filter #(= "application/zip" (:type %)) files)
binary-files (filter #(= "application/octet-stream" (:type %)) files)]