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

This commit is contained in:
Andrey Antukh 2024-02-14 13:01:58 +01:00
commit b426db133d
88 changed files with 1497 additions and 1034 deletions

View file

@ -141,8 +141,6 @@
" WHERE flr.file_id = ANY(?) AND l.deleted_at IS NULL")] " WHERE flr.file_id = ANY(?) AND l.deleted_at IS NULL")]
(db/exec! conn [sql ids]))))) (db/exec! conn [sql ids])))))
;; NOTE: Will be used in future, commented for satisfy linter
(def ^:private sql:get-libraries (def ^:private sql:get-libraries
"WITH RECURSIVE libs AS ( "WITH RECURSIVE libs AS (
SELECT fl.id SELECT fl.id
@ -409,7 +407,6 @@
(update :colors relink-colors) (update :colors relink-colors)
(d/without-nils)))))) (d/without-nils))))))
(defn- upsert-file! (defn- upsert-file!
[conn file] [conn file]
(let [sql (str "INSERT INTO file (id, project_id, name, revn, is_shared, data, created_at, modified_at) " (let [sql (str "INSERT INTO file (id, project_id, name, revn, is_shared, data, created_at, modified_at) "

View file

@ -40,6 +40,7 @@
[promesa.util :as pu] [promesa.util :as pu]
[yetti.adapter :as yt]) [yetti.adapter :as yt])
(:import (:import
com.github.luben.zstd.ZstdIOException
com.github.luben.zstd.ZstdInputStream com.github.luben.zstd.ZstdInputStream
com.github.luben.zstd.ZstdOutputStream com.github.luben.zstd.ZstdOutputStream
java.io.DataInputStream java.io.DataInputStream
@ -517,6 +518,15 @@
(update :object-id #(str/replace-first % #"^(.*?)/" (str file-id "/"))))) (update :object-id #(str/replace-first % #"^(.*?)/" (str file-id "/")))))
thumbnails)) 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 (defmethod read-section :v1/files
[{:keys [::db/conn ::input ::project-id ::bfc/overwrite ::name] :as system}] [{:keys [::db/conn ::input ::project-id ::bfc/overwrite ::name] :as system}]
@ -527,6 +537,7 @@
file-id (:id file) file-id (:id file)
file-id' (bfc/lookup-index file-id) file-id' (bfc/lookup-index file-id)
file (clean-features file)
thumbnails (:thumbnails file)] thumbnails (:thumbnails file)]
(when (not= file-id expected-file-id) (when (not= file-id expected-file-id)
@ -746,6 +757,12 @@
(pu/with-open [input (io/input-stream input)] (pu/with-open [input (io/input-stream input)]
(read-import! (assoc cfg ::input 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 (catch Throwable cause
(vreset! cs cause) (vreset! cs cause)
(throw cause)) (throw cause))

View file

@ -1187,15 +1187,18 @@
"Convert a media object that contains a bitmap image into shapes, "Convert a media object that contains a bitmap image into shapes,
one shape of type :image and one group that contains it." one shape of type :image and one group that contains it."
[{:keys [name width height id mtype]} frame-id position] [{:keys [name width height id mtype]} frame-id position]
(let [frame-shape (cts/setup-shape (let [frame-shape (-> (cts/setup-shape
{:type :frame {:type :frame
:x (:x position) :x (:x position)
:y (:y position) :y (:y position)
:width width :width width
:height height :height height
:name name :name name
:frame-id frame-id :frame-id frame-id
:parent-id frame-id}) :parent-id frame-id})
(assoc
:proportion (/ width height)
:proportion-lock true))
img-shape (cts/setup-shape img-shape (cts/setup-shape
{:type :image {:type :image
@ -1209,7 +1212,9 @@
:mtype mtype} :mtype mtype}
:name name :name name
:frame-id (:id frame-shape) :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]])) [frame-shape [img-shape]]))
(defn- parse-datauri (defn- parse-datauri

View file

@ -61,9 +61,6 @@
(let [result (handler)] (let [result (handler)]
(events/tap :end result)) (events/tap :end result))
(catch Throwable cause (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))) (events/tap :error (errors/handle' cause request)))
(finally (finally
(sp/close! events/*channel*) (sp/close! events/*channel*)

View file

@ -262,7 +262,8 @@
(merge (:props params)) (merge (:props params))
(merge {:viewed-tutorial? false (merge {:viewed-tutorial? false
:viewed-walkthrough? false :viewed-walkthrough? false
:nudge {:big 10 :small 1}}) :nudge {:big 10 :small 1}
:v2-info-shown true})
(db/tjson)) (db/tjson))
password (or (:password params) "!") password (or (:password params) "!")

View file

@ -16,8 +16,33 @@
[app.common.logging :as l] [app.common.logging :as l]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.db :as db] [app.db :as db]
[app.features.fdata :as feat.fdata]
[app.srepl.helpers :as h])) [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 (defn repair-file-media
"A helper intended to be used with `srepl.main/process-files!` that "A helper intended to be used with `srepl.main/process-files!` that
fixes all not propertly referenced file-media-object for a file" fixes all not propertly referenced file-media-object for a file"

View file

@ -168,7 +168,7 @@
(update-fn file libs opts) (update-fn file libs opts)
(update-fn file opts))] (update-fn file opts))]
(when (and (some? file) (when (and (some? file')
(not (identical? file file'))) (not (identical? file file')))
(when validate? (cfv/validate-file-schema! file')) (when validate? (cfv/validate-file-schema! file'))
(let [file' (update file' :revn inc)] (let [file' (update file' :revn inc)]

View file

@ -370,43 +370,11 @@
;; PROCESSING ;; PROCESSING
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def ^:private (def sql:get-files
sql:get-file-ids
"SELECT id FROM file "SELECT id FROM file
WHERE created_at < ? AND deleted_at is NULL WHERE deleted_at is NULL
ORDER BY created_at DESC") 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! (defn process-file!
"Apply a function to the file. Optionally save the changes or not. "Apply a function to the file. Optionally save the changes or not.
The function receives the decoded and migrated file data." The function receives the decoded and migrated file data."
@ -438,11 +406,12 @@
"Apply a function to all files in the database" "Apply a function to all files in the database"
[update-fn & {:keys [max-items [update-fn & {:keys [max-items
max-jobs max-jobs
start-at rollback?
rollback?] query]
:or {max-jobs 1 :or {max-jobs 1
max-items Long/MAX_VALUE max-items Long/MAX_VALUE
rollback? true} rollback? true
query sql:get-files}
:as opts}] :as opts}]
(l/dbg :hint "process:start" (l/dbg :hint "process:start"
@ -486,7 +455,7 @@
(px/run! executor (partial process-file file-id idx (dt/tpoint))) (px/run! executor (partial process-file file-id idx (dt/tpoint)))
(inc idx)) (inc idx))
0 0
(->> (db/cursor conn [sql:get-file-ids (or start-at (dt/now))]) (->> (db/cursor conn [query] {:chunk-size 1})
(take max-items) (take max-items)
(map :id))) (map :id)))
(finally (finally

View file

@ -11,7 +11,8 @@
inactivity (the default threshold is 72h)." inactivity (the default threshold is 72h)."
(:require (:require
[app.binfile.common :as bfc] [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.logging :as l]
[app.common.thumbnails :as thc] [app.common.thumbnails :as thc]
[app.common.types.components-list :as ctkl] [app.common.types.components-list :as ctkl]
@ -29,9 +30,256 @@
[clojure.spec.alpha :as s] [clojure.spec.alpha :as s]
[integrant.core :as ig])) [integrant.core :as ig]))
(declare ^:private get-candidates)
(declare ^:private clean-file!) (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 ;; HANDLER
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -55,7 +303,7 @@
(assoc ::min-age min-age)) (assoc ::min-age min-age))
total (reduce (fn [total file] total (reduce (fn [total file]
(clean-file! cfg file) (process-file! cfg file)
(inc total)) (inc total))
0 0
(get-candidates cfg))] (get-candidates cfg))]
@ -69,223 +317,3 @@
(db/rollback! conn)) (db/rollback! conn))
{:processed total}))))) {: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))))

View file

@ -154,7 +154,7 @@
;; Check the number of fragments before adding the page ;; Check the number of fragments before adding the page
(let [rows (th/db-query :file-data-fragment {:file-id (:id file)})] (let [rows (th/db-query :file-data-fragment {:file-id (:id file)})]
(t/is (= 1 (count rows)))) (t/is (= 2 (count rows))))
;; Add page ;; Add page
(update-file! (update-file!
@ -172,15 +172,15 @@
;; Check the number of fragments ;; Check the number of fragments
(let [rows (th/db-query :file-data-fragment {:file-id (:id file)})] (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 ;; The objects-gc should remove unused fragments
(let [res (th/run-task! :objects-gc {:min-age 0})] (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 ;; Check the number of fragments
(let [rows (th/db-query :file-data-fragment {:file-id (:id file)})] (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 ;; Add shape to page that should add a new fragment
(update-file! (update-file!
@ -203,7 +203,7 @@
;; Check the number of fragments ;; Check the number of fragments
(let [rows (th/db-query :file-data-fragment {:file-id (:id file)})] (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 ;; The file-gc should mark for remove unused fragments
(let [res (th/run-task! :file-gc {:min-age 0})] (let [res (th/run-task! :file-gc {:min-age 0})]
@ -211,12 +211,13 @@
;; The objects-gc should remove unused fragments ;; The objects-gc should remove unused fragments
(let [res (th/run-task! :objects-gc {:min-age 0})] (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 ;; Check the number of fragments; should be 3 because changes
;; are also holding pointers to fragments; ;; are also holding pointers to fragments;
(let [rows (th/db-query :file-data-fragment {:file-id (:id file)})] (let [rows (th/db-query :file-data-fragment {:file-id (:id file)
(t/is (= 3 (count rows)))) :deleted-at nil})]
(t/is (= 6 (count rows))))
;; Lets proceed to delete all changes ;; Lets proceed to delete all changes
(th/db-delete! :file-change {:file-id (:id file)}) (th/db-delete! :file-change {:file-id (:id file)})
@ -224,7 +225,6 @@
{:has-media-trimmed false} {:has-media-trimmed false}
{:id (:id file)}) {:id (:id file)})
;; The file-gc should remove fragments related to changes ;; The file-gc should remove fragments related to changes
;; snapshots previously deleted. ;; snapshots previously deleted.
(let [res (th/run-task! :file-gc {:min-age 0})] (let [res (th/run-task! :file-gc {:min-age 0})]
@ -233,11 +233,11 @@
;; Check the number of fragments; ;; Check the number of fragments;
(let [rows (th/db-query :file-data-fragment {:file-id (:id file)})] (let [rows (th/db-query :file-data-fragment {:file-id (:id file)})]
;; (pp/pprint rows) ;; (pp/pprint rows)
(t/is (= 3 (count rows))) (t/is (= 8 (count rows)))
(t/is (= 2 (count (remove (comp some? :deleted-at) rows))))) (t/is (= 2 (count (remove (comp some? :deleted-at) rows)))))
(let [res (th/run-task! :objects-gc {:min-age 0})] (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)})] (let [rows (th/db-query :file-data-fragment {:file-id (:id file)})]
(t/is (= 2 (count rows))))))) (t/is (= 2 (count rows)))))))
@ -367,7 +367,7 @@
(t/is (= 1 (:processed res)))) (t/is (= 1 (:processed res))))
(let [res (th/run-task! :objects-gc {:min-age 0})] (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, ;; Now that file-gc have deleted the file-media-object usage,
;; lets execute the touched-gc task, we should see that two of ;; lets execute the touched-gc task, we should see that two of
@ -432,6 +432,11 @@
page-id (first (get-in file [:data :pages]))] 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 inserting a new image object
(update-file! (update-file!
:file-id (:id file) :file-id (:id file)
@ -491,6 +496,10 @@
(let [res (th/run-task! :objects-gc {:min-age 0})] (let [res (th/run-task! :objects-gc {:min-age 0})]
(t/is (= 1 (:processed res)))) (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 ;; retrieve file and check trimmed attribute
(let [row (th/db-get :file {:id (:id file)})] (let [row (th/db-get :file {:id (:id file)})]
(t/is (true? (:has-media-trimmed row)))) (t/is (true? (:has-media-trimmed row))))
@ -521,11 +530,16 @@
;; Now, we have deleted the usage of pointers to the ;; Now, we have deleted the usage of pointers to the
;; file-media-objects, if we paste file-gc, they should be marked ;; file-media-objects, if we paste file-gc, they should be marked
;; as deleted. ;; as deleted.
(let [res (th/run-task! :file-gc {:min-age 0})] (let [res (th/run-task! :file-gc {:min-age 0})]
(t/is (= 1 (:processed res)))) (t/is (= 1 (:processed res))))
(let [res (th/run-task! :objects-gc {:min-age 0})] (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, ;; Now that file-gc have deleted the file-media-object usage,
;; lets execute the touched-gc task, we should see that two of ;; 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})] (let [rows (th/db-query :file-tagged-object-thumbnail {:file-id file-id})]
(t/is (= 2 (count rows))) (t/is (= 2 (count rows)))
(t/is (= 1 (count (remove (comp some? :deleted-at) 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") (t/is (= (thc/fmt-object-id file-id page-id frame-id-1 "frame")
(-> rows first :object-id)))) (-> rows first :object-id))))
@ -689,7 +702,7 @@
;; thumbnail lets execute the objects-gc task which remove ;; thumbnail lets execute the objects-gc task which remove
;; the rows and mark as touched the storage object rows ;; the rows and mark as touched the storage object rows
(let [res (th/run-task! :objects-gc {:min-age 0})] (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 ;; Now that objects-gc have deleted the object thumbnail lets
;; execute the touched-gc task ;; execute the touched-gc task
@ -719,7 +732,7 @@
(let [res (th/run-task! :objects-gc {:min-age 0})] (let [res (th/run-task! :objects-gc {:min-age 0})]
;; (pp/pprint res) ;; (pp/pprint res)
(t/is (= 1 (:processed res)))) (t/is (= 2 (:processed res))))
;; We still have th storage objects in the table ;; We still have th storage objects in the table
(let [rows (th/db-query :storage-object {:deleted-at nil})] (let [rows (th/db-query :storage-object {:deleted-at nil})]
@ -736,6 +749,7 @@
;; (pp/pprint rows) ;; (pp/pprint rows)
(t/is (= 0 (count rows))))))) (t/is (= 0 (count rows)))))))
(t/deftest permissions-checks-creating-file (t/deftest permissions-checks-creating-file
(let [profile1 (th/create-profile* 1) (let [profile1 (th/create-profile* 1)
profile2 (th/create-profile* 2) profile2 (th/create-profile* 2)
@ -1147,7 +1161,7 @@
(t/is (= 1 (count (remove (comp some? :deleted-at) rows))))) (t/is (= 1 (count (remove (comp some? :deleted-at) rows)))))
(let [res (th/run-task! :objects-gc {:min-age 0})] (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)})] (let [rows (th/db-query :file-tagged-object-thumbnail {:file-id (:id file)})]
(t/is (= 1 (count rows))))))) (t/is (= 1 (count rows)))))))
@ -1203,7 +1217,7 @@
(t/is (= 1 (count (remove (comp some? :deleted-at) rows))))) (t/is (= 1 (count (remove (comp some? :deleted-at) rows)))))
(let [res (th/run-task! :objects-gc {:min-age 0})] (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)})] (let [rows (th/db-query :file-thumbnail {:file-id (:id file)})]
(t/is (= 1 (count rows))))))) (t/is (= 1 (count rows)))))))

View file

@ -222,7 +222,7 @@
(t/is (= 1 (:processed result)))) (t/is (= 1 (:processed result))))
(let [result (th/run-task! :objects-gc {:min-age 0})] (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 ;; check if row1 related thumbnail row still exists
(let [[row :as rows] (th/db-query :file-thumbnail (let [[row :as rows] (th/db-query :file-thumbnail

View file

@ -326,7 +326,9 @@
(some? index) (some? index)
(assoc :index index) (assoc :index index)
(:component-swap options) (:component-swap options)
(assoc :component-swap true)) (assoc :component-swap true)
(:ignore-touched options)
(assoc :ignore-touched true))
mk-undo-change mk-undo-change
(fn [undo-changes shape] (fn [undo-changes shape]
@ -450,10 +452,11 @@
add-redo-change add-redo-change
(fn [change-set id] (fn [change-set id]
(conj change-set (conj change-set
{:type :del-obj (cond-> {:type :del-obj
:page-id page-id :page-id page-id
:ignore-touched ignore-touched :id id}
:id id})) ignore-touched
(assoc :ignore-touched true))))
add-undo-change-shape add-undo-change-shape
(fn [change-set id] (fn [change-set id]

View file

@ -166,13 +166,6 @@
:else :else
(get-instance-root objects (get objects (:parent-id shape))))) (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? (defn inside-component-main?
"Check if the shape is a component main instance or is inside one." "Check if the shape is a component main instance or is inside one."
[objects shape] [objects shape]

View file

@ -190,7 +190,7 @@
"Locate the near component in the local file or libraries, and retrieve the shape "Locate the near component in the local file or libraries, and retrieve the shape
referenced by the instance shape." referenced by the instance shape."
[file page libraries shape & {:keys [include-deleted?] :or {include-deleted? false}}] [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 component-file (when root-shape
(if (and (some? file) (= (:component-file root-shape) (:id file))) (if (and (some? file) (= (:component-file root-shape) (:id file)))
file file
@ -218,10 +218,23 @@
component-file (get-in libraries [(:component-file top-instance) :data]) component-file (get-in libraries [(:component-file top-instance) :data])
component (ctkl/get-component component-file (:component-id top-instance) true) component (ctkl/get-component component-file (:component-id top-instance) true)
remote-shape (get-ref-shape component-file component shape) 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) (if (nil? remote-shape)
shape nil
(find-remote-shape component-container libraries remote-shape)))) (if (nil? (:shape-ref remote-shape))
remote-shape
(find-remote-shape component-container libraries remote-shape)))))
(defn get-component-shapes (defn get-component-shapes
"Retrieve all shapes of the component" "Retrieve all shapes of the component"

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" stroke-linecap="round" stroke-linejoin="round">
<path d="M15.333 14H.667"/>
</svg>

After

Width:  |  Height:  |  Size: 145 B

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 14H9.5A7.5 7.5 0 012 6.5V2"/>
</svg>

After

Width:  |  Height:  |  Size: 160 B

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" stroke-linecap="round" stroke-linejoin="round">
<path d="M2 14h4.5A7.5 7.5 0 0014 6.5V2"/>
</svg>

After

Width:  |  Height:  |  Size: 160 B

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" stroke-linecap="round" stroke-linejoin="round">
<path d="M.667 8h14.666"/>
</svg>

After

Width:  |  Height:  |  Size: 144 B

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" stroke-linecap="round" stroke-linejoin="round">
<path d="M15.333 2H.667"/>
</svg>

After

Width:  |  Height:  |  Size: 144 B

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 2H9.5A7.499 7.499 0 002 9.5V14"/>
</svg>

After

Width:  |  Height:  |  Size: 164 B

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" stroke-linecap="round" stroke-linejoin="round">
<path d="M2 2h4.5A7.499 7.499 0 0114 9.5V14"/>
</svg>

After

Width:  |  Height:  |  Size: 164 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="4494 307 310 290"><path d="m4643.25 309.15-18.86 15.75a9.32 9.32 0 0 0-1.2 13.1 9.26 9.26 0 0 0 13.07 1.2l3.67-3.05.2 82.04a103.8 103.8 0 0 0-14.46-19.07c-15.68-16.23-36.46-25.7-59.17-26.11a86.83 86.83 0 0 0-11.04.52l1.91-3.29a9.32 9.32 0 0 0-3.42-12.57 9.26 9.26 0 0 0-12.6 3.2l-12.59 21.63a9.32 9.32 0 0 0 2.1 11.86l18.93 15.67a9.26 9.26 0 0 0 9.16 1.54 9.32 9.32 0 0 0 2.66-15.9l-4.42-3.66c21.64-2.66 40.4 4.82 55.14 20.07 16.68 17.27 27.77 45.12 27.98 79.86-.05 9.54.04 19.06.07 28.6-10.61-16.53-24.8-30.38-40.13-40.85-15.92-10.87-33.13-18.28-49.55-21.38-8.2-1.55-16.3-2.04-23.93-1.2-2.3.26-4.6.66-6.85 1.2l1.09-2.8a9.27 9.27 0 0 0-17.27-6.77l-9.1 23.32a9.32 9.32 0 0 0 3.91 11.4l21.13 12.53a9.26 9.26 0 0 0 12.54-3.37 9.32 9.32 0 0 0-3.1-12.65l-5.56-3.3c6.54-1.62 14.64-1.77 23.72-.05 13.42 2.53 28.56 8.92 42.53 18.46 27.92 19.09 50.72 50.2 50.72 87.3v.03l.02 5.3a9.3 9.3 0 0 0 9.3 9.29 9.29 9.29 0 0 0 9.25-9.32v-2.77c.5-37.1 19.92-67.3 48.22-89.72 13.17-9.62 27.44-15.99 39.99-18.46 10.08-2 18.76-1.4 25.26 1.19l-4 2.43a9.32 9.32 0 0 0-3.14 12.79 9.26 9.26 0 0 0 12.74 3.14l21.37-13a9.32 9.32 0 0 0 3.78-11.43l-9.17-22.82a9.27 9.27 0 1 0-17.21 6.96l1.72 4.29a50.78 50.78 0 0 0-11.87-2.93c-7.38-.92-15.18-.45-23.06 1.11-15.78 3.1-32.2 10.63-47.34 21.68a142.37 142.37 0 0 0-37.45 40.54l-.07-24.5c.65-34.78 11.26-64.22 26.98-82.93 15.4-18.33 34.64-26.71 56.88-22.07l-4.52 2.74a9.32 9.32 0 0 0-3 12.7 9.26 9.26 0 0 0 12.6 3.22l21.37-12.99a9.32 9.32 0 0 0 3.79-11.43l-9.17-22.83a9.27 9.27 0 0 0-12.08-5.15 9.32 9.32 0 0 0-5.14 12.11l1.5 3.74a73.41 73.41 0 0 0-19.47-1.8c-22.03.99-42.11 12.09-56.95 29.76a115.7 115.7 0 0 0-12.97 19.25l-.2-83.78 3.47 3.03a9.26 9.26 0 0 0 12.97-1.03 9.32 9.32 0 0 0-.8-13.02l-18.87-16.42a9.26 9.26 0 0 0-12.01-.13Z" paint-order="fill markers"/></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="4870 326 249.25 261.8"><path fill="#000" d="M5050.75 326.04a8.57 8.57 0 0 0-7.77 3.58l-19.12 27.03a8.57 8.57 0 0 0 0 9.9l19.12 27.04a8.57 8.57 0 0 0 15.44-3.5 8.57 8.57 0 0 0-1.45-6.4l-8.43-11.92c13.34-.5 22.4 1.84 28.61 5.26 6.78 3.74 10.6 8.83 13.14 14.83 2.54 6 3.51 12.91 3.77 19.07.24 6.16-.26 11.02-.19 15.04a8.57 8.57 0 0 0 8.7 8.43 8.57 8.57 0 0 0 8.44-8.69c-.02-1.7.47-8.1.17-15.49-.3-7.37-1.4-16.26-5.1-25.03a46.95 46.95 0 0 0-20.64-23.17c-9.83-5.43-22.72-8.14-39.22-7.3l10.75-15.21a8.57 8.57 0 0 0-2.05-11.93 8.56 8.56 0 0 0-4.17-1.54Zm-158.97 1.7A21.92 21.92 0 0 0 4870 349.5v61.37a21.92 21.92 0 0 0 21.77 21.78h61.38a21.92 21.92 0 0 0 21.78-21.77v-61.37a21.92 21.92 0 0 0-21.77-21.79h-61.38Zm0 17.13h61.37a4.46 4.46 0 0 1 4.64 4.64v61.37a4.46 4.46 0 0 1-4.63 4.65h-61.38a4.45 4.45 0 0 1-4.63-4.65v-61.37a4.45 4.45 0 0 1 4.63-4.63Zm-5.1 134.52a8.57 8.57 0 0 0-8.43 8.7c.02 1.7-.47 8.1-.17 15.47.3 7.37 1.4 16.26 5.1 25.04a47 47 0 0 0 20.65 23.18c9.83 5.41 22.7 8.14 39.2 7.3l-10.74 15.2a8.57 8.57 0 0 0 8.44 13.4 8.57 8.57 0 0 0 5.56-3.51l19.1-27.04a8.57 8.57 0 0 0 0-9.9l-19.1-27.03a8.57 8.57 0 0 0-5.56-3.5 8.57 8.57 0 0 0-8.44 13.4l8.42 11.92c-13.34.5-22.4-1.82-28.6-5.24-6.78-3.74-10.6-8.83-13.14-14.83-2.54-6.02-3.51-12.92-3.77-19.08-.24-6.16.26-11.02.2-15.03a8.57 8.57 0 0 0-8.71-8.45Zm152.1 1.74a24.6 24.6 0 0 0-24.46 24.46v56.02a24.6 24.6 0 0 0 24.47 24.45h56a24.6 24.6 0 0 0 24.46-24.45v-56.02a24.6 24.6 0 0 0-24.45-24.46h-56.02Zm0 17.14h56.02a7.12 7.12 0 0 1 7.31 7.32v56.02a7.11 7.11 0 0 1-7.31 7.3h-56.02a7.12 7.12 0 0 1-5.23-2.07 7.12 7.12 0 0 1-2.09-5.23v-56.02a7.11 7.11 0 0 1 7.33-7.32Z" class="fills" color="#000" paint-order="fill markers"/></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="4563 696 212.14 362.63"><path d="M4669.07 696a41.22 41.22 0 0 0-29.24 12.07l-64.76 64.75a41.47 41.47 0 0 0 0 58.49l8.39 8.39-8.39 8.38a41.47 41.47 0 0 0 0 58.5l8.38 8.38-8.36 8.37a41.47 41.47 0 0 0 0 58.49l64.73 64.73a41.48 41.48 0 0 0 58.5.01l64.74-64.73a41.49 41.49 0 0 0 0-58.51l-8.37-8.38 8.37-8.37a41.47 41.47 0 0 0 0-58.5l-8.38-8.37 8.39-8.38a41.47 41.47 0 0 0 0-58.5l-64.75-64.75a41.25 41.25 0 0 0-29.25-12.07Zm0 18.28a22.85 22.85 0 0 1 16.2 6.83l64.76 64.75a22.68 22.68 0 0 1 0 32.42l-64.74 64.75a22.68 22.68 0 0 1-32.43.01l-64.76-64.75a22.68 22.68 0 0 1 0-32.43l64.75-64.75a22.86 22.86 0 0 1 16.22-6.83Zm0 52.73c-4.3 0-8.62 1.62-11.85 4.85l-18.35 18.36a16.89 16.89 0 0 0 0 23.7l18.35 18.35a16.89 16.89 0 0 0 23.7 0l18.36-18.35a16.89 16.89 0 0 0-.01-23.7l-18.35-18.35a16.7 16.7 0 0 0-11.85-4.86Zm0 15.19c.34 0 .68.16 1.01.5l18.36 18.35c.66.67.68 1.38.01 2.05l-18.35 18.35c-.67.67-1.4.67-2.06 0l-18.36-18.35c-.66-.66-.65-1.39.01-2.05l18.35-18.35c.34-.33.68-.5 1.03-.5Zm-72.58 70.53 43.33 43.33a41.47 41.47 0 0 0 58.5.01l43.33-43.33 8.37 8.37a22.68 22.68 0 0 1 0 32.42l-64.75 64.75a22.68 22.68 0 0 1-32.43 0l-64.73-64.73a22.68 22.68 0 0 1 0-32.44Zm145.17 75.24 8.37 8.37a22.7 22.7 0 0 1 0 32.45l-64.74 64.74a22.7 22.7 0 0 1-32.44-.01l-64.73-64.74a22.67 22.67 0 0 1 0-32.42l8.36-8.37 43.33 43.33a41.47 41.47 0 0 0 58.5 0Z" paint-order="fill markers"/></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="4870 696 216.34 319.12"><path d="M5052.72 696a5.69 5.69 0 0 0-5.8 4.77c-1.72 10.62-4.08 20.19-7.8 27-3.71 6.79-8.19 10.82-15.77 12.25-6.11 1.16-6.17 9.92-.07 11.15 7.97 1.64 12.63 5.72 16.33 12.39 3.7 6.68 5.93 16.05 7.27 26.7.89 6.88 10.98 6.47 11.32-.46.83-18.37 4.47-27.31 8.37-31.76 3.9-4.45 8.67-5.56 15.17-6.85 5.8-1.12 6.24-9.26.6-11.01-7.53-2.35-12.77-6.54-16.73-12.94-3.95-6.4-6.46-15.15-7.41-26.06a5.69 5.69 0 0 0-5.48-5.18Zm-151.65 71.03c-17.13.1-31 13.97-31.07 31.1v185.9a31.21 31.21 0 0 0 31.07 31.09h136.32a31.24 31.24 0 0 0 31.1-31.08V871.7c0-6.01-1.92-12.93-6.51-17.91-21.13-22.9-59.2-64.23-73.79-78.82-4-4.01-10.62-7.95-17.97-7.95Zm0 18.43h65.88v71.29c0 6.42 4.03 12.41 8.53 15.12 4.51 2.71 9.05 3.33 13.15 3.33h61.43v108.83a12.44 12.44 0 0 1-12.67 12.66h-136.32a12.42 12.42 0 0 1-12.65-12.66v-185.9a12.44 12.44 0 0 1 12.65-12.67Zm84.31 13.15a5423.07 5423.07 0 0 1 54.24 58.17h-50.99a8.99 8.99 0 0 1-3.25-.62Z" paint-order="fill markers"/></svg>

After

Width:  |  Height:  |  Size: 1,012 B

View file

@ -125,10 +125,11 @@
@include buttonStyle; @include buttonStyle;
@include flexCenter; @include flexCenter;
@include focusTertiary; @include focusTertiary;
--button-tertiary-border-width: #{$s-2};
border-radius: $br-8; border-radius: $br-8;
color: var(--button-tertiary-foreground-color-rest); color: var(--button-tertiary-foreground-color-rest);
background-color: transparent; background-color: transparent;
border: $s-2 solid transparent; border: var(--button-tertiary-border-width) solid transparent;
svg, svg,
span svg { span svg {
stroke: var(--button-tertiary-foreground-color-rest); stroke: var(--button-tertiary-foreground-color-rest);
@ -136,7 +137,7 @@
&:hover { &:hover {
background-color: var(--button-tertiary-background-color-hover); background-color: var(--button-tertiary-background-color-hover);
color: var(--button-tertiary-foreground-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, svg,
span svg { span svg {
stroke: var(--button-tertiary-foreground-color-hover); stroke: var(--button-tertiary-foreground-color-hover);
@ -144,7 +145,7 @@
} }
&:active { &:active {
outline: none; outline: none;
border: $s-2 solid transparent; border-color: transparent;
background-color: var(--button-tertiary-background-color-active); background-color: var(--button-tertiary-background-color-active);
color: var(--button-tertiary-foreground-color-active); color: var(--button-tertiary-foreground-color-active);
svg, svg,
@ -169,7 +170,7 @@
.button-icon-selected { .button-icon-selected {
outline: none; 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); background-color: var(--button-icon-background-color-selected);
color: var(--button-icon-foreground-color-selected); color: var(--button-icon-foreground-color-selected);
svg { svg {
@ -183,7 +184,7 @@
@include focusRadio; @include focusRadio;
border-radius: $br-8; border-radius: $br-8;
color: var(--button-radio-foreground-color-rest); 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, svg,
span svg { span svg {
stroke: var(--button-radio-foreground-color-rest); stroke: var(--button-radio-foreground-color-rest);

View file

@ -65,6 +65,9 @@
--button-tertiary-border-color-focus: var(--color-accent-primary); --button-tertiary-border-color-focus: var(--color-accent-primary);
--button-tertiary-foreground-color-focus: var(--color-foreground-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: var(--color-foreground-secondary);
--button-icon-foreground-color-hover: var(--color-foreground-secondary); --button-icon-foreground-color-hover: var(--color-foreground-secondary);
--button-icon-background-color-selected: var(--color-background-quaternary); --button-icon-background-color-selected: var(--color-background-quaternary);
@ -396,4 +399,5 @@
--assets-item-name-foreground-color: var(--color-foreground-primary); --assets-item-name-foreground-color: var(--color-foreground-primary);
--text-editor-selection-background-color: var(--la-tertiary-70); --text-editor-selection-background-color: var(--la-tertiary-70);
--expand-button-icon-border-width-selected: 2px;
} }

View file

@ -15,6 +15,7 @@ $fs-11: 0.688rem;
$fs-12: math.div(12, $fs-base) + rem; $fs-12: math.div(12, $fs-base) + rem;
$fs-14: math.div(14, $fs-base) + rem; $fs-14: math.div(14, $fs-base) + rem;
$fs-16: math.div(16, $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-24: math.div(24, $fs-base) + rem;
$fs-36: math.div(36, $fs-base) + rem; $fs-36: math.div(36, $fs-base) + rem;

View file

@ -115,8 +115,8 @@
@mixin copyWrapperBase { @mixin copyWrapperBase {
position: relative; position: relative;
min-height: $s-32; min-height: $s-32;
width: $s-156; width: $s-144;
max-width: $s-156; max-width: $s-144;
padding: calc($s-8 - $s-1) 0 calc($s-8 - $s-1) calc($s-8 - $s-1); padding: calc($s-8 - $s-1) 0 calc($s-8 - $s-1) calc($s-8 - $s-1);
border-radius: $s-8; border-radius: $s-8;
box-sizing: border-box; box-sizing: border-box;

View file

@ -35,6 +35,7 @@
[app.main.data.events :as ev] [app.main.data.events :as ev]
[app.main.data.fonts :as df] [app.main.data.fonts :as df]
[app.main.data.messages :as msg] [app.main.data.messages :as msg]
[app.main.data.modal :as modal]
[app.main.data.users :as du] [app.main.data.users :as du]
[app.main.data.workspace.bool :as dwb] [app.main.data.workspace.bool :as dwb]
[app.main.data.workspace.changes :as dch] [app.main.data.workspace.changes :as dch]
@ -119,10 +120,14 @@
(assoc :workspace-ready? true))) (assoc :workspace-ready? true)))
ptk/WatchEvent ptk/WatchEvent
(watch [_ _ _] (watch [_ state _]
(rx/of (fbc/fix-bool-contents) (rx/of
(fdf/fix-deleted-fonts) (when (and (not (boolean (-> state :profile :props :v2-info-shown)))
(fbs/fix-broken-shapes))))) (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 (defn- workspace-data-loaded
[data] [data]

View file

@ -442,6 +442,7 @@
(declare activate-colorpicker-color) (declare activate-colorpicker-color)
(declare activate-colorpicker-gradient) (declare activate-colorpicker-gradient)
(declare activate-colorpicker-image) (declare activate-colorpicker-image)
(declare update-colorpicker)
(defn apply-color-from-colorpicker (defn apply-color-from-colorpicker
[color] [color]
@ -453,8 +454,7 @@
(:image color) (activate-colorpicker-image) (:image color) (activate-colorpicker-image)
(:color color) (activate-colorpicker-color) (:color color) (activate-colorpicker-color)
(= :linear (get-in color [:gradient :type])) (activate-colorpicker-gradient :linear-gradient) (= :linear (get-in color [:gradient :type])) (activate-colorpicker-gradient :linear-gradient)
(= :radial (get-in color [:gradient :type])) (activate-colorpicker-gradient :radial-gradient)) (= :radial (get-in color [:gradient :type])) (activate-colorpicker-gradient :radial-gradient))))))
(apply-color-from-palette color false)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View file

@ -77,7 +77,11 @@
extract (cond-> {:type (:type change) extract (cond-> {:type (:type change)
:raw-change change} :raw-change change}
shape 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) (:operations change)
(assoc :operations (:operations change)))] (assoc :operations (:operations change)))]
extract))] extract))]
@ -894,7 +898,8 @@
(pcb/update-shapes [(:id new-shape)] #(d/patch-object % keep-props-values)) (pcb/update-shapes [(:id new-shape)] #(d/patch-object % keep-props-values))
;; We need to set the same index as the original shape ;; 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 ;; First delete so we don't break the grid layout cells
(rx/of (dch/commit-changes changes) (rx/of (dch/commit-changes changes)

View file

@ -886,7 +886,6 @@
(map #(redirect-shaperef %) children-inst) (map #(redirect-shaperef %) children-inst)
children-inst) children-inst)
only-inst (fn [changes child-inst] only-inst (fn [changes child-inst]
(add-shape-to-main changes (add-shape-to-main changes
child-inst child-inst
@ -1088,10 +1087,8 @@
root-main)) root-main))
update-original-shape (fn [original-shape new-shape] update-original-shape (fn [original-shape new-shape]
(if-not (:shape-ref original-shape) (assoc original-shape
(assoc original-shape :shape-ref (:id new-shape)))
:shape-ref (:id new-shape))
original-shape))
[_new-shape new-shapes updated-shapes] [_new-shape new-shapes updated-shapes]
(ctst/clone-shape shape (ctst/clone-shape shape
@ -1116,25 +1113,46 @@
:obj shape'})))) :obj shape'}))))
mod-obj-change (fn [changes shape'] mod-obj-change (fn [changes shape']
(update changes :redo-changes conj (let [shape-original (ctn/get-shape page (:id shape'))]
{:type :mod-obj (-> changes
:page-id (:id page) (update :redo-changes conj
:id (:id shape') {:type :mod-obj
:operations [{:type :set :page-id (:id page)
:attr :component-id :id (:id shape')
:val (:component-id shape')} :operations [{:type :set
{:type :set :attr :component-id
:attr :component-file :val (:component-id shape')}
:val (:component-file shape')} {:type :set
{:type :set :attr :component-file
:attr :component-root :val (:component-file shape')}
:val (:component-root shape')} {:type :set
{:type :set :attr :component-root
:attr :shape-ref :val (:component-root shape')}
:val (:shape-ref shape')} {:type :set
{:type :set :attr :shape-ref
:attr :touched :val (:shape-ref shape')}
:val (:touched 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'] del-obj-change (fn [changes shape']
(update changes :undo-changes conj (update changes :undo-changes conj
@ -1161,7 +1179,8 @@
parents (cfh/get-parent-ids objects (:id shape)) parents (cfh/get-parent-ids objects (:id shape))
parent (first parents) parent (first parents)
children (cfh/get-children-ids objects (:id shape)) 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] add-redo-change (fn [changes id]
(update changes :redo-changes conj (update changes :redo-changes conj
@ -1190,12 +1209,11 @@
(update :redo-changes conj (make-change (update :redo-changes conj (make-change
container container
{:type :reg-objects {:type :reg-objects
:shapes (vec parents)})) :shapes (vec parents)})))
(add-undo-change (:id shape)))
changes' (reduce add-undo-change changes' (reduce add-undo-change
changes' changes'
children)] ids)]
(if (and (cfh/touched-group? parent :shapes-group) omit-touched?) (if (and (cfh/touched-group? parent :shapes-group) omit-touched?)
changes changes

View file

@ -142,17 +142,17 @@
(rx/concat (rx/concat
(rx/of (dwu/start-undo-transaction undo-id) (rx/of (dwu/start-undo-transaction undo-id)
(update-shape-flags ids-to-hide {:hidden true})) (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)))))))) (rx/of (dwu/commit-undo-transaction undo-id))))))))
(defn- real-delete-shapes-changes (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)) (let [changes (-> (pcb/empty-changes it (:id page))
(pcb/with-page page) (pcb/with-page page)
(pcb/with-objects objects) (pcb/with-objects objects)
(pcb/with-library-data file))] (pcb/with-library-data file))]
(real-delete-shapes-changes 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] ([changes file page objects ids _it components-v2 ignore-touched]
(let [lookup (d/getf objects) (let [lookup (d/getf objects)
groups-to-unmask groups-to-unmask
(reduce (fn [group-ids id] (reduce (fn [group-ids id]
@ -252,7 +252,7 @@
changes (-> changes changes (-> changes
(pcb/remove-objects all-children {:ignore-touched true}) (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/remove-objects empty-parents)
(pcb/resize-parents all-parents) (pcb/resize-parents all-parents)
(pcb/update-shapes groups-to-unmask (pcb/update-shapes groups-to-unmask
@ -274,13 +274,13 @@
(defn delete-shapes-changes (defn 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)] (let [[changes _all-parents] (real-delete-shapes-changes changes file page objects ids it components-v2 ignore-touched)]
changes)) changes))
(defn- real-delete-shapes (defn- real-delete-shapes
[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) (let [[changes all-parents] (real-delete-shapes-changes file page objects ids it components-v2 ignore-touched)
undo-id (js/Symbol)] undo-id (js/Symbol)]
(rx/of (dwu/start-undo-transaction undo-id) (rx/of (dwu/start-undo-transaction undo-id)
(dc/detach-comment-thread ids) (dc/detach-comment-thread ids)
@ -288,7 +288,6 @@
(ptk/data-event :layout/update all-parents) (ptk/data-event :layout/update all-parents)
(dwu/commit-undo-transaction undo-id)))) (dwu/commit-undo-transaction undo-id))))
(defn create-and-add-shape (defn create-and-add-shape
[type frame-x frame-y {:keys [width height] :as attrs}] [type frame-x frame-y {:keys [width height] :as attrs}]
(ptk/reify ::create-and-add-shape (ptk/reify ::create-and-add-shape

View file

@ -57,5 +57,5 @@
:on-double-click on-double-click :on-double-click on-double-click
:title name} :title name}
(if (some? image) (if (some? image)
(tr "media.image") (tr "media.image.short")
(or name color (uc/gradient-type->string (:type gradient))))]))) (or name color (uc/gradient-type->string (:type gradient))))])))

View file

@ -9,7 +9,7 @@
(:require (:require
[app.config :as cfg] [app.config :as cfg]
[app.util.color :as uc] [app.util.color :as uc]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :refer [tr]]
[cuerdas.core :as str] [cuerdas.core :as str]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
@ -99,16 +99,16 @@
(mf/defc color-name (mf/defc color-name
{::mf/wrap-props false} {::mf/wrap-props false}
[{:keys [color size on-click on-double-click]}] [{:keys [color size on-click on-double-click origin]}]
(let [{:keys [name color gradient image]} (if (string? color) {:color color :opacity 1} color)] (let [{:keys [name color gradient]} (if (string? color) {:color color :opacity 1} color)]
(when (or (not size) (> size 64)) (when (or (not size) (> size 64))
[:span {:class (stl/css-case [:span {:class (stl/css-case
:color-text (< size 72) :color-text (and (= origin :palette) (< size 72))
:small-text (and (>= size 64) (< size 72)) :small-text (and (= origin :palette) (>= size 64) (< size 72))
:big-text (>= size 72)) :big-text (and (= origin :palette) (>= size 72))
:gradient (some? gradient)
:color-row-name (not= origin :palette))
:title name :title name
:on-click on-click :on-click on-click
:on-double-click on-double-click} :on-double-click on-double-click}
(if (some? image) (or name color (uc/gradient-type->string (:type gradient)))])))
(or name (tr "media.image"))
(or name color (uc/gradient-type->string (:type gradient))))])))

View file

@ -94,3 +94,7 @@
.no-text { .no-text {
display: none; display: none;
} }
.color-row-name {
color: var(--menu-foreground-color);
}

View file

@ -8,9 +8,8 @@
.copy-button { .copy-button {
@include buttonStyle; @include buttonStyle;
@include flexCenter; width: 100%;
height: $s-32; height: $s-32;
width: $s-32;
border: $s-1 solid transparent; border: $s-1 solid transparent;
border-radius: $br-8; border-radius: $br-8;
background-color: transparent; background-color: transparent;
@ -51,41 +50,32 @@
.copy-wrapper { .copy-wrapper {
@include buttonStyle; @include buttonStyle;
@include copyWrapperBase; @include copyWrapperBase;
display: grid;
grid-template-columns: 1fr $s-24;
grid-template-areas: "name button";
width: 100%; width: 100%;
height: fit-content; height: fit-content;
text-align: left; text-align: left;
border: 1px solid transparent; border: $s-1 solid transparent;
.icon-btn { .icon-btn {
@include flexCenter;
position: absolute; position: absolute;
display: flex;
justify-content: center;
align-items: center;
top: 0; top: 0;
right: 0; right: 0;
height: $s-32; height: $s-32;
width: $s-32; width: $s-28;
svg { svg {
@extend .button-icon-small; @extend .button-icon-small;
stroke: var(--button-tertiary-foreground-color-focus);
display: none; display: none;
} }
} }
&:hover { &:hover {
.icon-btn { background-color: var(--button-tertiary-background-color-focus);
svg { color: var(--button-tertiary-foreground-color-focus);
display: flex; border: $s-1 solid var(--button-tertiary-background-color-focus);
stroke: var(--button-tertiary-foreground-color-active); .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,
&:focus-visible { &:focus-visible {
outline: none; outline: none;

View file

@ -97,15 +97,17 @@
current-icon (:icon selected-option) current-icon (:icon selected-option)
current-icon-ref (i/key->icon current-icon)] current-icon-ref (i/key->icon current-icon)]
[:div {:on-click open-dropdown [:div {:on-click open-dropdown
:class (dm/str class " " (stl/css-case :custom-select true :class (dm/str (stl/css-case :custom-select true
:disabled disabled :disabled disabled
:icon (some? current-icon-ref)))} :icon (some? current-icon-ref))
" " class)}
(when (and current-icon current-icon-ref) (when (and current-icon current-icon-ref)
[:span {:class (stl/css :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 :current-label)} current-label]
[:span {:class (stl/css :dropdown-button)} i/arrow-refactor] [:span {:class (stl/css :dropdown-button)} i/arrow-refactor]
[:& dropdown {:show is-open? :on-close close-dropdown} [:& 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)] (for [[index item] (d/enumerate options)]
(if (= :separator item) (if (= :separator item)
[:li {:class (dom/classnames (stl/css :separator) true) [:li {:class (dom/classnames (stl/css :separator) true)

View file

@ -31,7 +31,7 @@
width: $s-24; width: $s-24;
padding-right: $s-4; padding-right: $s-4;
svg { svg {
@extend .button-icon; @extend .button-icon-small;
stroke: var(--icon-foreground); stroke: var(--icon-foreground);
} }
} }

View file

@ -13,7 +13,7 @@
(mf/defc title-bar (mf/defc title-bar
{::mf/wrap-props false} {::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)] (let [klass (dm/str (stl/css :title-bar) " " class)]
[:div {:class klass} [:div {:class klass}
(if ^boolean collapsable (if ^boolean collapsable
@ -33,7 +33,10 @@
:on-click on-collapsed} :on-click on-collapsed}
i/arrow-refactor] i/arrow-refactor]
[:div {:class (stl/css :title)} title]])] [: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 children
(when (some? on-btn-click) (when (some? on-btn-click)
[:button {:class (stl/css :title-button) [:button {:class (stl/css :title-button)

View file

@ -14,22 +14,39 @@
width: 100%; width: 100%;
min-height: $s-32; min-height: $s-32;
background-color: var(--title-background-color); 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; display: flex;
align-items: center; align-items: center;
flex-grow: 1; flex-grow: 1;
@ -37,42 +54,7 @@
color: var(--title-foreground-color); color: var(--title-foreground-color);
stroke: var(--title-foreground-color); stroke: var(--title-foreground-color);
overflow: hidden; 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 { .collapsabled-icon {
@include buttonStyle;
@include flexCenter; @include flexCenter;
height: $s-24; height: $s-24;
border-radius: $br-8; border-radius: $br-8;
@ -89,6 +71,7 @@
color: var(--title-foreground-color-hover); color: var(--title-foreground-color-hover);
stroke: var(--title-foreground-color-hover); stroke: var(--title-foreground-color-hover);
.title { .title {
color: var(--title-foreground-color-hover);
stroke: var(--title-foreground-color-hover); stroke: var(--title-foreground-color-hover);
} }
.collapsabled-icon svg { .collapsabled-icon svg {
@ -96,16 +79,61 @@
} }
} }
} }
.collapsabled-icon {
.title-button { @include buttonStyle;
@extend .button-tertiary; @include flexCenter;
height: $s-32; height: $s-24;
width: calc($s-24 + $s-4);
padding: 0;
border-radius: $br-8; border-radius: $br-8;
svg { svg {
@extend .button-icon; @extend .button-icon-small;
transform: rotate(90deg);
stroke: var(--icon-foreground); 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};
}

View file

@ -50,3 +50,7 @@
@extend .modal-danger-btn; @extend .modal-danger-btn;
} }
} }
.modal-msg {
color: var(--modal-text-foreground-color);
}

View file

@ -103,7 +103,7 @@
color: var(--modal-text-foreground-color); color: var(--modal-text-foreground-color);
} }
.modal-link { .modal-link {
@include titleTipography; @include bodyLargeTypography;
text-decoration: none; text-decoration: none;
cursor: pointer; cursor: pointer;
color: var(--modal-link-foreground-color); color: var(--modal-link-foreground-color);

View file

@ -322,6 +322,13 @@
(def ^:icon column-reverse-refactor (icon-xref :column-reverse-refactor)) (def ^:icon column-reverse-refactor (icon-xref :column-reverse-refactor))
(def ^:icon constraint-horizontal-refactor (icon-xref :constraint-horizontal-refactor)) (def ^:icon constraint-horizontal-refactor (icon-xref :constraint-horizontal-refactor))
(def ^:icon constraint-vertical-refactor (icon-xref :constraint-vertical-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 corner-radius-refactor (icon-xref :corner-radius-refactor))
(def ^:icon curve-refactor (icon-xref :curve-refactor)) (def ^:icon curve-refactor (icon-xref :curve-refactor))
(def ^:icon distribute-vertical-spacing-refactor (icon-xref :distribute-vertical-spacing-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-round (icon-xref :cap-round))
(def ^:icon cap-square (icon-xref :cap-square)) (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 (def ^:icon loader-pencil
(mf/html (mf/html

View file

@ -78,7 +78,7 @@
} }
.modal-link { .modal-link {
@include titleTipography; @include bodyLargeTypography;
color: var(--modal-link-foreground-color); color: var(--modal-link-foreground-color);
margin: 0; margin: 0;
} }

View file

@ -60,7 +60,7 @@
} }
.modal-link { .modal-link {
@include titleTipography; @include bodyLargeTypography;
color: var(--modal-link-foreground-color); color: var(--modal-link-foreground-color);
margin: 0; margin: 0;
} }

View file

@ -12,4 +12,5 @@
gap: $s-16; gap: $s-16;
width: 100%; width: 100%;
height: 100%; height: 100%;
padding-top: $s-8;
} }

View file

@ -7,6 +7,7 @@
(ns app.main.ui.viewer.inspect.attributes.blur (ns app.main.ui.viewer.inspect.attributes.blur
(:require-macros [app.main.style :as stl]) (:require-macros [app.main.style :as stl])
(:require (:require
[app.common.data.macros :as dm]
[app.main.ui.components.copy-button :refer [copy-button]] [app.main.ui.components.copy-button :refer [copy-button]]
[app.main.ui.components.title-bar :refer [title-bar]] [app.main.ui.components.title-bar :refer [title-bar]]
[app.util.code-gen.style-css :as css] [app.util.code-gen.style-css :as css]
@ -23,13 +24,16 @@
[:div {:class (stl/css :attributes-block)} [:div {:class (stl/css :attributes-block)}
[:& title-bar {:collapsable false [:& title-bar {:collapsable false
:title (tr "inspect.attributes.blur") :title (tr "inspect.attributes.blur")
:origin :inspect
:class (stl/css :title-spacing-blur)} :class (stl/css :title-spacing-blur)}
(when (= (count shapes) 1) (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)} [:div {:class (stl/css :attributes-content)}
(for [shape shapes] (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-label)} "Filter"]
[:div {:class (stl/css :global/attr-value)} [:div {:class (stl/css :global/attr-value)}
[:& copy-button {:data (css/get-css-property objects shape :filter)} [:& copy-button {:data (css/get-css-property objects shape :filter)}

View file

@ -21,3 +21,7 @@
.button-children { .button-children {
@extend .copy-button-children; @extend .copy-button-children;
} }
.copy-btn-title {
max-width: $s-28;
}

View file

@ -8,6 +8,8 @@
(:require-macros [app.main.style :as stl]) (:require-macros [app.main.style :as stl])
(:require (:require
[app.common.colors :as cc] [app.common.colors :as cc]
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.media :as cm] [app.common.media :as cm]
[app.config :as cf] [app.config :as cf]
[app.main.refs :as refs] [app.main.refs :as refs]
@ -15,6 +17,7 @@
[app.main.ui.components.color-bullet-new :as cbn] [app.main.ui.components.color-bullet-new :as cbn]
[app.main.ui.components.copy-button :refer [copy-button]] [app.main.ui.components.copy-button :refer [copy-button]]
[app.main.ui.components.select :refer [select]] [app.main.ui.components.select :refer [select]]
[app.main.ui.formats :as fmt]
[app.util.i18n :refer [tr]] [app.util.i18n :refer [tr]]
[cuerdas.core :as str] [cuerdas.core :as str]
[okulary.core :as l] [okulary.core :as l]
@ -43,6 +46,13 @@
(defn- get-file-colors [] (defn- get-file-colors []
(or (mf/deref file-colors-ref) (mf/deref refs/workspace-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]}] (mf/defc color-row [{:keys [color format copy-data on-change-format]}]
(let [colors-library (get-colors-library color) (let [colors-library (get-colors-library color)
file-colors (get-file-colors) file-colors (get-file-colors)
@ -50,85 +60,90 @@
color (assoc color :color-library-name color-library-name) color (assoc color :color-library-name color-library-name)
image (:image color)] 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 (if image
[:div {:class (stl/css :format-wrapper)} (let [mtype (-> image :mtype)
(when-not (and on-change-format (or (:gradient color) image)) name (or (:name image) (tr "media.image"))
[:div {:class (stl/css :select-format-wrapper)} extension (cm/mtype->extension mtype)]
[:& select [:div {:class (stl/css :attributes-image-as-color-row)}
{:default-value format [:div {:class (stl/css :attributes-color-row)}
:options [{:value :hex :label (tr "inspect.attributes.color.hex")} [:div {:class (stl/css :bullet-wrapper)
{:value :rgba :label (tr "inspect.attributes.color.rgba")} :style #js {"--bullet-size" "16px"}}
{:value :hsla :label (tr "inspect.attributes.color.hsla")}] [:& cbn/color-bullet {:color color
:on-change on-change-format}]]) :mini? true}]]
(when (:gradient color)
[:div {:class (stl/css :format-info)} "rgba"])])
(if (and copy-data (not image)) [:div {:class (stl/css :format-wrapper)}
[:& copy-button {:data copy-data [:div {:class (stl/css :image-format)}
:class (stl/css :color-row-copy-btn)} (tr "media.image.short")]]
[:* [:& copy-button {:data copy-data
[:div {:class (stl/css :first-row)} :class (stl/css :color-row-copy-btn)}
[:div {:class (stl/css :name-opacity)} [:div {:class (stl/css-case :color-info true
[:span {:class (stl/css-case :color-value-wrapper true :two-line (some? color-library-name))}
:gradient-name (:gradient color))} [:div {:class (stl/css :first-row)}
(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)
[:span {:class (stl/css :opacity-info)} [:span {:class (stl/css :opacity-info)}
(str (* 100 (:opacity color)) "%")])]] (str (* 100 (:opacity color)) "%")]]
(when color-library-name (when color-library-name
[:div {:class (stl/css :second-row)} [:div {:class (stl/css :second-row)}
[:div {:class (stl/css :color-name-library)} [:div {:class (stl/css :color-name-library)}
color-library-name]])])] color-library-name]])]]
(when image [:div {:class (stl/css :image-download)}
(let [mtype (-> image :mtype) [:div {:class (stl/css :image-wrapper)}
name (or (:name image) (tr "media.image")) [:img {:src (cf/resolve-file-media image)}]]
extension (cm/mtype->extension mtype)]
[:a {:class (stl/css :download-button) [:a {:class (stl/css :download-button)
:target "_blank" :target "_blank"
:download (cond-> name extension (str/concat extension)) :download (cond-> name extension (str/concat extension))
:href (cf/resolve-file-media image)} :href (cf/resolve-file-media image)}
(tr "inspect.attributes.image.download")]))])) (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]])]])))

View file

@ -6,9 +6,13 @@
@import "refactor/common-refactor.scss"; @import "refactor/common-refactor.scss";
.attributes-image-as-color-row {
max-width: $s-240;
}
.attributes-color-row { .attributes-color-row {
display: grid; display: grid;
grid-template-columns: $s-16 $s-72 $s-156; grid-template-columns: $s-16 $s-72 $s-144;
gap: $s-4; gap: $s-4;
} }
@ -22,14 +26,19 @@
height: $s-32; height: $s-32;
} }
.image-format {
@include tabTitleTipography;
height: $s-32;
padding: $s-8 0;
color: var(--menu-foreground-color-rest);
}
.select-format-wrapper { .select-format-wrapper {
width: 100%; width: 100%;
div { padding: $s-8 $s-2;
background-color: transparent; background-color: transparent;
border: none; border-color: transparent;
padding-left: $s-2; color: var(--menu-foreground-color-rest);
color: var(--menu-foreground-color-rest);
}
} }
.format-info { .format-info {
@ -43,12 +52,16 @@
color: var(--menu-foreground-color-rest); color: var(--menu-foreground-color-rest);
} }
.color-row-copy-btn {
max-width: $s-144;
}
.color-info { .color-info {
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
gap: $s-4; gap: $s-4;
flex-grow: 1; flex-grow: 1;
max-width: $s-144;
button { button {
visibility: hidden; visibility: hidden;
min-width: $s-28; min-width: $s-28;
@ -57,19 +70,22 @@
visibility: visible; visibility: visible;
} }
} }
.one-line {
.name-opacity { max-height: $s-32;
display: flex; }
align-items: baseline; .two-line {
display: grid;
grid-template-rows: 1fr 1fr;
} }
.color-name-wrapper { .color-name-wrapper {
@include titleTipography; @include titleTipography;
@include flexColumn; @include flexColumn;
padding: $s-8 $s-4 $s-8 $s-8; padding: $s-8 $s-4 $s-8 $s-8;
height: $s-32; height: $s-32;
max-width: $s-80; max-width: $s-80;
&.gradient-color { &.gradient-color {
color: var(--menu-foreground-color);
max-width: $s-124; max-width: $s-124;
} }
.color-name-library { .color-name-library {
@ -92,16 +108,9 @@
padding: $s-8 0; padding: $s-8 0;
} }
.color-info,
.color-row-copy-btn {
display: flex;
max-width: $s-144;
}
.first-row { .first-row {
display: grid; display: grid;
grid-template-columns: 1fr $s-24; grid-template-columns: 1fr $s-28;
grid-template-areas: "name button";
height: fit-content; height: fit-content;
width: 100%; width: 100%;
padding: 0; padding: 0;
@ -109,27 +118,26 @@
} }
.name-opacity { .name-opacity {
grid-area: name;
height: fit-content; height: fit-content;
max-width: $s-124; width: 100%;
line-height: $s-16; line-height: $s-16;
display: grid;
grid-template-columns: 1fr auto;
} }
.color-value-wrapper { .color-value-wrapper {
@include textEllipsis;
@include inspectValue; @include inspectValue;
text-transform: uppercase; text-transform: uppercase;
max-width: $s-80;
padding-right: $s-8;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&.gradient-name { &.gradient-name {
text-transform: none; text-transform: none;
} }
} }
.opacity-info { .opacity-info {
@include inspectValue; @include inspectValue;
text-transform: uppercase; text-transform: uppercase;
width: 100%;
} }
.second-row { .second-row {
@ -146,9 +154,32 @@
color: var(--menu-foreground-color-rest); color: var(--menu-foreground-color-rest);
} }
.image-download {
grid-column: 1 / 4;
}
.download-button { .download-button {
@extend .button-secondary; @extend .button-secondary;
@include tabTitleTipography; @include tabTitleTipography;
height: $s-32; height: $s-32;
width: 100%;
margin-top: $s-4; 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;
}
}

View file

@ -35,7 +35,6 @@
[{:keys [objects shape]}] [{:keys [objects shape]}]
(let [format* (mf/use-state :hex) (let [format* (mf/use-state :hex)
format (deref format*) format (deref format*)
color (shape->color shape) color (shape->color shape)
on-change on-change
(mf/use-fn (mf/use-fn
@ -55,6 +54,7 @@
(when (seq shapes) (when (seq shapes)
[:div {:class (stl/css :attributes-block)} [:div {:class (stl/css :attributes-block)}
[:& title-bar {:collapsable false [:& title-bar {:collapsable false
:origin :inspect
:title (tr "inspect.attributes.fill") :title (tr "inspect.attributes.fill")
:class (stl/css :title-spacing-fill)}] :class (stl/css :title-spacing-fill)}]

View file

@ -13,3 +13,8 @@
.title-spacing-fill { .title-spacing-fill {
@extend .attr-title; @extend .attr-title;
} }
.attributes-content {
display: grid;
gap: $s-4;
}

View file

@ -11,6 +11,7 @@
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.main.ui.components.copy-button :refer [copy-button]] [app.main.ui.components.copy-button :refer [copy-button]]
[app.main.ui.components.title-bar :refer [title-bar]] [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.code-gen.style-css :as css]
[app.util.i18n :refer [tr]] [app.util.i18n :refer [tr]]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
@ -22,12 +23,14 @@
[:* [:*
(for [[idx property] (d/enumerate properties)] (for [[idx property] (d/enumerate properties)]
(when-let [value (css/get-css-value objects shape property)] (when-let [value (css/get-css-value objects shape property)]
[:div {:key (dm/str "block-" idx "-" (d/name property)) (let [property-name (cmm/get-css-rule-humanized property)]
:class (stl/css :geometry-row)} [:div {:key (dm/str "block-" idx "-" (d/name property))
[:div {:class (stl/css :global/attr-label)} (d/name property)] :title property-name
[:div {:class (stl/css :global/attr-value)} :class (stl/css :geometry-row)}
[:& copy-button {:data (css/get-css-property objects shape property)} [:div {:class (stl/css :global/attr-label)} property-name]
[:div {:class (stl/css :button-children)} value]]]]))]) [: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 (mf/defc geometry-panel
@ -35,10 +38,12 @@
[:div {:class (stl/css :attributes-block)} [:div {:class (stl/css :attributes-block)}
[:& title-bar {:collapsable false [:& title-bar {:collapsable false
:title (tr "inspect.attributes.size") :title (tr "inspect.attributes.size")
:origin :inspect
:class (stl/css :title-spacing-geometry)} :class (stl/css :title-spacing-geometry)}
(when (= (count shapes) 1) (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] (for [shape shapes]
[:& geometry-block {:shape shape [:& geometry-block {:shape shape

View file

@ -21,3 +21,7 @@
.button-children { .button-children {
@extend .copy-button-children; @extend .copy-button-children;
} }
.copy-btn-title {
max-width: $s-28;
}

View file

@ -23,7 +23,7 @@
[{:keys [objects shapes]}] [{:keys [objects shapes]}]
(for [shape (filter cfh/image-shape? shapes)] (for [shape (filter cfh/image-shape? shapes)]
[:div {:class (stl/css :attributes-block) [:div {:class (stl/css :attributes-block)
:key (str "image-" (:id shape))} :key (str "image-" (:id shape))}
[:div {:class (stl/css :image-wrapper)} [:div {:class (stl/css :image-wrapper)}
[:img {:src (cf/resolve-file-media (-> shape :metadata))}]] [:img {:src (cf/resolve-file-media (-> shape :metadata))}]]

View file

@ -8,9 +8,11 @@
(:require-macros [app.main.style :as stl]) (:require-macros [app.main.style :as stl])
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.types.shape.layout :as ctl] [app.common.types.shape.layout :as ctl]
[app.main.ui.components.copy-button :refer [copy-button]] [app.main.ui.components.copy-button :refer [copy-button]]
[app.main.ui.components.title-bar :refer [title-bar]] [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.code-gen.style-css :as css]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
@ -31,13 +33,16 @@
[{:keys [objects shape]}] [{:keys [objects shape]}]
(for [property properties] (for [property properties]
(when-let [value (css/get-css-value objects shape property)] (when-let [value (css/get-css-value objects shape property)]
[:div {:class (stl/css :layout-row)} (let [property-name (cmm/get-css-rule-humanized property)]
[:div {:title (d/name property) [:div {:class (stl/css :layout-row)}
:class (stl/css :global/attr-label)} (d/name property)] [:div {:title property-name
[:div {:class (stl/css :global/attr-value)} :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)} [:& copy-button {:data (css/get-css-property objects shape property)}
[:div {:class (stl/css :button-children)} value]]]]))) [:div {:class (stl/css :button-children)} value]]]]))))
(mf/defc layout-panel (mf/defc layout-panel
[{:keys [objects shapes]}] [{:keys [objects shapes]}]
@ -46,11 +51,13 @@
(when (seq shapes) (when (seq shapes)
[:div {:class (stl/css :attributes-block)} [:div {:class (stl/css :attributes-block)}
[:& title-bar {:collapsable false [:& title-bar {:collapsable false
:origin :inspect
:title "Layout" :title "Layout"
:class (stl/css :title-spacing-layout)} :class (stl/css :title-spacing-layout)}
(when (= (count shapes) 1) (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] (for [shape shapes]
[:& layout-block {:shape shape [:& layout-block {:shape shape

View file

@ -21,3 +21,7 @@
.button-children { .button-children {
@extend .copy-button-children; @extend .copy-button-children;
} }
.copy-btn-title {
max-width: $s-28;
}

View file

@ -8,9 +8,11 @@
(:require-macros [app.main.style :as stl]) (:require-macros [app.main.style :as stl])
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.types.shape.layout :as ctl] [app.common.types.shape.layout :as ctl]
[app.main.ui.components.copy-button :refer [copy-button]] [app.main.ui.components.copy-button :refer [copy-button]]
[app.main.ui.components.title-bar :refer [title-bar]] [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.code-gen.style-css :as css]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
@ -33,12 +35,14 @@
[{:keys [objects shape]}] [{:keys [objects shape]}]
(for [property properties] (for [property properties]
(when-let [value (css/get-css-value objects shape property)] (when-let [value (css/get-css-value objects shape property)]
[:div {:class (stl/css :layout-element-row)} (let [property-name (cmm/get-css-rule-humanized property)]
[:div {:class (stl/css :global/attr-label)} (d/name property)] [:div {:class (stl/css :layout-element-row)
[:div {:class (stl/css :global/attr-value)} :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)} [:& copy-button {:data (css/get-css-property objects shape property)}
[:div {:class (stl/css :button-children)} value]]]]))) [:div {:class (stl/css :button-children)} value]]]]))))
(mf/defc layout-element-panel (mf/defc layout-element-panel
[{:keys [objects shapes]}] [{:keys [objects shapes]}]
@ -67,7 +71,8 @@
:title menu-title :title menu-title
:class (stl/css :title-spacing-layout-element)} :class (stl/css :title-spacing-layout-element)}
(when (= (count shapes) 1) (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] (for [shape shapes]
[:& layout-element-block {:shape shape [:& layout-element-block {:shape shape

View file

@ -21,3 +21,7 @@
.button-children { .button-children {
@extend .copy-button-children; @extend .copy-button-children;
} }
.copy-btn-title {
max-width: $s-28;
}

View file

@ -54,6 +54,7 @@
(when (and (seq shapes) (> (count shapes) 0)) (when (and (seq shapes) (> (count shapes) 0))
[:div {:class (stl/css :attributes-block)} [:div {:class (stl/css :attributes-block)}
[:& title-bar {:collapsable false [:& title-bar {:collapsable false
:origin :inspect
:title (tr "inspect.attributes.shadow") :title (tr "inspect.attributes.shadow")
:class (stl/css :title-spacing-shadow)}] :class (stl/css :title-spacing-shadow)}]
@ -61,4 +62,5 @@
(for [shape shapes] (for [shape shapes]
(for [shadow (:shadow shape)] (for [shadow (:shadow shape)]
[:& shadow-block {:shape shape [:& shadow-block {:shape shape
:key (dm/str "block-" (:id shape) "-shadow")
:shadow shadow}]))]]))) :shadow shadow}]))]])))

View file

@ -62,6 +62,7 @@
(when (seq shapes) (when (seq shapes)
[:div {:class (stl/css :attributes-block)} [:div {:class (stl/css :attributes-block)}
[:& title-bar {:collapsable false [:& title-bar {:collapsable false
:origin :inspect
:title (tr "inspect.attributes.stroke") :title (tr "inspect.attributes.stroke")
:class (stl/css :title-spacing-stroke)}] :class (stl/css :title-spacing-stroke)}]

View file

@ -25,3 +25,8 @@
.button-children { .button-children {
@extend .copy-button-children; @extend .copy-button-children;
} }
.attributes-content {
display: grid;
gap: $s-4;
}

View file

@ -27,19 +27,25 @@
[:& copy-button {:data (map->css value)}]] [:& copy-button {:data (map->css value)}]]
(for [[attr-key attr-value] 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)} (let [attr-name (as-> attr $
[:div {:class (stl/css :global/attr-label)} (d/name attr)] (d/name $)
[:div {:class (stl/css :global/attr-value)} (str/split $ "-")
[:& copy-button {:data (d/name value)} (str/join " " $)
[:div {:class (stl/css :button-children)} (str value)]]]])) (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 (mf/defc svg-block
[{:keys [shape]}] [{:keys [shape]}]
[:* [:*
(for [[attr-key attr-value] (:svg-attrs 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 (mf/defc svg-panel
@ -48,6 +54,7 @@
(when (seq (:svg-attrs shape)) (when (seq (:svg-attrs shape))
[:div {:class (stl/css :attributes-block)} [:div {:class (stl/css :attributes-block)}
[:& title-bar {:collapsable false [:& title-bar {:collapsable false
:origin :inspect
:title (tr "workspace.sidebar.options.svg-attrs.title") :title (tr "workspace.sidebar.options.svg-attrs.title")
:class (stl/css :title-spacing-svg)}] :class (stl/css :title-spacing-svg)}]
[:& svg-block {:shape shape}]]))) [:& svg-block {:shape shape}]])))

View file

@ -99,7 +99,7 @@
[:div {:class (stl/css :global/attr-value)} [:div {:class (stl/css :global/attr-value)}
[:& copy-button {:data (copy-style-data style :font-style)} [:& copy-button {:data (copy-style-data style :font-style)}
[:div {:class (stl/css :button-children)} [:div {:class (stl/css :button-children)}
(str (:font-style style))]]]]) (dm/str (:font-style style))]]]])
(when (:font-size style) (when (:font-size style)
[:div {:class (stl/css :text-row)} [:div {:class (stl/css :text-row)}
@ -117,7 +117,7 @@
[:div {:class (stl/css :global/attr-value)} [:div {:class (stl/css :global/attr-value)}
[:& copy-button {:data (copy-style-data style :font-weight)} [:& copy-button {:data (copy-style-data style :font-weight)}
[:div {:class (stl/css :button-children)} [:div {:class (stl/css :button-children)}
(str (:font-weight style))]]]]) (dm/str (:font-weight style))]]]])
(when (:line-height style) (when (:line-height style)
[:div {:class (stl/css :text-row)} [:div {:class (stl/css :text-row)}
@ -191,9 +191,10 @@
(when-let [shapes (seq (filter has-text? shapes))] (when-let [shapes (seq (filter has-text? shapes))]
[:div {:class (stl/css :attributes-block)} [:div {:class (stl/css :attributes-block)}
[:& title-bar {:collapsable false [:& title-bar {:collapsable false
:origin :inspect
:title (tr "inspect.attributes.typography") :title (tr "inspect.attributes.typography")
:class (stl/css :title-spacing-text)}] :class (stl/css :title-spacing-text)}]
(for [shape shapes] (for [shape shapes]
[:& text-block {:shape shape [:& text-block {:shape shape
:key (str "text-block" (:id shape))}])])) :key (dm/str "text-block" (:id shape))}])]))

View file

@ -30,7 +30,7 @@
} }
.attributes-content-row { .attributes-content-row {
max-width: $s-252; max-width: $s-240;
min-height: calc($s-2 + $s-32); min-height: calc($s-2 + $s-32);
border-radius: $br-8; border-radius: $br-8;
border: $s-1 solid var(--menu-border-color-disabled); border: $s-1 solid var(--menu-border-color-disabled);

View file

@ -19,7 +19,7 @@
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.code-block :refer [code-block]] [app.main.ui.components.code-block :refer [code-block]]
[app.main.ui.components.copy-button :refer [copy-button]] [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 :as hooks]
[app.main.ui.hooks.resize :refer [use-resize-hook]] [app.main.ui.hooks.resize :refer [use-resize-hook]]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
@ -177,10 +177,10 @@
style-size :size} style-size :size}
(use-resize-hook :code 400 100 800 :y false :bottom) (use-resize-hook :code 400 100 800 :y false :bottom)
set-style ;; set-style
(mf/use-callback ;; (mf/use-callback
(fn [value] ;; (fn [value]
(reset! style-type* value))) ;; (reset! style-type* value)))
set-markup set-markup
(mf/use-callback (mf/use-callback
@ -251,10 +251,13 @@
:rotated collapsed-css?)} :rotated collapsed-css?)}
i/arrow-refactor]] i/arrow-refactor]]
[:& select {:default-value style-type [:div {:class (stl/css :code-lang-option)}
:class (stl/css :code-lang-select) "CSS"]
:on-change set-style ;; We will have a select when we have more than one option
:options [{:label "CSS" :value "css"}]}] ;; [:& 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)} [:div {:class (stl/css :action-btns)}
[:button {:class (stl/css :expand-button) [:button {:class (stl/css :expand-button)
@ -262,6 +265,7 @@
i/code-refactor] i/code-refactor]
[:& copy-button {:data #(replace-map style-code images-data) [:& copy-button {:data #(replace-map style-code images-data)
:class (stl/css :css-copy-btn)
:on-copied on-style-copied}]]] :on-copied on-style-copied}]]]
(when-not collapsed-css? (when-not collapsed-css?
@ -285,11 +289,16 @@
:collapsabled-icon true :collapsabled-icon true
:rotated collapsed-markup?)} :rotated collapsed-markup?)}
i/arrow-refactor]] i/arrow-refactor]]
[:& select {:default-value markup-type
:class (stl/css :code-lang-select) [:& radio-buttons {:selected markup-type
:options [{:label "HTML" :value "html"} :on-change set-markup
{:label "SVG" :value "svg"}] :class (stl/css :code-lang-options)
:on-change set-markup}] :wide true
:name "listing-style"}
[:& radio-button {:value "html"
:id :html}]
[:& radio-button {:value "svg"
:id :svg}]]
[:div {:class (stl/css :action-btns)} [:div {:class (stl/css :action-btns)}
[:button {:class (stl/css :expand-button) [:button {:class (stl/css :expand-button)
@ -297,6 +306,7 @@
i/code-refactor] i/code-refactor]
[:& copy-button {:data #(replace-map markup-code images-data) [:& copy-button {:data #(replace-map markup-code images-data)
:class (stl/css :html-copy-btn)
:on-copied on-markup-copied}]]] :on-copied on-markup-copied}]]]
(when-not collapsed-markup? (when-not collapsed-markup?

View file

@ -48,8 +48,8 @@
} }
.code-row-lang { .code-row-lang {
display: flex; display: grid;
justify-content: space-between; grid-template-columns: $s-12 1fr $s-60;
gap: $s-4; gap: $s-4;
width: 100%; width: 100%;
} }
@ -61,13 +61,14 @@
} }
.action-btns { .action-btns {
display: flex; display: grid;
grid-template-columns: 1fr 1fr;
gap: $s-4; gap: $s-4;
flex: 1;
justify-content: end;
} }
.expand-button { .expand-button,
.css-copy-btn,
.html-copy-btn {
@extend .button-tertiary; @extend .button-tertiary;
height: $s-32; height: $s-32;
width: $s-28; width: $s-28;
@ -77,6 +78,9 @@
} }
} }
.code-lang-options {
max-width: $s-108;
}
.code-lang-select { .code-lang-select {
@include tabTitleTipography; @include tabTitleTipography;
width: $s-72; width: $s-72;
@ -84,6 +88,13 @@
background-color: transparent; background-color: transparent;
color: var(--menu-foreground-color-disabled); 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 { .code-row-display {
flex: 1; flex: 1;

View file

@ -14,8 +14,6 @@
left: unset; left: unset;
right: unset; right: unset;
grid-area: right-sidebar; grid-area: right-sidebar;
padding-top: $s-8;
padding-left: $s-12;
overflow: hidden; overflow: hidden;
&.viewer-code { &.viewer-code {
height: calc(100vh - $s-48); height: calc(100vh - $s-48);
@ -26,18 +24,20 @@
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: $s-8;
} }
.shape-row { .shape-row {
display: flex; display: flex;
gap: $s-8; gap: $s-8;
align-items: center; align-items: center;
margin-bottom: $s-16; height: $s-32;
} }
.layers-icon, .layers-icon,
.shape-icon { .shape-icon {
@include flexCenter; @include flexCenter;
height: $s-32;
svg { svg {
@extend .button-icon-small; @extend .button-icon-small;
stroke: var(--icon-foreground); stroke: var(--icon-foreground);
@ -46,7 +46,9 @@
.layer-title { .layer-title {
@include titleTipography; @include titleTipography;
color: $df-primary; height: $s-32;
padding: $s-8 0;
color: var(--assets-item-name-foreground-color-rest);
} }
.empty { .empty {

View file

@ -31,7 +31,7 @@
:title (uc/get-color-name color) :title (uc/get-color-name color)
:on-click select-color} :on-click select-color}
[:& cb/color-bullet {:color 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 (mf/defc palette

View file

@ -145,7 +145,8 @@
on-select-library-color on-select-library-color
(mf/use-fn (mf/use-fn
(fn [_ color] (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 on-add-library-color
(mf/use-fn (mf/use-fn

View file

@ -15,6 +15,7 @@
[app.common.types.typographies-list :as ctyl] [app.common.types.typographies-list :as ctyl]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
[app.main.data.users :as du]
[app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.libraries :as dwl]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.render :refer [component-svg]] [app.main.render :refer [component-svg]]
@ -511,3 +512,58 @@
[:& updates-tab {:file-id file-id [:& updates-tab {:file-id file-id
:file-data file-data :file-data file-data
:libraries libraries}]]]]]]]])) :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"]]]]]))

View file

@ -228,6 +228,15 @@
} }
} }
} }
.modal-v2-info {
width: $s-664;
height: fit-content;
.modal-title {
font-size: $fs-18;
}
}
} }
.item-contents { .item-contents {
@ -245,3 +254,61 @@
margin-inline: $s-4; 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;
}
}

View file

@ -16,7 +16,6 @@ $width-settings-bar-max: $s-500;
"content resize"; "content resize";
grid-template-rows: $s-52 1fr; grid-template-rows: $s-52 1fr;
grid-template-columns: 1fr 0; grid-template-columns: 1fr 0;
gap: $s-8 0;
position: relative; position: relative;
grid-area: left-sidebar; grid-area: left-sidebar;
min-width: $width-settings-bar; min-width: $width-settings-bar;
@ -85,10 +84,10 @@ $width-settings-bar-max: $s-500;
.resize-area-horiz { .resize-area-horiz {
position: absolute; position: absolute;
top: calc($s-80 + var(--height, 200px)); // top: calc($s-88 + var(--height, 200px));
left: 0; left: 0;
width: 100%; width: 100%;
height: $s-12; // height: $s-8;
border-top: $s-2 solid var(--resize-area-border-color); border-bottom: $s-2 solid var(--resize-area-border-color);
cursor: ns-resize; cursor: ns-resize;
} }

View file

@ -166,6 +166,7 @@
:collapsed (not open?) :collapsed (not open?)
:all-clickable true :all-clickable true
:on-collapsed on-collapsed :on-collapsed on-collapsed
:add-icon-gap (= 0 assets-count)
:class (stl/css-case :title-spacing open?) :class (stl/css-case :title-spacing open?)
:title title} :title title}
buttons] buttons]

View file

@ -21,8 +21,8 @@
@include flexCenter; @include flexCenter;
height: $s-16; height: $s-16;
width: $s-16; width: $s-16;
color: transparent;
fill: none; fill: none;
stroke: currentColor;
} }
} }

View file

@ -241,8 +241,7 @@
} }
.tool-window-content { .tool-window-content {
// TODO: sass variables are not being interpolated here, find why --calculated-height: calc(#{$s-136} + var(--height, #{$s-200}));
--calculated-height: calc(128px + var(--height, 200px));
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: calc(100vh - var(--calculated-height)); height: calc(100vh - var(--calculated-height));

View file

@ -17,6 +17,9 @@
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(def ^:private flatten-icon
(i/icon-xref :boolean-flatten-refactor (stl/css :flatten-icon)))
(mf/defc bool-options (mf/defc bool-options
[] []
(let [selected (mf/deref refs/selected-objects) (let [selected (mf/deref refs/selected-objects)
@ -86,9 +89,9 @@
[:button [:button
{:title (tr "workspace.shape.menu.flatten") {:title (tr "workspace.shape.menu.flatten")
:class (stl/css-case :class (stl/css-case
:flatten true :flatten-button true
:disabled disabled-flatten) :disabled disabled-flatten)
:disabled disabled-flatten :disabled disabled-flatten
:on-click flatten-objects} :on-click flatten-objects}
i/boolean-flatten-refactor]]]))) flatten-icon]]])))

View file

@ -18,29 +18,29 @@
align-items: center; align-items: center;
} }
.flatten { .flatten-button {
@extend .button-tertiary; @extend .button-tertiary;
height: $s-28; height: $s-32;
width: $s-28; width: $s-32;
border-radius: $br-8; border-radius: $br-8;
svg { --flatten-icon-foreground-color: var(--icon-foreground);
@extend .button-icon;
stroke: var(--icon-foreground);
}
&.disabled { &.disabled {
cursor: default; cursor: default;
svg { --flatten-icon-foreground-color: var(--button-foreground-color-disabled);
stroke: var(--button-foreground-color-disabled);
}
&:hover { &:hover {
background-color: var(--panel-background-color); background-color: var(--panel-background-color);
svg { --flatten-icon-foreground-color: var(--button-foreground-color-disabled);
stroke: var(--button-foreground-color-disabled);
}
} }
} }
} }
.flatten-icon {
@extend .button-icon;
stroke: var(--flatten-icon-foreground-color);
}
.boolean-radio-btn { .boolean-radio-btn {
background-color: transparent; background-color: transparent;
} }

View file

@ -182,6 +182,21 @@
(when flow (when flow
[:& flow-item {:flow flow :key (str (:id 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 (mf/defc interaction-entry
[{:keys [index shape interaction update-interaction remove-interaction]}] [{:keys [index shape interaction update-interaction remove-interaction]}]
(let [objects (deref refs/workspace-page-objects) (let [objects (deref refs/workspace-page-objects)
@ -403,12 +418,12 @@
[:div {:class (stl/css-case :element-set-options-group true [:div {:class (stl/css-case :element-set-options-group true
:open extended-open?)} :element-set-options-group-open extended-open?)}
; Summary ; Summary
[:div {:class (stl/css :interactions-summary)} [:div {:class (stl/css :interactions-summary)}
[:div {:class (stl/css-case :extend-btn true [:button {:class (stl/css-case :extend-btn true
:extended extended-open?) :extended extended-open?)
:on-click toggle-extended} :on-click toggle-extended}
i/menu-refactor] i/menu-refactor]
[:div {:class (stl/css :interactions-info) [:div {:class (stl/css :interactions-info)
@ -520,85 +535,75 @@
:active (= overlay-pos-type :center)) :active (= overlay-pos-type :center))
:data-value "center" :data-value "center"
:on-click toggle-overlay-pos-type} :on-click toggle-overlay-pos-type}
[:span {:class (stl/css :rectangle)}]] corner-center-icon]
[:button {:class (stl/css-case :direction-btn true [:button {:class (stl/css-case :direction-btn true
:top-left-btn true :top-left-btn true
:active (= overlay-pos-type :top-left)) :active (= overlay-pos-type :top-left))
:data-value "top-left" :data-value "top-left"
:on-click toggle-overlay-pos-type} :on-click toggle-overlay-pos-type}
[:span {:class (stl/css :rectangle)}]] corner-topleft-icon]
[:button {:class (stl/css-case :direction-btn true [:button {:class (stl/css-case :direction-btn true
:top-right-btn true :top-right-btn true
:active (= overlay-pos-type :top-right)) :active (= overlay-pos-type :top-right))
:data-value "top-right" :data-value "top-right"
:on-click toggle-overlay-pos-type} :on-click toggle-overlay-pos-type}
[:span {:class (stl/css :rectangle)}]] corner-topright-icon]
[:button {:class (stl/css-case :direction-btn true [:button {:class (stl/css-case :direction-btn true
:top-center-btn true :top-center-btn true
:active (= overlay-pos-type :top-center)) :active (= overlay-pos-type :top-center))
:data-value "top-center" :data-value "top-center"
:on-click toggle-overlay-pos-type} :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)}]]
[: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 [:button {:class (stl/css-case :direction-btn true
:bottom-left-btn true :bottom-left-btn true
:active (= overlay-pos-type :bottom-left)) :active (= overlay-pos-type :bottom-left))
:data-value "bottom-left" :data-value "bottom-left"
:on-click toggle-overlay-pos-type} :on-click toggle-overlay-pos-type}
[:span {:class (stl/css :rectangle)}]] corner-bottomleft-icon]
[:button {:class (stl/css-case :direction-btn true [:button {:class (stl/css-case :direction-btn true
:bottom-right-btn true :bottom-right-btn true
:active (= overlay-pos-type :bottom-right)) :active (= overlay-pos-type :bottom-right))
:data-value "bottom-right" :data-value "bottom-right"
:on-click toggle-overlay-pos-type} :on-click toggle-overlay-pos-type}
[:span {:class (stl/css :rectangle)}]] corner-bottomright-icon]
[:button {:class (stl/css-case :direction-btn true [:button {:class (stl/css-case :direction-btn true
:bottom-center-btn true :bottom-center-btn true
:active (= overlay-pos-type :bottom-center)) :active (= overlay-pos-type :bottom-center))
:data-value "bottom-center" :data-value "bottom-center"
:on-click toggle-overlay-pos-type} :on-click toggle-overlay-pos-type}
[:span {:class (stl/css :rectangle)}]]]] corner-bottom-icon]]]
;; Overlay click outside ;; Overlay click outside
[:div {:class (stl/css :property-row)}
[:div {:class (stl/css :checkbox-option)} [:ul {:class (stl/css :property-list)}
[:label {:for (str "close-" index) [:li {:class (stl/css :property-row)}
:class (stl/css-case :global/checked close-click-outside?)} [:div {:class (stl/css :checkbox-option)}
[:span {:class (stl/css-case :global/checked close-click-outside?)} [:label {:for (str "close-" index)
(when close-click-outside? :class (stl/css-case :global/checked close-click-outside?)}
i/status-tick-refactor)] [:span {:class (stl/css-case :global/checked close-click-outside?)}
(tr "workspace.options.interaction-close-outside") (when close-click-outside?
[:input {:type "checkbox" i/status-tick-refactor)]
:id (str "close-" index) (tr "workspace.options.interaction-close-outside")
:checked close-click-outside? [:input {:type "checkbox"
:on-change change-close-click-outside}]]]] :id (str "close-" index)
:checked close-click-outside?
:on-change change-close-click-outside}]]]]
;; Overlay background ;; Overlay background
[:div {:class (stl/css :property-row)} [:li {:class (stl/css :property-row)}
[:div {:class (stl/css :checkbox-option)} [:div {:class (stl/css :checkbox-option)}
[:label {:for (str "background-" index) [:label {:for (str "background-" index)
:class (stl/css-case :global/checked background-overlay?)} :class (stl/css-case :global/checked background-overlay?)}
[:span {:class (stl/css-case :global/checked background-overlay?)} [:span {:class (stl/css-case :global/checked background-overlay?)}
(when background-overlay? (when background-overlay?
i/status-tick-refactor)] i/status-tick-refactor)]
(tr "workspace.options.interaction-background") (tr "workspace.options.interaction-background")
[:input {:type "checkbox" [:input {:type "checkbox"
:id (str "background-" index) :id (str "background-" index)
:checked background-overlay? :checked background-overlay?
:on-change change-background-overlay}]]]]]) :on-change change-background-overlay}]]]]]])
(when (ctsi/has-animation? interaction) (when (ctsi/has-animation? interaction)
[:* [:*

View file

@ -96,152 +96,179 @@
@include flexColumn($s-12); @include flexColumn($s-12);
} }
.element-set-options-group { .element-set-options-group-open {
&.open { @include flexColumn;
@include flexColumn; }
.extended-options {
@include flexColumn; .extended-options {
.property-row { @include flexColumn;
@extend .attr-row; }
&.big-row {
height: 100%; .property-list {
} list-style: none;
.interaction-name { margin: 0;
@include twoLineTextEllipsis; display: grid;
@include titleTipography; row-gap: $s-16;
padding-left: $s-4; margin-block: calc(#{$s-16} - #{$s-4});
width: $s-92; }
margin: auto 0;
grid-area: name; .property-row {
color: var(--title-foreground-color); @extend .attr-row;
} height: auto;
.select-wrapper { &.big-row {
display: flex; height: 100%;
align-items: center; }
grid-area: content; .interaction-name {
.easing-select { @include twoLineTextEllipsis;
width: $s-156; @include titleTipography;
padding: 0 $s-8; padding-left: $s-4;
.dropdown-upwards { width: $s-92;
bottom: $s-36; margin: auto 0;
width: $s-156; grid-area: name;
top: unset; color: var(--title-foreground-color);
} }
} .select-wrapper {
} display: flex;
.input-element-wrapper { align-items: center;
@extend .input-element; grid-area: content;
grid-area: content; .easing-select {
} width: $s-156;
.checkbox-option { padding: 0 $s-8;
@extend .input-checkbox; .dropdown-upwards {
grid-area: content; bottom: $s-36;
} width: $s-156;
.position-btns-wrapper { top: unset;
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;
}
}
} }
} }
} }
.input-element-wrapper {
.interactions-summary { @extend .input-element;
@extend .asset-element; grid-area: content;
height: $s-44; }
padding: 0; .buttons-wrapper {
gap: $s-4; grid-area: content;
.extend-btn { .right svg {
@extend .button-tertiary; transform: rotate(-90deg);
height: 100%;
width: $s-28;
svg {
@extend .button-icon;
}
&.extended {
@extend .button-icon-selected;
}
} }
.left svg {
.remove-btn { transform: rotate(90deg);
@extend .button-tertiary; }
height: $s-32; .up svg {
width: $s-28; transform: rotate(180deg);
svg {
@extend .button-icon-small;
}
} }
} }
.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 { .flow-element {

View file

@ -23,11 +23,11 @@
(defn opacity->string (defn opacity->string
[opacity] [opacity]
(if (= opacity :multiple) (if (not= opacity :multiple)
""
(dm/str (-> opacity (dm/str (-> opacity
(d/coalesce 1) (d/coalesce 1)
(* 100))))) (* 100)))
:multiple))
(mf/defc layer-menu (mf/defc layer-menu
{::mf/wrap-props false} {::mf/wrap-props false}
@ -39,7 +39,7 @@
blocked? (:blocked values) blocked? (:blocked values)
current-blend-mode (or (:blend-mode values) :normal) current-blend-mode (or (:blend-mode values) :normal)
current-opacity (:opacity values) current-opacity (opacity->string (:opacity values))
state* (mf/use-state state* (mf/use-state
{:selected-blend-mode current-blend-mode {:selected-blend-mode current-blend-mode
@ -161,8 +161,8 @@
:title (tr "workspace.options.opacity")} :title (tr "workspace.options.opacity")}
[:span {:class (stl/css :icon)} "%"] [:span {:class (stl/css :icon)} "%"]
[:> numeric-input* [:> numeric-input*
{:value (opacity->string current-opacity) {:value current-opacity
:placeholder (tr "settings.multiple") :placeholder "--"
:on-change handle-opacity-change :on-change handle-opacity-change
:min 0 :min 0
:max 100 :max 100

View file

@ -409,14 +409,14 @@
:type "checkbox" :type "checkbox"
:value "uppercase" :value "uppercase"
:id "text-transform-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 [:& radio-button {:icon i/text-mixed-refactor
:type "checkbox" :type "checkbox"
:value "capitalize" :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/defc text-options
{::mf/wrap-props false} {::mf/wrap-props false}

View file

@ -284,19 +284,6 @@
padding: 0; padding: 0;
border: $s-1 solid var(--input-border-color); border: $s-1 solid var(--input-border-color);
position: relative; 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 { .icon {
@include flexCenter; @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 { .font-selector {
@include flexCenter; @include flexCenter;
position: absolute; position: absolute;

View file

@ -7,195 +7,202 @@
@import "refactor/common-refactor.scss"; @import "refactor/common-refactor.scss";
.shortcuts { .shortcuts {
.shortcuts-header { display: grid;
@include flexCenter; grid-template-rows: auto auto 1fr;
@include tabTitleTipography; // TODO: Fix this once we start implementing the DS.
position: relative; // We should not be doign these hardcoded calc's.
height: $s-32; height: calc(100vh - #{$s-60});
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 { .search-field {
@include flexCenter; display: flex;
flex-grow: 1; align-items: center;
color: var(--title-foreground-color-hover); height: $s-32;
} margin: $s-16 $s-12 $s-4 $s-12;
border-radius: $br-8;
.shortcuts-close-button { font-family: "worksans", sans-serif;
@extend .button-tertiary; background-color: var(--color-background-tertiary);
position: absolute; .search-box {
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;
align-items: center; align-items: center;
height: $s-32; display: flex;
margin: $s-16 $s-12 $s-4 $s-12; width: 100%;
border-radius: $br-8;
font-family: "worksans", sans-serif; .icon-wrapper {
background-color: var(--color-background-tertiary);
.search-box {
align-items: center;
display: flex; 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 { svg {
@extend .button-icon-small; @extend .button-icon-small;
stroke: var(--icon-foreground); stroke: var(--icon-foreground);
} }
} }
}
.section { .input-text {
margin: 0; @include removeInputStyle;
} height: $s-32;
.shortcuts-list { width: 100%;
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;
margin: 0; margin: 0;
padding: $s-8 0; padding: $s-4;
cursor: pointer; border: 0;
font-size: $fs-12;
.collapsed-shortcuts { 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; @include flexCenter;
svg { svg {
@extend .button-icon-small; @extend .button-icon-small;
stroke: var(--icon-foreground); 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 { .search-icon {
@include titleTipography; @include flexCenter;
color: var(--empty-message-foreground-color); width: $s-28;
margin: $s-12; 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;
}
}
}
} }
} }

View file

@ -5056,6 +5056,9 @@ msgstr "Done"
msgid "media.image" msgid "media.image"
msgstr "Image" msgstr "Image"
msgid "media.image.short"
msgstr "img"
msgid "media.solid" msgid "media.solid"
msgstr "Solid" msgstr "Solid"

View file

@ -5140,6 +5140,9 @@ msgstr "Hecho"
msgid "media.image" msgid "media.image"
msgstr "Imagen" msgstr "Imagen"
msgid "media.image.short"
msgstr "img"
msgid "media.solid" msgid "media.solid"
msgstr "Sólido" msgstr "Sólido"