@@ -100,7 +108,7 @@ Debug Main Page
-
+
Do not break on index lookup erros (remap operation).
diff --git a/backend/resources/templates/styles.css b/backend/resources/templates/styles.css
index 74b19390a..32fcea888 100644
--- a/backend/resources/templates/styles.css
+++ b/backend/resources/templates/styles.css
@@ -168,3 +168,12 @@ form .row {
padding: 5px 0;
}
+.set-of-inputs {
+ flex-direction: column;
+ display: flex;
+}
+
+.set-of-inputs input:not(:last-child) {
+ margin-bottom: 3px;
+}
+
diff --git a/backend/src/app/http/debug.clj b/backend/src/app/http/debug.clj
index 03c2a090d..1e04be78f 100644
--- a/backend/src/app/http/debug.clj
+++ b/backend/src/app/http/debug.clj
@@ -265,16 +265,21 @@
(defn export-handler
[{:keys [pool] :as cfg} {:keys [params profile-id] :as request}]
- (let [file-id (some-> params :file-id parse-uuid)
- libs? (contains? params :includelibs)
- clone? (contains? params :clone)]
- (when-not file-id
+ (let [file-ids (->> (:file-ids params)
+ (remove empty?)
+ (map parse-uuid))
+ libs? (contains? params :includelibs)
+ clone? (contains? params :clone)
+ embed? (contains? params :embedassets)]
+
+ (when-not (seq file-ids)
(ex/raise :type :validation
:code :missing-arguments))
(let [path (-> cfg
- (assoc ::binf/file-id file-id)
+ (assoc ::binf/file-ids file-ids)
+ (assoc ::binf/embed-assets? embed?)
(assoc ::binf/include-libraries? libs?)
(binf/export!))]
(if clone?
@@ -283,6 +288,7 @@
(assoc cfg
::binf/input path
::binf/overwrite? false
+ ::binf/ignore-index-errors? true
::binf/profile-id profile-id
::binf/project-id project-id))
@@ -294,7 +300,7 @@
(yrs/response
:status 200
:headers {"content-type" "application/octet-stream"
- "content-disposition" (str "attachmen; filename=" file-id ".penpot")}
+ "content-disposition" (str "attachmen; filename=" (first file-ids) ".penpot")}
:body (io/input-stream path))))))
diff --git a/backend/src/app/rpc/commands/binfile.clj b/backend/src/app/rpc/commands/binfile.clj
index dc46177e3..6a2fe9145 100644
--- a/backend/src/app/rpc/commands/binfile.clj
+++ b/backend/src/app/rpc/commands/binfile.clj
@@ -306,7 +306,7 @@
SELECT fl.id, fl.deleted_at
FROM file AS fl
JOIN file_library_rel AS flr ON (flr.library_file_id = fl.id)
- WHERE flr.file_id = ?::uuid
+ WHERE flr.file_id = ANY(?)
UNION
SELECT fl.id, fl.deleted_at
FROM file AS fl
@@ -318,8 +318,10 @@
WHERE l.deleted_at IS NULL OR l.deleted_at > now();")
(defn- retrieve-libraries
- [pool file-id]
- (map :id (db/exec! pool [sql:file-libraries file-id])))
+ [pool ids]
+ (with-open [^AutoCloseable conn (db/open pool)]
+ (let [ids (db/create-array conn "uuid" ids)]
+ (map :id (db/exec! pool [sql:file-libraries ids])))))
(def ^:private sql:file-library-rels
"SELECT * FROM file_library_rel
@@ -330,6 +332,58 @@
(with-open [^AutoCloseable conn (db/open pool)]
(db/exec! conn [sql:file-library-rels (db/create-array conn "uuid" ids)])))
+(defn- embed-file-assets
+ [pool {:keys [id] :as file}]
+ (letfn [(walk-map-form [state form]
+ (cond
+ (uuid? (:fill-color-ref-file form))
+ (do
+ (vswap! state conj [(:fill-color-ref-file form) :colors (:fill-color-ref-id form)])
+ (assoc form :fill-color-ref-file id))
+
+ (uuid? (:stroke-color-ref-file form))
+ (do
+ (vswap! state conj [(:stroke-color-ref-file form) :colors (:stroke-color-ref-id form)])
+ (assoc form :stroke-color-ref-file id))
+
+ (uuid? (:typography-ref-file form))
+ (do
+ (vswap! state conj [(:typography-ref-file form) :typographies (:typography-ref-id form)])
+ (assoc form :typography-ref-file id))
+
+ (uuid? (:component-file form))
+ (do
+ (vswap! state conj [(:component-file form) :components (:component-id form)])
+ (assoc form :component-file id))
+
+ :else
+ form))
+
+ (process-group-of-assets [data [lib-id items]]
+ ;; NOTE: there are a posibility that shape refers to a not
+ ;; existing file because the file was removed. In this
+ ;; case we just ignore the asset.
+ (if-let [lib (retrieve-file pool lib-id)]
+ (reduce #(process-asset %1 lib %2) data items)
+ data))
+
+ (process-asset [data lib [bucket asset-id]]
+ (let [asset (get-in lib [:data bucket asset-id])
+ ;; Add a special case for colors that need to have
+ ;; correctly set the :file-id prop (pending of the
+ ;; refactor that will remove it).
+ asset (cond-> asset
+ (= bucket :colors) (assoc :file-id id))]
+ (update data bucket assoc asset-id asset)))]
+
+ (update file :data (fn [data]
+ (let [assets (volatile! [])]
+ (walk/postwalk #(cond->> % (map? %) (walk-map-form assets)) data)
+ (->> (deref assets)
+ (filter #(as-> (first %) $ (and (uuid? $) (not= $ id))))
+ (d/group-by first rest)
+ (reduce process-group-of-assets data)))))))
+
(defn write-export!
"Do the exportation of a speficied file in custom penpot binary
format. There are some options available for customize the output:
@@ -337,20 +391,47 @@
`::include-libraries?`: additionaly to the specified file, all the
linked libraries also will be included (including transitive
dependencies).
+
+ `::embed-assets?`: instead of including the libraryes, embedd in the
+ same file library all assets used from external libraries.
"
- [{:keys [pool storage ::output ::file-id ::include-libraries?]}]
- (let [libs (when include-libraries?
- (retrieve-libraries pool file-id))
- rels (when include-libraries?
- (retrieve-library-relations pool (cons file-id libs)))
- files (into [file-id] libs)
- sids (atom #{})]
+ [{:keys [pool storage ::output ::file-ids ::include-libraries? ::embed-assets?] :as options}]
+
+ (us/assert! :spec ::db/pool :val pool)
+ (us/assert! :spec ::sto/storage :val storage)
+
+ (us/assert!
+ :expr (every? uuid? file-ids)
+ :hint "`files` should be a vector of uuid")
+
+ (us/assert!
+ :expr (bs/data-output-stream? output)
+ :hint "`output` should be an instance of OutputStream")
+
+ (us/assert!
+ :expr (d/boolean-or-nil? include-libraries?)
+ :hint "invalid value provided for `include-libraries?` option, expected boolean")
+
+ (us/assert!
+ :expr (d/boolean-or-nil? embed-assets?)
+ :hint "invalid value provided for `embed-assets?` option, expected boolean")
+
+ (us/assert!
+ :always? true
+ :expr (not (and include-libraries? embed-assets?))
+ :hint "the `include-libraries?` and `embed-assets?` are mutally excluding options")
+
+ (let [libs (when include-libraries? (retrieve-libraries pool file-ids))
+ files (into file-ids libs)
+ rels (when include-libraries? (retrieve-library-relations pool file-ids))
+ sids (volatile! #{})]
;; Write header with metadata
(l/debug :hint "exportation summary"
:files (count files)
:rels (count rels)
+ :embed-assets? embed-assets?
:include-libs? include-libraries?
::l/async false)
@@ -363,12 +444,13 @@
(l/debug :hint "write section" :section :v1/files :total (count files) ::l/async false)
(write-label! output :v1/files)
(doseq [file-id files]
- (let [file (retrieve-file pool file-id)
+ (let [file (cond->> (retrieve-file pool file-id)
+ embed-assets? (embed-file-assets pool))
media (retrieve-file-media pool file)]
;; Collect all storage ids for later write them all under
;; specific storage objects section.
- (swap! sids into (sequence storage-object-id-xf media))
+ (vswap! sids into (sequence storage-object-id-xf media))
(l/trace :hint "write penpot file"
:id file-id
diff --git a/backend/src/app/util/bytes.clj b/backend/src/app/util/bytes.clj
index 5be58f405..1a36151ab 100644
--- a/backend/src/app/util/bytes.clj
+++ b/backend/src/app/util/bytes.clj
@@ -28,6 +28,18 @@
(def ^:const default-buffer-size
(:xnio/buffer-size yt/defaults))
+(defn input-stream?
+ [s]
+ (instance? InputStream s))
+
+(defn output-stream?
+ [s]
+ (instance? OutputStream s))
+
+(defn data-output-stream?
+ [s]
+ (instance? DataOutputStream s))
+
(defn copy!
[src dst & {:keys [offset size buffer-size]
:or {offset 0 buffer-size default-buffer-size}}]
diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc
index 874ee56b9..baa0f2fe3 100644
--- a/common/src/app/common/data.cljc
+++ b/common/src/app/common/data.cljc
@@ -23,6 +23,9 @@
#?(:clj
(:import linked.set.LinkedSet)))
+(def boolean-or-nil?
+ (some-fn nil? boolean?))
+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Data Structures
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
diff --git a/common/src/app/common/spec.cljc b/common/src/app/common/spec.cljc
index 5fc4b8d9f..ffc24188e 100644
--- a/common/src/app/common/spec.cljc
+++ b/common/src/app/common/spec.cljc
@@ -261,6 +261,33 @@
message (str "spec verify: '" (pr-str spec) "'")]
`(spec-assert* ~spec ~x ~message ~context)))
+(defmacro assert!
+ "General purpose assertion macro."
+ [& {:keys [expr spec always? hint val]}]
+ (cond
+ (some? spec)
+ (let [context (if-let [nsdata (:ns &env)]
+ {:ns (str (:name nsdata))
+ :name (pr-str spec)
+ :line (:line &env)
+ :file (:file (:meta nsdata))}
+ {:ns (str (ns-name *ns*))
+ :name (pr-str spec)
+ :line (:line (meta &form))})
+ message (or hint (str "spec assert: " (pr-str spec)))]
+ (when (or always? *assert*)
+ `(spec-assert* ~spec ~val ~message ~context)))
+
+ (some? expr)
+ (let [message (or hint (str "expr assert: " (pr-str expr)))]
+ (when (or always? *assert*)
+ `(when-not ~expr
+ (ex/raise :type :assertion
+ :code :expr-validation
+ :hint ~message))))
+
+ :else nil))
+
;; --- Public Api
(defn conform
diff --git a/frontend/src/app/main/data/workspace/colors.cljs b/frontend/src/app/main/data/workspace/colors.cljs
index d1243cbea..904412bd2 100644
--- a/frontend/src/app/main/data/workspace/colors.cljs
+++ b/frontend/src/app/main/data/workspace/colors.cljs
@@ -14,34 +14,10 @@
[app.main.data.workspace.layout :as layout]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.data.workspace.texts :as dwt]
- [app.main.repo :as rp]
[app.util.color :as uc]
[beicon.core :as rx]
[potok.core :as ptk]))
-(def clear-color-for-rename
- (ptk/reify ::clear-color-for-rename
- ptk/UpdateEvent
- (update [_ state]
- (assoc-in state [:workspace-global :color-for-rename] nil))))
-
-(declare rename-color-result)
-
-(defn rename-color
- [file-id color-id name]
- (ptk/reify ::rename-color
- ptk/WatchEvent
- (watch [_ _ _]
- (->> (rp/mutation! :rename-color {:id color-id :name name})
- (rx/map (partial rename-color-result file-id))))))
-
-(defn rename-color-result
- [_file-id color]
- (ptk/reify ::rename-color-result
- ptk/UpdateEvent
- (update [_ state]
- (update-in state [:workspace-file :colors] #(d/replace-by-id % color)))))
-
(defn change-palette-selected
"Change the library used by the general palette tool"
[selected]
diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs
index 71c5fb260..1352747f0 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs
@@ -47,14 +47,10 @@
[potok.core :as ptk]
[rumext.alpha :as mf]))
-;; TODO: refactor to remove duplicate code and less parameter passing.
-;; - Move all state to [:workspace-local :assets-bar file-id :open-boxes {}
-;; :open-groups {}
-;; :reverse-sort?
-;; :listing-thumbs?
-;; :selected-assets {}]
-;; - Move selection code to independent functions that receive the state as a parameter.
-;;
+;; NOTE: TODO: for avoid too many arguments, I think we can use react
+;; context variables for pass to the down tree all the common
+;; variables that are defined on the MAIN container/box component.
+
;; TODO: change update operations to admit multiple ids, thus avoiding the need of
;; emitting many events and opening an undo transaction. Also move the logic
;; of grouping, deleting, etc. to events in the data module, since now the
@@ -205,8 +201,6 @@
create-typed-assets-group (partial create-typed-assets-group components-to-group)]
(modal/show! :name-group-dialog {:accept create-typed-assets-group}))))))
-
-
(defn- on-drag-enter-asset
[event asset dragging? selected-assets selected-assets-paths]
(when (and
@@ -275,8 +269,6 @@
(:id target-asset)
(cph/merge-path-item prefix (:name target-asset))))))))
-
-
;; ---- Common blocks ----
(def auto-pos-menu-state {:open? false
@@ -1090,6 +1082,7 @@
:else (:value color))
;; TODO: looks like the first argument is not necessary
+ ;; TODO: this code should be out of this UI component
apply-color
(fn [_ event]
(let [objects (wsh/lookup-page-objects @st/state)