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