diff --git a/backend/deps.edn b/backend/deps.edn index afd1e6840b..90d78fa219 100644 --- a/backend/deps.edn +++ b/backend/deps.edn @@ -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" diff --git a/backend/resources/app/onboarding.edn b/backend/resources/app/onboarding.edn index 0438d25ba0..3a94d29edb 100644 --- a/backend/resources/app/onboarding.edn +++ b/backend/resources/app/onboarding.edn @@ -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"}] diff --git a/backend/resources/log4j2-experiments.xml b/backend/resources/log4j2-experiments.xml index 3357aae31f..a874af5d84 100644 --- a/backend/resources/log4j2-experiments.xml +++ b/backend/resources/log4j2-experiments.xml @@ -6,7 +6,7 @@ alwaysWriteExceptions="true" /> - + @@ -15,7 +15,7 @@ - + diff --git a/backend/scripts/repl-test b/backend/scripts/repl-test index a1333a5317..2ba1acdbfd 100755 --- a/backend/scripts/repl-test +++ b/backend/scripts/repl-test @@ -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 \ No newline at end of file +exec clojure $OPTIONS -M -e "$OPTIONS_EVAL" -m rebel-readline.main diff --git a/backend/src/app/features/components_v2.clj b/backend/src/app/features/components_v2.clj index 15d5221c8f..36f0a5ad55 100644 --- a/backend/src/app/features/components_v2.clj +++ b/backend/src/app/features/components_v2.clj @@ -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)))))))) diff --git a/backend/src/app/rpc/commands/binfile.clj b/backend/src/app/rpc/commands/binfile.clj index 8f2216e636..60148eb7e4 100644 --- a/backend/src/app/rpc/commands/binfile.clj +++ b/backend/src/app/rpc/commands/binfile.clj @@ -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 diff --git a/backend/src/app/rpc/commands/files_temp.clj b/backend/src/app/rpc/commands/files_temp.clj index 9ac4f7e5e6..bc183cfd95 100644 --- a/backend/src/app/rpc/commands/files_temp.clj +++ b/backend/src/app/rpc/commands/files_temp.clj @@ -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 diff --git a/backend/src/app/srepl/components_v2.clj b/backend/src/app/srepl/components_v2.clj index 0922904938..00a3c34fb5 100644 --- a/backend/src/app/srepl/components_v2.clj +++ b/backend/src/app/srepl/components_v2.clj @@ -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 diff --git a/common/src/app/common/files/helpers.cljc b/common/src/app/common/files/helpers.cljc index 287bb7bb86..d6a15fcd4e 100644 --- a/common/src/app/common/files/helpers.cljc +++ b/common/src/app/common/files/helpers.cljc @@ -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 diff --git a/common/src/app/common/geom/proportions.cljc b/common/src/app/common/geom/proportions.cljc index c884aa3693..342145b68d 100644 --- a/common/src/app/common/geom/proportions.cljc +++ b/common/src/app/common/geom/proportions.cljc @@ -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 diff --git a/common/src/app/common/types/shape.cljc b/common/src/app/common/types/shape.cljc index e76fbe2a69..2b610ca450 100644 --- a/common/src/app/common/types/shape.cljc +++ b/common/src/app/common/types/shape.cljc @@ -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 diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index c395450412..efccf250c2 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -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))) diff --git a/frontend/resources/images/thumbnails/template-avataaars.jpg b/frontend/resources/images/thumbnails/template-avataaars.jpg new file mode 100644 index 0000000000..d5abeaf8eb Binary files /dev/null and b/frontend/resources/images/thumbnails/template-avataaars.jpg differ diff --git a/frontend/resources/images/thumbnails/template-black-white-mobile-templates.jpg b/frontend/resources/images/thumbnails/template-black-white-mobile-templates.jpg new file mode 100644 index 0000000000..16ce2001c8 Binary files /dev/null and b/frontend/resources/images/thumbnails/template-black-white-mobile-templates.jpg differ diff --git a/frontend/resources/images/thumbnails/template-font-awesome.jpg b/frontend/resources/images/thumbnails/template-font-awesome.jpg new file mode 100644 index 0000000000..56fda33a6a Binary files /dev/null and b/frontend/resources/images/thumbnails/template-font-awesome.jpg differ diff --git a/frontend/resources/images/thumbnails/template-lucide-icons.jpg b/frontend/resources/images/thumbnails/template-lucide-icons.jpg new file mode 100644 index 0000000000..0282b70a8e Binary files /dev/null and b/frontend/resources/images/thumbnails/template-lucide-icons.jpg differ diff --git a/frontend/resources/images/thumbnails/template-open-color-scheme.jpg b/frontend/resources/images/thumbnails/template-open-color-scheme.jpg new file mode 100644 index 0000000000..cd73ddecc9 Binary files /dev/null and b/frontend/resources/images/thumbnails/template-open-color-scheme.jpg differ diff --git a/frontend/resources/images/thumbnails/template-plants-app.jpg b/frontend/resources/images/thumbnails/template-plants-app.jpg new file mode 100644 index 0000000000..d2dfbbcabd Binary files /dev/null and b/frontend/resources/images/thumbnails/template-plants-app.jpg differ diff --git a/frontend/resources/images/thumbnails/template-ux-notes.jpg b/frontend/resources/images/thumbnails/template-ux-notes.jpg new file mode 100644 index 0000000000..43cc3bd992 Binary files /dev/null and b/frontend/resources/images/thumbnails/template-ux-notes.jpg differ diff --git a/frontend/resources/images/walkthrough-cover.png b/frontend/resources/images/walkthrough-cover.png index eb602f871d..109e00b30d 100644 Binary files a/frontend/resources/images/walkthrough-cover.png and b/frontend/resources/images/walkthrough-cover.png differ diff --git a/frontend/src/app/main/data/workspace/colors.cljs b/frontend/src/app/main/data/workspace/colors.cljs index 3a8bf6e13e..287b6c3447 100644 --- a/frontend/src/app/main/data/workspace/colors.cljs +++ b/frontend/src/app/main/data/workspace/colors.cljs @@ -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? diff --git a/frontend/src/app/main/data/workspace/comments.cljs b/frontend/src/app/main/data/workspace/comments.cljs index 246034878e..4718b252cb 100644 --- a/frontend/src/app/main/data/workspace/comments.cljs +++ b/frontend/src/app/main/data/workspace/comments.cljs @@ -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, diff --git a/frontend/src/app/main/data/workspace/drawing.cljs b/frontend/src/app/main/data/workspace/drawing.cljs index c4c3a148dd..1b1d96beee 100644 --- a/frontend/src/app/main/data/workspace/drawing.cljs +++ b/frontend/src/app/main/data/workspace/drawing.cljs @@ -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 diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 25e2660e6e..7cea9cde9d 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -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)))))))))))))) diff --git a/frontend/src/app/main/ui/components/forms.cljs b/frontend/src/app/main/ui/components/forms.cljs index 7d1ad4a46d..9aec3f3022 100644 --- a/frontend/src/app/main/ui/components/forms.cljs +++ b/frontend/src/app/main/ui/components/forms.cljs @@ -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) diff --git a/frontend/src/app/main/ui/onboarding/questions.cljs b/frontend/src/app/main/ui/onboarding/questions.cljs index f7d96dca43..ae9f5d4274 100644 --- a/frontend/src/app/main/ui/onboarding/questions.cljs +++ b/frontend/src/app/main/ui/onboarding/questions.cljs @@ -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}] diff --git a/frontend/src/app/main/ui/shapes/frame.cljs b/frontend/src/app/main/ui/shapes/frame.cljs index c907291ad1..291c27130f 100644 --- a/frontend/src/app/main/ui/shapes/frame.cljs +++ b/frontend/src/app/main/ui/shapes/frame.cljs @@ -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 diff --git a/frontend/src/app/main/ui/workspace/comments.cljs b/frontend/src/app/main/ui/workspace/comments.cljs index 4ff0e1842d..17be9321c0 100644 --- a/frontend/src/app/main/ui/workspace/comments.cljs +++ b/frontend/src/app/main/ui/workspace/comments.cljs @@ -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)) diff --git a/frontend/src/app/main/ui/workspace/right_header.cljs b/frontend/src/app/main/ui/workspace/right_header.cljs index f2e1b53f0a..d3b6a60269 100644 --- a/frontend/src/app/main/ui/workspace/right_header.cljs +++ b/frontend/src/app/main/ui/workspace/right_header.cljs @@ -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")))))] diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 8539def89e..d20d35e3cb 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -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?) diff --git a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs index c2d4ab55b7..d9f1dd1b53 100644 --- a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs @@ -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 [] diff --git a/frontend/src/app/main/ui/workspace/viewport/rulers.cljs b/frontend/src/app/main/ui/workspace/viewport/rulers.cljs index 4237014456..bec8eaf330 100644 --- a/frontend/src/app/main/ui/workspace/viewport/rulers.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/rulers.cljs @@ -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" diff --git a/frontend/src/app/util/code_gen/markup_html.cljs b/frontend/src/app/util/code_gen/markup_html.cljs index 0f1c1ac24a..cb21d00ed7 100644 --- a/frontend/src/app/util/code_gen/markup_html.cljs +++ b/frontend/src/app/util/code_gen/markup_html.cljs @@ -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 "%
\n%\n%
" - 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 "%
\n%\n%
" + 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) diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs index 2130ff4147..998459c604 100644 --- a/frontend/src/app/worker/import.cljs +++ b/frontend/src/app/worker/import.cljs @@ -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)]