diff --git a/backend/src/app/binfile/common.clj b/backend/src/app/binfile/common.clj
index aceb4ef7b..4f9af0e7f 100644
--- a/backend/src/app/binfile/common.clj
+++ b/backend/src/app/binfile/common.clj
@@ -141,8 +141,6 @@
" WHERE flr.file_id = ANY(?) AND l.deleted_at IS NULL")]
(db/exec! conn [sql ids])))))
-
-;; NOTE: Will be used in future, commented for satisfy linter
(def ^:private sql:get-libraries
"WITH RECURSIVE libs AS (
SELECT fl.id
@@ -409,7 +407,6 @@
(update :colors relink-colors)
(d/without-nils))))))
-
(defn- upsert-file!
[conn file]
(let [sql (str "INSERT INTO file (id, project_id, name, revn, is_shared, data, created_at, modified_at) "
diff --git a/backend/src/app/binfile/v1.clj b/backend/src/app/binfile/v1.clj
index 1e3a7b917..8597254c7 100644
--- a/backend/src/app/binfile/v1.clj
+++ b/backend/src/app/binfile/v1.clj
@@ -40,6 +40,7 @@
[promesa.util :as pu]
[yetti.adapter :as yt])
(:import
+ com.github.luben.zstd.ZstdIOException
com.github.luben.zstd.ZstdInputStream
com.github.luben.zstd.ZstdOutputStream
java.io.DataInputStream
@@ -517,6 +518,15 @@
(update :object-id #(str/replace-first % #"^(.*?)/" (str file-id "/")))))
thumbnails))
+(defn- clean-features
+ [file]
+ (update file :features (fn [features]
+ (if (set? features)
+ (-> features
+ (cfeat/migrate-legacy-features)
+ (set/difference cfeat/backend-only-features))
+ #{}))))
+
(defmethod read-section :v1/files
[{:keys [::db/conn ::input ::project-id ::bfc/overwrite ::name] :as system}]
@@ -527,6 +537,7 @@
file-id (:id file)
file-id' (bfc/lookup-index file-id)
+ file (clean-features file)
thumbnails (:thumbnails file)]
(when (not= file-id expected-file-id)
@@ -746,6 +757,12 @@
(pu/with-open [input (io/input-stream input)]
(read-import! (assoc cfg ::input input))))
+ (catch ZstdIOException cause
+ (ex/raise :type :validation
+ :code :invalid-penpot-file
+ :hint "invalid penpot file received: probably truncated"
+ :cause cause))
+
(catch Throwable cause
(vreset! cs cause)
(throw cause))
diff --git a/backend/src/app/features/components_v2.clj b/backend/src/app/features/components_v2.clj
index 72f416bdf..67cf48b07 100644
--- a/backend/src/app/features/components_v2.clj
+++ b/backend/src/app/features/components_v2.clj
@@ -1187,15 +1187,18 @@
"Convert a media object that contains a bitmap image into shapes,
one shape of type :image and one group that contains it."
[{:keys [name width height id mtype]} frame-id position]
- (let [frame-shape (cts/setup-shape
- {:type :frame
- :x (:x position)
- :y (:y position)
- :width width
- :height height
- :name name
- :frame-id frame-id
- :parent-id frame-id})
+ (let [frame-shape (-> (cts/setup-shape
+ {:type :frame
+ :x (:x position)
+ :y (:y position)
+ :width width
+ :height height
+ :name name
+ :frame-id frame-id
+ :parent-id frame-id})
+ (assoc
+ :proportion (/ width height)
+ :proportion-lock true))
img-shape (cts/setup-shape
{:type :image
@@ -1209,7 +1212,9 @@
:mtype mtype}
:name name
:frame-id (:id frame-shape)
- :parent-id (:id frame-shape)})]
+ :parent-id (:id frame-shape)
+ :constraints-h :scale
+ :constraints-v :scale})]
[frame-shape [img-shape]]))
(defn- parse-datauri
diff --git a/backend/src/app/http/sse.clj b/backend/src/app/http/sse.clj
index ec80df72d..868801091 100644
--- a/backend/src/app/http/sse.clj
+++ b/backend/src/app/http/sse.clj
@@ -61,9 +61,6 @@
(let [result (handler)]
(events/tap :end result))
(catch Throwable cause
- (binding [l/*context* (errors/request->context request)]
- (l/err :hint "unexpected error process streaming response"
- :cause cause))
(events/tap :error (errors/handle' cause request)))
(finally
(sp/close! events/*channel*)
diff --git a/backend/src/app/rpc/commands/auth.clj b/backend/src/app/rpc/commands/auth.clj
index 66bec377d..8e9671e59 100644
--- a/backend/src/app/rpc/commands/auth.clj
+++ b/backend/src/app/rpc/commands/auth.clj
@@ -262,7 +262,8 @@
(merge (:props params))
(merge {:viewed-tutorial? false
:viewed-walkthrough? false
- :nudge {:big 10 :small 1}})
+ :nudge {:big 10 :small 1}
+ :v2-info-shown true})
(db/tjson))
password (or (:password params) "!")
diff --git a/backend/src/app/srepl/fixes.clj b/backend/src/app/srepl/fixes.clj
index 955b87f81..c5ba162f0 100644
--- a/backend/src/app/srepl/fixes.clj
+++ b/backend/src/app/srepl/fixes.clj
@@ -16,8 +16,33 @@
[app.common.logging :as l]
[app.common.uuid :as uuid]
[app.db :as db]
+ [app.features.fdata :as feat.fdata]
[app.srepl.helpers :as h]))
+(defn disable-fdata-features
+ [{:keys [id features] :as file} _]
+ (when (or (contains? features "fdata/pointer-map")
+ (contains? features "fdata/objects-map"))
+ (l/warn :hint "disable fdata features" :file-id (str id))
+ (-> file
+ (update :data feat.fdata/process-pointers deref)
+ (update :data feat.fdata/process-objects (partial into {}))
+ (update :features disj "fdata/pointer-map" "fdata/objects-map"))))
+
+(def sql:get-fdata-files
+ "SELECT id FROM file
+ WHERE deleted_at is NULL
+ AND (features @> '{fdata/pointer-map}' OR
+ features @> '{fdata/objects-map}')
+ ORDER BY created_at DESC")
+
+(defn find-fdata-pointers
+ [{:keys [id features data] :as file} _]
+ (when (contains? features "fdata/pointer-map")
+ (let [pointers (feat.fdata/get-used-pointer-ids data)]
+ (l/warn :hint "found pointers" :file-id (str id) :pointers pointers)
+ nil)))
+
(defn repair-file-media
"A helper intended to be used with `srepl.main/process-files!` that
fixes all not propertly referenced file-media-object for a file"
diff --git a/backend/src/app/srepl/helpers.clj b/backend/src/app/srepl/helpers.clj
index ba98c2968..9df761db0 100644
--- a/backend/src/app/srepl/helpers.clj
+++ b/backend/src/app/srepl/helpers.clj
@@ -168,7 +168,7 @@
(update-fn file libs opts)
(update-fn file opts))]
- (when (and (some? file)
+ (when (and (some? file')
(not (identical? file file')))
(when validate? (cfv/validate-file-schema! file'))
(let [file' (update file' :revn inc)]
diff --git a/backend/src/app/srepl/main.clj b/backend/src/app/srepl/main.clj
index 16e9a0905..ba9693e93 100644
--- a/backend/src/app/srepl/main.clj
+++ b/backend/src/app/srepl/main.clj
@@ -370,43 +370,11 @@
;; PROCESSING
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-(def ^:private
- sql:get-file-ids
+(def sql:get-files
"SELECT id FROM file
- WHERE created_at < ? AND deleted_at is NULL
+ WHERE deleted_at is NULL
ORDER BY created_at DESC")
-(defn analyze-files
- "Apply a function to all files in the database, reading them in
- batches. Do not change data.
-
- The `on-file` parameter should be a function that receives the file
- and the previous state and returns the new state.
-
- Emits rollback at the end of operation."
- [on-file & {:keys [max-items start-at with-libraries?]}]
- (letfn [(get-candidates [conn]
- (cond->> (db/cursor conn [sql:get-file-ids (or start-at (dt/now))])
- (some? max-items)
- (take max-items)))
-
- (process-file [{:keys [::db/conn] :as system} file-id]
- (let [file (h/get-file system file-id)
- libs (when with-libraries?
- (->> (files/get-file-libraries conn file-id)
- (into [file] (map (fn [{:keys [id]}]
- (h/get-file system id))))
- (d/index-by :id)))]
- (if with-libraries?
- (on-file file libs)
- (on-file file))))]
-
- (db/tx-run! (assoc main/system ::db/rollback true)
- (fn [{:keys [::db/conn] :as system}]
- (binding [h/*system* system]
- (run! (partial process-file system)
- (get-candidates conn)))))))
-
(defn process-file!
"Apply a function to the file. Optionally save the changes or not.
The function receives the decoded and migrated file data."
@@ -438,11 +406,12 @@
"Apply a function to all files in the database"
[update-fn & {:keys [max-items
max-jobs
- start-at
- rollback?]
+ rollback?
+ query]
:or {max-jobs 1
max-items Long/MAX_VALUE
- rollback? true}
+ rollback? true
+ query sql:get-files}
:as opts}]
(l/dbg :hint "process:start"
@@ -486,7 +455,7 @@
(px/run! executor (partial process-file file-id idx (dt/tpoint)))
(inc idx))
0
- (->> (db/cursor conn [sql:get-file-ids (or start-at (dt/now))])
+ (->> (db/cursor conn [query] {:chunk-size 1})
(take max-items)
(map :id)))
(finally
diff --git a/backend/src/app/tasks/file_gc.clj b/backend/src/app/tasks/file_gc.clj
index b5b9e4dd9..031bf357a 100644
--- a/backend/src/app/tasks/file_gc.clj
+++ b/backend/src/app/tasks/file_gc.clj
@@ -11,7 +11,8 @@
inactivity (the default threshold is 72h)."
(:require
[app.binfile.common :as bfc]
- [app.common.files.migrations :as pmg]
+ [app.common.files.migrations :as fmg]
+ [app.common.files.validate :as cfv]
[app.common.logging :as l]
[app.common.thumbnails :as thc]
[app.common.types.components-list :as ctkl]
@@ -29,9 +30,256 @@
[clojure.spec.alpha :as s]
[integrant.core :as ig]))
-(declare ^:private get-candidates)
(declare ^:private clean-file!)
+(defn- decode-file
+ [cfg {:keys [id] :as file}]
+ (binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg id)]
+ (-> file
+ (update :features db/decode-pgarray #{})
+ (update :data blob/decode)
+ (update :data feat.fdata/process-pointers deref)
+ (update :data feat.fdata/process-objects (partial into {}))
+ (update :data assoc :id id)
+ (fmg/migrate-file))))
+
+(defn- update-file!
+ [{:keys [::db/conn] :as cfg} {:keys [id] :as file}]
+ (let [file (if (contains? (:features file) "fdata/objects-map")
+ (feat.fdata/enable-objects-map file)
+ file)
+
+ file (if (contains? (:features file) "fdata/pointer-map")
+ (binding [pmap/*tracked* (pmap/create-tracked)]
+ (let [file (feat.fdata/enable-pointer-map file)]
+ (feat.fdata/persist-pointers! cfg id)
+ file))
+ file)
+
+ file (-> file
+ (update :features db/encode-pgarray conn "text")
+ (update :data blob/encode))]
+
+ (db/update! conn :file
+ {:has-media-trimmed true
+ :data (:data file)}
+ {:id id})))
+
+(defn- process-file!
+ [cfg file]
+ (try
+ (let [file (decode-file cfg file)
+ file (clean-file! cfg file)]
+ (cfv/validate-file-schema! file)
+ (update-file! cfg file))
+ (catch Throwable cause
+ (l/err :hint "error on cleaning file (skiping)"
+ :file-id (str (:id file))
+ :cause cause))))
+
+(def ^:private
+ sql:get-candidates
+ "SELECT f.id,
+ f.data,
+ f.revn,
+ f.features,
+ f.modified_at
+ FROM file AS f
+ WHERE f.has_media_trimmed IS false
+ AND f.modified_at < now() - ?::interval
+ ORDER BY f.modified_at DESC
+ FOR UPDATE
+ SKIP LOCKED")
+
+(defn- get-candidates
+ [{:keys [::db/conn ::min-age ::file-id]}]
+ (if (uuid? file-id)
+ (do
+ (l/warn :hint "explicit file id passed on params" :file-id (str file-id))
+ (db/query conn :file {:id file-id}))
+
+ (let [min-age (db/interval min-age)]
+ (db/cursor conn [sql:get-candidates min-age] {:chunk-size 1}))))
+
+(def ^:private sql:mark-file-media-object-deleted
+ "UPDATE file_media_object
+ SET deleted_at = now()
+ WHERE file_id = ? AND id != ALL(?::uuid[])
+ RETURNING id")
+
+(defn- clean-file-media!
+ "Performs the garbage collection of file media objects."
+ [{:keys [::db/conn]} {:keys [id data] :as file}]
+ (let [used (bfc/collect-used-media data)
+ ids (db/create-array conn "uuid" used)
+ unused (->> (db/exec! conn [sql:mark-file-media-object-deleted id ids])
+ (into #{} (map :id)))]
+
+ (doseq [id unused]
+ (l/trc :hint "mark deleted"
+ :rel "file-media-object"
+ :id (str id)
+ :file-id (str id)))
+
+ [(count unused) file]))
+
+(def ^:private sql:mark-file-object-thumbnails-deleted
+ "UPDATE file_tagged_object_thumbnail
+ SET deleted_at = now()
+ WHERE file_id = ? AND object_id != ALL(?::text[])
+ RETURNING object_id")
+
+(defn- clean-file-object-thumbnails!
+ [{:keys [::db/conn]} {:keys [data] :as file}]
+ (let [file-id (:id file)
+ using (->> (vals (:pages-index data))
+ (into #{} (comp
+ (mapcat (fn [{:keys [id objects]}]
+ (->> (ctt/get-frames objects)
+ (map #(assoc % :page-id id)))))
+ (mapcat (fn [{:keys [id page-id]}]
+ (list
+ (thc/fmt-object-id file-id page-id id "frame")
+ (thc/fmt-object-id file-id page-id id "component")))))))
+
+ ids (db/create-array conn "text" using)
+ unused (->> (db/exec! conn [sql:mark-file-object-thumbnails-deleted file-id ids])
+ (into #{} (map :object-id)))]
+
+ (doseq [object-id unused]
+ (l/trc :hint "mark deleted"
+ :rel "file-tagged-object-thumbnail"
+ :object-id object-id
+ :file-id (str file-id)))
+
+ [(count unused) file]))
+
+(def ^:private sql:mark-file-thumbnails-deleted
+ "UPDATE file_thumbnail
+ SET deleted_at = now()
+ WHERE file_id = ? AND revn < ?
+ RETURNING revn")
+
+(defn- clean-file-thumbnails!
+ [{:keys [::db/conn]} {:keys [id revn] :as file}]
+ (let [unused (->> (db/exec! conn [sql:mark-file-thumbnails-deleted id revn])
+ (into #{} (map :revn)))]
+
+ (doseq [revn unused]
+ (l/trc :hint "mark deleted"
+ :rel "file-thumbnail"
+ :revn revn
+ :file-id (str id)))
+
+ [(count unused) file]))
+
+
+(def ^:private sql:get-files-for-library
+ "SELECT f.id, f.data, f.modified_at, f.features
+ FROM file AS f
+ LEFT JOIN file_library_rel AS fl ON (fl.file_id = f.id)
+ WHERE fl.library_file_id = ?
+ AND f.deleted_at IS null
+ ORDER BY f.modified_at ASC")
+
+(defn- clean-deleted-components!
+ "Performs the garbage collection of unreferenced deleted components."
+ [{:keys [::db/conn] :as cfg} {:keys [data] :as file}]
+ (let [file-id (:id file)
+
+ get-used-components
+ (fn [data components]
+ ;; Find which of the components are used in the file.
+ (into #{}
+ (filter #(ctf/used-in? data file-id % :component))
+ components))
+
+ get-unused-components
+ (fn [components files]
+ ;; Find and return a set of unused components (on all files).
+ (reduce (fn [components {:keys [data]}]
+ (if (seq components)
+ (->> (get-used-components data components)
+ (set/difference components))
+ (reduced components)))
+
+ components
+ files))
+
+ process-fdata
+ (fn [data unused]
+ (reduce (fn [data id]
+ (l/trc :hint "delete component"
+ :component-id (str id)
+ :file-id (str file-id))
+ (ctkl/delete-component data id))
+ data
+ unused))
+
+ deleted (into #{} (ctkl/deleted-components-seq data))
+
+ unused (->> (db/cursor conn [sql:get-files-for-library file-id] {:chunk-size 1})
+ (map (partial decode-file cfg))
+ (cons file)
+ (get-unused-components deleted)
+ (mapv :id)
+ (set))
+
+ file (update file :data process-fdata unused)]
+
+ [(count unused) file]))
+
+(def ^:private sql:get-changes
+ "SELECT id, data FROM file_change
+ WHERE file_id = ? AND data IS NOT NULL
+ ORDER BY created_at ASC")
+
+(def ^:private sql:mark-deleted-data-fragments
+ "UPDATE file_data_fragment
+ SET deleted_at = now()
+ WHERE file_id = ?
+ AND id != ALL(?::uuid[])
+ RETURNING id")
+
+(defn- clean-data-fragments!
+ [{:keys [::db/conn]} {:keys [id data] :as file}]
+ (let [used (->> (db/cursor conn [sql:get-changes id])
+ (into (feat.fdata/get-used-pointer-ids data)
+ (comp (map :data)
+ (map blob/decode)
+ (mapcat feat.fdata/get-used-pointer-ids))))
+
+ unused (let [ids (db/create-array conn "uuid" used)]
+ (->> (db/exec! conn [sql:mark-deleted-data-fragments id ids])
+ (into #{} (map :id))))]
+
+ (doseq [id unused]
+ (l/trc :hint "mark deleted"
+ :rel "file-data-fragment"
+ :id (str id)
+ :file-id (str id)))
+
+ [(count unused) file]))
+
+(defn- clean-file!
+ [cfg {:keys [id] :as file}]
+ (let [[n1 file] (clean-file-media! cfg file)
+ [n2 file] (clean-file-thumbnails! cfg file)
+ [n3 file] (clean-file-object-thumbnails! cfg file)
+ [n4 file] (clean-deleted-components! cfg file)
+ [n5 file] (clean-data-fragments! cfg file)]
+
+ (l/dbg :hint "file clened"
+ :file-id (str id)
+ :modified-at (dt/format-instant (:modified-at file))
+ :media-objects n1
+ :thumbnails n2
+ :object-thumbnails n3
+ :components n4
+ :data-fragments n5)
+
+ file))
+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; HANDLER
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -55,7 +303,7 @@
(assoc ::min-age min-age))
total (reduce (fn [total file]
- (clean-file! cfg file)
+ (process-file! cfg file)
(inc total))
0
(get-candidates cfg))]
@@ -69,223 +317,3 @@
(db/rollback! conn))
{:processed total})))))
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; IMPL
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
-(def ^:private
- sql:get-candidates
- "SELECT f.id,
- f.data,
- f.revn,
- f.features,
- f.modified_at
- FROM file AS f
- WHERE f.has_media_trimmed IS false
- AND f.modified_at < now() - ?::interval
- ORDER BY f.modified_at DESC
- FOR UPDATE
- SKIP LOCKED")
-
-(defn- get-candidates
- [{:keys [::db/conn ::min-age ::file-id]}]
- (if (uuid? file-id)
- (do
- (l/warn :hint "explicit file id passed on params" :file-id (str file-id))
- (->> (db/query conn :file {:id file-id})
- (map #(update % :features db/decode-pgarray #{}))))
-
- (let [min-age (db/interval min-age)]
- (->> (db/cursor conn [sql:get-candidates min-age] {:chunk-size 1})
- (map #(update % :features db/decode-pgarray #{}))))))
-
-(def ^:private sql:mark-file-media-object-deleted
- "UPDATE file_media_object
- SET deleted_at = now()
- WHERE file_id = ? AND id != ALL(?::uuid[])
- RETURNING id")
-
-(defn- clean-file-media!
- "Performs the garbage collection of file media objects."
- [conn file-id data]
- (let [used (bfc/collect-used-media data)
- ids (db/create-array conn "uuid" used)
- unused (->> (db/exec! conn [sql:mark-file-media-object-deleted file-id ids])
- (into #{} (map :id)))]
-
- (doseq [id unused]
- (l/trc :hint "mark deleted"
- :rel "file-media-object"
- :id (str id)
- :file-id (str file-id)))
-
- (count unused)))
-
-
-(def ^:private sql:mark-file-object-thumbnails-deleted
- "UPDATE file_tagged_object_thumbnail
- SET deleted_at = now()
- WHERE file_id = ? AND object_id != ALL(?::text[])
- RETURNING object_id")
-
-(defn- clean-file-object-thumbnails!
- [{:keys [::db/conn]} file-id data]
- (let [using (->> (vals (:pages-index data))
- (into #{} (comp
- (mapcat (fn [{:keys [id objects]}]
- (->> (ctt/get-frames objects)
- (map #(assoc % :page-id id)))))
- (mapcat (fn [{:keys [id page-id]}]
- (list
- (thc/fmt-object-id file-id page-id id "frame")
- (thc/fmt-object-id file-id page-id id "component")))))))
-
- ids (db/create-array conn "text" using)
- unused (->> (db/exec! conn [sql:mark-file-object-thumbnails-deleted file-id ids])
- (into #{} (map :object-id)))]
-
- (doseq [object-id unused]
- (l/trc :hint "mark deleted"
- :rel "file-tagged-object-thumbnail"
- :object-id object-id
- :file-id (str file-id)))
-
- (count unused)))
-
-
-(def ^:private sql:mark-file-thumbnails-deleted
- "UPDATE file_thumbnail
- SET deleted_at = now()
- WHERE file_id = ? AND revn < ?
- RETURNING revn")
-
-(defn- clean-file-thumbnails!
- [{:keys [::db/conn]} file-id revn]
- (let [unused (->> (db/exec! conn [sql:mark-file-thumbnails-deleted file-id revn])
- (into #{} (map :revn)))]
-
- (doseq [revn unused]
- (l/trc :hint "mark deleted"
- :rel "file-thumbnail"
- :revn revn
- :file-id (str file-id)))
-
- (count unused)))
-
-
-(def ^:private sql:get-files-for-library
- "SELECT f.id, f.data, f.modified_at
- FROM file AS f
- LEFT JOIN file_library_rel AS fl ON (fl.file_id = f.id)
- WHERE fl.library_file_id = ?
- AND f.deleted_at IS null
- ORDER BY f.modified_at ASC")
-
-(defn- clean-deleted-components!
- "Performs the garbage collection of unreferenced deleted components."
- [{:keys [::db/conn] :as cfg} file-id data]
- (letfn [(get-used-components [fdata components]
- ;; Find which of the components are used in the file.
- (into #{}
- (filter #(ctf/used-in? fdata file-id % :component))
- components))
-
- (get-unused-components [components files-data]
- ;; Find and return a set of unused components (on all files).
- (reduce (fn [components fdata]
- (if (seq components)
- (->> (get-used-components fdata components)
- (set/difference components))
- (reduced components)))
-
- components
- files-data))]
-
- (let [deleted (into #{} (ctkl/deleted-components-seq data))
- unused (->> (db/cursor conn [sql:get-files-for-library file-id] {:chunk-size 1})
- (map (fn [{:keys [id data] :as file}]
- (binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg id)]
- (-> (blob/decode data)
- (feat.fdata/process-pointers deref)))))
- (cons data)
- (get-unused-components deleted)
- (mapv :id))]
-
- (doseq [id unused]
- (l/trc :hint "delete component" :component-id (str id) :file-id (str file-id)))
-
-
- (when-let [data (some->> (seq unused)
- (reduce ctkl/delete-component data)
- (blob/encode))]
- (db/update! conn :file
- {:data data}
- {:id file-id}))
-
- (count unused))))
-
-
-(def ^:private sql:get-changes
- "SELECT id, data FROM file_change
- WHERE file_id = ? AND data IS NOT NULL
- ORDER BY created_at ASC")
-
-(def ^:private sql:mark-deleted-data-fragments
- "UPDATE file_data_fragment
- SET deleted_at = now()
- WHERE file_id = ?
- AND id != ALL(?::uuid[])
- RETURNING id")
-
-(defn- clean-data-fragments!
- [conn file-id data]
- (let [used (->> (db/cursor conn [sql:get-changes file-id])
- (into (feat.fdata/get-used-pointer-ids data)
- (comp (map :data)
- (map blob/decode)
- (mapcat feat.fdata/get-used-pointer-ids))))
-
- unused (let [ids (db/create-array conn "uuid" used)]
- (->> (db/exec! conn [sql:mark-deleted-data-fragments file-id ids])
- (into #{} (map :id))))]
-
- (doseq [id unused]
- (l/trc :hint "mark deleted"
- :rel "file-data-fragment"
- :id (str id)
- :file-id (str file-id)))
-
- (count unused)))
-
-
-(defn- clean-file!
- [{:keys [::db/conn] :as cfg} {:keys [id data revn modified-at] :as file}]
-
- (binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg id)
- pmap/*tracked* (pmap/create-tracked)]
- (let [data (-> (blob/decode data)
- (assoc :id id)
- (pmg/migrate-data))
-
- nfm (clean-file-media! conn id data)
- nfot (clean-file-object-thumbnails! cfg id data)
- nft (clean-file-thumbnails! cfg id revn)
- nc (clean-deleted-components! cfg id data)
- ndf (clean-data-fragments! conn id data)]
-
- (l/dbg :hint "file clened"
- :file-id (str id)
- :modified-at (dt/format-instant modified-at)
- :media-objects nfm
- :thumbnails nft
- :object-thumbnails nfot
- :components nc
- :data-fragments ndf)
-
- ;; Mark file as trimmed
- (db/update! conn :file
- {:has-media-trimmed true}
- {:id id})
-
- (feat.fdata/persist-pointers! cfg id))))
diff --git a/backend/test/backend_tests/rpc_file_test.clj b/backend/test/backend_tests/rpc_file_test.clj
index 510aadd89..a684227c8 100644
--- a/backend/test/backend_tests/rpc_file_test.clj
+++ b/backend/test/backend_tests/rpc_file_test.clj
@@ -154,7 +154,7 @@
;; Check the number of fragments before adding the page
(let [rows (th/db-query :file-data-fragment {:file-id (:id file)})]
- (t/is (= 1 (count rows))))
+ (t/is (= 2 (count rows))))
;; Add page
(update-file!
@@ -172,15 +172,15 @@
;; Check the number of fragments
(let [rows (th/db-query :file-data-fragment {:file-id (:id file)})]
- (t/is (= 2 (count rows))))
+ (t/is (= 5 (count rows))))
;; The objects-gc should remove unused fragments
(let [res (th/run-task! :objects-gc {:min-age 0})]
- (t/is (= 0 (:processed res))))
+ (t/is (= 1 (:processed res))))
;; Check the number of fragments
(let [rows (th/db-query :file-data-fragment {:file-id (:id file)})]
- (t/is (= 2 (count rows))))
+ (t/is (= 4 (count rows))))
;; Add shape to page that should add a new fragment
(update-file!
@@ -203,7 +203,7 @@
;; Check the number of fragments
(let [rows (th/db-query :file-data-fragment {:file-id (:id file)})]
- (t/is (= 3 (count rows))))
+ (t/is (= 5 (count rows))))
;; The file-gc should mark for remove unused fragments
(let [res (th/run-task! :file-gc {:min-age 0})]
@@ -211,12 +211,13 @@
;; The objects-gc should remove unused fragments
(let [res (th/run-task! :objects-gc {:min-age 0})]
- (t/is (= 0 (:processed res))))
+ (t/is (= 1 (:processed res))))
;; Check the number of fragments; should be 3 because changes
;; are also holding pointers to fragments;
- (let [rows (th/db-query :file-data-fragment {:file-id (:id file)})]
- (t/is (= 3 (count rows))))
+ (let [rows (th/db-query :file-data-fragment {:file-id (:id file)
+ :deleted-at nil})]
+ (t/is (= 6 (count rows))))
;; Lets proceed to delete all changes
(th/db-delete! :file-change {:file-id (:id file)})
@@ -224,7 +225,6 @@
{:has-media-trimmed false}
{:id (:id file)})
-
;; The file-gc should remove fragments related to changes
;; snapshots previously deleted.
(let [res (th/run-task! :file-gc {:min-age 0})]
@@ -233,11 +233,11 @@
;; Check the number of fragments;
(let [rows (th/db-query :file-data-fragment {:file-id (:id file)})]
;; (pp/pprint rows)
- (t/is (= 3 (count rows)))
+ (t/is (= 8 (count rows)))
(t/is (= 2 (count (remove (comp some? :deleted-at) rows)))))
(let [res (th/run-task! :objects-gc {:min-age 0})]
- (t/is (= 1 (:processed res))))
+ (t/is (= 6 (:processed res))))
(let [rows (th/db-query :file-data-fragment {:file-id (:id file)})]
(t/is (= 2 (count rows)))))))
@@ -367,7 +367,7 @@
(t/is (= 1 (:processed res))))
(let [res (th/run-task! :objects-gc {:min-age 0})]
- (t/is (= 1 (:processed res))))
+ (t/is (= 2 (:processed res))))
;; Now that file-gc have deleted the file-media-object usage,
;; lets execute the touched-gc task, we should see that two of
@@ -432,6 +432,11 @@
page-id (first (get-in file [:data :pages]))]
+
+ (let [rows (th/db-query :file-data-fragment {:file-id (:id file)
+ :deleted-at nil})]
+ (t/is (= (count rows) 1)))
+
;; Update file inserting a new image object
(update-file!
:file-id (:id file)
@@ -491,6 +496,10 @@
(let [res (th/run-task! :objects-gc {:min-age 0})]
(t/is (= 1 (:processed res))))
+ (let [rows (th/db-query :file-data-fragment {:file-id (:id file)
+ :deleted-at nil})]
+ (t/is (= (count rows) 2)))
+
;; retrieve file and check trimmed attribute
(let [row (th/db-get :file {:id (:id file)})]
(t/is (true? (:has-media-trimmed row))))
@@ -521,11 +530,16 @@
;; Now, we have deleted the usage of pointers to the
;; file-media-objects, if we paste file-gc, they should be marked
;; as deleted.
+
(let [res (th/run-task! :file-gc {:min-age 0})]
(t/is (= 1 (:processed res))))
(let [res (th/run-task! :objects-gc {:min-age 0})]
- (t/is (= 5 (:processed res))))
+ (t/is (= 6 (:processed res))))
+
+ (let [rows (th/db-query :file-data-fragment {:file-id (:id file)
+ :deleted-at nil})]
+ (t/is (= (count rows) 3)))
;; Now that file-gc have deleted the file-media-object usage,
;; lets execute the touched-gc task, we should see that two of
@@ -681,7 +695,6 @@
(let [rows (th/db-query :file-tagged-object-thumbnail {:file-id file-id})]
(t/is (= 2 (count rows)))
(t/is (= 1 (count (remove (comp some? :deleted-at) rows))))
-
(t/is (= (thc/fmt-object-id file-id page-id frame-id-1 "frame")
(-> rows first :object-id))))
@@ -689,7 +702,7 @@
;; thumbnail lets execute the objects-gc task which remove
;; the rows and mark as touched the storage object rows
(let [res (th/run-task! :objects-gc {:min-age 0})]
- (t/is (= 2 (:processed res))))
+ (t/is (= 3 (:processed res))))
;; Now that objects-gc have deleted the object thumbnail lets
;; execute the touched-gc task
@@ -719,7 +732,7 @@
(let [res (th/run-task! :objects-gc {:min-age 0})]
;; (pp/pprint res)
- (t/is (= 1 (:processed res))))
+ (t/is (= 2 (:processed res))))
;; We still have th storage objects in the table
(let [rows (th/db-query :storage-object {:deleted-at nil})]
@@ -736,6 +749,7 @@
;; (pp/pprint rows)
(t/is (= 0 (count rows)))))))
+
(t/deftest permissions-checks-creating-file
(let [profile1 (th/create-profile* 1)
profile2 (th/create-profile* 2)
@@ -1147,7 +1161,7 @@
(t/is (= 1 (count (remove (comp some? :deleted-at) rows)))))
(let [res (th/run-task! :objects-gc {:min-age 0})]
- (t/is (= 2 (:processed res))))
+ (t/is (= 3 (:processed res))))
(let [rows (th/db-query :file-tagged-object-thumbnail {:file-id (:id file)})]
(t/is (= 1 (count rows)))))))
@@ -1203,7 +1217,7 @@
(t/is (= 1 (count (remove (comp some? :deleted-at) rows)))))
(let [res (th/run-task! :objects-gc {:min-age 0})]
- (t/is (= 1 (:processed res))))
+ (t/is (= 2 (:processed res))))
(let [rows (th/db-query :file-thumbnail {:file-id (:id file)})]
(t/is (= 1 (count rows)))))))
diff --git a/backend/test/backend_tests/rpc_file_thumbnails_test.clj b/backend/test/backend_tests/rpc_file_thumbnails_test.clj
index 88e2ac2d2..1ad3c6e09 100644
--- a/backend/test/backend_tests/rpc_file_thumbnails_test.clj
+++ b/backend/test/backend_tests/rpc_file_thumbnails_test.clj
@@ -222,7 +222,7 @@
(t/is (= 1 (:processed result))))
(let [result (th/run-task! :objects-gc {:min-age 0})]
- (t/is (= 1 (:processed result))))
+ (t/is (= 2 (:processed result))))
;; check if row1 related thumbnail row still exists
(let [[row :as rows] (th/db-query :file-thumbnail
diff --git a/common/src/app/common/files/changes_builder.cljc b/common/src/app/common/files/changes_builder.cljc
index f5d1cf201..f868cbd2a 100644
--- a/common/src/app/common/files/changes_builder.cljc
+++ b/common/src/app/common/files/changes_builder.cljc
@@ -326,7 +326,9 @@
(some? index)
(assoc :index index)
(:component-swap options)
- (assoc :component-swap true))
+ (assoc :component-swap true)
+ (:ignore-touched options)
+ (assoc :ignore-touched true))
mk-undo-change
(fn [undo-changes shape]
@@ -450,10 +452,11 @@
add-redo-change
(fn [change-set id]
(conj change-set
- {:type :del-obj
- :page-id page-id
- :ignore-touched ignore-touched
- :id id}))
+ (cond-> {:type :del-obj
+ :page-id page-id
+ :id id}
+ ignore-touched
+ (assoc :ignore-touched true))))
add-undo-change-shape
(fn [change-set id]
diff --git a/common/src/app/common/types/container.cljc b/common/src/app/common/types/container.cljc
index c217969e1..4265329ea 100644
--- a/common/src/app/common/types/container.cljc
+++ b/common/src/app/common/types/container.cljc
@@ -166,13 +166,6 @@
:else
(get-instance-root objects (get objects (:parent-id shape)))))
-(defn get-copy-root
- "Get the top shape of the copy."
- [objects shape]
- (when (:shape-ref shape)
- (let [parent (cfh/get-parent objects (:id shape))]
- (or (get-copy-root objects parent) shape))))
-
(defn inside-component-main?
"Check if the shape is a component main instance or is inside one."
[objects shape]
diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc
index 3f8e7b3bd..1fd8b1f00 100644
--- a/common/src/app/common/types/file.cljc
+++ b/common/src/app/common/types/file.cljc
@@ -190,7 +190,7 @@
"Locate the near component in the local file or libraries, and retrieve the shape
referenced by the instance shape."
[file page libraries shape & {:keys [include-deleted?] :or {include-deleted? false}}]
- (let [root-shape (ctn/get-copy-root (:objects page) shape)
+ (let [root-shape (ctn/get-component-shape (:objects page) shape)
component-file (when root-shape
(if (and (some? file) (= (:component-file root-shape) (:id file)))
file
@@ -218,10 +218,23 @@
component-file (get-in libraries [(:component-file top-instance) :data])
component (ctkl/get-component component-file (:component-id top-instance) true)
remote-shape (get-ref-shape component-file component shape)
- component-container (get-component-container component-file component)]
+ component-container (get-component-container component-file component)
+ [remote-shape component-container]
+ (if (some? remote-shape)
+ [remote-shape component-container]
+ ;; If not found, try the case of this being a fostered or swapped children
+ (let [head-instance (ctn/get-head-shape (:objects container) shape)
+ component-file (get-in libraries [(:component-file head-instance) :data])
+ head-component (ctkl/get-component component-file (:component-id head-instance) true)
+ remote-shape' (get-ref-shape component-file head-component shape)
+ component-container (get-component-container component-file component)]
+ [remote-shape' component-container]))]
+
(if (nil? remote-shape)
- shape
- (find-remote-shape component-container libraries remote-shape))))
+ nil
+ (if (nil? (:shape-ref remote-shape))
+ remote-shape
+ (find-remote-shape component-container libraries remote-shape)))))
(defn get-component-shapes
"Retrieve all shapes of the component"
diff --git a/frontend/resources/images/icons/corner-bottom-refactor.svg b/frontend/resources/images/icons/corner-bottom-refactor.svg
new file mode 100644
index 000000000..ca2c80ea5
--- /dev/null
+++ b/frontend/resources/images/icons/corner-bottom-refactor.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/resources/images/icons/corner-bottomleft-refactor.svg b/frontend/resources/images/icons/corner-bottomleft-refactor.svg
new file mode 100644
index 000000000..ed9972420
--- /dev/null
+++ b/frontend/resources/images/icons/corner-bottomleft-refactor.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/resources/images/icons/corner-bottomright-refactor.svg b/frontend/resources/images/icons/corner-bottomright-refactor.svg
new file mode 100644
index 000000000..8292d9bbc
--- /dev/null
+++ b/frontend/resources/images/icons/corner-bottomright-refactor.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/resources/images/icons/corner-center-refactor.svg b/frontend/resources/images/icons/corner-center-refactor.svg
new file mode 100644
index 000000000..72b387bc0
--- /dev/null
+++ b/frontend/resources/images/icons/corner-center-refactor.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/resources/images/icons/corner-top-refactor.svg b/frontend/resources/images/icons/corner-top-refactor.svg
new file mode 100644
index 000000000..542f48376
--- /dev/null
+++ b/frontend/resources/images/icons/corner-top-refactor.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/resources/images/icons/corner-topleft-refactor.svg b/frontend/resources/images/icons/corner-topleft-refactor.svg
new file mode 100644
index 000000000..eeabf0c23
--- /dev/null
+++ b/frontend/resources/images/icons/corner-topleft-refactor.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/resources/images/icons/corner-topright-refactor.svg b/frontend/resources/images/icons/corner-topright-refactor.svg
new file mode 100644
index 000000000..27454f142
--- /dev/null
+++ b/frontend/resources/images/icons/corner-topright-refactor.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/resources/images/icons/v2-icon-1.svg b/frontend/resources/images/icons/v2-icon-1.svg
new file mode 100644
index 000000000..b39647eb1
--- /dev/null
+++ b/frontend/resources/images/icons/v2-icon-1.svg
@@ -0,0 +1 @@
+
diff --git a/frontend/resources/images/icons/v2-icon-2.svg b/frontend/resources/images/icons/v2-icon-2.svg
new file mode 100644
index 000000000..d384ee2ed
--- /dev/null
+++ b/frontend/resources/images/icons/v2-icon-2.svg
@@ -0,0 +1 @@
+
diff --git a/frontend/resources/images/icons/v2-icon-3.svg b/frontend/resources/images/icons/v2-icon-3.svg
new file mode 100644
index 000000000..bfc88da6d
--- /dev/null
+++ b/frontend/resources/images/icons/v2-icon-3.svg
@@ -0,0 +1 @@
+
diff --git a/frontend/resources/images/icons/v2-icon-4.svg b/frontend/resources/images/icons/v2-icon-4.svg
new file mode 100644
index 000000000..dc2443432
--- /dev/null
+++ b/frontend/resources/images/icons/v2-icon-4.svg
@@ -0,0 +1 @@
+
diff --git a/frontend/resources/styles/common/refactor/basic-rules.scss b/frontend/resources/styles/common/refactor/basic-rules.scss
index 29a9219f0..4dd70f856 100644
--- a/frontend/resources/styles/common/refactor/basic-rules.scss
+++ b/frontend/resources/styles/common/refactor/basic-rules.scss
@@ -125,10 +125,11 @@
@include buttonStyle;
@include flexCenter;
@include focusTertiary;
+ --button-tertiary-border-width: #{$s-2};
border-radius: $br-8;
color: var(--button-tertiary-foreground-color-rest);
background-color: transparent;
- border: $s-2 solid transparent;
+ border: var(--button-tertiary-border-width) solid transparent;
svg,
span svg {
stroke: var(--button-tertiary-foreground-color-rest);
@@ -136,7 +137,7 @@
&:hover {
background-color: var(--button-tertiary-background-color-hover);
color: var(--button-tertiary-foreground-color-hover);
- border: $s-2 solid var(--button-secondary-border-color-hover);
+ border-color: var(--button-secondary-border-color-hover);
svg,
span svg {
stroke: var(--button-tertiary-foreground-color-hover);
@@ -144,7 +145,7 @@
}
&:active {
outline: none;
- border: $s-2 solid transparent;
+ border-color: transparent;
background-color: var(--button-tertiary-background-color-active);
color: var(--button-tertiary-foreground-color-active);
svg,
@@ -169,7 +170,7 @@
.button-icon-selected {
outline: none;
- border: $s-2 solid var(--button-icon-border-color-selected);
+ border-color: var(--button-icon-border-color-selected);
background-color: var(--button-icon-background-color-selected);
color: var(--button-icon-foreground-color-selected);
svg {
@@ -183,7 +184,7 @@
@include focusRadio;
border-radius: $br-8;
color: var(--button-radio-foreground-color-rest);
- border: $s-1 solid var(--button-radio-background-color-rest);
+ border-color: $s-1 solid var(--button-radio-background-color-rest);
svg,
span svg {
stroke: var(--button-radio-foreground-color-rest);
diff --git a/frontend/resources/styles/common/refactor/design-tokens.scss b/frontend/resources/styles/common/refactor/design-tokens.scss
index 67abb7774..799195f84 100644
--- a/frontend/resources/styles/common/refactor/design-tokens.scss
+++ b/frontend/resources/styles/common/refactor/design-tokens.scss
@@ -65,6 +65,9 @@
--button-tertiary-border-color-focus: var(--color-accent-primary);
--button-tertiary-foreground-color-focus: var(--color-foreground-primary);
+ --expand-button-icon-border-width: 0;
+ --expand-button-icon-border-width-selected: 0;
+
--button-icon-foreground-color: var(--color-foreground-secondary);
--button-icon-foreground-color-hover: var(--color-foreground-secondary);
--button-icon-background-color-selected: var(--color-background-quaternary);
@@ -396,4 +399,5 @@
--assets-item-name-foreground-color: var(--color-foreground-primary);
--text-editor-selection-background-color: var(--la-tertiary-70);
+ --expand-button-icon-border-width-selected: 2px;
}
diff --git a/frontend/resources/styles/common/refactor/fonts.scss b/frontend/resources/styles/common/refactor/fonts.scss
index 40d1dcb87..c4bc4cb3f 100644
--- a/frontend/resources/styles/common/refactor/fonts.scss
+++ b/frontend/resources/styles/common/refactor/fonts.scss
@@ -15,6 +15,7 @@ $fs-11: 0.688rem;
$fs-12: math.div(12, $fs-base) + rem;
$fs-14: math.div(14, $fs-base) + rem;
$fs-16: math.div(16, $fs-base) + rem;
+$fs-18: math.div(18, $fs-base) + rem;
$fs-24: math.div(24, $fs-base) + rem;
$fs-36: math.div(36, $fs-base) + rem;
diff --git a/frontend/resources/styles/common/refactor/mixins.scss b/frontend/resources/styles/common/refactor/mixins.scss
index f988cd0df..2b9a05efd 100644
--- a/frontend/resources/styles/common/refactor/mixins.scss
+++ b/frontend/resources/styles/common/refactor/mixins.scss
@@ -115,8 +115,8 @@
@mixin copyWrapperBase {
position: relative;
min-height: $s-32;
- width: $s-156;
- max-width: $s-156;
+ width: $s-144;
+ max-width: $s-144;
padding: calc($s-8 - $s-1) 0 calc($s-8 - $s-1) calc($s-8 - $s-1);
border-radius: $s-8;
box-sizing: border-box;
diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs
index 46d847794..0249bf2ca 100644
--- a/frontend/src/app/main/data/workspace.cljs
+++ b/frontend/src/app/main/data/workspace.cljs
@@ -35,6 +35,7 @@
[app.main.data.events :as ev]
[app.main.data.fonts :as df]
[app.main.data.messages :as msg]
+ [app.main.data.modal :as modal]
[app.main.data.users :as du]
[app.main.data.workspace.bool :as dwb]
[app.main.data.workspace.changes :as dch]
@@ -119,10 +120,14 @@
(assoc :workspace-ready? true)))
ptk/WatchEvent
- (watch [_ _ _]
- (rx/of (fbc/fix-bool-contents)
- (fdf/fix-deleted-fonts)
- (fbs/fix-broken-shapes)))))
+ (watch [_ state _]
+ (rx/of
+ (when (and (not (boolean (-> state :profile :props :v2-info-shown)))
+ (features/active-feature? state "components/v2"))
+ (modal/show :v2-info {}))
+ (fbc/fix-bool-contents)
+ (fdf/fix-deleted-fonts)
+ (fbs/fix-broken-shapes)))))
(defn- workspace-data-loaded
[data]
diff --git a/frontend/src/app/main/data/workspace/colors.cljs b/frontend/src/app/main/data/workspace/colors.cljs
index 1d4afbd9b..7c31ef43b 100644
--- a/frontend/src/app/main/data/workspace/colors.cljs
+++ b/frontend/src/app/main/data/workspace/colors.cljs
@@ -442,6 +442,7 @@
(declare activate-colorpicker-color)
(declare activate-colorpicker-gradient)
(declare activate-colorpicker-image)
+(declare update-colorpicker)
(defn apply-color-from-colorpicker
[color]
@@ -453,8 +454,7 @@
(:image color) (activate-colorpicker-image)
(:color color) (activate-colorpicker-color)
(= :linear (get-in color [:gradient :type])) (activate-colorpicker-gradient :linear-gradient)
- (= :radial (get-in color [:gradient :type])) (activate-colorpicker-gradient :radial-gradient))
- (apply-color-from-palette color false)))))
+ (= :radial (get-in color [:gradient :type])) (activate-colorpicker-gradient :radial-gradient))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs
index 269701c78..5edcafbe0 100644
--- a/frontend/src/app/main/data/workspace/libraries.cljs
+++ b/frontend/src/app/main/data/workspace/libraries.cljs
@@ -77,7 +77,11 @@
extract (cond-> {:type (:type change)
:raw-change change}
shape
- (assoc :shape (str prefix (:name shape)))
+ (assoc :shape (str prefix (:name shape))
+ :shape-id (str (:id shape)))
+ (:obj change)
+ (assoc :obj (:name (:obj change))
+ :obj-id (:id (:obj change)))
(:operations change)
(assoc :operations (:operations change)))]
extract))]
@@ -894,7 +898,8 @@
(pcb/update-shapes [(:id new-shape)] #(d/patch-object % keep-props-values))
;; We need to set the same index as the original shape
- (pcb/change-parent (:parent-id shape) [new-shape] index {:component-swap true}))]
+ (pcb/change-parent (:parent-id shape) [new-shape] index {:component-swap true
+ :ignore-touched true}))]
;; First delete so we don't break the grid layout cells
(rx/of (dch/commit-changes changes)
diff --git a/frontend/src/app/main/data/workspace/libraries_helpers.cljs b/frontend/src/app/main/data/workspace/libraries_helpers.cljs
index d19e776f3..9f19e14e6 100644
--- a/frontend/src/app/main/data/workspace/libraries_helpers.cljs
+++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs
@@ -886,7 +886,6 @@
(map #(redirect-shaperef %) children-inst)
children-inst)
-
only-inst (fn [changes child-inst]
(add-shape-to-main changes
child-inst
@@ -1088,10 +1087,8 @@
root-main))
update-original-shape (fn [original-shape new-shape]
- (if-not (:shape-ref original-shape)
- (assoc original-shape
- :shape-ref (:id new-shape))
- original-shape))
+ (assoc original-shape
+ :shape-ref (:id new-shape)))
[_new-shape new-shapes updated-shapes]
(ctst/clone-shape shape
@@ -1116,25 +1113,46 @@
:obj shape'}))))
mod-obj-change (fn [changes shape']
- (update changes :redo-changes conj
- {:type :mod-obj
- :page-id (:id page)
- :id (:id shape')
- :operations [{:type :set
- :attr :component-id
- :val (:component-id shape')}
- {:type :set
- :attr :component-file
- :val (:component-file shape')}
- {:type :set
- :attr :component-root
- :val (:component-root shape')}
- {:type :set
- :attr :shape-ref
- :val (:shape-ref shape')}
- {:type :set
- :attr :touched
- :val (:touched shape')}]}))
+ (let [shape-original (ctn/get-shape page (:id shape'))]
+ (-> changes
+ (update :redo-changes conj
+ {:type :mod-obj
+ :page-id (:id page)
+ :id (:id shape')
+ :operations [{:type :set
+ :attr :component-id
+ :val (:component-id shape')}
+ {:type :set
+ :attr :component-file
+ :val (:component-file shape')}
+ {:type :set
+ :attr :component-root
+ :val (:component-root shape')}
+ {:type :set
+ :attr :shape-ref
+ :val (:shape-ref shape')}
+ {:type :set
+ :attr :touched
+ :val (:touched shape')}]})
+ (update :undo-changes conj
+ {:type :mod-obj
+ :page-id (:id page)
+ :id (:id shape-original)
+ :operations [{:type :set
+ :attr :component-id
+ :val (:component-id shape-original)}
+ {:type :set
+ :attr :component-file
+ :val (:component-file shape-original)}
+ {:type :set
+ :attr :component-root
+ :val (:component-root shape-original)}
+ {:type :set
+ :attr :shape-ref
+ :val (:shape-ref shape-original)}
+ {:type :set
+ :attr :touched
+ :val (:touched shape-original)}]}))))
del-obj-change (fn [changes shape']
(update changes :undo-changes conj
@@ -1161,7 +1179,8 @@
parents (cfh/get-parent-ids objects (:id shape))
parent (first parents)
children (cfh/get-children-ids objects (:id shape))
- ids (into [(:id shape)] children)
+ ids (-> (into [(:id shape)] children)
+ (reverse)) ;; Remove from bottom to top
add-redo-change (fn [changes id]
(update changes :redo-changes conj
@@ -1190,12 +1209,11 @@
(update :redo-changes conj (make-change
container
{:type :reg-objects
- :shapes (vec parents)}))
- (add-undo-change (:id shape)))
+ :shapes (vec parents)})))
changes' (reduce add-undo-change
changes'
- children)]
+ ids)]
(if (and (cfh/touched-group? parent :shapes-group) omit-touched?)
changes
diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs
index 99e13369a..805669e4d 100644
--- a/frontend/src/app/main/data/workspace/shapes.cljs
+++ b/frontend/src/app/main/data/workspace/shapes.cljs
@@ -142,17 +142,17 @@
(rx/concat
(rx/of (dwu/start-undo-transaction undo-id)
(update-shape-flags ids-to-hide {:hidden true}))
- (real-delete-shapes file page objects ids-to-delete it components-v2)
+ (real-delete-shapes file page objects ids-to-delete it components-v2 (:component-swap options))
(rx/of (dwu/commit-undo-transaction undo-id))))))))
(defn- real-delete-shapes-changes
- ([file page objects ids it components-v2]
+ ([file page objects ids it components-v2 ignore-touched]
(let [changes (-> (pcb/empty-changes it (:id page))
(pcb/with-page page)
(pcb/with-objects objects)
(pcb/with-library-data file))]
- (real-delete-shapes-changes changes file page objects ids it components-v2)))
- ([changes file page objects ids _it components-v2]
+ (real-delete-shapes-changes changes file page objects ids it components-v2 ignore-touched)))
+ ([changes file page objects ids _it components-v2 ignore-touched]
(let [lookup (d/getf objects)
groups-to-unmask
(reduce (fn [group-ids id]
@@ -252,7 +252,7 @@
changes (-> changes
(pcb/remove-objects all-children {:ignore-touched true})
- (pcb/remove-objects ids)
+ (pcb/remove-objects ids {:ignore-touched ignore-touched})
(pcb/remove-objects empty-parents)
(pcb/resize-parents all-parents)
(pcb/update-shapes groups-to-unmask
@@ -274,13 +274,13 @@
(defn delete-shapes-changes
- [changes file page objects ids it components-v2]
- (let [[changes _all-parents] (real-delete-shapes-changes changes file page objects ids it components-v2)]
+ [changes file page objects ids it components-v2 ignore-touched]
+ (let [[changes _all-parents] (real-delete-shapes-changes changes file page objects ids it components-v2 ignore-touched)]
changes))
(defn- real-delete-shapes
- [file page objects ids it components-v2]
- (let [[changes all-parents] (real-delete-shapes-changes file page objects ids it components-v2)
+ [file page objects ids it components-v2 ignore-touched]
+ (let [[changes all-parents] (real-delete-shapes-changes file page objects ids it components-v2 ignore-touched)
undo-id (js/Symbol)]
(rx/of (dwu/start-undo-transaction undo-id)
(dc/detach-comment-thread ids)
@@ -288,7 +288,6 @@
(ptk/data-event :layout/update all-parents)
(dwu/commit-undo-transaction undo-id))))
-
(defn create-and-add-shape
[type frame-x frame-y {:keys [width height] :as attrs}]
(ptk/reify ::create-and-add-shape
diff --git a/frontend/src/app/main/ui/components/color_bullet.cljs b/frontend/src/app/main/ui/components/color_bullet.cljs
index 0b273aac2..7213a8347 100644
--- a/frontend/src/app/main/ui/components/color_bullet.cljs
+++ b/frontend/src/app/main/ui/components/color_bullet.cljs
@@ -57,5 +57,5 @@
:on-double-click on-double-click
:title name}
(if (some? image)
- (tr "media.image")
+ (tr "media.image.short")
(or name color (uc/gradient-type->string (:type gradient))))])))
diff --git a/frontend/src/app/main/ui/components/color_bullet_new.cljs b/frontend/src/app/main/ui/components/color_bullet_new.cljs
index ff333f35e..6173a582f 100644
--- a/frontend/src/app/main/ui/components/color_bullet_new.cljs
+++ b/frontend/src/app/main/ui/components/color_bullet_new.cljs
@@ -9,7 +9,7 @@
(:require
[app.config :as cfg]
[app.util.color :as uc]
- [app.util.i18n :as i18n :refer [tr]]
+ [app.util.i18n :refer [tr]]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
@@ -99,16 +99,16 @@
(mf/defc color-name
{::mf/wrap-props false}
- [{:keys [color size on-click on-double-click]}]
- (let [{:keys [name color gradient image]} (if (string? color) {:color color :opacity 1} color)]
+ [{:keys [color size on-click on-double-click origin]}]
+ (let [{:keys [name color gradient]} (if (string? color) {:color color :opacity 1} color)]
(when (or (not size) (> size 64))
[:span {:class (stl/css-case
- :color-text (< size 72)
- :small-text (and (>= size 64) (< size 72))
- :big-text (>= size 72))
+ :color-text (and (= origin :palette) (< size 72))
+ :small-text (and (= origin :palette) (>= size 64) (< size 72))
+ :big-text (and (= origin :palette) (>= size 72))
+ :gradient (some? gradient)
+ :color-row-name (not= origin :palette))
:title name
:on-click on-click
:on-double-click on-double-click}
- (if (some? image)
- (or name (tr "media.image"))
- (or name color (uc/gradient-type->string (:type gradient))))])))
+ (or name color (uc/gradient-type->string (:type gradient)))])))
diff --git a/frontend/src/app/main/ui/components/color_bullet_new.scss b/frontend/src/app/main/ui/components/color_bullet_new.scss
index d6e617251..2dddb14b9 100644
--- a/frontend/src/app/main/ui/components/color_bullet_new.scss
+++ b/frontend/src/app/main/ui/components/color_bullet_new.scss
@@ -94,3 +94,7 @@
.no-text {
display: none;
}
+
+.color-row-name {
+ color: var(--menu-foreground-color);
+}
diff --git a/frontend/src/app/main/ui/components/copy_button.scss b/frontend/src/app/main/ui/components/copy_button.scss
index c81487f45..ec8e362bb 100644
--- a/frontend/src/app/main/ui/components/copy_button.scss
+++ b/frontend/src/app/main/ui/components/copy_button.scss
@@ -8,9 +8,8 @@
.copy-button {
@include buttonStyle;
- @include flexCenter;
+ width: 100%;
height: $s-32;
- width: $s-32;
border: $s-1 solid transparent;
border-radius: $br-8;
background-color: transparent;
@@ -51,41 +50,32 @@
.copy-wrapper {
@include buttonStyle;
@include copyWrapperBase;
- display: grid;
- grid-template-columns: 1fr $s-24;
- grid-template-areas: "name button";
width: 100%;
height: fit-content;
text-align: left;
- border: 1px solid transparent;
+ border: $s-1 solid transparent;
.icon-btn {
+ @include flexCenter;
position: absolute;
- display: flex;
- justify-content: center;
- align-items: center;
top: 0;
right: 0;
height: $s-32;
- width: $s-32;
+ width: $s-28;
svg {
@extend .button-icon-small;
+ stroke: var(--button-tertiary-foreground-color-focus);
display: none;
}
}
&:hover {
- .icon-btn {
- svg {
- display: flex;
- stroke: var(--button-tertiary-foreground-color-active);
- }
+ background-color: var(--button-tertiary-background-color-focus);
+ color: var(--button-tertiary-foreground-color-focus);
+ border: $s-1 solid var(--button-tertiary-background-color-focus);
+ .icon-btn svg {
+ display: flex;
}
}
- &:hover {
- background-color: var(--color-background-tertiary);
- color: var(--color-foreground-primary);
- border: $s-1 solid var(--color-background-tertiary);
- }
&:focus,
&:focus-visible {
outline: none;
diff --git a/frontend/src/app/main/ui/components/select.cljs b/frontend/src/app/main/ui/components/select.cljs
index e3ae25732..ffacb7055 100644
--- a/frontend/src/app/main/ui/components/select.cljs
+++ b/frontend/src/app/main/ui/components/select.cljs
@@ -97,15 +97,17 @@
current-icon (:icon selected-option)
current-icon-ref (i/key->icon current-icon)]
[:div {:on-click open-dropdown
- :class (dm/str class " " (stl/css-case :custom-select true
- :disabled disabled
- :icon (some? current-icon-ref)))}
+ :class (dm/str (stl/css-case :custom-select true
+ :disabled disabled
+ :icon (some? current-icon-ref))
+ " " class)}
(when (and current-icon current-icon-ref)
[:span {:class (stl/css :current-icon)} current-icon-ref])
[:span {:class (stl/css :current-label)} current-label]
[:span {:class (stl/css :dropdown-button)} i/arrow-refactor]
[:& dropdown {:show is-open? :on-close close-dropdown}
- [:ul {:ref dropdown-element* :data-direction @dropdown-direction* :class (dm/str dropdown-class " " (stl/css :custom-select-dropdown))}
+ [:ul {:ref dropdown-element* :data-direction @dropdown-direction*
+ :class (dm/str (stl/css :custom-select-dropdown) " " dropdown-class)}
(for [[index item] (d/enumerate options)]
(if (= :separator item)
[:li {:class (dom/classnames (stl/css :separator) true)
diff --git a/frontend/src/app/main/ui/components/select.scss b/frontend/src/app/main/ui/components/select.scss
index 734fb3a40..e904c48ba 100644
--- a/frontend/src/app/main/ui/components/select.scss
+++ b/frontend/src/app/main/ui/components/select.scss
@@ -31,7 +31,7 @@
width: $s-24;
padding-right: $s-4;
svg {
- @extend .button-icon;
+ @extend .button-icon-small;
stroke: var(--icon-foreground);
}
}
diff --git a/frontend/src/app/main/ui/components/title_bar.cljs b/frontend/src/app/main/ui/components/title_bar.cljs
index 61c664303..dcce61678 100644
--- a/frontend/src/app/main/ui/components/title_bar.cljs
+++ b/frontend/src/app/main/ui/components/title_bar.cljs
@@ -13,7 +13,7 @@
(mf/defc title-bar
{::mf/wrap-props false}
- [{:keys [collapsable collapsed on-collapsed title children on-btn-click btn-children class all-clickable]}]
+ [{:keys [collapsable collapsed on-collapsed title children on-btn-click btn-children class all-clickable add-icon-gap origin]}]
(let [klass (dm/str (stl/css :title-bar) " " class)]
[:div {:class klass}
(if ^boolean collapsable
@@ -33,7 +33,10 @@
:on-click on-collapsed}
i/arrow-refactor]
[:div {:class (stl/css :title)} title]])]
- [:div {:class (stl/css :title-only)} title])
+ [:div {:class (stl/css-case :title-only true
+ :title-only-icon-gap add-icon-gap
+ :title-only (not= :inspect origin)
+ :inspect-title (= :inspect origin))} title])
children
(when (some? on-btn-click)
[:button {:class (stl/css :title-button)
diff --git a/frontend/src/app/main/ui/components/title_bar.scss b/frontend/src/app/main/ui/components/title_bar.scss
index 2ec13406b..bfbd756fc 100644
--- a/frontend/src/app/main/ui/components/title_bar.scss
+++ b/frontend/src/app/main/ui/components/title_bar.scss
@@ -14,22 +14,39 @@
width: 100%;
min-height: $s-32;
background-color: var(--title-background-color);
- .title,
- .title-only {
- @include tabTitleTipography;
- display: flex;
- align-items: center;
- flex-grow: 1;
- height: 100%;
- min-height: $s-32;
- color: var(--title-foreground-color);
- overflow: hidden;
- }
- .title-only {
- margin-left: $s-8;
- }
+}
- .title-wrapper {
+.title,
+.title-only,
+.inspect-title {
+ @include tabTitleTipography;
+ display: flex;
+ align-items: center;
+ flex-grow: 1;
+ height: 100%;
+ min-height: $s-32;
+ color: var(--title-foreground-color);
+ overflow: hidden;
+}
+
+.title-only {
+ margin-left: $s-8;
+}
+
+.inspect-title {
+ color: var(--title-foreground-color-hover);
+}
+
+.title-wrapper {
+ display: flex;
+ align-items: center;
+ flex-grow: 1;
+ padding: 0;
+ color: var(--title-foreground-color);
+ stroke: var(--title-foreground-color);
+ overflow: hidden;
+ .toggle-btn {
+ @include buttonStyle;
display: flex;
align-items: center;
flex-grow: 1;
@@ -37,42 +54,7 @@
color: var(--title-foreground-color);
stroke: var(--title-foreground-color);
overflow: hidden;
- .toggle-btn {
- @include buttonStyle;
- display: flex;
- align-items: center;
- flex-grow: 1;
- padding: 0;
- color: var(--title-foreground-color);
- stroke: var(--title-foreground-color);
- overflow: hidden;
- .collapsabled-icon {
- @include flexCenter;
- height: $s-24;
- border-radius: $br-8;
- svg {
- @extend .button-icon-small;
- transform: rotate(90deg);
- stroke: var(--icon-foreground);
- }
- &.rotated svg {
- transform: rotate(0deg);
- }
- }
- &:hover {
- color: var(--title-foreground-color-hover);
- stroke: var(--title-foreground-color-hover);
- .title {
- color: var(--title-foreground-color-hover);
- stroke: var(--title-foreground-color-hover);
- }
- .collapsabled-icon svg {
- stroke: var(--title-foreground-color-hover);
- }
- }
- }
.collapsabled-icon {
- @include buttonStyle;
@include flexCenter;
height: $s-24;
border-radius: $br-8;
@@ -89,6 +71,7 @@
color: var(--title-foreground-color-hover);
stroke: var(--title-foreground-color-hover);
.title {
+ color: var(--title-foreground-color-hover);
stroke: var(--title-foreground-color-hover);
}
.collapsabled-icon svg {
@@ -96,16 +79,61 @@
}
}
}
-
- .title-button {
- @extend .button-tertiary;
- height: $s-32;
- width: calc($s-24 + $s-4);
- padding: 0;
+ .collapsabled-icon {
+ @include buttonStyle;
+ @include flexCenter;
+ height: $s-24;
border-radius: $br-8;
svg {
- @extend .button-icon;
+ @extend .button-icon-small;
+ transform: rotate(90deg);
stroke: var(--icon-foreground);
}
+ &.rotated svg {
+ transform: rotate(0deg);
+ }
+ }
+ &:hover {
+ color: var(--title-foreground-color-hover);
+ stroke: var(--title-foreground-color-hover);
+ .title {
+ stroke: var(--title-foreground-color-hover);
+ }
+ .collapsabled-icon svg {
+ stroke: var(--title-foreground-color-hover);
+ }
}
}
+
+.title-button {
+ @extend .button-tertiary;
+ height: $s-32;
+ width: calc($s-24 + $s-4);
+ padding: 0;
+ border-radius: $br-8;
+ svg {
+ @extend .button-icon;
+ stroke: var(--icon-foreground);
+ }
+}
+
+.title,
+.title-only {
+ @include tabTitleTipography;
+ display: flex;
+ align-items: center;
+ flex-grow: 1;
+ height: 100%;
+ min-height: $s-32;
+ color: var(--title-foreground-color);
+ overflow: hidden;
+}
+
+.title-only {
+ --title-bar-title-margin: #{$s-8};
+ margin-inline-start: var(--title-bar-title-margin);
+}
+
+.title-only-icon-gap {
+ --title-bar-title-margin: #{$s-12};
+}
diff --git a/frontend/src/app/main/ui/dashboard/change_owner.scss b/frontend/src/app/main/ui/dashboard/change_owner.scss
index 2da4ec233..c1f6bf7d9 100644
--- a/frontend/src/app/main/ui/dashboard/change_owner.scss
+++ b/frontend/src/app/main/ui/dashboard/change_owner.scss
@@ -50,3 +50,7 @@
@extend .modal-danger-btn;
}
}
+
+.modal-msg {
+ color: var(--modal-text-foreground-color);
+}
diff --git a/frontend/src/app/main/ui/export.scss b/frontend/src/app/main/ui/export.scss
index 0f687fe61..f528d2eeb 100644
--- a/frontend/src/app/main/ui/export.scss
+++ b/frontend/src/app/main/ui/export.scss
@@ -103,7 +103,7 @@
color: var(--modal-text-foreground-color);
}
.modal-link {
- @include titleTipography;
+ @include bodyLargeTypography;
text-decoration: none;
cursor: pointer;
color: var(--modal-link-foreground-color);
diff --git a/frontend/src/app/main/ui/icons.cljs b/frontend/src/app/main/ui/icons.cljs
index e90cc022e..edcb1016b 100644
--- a/frontend/src/app/main/ui/icons.cljs
+++ b/frontend/src/app/main/ui/icons.cljs
@@ -322,6 +322,13 @@
(def ^:icon column-reverse-refactor (icon-xref :column-reverse-refactor))
(def ^:icon constraint-horizontal-refactor (icon-xref :constraint-horizontal-refactor))
(def ^:icon constraint-vertical-refactor (icon-xref :constraint-vertical-refactor))
+(def ^:icon corner-bottom-refactor (icon-xref :corner-bottom-refactor))
+(def ^:icon corner-bottomleft-refactor (icon-xref :corner-bottomleft-refactor))
+(def ^:icon corner-bottomright-refactor (icon-xref :corner-bottom-refactor))
+(def ^:icon corner-center-refactor (icon-xref :corner-center-refactor))
+(def ^:icon corner-top-refactor (icon-xref :corner-top-refactor))
+(def ^:icon corner-topleft-refactor (icon-xref :corner-topleft-refactor))
+(def ^:icon corner-topright-refactor (icon-xref :corner-topright-refactor))
(def ^:icon corner-radius-refactor (icon-xref :corner-radius-refactor))
(def ^:icon curve-refactor (icon-xref :curve-refactor))
(def ^:icon distribute-vertical-spacing-refactor (icon-xref :distribute-vertical-spacing-refactor))
@@ -479,6 +486,11 @@
(def ^:icon cap-round (icon-xref :cap-round))
(def ^:icon cap-square (icon-xref :cap-square))
+(def ^:icon v2-icon-1 (icon-xref :v2-icon-1))
+(def ^:icon v2-icon-2 (icon-xref :v2-icon-2))
+(def ^:icon v2-icon-3 (icon-xref :v2-icon-3))
+(def ^:icon v2-icon-4 (icon-xref :v2-icon-4))
+
(def ^:icon loader-pencil
(mf/html
diff --git a/frontend/src/app/main/ui/onboarding.scss b/frontend/src/app/main/ui/onboarding.scss
index 7c2e1b22f..66ecddbe3 100644
--- a/frontend/src/app/main/ui/onboarding.scss
+++ b/frontend/src/app/main/ui/onboarding.scss
@@ -78,7 +78,7 @@
}
.modal-link {
- @include titleTipography;
+ @include bodyLargeTypography;
color: var(--modal-link-foreground-color);
margin: 0;
}
diff --git a/frontend/src/app/main/ui/onboarding/newsletter.scss b/frontend/src/app/main/ui/onboarding/newsletter.scss
index 350b75499..86f854597 100644
--- a/frontend/src/app/main/ui/onboarding/newsletter.scss
+++ b/frontend/src/app/main/ui/onboarding/newsletter.scss
@@ -60,7 +60,7 @@
}
.modal-link {
- @include titleTipography;
+ @include bodyLargeTypography;
color: var(--modal-link-foreground-color);
margin: 0;
}
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes.scss b/frontend/src/app/main/ui/viewer/inspect/attributes.scss
index 17a761d01..54980db83 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes.scss
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes.scss
@@ -12,4 +12,5 @@
gap: $s-16;
width: 100%;
height: 100%;
+ padding-top: $s-8;
}
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/blur.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/blur.cljs
index 162fb5db8..3b006e94c 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes/blur.cljs
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes/blur.cljs
@@ -7,6 +7,7 @@
(ns app.main.ui.viewer.inspect.attributes.blur
(:require-macros [app.main.style :as stl])
(:require
+ [app.common.data.macros :as dm]
[app.main.ui.components.copy-button :refer [copy-button]]
[app.main.ui.components.title-bar :refer [title-bar]]
[app.util.code-gen.style-css :as css]
@@ -23,13 +24,16 @@
[:div {:class (stl/css :attributes-block)}
[:& title-bar {:collapsable false
:title (tr "inspect.attributes.blur")
+ :origin :inspect
:class (stl/css :title-spacing-blur)}
(when (= (count shapes) 1)
- [:& copy-button {:data (css/get-css-property objects (first shapes) :filter)}])]
+ [:& copy-button {:data (css/get-css-property objects (first shapes) :filter)
+ :class (stl/css :copy-btn-title)}])]
[:div {:class (stl/css :attributes-content)}
(for [shape shapes]
- [:div {:class (stl/css :blur-row)}
+ [:div {:class (stl/css :blur-row)
+ :key (dm/str "block-" (:id shape) "-blur")}
[:div {:class (stl/css :global/attr-label)} "Filter"]
[:div {:class (stl/css :global/attr-value)}
[:& copy-button {:data (css/get-css-property objects shape :filter)}
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/blur.scss b/frontend/src/app/main/ui/viewer/inspect/attributes/blur.scss
index e2da40708..a3f2dc334 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes/blur.scss
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes/blur.scss
@@ -21,3 +21,7 @@
.button-children {
@extend .copy-button-children;
}
+
+.copy-btn-title {
+ max-width: $s-28;
+}
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/common.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/common.cljs
index 76001fe46..bbf5148f3 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes/common.cljs
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes/common.cljs
@@ -8,6 +8,8 @@
(:require-macros [app.main.style :as stl])
(:require
[app.common.colors :as cc]
+ [app.common.data :as d]
+ [app.common.data.macros :as dm]
[app.common.media :as cm]
[app.config :as cf]
[app.main.refs :as refs]
@@ -15,6 +17,7 @@
[app.main.ui.components.color-bullet-new :as cbn]
[app.main.ui.components.copy-button :refer [copy-button]]
[app.main.ui.components.select :refer [select]]
+ [app.main.ui.formats :as fmt]
[app.util.i18n :refer [tr]]
[cuerdas.core :as str]
[okulary.core :as l]
@@ -43,6 +46,13 @@
(defn- get-file-colors []
(or (mf/deref file-colors-ref) (mf/deref refs/workspace-file-colors)))
+(defn get-css-rule-humanized [property]
+ (as-> property $
+ (d/name $)
+ (str/split $ "-")
+ (str/join " " $)
+ (str/capital $)))
+
(mf/defc color-row [{:keys [color format copy-data on-change-format]}]
(let [colors-library (get-colors-library color)
file-colors (get-file-colors)
@@ -50,85 +60,90 @@
color (assoc color :color-library-name color-library-name)
image (:image color)]
- [:*
- [:div {:class (stl/css :attributes-color-row)}
- [:div {:class (stl/css :bullet-wrapper)
- :style #js {"--bullet-size" "16px"}}
- [:& cbn/color-bullet {:color color
- :mini? true}]]
- (when-not image
- [:div {:class (stl/css :format-wrapper)}
- (when-not (and on-change-format (or (:gradient color) image))
- [:div {:class (stl/css :select-format-wrapper)}
- [:& select
- {:default-value format
- :options [{:value :hex :label (tr "inspect.attributes.color.hex")}
- {:value :rgba :label (tr "inspect.attributes.color.rgba")}
- {:value :hsla :label (tr "inspect.attributes.color.hsla")}]
- :on-change on-change-format}]])
- (when (:gradient color)
- [:div {:class (stl/css :format-info)} "rgba"])])
+ (if image
+ (let [mtype (-> image :mtype)
+ name (or (:name image) (tr "media.image"))
+ extension (cm/mtype->extension mtype)]
+ [:div {:class (stl/css :attributes-image-as-color-row)}
+ [:div {:class (stl/css :attributes-color-row)}
+ [:div {:class (stl/css :bullet-wrapper)
+ :style #js {"--bullet-size" "16px"}}
+ [:& cbn/color-bullet {:color color
+ :mini? true}]]
- (if (and copy-data (not image))
- [:& copy-button {:data copy-data
- :class (stl/css :color-row-copy-btn)}
- [:*
- [:div {:class (stl/css :first-row)}
- [:div {:class (stl/css :name-opacity)}
- [:span {:class (stl/css-case :color-value-wrapper true
- :gradient-name (:gradient color))}
- (if (:gradient color)
- [:& cbn/color-name {:color color :size 80}]
- (case format
- :hex [:& cbn/color-name {:color color}]
- :rgba (let [[r g b a] (cc/hex->rgba (:color color) (:opacity color))]
- [:* (str/fmt "%s, %s, %s, %s" r g b a)])
- :hsla (let [[h s l a] (cc/hex->hsla (:color color) (:opacity color))
- result (cc/format-hsla [h s l a])]
- [:* result])))]
-
- (when-not (:gradient color)
- [:span {:class (stl/css :opacity-info)}
- (str (* 100 (:opacity color)) "%")])]]
-
- (when color-library-name
- [:div {:class (stl/css :second-row)}
- [:div {:class (stl/css :color-name-library)}
- color-library-name]])]]
-
- [:div {:class (stl/css :color-info)}
- [:div {:class (stl/css :first-row)}
- [:div {:class (stl/css :name-opacity)}
- [:span {:class (stl/css-case :color-value-wrapper true
- :gradient-name (:gradient color))}
- (if (:gradient color)
- [:& cbn/color-name {:color color}]
- (case format
- :hex [:& cbn/color-name {:color color
- :size 80}]
- :rgba (let [[r g b a] (cc/hex->rgba (:color color) (:opacity color))]
- [:* (str/fmt "%s, %s, %s, %s" r g b a)])
- :hsla (let [[h s l a] (cc/hex->hsla (:color color) (:opacity color))
- result (cc/format-hsla [h s l a])]
- [:* result])))]
-
- (when-not (:gradient color)
+ [:div {:class (stl/css :format-wrapper)}
+ [:div {:class (stl/css :image-format)}
+ (tr "media.image.short")]]
+ [:& copy-button {:data copy-data
+ :class (stl/css :color-row-copy-btn)}
+ [:div {:class (stl/css-case :color-info true
+ :two-line (some? color-library-name))}
+ [:div {:class (stl/css :first-row)}
[:span {:class (stl/css :opacity-info)}
- (str (* 100 (:opacity color)) "%")])]]
+ (str (* 100 (:opacity color)) "%")]]
- (when color-library-name
- [:div {:class (stl/css :second-row)}
- [:div {:class (stl/css :color-name-library)}
- color-library-name]])])]
+ (when color-library-name
+ [:div {:class (stl/css :second-row)}
+ [:div {:class (stl/css :color-name-library)}
+ color-library-name]])]]
- (when image
- (let [mtype (-> image :mtype)
- name (or (:name image) (tr "media.image"))
- extension (cm/mtype->extension mtype)]
- [:a {:class (stl/css :download-button)
- :target "_blank"
- :download (cond-> name extension (str/concat extension))
- :href (cf/resolve-file-media image)}
- (tr "inspect.attributes.image.download")]))]))
+ [:div {:class (stl/css :image-download)}
+ [:div {:class (stl/css :image-wrapper)}
+ [:img {:src (cf/resolve-file-media image)}]]
+
+ [:a {:class (stl/css :download-button)
+ :target "_blank"
+ :download (cond-> name extension (str/concat extension))
+ :href (cf/resolve-file-media image)}
+ (tr "inspect.attributes.image.download")]]]])
+
+ [:div {:class (stl/css :attributes-color-row)}
+ [:div {:class (stl/css :bullet-wrapper)
+ :style #js {"--bullet-size" "16px"}}
+ [:& cbn/color-bullet {:color color
+ :mini? true}]]
+
+ [:div {:class (stl/css :format-wrapper)}
+ (when-not (and on-change-format (or (:gradient color) image))
+ [:& select
+ {:default-value format
+ :class (stl/css :select-format-wrapper)
+ :options [{:value :hex :label (tr "inspect.attributes.color.hex")}
+ {:value :rgba :label (tr "inspect.attributes.color.rgba")}
+ {:value :hsla :label (tr "inspect.attributes.color.hsla")}]
+ :on-change on-change-format}])
+ (when (:gradient color)
+ [:div {:class (stl/css :format-info)} "rgba"])]
+
+ [:& copy-button {:data copy-data
+ :class (stl/css-case :color-row-copy-btn true
+ :one-line (not color-library-name)
+ :two-line (some? color-library-name))}
+ [:div {:class (stl/css :first-row)}
+ [:div {:class (stl/css :name-opacity)}
+ [:span {:class (stl/css-case :color-value-wrapper true
+ :gradient-name (:gradient color))}
+ (if (:gradient color)
+ [:& cbn/color-name {:color color :size 90}]
+ (case format
+ :hex [:& cbn/color-name {:color color}]
+ :rgba (let [[r g b a] (cc/hex->rgba (:color color) (:opacity color))]
+ (str/ffmt "%, %, %, %" r g b a))
+ :hsla (let [[h s l a] (cc/hex->hsla (:color color) (:opacity color))
+ result (cc/format-hsla [h s l a])]
+ [:* result])))]
+
+ (when-not (:gradient color)
+ [:span {:class (stl/css :opacity-info)}
+ (dm/str (-> color
+ (:opacity)
+ (d/coalesce 1)
+ (* 100)
+ (fmt/format-number)) "%")])]]
+
+ (when color-library-name
+ [:div {:class (stl/css :second-row)}
+ [:div {:class (stl/css :color-name-library)}
+ color-library-name]])]])))
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/common.scss b/frontend/src/app/main/ui/viewer/inspect/attributes/common.scss
index f05e0faa4..7f30931c3 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes/common.scss
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes/common.scss
@@ -6,9 +6,13 @@
@import "refactor/common-refactor.scss";
+.attributes-image-as-color-row {
+ max-width: $s-240;
+}
+
.attributes-color-row {
display: grid;
- grid-template-columns: $s-16 $s-72 $s-156;
+ grid-template-columns: $s-16 $s-72 $s-144;
gap: $s-4;
}
@@ -22,14 +26,19 @@
height: $s-32;
}
+.image-format {
+ @include tabTitleTipography;
+ height: $s-32;
+ padding: $s-8 0;
+ color: var(--menu-foreground-color-rest);
+}
+
.select-format-wrapper {
width: 100%;
- div {
- background-color: transparent;
- border: none;
- padding-left: $s-2;
- color: var(--menu-foreground-color-rest);
- }
+ padding: $s-8 $s-2;
+ background-color: transparent;
+ border-color: transparent;
+ color: var(--menu-foreground-color-rest);
}
.format-info {
@@ -43,12 +52,16 @@
color: var(--menu-foreground-color-rest);
}
+.color-row-copy-btn {
+ max-width: $s-144;
+}
+
.color-info {
display: flex;
align-items: flex-start;
gap: $s-4;
flex-grow: 1;
-
+ max-width: $s-144;
button {
visibility: hidden;
min-width: $s-28;
@@ -57,19 +70,22 @@
visibility: visible;
}
}
-
-.name-opacity {
- display: flex;
- align-items: baseline;
+.one-line {
+ max-height: $s-32;
+}
+.two-line {
+ display: grid;
+ grid-template-rows: 1fr 1fr;
}
-
.color-name-wrapper {
@include titleTipography;
@include flexColumn;
padding: $s-8 $s-4 $s-8 $s-8;
height: $s-32;
max-width: $s-80;
+
&.gradient-color {
+ color: var(--menu-foreground-color);
max-width: $s-124;
}
.color-name-library {
@@ -92,16 +108,9 @@
padding: $s-8 0;
}
-.color-info,
-.color-row-copy-btn {
- display: flex;
- max-width: $s-144;
-}
-
.first-row {
display: grid;
- grid-template-columns: 1fr $s-24;
- grid-template-areas: "name button";
+ grid-template-columns: 1fr $s-28;
height: fit-content;
width: 100%;
padding: 0;
@@ -109,27 +118,26 @@
}
.name-opacity {
- grid-area: name;
height: fit-content;
- max-width: $s-124;
+ width: 100%;
line-height: $s-16;
+ display: grid;
+ grid-template-columns: 1fr auto;
}
.color-value-wrapper {
+ @include textEllipsis;
@include inspectValue;
text-transform: uppercase;
- max-width: $s-80;
- padding-right: $s-8;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
&.gradient-name {
text-transform: none;
}
}
+
.opacity-info {
@include inspectValue;
text-transform: uppercase;
+ width: 100%;
}
.second-row {
@@ -146,9 +154,32 @@
color: var(--menu-foreground-color-rest);
}
+.image-download {
+ grid-column: 1 / 4;
+}
+
.download-button {
@extend .button-secondary;
@include tabTitleTipography;
height: $s-32;
+ width: 100%;
margin-top: $s-4;
}
+
+.image-wrapper {
+ background-color: var(--menu-background-color);
+ position: relative;
+ @include flexCenter;
+ width: $s-240;
+ height: $s-160;
+ max-height: $s-160;
+ max-width: $s-248;
+ margin: $s-8 0;
+ border-radius: $br-8;
+
+ img {
+ height: 100%;
+ width: 100%;
+ object-fit: contain;
+ }
+}
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/fill.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/fill.cljs
index 8383ca085..85f607333 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes/fill.cljs
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes/fill.cljs
@@ -35,7 +35,6 @@
[{:keys [objects shape]}]
(let [format* (mf/use-state :hex)
format (deref format*)
-
color (shape->color shape)
on-change
(mf/use-fn
@@ -55,6 +54,7 @@
(when (seq shapes)
[:div {:class (stl/css :attributes-block)}
[:& title-bar {:collapsable false
+ :origin :inspect
:title (tr "inspect.attributes.fill")
:class (stl/css :title-spacing-fill)}]
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/fill.scss b/frontend/src/app/main/ui/viewer/inspect/attributes/fill.scss
index 3f3e3a9bd..9515dad3e 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes/fill.scss
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes/fill.scss
@@ -13,3 +13,8 @@
.title-spacing-fill {
@extend .attr-title;
}
+
+.attributes-content {
+ display: grid;
+ gap: $s-4;
+}
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/geometry.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/geometry.cljs
index 4735502f7..cc7cc70f5 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes/geometry.cljs
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes/geometry.cljs
@@ -11,6 +11,7 @@
[app.common.data.macros :as dm]
[app.main.ui.components.copy-button :refer [copy-button]]
[app.main.ui.components.title-bar :refer [title-bar]]
+ [app.main.ui.viewer.inspect.attributes.common :as cmm]
[app.util.code-gen.style-css :as css]
[app.util.i18n :refer [tr]]
[rumext.v2 :as mf]))
@@ -22,12 +23,14 @@
[:*
(for [[idx property] (d/enumerate properties)]
(when-let [value (css/get-css-value objects shape property)]
- [:div {:key (dm/str "block-" idx "-" (d/name property))
- :class (stl/css :geometry-row)}
- [:div {:class (stl/css :global/attr-label)} (d/name property)]
- [:div {:class (stl/css :global/attr-value)}
- [:& copy-button {:data (css/get-css-property objects shape property)}
- [:div {:class (stl/css :button-children)} value]]]]))])
+ (let [property-name (cmm/get-css-rule-humanized property)]
+ [:div {:key (dm/str "block-" idx "-" (d/name property))
+ :title property-name
+ :class (stl/css :geometry-row)}
+ [:div {:class (stl/css :global/attr-label)} property-name]
+ [:div {:class (stl/css :global/attr-value)}
+ [:& copy-button {:data (css/get-css-property objects shape property)}
+ [:div {:class (stl/css :button-children)} value]]]])))])
(mf/defc geometry-panel
@@ -35,10 +38,12 @@
[:div {:class (stl/css :attributes-block)}
[:& title-bar {:collapsable false
:title (tr "inspect.attributes.size")
+ :origin :inspect
:class (stl/css :title-spacing-geometry)}
(when (= (count shapes) 1)
- [:& copy-button {:data (css/get-shape-properties-css objects (first shapes) properties)}])]
+ [:& copy-button {:data (css/get-shape-properties-css objects (first shapes) properties)
+ :class (stl/css :copy-btn-title)}])]
(for [shape shapes]
[:& geometry-block {:shape shape
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/geometry.scss b/frontend/src/app/main/ui/viewer/inspect/attributes/geometry.scss
index 2b1f431fb..b32c0c103 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes/geometry.scss
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes/geometry.scss
@@ -21,3 +21,7 @@
.button-children {
@extend .copy-button-children;
}
+
+.copy-btn-title {
+ max-width: $s-28;
+}
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/image.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/image.cljs
index 4f22213db..093b6a657 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes/image.cljs
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes/image.cljs
@@ -23,7 +23,7 @@
[{:keys [objects shapes]}]
(for [shape (filter cfh/image-shape? shapes)]
[:div {:class (stl/css :attributes-block)
- :key (str "image-" (:id shape))}
+ :key (str "image-" (:id shape))}
[:div {:class (stl/css :image-wrapper)}
[:img {:src (cf/resolve-file-media (-> shape :metadata))}]]
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/layout.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/layout.cljs
index e015aecd1..ecfb19e3b 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes/layout.cljs
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes/layout.cljs
@@ -8,9 +8,11 @@
(:require-macros [app.main.style :as stl])
(:require
[app.common.data :as d]
+ [app.common.data.macros :as dm]
[app.common.types.shape.layout :as ctl]
[app.main.ui.components.copy-button :refer [copy-button]]
[app.main.ui.components.title-bar :refer [title-bar]]
+ [app.main.ui.viewer.inspect.attributes.common :as cmm]
[app.util.code-gen.style-css :as css]
[rumext.v2 :as mf]))
@@ -31,13 +33,16 @@
[{:keys [objects shape]}]
(for [property properties]
(when-let [value (css/get-css-value objects shape property)]
- [:div {:class (stl/css :layout-row)}
- [:div {:title (d/name property)
- :class (stl/css :global/attr-label)} (d/name property)]
- [:div {:class (stl/css :global/attr-value)}
+ (let [property-name (cmm/get-css-rule-humanized property)]
+ [:div {:class (stl/css :layout-row)}
+ [:div {:title property-name
+ :key (dm/str "layout-" (:id shape) "-" (d/name property))
+ :class (stl/css :global/attr-label)}
+ property-name]
+ [:div {:class (stl/css :global/attr-value)}
- [:& copy-button {:data (css/get-css-property objects shape property)}
- [:div {:class (stl/css :button-children)} value]]]])))
+ [:& copy-button {:data (css/get-css-property objects shape property)}
+ [:div {:class (stl/css :button-children)} value]]]]))))
(mf/defc layout-panel
[{:keys [objects shapes]}]
@@ -46,11 +51,13 @@
(when (seq shapes)
[:div {:class (stl/css :attributes-block)}
[:& title-bar {:collapsable false
+ :origin :inspect
:title "Layout"
:class (stl/css :title-spacing-layout)}
(when (= (count shapes) 1)
- [:& copy-button {:data (css/get-shape-properties-css objects (first shapes) properties)}])]
+ [:& copy-button {:data (css/get-shape-properties-css objects (first shapes) properties)
+ :class (stl/css :copy-btn-title)}])]
(for [shape shapes]
[:& layout-block {:shape shape
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/layout.scss b/frontend/src/app/main/ui/viewer/inspect/attributes/layout.scss
index e2a409c0f..8a84e1d98 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes/layout.scss
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes/layout.scss
@@ -21,3 +21,7 @@
.button-children {
@extend .copy-button-children;
}
+
+.copy-btn-title {
+ max-width: $s-28;
+}
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/layout_element.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/layout_element.cljs
index 14eaf7f93..4ba5a98a6 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes/layout_element.cljs
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes/layout_element.cljs
@@ -8,9 +8,11 @@
(:require-macros [app.main.style :as stl])
(:require
[app.common.data :as d]
+ [app.common.data.macros :as dm]
[app.common.types.shape.layout :as ctl]
[app.main.ui.components.copy-button :refer [copy-button]]
[app.main.ui.components.title-bar :refer [title-bar]]
+ [app.main.ui.viewer.inspect.attributes.common :as cmm]
[app.util.code-gen.style-css :as css]
[rumext.v2 :as mf]))
@@ -33,12 +35,14 @@
[{:keys [objects shape]}]
(for [property properties]
(when-let [value (css/get-css-value objects shape property)]
- [:div {:class (stl/css :layout-element-row)}
- [:div {:class (stl/css :global/attr-label)} (d/name property)]
- [:div {:class (stl/css :global/attr-value)}
+ (let [property-name (cmm/get-css-rule-humanized property)]
+ [:div {:class (stl/css :layout-element-row)
+ :key (dm/str "layout-element-" (:id shape) "-" (d/name property))}
+ [:div {:class (stl/css :global/attr-label)} property-name]
+ [:div {:class (stl/css :global/attr-value)}
- [:& copy-button {:data (css/get-css-property objects shape property)}
- [:div {:class (stl/css :button-children)} value]]]])))
+ [:& copy-button {:data (css/get-css-property objects shape property)}
+ [:div {:class (stl/css :button-children)} value]]]]))))
(mf/defc layout-element-panel
[{:keys [objects shapes]}]
@@ -67,7 +71,8 @@
:title menu-title
:class (stl/css :title-spacing-layout-element)}
(when (= (count shapes) 1)
- [:& copy-button {:data (css/get-shape-properties-css objects (first shapes) properties)}])]
+ [:& copy-button {:data (css/get-shape-properties-css objects (first shapes) properties)
+ :class (stl/css :copy-btn-title)}])]
(for [shape shapes]
[:& layout-element-block {:shape shape
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/layout_element.scss b/frontend/src/app/main/ui/viewer/inspect/attributes/layout_element.scss
index 6ef7e6cea..56b174dd5 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes/layout_element.scss
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes/layout_element.scss
@@ -21,3 +21,7 @@
.button-children {
@extend .copy-button-children;
}
+
+.copy-btn-title {
+ max-width: $s-28;
+}
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/shadow.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/shadow.cljs
index 586d4488a..c77ab51b3 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes/shadow.cljs
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes/shadow.cljs
@@ -54,6 +54,7 @@
(when (and (seq shapes) (> (count shapes) 0))
[:div {:class (stl/css :attributes-block)}
[:& title-bar {:collapsable false
+ :origin :inspect
:title (tr "inspect.attributes.shadow")
:class (stl/css :title-spacing-shadow)}]
@@ -61,4 +62,5 @@
(for [shape shapes]
(for [shadow (:shadow shape)]
[:& shadow-block {:shape shape
+ :key (dm/str "block-" (:id shape) "-shadow")
:shadow shadow}]))]])))
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/stroke.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/stroke.cljs
index 9dd86a035..2ba125be3 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes/stroke.cljs
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes/stroke.cljs
@@ -62,6 +62,7 @@
(when (seq shapes)
[:div {:class (stl/css :attributes-block)}
[:& title-bar {:collapsable false
+ :origin :inspect
:title (tr "inspect.attributes.stroke")
:class (stl/css :title-spacing-stroke)}]
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/stroke.scss b/frontend/src/app/main/ui/viewer/inspect/attributes/stroke.scss
index 86cb67104..fe0f59df4 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes/stroke.scss
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes/stroke.scss
@@ -25,3 +25,8 @@
.button-children {
@extend .copy-button-children;
}
+
+.attributes-content {
+ display: grid;
+ gap: $s-4;
+}
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/svg.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/svg.cljs
index 8df36110f..c788c95db 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes/svg.cljs
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes/svg.cljs
@@ -27,19 +27,25 @@
[:& copy-button {:data (map->css value)}]]
(for [[attr-key attr-value] value]
- [:& svg-attr {:attr attr-key :value attr-value}])]
+ [:& svg-attr {:attr attr-key :value attr-value :key (str/join "svg-key-" attr-key)}])]
- [:div {:class (stl/css :svg-row)}
- [:div {:class (stl/css :global/attr-label)} (d/name attr)]
- [:div {:class (stl/css :global/attr-value)}
- [:& copy-button {:data (d/name value)}
- [:div {:class (stl/css :button-children)} (str value)]]]]))
+ (let [attr-name (as-> attr $
+ (d/name $)
+ (str/split $ "-")
+ (str/join " " $)
+ (str/capital $))]
+ [:div {:class (stl/css :svg-row)}
+ [:div {:class (stl/css :global/attr-label)} attr-name]
+ [:div {:class (stl/css :global/attr-value)}
+ [:& copy-button {:data (d/name value)
+ :class (stl/css :copy-btn-title)}
+ [:div {:class (stl/css :button-children)} (str value)]]]])))
(mf/defc svg-block
[{:keys [shape]}]
[:*
(for [[attr-key attr-value] (:svg-attrs shape)]
- [:& svg-attr {:attr attr-key :value attr-value}])])
+ [:& svg-attr {:attr attr-key :value attr-value :key (str/join "svg-block-key" attr-key)}])])
(mf/defc svg-panel
@@ -48,6 +54,7 @@
(when (seq (:svg-attrs shape))
[:div {:class (stl/css :attributes-block)}
[:& title-bar {:collapsable false
+ :origin :inspect
:title (tr "workspace.sidebar.options.svg-attrs.title")
:class (stl/css :title-spacing-svg)}]
[:& svg-block {:shape shape}]])))
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/text.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/text.cljs
index f3f817397..137fcc01c 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes/text.cljs
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes/text.cljs
@@ -99,7 +99,7 @@
[:div {:class (stl/css :global/attr-value)}
[:& copy-button {:data (copy-style-data style :font-style)}
[:div {:class (stl/css :button-children)}
- (str (:font-style style))]]]])
+ (dm/str (:font-style style))]]]])
(when (:font-size style)
[:div {:class (stl/css :text-row)}
@@ -117,7 +117,7 @@
[:div {:class (stl/css :global/attr-value)}
[:& copy-button {:data (copy-style-data style :font-weight)}
[:div {:class (stl/css :button-children)}
- (str (:font-weight style))]]]])
+ (dm/str (:font-weight style))]]]])
(when (:line-height style)
[:div {:class (stl/css :text-row)}
@@ -191,9 +191,10 @@
(when-let [shapes (seq (filter has-text? shapes))]
[:div {:class (stl/css :attributes-block)}
[:& title-bar {:collapsable false
+ :origin :inspect
:title (tr "inspect.attributes.typography")
:class (stl/css :title-spacing-text)}]
(for [shape shapes]
[:& text-block {:shape shape
- :key (str "text-block" (:id shape))}])]))
+ :key (dm/str "text-block" (:id shape))}])]))
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/text.scss b/frontend/src/app/main/ui/viewer/inspect/attributes/text.scss
index a506af1b4..d32a5a516 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes/text.scss
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes/text.scss
@@ -30,7 +30,7 @@
}
.attributes-content-row {
- max-width: $s-252;
+ max-width: $s-240;
min-height: calc($s-2 + $s-32);
border-radius: $br-8;
border: $s-1 solid var(--menu-border-color-disabled);
diff --git a/frontend/src/app/main/ui/viewer/inspect/code.cljs b/frontend/src/app/main/ui/viewer/inspect/code.cljs
index 03afee4ac..6baa45ef9 100644
--- a/frontend/src/app/main/ui/viewer/inspect/code.cljs
+++ b/frontend/src/app/main/ui/viewer/inspect/code.cljs
@@ -19,7 +19,7 @@
[app.main.store :as st]
[app.main.ui.components.code-block :refer [code-block]]
[app.main.ui.components.copy-button :refer [copy-button]]
- [app.main.ui.components.select :refer [select]]
+ [app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]]
[app.main.ui.hooks :as hooks]
[app.main.ui.hooks.resize :refer [use-resize-hook]]
[app.main.ui.icons :as i]
@@ -177,10 +177,10 @@
style-size :size}
(use-resize-hook :code 400 100 800 :y false :bottom)
- set-style
- (mf/use-callback
- (fn [value]
- (reset! style-type* value)))
+ ;; set-style
+ ;; (mf/use-callback
+ ;; (fn [value]
+ ;; (reset! style-type* value)))
set-markup
(mf/use-callback
@@ -251,10 +251,13 @@
:rotated collapsed-css?)}
i/arrow-refactor]]
- [:& select {:default-value style-type
- :class (stl/css :code-lang-select)
- :on-change set-style
- :options [{:label "CSS" :value "css"}]}]
+ [:div {:class (stl/css :code-lang-option)}
+ "CSS"]
+ ;; We will have a select when we have more than one option
+ ;; [:& select {:default-value style-type
+ ;; :class (stl/css :code-lang-select)
+ ;; :on-change set-style
+ ;; :options [{:label "CSS" :value "css"}]}]
[:div {:class (stl/css :action-btns)}
[:button {:class (stl/css :expand-button)
@@ -262,6 +265,7 @@
i/code-refactor]
[:& copy-button {:data #(replace-map style-code images-data)
+ :class (stl/css :css-copy-btn)
:on-copied on-style-copied}]]]
(when-not collapsed-css?
@@ -285,11 +289,16 @@
:collapsabled-icon true
:rotated collapsed-markup?)}
i/arrow-refactor]]
- [:& select {:default-value markup-type
- :class (stl/css :code-lang-select)
- :options [{:label "HTML" :value "html"}
- {:label "SVG" :value "svg"}]
- :on-change set-markup}]
+
+ [:& radio-buttons {:selected markup-type
+ :on-change set-markup
+ :class (stl/css :code-lang-options)
+ :wide true
+ :name "listing-style"}
+ [:& radio-button {:value "html"
+ :id :html}]
+ [:& radio-button {:value "svg"
+ :id :svg}]]
[:div {:class (stl/css :action-btns)}
[:button {:class (stl/css :expand-button)
@@ -297,6 +306,7 @@
i/code-refactor]
[:& copy-button {:data #(replace-map markup-code images-data)
+ :class (stl/css :html-copy-btn)
:on-copied on-markup-copied}]]]
(when-not collapsed-markup?
diff --git a/frontend/src/app/main/ui/viewer/inspect/code.scss b/frontend/src/app/main/ui/viewer/inspect/code.scss
index 5b6d66c9c..a383684c7 100644
--- a/frontend/src/app/main/ui/viewer/inspect/code.scss
+++ b/frontend/src/app/main/ui/viewer/inspect/code.scss
@@ -48,8 +48,8 @@
}
.code-row-lang {
- display: flex;
- justify-content: space-between;
+ display: grid;
+ grid-template-columns: $s-12 1fr $s-60;
gap: $s-4;
width: 100%;
}
@@ -61,13 +61,14 @@
}
.action-btns {
- display: flex;
+ display: grid;
+ grid-template-columns: 1fr 1fr;
gap: $s-4;
- flex: 1;
- justify-content: end;
}
-.expand-button {
+.expand-button,
+.css-copy-btn,
+.html-copy-btn {
@extend .button-tertiary;
height: $s-32;
width: $s-28;
@@ -77,6 +78,9 @@
}
}
+.code-lang-options {
+ max-width: $s-108;
+}
.code-lang-select {
@include tabTitleTipography;
width: $s-72;
@@ -84,6 +88,13 @@
background-color: transparent;
color: var(--menu-foreground-color-disabled);
}
+.code-lang-option {
+ @include tabTitleTipography;
+ width: $s-72;
+ height: $s-32;
+ padding: $s-8;
+ color: var(--menu-foreground-color-disabled);
+}
.code-row-display {
flex: 1;
diff --git a/frontend/src/app/main/ui/viewer/inspect/right_sidebar.scss b/frontend/src/app/main/ui/viewer/inspect/right_sidebar.scss
index b66e44ce7..5d9646ab6 100644
--- a/frontend/src/app/main/ui/viewer/inspect/right_sidebar.scss
+++ b/frontend/src/app/main/ui/viewer/inspect/right_sidebar.scss
@@ -14,8 +14,6 @@
left: unset;
right: unset;
grid-area: right-sidebar;
- padding-top: $s-8;
- padding-left: $s-12;
overflow: hidden;
&.viewer-code {
height: calc(100vh - $s-48);
@@ -26,18 +24,20 @@
height: 100%;
display: flex;
flex-direction: column;
+ gap: $s-8;
}
.shape-row {
display: flex;
gap: $s-8;
align-items: center;
- margin-bottom: $s-16;
+ height: $s-32;
}
.layers-icon,
.shape-icon {
@include flexCenter;
+ height: $s-32;
svg {
@extend .button-icon-small;
stroke: var(--icon-foreground);
@@ -46,7 +46,9 @@
.layer-title {
@include titleTipography;
- color: $df-primary;
+ height: $s-32;
+ padding: $s-8 0;
+ color: var(--assets-item-name-foreground-color-rest);
}
.empty {
diff --git a/frontend/src/app/main/ui/workspace/color_palette.cljs b/frontend/src/app/main/ui/workspace/color_palette.cljs
index ceaeebd9b..b07848671 100644
--- a/frontend/src/app/main/ui/workspace/color_palette.cljs
+++ b/frontend/src/app/main/ui/workspace/color_palette.cljs
@@ -31,7 +31,7 @@
:title (uc/get-color-name color)
:on-click select-color}
[:& cb/color-bullet {:color color}]
- [:& cb/color-name {:color color :size size}]]))
+ [:& cb/color-name {:color color :size size :origin :palette}]]))
(mf/defc palette
diff --git a/frontend/src/app/main/ui/workspace/colorpicker.cljs b/frontend/src/app/main/ui/workspace/colorpicker.cljs
index c98caaa6c..e74f83525 100644
--- a/frontend/src/app/main/ui/workspace/colorpicker.cljs
+++ b/frontend/src/app/main/ui/workspace/colorpicker.cljs
@@ -145,7 +145,8 @@
on-select-library-color
(mf/use-fn
(fn [_ color]
- (st/emit! (dc/apply-color-from-colorpicker color))))
+ (st/emit! (dc/apply-color-from-colorpicker color))
+ (on-change color)))
on-add-library-color
(mf/use-fn
diff --git a/frontend/src/app/main/ui/workspace/libraries.cljs b/frontend/src/app/main/ui/workspace/libraries.cljs
index 3262f202b..871c53799 100644
--- a/frontend/src/app/main/ui/workspace/libraries.cljs
+++ b/frontend/src/app/main/ui/workspace/libraries.cljs
@@ -15,6 +15,7 @@
[app.common.types.typographies-list :as ctyl]
[app.common.uuid :as uuid]
[app.main.data.modal :as modal]
+ [app.main.data.users :as du]
[app.main.data.workspace.libraries :as dwl]
[app.main.refs :as refs]
[app.main.render :refer [component-svg]]
@@ -511,3 +512,58 @@
[:& updates-tab {:file-id file-id
:file-data file-data
:libraries libraries}]]]]]]]]))
+
+(mf/defc v2-info-dialog
+ {::mf/register modal/components
+ ::mf/register-as :v2-info}
+ []
+ (let [handle-gotit-click
+ (mf/use-fn
+ (fn []
+ (modal/hide!)
+ (st/emit! (du/update-profile-props {:v2-info-shown true}))))]
+
+ [:div {:class (stl/css :modal-overlay)}
+ [:div {:class (stl/css :modal-dialog :modal-v2-info)}
+ [:div {:class (stl/css :modal-title)} "IMPORTANT INFORMATION ABOUT NEW COMPONENTS"]
+ [:div {:class (stl/css :modal-content)}
+ [:div {:class (stl/css :info-content)}
+ [:div {:class (stl/css :info-block)}
+ [:div {:class (stl/css :info-icon)} i/v2-icon-1]
+ [:div {:class (stl/css :info-block-title)}
+ "One physical source of truth"]
+ [:div {:class (stl/css :info-block-content)}
+ "Main components are now found at the design space. They act as a single source "
+ "of truth and can be worked on with their copies. This ensures consistency and "
+ "allows better control and synchronization."]]
+
+ [:div {:class (stl/css :info-block)}
+ [:div {:class (stl/css :info-icon)} i/v2-icon-2]
+ [:div {:class (stl/css :info-block-title)}
+ "Swap components"]
+ [:div {:class (stl/css :info-block-content)}
+ "Now, you can replace one component copy with another within your libraries. "
+ "The swap components functionality streamlines making changes, testing "
+ "variations, or updating elements without extensive manual adjustments."]]
+
+ [:div {:class (stl/css :info-block)}
+ [:div {:class (stl/css :info-icon)} i/v2-icon-3]
+ [:div {:class (stl/css :info-block-title)}
+ "Graphic assets no longer exist"]
+ [:div {:class (stl/css :info-block-content)}
+ "Graphic assets now disappear, so that all graphic assets become components. "
+ "This way, swapping between them is possible, and we avoid confusion about "
+ "what should go in each typology."]]
+
+ [:div {:class (stl/css :info-block)}
+ [:div {:class (stl/css :info-icon)} i/v2-icon-4]
+ [:div {:class (stl/css :info-block-title)}
+ "Main components page"]
+ [:div {:class (stl/css :info-block-content)}
+ "You might find that a new page called 'Main components' has appeared in "
+ "your file. On that page, you'll find all the main components that were "
+ "created in your files previously to this new version."]]]
+
+ [:div {:class (stl/css :info-bottom)}
+ [:button {:class (stl/css :primary-button)
+ :on-click handle-gotit-click} "I GOT IT"]]]]]))
diff --git a/frontend/src/app/main/ui/workspace/libraries.scss b/frontend/src/app/main/ui/workspace/libraries.scss
index 2664f3209..3fa999a1e 100644
--- a/frontend/src/app/main/ui/workspace/libraries.scss
+++ b/frontend/src/app/main/ui/workspace/libraries.scss
@@ -228,6 +228,15 @@
}
}
}
+
+ .modal-v2-info {
+ width: $s-664;
+ height: fit-content;
+
+ .modal-title {
+ font-size: $fs-18;
+ }
+ }
}
.item-contents {
@@ -245,3 +254,61 @@
margin-inline: $s-4;
}
}
+
+.info-content {
+ margin-top: $s-32;
+ display: flex;
+ flex-direction: column;
+ gap: $s-24;
+
+ .info-block {
+ display: grid;
+ grid-template-columns: auto 1fr;
+ column-gap: $s-20;
+ grid-template:
+ "icon title"
+ "icon content";
+ }
+
+ .info-icon {
+ grid-area: icon;
+ width: $s-52;
+ height: $s-52;
+ margin-top: $s-8;
+ border-radius: $br-circle;
+ background: $db-quaternary;
+
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ svg {
+ width: $s-32;
+ height: $s-32;
+ fill: $da-primary;
+ }
+ }
+ .info-block-title {
+ grid-area: title;
+ font-size: $fs-16;
+ color: $df-primary;
+ }
+ .info-block-content {
+ grid-area: content;
+ font-size: $fs-14;
+ color: $df-secondary;
+ line-height: 1.2;
+ }
+}
+
+.info-bottom {
+ margin-top: $s-24;
+ margin-right: $s-8;
+ display: flex;
+ justify-content: flex-end;
+ .primary-button {
+ @extend .button-primary;
+ @include tabTitleTipography;
+ padding: $s-0 $s-16;
+ }
+}
diff --git a/frontend/src/app/main/ui/workspace/sidebar.scss b/frontend/src/app/main/ui/workspace/sidebar.scss
index b2ece935e..b14ca1253 100644
--- a/frontend/src/app/main/ui/workspace/sidebar.scss
+++ b/frontend/src/app/main/ui/workspace/sidebar.scss
@@ -16,7 +16,6 @@ $width-settings-bar-max: $s-500;
"content resize";
grid-template-rows: $s-52 1fr;
grid-template-columns: 1fr 0;
- gap: $s-8 0;
position: relative;
grid-area: left-sidebar;
min-width: $width-settings-bar;
@@ -85,10 +84,10 @@ $width-settings-bar-max: $s-500;
.resize-area-horiz {
position: absolute;
- top: calc($s-80 + var(--height, 200px));
+ // top: calc($s-88 + var(--height, 200px));
left: 0;
width: 100%;
- height: $s-12;
- border-top: $s-2 solid var(--resize-area-border-color);
+ // height: $s-8;
+ border-bottom: $s-2 solid var(--resize-area-border-color);
cursor: ns-resize;
}
diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets/common.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets/common.cljs
index d2f3ed1db..3ef6fb6ad 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/assets/common.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/assets/common.cljs
@@ -166,6 +166,7 @@
:collapsed (not open?)
:all-clickable true
:on-collapsed on-collapsed
+ :add-icon-gap (= 0 assets-count)
:class (stl/css-case :title-spacing open?)
:title title}
buttons]
diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets/common.scss b/frontend/src/app/main/ui/workspace/sidebar/assets/common.scss
index 84d86bdc8..063cfa831 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/assets/common.scss
+++ b/frontend/src/app/main/ui/workspace/sidebar/assets/common.scss
@@ -21,8 +21,8 @@
@include flexCenter;
height: $s-16;
width: $s-16;
- color: transparent;
fill: none;
+ stroke: currentColor;
}
}
diff --git a/frontend/src/app/main/ui/workspace/sidebar/layers.scss b/frontend/src/app/main/ui/workspace/sidebar/layers.scss
index 4b39fbaa4..25cf2cc4a 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/layers.scss
+++ b/frontend/src/app/main/ui/workspace/sidebar/layers.scss
@@ -241,8 +241,7 @@
}
.tool-window-content {
- // TODO: sass variables are not being interpolated here, find why
- --calculated-height: calc(128px + var(--height, 200px));
+ --calculated-height: calc(#{$s-136} + var(--height, #{$s-200}));
display: flex;
flex-direction: column;
height: calc(100vh - var(--calculated-height));
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/bool.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/bool.cljs
index 7a25e1bf5..c2baaf769 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/bool.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/bool.cljs
@@ -17,6 +17,9 @@
[app.util.i18n :as i18n :refer [tr]]
[rumext.v2 :as mf]))
+(def ^:private flatten-icon
+ (i/icon-xref :boolean-flatten-refactor (stl/css :flatten-icon)))
+
(mf/defc bool-options
[]
(let [selected (mf/deref refs/selected-objects)
@@ -86,9 +89,9 @@
[:button
{:title (tr "workspace.shape.menu.flatten")
:class (stl/css-case
- :flatten true
+ :flatten-button true
:disabled disabled-flatten)
:disabled disabled-flatten
:on-click flatten-objects}
- i/boolean-flatten-refactor]]])))
+ flatten-icon]]])))
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/bool.scss b/frontend/src/app/main/ui/workspace/sidebar/options/menus/bool.scss
index 625a80b71..bc26d5b8b 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/bool.scss
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/bool.scss
@@ -18,29 +18,29 @@
align-items: center;
}
-.flatten {
+.flatten-button {
@extend .button-tertiary;
- height: $s-28;
- width: $s-28;
+ height: $s-32;
+ width: $s-32;
border-radius: $br-8;
- svg {
- @extend .button-icon;
- stroke: var(--icon-foreground);
- }
+ --flatten-icon-foreground-color: var(--icon-foreground);
+
&.disabled {
cursor: default;
- svg {
- stroke: var(--button-foreground-color-disabled);
- }
+ --flatten-icon-foreground-color: var(--button-foreground-color-disabled);
+
&:hover {
background-color: var(--panel-background-color);
- svg {
- stroke: var(--button-foreground-color-disabled);
- }
+ --flatten-icon-foreground-color: var(--button-foreground-color-disabled);
}
}
}
+.flatten-icon {
+ @extend .button-icon;
+ stroke: var(--flatten-icon-foreground-color);
+}
+
.boolean-radio-btn {
background-color: transparent;
}
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
index c88dbc51b..766a6b3c0 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
@@ -182,6 +182,21 @@
(when flow
[:& flow-item {:flow flow :key (str (:id flow))}])])))
+(def ^:private corner-center-icon
+ (i/icon-xref :corner-center-refactor (stl/css :corner-icon)))
+(def ^:private corner-bottom-icon
+ (i/icon-xref :corner-bottom-refactor (stl/css :corner-icon)))
+(def ^:private corner-bottomleft-icon
+ (i/icon-xref :corner-bottomleft-refactor (stl/css :corner-icon)))
+(def ^:private corner-bottomright-icon
+ (i/icon-xref :corner-bottomright-refactor (stl/css :corner-icon)))
+(def ^:private corner-top-icon
+ (i/icon-xref :corner-top-refactor (stl/css :corner-icon)))
+(def ^:private corner-topleft-icon
+ (i/icon-xref :corner-topleft-refactor (stl/css :corner-icon)))
+(def ^:private corner-topright-icon
+ (i/icon-xref :corner-topright-refactor (stl/css :corner-icon)))
+
(mf/defc interaction-entry
[{:keys [index shape interaction update-interaction remove-interaction]}]
(let [objects (deref refs/workspace-page-objects)
@@ -403,12 +418,12 @@
[:div {:class (stl/css-case :element-set-options-group true
- :open extended-open?)}
+ :element-set-options-group-open extended-open?)}
; Summary
[:div {:class (stl/css :interactions-summary)}
- [:div {:class (stl/css-case :extend-btn true
- :extended extended-open?)
- :on-click toggle-extended}
+ [:button {:class (stl/css-case :extend-btn true
+ :extended extended-open?)
+ :on-click toggle-extended}
i/menu-refactor]
[:div {:class (stl/css :interactions-info)
@@ -520,85 +535,75 @@
:active (= overlay-pos-type :center))
:data-value "center"
:on-click toggle-overlay-pos-type}
- [:span {:class (stl/css :rectangle)}]]
+ corner-center-icon]
[:button {:class (stl/css-case :direction-btn true
:top-left-btn true
:active (= overlay-pos-type :top-left))
:data-value "top-left"
:on-click toggle-overlay-pos-type}
- [:span {:class (stl/css :rectangle)}]]
+ corner-topleft-icon]
[:button {:class (stl/css-case :direction-btn true
:top-right-btn true
:active (= overlay-pos-type :top-right))
:data-value "top-right"
:on-click toggle-overlay-pos-type}
- [:span {:class (stl/css :rectangle)}]]
+ corner-topright-icon]
[:button {:class (stl/css-case :direction-btn true
:top-center-btn true
:active (= overlay-pos-type :top-center))
:data-value "top-center"
:on-click toggle-overlay-pos-type}
- [:span {:class (stl/css :rectangle)}]]
- [:button {:class (stl/css-case :direction-btn true
- :bottom-left-btn true
- :active (= overlay-pos-type :bottom-left))
- :data-value "bottom-left"
- :on-click toggle-overlay-pos-type}
- [:span {:class (stl/css :rectangle)}]]
- [:button {:class (stl/css-case :direction-btn true
- :bottom-left-btn true
- :active (= overlay-pos-type :bottom-left))
- :data-value "bottom-left"
- :on-click toggle-overlay-pos-type}
- [:span {:class (stl/css :rectangle)}]]
+ corner-top-icon]
[:button {:class (stl/css-case :direction-btn true
:bottom-left-btn true
:active (= overlay-pos-type :bottom-left))
:data-value "bottom-left"
:on-click toggle-overlay-pos-type}
- [:span {:class (stl/css :rectangle)}]]
+ corner-bottomleft-icon]
[:button {:class (stl/css-case :direction-btn true
:bottom-right-btn true
:active (= overlay-pos-type :bottom-right))
:data-value "bottom-right"
:on-click toggle-overlay-pos-type}
- [:span {:class (stl/css :rectangle)}]]
+ corner-bottomright-icon]
[:button {:class (stl/css-case :direction-btn true
:bottom-center-btn true
:active (= overlay-pos-type :bottom-center))
:data-value "bottom-center"
:on-click toggle-overlay-pos-type}
- [:span {:class (stl/css :rectangle)}]]]]
+ corner-bottom-icon]]]
;; Overlay click outside
- [:div {:class (stl/css :property-row)}
- [:div {:class (stl/css :checkbox-option)}
- [:label {:for (str "close-" index)
- :class (stl/css-case :global/checked close-click-outside?)}
- [:span {:class (stl/css-case :global/checked close-click-outside?)}
- (when close-click-outside?
- i/status-tick-refactor)]
- (tr "workspace.options.interaction-close-outside")
- [:input {:type "checkbox"
- :id (str "close-" index)
- :checked close-click-outside?
- :on-change change-close-click-outside}]]]]
+
+ [:ul {:class (stl/css :property-list)}
+ [:li {:class (stl/css :property-row)}
+ [:div {:class (stl/css :checkbox-option)}
+ [:label {:for (str "close-" index)
+ :class (stl/css-case :global/checked close-click-outside?)}
+ [:span {:class (stl/css-case :global/checked close-click-outside?)}
+ (when close-click-outside?
+ i/status-tick-refactor)]
+ (tr "workspace.options.interaction-close-outside")
+ [:input {:type "checkbox"
+ :id (str "close-" index)
+ :checked close-click-outside?
+ :on-change change-close-click-outside}]]]]
;; Overlay background
- [:div {:class (stl/css :property-row)}
- [:div {:class (stl/css :checkbox-option)}
- [:label {:for (str "background-" index)
- :class (stl/css-case :global/checked background-overlay?)}
- [:span {:class (stl/css-case :global/checked background-overlay?)}
- (when background-overlay?
- i/status-tick-refactor)]
- (tr "workspace.options.interaction-background")
- [:input {:type "checkbox"
- :id (str "background-" index)
- :checked background-overlay?
- :on-change change-background-overlay}]]]]])
+ [:li {:class (stl/css :property-row)}
+ [:div {:class (stl/css :checkbox-option)}
+ [:label {:for (str "background-" index)
+ :class (stl/css-case :global/checked background-overlay?)}
+ [:span {:class (stl/css-case :global/checked background-overlay?)}
+ (when background-overlay?
+ i/status-tick-refactor)]
+ (tr "workspace.options.interaction-background")
+ [:input {:type "checkbox"
+ :id (str "background-" index)
+ :checked background-overlay?
+ :on-change change-background-overlay}]]]]]])
(when (ctsi/has-animation? interaction)
[:*
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.scss b/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.scss
index cc3679140..f9eb79f54 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.scss
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.scss
@@ -96,152 +96,179 @@
@include flexColumn($s-12);
}
-.element-set-options-group {
- &.open {
- @include flexColumn;
- .extended-options {
- @include flexColumn;
- .property-row {
- @extend .attr-row;
- &.big-row {
- height: 100%;
- }
- .interaction-name {
- @include twoLineTextEllipsis;
- @include titleTipography;
- padding-left: $s-4;
- width: $s-92;
- margin: auto 0;
- grid-area: name;
- color: var(--title-foreground-color);
- }
- .select-wrapper {
- display: flex;
- align-items: center;
- grid-area: content;
- .easing-select {
- width: $s-156;
- padding: 0 $s-8;
- .dropdown-upwards {
- bottom: $s-36;
- width: $s-156;
- top: unset;
- }
- }
- }
- .input-element-wrapper {
- @extend .input-element;
- grid-area: content;
- }
- .checkbox-option {
- @extend .input-checkbox;
- grid-area: content;
- }
- .position-btns-wrapper {
- grid-area: content;
- display: grid;
- grid-template-areas:
- "topleft top topright"
- "left center right"
- "bottomleft bottom bottomright";
- grid-template-columns: repeat(3, 1fr);
- grid-template-rows: repeat(3, 1fr);
- width: $s-84;
- height: $s-84;
- border-radius: $br-8;
- background-color: var(--color-background-tertiary);
- .direction-btn {
- @extend .button-tertiary;
- height: $s-28;
- width: $s-28;
- .rectangle {
- height: $s-8;
- width: $s-8;
- background-color: var(--color-background-quaternary);
- }
- &:hover {
- .rectangle {
- background-color: var(--color-accent-primary);
- }
- }
- &.active {
- background-color: var(--color-background-quaternary);
- .rectangle {
- background-color: var(--color-accent-primary);
- }
- }
- }
- .center-btn {
- grid-area: center;
- }
- .top-left-btn {
- grid-area: topleft;
- }
- .top-right-btn {
- grid-area: topright;
- }
- .top-center-btn {
- grid-area: top;
- }
- .bottom-left-btn {
- grid-area: bottomleft;
- }
- .bottom-right-btn {
- grid-area: bottomright;
- }
- .bottom-center-btn {
- grid-area: bottom;
- }
- }
- .buttons-wrapper {
- grid-area: content;
- .right svg {
- transform: rotate(-90deg);
- }
- .left svg {
- transform: rotate(90deg);
- }
- .up svg {
- transform: rotate(180deg);
- }
- }
- .inputs-wrapper {
- grid-area: content;
- @include flexRow;
- .radio-btn {
- @extend .input-checkbox;
- }
- }
+.element-set-options-group-open {
+ @include flexColumn;
+}
+
+.extended-options {
+ @include flexColumn;
+}
+
+.property-list {
+ list-style: none;
+ margin: 0;
+ display: grid;
+ row-gap: $s-16;
+ margin-block: calc(#{$s-16} - #{$s-4});
+}
+
+.property-row {
+ @extend .attr-row;
+ height: auto;
+ &.big-row {
+ height: 100%;
+ }
+ .interaction-name {
+ @include twoLineTextEllipsis;
+ @include titleTipography;
+ padding-left: $s-4;
+ width: $s-92;
+ margin: auto 0;
+ grid-area: name;
+ color: var(--title-foreground-color);
+ }
+ .select-wrapper {
+ display: flex;
+ align-items: center;
+ grid-area: content;
+ .easing-select {
+ width: $s-156;
+ padding: 0 $s-8;
+ .dropdown-upwards {
+ bottom: $s-36;
+ width: $s-156;
+ top: unset;
}
}
}
-
- .interactions-summary {
- @extend .asset-element;
- height: $s-44;
- padding: 0;
- gap: $s-4;
- .extend-btn {
- @extend .button-tertiary;
- height: 100%;
- width: $s-28;
- svg {
- @extend .button-icon;
- }
- &.extended {
- @extend .button-icon-selected;
- }
+ .input-element-wrapper {
+ @extend .input-element;
+ grid-area: content;
+ }
+ .buttons-wrapper {
+ grid-area: content;
+ .right svg {
+ transform: rotate(-90deg);
}
-
- .remove-btn {
- @extend .button-tertiary;
- height: $s-32;
- width: $s-28;
- svg {
- @extend .button-icon-small;
- }
+ .left svg {
+ transform: rotate(90deg);
+ }
+ .up svg {
+ transform: rotate(180deg);
}
}
+ .inputs-wrapper {
+ grid-area: content;
+ @include flexRow;
+ .radio-btn {
+ @extend .input-checkbox;
+ }
+ }
+}
+
+.position-btns-wrapper {
+ grid-area: content;
+ display: grid;
+ grid-template-areas:
+ "topleft top topright"
+ "left center right"
+ "bottomleft bottom bottomright";
+ grid-template-columns: repeat(3, 1fr);
+ grid-template-rows: repeat(3, 1fr);
+ width: $s-84;
+ height: $s-84;
+ border-radius: $br-8;
+ background-color: var(--color-background-tertiary);
+ .center-btn {
+ grid-area: center;
+ }
+ .top-left-btn {
+ grid-area: topleft;
+ }
+ .top-right-btn {
+ grid-area: topright;
+ }
+ .top-center-btn {
+ grid-area: top;
+ }
+ .bottom-left-btn {
+ grid-area: bottomleft;
+ }
+ .bottom-right-btn {
+ grid-area: bottomright;
+ }
+ .bottom-center-btn {
+ grid-area: bottom;
+ }
+}
+
+.direction-btn {
+ @extend .button-tertiary;
+ height: $s-28;
+ width: $s-28;
+
+ &.active {
+ @extend .button-icon-selected;
+ }
+}
+
+.checkbox-option {
+ @extend .input-checkbox;
+ grid-area: content;
+ line-height: 1.2;
+ label {
+ align-items: start;
+ }
+}
+
+.interactions-summary {
+ @extend .asset-element;
+ height: $s-44;
+ padding: 0;
+ gap: $s-8;
+
+ .remove-btn {
+ @extend .button-tertiary;
+ height: $s-32;
+ width: $s-28;
+ svg {
+ @extend .button-icon-small;
+ }
+ }
+}
+
+.extend-btn {
+ @extend .button-tertiary;
+ --button-tertiary-border-width: var(--expand-button-icon-border-width);
+ height: 100%;
+ width: $s-28;
+ border-end-end-radius: 0;
+ border-start-end-radius: 0;
+ padding: 0;
+ svg {
+ @extend .button-icon;
+ }
+ position: relative;
+ &:after {
+ content: "";
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ border-inline-end: $s-1 solid var(--panel-background-color);
+ }
+ &.extended {
+ @extend .button-icon-selected;
+ --button-tertiary-border-width: var(--expand-button-icon-border-width-selected);
+ }
+}
+
+.corner-icon {
+ fill: none;
+ stroke: currentColor;
+ width: $s-12;
+ height: $s-12;
}
.flow-element {
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layer.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
index bdfec509f..eea27c06c 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
@@ -23,11 +23,11 @@
(defn opacity->string
[opacity]
- (if (= opacity :multiple)
- ""
+ (if (not= opacity :multiple)
(dm/str (-> opacity
(d/coalesce 1)
- (* 100)))))
+ (* 100)))
+ :multiple))
(mf/defc layer-menu
{::mf/wrap-props false}
@@ -39,7 +39,7 @@
blocked? (:blocked values)
current-blend-mode (or (:blend-mode values) :normal)
- current-opacity (:opacity values)
+ current-opacity (opacity->string (:opacity values))
state* (mf/use-state
{:selected-blend-mode current-blend-mode
@@ -161,8 +161,8 @@
:title (tr "workspace.options.opacity")}
[:span {:class (stl/css :icon)} "%"]
[:> numeric-input*
- {:value (opacity->string current-opacity)
- :placeholder (tr "settings.multiple")
+ {:value current-opacity
+ :placeholder "--"
:on-change handle-opacity-change
:min 0
:max 100
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs
index f759c634a..5f945a406 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs
@@ -409,14 +409,14 @@
:type "checkbox"
:value "uppercase"
:id "text-transform-uppercase"}]
- [:& radio-button {:icon i/text-lowercase-refactor
- :type "checkbox"
- :value "lowercase"
- :id "text-transform-lowercase"}]
[:& radio-button {:icon i/text-mixed-refactor
:type "checkbox"
:value "capitalize"
- :id "text-transform-capitalize"}]]]))
+ :id "text-transform-capitalize"}]
+ [:& radio-button {:icon i/text-lowercase-refactor
+ :type "checkbox"
+ :value "lowercase"
+ :id "text-transform-lowercase"}]]]))
(mf/defc text-options
{::mf/wrap-props false}
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.scss b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.scss
index c2ab44d9a..bec81db5b 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.scss
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.scss
@@ -284,19 +284,6 @@
padding: 0;
border: $s-1 solid var(--input-border-color);
position: relative;
- .font-size-select {
- @include removeInputStyle;
- @include titleTipography;
- height: $s-32;
- height: 100%;
- width: 100%;
- margin: 0;
- padding: 0;
- .numeric-input {
- @extend .input-base;
- padding-inline-start: $s-8;
- }
- }
.icon {
@include flexCenter;
@@ -342,6 +329,20 @@
}
}
+.font-size-select {
+ @include removeInputStyle;
+ @include titleTipography;
+ height: $s-32;
+ height: 100%;
+ width: 100%;
+ margin: 0;
+ padding: $s-8;
+ .numeric-input {
+ @extend .input-base;
+ padding: 0;
+ }
+}
+
.font-selector {
@include flexCenter;
position: absolute;
diff --git a/frontend/src/app/main/ui/workspace/sidebar/shortcuts.scss b/frontend/src/app/main/ui/workspace/sidebar/shortcuts.scss
index acd82fd10..530de5577 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/shortcuts.scss
+++ b/frontend/src/app/main/ui/workspace/sidebar/shortcuts.scss
@@ -7,195 +7,202 @@
@import "refactor/common-refactor.scss";
.shortcuts {
- .shortcuts-header {
- @include flexCenter;
- @include tabTitleTipography;
- position: relative;
- height: $s-32;
- padding: $s-2 $s-2 $s-2 0;
- margin: $s-4 $s-4 0 $s-4;
- border-radius: $br-6;
- background-color: var(--panel-title-background-color);
+ display: grid;
+ grid-template-rows: auto auto 1fr;
+ // TODO: Fix this once we start implementing the DS.
+ // We should not be doign these hardcoded calc's.
+ height: calc(100vh - #{$s-60});
+}
- .shortcuts-title {
- @include flexCenter;
- flex-grow: 1;
- color: var(--title-foreground-color-hover);
- }
-
- .shortcuts-close-button {
- @extend .button-tertiary;
- position: absolute;
- right: $s-2;
- top: $s-2;
- height: $s-28;
- width: $s-28;
- border-radius: $br-5;
-
- svg {
- @extend .button-icon;
- stroke: var(--icon-foreground);
- }
- }
- }
-
- .search-field {
- display: flex;
+.search-field {
+ display: flex;
+ align-items: center;
+ height: $s-32;
+ margin: $s-16 $s-12 $s-4 $s-12;
+ border-radius: $br-8;
+ font-family: "worksans", sans-serif;
+ background-color: var(--color-background-tertiary);
+ .search-box {
align-items: center;
- height: $s-32;
- margin: $s-16 $s-12 $s-4 $s-12;
- border-radius: $br-8;
- font-family: "worksans", sans-serif;
- background-color: var(--color-background-tertiary);
- .search-box {
- align-items: center;
+ display: flex;
+ width: 100%;
+
+ .icon-wrapper {
display: flex;
- width: 100%;
-
- .icon-wrapper {
- display: flex;
- svg {
- @extend .button-icon-small;
- stroke: var(--icon-foreground);
- }
- }
-
- .input-text {
- @include removeInputStyle;
- height: $s-32;
- width: 100%;
- margin: 0;
- padding: $s-4;
- border: 0;
- font-size: $fs-12;
- color: var(--color-foreground-primary);
- &::placeholder {
- color: var(--color-foreground-secondary);
- }
- &:focus-visible {
- border-color: var(--color-accent-primary-muted);
- }
- }
- .clear-btn {
- @include buttonStyle;
- @include flexCenter;
- height: $s-16;
- width: $s-16;
- .clear-icon {
- @include flexCenter;
- svg {
- @extend .button-icon-small;
- stroke: var(--icon-foreground);
- }
- }
- }
- }
- .search-icon {
- @include flexCenter;
- width: $s-28;
svg {
@extend .button-icon-small;
stroke: var(--icon-foreground);
}
}
- }
- .section {
- margin: 0;
- }
- .shortcuts-list {
- display: flex;
- flex-direction: column;
- height: 90%;
- padding: $s-12;
- margin-bottom: $s-12;
- overflow-y: overlay;
- font-size: $fs-12;
- color: var(--title-foreground-color);
-
- .section-title,
- .subsection-title {
- @include tabTitleTipography;
- display: flex;
- align-items: center;
+ .input-text {
+ @include removeInputStyle;
+ height: $s-32;
+ width: 100%;
margin: 0;
- padding: $s-8 0;
- cursor: pointer;
-
- .collapsed-shortcuts {
+ padding: $s-4;
+ border: 0;
+ font-size: $fs-12;
+ color: var(--color-foreground-primary);
+ &::placeholder {
+ color: var(--color-foreground-secondary);
+ }
+ &:focus-visible {
+ border-color: var(--color-accent-primary-muted);
+ }
+ }
+ .clear-btn {
+ @include buttonStyle;
+ @include flexCenter;
+ height: $s-16;
+ width: $s-16;
+ .clear-icon {
@include flexCenter;
svg {
@extend .button-icon-small;
stroke: var(--icon-foreground);
}
- &.open {
- transform: rotate(90deg);
- }
- }
- .subsection-name,
- .section-name {
- padding-left: $s-4;
- }
- &:hover {
- color: var(--title-foreground-color-hover);
- .collapsed-shortcuts {
- svg {
- stroke: var(--title-foreground-color-hover);
- }
- }
- }
- }
-
- .subsection-title {
- text-transform: none;
- padding-left: $s-12;
- }
- .subsection-menu {
- margin-bottom: $s-4;
- }
- .sub-menu {
- margin-bottom: $s-4;
-
- .shortcuts-name {
- display: flex;
- align-items: center;
- justify-content: space-between;
- width: 100%;
- min-height: $s-32;
- padding: $s-6;
- margin-bottom: $s-4;
- border-radius: $br-8;
- background-color: var(--pill-background-color);
-
- .command-name {
- @include titleTipography;
- margin-left: $s-2;
- color: var(--pill-foreground-color);
- }
- .keys {
- @include flexCenter;
- gap: $s-2;
- color: var(--pill-foreground-color);
-
- .key {
- @include titleTipography;
- @include flexCenter;
- text-transform: capitalize;
- height: $s-20;
- padding: $s-2 $s-6;
- border-radius: $s-6;
- background-color: var(--menu-shortcut-background-color);
- }
- .space {
- margin: 0 $s-2;
- }
- }
}
}
}
- .not-found {
- @include titleTipography;
- color: var(--empty-message-foreground-color);
- margin: $s-12;
+ .search-icon {
+ @include flexCenter;
+ width: $s-28;
+ svg {
+ @extend .button-icon-small;
+ stroke: var(--icon-foreground);
+ }
+ }
+}
+
+.shortcuts-header {
+ @include flexCenter;
+ @include tabTitleTipography;
+ position: relative;
+ height: $s-32;
+ padding: $s-2 $s-2 $s-2 0;
+ margin: $s-4 $s-4 0 $s-4;
+ border-radius: $br-6;
+ background-color: var(--panel-title-background-color);
+
+ .shortcuts-title {
+ @include flexCenter;
+ flex-grow: 1;
+ color: var(--title-foreground-color-hover);
+ }
+
+ .shortcuts-close-button {
+ @extend .button-tertiary;
+ position: absolute;
+ right: $s-2;
+ top: $s-2;
+ height: $s-28;
+ width: $s-28;
+ border-radius: $br-5;
+
+ svg {
+ @extend .button-icon;
+ stroke: var(--icon-foreground);
+ }
+ }
+}
+
+.section {
+ margin: 0;
+}
+
+.not-found {
+ @include titleTipography;
+ color: var(--empty-message-foreground-color);
+ margin: $s-12;
+}
+
+.shortcuts-list {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ padding: $s-12;
+ overflow-y: scroll;
+ font-size: $fs-12;
+ color: var(--title-foreground-color);
+
+ .section-title,
+ .subsection-title {
+ @include tabTitleTipography;
+ display: flex;
+ align-items: center;
+ margin: 0;
+ padding: $s-8 0;
+ cursor: pointer;
+
+ .collapsed-shortcuts {
+ @include flexCenter;
+ svg {
+ @extend .button-icon-small;
+ stroke: var(--icon-foreground);
+ }
+ &.open {
+ transform: rotate(90deg);
+ }
+ }
+ .subsection-name,
+ .section-name {
+ padding-left: $s-4;
+ }
+ &:hover {
+ color: var(--title-foreground-color-hover);
+ .collapsed-shortcuts {
+ svg {
+ stroke: var(--title-foreground-color-hover);
+ }
+ }
+ }
+ }
+
+ .subsection-title {
+ text-transform: none;
+ padding-left: $s-12;
+ }
+ .subsection-menu {
+ margin-bottom: $s-4;
+ }
+ .sub-menu {
+ margin-bottom: $s-4;
+
+ .shortcuts-name {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ width: 100%;
+ min-height: $s-32;
+ padding: $s-6;
+ margin-bottom: $s-4;
+ border-radius: $br-8;
+ background-color: var(--pill-background-color);
+
+ .command-name {
+ @include titleTipography;
+ margin-left: $s-2;
+ color: var(--pill-foreground-color);
+ }
+ .keys {
+ @include flexCenter;
+ gap: $s-2;
+ color: var(--pill-foreground-color);
+
+ .key {
+ @include titleTipography;
+ @include flexCenter;
+ text-transform: capitalize;
+ height: $s-20;
+ padding: $s-2 $s-6;
+ border-radius: $s-6;
+ background-color: var(--menu-shortcut-background-color);
+ }
+ .space {
+ margin: 0 $s-2;
+ }
+ }
+ }
}
}
diff --git a/frontend/translations/en.po b/frontend/translations/en.po
index 46f2e63c1..f26593180 100644
--- a/frontend/translations/en.po
+++ b/frontend/translations/en.po
@@ -5056,6 +5056,9 @@ msgstr "Done"
msgid "media.image"
msgstr "Image"
+msgid "media.image.short"
+msgstr "img"
+
msgid "media.solid"
msgstr "Solid"
diff --git a/frontend/translations/es.po b/frontend/translations/es.po
index 8c8c0d4cc..4fe0c535e 100644
--- a/frontend/translations/es.po
+++ b/frontend/translations/es.po
@@ -5140,6 +5140,9 @@ msgstr "Hecho"
msgid "media.image"
msgstr "Imagen"
+msgid "media.image.short"
+msgstr "img"
+
msgid "media.solid"
msgstr "Sólido"