mirror of https://github.com/penpot/penpot.git
Merge pull request #3206 from penpot/niwinz-workspace-assets-component-performance
⚡ Improve performance of workspace assets sidebar
This commit is contained in:
commit
6000dc251d
|
|
@ -7,7 +7,6 @@
|
|||
org.clojure/core.async {:mvn/version "1.6.673"}
|
||||
|
||||
com.github.luben/zstd-jni {:mvn/version "1.5.2-5"}
|
||||
org.clojure/data.fressian {:mvn/version "1.0.0"}
|
||||
|
||||
io.prometheus/simpleclient {:mvn/version "0.16.0"}
|
||||
io.prometheus/simpleclient_hotspot {:mvn/version "0.16.0"}
|
||||
|
|
|
|||
|
|
@ -8,30 +8,23 @@
|
|||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.fressian :as fres]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.logging :as l]
|
||||
[app.common.perf :as perf]
|
||||
[app.common.pprint :as pp]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.schema.desc-js-like :as smdj]
|
||||
[app.common.schema.desc-native :as smdn]
|
||||
[app.common.schema.generators :as sg]
|
||||
[app.common.spec :as us]
|
||||
[app.common.transit :as t]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.schema.generators :as sg]
|
||||
[app.common.schema.desc-native :as smdn]
|
||||
[app.common.schema.desc-js-like :as smdj]
|
||||
[app.config :as cfg]
|
||||
[app.main :as main]
|
||||
[malli.core :as m]
|
||||
[malli.error :as me]
|
||||
[malli.dev.pretty :as mdp]
|
||||
[malli.transform :as mt]
|
||||
[malli.util :as mu]
|
||||
[malli.registry :as mr]
|
||||
[malli.generator :as mg]
|
||||
[app.srepl.helpers]
|
||||
[app.srepl.main :as srepl]
|
||||
[app.util.blob :as blob]
|
||||
[app.util.fressian :as fres]
|
||||
[app.util.json :as json]
|
||||
[app.util.time :as dt]
|
||||
[clj-async-profiler.core :as prof]
|
||||
|
|
@ -48,7 +41,14 @@
|
|||
[criterium.core :as crit]
|
||||
[cuerdas.core :as str]
|
||||
[datoteka.core]
|
||||
[integrant.core :as ig]))
|
||||
[integrant.core :as ig]
|
||||
[malli.core :as m]
|
||||
[malli.dev.pretty :as mdp]
|
||||
[malli.error :as me]
|
||||
[malli.generator :as mg]
|
||||
[malli.registry :as mr]
|
||||
[malli.transform :as mt]
|
||||
[malli.util :as mu]))
|
||||
|
||||
(repl/disable-reload! (find-ns 'integrant.core))
|
||||
(set! *warn-on-reflection* true)
|
||||
|
|
|
|||
|
|
@ -8,8 +8,10 @@
|
|||
(:refer-clojure :exclude [assert])
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.files.features :as ffeat]
|
||||
[app.common.fressian :as fres]
|
||||
[app.common.logging :as l]
|
||||
[app.common.pages.migrations :as pmg]
|
||||
[app.common.spec :as us]
|
||||
|
|
@ -28,7 +30,6 @@
|
|||
[app.storage.tmp :as tmp]
|
||||
[app.tasks.file-gc]
|
||||
[app.util.blob :as blob]
|
||||
[app.util.fressian :as fres]
|
||||
[app.util.objects-map :as omap]
|
||||
[app.util.pointer-map :as pmap]
|
||||
[app.util.services :as sv]
|
||||
|
|
@ -45,8 +46,7 @@
|
|||
java.io.DataInputStream
|
||||
java.io.DataOutputStream
|
||||
java.io.InputStream
|
||||
java.io.OutputStream
|
||||
java.lang.AutoCloseable))
|
||||
java.io.OutputStream))
|
||||
|
||||
(set! *warn-on-reflection* true)
|
||||
|
||||
|
|
@ -296,7 +296,7 @@
|
|||
|
||||
(defn- retrieve-file
|
||||
[pool file-id]
|
||||
(with-open [^AutoCloseable conn (db/open pool)]
|
||||
(dm/with-open [conn (db/open pool)]
|
||||
(binding [pmap/*load-fn* (partial files/load-pointer conn file-id)]
|
||||
(some-> (db/get* conn :file {:id file-id})
|
||||
(files/decode-row)
|
||||
|
|
@ -307,7 +307,7 @@
|
|||
|
||||
(defn- retrieve-file-media
|
||||
[pool {:keys [data id] :as file}]
|
||||
(with-open [^AutoCloseable conn (db/open pool)]
|
||||
(dm/with-open [conn (db/open pool)]
|
||||
(let [ids (app.tasks.file-gc/collect-used-media data)
|
||||
ids (db/create-array conn "uuid" ids)]
|
||||
|
||||
|
|
@ -341,7 +341,7 @@
|
|||
|
||||
(defn- retrieve-libraries
|
||||
[pool ids]
|
||||
(with-open [^AutoCloseable conn (db/open pool)]
|
||||
(dm/with-open [conn (db/open pool)]
|
||||
(let [ids (db/create-array conn "uuid" ids)]
|
||||
(map :id (db/exec! pool [sql:file-libraries ids])))))
|
||||
|
||||
|
|
@ -351,7 +351,7 @@
|
|||
|
||||
(defn- retrieve-library-relations
|
||||
[pool ids]
|
||||
(with-open [^AutoCloseable conn (db/open pool)]
|
||||
(dm/with-open [conn (db/open pool)]
|
||||
(db/exec! conn [sql:file-library-rels (db/create-array conn "uuid" ids)])))
|
||||
|
||||
(defn- create-or-update-file
|
||||
|
|
@ -616,7 +616,7 @@
|
|||
(-> data
|
||||
(update :pages-index update-vals #(update % :objects omap-wrap))
|
||||
(update :pages-index update-vals pmap-wrap)
|
||||
(update :components update-vals #(update % :objects omap-wrap))
|
||||
(update :components update-vals #(d/update-when % :objects omap-wrap))
|
||||
(update :components pmap-wrap))))
|
||||
|
||||
(defmethod read-section :v1/files
|
||||
|
|
@ -834,7 +834,7 @@
|
|||
cs (volatile! nil)]
|
||||
(try
|
||||
(l/info :hint "start exportation" :export-id id)
|
||||
(with-open [^AutoCloseable output (io/output-stream output)]
|
||||
(dm/with-open [output (io/output-stream output)]
|
||||
(binding [*position* (atom 0)]
|
||||
(write-export! (assoc cfg ::output output))))
|
||||
|
||||
|
|
@ -857,7 +857,7 @@
|
|||
(defn export-to-tmpfile!
|
||||
[cfg]
|
||||
(let [path (tmp/tempfile :prefix "penpot.export.")]
|
||||
(with-open [^AutoCloseable output (io/output-stream path)]
|
||||
(dm/with-open [output (io/output-stream path)]
|
||||
(export! cfg output)
|
||||
path)))
|
||||
|
||||
|
|
@ -869,7 +869,7 @@
|
|||
(l/info :hint "import: started" :import-id id)
|
||||
(try
|
||||
(binding [*position* (atom 0)]
|
||||
(with-open [^AutoCloseable input (io/input-stream input)]
|
||||
(dm/with-open [input (io/input-stream input)]
|
||||
(read-import! (assoc cfg ::input input))))
|
||||
|
||||
(catch Throwable cause
|
||||
|
|
|
|||
|
|
@ -201,15 +201,15 @@
|
|||
::db/check-deleted? false})]
|
||||
(blob/decode (:content row))))
|
||||
|
||||
(defn load-all-pointers!
|
||||
[data]
|
||||
(defn- load-all-pointers!
|
||||
[{:keys [data] :as file}]
|
||||
(doseq [[_id page] (:pages-index data)]
|
||||
(when (pmap/pointer-map? page)
|
||||
(pmap/load! page)))
|
||||
(doseq [[_id component] (:components data)]
|
||||
(when (pmap/pointer-map? component)
|
||||
(pmap/load! component)))
|
||||
data)
|
||||
file)
|
||||
|
||||
(defn persist-pointers!
|
||||
[conn file-id]
|
||||
|
|
@ -247,22 +247,45 @@
|
|||
;; QUERY COMMANDS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn handle-file-features
|
||||
[{:keys [features] :as file} client-features]
|
||||
(defn handle-file-features!
|
||||
[conn {:keys [id features data] :as file} client-features]
|
||||
|
||||
(when (and (contains? features "components/v2")
|
||||
(not (contains? client-features "components/v2")))
|
||||
(ex/raise :type :restriction
|
||||
:code :feature-mismatch
|
||||
:feature "components/v2"
|
||||
:hint "file has 'components/v2' feature enabled but frontend didn't specifies it"))
|
||||
(cond-> file
|
||||
(and (contains? client-features "components/v2")
|
||||
(not (contains? features "components/v2")))
|
||||
(update :data ctf/migrate-to-components-v2)
|
||||
|
||||
(and (contains? features "storage/pointer-map")
|
||||
(not (contains? client-features "storage/pointer-map")))
|
||||
(process-pointers deref)))
|
||||
;; NOTE: this operation is needed because the components migration
|
||||
;; generates a new page with random id which is returned to the
|
||||
;; client; without persisting the migration this can cause that two
|
||||
;; simultaneous clients can have a different view of the file data
|
||||
;; and end persisting two pages with main components and breaking
|
||||
;; the whole file
|
||||
(let [file (if (and (contains? client-features "components/v2")
|
||||
(not (contains? features "components/v2")))
|
||||
(binding [pmap/*tracked* (atom {})]
|
||||
(let [data (ctf/migrate-to-components-v2 data)
|
||||
features (conj features "components/v2")
|
||||
modified-at (dt/now)
|
||||
features (db/create-array conn "text" features)]
|
||||
(db/update! conn :file
|
||||
{:data (blob/encode data)
|
||||
:modified-at modified-at
|
||||
:features features}
|
||||
{:id id})
|
||||
(persist-pointers! conn id)
|
||||
(-> file
|
||||
(assoc :modified-at modified-at)
|
||||
(assoc :features features)
|
||||
(assoc :data data))))
|
||||
file)]
|
||||
|
||||
(cond-> file
|
||||
(and (contains? features "storage/pointer-map")
|
||||
(not (contains? client-features "storage/pointer-map")))
|
||||
(process-pointers deref))))
|
||||
|
||||
;; --- COMMAND QUERY: get-file (by id)
|
||||
|
||||
|
|
@ -306,10 +329,18 @@
|
|||
;; here we check if client requested features are supported
|
||||
(check-features-compatibility! client-features)
|
||||
(binding [pmap/*load-fn* (partial load-pointer conn id)]
|
||||
(-> (db/get-by-id conn :file id)
|
||||
(decode-row)
|
||||
(pmg/migrate-file)
|
||||
(handle-file-features client-features))))
|
||||
(let [file (-> (db/get-by-id conn :file id)
|
||||
(decode-row)
|
||||
(pmg/migrate-file))
|
||||
|
||||
file (handle-file-features! conn file client-features)]
|
||||
|
||||
;; NOTE: if migrations are applied, probably new pointers generated so
|
||||
;; instead of persiting them on each get-file, we just resolve them until
|
||||
;; user updates the file and permanently persists the new pointers
|
||||
(cond-> file
|
||||
(pmg/migrated? file)
|
||||
(process-pointers deref)))))
|
||||
|
||||
(defn get-minimal-file
|
||||
[{:keys [::db/pool] :as cfg} id]
|
||||
|
|
@ -543,7 +574,7 @@
|
|||
|
||||
;; --- COMMAND QUERY: get-file-libraries
|
||||
|
||||
(def ^:private sql:file-libraries
|
||||
(def ^:private sql:get-file-libraries
|
||||
"WITH RECURSIVE libs AS (
|
||||
SELECT fl.*, flr.synced_at
|
||||
FROM file AS fl
|
||||
|
|
@ -556,7 +587,6 @@
|
|||
JOIN libs AS l ON (flr.file_id = l.id)
|
||||
)
|
||||
SELECT l.id,
|
||||
l.data,
|
||||
l.features,
|
||||
l.project_id,
|
||||
l.created_at,
|
||||
|
|
@ -569,33 +599,24 @@
|
|||
WHERE l.deleted_at IS NULL OR l.deleted_at > now();")
|
||||
|
||||
(defn get-file-libraries
|
||||
[conn file-id client-features]
|
||||
(check-features-compatibility! client-features)
|
||||
(->> (db/exec! conn [sql:file-libraries file-id])
|
||||
(map decode-row)
|
||||
(map #(assoc % :is-indirect false))
|
||||
(map (fn [{:keys [id] :as row}]
|
||||
(binding [pmap/*load-fn* (partial load-pointer conn id)]
|
||||
(-> row
|
||||
;; TODO: re-enable this dissoc and replace call
|
||||
;; with other that gets files individually
|
||||
;; See task https://tree.taiga.io/project/penpot/task/4904
|
||||
;; (update :data dissoc :pages-index)
|
||||
(handle-file-features client-features)))))
|
||||
(vec)))
|
||||
[conn file-id]
|
||||
(into []
|
||||
(comp
|
||||
(map #(assoc % :is-indirect false))
|
||||
(map decode-row))
|
||||
(db/exec! conn [sql:get-file-libraries file-id])))
|
||||
|
||||
(s/def ::get-file-libraries
|
||||
(s/keys :req [::rpc/profile-id]
|
||||
:req-un [::file-id]
|
||||
:opt-un [::features]))
|
||||
:req-un [::file-id]))
|
||||
|
||||
(sv/defmethod ::get-file-libraries
|
||||
"Get libraries used by the specified file."
|
||||
{::doc/added "1.17"}
|
||||
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id features]}]
|
||||
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id]}]
|
||||
(dm/with-open [conn (db/open pool)]
|
||||
(check-read-permissions! conn profile-id file-id)
|
||||
(get-file-libraries conn file-id features)))
|
||||
(get-file-libraries conn file-id)))
|
||||
|
||||
|
||||
;; --- COMMAND QUERY: Files that use this File library
|
||||
|
|
@ -719,31 +740,38 @@
|
|||
{:is-shared is-shared}
|
||||
{:id id}))
|
||||
|
||||
(def sql:get-referenced-files
|
||||
"SELECT f.id
|
||||
FROM file_library_rel AS flr
|
||||
INNER JOIN file AS f ON (f.id = flr.file_id)
|
||||
WHERE flr.library_file_id = ?
|
||||
ORDER BY f.created_at ASC;")
|
||||
|
||||
(defn absorb-library
|
||||
"Find all files using a shared library, and absorb all library assets
|
||||
into the file local libraries"
|
||||
[conn {:keys [id] :as params}]
|
||||
(let [library (db/get-by-id conn :file id)]
|
||||
(when (:is-shared library)
|
||||
(let [ldata (-> library decode-row pmg/migrate-file :data)]
|
||||
(binding [pmap/*load-fn* (partial load-pointer conn id)]
|
||||
(load-all-pointers! ldata))
|
||||
|
||||
(->> (db/query conn :file-library-rel {:library-file-id id})
|
||||
(map :file-id)
|
||||
(keep #(db/get-by-id conn :file % ::db/check-deleted? false))
|
||||
(map decode-row)
|
||||
(map pmg/migrate-file)
|
||||
(run! (fn [{:keys [id data revn] :as file}]
|
||||
(binding [pmap/*tracked* (atom {})
|
||||
pmap/*load-fn* (partial load-pointer conn id)]
|
||||
(let [data (ctf/absorb-assets data ldata)]
|
||||
(db/update! conn :file
|
||||
{:revn (inc revn)
|
||||
:data (blob/encode data)
|
||||
:modified-at (dt/now)}
|
||||
{:id id}))
|
||||
(persist-pointers! conn id)))))))))
|
||||
(let [ldata (binding [pmap/*load-fn* (partial load-pointer conn id)]
|
||||
(-> library decode-row load-all-pointers! pmg/migrate-file :data))
|
||||
rows (db/exec! conn [sql:get-referenced-files id])]
|
||||
(doseq [file-id (map :id rows)]
|
||||
(binding [pmap/*load-fn* (partial load-pointer conn file-id)
|
||||
pmap/*tracked* (atom {})]
|
||||
(let [file (-> (db/get-by-id conn :file file-id
|
||||
::db/check-deleted? false
|
||||
::db/remove-deleted? false)
|
||||
(decode-row)
|
||||
(load-all-pointers!)
|
||||
(pmg/migrate-file))
|
||||
data (ctf/absorb-assets (:data file) ldata)]
|
||||
(db/update! conn :file
|
||||
{:revn (inc (:revn file))
|
||||
:data (blob/encode data)
|
||||
:modified-at (dt/now)}
|
||||
{:id file-id})
|
||||
(persist-pointers! conn file-id))))))))
|
||||
|
||||
(s/def ::set-file-shared
|
||||
(s/keys :req [::rpc/profile-id]
|
||||
|
|
|
|||
|
|
@ -27,9 +27,8 @@
|
|||
[conn file-id profile-id features]
|
||||
(let [file (files/get-file conn file-id features)
|
||||
project (get-project conn (:project-id file))
|
||||
libs (files/get-file-libraries conn file-id features)
|
||||
libs (files/get-file-libraries conn file-id)
|
||||
users (comments/get-file-comments-users conn file-id profile-id)
|
||||
|
||||
links (->> (db/query conn :share-link {:file-id file-id})
|
||||
(mapv (fn [row]
|
||||
(-> row
|
||||
|
|
|
|||
|
|
@ -275,7 +275,8 @@
|
|||
[{:keys [::db/conn] :as cfg} {:keys [id data revn modified-at features] :as file}]
|
||||
(l/debug :hint "processing file" :id id :modified-at modified-at)
|
||||
|
||||
(binding [pmap/*load-fn* (partial files/load-pointer conn id)]
|
||||
(binding [pmap/*load-fn* (partial files/load-pointer conn id)
|
||||
pmap/*tracked* (atom {})]
|
||||
(let [data (-> (blob/decode data)
|
||||
(assoc :id id)
|
||||
(pmg/migrate-data))]
|
||||
|
|
@ -291,4 +292,6 @@
|
|||
;; Mark file as trimmed
|
||||
(db/update! conn :file
|
||||
{:has-media-trimmed true}
|
||||
{:id id}))))
|
||||
{:id id})
|
||||
|
||||
(files/persist-pointers! conn id))))
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@
|
|||
"A generic blob storage encoding. Mainly used for page data, page
|
||||
options and txlog payload storage."
|
||||
(:require
|
||||
[app.common.fressian :as fres]
|
||||
[app.common.transit :as t]
|
||||
[app.config :as cf]
|
||||
[app.util.fressian :as fres])
|
||||
[app.config :as cf])
|
||||
(:import
|
||||
com.github.luben.zstd.Zstd
|
||||
java.io.ByteArrayInputStream
|
||||
|
|
|
|||
|
|
@ -16,10 +16,9 @@
|
|||
properly from each value."
|
||||
|
||||
(:require
|
||||
;; [app.common.logging :as l]
|
||||
[app.common.fressian :as fres]
|
||||
[app.common.transit :as t]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.util.fressian :as fres]
|
||||
[clojure.core :as c])
|
||||
(:import
|
||||
clojure.lang.Counted
|
||||
|
|
|
|||
|
|
@ -36,10 +36,10 @@
|
|||
"
|
||||
|
||||
(:require
|
||||
[app.common.fressian :as fres]
|
||||
[app.common.logging :as l]
|
||||
[app.common.transit :as t]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.util.fressian :as fres]
|
||||
[app.util.time :as dt]
|
||||
[clojure.core :as c])
|
||||
(:import
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@
|
|||
|
||||
(ns backend-tests.util-objects-map-test
|
||||
(:require
|
||||
[app.common.fressian :as fres]
|
||||
[app.common.schema.generators :as sg]
|
||||
[app.common.transit :as transit]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.util.fressian :as fres]
|
||||
[app.util.objects-map :as omap]
|
||||
[backend-tests.helpers :as th]
|
||||
[clojure.pprint :refer [pprint]]
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@
|
|||
|
||||
(ns backend-tests.util-pointer-map-test
|
||||
(:require
|
||||
[backend-tests.helpers :as th]
|
||||
[app.common.fressian :as fres]
|
||||
[app.common.spec :as us]
|
||||
[app.common.transit :as transit]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.util.fressian :as fres]
|
||||
[app.util.pointer-map :as pmap]
|
||||
[backend-tests.helpers :as th]
|
||||
[clojure.pprint :refer [pprint]]
|
||||
[clojure.spec.alpha :as s]
|
||||
[clojure.test :as t]
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
{org.clojure/clojure {:mvn/version "1.11.1"}
|
||||
org.clojure/data.json {:mvn/version "2.4.0"}
|
||||
org.clojure/tools.cli {:mvn/version "1.0.214"}
|
||||
metosin/jsonista {:mvn/version "0.3.7"}
|
||||
org.clojure/clojurescript {:mvn/version "1.11.60"}
|
||||
org.clojure/test.check {:mvn/version "1.1.1"}
|
||||
org.clojure/data.fressian {:mvn/version "1.0.0"}
|
||||
|
||||
;; Logging
|
||||
org.apache.logging.log4j/log4j-api {:mvn/version "2.19.0"}
|
||||
|
|
@ -18,6 +18,7 @@
|
|||
selmer/selmer {:mvn/version "1.12.55"}
|
||||
criterium/criterium {:mvn/version "0.4.6"}
|
||||
|
||||
metosin/jsonista {:mvn/version "0.3.7"}
|
||||
metosin/malli {:mvn/version "0.11.0"}
|
||||
|
||||
expound/expound {:mvn/version "0.9.0"}
|
||||
|
|
@ -30,14 +31,14 @@
|
|||
{:git/tag "11.0-alpha13"
|
||||
:git/sha "f6cab38"
|
||||
:git/url "https://github.com/funcool/promesa.git"}
|
||||
funcool/datoteka {:mvn/version "3.0.66"
|
||||
:exclusions [funcool/promesa]}
|
||||
|
||||
lambdaisland/uri {:mvn/version "1.13.95"
|
||||
:exclusions [org.clojure/data.json]}
|
||||
|
||||
frankiesardo/linked {:mvn/version "1.3.0"}
|
||||
|
||||
funcool/datoteka {:mvn/version "3.0.66"
|
||||
:exclusions [funcool/promesa]}
|
||||
com.sun.mail/jakarta.mail {:mvn/version "2.0.1"}
|
||||
org.la4j/la4j {:mvn/version "0.6.0"}
|
||||
|
||||
|
|
|
|||
|
|
@ -580,8 +580,10 @@
|
|||
|
||||
(defn nilv
|
||||
"Returns a default value if the given value is nil"
|
||||
[v default]
|
||||
(if (some? v) v default))
|
||||
([default]
|
||||
(map #(nilv % default)))
|
||||
([v default]
|
||||
(if (some? v) v default)))
|
||||
|
||||
(defn num?
|
||||
"Checks if a value `val` is a number but not an Infinite or NaN"
|
||||
|
|
|
|||
|
|
@ -153,6 +153,22 @@
|
|||
(throw (ex-info hint# params#)))))))))
|
||||
|
||||
(defmacro verify!
|
||||
[& params]
|
||||
(binding [*assert* true]
|
||||
`(assert! ~@params)))
|
||||
([expr]
|
||||
`(assert! nil ~expr))
|
||||
([hint expr]
|
||||
(let [hint (cond
|
||||
(vector? hint)
|
||||
`(str/ffmt ~@hint)
|
||||
|
||||
(some? hint)
|
||||
hint
|
||||
|
||||
:else
|
||||
(str "expr assert: " (pr-str expr)))]
|
||||
`(binding [*assert-context* true]
|
||||
(when-not ~expr
|
||||
(let [hint# ~hint
|
||||
params# {:type :assertion
|
||||
:code :expr-validation
|
||||
:hint hint#}]
|
||||
(throw (ex-info hint# params#))))))))
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.util.fressian
|
||||
(ns app.common.fressian
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
|
|
@ -240,25 +240,28 @@
|
|||
;; Changes Processing Impl
|
||||
|
||||
(defn validate-shapes!
|
||||
[data objects items]
|
||||
(letfn [(validate-shape! [[page-id {:keys [id] :as shape}]]
|
||||
(when-not (= shape (dm/get-in data [:pages-index page-id :objects id]))
|
||||
;; If object has changed verify is correct
|
||||
(dm/verify! (cts/shape? shape))))]
|
||||
[data-old data-new items]
|
||||
(letfn [(validate-shape! [[page-id id]]
|
||||
(let [shape-old (dm/get-in data-old [:pages-index page-id :objects id])
|
||||
shape-new (dm/get-in data-new [:pages-index page-id :objects id])]
|
||||
|
||||
(let [lookup (d/getf objects)]
|
||||
(->> (into #{} (map :page-id) items)
|
||||
(mapcat (fn [page-id]
|
||||
(filter #(= page-id (:page-id %)) items)))
|
||||
(mapcat (fn [{:keys [type id page-id] :as item}]
|
||||
(sequence
|
||||
(comp (keep lookup)
|
||||
(map (partial vector page-id)))
|
||||
(case type
|
||||
(:add-obj :mod-obj :del-obj) (cons id nil)
|
||||
(:mov-objects :reg-objects) (:shapes item)
|
||||
nil))))
|
||||
(run! validate-shape!)))))
|
||||
;; If object has changed verify is correct
|
||||
(when (and (some? shape-old)
|
||||
(some? shape-new)
|
||||
(not= shape-old shape-new))
|
||||
(dm/verify! (cts/shape? shape-new)))))]
|
||||
|
||||
(->> (into #{} (map :page-id) items)
|
||||
(mapcat (fn [page-id]
|
||||
(filter #(= page-id (:page-id %)) items)))
|
||||
(mapcat (fn [{:keys [type id page-id] :as item}]
|
||||
(sequence
|
||||
(map (partial vector page-id))
|
||||
(case type
|
||||
(:add-obj :mod-obj :del-obj) (cons id nil)
|
||||
(:mov-objects :reg-objects) (:shapes item)
|
||||
nil))))
|
||||
(run! validate-shape!))))
|
||||
|
||||
(defmulti process-change (fn [_ change] (:type change)))
|
||||
(defmulti process-operation (fn [_ _ op] (:type op)))
|
||||
|
|
|
|||
|
|
@ -37,10 +37,16 @@
|
|||
(reduce migrate-fn data (range (:version data 0) to-version))))))
|
||||
|
||||
(defn migrate-file
|
||||
[file]
|
||||
(-> file
|
||||
(update :data assoc :id (:id file))
|
||||
(update :data migrate-data)))
|
||||
[{:keys [id data] :as file}]
|
||||
(let [data (assoc data :id id)]
|
||||
(-> file
|
||||
(assoc ::orig-version (:version data))
|
||||
(assoc :data (migrate-data data)))))
|
||||
|
||||
(defn migrated?
|
||||
[{:keys [data] :as file}]
|
||||
(> (:version data)
|
||||
(::orig-version file)))
|
||||
|
||||
;; Default handler, noop
|
||||
(defmethod migrate :default [data] data)
|
||||
|
|
|
|||
|
|
@ -30,25 +30,19 @@
|
|||
(assoc component :modified-at (dt/now)))
|
||||
|
||||
(defn add-component
|
||||
[file-data {:keys [id name path main-instance-id main-instance-page shapes]}]
|
||||
(let [components-v2 (dm/get-in file-data [:options :components-v2])
|
||||
wrap-object-fn feat/*wrap-with-objects-map-fn*]
|
||||
(cond-> file-data
|
||||
:always
|
||||
(assoc-in [:components id]
|
||||
(touch {:id id
|
||||
:name name
|
||||
:path path}))
|
||||
|
||||
(not components-v2)
|
||||
(assoc-in [:components id :objects]
|
||||
(->> shapes
|
||||
(d/index-by :id)
|
||||
(wrap-object-fn)))
|
||||
components-v2
|
||||
(update-in [:components id] assoc
|
||||
[fdata {:keys [id name path main-instance-id main-instance-page shapes]}]
|
||||
(let [components-v2 (dm/get-in fdata [:options :components-v2])
|
||||
fdata (update fdata :components assoc id (touch {:id id :name name :path path}))]
|
||||
(if components-v2
|
||||
(update-in fdata [:components id] assoc
|
||||
:main-instance-id main-instance-id
|
||||
:main-instance-page main-instance-page))))
|
||||
:main-instance-page main-instance-page)
|
||||
|
||||
(let [wrap-object-fn feat/*wrap-with-objects-map-fn*]
|
||||
(assoc-in fdata [:components id :objects]
|
||||
(->> shapes
|
||||
(d/index-by :id)
|
||||
(wrap-object-fn)))))))
|
||||
|
||||
(defn mod-component
|
||||
[file-data {:keys [id name path objects annotation]}]
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@
|
|||
[app.common.types.component :as ctk]
|
||||
[app.common.types.components-list :as ctkl]
|
||||
[app.common.types.pages-list :as ctpl]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.uuid :as uuid]))
|
||||
|
||||
|
|
@ -34,7 +33,7 @@
|
|||
[:path {:optional true} [:maybe :string]]
|
||||
[:modified-at {:optional true} ::sm/inst]
|
||||
[:objects {:optional true}
|
||||
[:map-of {:gen/max 10} ::sm/uuid ::cts/shape]]])
|
||||
[:map-of {:gen/max 10} ::sm/uuid :map]]])
|
||||
|
||||
(def container?
|
||||
(sm/pred-fn ::container))
|
||||
|
|
@ -68,6 +67,7 @@
|
|||
|
||||
(defn get-shape
|
||||
[container shape-id]
|
||||
|
||||
(dm/assert!
|
||||
"expected valid container"
|
||||
(container? container))
|
||||
|
|
@ -173,7 +173,8 @@
|
|||
(make-component-instance container component library-data position components-v2 {}))
|
||||
|
||||
([container component library-data position components-v2
|
||||
{:keys [main-instance? force-id] :or {main-instance? false force-id nil}}]
|
||||
{:keys [main-instance? force-id force-frame-id]
|
||||
:or {main-instance? false force-id nil force-frame-id nil}}]
|
||||
(let [component-page (when components-v2
|
||||
(ctpl/get-page library-data (:main-instance-page component)))
|
||||
component-shape (if components-v2
|
||||
|
|
@ -188,10 +189,11 @@
|
|||
objects (:objects container)
|
||||
unames (volatile! (common/retrieve-used-names objects))
|
||||
|
||||
frame-id (ctst/frame-id-by-position objects
|
||||
(gpt/add orig-pos delta)
|
||||
{:skip-components? true}) ; It'd be weird to make an instance
|
||||
frame-ids-map (volatile! {}) ; inside other component
|
||||
frame-id (or force-frame-id
|
||||
(ctst/frame-id-by-position objects
|
||||
(gpt/add orig-pos delta)
|
||||
{:skip-components? true}))
|
||||
frame-ids-map (volatile! {})
|
||||
|
||||
update-new-shape
|
||||
(fn [new-shape original-shape]
|
||||
|
|
|
|||
|
|
@ -326,87 +326,88 @@
|
|||
main instances for all components there and remove shapes from library components.
|
||||
Mark the file with the :components-v2 option."
|
||||
[file-data]
|
||||
(let [components (ctkl/components-seq file-data)]
|
||||
(if (or (empty? components)
|
||||
(dm/get-in file-data [:options :components-v2]))
|
||||
(assoc-in file-data [:options :components-v2] true)
|
||||
(let [grid-gap 50
|
||||
(let [migrated? (dm/get-in file-data [:options :components-v2])]
|
||||
(if migrated?
|
||||
file-data
|
||||
(let [components (ctkl/components-seq file-data)]
|
||||
(if (empty? components)
|
||||
(assoc-in file-data [:options :components-v2] true)
|
||||
(let [grid-gap 50
|
||||
[file-data page-id start-pos]
|
||||
(get-or-add-library-page file-data grid-gap)
|
||||
|
||||
[file-data page-id start-pos]
|
||||
(get-or-add-library-page file-data grid-gap)
|
||||
add-main-instance
|
||||
(fn [file-data component position]
|
||||
(let [page (ctpl/get-page file-data page-id)
|
||||
|
||||
add-main-instance
|
||||
(fn [file-data component position]
|
||||
(let [page (ctpl/get-page file-data page-id)
|
||||
[new-shape new-shapes]
|
||||
(ctn/make-component-instance page
|
||||
component
|
||||
file-data
|
||||
position
|
||||
false
|
||||
{:main-instance? true
|
||||
:force-frame-id uuid/zero})
|
||||
|
||||
[new-shape new-shapes]
|
||||
(ctn/make-component-instance page
|
||||
component
|
||||
file-data
|
||||
position
|
||||
false
|
||||
{:main-instance? true})
|
||||
add-shapes
|
||||
(fn [page]
|
||||
(reduce (fn [page shape]
|
||||
(ctst/add-shape (:id shape)
|
||||
shape
|
||||
page
|
||||
(:frame-id shape)
|
||||
(:parent-id shape)
|
||||
nil ; <- As shapes are ordered, we can safely add each
|
||||
true)) ; one at the end of the parent's children list.
|
||||
page
|
||||
new-shapes))
|
||||
|
||||
add-shapes
|
||||
(fn [page]
|
||||
(reduce (fn [page shape]
|
||||
(ctst/add-shape (:id shape)
|
||||
shape
|
||||
page
|
||||
(:frame-id shape)
|
||||
(:parent-id shape)
|
||||
nil ; <- As shapes are ordered, we can safely add each
|
||||
true)) ; one at the end of the parent's children list.
|
||||
page
|
||||
new-shapes))
|
||||
update-component
|
||||
(fn [component]
|
||||
(-> component
|
||||
(assoc :main-instance-id (:id new-shape)
|
||||
:main-instance-page page-id)
|
||||
(dissoc :objects)))]
|
||||
|
||||
update-component
|
||||
(fn [component]
|
||||
(-> component
|
||||
(assoc :main-instance-id (:id new-shape)
|
||||
:main-instance-page page-id)
|
||||
(dissoc :objects)))]
|
||||
(-> file-data
|
||||
(ctpl/update-page page-id add-shapes)
|
||||
(ctkl/update-component (:id component) update-component))))
|
||||
|
||||
(-> file-data
|
||||
(ctpl/update-page page-id add-shapes)
|
||||
(ctkl/update-component (:id component) update-component))))
|
||||
add-instance-grid
|
||||
(fn [file-data components]
|
||||
(let [position-seq (ctst/generate-shape-grid
|
||||
(map (partial get-component-root file-data) components)
|
||||
start-pos
|
||||
grid-gap)]
|
||||
(loop [file-data file-data
|
||||
components-seq (seq components)
|
||||
position-seq position-seq]
|
||||
(let [component (first components-seq)
|
||||
position (first position-seq)]
|
||||
(if (nil? component)
|
||||
file-data
|
||||
(recur (add-main-instance file-data component position)
|
||||
(rest components-seq)
|
||||
(rest position-seq)))))))
|
||||
|
||||
add-instance-grid
|
||||
(fn [file-data components]
|
||||
(let [position-seq (ctst/generate-shape-grid
|
||||
(map (partial get-component-root file-data) components)
|
||||
start-pos
|
||||
grid-gap)]
|
||||
(loop [file-data file-data
|
||||
components-seq (seq components)
|
||||
position-seq position-seq]
|
||||
(let [component (first components-seq)
|
||||
position (first position-seq)]
|
||||
(if (nil? component)
|
||||
file-data
|
||||
(recur (add-main-instance file-data component position)
|
||||
(rest components-seq)
|
||||
(rest position-seq)))))))
|
||||
root-to-board
|
||||
(fn [shape]
|
||||
(cond-> shape
|
||||
(and (ctk/instance-root? shape)
|
||||
(cph/frame-shape? shape))
|
||||
(assoc :fills []
|
||||
:hide-in-viewer true
|
||||
:rx 0
|
||||
:ry 0)))
|
||||
|
||||
root-to-board
|
||||
(fn [shape]
|
||||
(cond-> shape
|
||||
(and (ctk/instance-head? shape)
|
||||
(not= (:type shape) :frame))
|
||||
(assoc :type :frame
|
||||
:fills []
|
||||
:hide-in-viewer true
|
||||
:rx 0
|
||||
:ry 0)))
|
||||
roots-to-board
|
||||
(fn [page]
|
||||
(update page :objects update-vals root-to-board))]
|
||||
|
||||
roots-to-board
|
||||
(fn [page]
|
||||
(update page :objects update-vals root-to-board))]
|
||||
|
||||
(-> file-data
|
||||
(add-instance-grid (sort-by :name components))
|
||||
(update :pages-index update-vals roots-to-board)
|
||||
(assoc-in [:options :components-v2] true))))))
|
||||
(-> file-data
|
||||
(add-instance-grid (sort-by :name components))
|
||||
(update :pages-index update-vals roots-to-board)
|
||||
(assoc-in [:options :components-v2] true))))))))
|
||||
|
||||
(defn- absorb-components
|
||||
[file-data used-components library-data]
|
||||
|
|
|
|||
|
|
@ -31,7 +31,8 @@
|
|||
[:type [:= "paragraph"]]
|
||||
[:key {:optional true} :string]
|
||||
[:fills {:optional true}
|
||||
[:vector {:gen/max 2} ::shape/fill]]
|
||||
[:maybe
|
||||
[:vector {:gen/max 2} ::shape/fill]]]
|
||||
[:font-family {:optional true} :string]
|
||||
[:font-size {:optional true} :string]
|
||||
[:font-style {:optional true} :string]
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@
|
|||
funcool/tubax {:mvn/version "2021.05.20-0"}
|
||||
|
||||
funcool/rumext
|
||||
{:git/tag "v2.1"
|
||||
:git/sha "6343102"
|
||||
{:git/tag "v2.3"
|
||||
:git/sha "09942e7"
|
||||
:git/url "https://github.com/funcool/rumext.git"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -98,3 +98,6 @@
|
|||
(fn [_ _ old-value current-value]
|
||||
(when (not= old-value current-value)
|
||||
(reinit))))
|
||||
|
||||
(set! (.-stackTraceLimit js/Error) 50)
|
||||
|
||||
|
|
|
|||
|
|
@ -43,4 +43,3 @@
|
|||
(watch [_ _ _]
|
||||
(->> (rp/cmd! :delete-share-link {:id id})
|
||||
(rx/ignore)))))
|
||||
|
||||
|
|
|
|||
|
|
@ -237,34 +237,6 @@
|
|||
(->> (rp/cmd! :get-team-shared-files {:team-id team-id})
|
||||
(rx/map shared-files-fetched))))))
|
||||
|
||||
;; --- EVENT: Get files that use this shared-file
|
||||
|
||||
(defn clean-temp-shared
|
||||
[]
|
||||
(ptk/reify ::clean-temp-shared
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:dashboard-local :files-with-shared] nil))))
|
||||
|
||||
(defn library-using-files-fetched
|
||||
[files]
|
||||
(ptk/reify ::library-using-files-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [files (d/index-by :id files)]
|
||||
(assoc-in state [:dashboard-local :files-with-shared] files)))))
|
||||
|
||||
(defn fetch-libraries-using-files
|
||||
[files]
|
||||
(ptk/reify ::fetch-library-using-files
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(->> (rx/from files)
|
||||
(rx/map :id)
|
||||
(rx/mapcat #(rp/cmd! :get-library-file-references {:file-id %}))
|
||||
(rx/reduce into [])
|
||||
(rx/map library-using-files-fetched)))))
|
||||
|
||||
;; --- EVENT: recent-files
|
||||
|
||||
(defn recent-files-fetched
|
||||
|
|
|
|||
|
|
@ -60,8 +60,8 @@
|
|||
(c/update ::modal merge options)))))
|
||||
|
||||
(defn show!
|
||||
[type props]
|
||||
(st/emit! (show type props)))
|
||||
([props] (st/emit! (show props)))
|
||||
([type props] (st/emit! (show type props))))
|
||||
|
||||
(defn update-props!
|
||||
[type props]
|
||||
|
|
|
|||
|
|
@ -78,7 +78,6 @@
|
|||
[beicon.core :as rx]
|
||||
[cljs.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[linked.core :as lks]
|
||||
[potok.core :as ptk]))
|
||||
|
||||
(def default-workspace-local {:zoom 1})
|
||||
|
|
@ -142,113 +141,86 @@
|
|||
(let [data (d/removem (comp t/pointer? val) data)]
|
||||
(assoc state :workspace-data data)))))
|
||||
|
||||
(defn- workspace-data-pointers-loaded
|
||||
[pdata]
|
||||
(ptk/reify ::workspace-data-pointers-loaded
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :workspace-data merge pdata))))
|
||||
(defn- resolve-file-data
|
||||
[file-id {:keys [pages-index] :as data}]
|
||||
(letfn [(resolve-pointer [[key val :as kv]]
|
||||
(if (t/pointer? val)
|
||||
(->> (rp/cmd! :get-file-fragment {:file-id file-id :fragment-id @val})
|
||||
(rx/map #(get % :content))
|
||||
(rx/map #(vector key %)))
|
||||
(rx/of kv)))
|
||||
|
||||
(resolve-pointers [coll]
|
||||
(->> (rx/from (seq coll))
|
||||
(rx/merge-map resolve-pointer)
|
||||
(rx/reduce conj {})))]
|
||||
|
||||
(->> (rx/zip (resolve-pointers data)
|
||||
(resolve-pointers pages-index))
|
||||
(rx/take 1)
|
||||
(rx/map (fn [[data pages-index]]
|
||||
(assoc data :pages-index pages-index))))))
|
||||
|
||||
(defn- bundle-fetched
|
||||
[features [{:keys [id data] :as file} thumbnails project users comments-users]]
|
||||
(letfn [(resolve-pointer [file-id [key pointer]]
|
||||
(->> (rp/cmd! :get-file-fragment {:file-id file-id :fragment-id @pointer})
|
||||
(rx/map :content)
|
||||
(rx/map #(vector key %))))
|
||||
(ptk/reify ::bundle-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(assoc :workspace-thumbnails thumbnails)
|
||||
(assoc :workspace-file (dissoc file :data))
|
||||
(assoc :workspace-project project)
|
||||
(assoc :current-team-id (:team-id project))
|
||||
(assoc :users (d/index-by :id users))
|
||||
(assoc :current-file-comments-users (d/index-by :id comments-users))))
|
||||
|
||||
(resolve-pointers [file-id coll]
|
||||
(->> (rx/from (seq coll))
|
||||
(rx/merge-map (partial resolve-pointer file-id))
|
||||
(rx/reduce conj {})))]
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ stream]
|
||||
(let [team-id (:team-id project)
|
||||
stoper (rx/filter (ptk/type? ::bundle-fetched) stream)]
|
||||
(->> (rx/concat
|
||||
;; Initialize notifications
|
||||
(rx/of (dwn/initialize team-id id)
|
||||
(dwsl/initialize))
|
||||
|
||||
(ptk/reify ::bundle-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(assoc :workspace-thumbnails thumbnails)
|
||||
(assoc :workspace-file (dissoc file :data))
|
||||
(assoc :workspace-project project)
|
||||
(assoc :current-team-id (:team-id project))
|
||||
(assoc :users (d/index-by :id users))
|
||||
(assoc :current-file-comments-users (d/index-by :id comments-users))))
|
||||
;; Load team fonts. We must ensure custom fonts are
|
||||
;; fully loadad before mark workspace as initialized
|
||||
(rx/merge
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? :app.main.data.fonts/team-fonts-loaded))
|
||||
(rx/take 1)
|
||||
(rx/ignore))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ stream]
|
||||
(let [team-id (:team-id project)
|
||||
stoper (rx/filter (ptk/type? ::bundle-fetched) stream)]
|
||||
(->> (rx/concat
|
||||
;; Initialize notifications
|
||||
(rx/of (dwn/initialize team-id id)
|
||||
(dwsl/initialize))
|
||||
(rx/of (df/load-team-fonts team-id))
|
||||
|
||||
;; Load team fonts. We must ensure custom fonts are fully loadad before starting the workspace load
|
||||
(rx/merge
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? :app.main.data.fonts/team-fonts-loaded))
|
||||
(rx/take 1)
|
||||
(rx/ignore))
|
||||
(rx/of (df/load-team-fonts team-id)))
|
||||
;; Load main file
|
||||
(->> (resolve-file-data id data)
|
||||
(rx/mapcat (fn [{:keys [pages-index] :as data}]
|
||||
(->> (rx/from (seq pages-index))
|
||||
(rx/mapcat
|
||||
(fn [[id page]]
|
||||
(let [page (update page :objects ctst/start-page-index)]
|
||||
(->> (uw/ask! {:cmd :initialize-page-index :page page})
|
||||
(rx/map (fn [_] [id page]))))))
|
||||
(rx/reduce conj {})
|
||||
(rx/map (fn [pages-index]
|
||||
(assoc data :pages-index pages-index))))))
|
||||
(rx/map workspace-data-loaded))
|
||||
|
||||
(rx/merge
|
||||
;; Load all pages, independently if they are pointers or already
|
||||
;; resolved values.
|
||||
(->> (rx/from (seq (:pages-index data)))
|
||||
(rx/merge-map
|
||||
(fn [[_ page :as kp]]
|
||||
(if (t/pointer? page)
|
||||
(resolve-pointer id kp)
|
||||
(rx/of kp))))
|
||||
(rx/merge-map
|
||||
(fn [[id page]]
|
||||
(let [page (update page :objects ctst/start-page-index)]
|
||||
(->> (uw/ask! {:cmd :initialize-page-index :page page})
|
||||
(rx/map (constantly [id page]))))))
|
||||
(rx/reduce conj {})
|
||||
(rx/map (fn [pages-index]
|
||||
(-> data
|
||||
(assoc :pages-index pages-index)
|
||||
(workspace-data-loaded)))))
|
||||
|
||||
;; Once workspace data is loaded, proceed asynchronously load
|
||||
;; the local library and all referenced libraries, without
|
||||
;; blocking the main workspace load process.
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::workspace-data-loaded))
|
||||
(rx/take 1)
|
||||
(rx/merge-map
|
||||
(fn [_]
|
||||
(rx/merge
|
||||
(rx/of (workspace-initialized))
|
||||
|
||||
(->> data
|
||||
(filter (comp t/pointer? val))
|
||||
(resolve-pointers id)
|
||||
(rx/map workspace-data-pointers-loaded))
|
||||
|
||||
(->> (rp/cmd! :get-file-libraries {:file-id id :features features})
|
||||
(rx/mapcat identity)
|
||||
(rx/mapcat
|
||||
(fn [{:keys [id data] :as file}]
|
||||
(->> (filter (comp t/pointer? val) data)
|
||||
(resolve-pointers id)
|
||||
(rx/map #(update file :data merge %)))))
|
||||
(rx/mapcat
|
||||
(fn [{:keys [id data] :as file}]
|
||||
;; Resolve all pages of each library, if needed
|
||||
(->> (rx/from (seq (:pages-index data)))
|
||||
(rx/merge-map
|
||||
(fn [[_ page :as kp]]
|
||||
(if (t/pointer? page)
|
||||
(resolve-pointer id kp)
|
||||
(rx/of kp))))
|
||||
(rx/reduce conj {})
|
||||
(rx/map
|
||||
(fn [pages-index]
|
||||
(assoc-in file [:data :pages-index] pages-index))))))
|
||||
(rx/reduce conj [])
|
||||
(rx/map libraries-fetched))))))))
|
||||
|
||||
(rx/take-until stoper)))))))
|
||||
;; Load libraries
|
||||
(->> (rp/cmd! :get-file-libraries {:file-id id})
|
||||
(rx/mapcat identity)
|
||||
(rx/merge-map
|
||||
(fn [{:keys [id]}]
|
||||
(rp/cmd! :get-file {:id id :features features})))
|
||||
(rx/merge-map
|
||||
(fn [{:keys [id data] :as file}]
|
||||
(->> (resolve-file-data id data)
|
||||
(rx/map (fn [data] (assoc file :data data))))))
|
||||
(rx/reduce conj [])
|
||||
(rx/map libraries-fetched)))
|
||||
(rx/of (with-meta (workspace-initialized) {:file-id id})))
|
||||
(rx/take-until stoper))))))
|
||||
|
||||
(defn- libraries-fetched
|
||||
[libraries]
|
||||
|
|
@ -326,13 +298,15 @@
|
|||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc state
|
||||
:workspace-ready? false
|
||||
:current-file-id file-id
|
||||
:current-project-id project-id
|
||||
:workspace-presence {}))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of (dcm/retrieve-comment-threads file-id)
|
||||
(rx/of msg/hide
|
||||
(dcm/retrieve-comment-threads file-id)
|
||||
(dwp/initialize-file-persistence file-id)
|
||||
(fetch-bundle project-id file-id)))
|
||||
|
||||
|
|
@ -395,7 +369,10 @@
|
|||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [pindex (-> state :workspace-data :pages-index)]
|
||||
;; NOTE: there are cases between files navigation when this
|
||||
;; event is emmited but the page-index is still not loaded, so
|
||||
;; we only need to proceed when page-index is properly loaded
|
||||
(when-let [pindex (-> state :workspace-data :pages-index)]
|
||||
(if (contains? pindex page-id)
|
||||
(rx/of (preload-data-uris page-id)
|
||||
(dwth/watch-state-changes)
|
||||
|
|
@ -414,7 +391,10 @@
|
|||
exit? (not= :workspace (dm/get-in state [:route :data :name]))
|
||||
state (-> state
|
||||
(update :workspace-cache assoc page-id local)
|
||||
(dissoc :current-page-id :workspace-local :workspace-trimmed-page :workspace-focus-selected))]
|
||||
(dissoc :current-page-id
|
||||
:workspace-local
|
||||
:workspace-trimmed-page
|
||||
:workspace-focus-selected))]
|
||||
|
||||
(cond-> state
|
||||
exit? (dissoc :workspace-drawing))))))
|
||||
|
|
@ -619,6 +599,7 @@
|
|||
|
||||
|
||||
(defn start-rename-shape
|
||||
"Start shape renaming process"
|
||||
[id]
|
||||
(dm/assert! (uuid? id))
|
||||
(ptk/reify ::start-rename-shape
|
||||
|
|
@ -627,14 +608,31 @@
|
|||
(assoc-in state [:workspace-local :shape-for-rename] id))))
|
||||
|
||||
(defn end-rename-shape
|
||||
[]
|
||||
(ptk/reify ::end-rename-shape
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :workspace-local dissoc :shape-for-rename))))
|
||||
"End the ongoing shape rename process"
|
||||
([] (end-rename-shape nil))
|
||||
([name]
|
||||
(ptk/reify ::end-rename-shape
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(when-let [shape-id (dm/get-in state [:workspace-local :shape-for-rename])]
|
||||
(let [shape (wsh/lookup-shape state shape-id)]
|
||||
(rx/concat
|
||||
;; Remove rename state from workspace local state
|
||||
(rx/of #(update % :workspace-local dissoc :shape-for-rename))
|
||||
|
||||
;; Rename the shape if string is not empty/blank
|
||||
(when (and (string? name) (not (str/blank? name)))
|
||||
(rx/of (update-shape shape-id {:name name})))
|
||||
|
||||
;; Update the component in case if shape is a main instance
|
||||
(when (:main-instance? shape)
|
||||
(when-let [component-id (:component-id shape)]
|
||||
(rx/of (dwl/rename-component component-id name)))))))))))
|
||||
|
||||
|
||||
;; --- Update Selected Shapes attrs
|
||||
|
||||
|
||||
(defn update-selected-shapes
|
||||
[attrs]
|
||||
(dm/assert! (cts/shape-attrs? attrs))
|
||||
|
|
@ -1132,7 +1130,34 @@
|
|||
qparams {:page-id page-id :layout (name layout)}]
|
||||
(rx/of (rt/nav :workspace pparams qparams))))))
|
||||
|
||||
(defn check-in-asset
|
||||
(defn navigate-to-library
|
||||
"Open a new tab, and navigate to the workspace with the provided file"
|
||||
[library-id]
|
||||
(ptk/reify ::navigate-to-file
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(when-let [file (dm/get-in state [:workspace-libraries library-id])]
|
||||
(let [params {:rname :workspace
|
||||
:path-params {:project-id (:project-id file)
|
||||
:file-id (:id file)}
|
||||
:query-params {:page-id (dm/get-in file [:data :pages 0])}}]
|
||||
(rx/of (rt/nav-new-window* params)))))))
|
||||
|
||||
(defn set-assets-section-open
|
||||
[file-id section open?]
|
||||
(ptk/reify ::set-assets-section-open
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:workspace-assets :open-status file-id section] open?))))
|
||||
|
||||
(defn set-assets-group-open
|
||||
[file-id section path open?]
|
||||
(ptk/reify ::set-assets-group-open
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:workspace-assets :open-status file-id :groups section path] open?))))
|
||||
|
||||
(defn- check-in-asset
|
||||
[items element]
|
||||
(let [items (or items #{})]
|
||||
(if (contains? items element)
|
||||
|
|
@ -1140,57 +1165,85 @@
|
|||
(conj items element))))
|
||||
|
||||
(defn toggle-selected-assets
|
||||
[asset type]
|
||||
[file-id asset-id type]
|
||||
(ptk/reify ::toggle-selected-assets
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:workspace-global :selected-assets type] #(check-in-asset % asset)))))
|
||||
(update-in state [:workspace-assets :selected file-id type] check-in-asset asset-id))))
|
||||
|
||||
(defn select-single-asset
|
||||
[asset type]
|
||||
[file-id asset-id type]
|
||||
(ptk/reify ::select-single-asset
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:workspace-global :selected-assets type] #{asset}))))
|
||||
(prn "select-single-asset" file-id asset-id type)
|
||||
(assoc-in state [:workspace-assets :selected file-id type] #{asset-id}))))
|
||||
|
||||
(defn select-assets
|
||||
[assets type]
|
||||
[file-id assets-ids type]
|
||||
(ptk/reify ::select-assets
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:workspace-global :selected-assets type] (into #{} assets)))))
|
||||
(assoc-in state [:workspace-assets :selected file-id type] (into #{} assets-ids)))))
|
||||
|
||||
(defn unselect-all-assets
|
||||
[]
|
||||
(ptk/reify ::unselect-all-assets
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:workspace-global :selected-assets] {:components #{}
|
||||
:graphics #{}
|
||||
:colors #{}
|
||||
:typographies #{}}))))
|
||||
([] (unselect-all-assets nil))
|
||||
([file-id]
|
||||
(ptk/reify ::unselect-all-assets
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(if file-id
|
||||
(update-in state [:workspace-assets :selected] dissoc file-id)
|
||||
(update state :workspace-assets dissoc :selected))))))
|
||||
|
||||
(defn go-to-main-instance
|
||||
[page-id shape-id]
|
||||
(dm/assert! (uuid? page-id))
|
||||
(dm/assert! (uuid? shape-id))
|
||||
[file-id component-id]
|
||||
(dm/assert!
|
||||
"expected uuid type for `file-id` parameter (nilable)"
|
||||
(or (nil? file-id)
|
||||
(uuid? file-id)))
|
||||
|
||||
(dm/assert!
|
||||
"expected uuid type for `component-id` parameter"
|
||||
(uuid? component-id))
|
||||
|
||||
(ptk/reify ::go-to-main-instance
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [current-page-id (:current-page-id state)]
|
||||
(if (= page-id current-page-id)
|
||||
(rx/of (dws/select-shapes (lks/set shape-id))
|
||||
dwz/zoom-to-selected-shape)
|
||||
(let [project-id (:current-project-id state)
|
||||
file-id (:current-file-id state)
|
||||
pparams {:file-id file-id :project-id project-id}
|
||||
qparams {:page-id page-id}]
|
||||
(rx/merge
|
||||
(rx/of (rt/nav :workspace pparams qparams))
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::dwv/page-loaded))
|
||||
(rx/take 1)
|
||||
(rx/mapcat #(rx/of (dws/select-shapes (lks/set shape-id))
|
||||
dwz/zoom-to-selected-shape))))))))))
|
||||
(let [current-file-id (:current-file-id state)
|
||||
current-page-id (:current-page-id state)
|
||||
current-project-id (:current-project-id state)
|
||||
file-id (or file-id current-file-id)
|
||||
|
||||
redirect-to
|
||||
(fn [file-id page-id]
|
||||
(let [pparams {:file-id file-id :project-id current-project-id}
|
||||
qparams {:page-id page-id}]
|
||||
(rx/merge
|
||||
(rx/of (rt/nav :workspace pparams qparams))
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::workspace-initialized))
|
||||
(rx/map meta)
|
||||
(rx/filter #(= file-id (:file-id %)))
|
||||
(rx/take 1)
|
||||
(rx/observe-on :async)
|
||||
(rx/map #(go-to-main-instance file-id component-id))))))]
|
||||
|
||||
(if (= file-id current-file-id)
|
||||
(let [component (dm/get-in state [:workspace-data :components component-id])
|
||||
page-id (:main-instance-page component)]
|
||||
|
||||
(when (some? page-id)
|
||||
(if (= page-id current-page-id)
|
||||
(let [shape-id (:main-instance-id component)]
|
||||
(rx/of (dws/select-shapes (d/ordered-set shape-id))
|
||||
dwz/zoom-to-selected-shape))
|
||||
(redirect-to current-page-id page-id))))
|
||||
|
||||
(let [component (dm/get-in state [:workspace-libraries file-id :data :components component-id])]
|
||||
(some->> (:main-instance-page component)
|
||||
(redirect-to file-id))))))))
|
||||
|
||||
|
||||
(defn go-to-component
|
||||
[component-id]
|
||||
|
|
@ -1213,9 +1266,9 @@
|
|||
pparams {:file-id file-id :project-id project-id}
|
||||
qparams {:page-id page-id :layout :assets}]
|
||||
(rx/of (rt/nav :workspace pparams qparams)
|
||||
(dwl/set-assets-box-open file-id :library true)
|
||||
(dwl/set-assets-box-open file-id :components true)
|
||||
(select-single-asset component-id :components))))))
|
||||
(set-assets-section-open file-id :library true)
|
||||
(set-assets-section-open file-id :components true)
|
||||
(select-single-asset file-id component-id :components))))))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ state _]
|
||||
|
|
@ -1235,9 +1288,9 @@
|
|||
pparams {:file-id file-id :project-id project-id}
|
||||
qparams {:page-id page-id :layout :assets}]
|
||||
(rx/of (rt/nav :workspace pparams qparams)
|
||||
(dwl/set-assets-box-open file-id :library true)
|
||||
(dwl/set-assets-box-open file-id :components true)
|
||||
(select-single-asset component-id :components))))
|
||||
(set-assets-section-open file-id :library true)
|
||||
(set-assets-section-open file-id :components true)
|
||||
(select-single-asset file-id component-id :components))))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ _ _]
|
||||
|
|
@ -2088,26 +2141,6 @@
|
|||
(dissoc :page-item))]
|
||||
(assoc state :workspace-local local)))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; File Library persistent settings
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
|
||||
(defn set-file-library-listing-thumbs
|
||||
[listing-thumbs?]
|
||||
(ptk/reify ::set-file-library-listing-thumbs
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:workspace-global :file-library-listing-thumbs] listing-thumbs?))))
|
||||
|
||||
(defn set-file-library-reverse-sort
|
||||
[reverse-sort?]
|
||||
(ptk/reify ::set-file-library-reverse-sort
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:workspace-global :file-library-reverse-sort] reverse-sort?))))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Components annotations
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
|
@ -2267,7 +2300,6 @@
|
|||
(dm/export dwv/update-viewport-size)
|
||||
(dm/export dwv/start-panning)
|
||||
(dm/export dwv/finish-panning)
|
||||
(dm/export dwv/page-loaded)
|
||||
|
||||
;; Undo
|
||||
(dm/export dwu/reinitialize-undo)
|
||||
|
|
|
|||
|
|
@ -76,20 +76,6 @@
|
|||
|
||||
(declare sync-file)
|
||||
|
||||
(defn set-assets-box-open
|
||||
[file-id box open?]
|
||||
(ptk/reify ::set-assets-box-open
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:workspace-global :assets-files-open file-id box] open?))))
|
||||
|
||||
(defn set-assets-group-open
|
||||
[file-id box path open?]
|
||||
(ptk/reify ::set-assets-group-open
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:workspace-global :assets-files-open file-id :groups box path] open?))))
|
||||
|
||||
(defn extract-path-if-missing
|
||||
[item]
|
||||
(let [[path name] (cph/parse-path-name (:name item))]
|
||||
|
|
@ -349,6 +335,8 @@
|
|||
;; NOTE: we need to ensure the component exists,
|
||||
;; because there are small possibilities of race
|
||||
;; conditions with component deletion.
|
||||
;;
|
||||
;; FIXME: this race conditon should be handled in pcb/update-component
|
||||
(when component
|
||||
(cond-> component
|
||||
:always
|
||||
|
|
@ -357,7 +345,7 @@
|
|||
|
||||
(not components-v2)
|
||||
(update :objects
|
||||
;; Give the same name to the root shape
|
||||
;; Give the same name to the root shape
|
||||
#(assoc-in % [id :name] name)))))
|
||||
|
||||
changes (-> (pcb/empty-changes it)
|
||||
|
|
@ -367,9 +355,19 @@
|
|||
(rx/of (dch/commit-changes changes)))))))
|
||||
|
||||
(defn rename-component-and-main-instance
|
||||
[component-id shape-id name page-id]
|
||||
(st/emit! (rename-component component-id name)
|
||||
(dch/update-shapes [shape-id] #(merge % {:name name}) {:page-id page-id :stack-undo? true})))
|
||||
[component-id name]
|
||||
(ptk/reify ::rename-component-and-main-instance
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(when-let [component (dm/get-in state [:workspace-data :components component-id])]
|
||||
(let [shape-id (:main-instance-id component)
|
||||
page-id (:main-instance-page component)]
|
||||
(rx/concat
|
||||
(rx/of (rename-component component-id name))
|
||||
|
||||
;; NOTE: only when components-v2 is enabled
|
||||
(when (and shape-id page-id)
|
||||
(rx/of (dch/update-shapes [shape-id] #(assoc % :name name) {:page-id page-id :stack-undo? true})))))))))
|
||||
|
||||
(defn duplicate-component
|
||||
"Create a new component copied from the one with the given id."
|
||||
|
|
@ -951,12 +949,22 @@
|
|||
(defn link-file-to-library
|
||||
[file-id library-id]
|
||||
(ptk/reify ::attach-library
|
||||
;; NOTE: this event implements UpdateEvent protocol for perform an
|
||||
;; optimistic update state for make the UI feel more responsive.
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [libraries (:workspace-shared-files state)
|
||||
library (d/seek #(= (:id %) library-id) libraries)]
|
||||
(if library
|
||||
(update state :workspace-libraries assoc library-id (dissoc library :library-summary))
|
||||
state)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [features (cond-> ffeat/enabled
|
||||
(features/active-feature? state :components-v2)
|
||||
(conj "components/v2"))]
|
||||
(rx/concat
|
||||
(rx/merge
|
||||
(->> (rp/cmd! :link-file-to-library {:file-id file-id :library-id library-id})
|
||||
(rx/ignore))
|
||||
(->> (rp/cmd! :get-file {:id library-id :features features})
|
||||
|
|
|
|||
|
|
@ -15,17 +15,22 @@
|
|||
[app.common.pages.helpers :as cph]
|
||||
[app.common.text :as txt]
|
||||
[app.common.types.modifiers :as ctm]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.workspace.changes :as dch]
|
||||
[app.main.data.workspace.common :as dwc]
|
||||
[app.main.data.workspace.libraries :as dwl]
|
||||
[app.main.data.workspace.modifiers :as dwm]
|
||||
[app.main.data.workspace.selection :as dws]
|
||||
[app.main.data.workspace.shapes :as dwsh]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
[app.main.data.workspace.undo :as dwu]
|
||||
[app.main.fonts :as fonts]
|
||||
[app.util.router :as rt]
|
||||
[app.util.text-editor :as ted]
|
||||
[app.util.timers :as ts]
|
||||
[beicon.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
[potok.core :as ptk]))
|
||||
|
||||
;; -- Attrs
|
||||
|
|
@ -321,6 +326,7 @@
|
|||
(cph/group-shape? shape) (cph/get-children-ids objects id))]
|
||||
(rx/of (dch/update-shapes shape-ids #(update-text-content % update-node? d/txt-merge attrs))))))))
|
||||
|
||||
|
||||
(defn migrate-node
|
||||
[node]
|
||||
(let [color-attrs (select-keys node [:fill-color :fill-opacity :fill-color-ref-id :fill-color-ref-file :fill-color-gradient])]
|
||||
|
|
@ -595,22 +601,97 @@
|
|||
(rx/empty))))))
|
||||
|
||||
(defn update-attrs
|
||||
[id attrs]
|
||||
[id attrs]
|
||||
(ptk/reify ::update-attrs
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/concat
|
||||
(let [attrs (select-keys attrs root-attrs)]
|
||||
(if-not (empty? attrs)
|
||||
(rx/of (update-root-attrs {:id id :attrs attrs}))
|
||||
(rx/empty)))
|
||||
|
||||
(let [attrs (select-keys attrs paragraph-attrs)]
|
||||
(if-not (empty? attrs)
|
||||
(rx/of (update-paragraph-attrs {:id id :attrs attrs}))
|
||||
(rx/empty)))
|
||||
|
||||
(let [attrs (select-keys attrs text-attrs)]
|
||||
(if-not (empty? attrs)
|
||||
(rx/of (update-text-attrs {:id id :attrs attrs}))
|
||||
(rx/empty)))))))
|
||||
|
||||
|
||||
(defn apply-typography
|
||||
"A higher level event that has the resposability of to apply the
|
||||
specified typography to the selected shapes."
|
||||
[typography file-id]
|
||||
(ptk/reify ::apply-typography
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [editor-state (:workspace-editor-state state)
|
||||
selected (wsh/lookup-selected state)
|
||||
attrs (-> typography
|
||||
(assoc :typography-ref-file file-id)
|
||||
(assoc :typography-ref-id (:id typography))
|
||||
(dissoc :id :name))]
|
||||
|
||||
(->> (rx/from (seq selected))
|
||||
(rx/map (fn [id]
|
||||
(let [editor (get editor-state id)]
|
||||
(update-text-attrs {:id id :editor editor :attrs attrs})))))))))
|
||||
|
||||
(defn generate-typography-name
|
||||
[{:keys [font-id font-variant-id] :as typography}]
|
||||
(let [{:keys [name]} (fonts/get-font-data font-id)]
|
||||
(assoc typography :name (str name " " (str/title font-variant-id)))))
|
||||
|
||||
(defn add-typography
|
||||
"A higher level version of dwl/add-typography, and has mainly two
|
||||
responsabilities: add the typography to the library and apply it to
|
||||
the currently selected text shapes (being aware of the open text
|
||||
editors."
|
||||
[file-id]
|
||||
(ptk/reify ::add-typography
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [selected (wsh/lookup-selected state)
|
||||
objects (wsh/lookup-page-objects state)
|
||||
|
||||
xform (comp (keep (d/getf objects))
|
||||
(filter cph/text-shape?))
|
||||
shapes (into [] xform selected)
|
||||
shape (first shapes)
|
||||
|
||||
values (current-text-values
|
||||
{:editor-state (dm/get-in state [:workspace-editor-state (:id shape)])
|
||||
:shape shape
|
||||
:attrs text-attrs})
|
||||
|
||||
multiple? (or (> 1 (count shapes))
|
||||
(d/seek (partial = :multiple)
|
||||
(vals values)))
|
||||
|
||||
values (-> (d/without-nils values)
|
||||
(select-keys
|
||||
(d/concat-vec text-font-attrs
|
||||
text-spacing-attrs
|
||||
text-transform-attrs)))
|
||||
|
||||
typ-id (uuid/next)
|
||||
typ (-> (if multiple?
|
||||
txt/default-typography
|
||||
(merge txt/default-typography values))
|
||||
(generate-typography-name)
|
||||
(assoc :id typ-id))]
|
||||
|
||||
(rx/concat
|
||||
(let [attrs (select-keys attrs root-attrs)]
|
||||
(if-not (empty? attrs)
|
||||
(rx/of (update-root-attrs {:id id :attrs attrs}))
|
||||
(rx/empty)))
|
||||
(rx/of (dwl/add-typography typ)
|
||||
(ptk/event ::ev/event {::ev/name "add-asset-to-library"
|
||||
:asset-type "typography"}))
|
||||
|
||||
(let [attrs (select-keys attrs paragraph-attrs)]
|
||||
(if-not (empty? attrs)
|
||||
(rx/of (update-paragraph-attrs {:id id :attrs attrs}))
|
||||
(rx/empty)))
|
||||
(when (not multiple?)
|
||||
(rx/of (update-attrs (:id shape)
|
||||
{:typography-ref-id typ-id
|
||||
:typography-ref-file file-id}))))))))
|
||||
|
||||
(let [attrs (select-keys attrs text-attrs)]
|
||||
(if-not (empty? attrs)
|
||||
(rx/of (update-text-attrs {:id id :attrs attrs}))
|
||||
(rx/empty)))))))
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
(defn initialize-viewport
|
||||
[{:keys [width height] :as size}]
|
||||
|
||||
(letfn [(update* [{:keys [vport] :as local}]
|
||||
(let [wprop (/ (:width vport) width)
|
||||
hprop (/ (:height vport) height)]
|
||||
|
|
@ -153,11 +154,3 @@
|
|||
(update [_ state]
|
||||
(-> state
|
||||
(update :workspace-local dissoc :panning)))))
|
||||
|
||||
|
||||
;; This event does nothing. Is only for subscibe and know when the page has been loaded
|
||||
(defn page-loaded [_page-id]
|
||||
(ptk/reify ::page-loaded
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
state)))
|
||||
|
|
|
|||
|
|
@ -104,9 +104,6 @@
|
|||
(def workspace-drawing
|
||||
(l/derived :workspace-drawing st/state))
|
||||
|
||||
(def workspace-ready?
|
||||
(l/derived :workspace-ready? st/state))
|
||||
|
||||
;; TODO: rename to workspace-selected (?)
|
||||
;; Don't use directly from components, this is a proxy to improve performance of selected-shapes
|
||||
(def ^:private selected-shapes-data
|
||||
|
|
@ -187,18 +184,9 @@
|
|||
(def editing-page-item
|
||||
(l/derived :page-item workspace-local))
|
||||
|
||||
(def file-library-listing-thumbs?
|
||||
(l/derived :file-library-listing-thumbs workspace-global))
|
||||
|
||||
(def file-library-reverse-sort?
|
||||
(l/derived :file-library-reverse-sort workspace-global))
|
||||
|
||||
(def current-hover-ids
|
||||
(l/derived :hover-ids context-menu))
|
||||
|
||||
(def selected-assets
|
||||
(l/derived :selected-assets workspace-global))
|
||||
|
||||
(def workspace-layout
|
||||
(l/derived :workspace-layout st/state))
|
||||
|
||||
|
|
@ -212,10 +200,9 @@
|
|||
data (:workspace-data state)]
|
||||
(-> file
|
||||
(dissoc :data)
|
||||
(assoc :options (:options data)
|
||||
:components (:components data)
|
||||
:pages (:pages data)
|
||||
:pages-index (:pages-index data)))))
|
||||
;; FIXME: still used in sitemaps but sitemaps
|
||||
;; should declare its own lense for it
|
||||
(assoc :pages (:pages data)))))
|
||||
st/state =))
|
||||
|
||||
(def workspace-data
|
||||
|
|
@ -395,9 +382,7 @@
|
|||
|
||||
(def selected-objects
|
||||
(letfn [(selector [{:keys [selected objects]}]
|
||||
(->> selected
|
||||
(map #(get objects %))
|
||||
(filterv (comp not nil?))))]
|
||||
(into [] (keep (d/getf objects)) selected))]
|
||||
(l/derived selector selected-data =)))
|
||||
|
||||
(def selected-shapes-with-children
|
||||
|
|
|
|||
|
|
@ -98,12 +98,12 @@
|
|||
(let [num-shared (filter #(:is-shared %) files)]
|
||||
|
||||
(if (< 0 (count num-shared))
|
||||
(do (st/emit! (dd/fetch-libraries-using-files files))
|
||||
(st/emit! (modal/show
|
||||
{:type :delete-shared
|
||||
:origin :delete
|
||||
:on-accept delete-fn
|
||||
:count-libraries (count num-shared)})))
|
||||
(st/emit! (modal/show
|
||||
{:type :delete-shared-libraries
|
||||
:origin :delete
|
||||
:ids (into #{} (map :id) files)
|
||||
:on-accept delete-fn
|
||||
:count-libraries (count num-shared)}))
|
||||
|
||||
(if multi?
|
||||
(st/emit! (modal/show
|
||||
|
|
@ -161,10 +161,10 @@
|
|||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! (dd/fetch-libraries-using-files files))
|
||||
(st/emit! (modal/show
|
||||
{:type :delete-shared
|
||||
{:type :delete-shared-libraries
|
||||
:origin :unpublish
|
||||
:ids (into #{} (map :id) files)
|
||||
:on-accept del-shared
|
||||
:count-libraries file-count})))
|
||||
|
||||
|
|
|
|||
|
|
@ -6,88 +6,101 @@
|
|||
|
||||
(ns app.main.ui.delete-shared
|
||||
(:require
|
||||
[app.main.data.dashboard :as dd]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.keyboard :as k]
|
||||
[beicon.core :as rx]
|
||||
[goog.events :as events]
|
||||
[rumext.v2 :as mf])
|
||||
(:import goog.events.EventType))
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def ^:private noop (constantly nil))
|
||||
|
||||
(mf/defc delete-shared-dialog
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :delete-shared}
|
||||
[{:keys [on-accept
|
||||
on-cancel
|
||||
accept-style
|
||||
origin
|
||||
count-libraries] :as props}]
|
||||
(let [on-accept (or on-accept identity)
|
||||
on-cancel (or on-cancel identity)
|
||||
cancel-label (tr "labels.cancel")
|
||||
accept-style (or accept-style :danger)
|
||||
dashboard-local (mf/deref refs/dashboard-local)
|
||||
files->shared (:files-with-shared dashboard-local)
|
||||
::mf/register-as :delete-shared-libraries
|
||||
::mf/wrap-props false}
|
||||
[{:keys [ids on-accept on-cancel accept-style origin count-libraries]}]
|
||||
(let [references* (mf/use-state {})
|
||||
references (deref references*)
|
||||
|
||||
is-delete? (= origin :delete)
|
||||
count-files (count (keys files->shared))
|
||||
on-accept (or on-accept noop)
|
||||
on-cancel (or on-cancel noop)
|
||||
|
||||
title (if is-delete?
|
||||
(tr "modals.delete-shared-confirm.title" (i18n/c count-libraries))
|
||||
(tr "modals.unpublish-shared-confirm.title" (i18n/c count-libraries)))
|
||||
message (if is-delete?
|
||||
(tr "modals.delete-shared-confirm.message" (i18n/c count-libraries))
|
||||
(tr "modals.unpublish-shared-confirm.message" (i18n/c count-libraries)))
|
||||
accept-label (if is-delete?
|
||||
(tr "modals.delete-shared-confirm.accept" (i18n/c count-libraries))
|
||||
(tr "modals.unpublish-shared-confirm.accept" (i18n/c count-libraries)))
|
||||
no-files-message (if is-delete?
|
||||
(tr "modals.delete-shared-confirm.no-files-message" (i18n/c count-libraries))
|
||||
(tr "modals.unpublish-shared-confirm.no-files-message" (i18n/c count-libraries)))
|
||||
scd-message (if is-delete?
|
||||
(if (= count-files 1)
|
||||
(tr "modals.delete-shared-confirm.scd-message" (i18n/c count-libraries))
|
||||
(tr "modals.delete-shared-confirm.scd-message-many" (i18n/c count-libraries)))
|
||||
(if (= count-files 1)
|
||||
(tr "modals.unpublish-shared-confirm.scd-message" (i18n/c count-libraries))
|
||||
(tr "modals.unpublish-shared-confirm.scd-message-many" (i18n/c count-libraries))))
|
||||
hint (if is-delete?
|
||||
(if (= count-files 1)
|
||||
(tr "modals.delete-shared-confirm.hint" (i18n/c count-libraries))
|
||||
cancel-label (tr "labels.cancel")
|
||||
accept-style (or accept-style :danger)
|
||||
|
||||
is-delete? (= origin :delete)
|
||||
count-files (count (keys references))
|
||||
|
||||
title (if ^boolean is-delete?
|
||||
(tr "modals.delete-shared-confirm.title" (i18n/c count-libraries))
|
||||
(tr "modals.unpublish-shared-confirm.title" (i18n/c count-libraries)))
|
||||
|
||||
subtitle (if ^boolean is-delete?
|
||||
(tr "modals.delete-shared-confirm.message" (i18n/c count-libraries))
|
||||
(tr "modals.unpublish-shared-confirm.message" (i18n/c count-libraries)))
|
||||
|
||||
accept-label (if ^boolean is-delete?
|
||||
(tr "modals.delete-shared-confirm.accept" (i18n/c count-libraries))
|
||||
(tr "modals.unpublish-shared-confirm.accept" (i18n/c count-libraries)))
|
||||
|
||||
no-files-msg (if ^boolean is-delete?
|
||||
(tr "modals.delete-shared-confirm.no-files-message" (i18n/c count-libraries))
|
||||
(tr "modals.unpublish-shared-confirm.no-files-message" (i18n/c count-libraries)))
|
||||
|
||||
scd-msg (if ^boolean is-delete?
|
||||
(if (= count-files 1)
|
||||
(tr "modals.delete-shared-confirm.scd-message" (i18n/c count-libraries))
|
||||
(tr "modals.delete-shared-confirm.scd-message-many" (i18n/c count-libraries)))
|
||||
(if (= count-files 1)
|
||||
(tr "modals.unpublish-shared-confirm.scd-message" (i18n/c count-libraries))
|
||||
(tr "modals.unpublish-shared-confirm.scd-message-many" (i18n/c count-libraries))))
|
||||
hint (if ^boolean is-delete?
|
||||
(if (= count-files 1)
|
||||
(tr "modals.delete-shared-confirm.hint" (i18n/c count-libraries))
|
||||
(tr "modals.delete-shared-confirm.hint-many" (i18n/c count-libraries)))
|
||||
(if (= count-files 1)
|
||||
(tr "modals.unpublish-shared-confirm.hint" (i18n/c count-libraries))
|
||||
(tr "modals.unpublish-shared-confirm.hint-many" (i18n/c count-libraries))))
|
||||
(if (= count-files 1)
|
||||
(tr "modals.unpublish-shared-confirm.hint" (i18n/c count-libraries))
|
||||
(tr "modals.unpublish-shared-confirm.hint-many" (i18n/c count-libraries))))
|
||||
|
||||
accept-fn
|
||||
(mf/use-callback
|
||||
(mf/use-fn
|
||||
(mf/deps on-accept)
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(st/emit! (modal/hide))
|
||||
(on-accept props)))
|
||||
(on-accept)))
|
||||
|
||||
cancel-fn
|
||||
(mf/use-callback
|
||||
(mf/use-fn
|
||||
(mf/deps on-cancel)
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(st/emit! (modal/hide))
|
||||
(on-cancel props)))]
|
||||
(on-cancel)))]
|
||||
|
||||
(mf/with-effect
|
||||
(mf/with-effect [ids]
|
||||
(->> (rx/from ids)
|
||||
(rx/map #(array-map :file-id %))
|
||||
(rx/mapcat #(rp/cmd! :get-library-file-references %))
|
||||
(rx/mapcat identity)
|
||||
(rx/map (juxt :id :name))
|
||||
(rx/reduce conj [])
|
||||
(rx/subs #(reset! references* %))))
|
||||
|
||||
(mf/with-effect [accept-fn]
|
||||
(letfn [(on-keydown [event]
|
||||
(when (k/enter? event)
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! (modal/hide))
|
||||
(on-accept props)))]
|
||||
(let [key (events/listen js/document EventType.KEYDOWN on-keydown)]
|
||||
(fn []
|
||||
(events/unlistenByKey key)
|
||||
(st/emit! (dd/clean-temp-shared))))))
|
||||
(accept-fn)))]
|
||||
(let [key (events/listen js/document "keydown" on-keydown)]
|
||||
(partial events/unlistenByKey key))))
|
||||
|
||||
[:div.modal-overlay
|
||||
[:div.modal-container.confirm-dialog
|
||||
|
|
@ -98,23 +111,23 @@
|
|||
{:on-click cancel-fn} i/close]]
|
||||
|
||||
[:div.modal-content.delete-shared
|
||||
(when (and (string? message) (not= message ""))
|
||||
[:h3 message])
|
||||
(when (and (string? subtitle) (not= subtitle ""))
|
||||
[:h3 subtitle])
|
||||
(when (not= 0 count-libraries)
|
||||
(if (> (count files->shared) 0)
|
||||
(if (pos? (count references))
|
||||
[:*
|
||||
[:div
|
||||
(when (and (string? scd-message) (not= scd-message ""))
|
||||
[:h3 scd-message])
|
||||
(when (and (string? scd-msg) (not= scd-msg ""))
|
||||
[:h3 scd-msg])
|
||||
[:ul.file-list
|
||||
(for [[id file] files->shared]
|
||||
(for [[file-id file-name] references]
|
||||
[:li.modal-item-element
|
||||
{:key id}
|
||||
[:span "- " (:name file)]])]]
|
||||
{:key (dm/str file-id)}
|
||||
[:span "- " file-name]])]]
|
||||
(when (and (string? hint) (not= hint ""))
|
||||
[:h3 hint])]
|
||||
[:*
|
||||
[:h3 no-files-message]]))]
|
||||
[:h3 no-files-msg]]))]
|
||||
|
||||
[:div.modal-footer
|
||||
[:div.action-buttons
|
||||
|
|
|
|||
|
|
@ -5,10 +5,8 @@
|
|||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.main.ui.workspace
|
||||
(:import goog.events.EventType)
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.main.data.messages :as msg]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.persistence :as dwp]
|
||||
|
|
@ -28,41 +26,51 @@
|
|||
[app.main.ui.workspace.libraries]
|
||||
[app.main.ui.workspace.nudge]
|
||||
[app.main.ui.workspace.palette :refer [palette]]
|
||||
[app.main.ui.workspace.sidebar :refer [left-sidebar right-sidebar]]
|
||||
[app.main.ui.workspace.sidebar.collapsable-button :refer [collapsed-button]]
|
||||
[app.main.ui.workspace.sidebar.history :refer [history-toolbox]]
|
||||
[app.main.ui.workspace.sidebar.sidebar :refer [left-sidebar right-sidebar]]
|
||||
[app.main.ui.workspace.textpalette :refer [textpalette]]
|
||||
[app.main.ui.workspace.viewport :refer [viewport]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.globals :as globals]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.object :as obj]
|
||||
[app.util.router :as rt]
|
||||
[debug :refer [debug?]]
|
||||
[goog.events :as events]
|
||||
[okulary.core :as l]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
;; --- Workspace
|
||||
(defn- make-file-ready-ref
|
||||
[file-id]
|
||||
(l/derived (fn [state]
|
||||
(let [data (:workspace-data state)]
|
||||
(and (:workspace-ready? state)
|
||||
(= file-id (:current-file-id state))
|
||||
(= file-id (:id data)))))
|
||||
st/state))
|
||||
|
||||
(defn- make-page-ready-ref
|
||||
[page-id]
|
||||
(l/derived (fn [state]
|
||||
(and (some? page-id)
|
||||
(= page-id (:current-page-id state))))
|
||||
st/state))
|
||||
|
||||
(mf/defc workspace-content
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
[{:keys [file layout page-id wglobal]}]
|
||||
(let [selected (mf/deref refs/selected-shapes)
|
||||
file (obj/get props "file")
|
||||
layout (obj/get props "layout")
|
||||
page-id (obj/get props "page-id")
|
||||
|
||||
{:keys [vport] :as wlocal} (mf/deref refs/workspace-local)
|
||||
{:keys [options-mode] :as wglobal} (obj/get props "wglobal")
|
||||
{:keys [options-mode]} wglobal
|
||||
|
||||
colorpalette? (:colorpalette layout)
|
||||
textpalette? (:textpalette layout)
|
||||
hide-ui? (:hide-ui layout)
|
||||
colorpalette? (:colorpalette layout)
|
||||
textpalette? (:textpalette layout)
|
||||
hide-ui? (:hide-ui layout)
|
||||
new-css-system (mf/use-ctx ctx/new-css-system)
|
||||
|
||||
on-resize
|
||||
(mf/use-callback
|
||||
(mf/use-fn
|
||||
(mf/deps vport)
|
||||
(fn [resize-type size]
|
||||
(when (and vport (not= size vport))
|
||||
|
|
@ -79,8 +87,9 @@
|
|||
(when (and textpalette? (not hide-ui?))
|
||||
[:& textpalette])])
|
||||
|
||||
[:section.workspace-content {:key (dm/str "workspace-" page-id)
|
||||
:ref node-ref}
|
||||
[:section.workspace-content
|
||||
{:key (dm/str "workspace-" page-id)
|
||||
:ref node-ref}
|
||||
[:section.workspace-viewport
|
||||
(when (debug? :coordinates)
|
||||
[:& coordinates/coordinates {:colorpalette? colorpalette?}])
|
||||
|
|
@ -89,6 +98,7 @@
|
|||
[:div.history-debug-overlay
|
||||
[:button {:on-click #(st/emit! dw/reinitialize-undo)} "CLEAR"]
|
||||
[:& history-toolbox]])
|
||||
|
||||
[:& viewport {:file file
|
||||
:wlocal wlocal
|
||||
:wglobal wglobal
|
||||
|
|
@ -105,85 +115,84 @@
|
|||
:selected selected
|
||||
:layout layout}]])]))
|
||||
|
||||
(def trimmed-page-ref (l/derived :workspace-trimmed-page st/state =))
|
||||
|
||||
(mf/defc workspace-page
|
||||
[{:keys [file layout page-id wglobal] :as props}]
|
||||
|
||||
(let [prev-page-id (hooks/use-previous page-id)]
|
||||
(mf/with-effect
|
||||
[page-id]
|
||||
(when (and prev-page-id (not= prev-page-id page-id))
|
||||
(st/emit! (dw/finalize-page prev-page-id)))
|
||||
|
||||
(if (nil? page-id)
|
||||
(st/emit! (dw/go-to-page))
|
||||
(st/emit! (dw/initialize-page page-id))))
|
||||
|
||||
(when (mf/deref trimmed-page-ref)
|
||||
[:& workspace-content {:page-id page-id
|
||||
:file file
|
||||
:wglobal wglobal
|
||||
:layout layout}])))
|
||||
|
||||
(mf/defc workspace-loader
|
||||
[]
|
||||
[:div.workspace-loader
|
||||
i/loader-pencil])
|
||||
|
||||
(mf/defc workspace-page
|
||||
{::mf/wrap-props false}
|
||||
[{:keys [page-id file layout wglobal]}]
|
||||
(let [page-id (hooks/use-equal-memo page-id)
|
||||
page-ready* (mf/with-memo [page-id]
|
||||
(make-page-ready-ref page-id))
|
||||
page-ready? (mf/deref page-ready*)]
|
||||
|
||||
(mf/with-effect []
|
||||
(let [focus-out #(st/emit! (dw/workspace-focus-lost))
|
||||
key (events/listen globals/document "blur" focus-out)]
|
||||
(partial events/unlistenByKey key)))
|
||||
|
||||
(mf/with-effect [page-id]
|
||||
(if (some? page-id)
|
||||
(st/emit! (dw/initialize-page page-id))
|
||||
(st/emit! (dw/go-to-page)))
|
||||
(fn []
|
||||
(when (some? page-id)
|
||||
(st/emit! (dw/finalize-page page-id)))))
|
||||
|
||||
(if ^boolean page-ready?
|
||||
[:& workspace-content {:page-id page-id
|
||||
:file file
|
||||
:wglobal wglobal
|
||||
:layout layout}]
|
||||
[:& workspace-loader])))
|
||||
|
||||
(mf/defc workspace
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [project-id file-id page-id layout-name] :as props}]
|
||||
(let [file (mf/deref refs/workspace-file)
|
||||
project (mf/deref refs/workspace-project)
|
||||
layout (mf/deref refs/workspace-layout)
|
||||
wglobal (mf/deref refs/workspace-global)
|
||||
ready? (mf/deref refs/workspace-ready?)
|
||||
workspace-read-only? (mf/deref refs/workspace-read-only?)
|
||||
{::mf/wrap-props false
|
||||
::mf/wrap [mf/memo]}
|
||||
[{:keys [project-id file-id page-id layout-name]}]
|
||||
|
||||
components-v2 (features/use-feature :components-v2)
|
||||
new-css-system (features/use-feature :new-css-system)
|
||||
(let [layout (mf/deref refs/workspace-layout)
|
||||
wglobal (mf/deref refs/workspace-global)
|
||||
read-only? (mf/deref refs/workspace-read-only?)
|
||||
|
||||
background-color (:background-color wglobal)
|
||||
file (mf/deref refs/workspace-file)
|
||||
project (mf/deref refs/workspace-project)
|
||||
|
||||
focus-out
|
||||
(mf/use-callback
|
||||
(fn []
|
||||
(st/emit! (dw/workspace-focus-lost))))]
|
||||
team-id (:team-id project)
|
||||
file-name (:name file)
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps focus-out)
|
||||
(fn []
|
||||
(let [keys [(events/listen globals/window EventType.BLUR focus-out)]]
|
||||
#(doseq [key keys]
|
||||
(events/unlistenByKey key)))))
|
||||
file-ready* (mf/with-memo [file-id]
|
||||
(make-file-ready-ref file-id))
|
||||
file-ready? (mf/deref file-ready*)
|
||||
|
||||
components-v2? (features/use-feature :components-v2)
|
||||
new-css? (features/use-feature :new-css-system)
|
||||
|
||||
background-color (:background-color wglobal)]
|
||||
|
||||
;; Setting the layout preset by its name
|
||||
(mf/with-effect [layout-name]
|
||||
(st/emit! (dw/initialize-layout layout-name)))
|
||||
|
||||
(mf/with-effect [file-name]
|
||||
(when file-name
|
||||
(dom/set-html-title (tr "title.workspace" file-name))))
|
||||
|
||||
(mf/with-effect [project-id file-id]
|
||||
(st/emit! (dw/initialize-file project-id file-id))
|
||||
(fn []
|
||||
(st/emit! ::dwp/force-persist
|
||||
(dw/finalize-file project-id file-id))))
|
||||
|
||||
;; Set html theme color and close any non-modal dialog that may be still open
|
||||
(mf/with-effect
|
||||
(st/emit! msg/hide))
|
||||
|
||||
;; Set properly the page title
|
||||
(mf/with-effect [(:name file)]
|
||||
(when (:name file)
|
||||
(dom/set-html-title (tr "title.workspace" (:name file)))))
|
||||
|
||||
[:& (mf/provider ctx/current-file-id) {:value (:id file)}
|
||||
[:& (mf/provider ctx/current-team-id) {:value (:team-id project)}
|
||||
[:& (mf/provider ctx/current-project-id) {:value (:id project)}
|
||||
[:& (mf/provider ctx/current-file-id) {:value file-id}
|
||||
[:& (mf/provider ctx/current-project-id) {:value project-id}
|
||||
[:& (mf/provider ctx/current-team-id) {:value team-id}
|
||||
[:& (mf/provider ctx/current-page-id) {:value page-id}
|
||||
[:& (mf/provider ctx/components-v2) {:value components-v2}
|
||||
[:& (mf/provider ctx/new-css-system) {:value new-css-system}
|
||||
[:& (mf/provider ctx/workspace-read-only?) {:value workspace-read-only?}
|
||||
[:& (mf/provider ctx/components-v2) {:value components-v2?}
|
||||
[:& (mf/provider ctx/new-css-system) {:value new-css?}
|
||||
[:& (mf/provider ctx/workspace-read-only?) {:value read-only?}
|
||||
[:section#workspace {:style {:background-color background-color
|
||||
:touch-action "none"}}
|
||||
(when (not (:hide-ui layout))
|
||||
|
|
@ -194,7 +203,7 @@
|
|||
|
||||
[:& context-menu]
|
||||
|
||||
(if ready?
|
||||
(if ^boolean file-ready?
|
||||
[:& workspace-page {:page-id page-id
|
||||
:file file
|
||||
:wglobal wglobal
|
||||
|
|
@ -215,6 +224,7 @@
|
|||
(mf/use-effect
|
||||
(fn []
|
||||
#(st/emit! (dw/clear-remove-graphics))))
|
||||
|
||||
[:div.modal-overlay
|
||||
[:div.modal-container.remove-graphics-dialog
|
||||
[:div.modal-header
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -20,30 +20,28 @@
|
|||
[app.main.ui.icons :as i]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.object :as obj]
|
||||
[app.util.timers :as ts]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
|
||||
(mf/defc image-upload
|
||||
{::mf/wrap [mf/memo]}
|
||||
[]
|
||||
(let [ref (mf/use-ref nil)
|
||||
file (mf/deref refs/workspace-file)
|
||||
(let [ref (mf/use-ref nil)
|
||||
file-id (mf/use-ctx ctx/current-file-id)
|
||||
|
||||
on-click
|
||||
(mf/use-callback #(dom/click (mf/ref-val ref)))
|
||||
(mf/use-fn #(dom/click (mf/ref-val ref)))
|
||||
|
||||
on-files-selected
|
||||
(mf/use-callback
|
||||
(mf/deps file)
|
||||
on-selected
|
||||
(mf/use-fn
|
||||
(mf/deps file-id)
|
||||
(fn [blobs]
|
||||
;; We don't want to add a ref because that redraws the component
|
||||
;; for everychange. Better direct access on the callback.
|
||||
(let [vbox (deref refs/vbox)
|
||||
x (+ (:x vbox) (/ (:width vbox) 2))
|
||||
y (+ (:y vbox) (/ (:height vbox) 2))
|
||||
params {:file-id (:id file)
|
||||
params {:file-id file-id
|
||||
:blobs (seq blobs)
|
||||
:position (gpt/point x y)}]
|
||||
(st/emit! (dwm/upload-media-workspace params)))))]
|
||||
|
|
@ -53,26 +51,84 @@
|
|||
{:title (tr "workspace.toolbar.image" (sc/get-tooltip :insert-image))
|
||||
:aria-label (tr "workspace.toolbar.image" (sc/get-tooltip :insert-image))
|
||||
:on-click on-click}
|
||||
[:*
|
||||
i/image
|
||||
[:& file-uploader {:input-id "image-upload"
|
||||
:accept cm/str-image-types
|
||||
:multi true
|
||||
:ref ref
|
||||
:on-selected on-files-selected}]]]]))
|
||||
i/image
|
||||
[:& file-uploader
|
||||
{:input-id "image-upload"
|
||||
:accept cm/str-image-types
|
||||
:multi true
|
||||
:ref ref
|
||||
:on-selected on-selected}]]]))
|
||||
|
||||
(mf/defc left-toolbar
|
||||
{::mf/wrap [mf/memo]
|
||||
::mf/wrap-props false}
|
||||
[props]
|
||||
(let [layout (obj/get props "layout")
|
||||
selected-drawtool (mf/deref refs/selected-drawing-tool)
|
||||
select-drawtool #(st/emit! :interrupt (dw/select-for-drawing %))
|
||||
[{:keys [layout]}]
|
||||
(let [selected-drawtool (mf/deref refs/selected-drawing-tool)
|
||||
edition (mf/deref refs/selected-edition)
|
||||
workspace-read-only? (mf/use-ctx ctx/workspace-read-only?)
|
||||
new-css-system (mf/use-ctx ctx/new-css-system)
|
||||
show-palette-btn? (and (not workspace-read-only?) (not new-css-system))
|
||||
|
||||
new-css? (mf/use-ctx ctx/new-css-system)
|
||||
read-only? (mf/use-ctx ctx/workspace-read-only?)
|
||||
|
||||
show-palette-btn? (and (not ^boolean read-only?) (not ^boolean new-css?))
|
||||
|
||||
|
||||
interrupt
|
||||
(mf/use-fn #(st/emit! :interrupt))
|
||||
|
||||
select-drawtool
|
||||
(mf/use-fn
|
||||
(fn [event]
|
||||
(let [tool (-> (dom/get-current-target event)
|
||||
(dom/get-data "tool")
|
||||
(keyword))]
|
||||
(st/emit! :interrupt (dw/select-for-drawing tool)))))
|
||||
|
||||
toggle-text-palette
|
||||
(mf/use-fn
|
||||
(fn []
|
||||
(r/set-resize-type! :bottom)
|
||||
(-> (dom/get-element-by-class "color-palette")
|
||||
(dom/add-class! "fade-out-down"))
|
||||
(ts/schedule 300 #(st/emit! (dw/remove-layout-flag :colorpalette)
|
||||
(-> (dw/toggle-layout-flag :textpalette)
|
||||
(vary-meta assoc ::ev/origin "workspace-left-toolbar"))))))
|
||||
|
||||
toggle-color-palette
|
||||
(mf/use-fn
|
||||
(fn []
|
||||
(r/set-resize-type! :bottom)
|
||||
(-> (dom/get-element-by-class "color-palette")
|
||||
(dom/add-class! "fade-out-down"))
|
||||
(ts/schedule 300 #(st/emit! (dw/remove-layout-flag :textpalette)
|
||||
(-> (dw/toggle-layout-flag :colorpalette)
|
||||
(vary-meta assoc ::ev/origin "workspace-left-toolbar"))))))
|
||||
|
||||
toggle-shortcuts
|
||||
(mf/use-fn
|
||||
(mf/deps layout)
|
||||
(fn []
|
||||
(let [is-sidebar-closed? (contains? layout :collapse-left-sidebar)]
|
||||
(when is-sidebar-closed?
|
||||
(st/emit! (dw/toggle-layout-flag :collapse-left-sidebar)))
|
||||
(st/emit!
|
||||
(dw/remove-layout-flag :debug-panel)
|
||||
(-> (dw/toggle-layout-flag :shortcuts)
|
||||
(vary-meta assoc ::ev/origin "workspace-left-toolbar"))))))
|
||||
|
||||
toggle-debug-panel
|
||||
(mf/use-fn
|
||||
(mf/deps layout)
|
||||
(fn []
|
||||
(let [is-sidebar-closed? (contains? layout :collapse-left-sidebar)]
|
||||
(when is-sidebar-closed?
|
||||
(st/emit! (dw/toggle-layout-flag :collapse-left-sidebar)))
|
||||
(st/emit!
|
||||
(dw/remove-layout-flag :shortcuts)
|
||||
(-> (dw/toggle-layout-flag :debug-panel)
|
||||
(vary-meta assoc ::ev/origin "workspace-left-toolbar"))))))
|
||||
|
||||
]
|
||||
|
||||
[:aside.left-toolbar
|
||||
[:ul.left-toolbar-options
|
||||
[:li
|
||||
|
|
@ -81,16 +137,17 @@
|
|||
:aria-label (tr "workspace.toolbar.move" (sc/get-tooltip :move))
|
||||
:class (when (and (nil? selected-drawtool)
|
||||
(not edition)) "selected")
|
||||
:on-click #(st/emit! :interrupt)}
|
||||
:on-click interrupt}
|
||||
i/pointer-inner]]
|
||||
(when-not workspace-read-only?
|
||||
(when-not ^boolean read-only?
|
||||
[:*
|
||||
[:li
|
||||
[:button
|
||||
{:title (tr "workspace.toolbar.frame" (sc/get-tooltip :draw-frame))
|
||||
:aria-label (tr "workspace.toolbar.frame" (sc/get-tooltip :draw-frame))
|
||||
:class (when (= selected-drawtool :frame) "selected")
|
||||
:on-click (partial select-drawtool :frame)
|
||||
:on-click select-drawtool
|
||||
:data-tool "frame"
|
||||
:data-test "artboard-btn"}
|
||||
i/artboard]]
|
||||
[:li
|
||||
|
|
@ -98,7 +155,8 @@
|
|||
{:title (tr "workspace.toolbar.rect" (sc/get-tooltip :draw-rect))
|
||||
:aria-label (tr "workspace.toolbar.rect" (sc/get-tooltip :draw-rect))
|
||||
:class (when (= selected-drawtool :rect) "selected")
|
||||
:on-click (partial select-drawtool :rect)
|
||||
:on-click select-drawtool
|
||||
:data-tool "rect"
|
||||
:data-test "rect-btn"}
|
||||
i/box]]
|
||||
[:li
|
||||
|
|
@ -106,7 +164,8 @@
|
|||
{:title (tr "workspace.toolbar.ellipse" (sc/get-tooltip :draw-ellipse))
|
||||
:aria-label (tr "workspace.toolbar.ellipse" (sc/get-tooltip :draw-ellipse))
|
||||
:class (when (= selected-drawtool :circle) "selected")
|
||||
:on-click (partial select-drawtool :circle)
|
||||
:on-click select-drawtool
|
||||
:data-tool "circle"
|
||||
:data-test "ellipse-btn"}
|
||||
i/circle]]
|
||||
[:li
|
||||
|
|
@ -114,7 +173,8 @@
|
|||
{:title (tr "workspace.toolbar.text" (sc/get-tooltip :draw-text))
|
||||
:aria-label (tr "workspace.toolbar.text" (sc/get-tooltip :draw-text))
|
||||
:class (when (= selected-drawtool :text) "selected")
|
||||
:on-click (partial select-drawtool :text)}
|
||||
:on-click select-drawtool
|
||||
:data-tool "text"}
|
||||
i/text]]
|
||||
|
||||
[:& image-upload]
|
||||
|
|
@ -124,7 +184,8 @@
|
|||
{:title (tr "workspace.toolbar.curve" (sc/get-tooltip :draw-curve))
|
||||
:aria-label (tr "workspace.toolbar.curve" (sc/get-tooltip :draw-curve))
|
||||
:class (when (= selected-drawtool :curve) "selected")
|
||||
:on-click (partial select-drawtool :curve)
|
||||
:on-click select-drawtool
|
||||
:data-tool "curve"
|
||||
:data-test "curve-btn"}
|
||||
i/pencil]]
|
||||
[:li
|
||||
|
|
@ -132,7 +193,8 @@
|
|||
{:title (tr "workspace.toolbar.path" (sc/get-tooltip :draw-path))
|
||||
:aria-label (tr "workspace.toolbar.path" (sc/get-tooltip :draw-path))
|
||||
:class (when (= selected-drawtool :path) "selected")
|
||||
:on-click (partial select-drawtool :path)
|
||||
:on-click select-drawtool
|
||||
:data-tool "path"
|
||||
:data-test "path-btn"}
|
||||
i/pen]]])
|
||||
|
||||
|
|
@ -141,23 +203,19 @@
|
|||
{:title (tr "workspace.toolbar.comments" (sc/get-tooltip :add-comment))
|
||||
:aria-label (tr "workspace.toolbar.comments" (sc/get-tooltip :add-comment))
|
||||
:class (when (= selected-drawtool :comments) "selected")
|
||||
:on-click (partial select-drawtool :comments)}
|
||||
:on-click select-drawtool
|
||||
:data-tool "comments"}
|
||||
i/chat]]]
|
||||
|
||||
[:ul.left-toolbar-options.panels
|
||||
(when show-palette-btn?
|
||||
(when ^boolean show-palette-btn?
|
||||
[:*
|
||||
[:li
|
||||
[:button
|
||||
{:title (tr "workspace.toolbar.text-palette" (sc/get-tooltip :toggle-textpalette))
|
||||
:aria-label (tr "workspace.toolbar.text-palette" (sc/get-tooltip :toggle-textpalette))
|
||||
:class (when (contains? layout :textpalette) "selected")
|
||||
:on-click (fn []
|
||||
(r/set-resize-type! :bottom)
|
||||
(dom/add-class! (dom/get-element-by-class "color-palette") "fade-out-down")
|
||||
(ts/schedule 300 #(st/emit! (dw/remove-layout-flag :colorpalette)
|
||||
(-> (dw/toggle-layout-flag :textpalette)
|
||||
(vary-meta assoc ::ev/origin "workspace-left-toolbar")))))}
|
||||
:on-click toggle-text-palette}
|
||||
"Ag"]]
|
||||
|
||||
[:li
|
||||
|
|
@ -165,34 +223,19 @@
|
|||
{:title (tr "workspace.toolbar.color-palette" (sc/get-tooltip :toggle-colorpalette))
|
||||
:aria-label (tr "workspace.toolbar.color-palette" (sc/get-tooltip :toggle-colorpalette))
|
||||
:class (when (contains? layout :colorpalette) "selected")
|
||||
:on-click (fn []
|
||||
(r/set-resize-type! :bottom)
|
||||
(dom/add-class! (dom/get-element-by-class "color-palette") "fade-out-down")
|
||||
(ts/schedule 300 #(st/emit! (dw/remove-layout-flag :textpalette)
|
||||
(-> (dw/toggle-layout-flag :colorpalette)
|
||||
(vary-meta assoc ::ev/origin "workspace-left-toolbar")))))}
|
||||
:on-click toggle-color-palette}
|
||||
i/palette]]])
|
||||
[:li
|
||||
[:button
|
||||
{:title (tr "workspace.toolbar.shortcuts" (sc/get-tooltip :show-shortcuts))
|
||||
:aria-label (tr "workspace.toolbar.shortcuts" (sc/get-tooltip :show-shortcuts))
|
||||
:class (when (contains? layout :shortcuts) "selected")
|
||||
:on-click (fn []
|
||||
(let [is-sidebar-closed? (contains? layout :collapse-left-sidebar)]
|
||||
(ts/schedule 300 #(st/emit! (when is-sidebar-closed? (dw/toggle-layout-flag :collapse-left-sidebar))
|
||||
(dw/remove-layout-flag :debug-panel)
|
||||
(-> (dw/toggle-layout-flag :shortcuts)
|
||||
(vary-meta assoc ::ev/origin "workspace-left-toolbar"))))))}
|
||||
:on-click toggle-shortcuts}
|
||||
i/shortcut]
|
||||
|
||||
(when *assert*
|
||||
[:button
|
||||
{:title "Debugging tool"
|
||||
:class (when (contains? layout :debug-panel) "selected")
|
||||
:on-click (fn []
|
||||
(let [is-sidebar-closed? (contains? layout :collapse-left-sidebar)]
|
||||
(ts/schedule 300 #(st/emit! (when is-sidebar-closed? (dw/toggle-layout-flag :collapse-left-sidebar))
|
||||
(dw/remove-layout-flag :shortcuts)
|
||||
(-> (dw/toggle-layout-flag :debug-panel)
|
||||
(vary-meta assoc ::ev/origin "workspace-left-toolbar"))))))}
|
||||
:on-click toggle-debug-panel}
|
||||
i/bug])]]]))
|
||||
|
|
|
|||
|
|
@ -7,13 +7,13 @@
|
|||
(ns app.main.ui.workspace.libraries
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.types.components-list :as ctkl]
|
||||
[app.main.data.dashboard :as dd]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.workspace.libraries :as dwl]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.icons :as i] [app.main.ui.workspace.sidebar.assets :as a]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.keyboard :as kbd]
|
||||
|
|
@ -22,243 +22,302 @@
|
|||
[okulary.core :as l]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def workspace-file
|
||||
(def ref:workspace-file
|
||||
(l/derived :workspace-file st/state))
|
||||
|
||||
(defn library-str
|
||||
(defn create-file-library-ref
|
||||
[library-id]
|
||||
(letfn [(getter-fn [state]
|
||||
(let [fdata (let [{:keys [id] :as wfile} (:workspace-data state)]
|
||||
(if (= id library-id)
|
||||
wfile
|
||||
(dm/get-in state [:workspace-libraries library-id :data])))]
|
||||
{:colors (-> fdata :colors vals)
|
||||
:media (-> fdata :media vals)
|
||||
:components (ctkl/components-seq fdata)
|
||||
:typographies (-> fdata :typographies vals)}))]
|
||||
(l/derived getter-fn st/state =)))
|
||||
|
||||
(defn- describe-library
|
||||
[components-count graphics-count colors-count typography-count]
|
||||
(str
|
||||
(str/join " · "
|
||||
(cond-> []
|
||||
(< 0 components-count)
|
||||
(pos? components-count)
|
||||
(conj (tr "workspace.libraries.components" components-count))
|
||||
|
||||
(< 0 graphics-count)
|
||||
(pos? graphics-count)
|
||||
(conj (tr "workspace.libraries.graphics" graphics-count))
|
||||
|
||||
(< 0 colors-count)
|
||||
(pos? colors-count)
|
||||
(conj (tr "workspace.libraries.colors" colors-count))
|
||||
|
||||
(< 0 typography-count)
|
||||
(pos? typography-count)
|
||||
(conj (tr "workspace.libraries.typography" typography-count))))
|
||||
"\u00A0"))
|
||||
|
||||
(defn local-library-str
|
||||
(defn- describe-linked-library
|
||||
[library]
|
||||
(let [components-count (count (or (ctkl/components-seq (:data library)) []))
|
||||
graphics-count (count (get-in library [:data :media] []))
|
||||
colors-count (count (get-in library [:data :colors] []))
|
||||
typography-count (count (get-in library [:data :typographies] []))]
|
||||
(library-str components-count graphics-count colors-count typography-count)))
|
||||
graphics-count (count (dm/get-in library [:data :media] []))
|
||||
colors-count (count (dm/get-in library [:data :colors] []))
|
||||
typography-count (count (dm/get-in library [:data :typographies] []))]
|
||||
(describe-library components-count graphics-count colors-count typography-count)))
|
||||
|
||||
(defn external-library-str
|
||||
(defn- describe-external-library
|
||||
[library]
|
||||
(let [components-count (get-in library [:library-summary :components :count] 0)
|
||||
graphics-count (get-in library [:library-summary :media :count] 0)
|
||||
colors-count (get-in library [:library-summary :colors :count] 0)
|
||||
typography-count (get-in library [:library-summary :typographies :count] 0)]
|
||||
(library-str components-count graphics-count colors-count typography-count)))
|
||||
(let [components-count (dm/get-in library [:library-summary :components :count] 0)
|
||||
graphics-count (dm/get-in library [:library-summary :media :count] 0)
|
||||
colors-count (dm/get-in library [:library-summary :colors :count] 0)
|
||||
typography-count (dm/get-in library [:library-summary :typographies :count] 0)]
|
||||
(describe-library components-count graphics-count colors-count typography-count)))
|
||||
|
||||
(mf/defc libraries-tab
|
||||
[{:keys [file colors typographies media components libraries shared-files] :as props}]
|
||||
(let [search-term (mf/use-state "")
|
||||
{::mf/wrap-props false}
|
||||
[{:keys [file-id shared? linked-libraries shared-libraries]}]
|
||||
(let [search-term* (mf/use-state "")
|
||||
search-term (deref search-term*)
|
||||
|
||||
sorted-libraries (->> (vals libraries)
|
||||
(sort-by #(str/lower (:name %))))
|
||||
library-ref (mf/with-memo [file-id]
|
||||
(create-file-library-ref file-id))
|
||||
library (deref library-ref)
|
||||
colors (:colors library)
|
||||
components (:components library)
|
||||
media (:media library)
|
||||
typographies (:typographies library)
|
||||
|
||||
filtered-files (->> shared-files
|
||||
(filter #(not= (:id %) (:id file)))
|
||||
(filter #(nil? (get libraries (:id %))))
|
||||
(filter #(matches-search (:name %) @search-term))
|
||||
(sort-by #(str/lower (:name %))))
|
||||
shared-libraries
|
||||
(mf/with-memo [shared-libraries linked-libraries file-id search-term]
|
||||
(->> shared-libraries
|
||||
(remove #(= (:id %) file-id))
|
||||
(remove #(contains? linked-libraries (:id %)))
|
||||
(filter #(matches-search (:name %) search-term))
|
||||
(sort-by (comp str/lower :name))))
|
||||
|
||||
on-search-term-change
|
||||
(mf/use-callback
|
||||
linked-libraries
|
||||
(mf/with-memo [linked-libraries]
|
||||
(->> (vals linked-libraries)
|
||||
(sort-by (comp str/lower :name))))
|
||||
|
||||
change-search-term
|
||||
(mf/use-fn
|
||||
(fn [event]
|
||||
(let [value (-> (dom/get-target event)
|
||||
(dom/get-value))]
|
||||
(reset! search-term value))))
|
||||
(reset! search-term* value))))
|
||||
|
||||
on-search-clear
|
||||
(mf/use-callback
|
||||
(fn [_]
|
||||
(reset! search-term "")))
|
||||
clear-search-term
|
||||
(mf/use-fn #(reset! search-term* ""))
|
||||
|
||||
link-library
|
||||
(mf/use-callback (mf/deps file) #(st/emit! (dwl/link-file-to-library (:id file) %)))
|
||||
(mf/use-fn
|
||||
(mf/deps file-id)
|
||||
(fn [event]
|
||||
(let [library-id (some-> (dom/get-target event)
|
||||
(dom/get-data "library-id")
|
||||
(parse-uuid))]
|
||||
(st/emit! (dwl/link-file-to-library file-id library-id)))))
|
||||
|
||||
unlink-library
|
||||
(mf/use-callback
|
||||
(mf/deps file)
|
||||
(fn [library-id]
|
||||
(st/emit! (dwl/unlink-file-from-library (:id file) library-id)
|
||||
(dwl/sync-file (:id file) library-id))))
|
||||
add-shared
|
||||
(mf/use-callback
|
||||
(mf/deps file)
|
||||
#(st/emit! (dwl/set-file-shared (:id file) true)))
|
||||
|
||||
del-shared
|
||||
(mf/use-callback
|
||||
(mf/deps file)
|
||||
(fn [_]
|
||||
(st/emit! (dd/fetch-libraries-using-files [file]))
|
||||
(st/emit! (modal/show
|
||||
{:type :delete-shared
|
||||
:origin :unpublish
|
||||
:on-accept (fn []
|
||||
(st/emit! (dwl/set-file-shared (:id file) false))
|
||||
(modal/show! :libraries-dialog {}))
|
||||
:on-cancel #(modal/show! :libraries-dialog {})
|
||||
:count-libraries 1}))))
|
||||
handle-key-down
|
||||
(mf/use-callback
|
||||
(mf/use-fn
|
||||
(mf/deps file-id)
|
||||
(fn [event]
|
||||
(let [enter? (kbd/enter? event)
|
||||
esc? (kbd/esc? event)
|
||||
input-node (dom/event->target event)]
|
||||
(let [library-id (some-> (dom/get-target event)
|
||||
(dom/get-data "library-id")
|
||||
(parse-uuid))]
|
||||
(st/emit! (dwl/unlink-file-from-library file-id library-id)
|
||||
(dwl/sync-file file-id library-id)))))
|
||||
|
||||
(when enter?
|
||||
on-delete-accept
|
||||
(mf/use-fn
|
||||
(mf/deps file-id)
|
||||
#(st/emit! (dwl/set-file-shared file-id false)
|
||||
(modal/show :libraries-dialog {})))
|
||||
|
||||
on-delete-cancel
|
||||
(mf/use-fn #(st/emit! (modal/show :libraries-dialog {})))
|
||||
|
||||
publish
|
||||
(mf/use-fn
|
||||
(mf/deps file-id)
|
||||
#(st/emit! (dwl/set-file-shared file-id true)))
|
||||
|
||||
unpublish
|
||||
(mf/use-fn
|
||||
(mf/deps file-id)
|
||||
(fn [_]
|
||||
(st/emit! (modal/show
|
||||
{:type :delete-shared-libraries
|
||||
:ids #{file-id}
|
||||
:origin :unpublish
|
||||
:on-accept on-delete-accept
|
||||
:on-cancel on-delete-cancel
|
||||
:count-libraries 1}))))
|
||||
|
||||
handle-key-down
|
||||
(mf/use-fn
|
||||
(fn [event]
|
||||
(let [enter? (kbd/enter? event)
|
||||
esc? (kbd/esc? event)
|
||||
input-node (dom/event->target event)]
|
||||
(when ^boolean enter?
|
||||
(dom/blur! input-node))
|
||||
(when esc?
|
||||
(when ^boolean esc?
|
||||
(dom/blur! input-node)))))]
|
||||
|
||||
[:*
|
||||
[:div.section
|
||||
[:div.section-title (tr "workspace.libraries.in-this-file")]
|
||||
[:div.section-list
|
||||
|
||||
[:div.section-list-item
|
||||
[:div
|
||||
[:div.item-name (tr "workspace.libraries.file-library")]
|
||||
[:div.item-contents (library-str (count components) (count media) (count colors) (count typographies) )]]
|
||||
[:div.item-contents (describe-library
|
||||
(count components)
|
||||
(count media)
|
||||
(count colors)
|
||||
(count typographies))]]
|
||||
[:div
|
||||
(if (:is-shared file)
|
||||
(if ^boolean shared?
|
||||
[:input.item-button {:type "button"
|
||||
:value (tr "common.unpublish")
|
||||
:on-click del-shared}]
|
||||
:on-click unpublish}]
|
||||
[:input.item-button {:type "button"
|
||||
:value (tr "common.publish")
|
||||
:on-click add-shared}])]]
|
||||
:on-click publish}])]]
|
||||
|
||||
(for [library sorted-libraries]
|
||||
[:div.section-list-item {:key (:id library)}
|
||||
[:div.item-name (:name library)]
|
||||
[:div.item-contents (local-library-str library)]
|
||||
(for [{:keys [id name] :as library} linked-libraries]
|
||||
[:div.section-list-item {:key (dm/str id)}
|
||||
[:div.item-name name]
|
||||
[:div.item-contents (describe-linked-library library)]
|
||||
[:input.item-button {:type "button"
|
||||
:value (tr "labels.remove")
|
||||
:on-click #(unlink-library (:id library))}]])
|
||||
]]
|
||||
:data-library-id (dm/str id)
|
||||
:on-click unlink-library}]])]]
|
||||
|
||||
[:div.section
|
||||
[:div.section-title (tr "workspace.libraries.shared-libraries")]
|
||||
[:div.libraries-search
|
||||
[:input.search-input
|
||||
{:placeholder (tr "workspace.libraries.search-shared-libraries")
|
||||
:type "text"
|
||||
:value @search-term
|
||||
:on-change on-search-term-change
|
||||
:value search-term
|
||||
:on-change change-search-term
|
||||
:on-key-down handle-key-down}]
|
||||
(if (str/empty? @search-term)
|
||||
(if (str/empty? search-term)
|
||||
[:div.search-icon
|
||||
i/search]
|
||||
[:div.search-icon.search-close
|
||||
{:on-click on-search-clear}
|
||||
{:on-click clear-search-term}
|
||||
i/close])]
|
||||
(if (> (count filtered-files) 0)
|
||||
|
||||
(if (seq shared-libraries)
|
||||
[:div.section-list
|
||||
(for [file filtered-files]
|
||||
[:div.section-list-item {:key (:id file)}
|
||||
[:div.item-name (:name file)]
|
||||
[:div.item-contents (external-library-str file)]
|
||||
(for [{:keys [id name] :as library} shared-libraries]
|
||||
[:div.section-list-item {:key (dm/str id)}
|
||||
[:div.item-name name]
|
||||
[:div.item-contents (describe-external-library library)]
|
||||
[:input.item-button {:type "button"
|
||||
:value (tr "workspace.libraries.add")
|
||||
:on-click #(link-library (:id file))}]])]
|
||||
:data-library-id (dm/str id)
|
||||
:on-click link-library}]])]
|
||||
|
||||
[:div.section-list-empty
|
||||
(if (nil? shared-files)
|
||||
(if (nil? shared-libraries)
|
||||
i/loader-pencil
|
||||
[:* i/library
|
||||
(if (str/empty? @search-term)
|
||||
(if (str/empty? search-term)
|
||||
(tr "workspace.libraries.no-shared-libraries-available")
|
||||
(tr "workspace.libraries.no-matches-for" @search-term))])])]]))
|
||||
(tr "workspace.libraries.no-matches-for" search-term))])])]]))
|
||||
|
||||
|
||||
(mf/defc updates-tab
|
||||
[{:keys [file libraries] :as props}]
|
||||
(let [libraries-need-sync (filter #(> (:modified-at %) (:synced-at %))
|
||||
(vals libraries))
|
||||
update-library #(st/emit! (dwl/sync-file (:id file) %))]
|
||||
[:div.section
|
||||
(if (empty? libraries-need-sync)
|
||||
[:div.section-list-empty
|
||||
i/library
|
||||
(tr "workspace.libraries.no-libraries-need-sync")]
|
||||
[:*
|
||||
[:div.section-title (tr "workspace.libraries.library")]
|
||||
[:div.section-list
|
||||
(for [library libraries-need-sync]
|
||||
[:div.section-list-item {:key (:id library)}
|
||||
[:div.item-name (:name library)]
|
||||
[:div.item-contents (external-library-str library)]
|
||||
[:input.item-button {:type "button"
|
||||
:value (tr "workspace.libraries.update")
|
||||
:on-click #(update-library (:id library))}]])]])]))
|
||||
{::mf/wrap-props false}
|
||||
[{:keys [file-id libraries]}]
|
||||
(let [libraries (mf/with-memo [libraries]
|
||||
(filter #(> (:modified-at %) (:synced-at %)) (vals libraries)))
|
||||
|
||||
update (mf/use-fn
|
||||
(mf/deps file-id)
|
||||
(fn [event]
|
||||
(let [library-id (some-> (dom/get-target event)
|
||||
(dom/get-data "library-id")
|
||||
(parse-uuid))]
|
||||
(st/emit! (dwl/sync-file file-id library-id)))))]
|
||||
[:div.section
|
||||
(if (empty? libraries)
|
||||
[:div.section-list-empty
|
||||
i/library
|
||||
(tr "workspace.libraries.no-libraries-need-sync")]
|
||||
[:*
|
||||
[:div.section-title (tr "workspace.libraries.library")]
|
||||
[:div.section-list
|
||||
(for [{:keys [id name] :as library} libraries]
|
||||
[:div.section-list-item {:key (dm/str id)}
|
||||
[:div.item-name name]
|
||||
[:div.item-contents (describe-external-library library)]
|
||||
[:input.item-button {:type "button"
|
||||
:value (tr "workspace.libraries.update")
|
||||
:data-library-id (dm/str id)
|
||||
:on-click update}]])]])]))
|
||||
|
||||
(mf/defc libraries-dialog
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :libraries-dialog}
|
||||
[{:keys [] :as ctx}]
|
||||
(let [selected-tab (mf/use-state :libraries)
|
||||
project (mf/deref refs/workspace-project)
|
||||
file (mf/deref workspace-file)
|
||||
[]
|
||||
(let [project (mf/deref refs/workspace-project)
|
||||
file (mf/deref ref:workspace-file)
|
||||
|
||||
libraries (->> (mf/deref refs/workspace-libraries)
|
||||
(d/removem (fn [[_ val]] (:is-indirect val))))
|
||||
shared-files (mf/deref refs/workspace-shared-files)
|
||||
team-id (:team-id project)
|
||||
file-id (:id file)
|
||||
shared? (:is-shared file)
|
||||
|
||||
colors-ref (mf/use-memo (mf/deps (:id file)) #(a/file-colors-ref (:id file)))
|
||||
colors (mf/deref colors-ref)
|
||||
selected-tab* (mf/use-state :libraries)
|
||||
selected-tab (deref selected-tab*)
|
||||
|
||||
typography-ref (mf/use-memo (mf/deps (:id file)) #(a/file-typography-ref (:id file)))
|
||||
typographies (mf/deref typography-ref)
|
||||
libraries (mf/deref refs/workspace-libraries)
|
||||
libraries (mf/with-memo [libraries]
|
||||
(d/removem (fn [[_ val]] (:is-indirect val)) libraries))
|
||||
|
||||
media-ref (mf/use-memo (mf/deps (:id file)) #(a/file-media-ref (:id file)))
|
||||
media (mf/deref media-ref)
|
||||
;; NOTE: we really don't need react on shared files
|
||||
shared-libraries
|
||||
(mf/deref refs/workspace-shared-files)
|
||||
|
||||
components-ref (mf/use-memo (mf/deps (:id file)) #(a/file-components-ref (:id file)))
|
||||
components (mf/deref components-ref)
|
||||
select-libraries-tab
|
||||
(mf/use-fn #(reset! selected-tab* :libraries))
|
||||
|
||||
change-tab #(reset! selected-tab %)
|
||||
close #(modal/hide!)]
|
||||
select-updates-tab
|
||||
(mf/use-fn #(reset! selected-tab* :updates))
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps project)
|
||||
(fn []
|
||||
(when (:team-id project)
|
||||
(st/emit! (dwl/fetch-shared-files {:team-id (:team-id project)})))))
|
||||
close-dialog
|
||||
(mf/use-fn #(modal/hide!))]
|
||||
|
||||
(mf/with-effect [team-id]
|
||||
(when team-id
|
||||
(st/emit! (dwl/fetch-shared-files {:team-id team-id}))))
|
||||
|
||||
[:div.modal-overlay
|
||||
[:div.modal.libraries-dialog
|
||||
[:a.close {:on-click close} i/close]
|
||||
[:a.close {:on-click close-dialog} i/close]
|
||||
[:div.modal-content
|
||||
[:div.libraries-header
|
||||
[:div.header-item
|
||||
{:class (dom/classnames :active (= @selected-tab :libraries))
|
||||
:on-click #(change-tab :libraries)}
|
||||
{:class (dom/classnames :active (= selected-tab :libraries))
|
||||
:on-click select-libraries-tab}
|
||||
(tr "workspace.libraries.libraries")]
|
||||
[:div.header-item
|
||||
{:class (dom/classnames :active (= @selected-tab :updates))
|
||||
:on-click #(change-tab :updates)}
|
||||
{:class (dom/classnames :active (= selected-tab :updates))
|
||||
:on-click select-updates-tab}
|
||||
(tr "workspace.libraries.updates")]]
|
||||
[:div.libraries-content
|
||||
(case @selected-tab
|
||||
(case selected-tab
|
||||
:libraries
|
||||
[:& libraries-tab {:file file
|
||||
:colors colors
|
||||
:typographies typographies
|
||||
:media media
|
||||
:components components
|
||||
:libraries libraries
|
||||
:shared-files shared-files}]
|
||||
[:& libraries-tab {:file-id file-id
|
||||
:shared? shared?
|
||||
:linked-libraries libraries
|
||||
:shared-libraries shared-libraries}]
|
||||
:updates
|
||||
[:& updates-tab {:file file
|
||||
[:& updates-tab {:file-id file-id
|
||||
:libraries libraries}])]]]]))
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,153 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.main.ui.workspace.sidebar
|
||||
(:require-macros [app.main.style :refer [css]])
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.tab-container :refer [tab-container tab-element]]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.hooks.resize :refer [use-resize-hook]]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.ui.workspace.comments :refer [comments-sidebar]]
|
||||
[app.main.ui.workspace.sidebar.assets :refer [assets-toolbox]]
|
||||
[app.main.ui.workspace.sidebar.debug :refer [debug-panel]]
|
||||
[app.main.ui.workspace.sidebar.history :refer [history-toolbox]]
|
||||
[app.main.ui.workspace.sidebar.layers :refer [layers-toolbox]]
|
||||
[app.main.ui.workspace.sidebar.options :refer [options-toolbox]]
|
||||
[app.main.ui.workspace.sidebar.shortcuts :refer [shortcuts-container]]
|
||||
[app.main.ui.workspace.sidebar.sitemap :refer [sitemap]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
;; --- Left Sidebar (Component)
|
||||
|
||||
(mf/defc left-sidebar
|
||||
{::mf/wrap [mf/memo]
|
||||
::mf/wrap-props false}
|
||||
[{:keys [layout] :as props}]
|
||||
(let [options-mode (mf/deref refs/options-mode-global)
|
||||
mode-inspect? (= options-mode :inspect)
|
||||
|
||||
section (cond (or mode-inspect? (contains? layout :layers)) :layers
|
||||
(contains? layout :assets) :assets)
|
||||
shortcuts? (contains? layout :shortcuts)
|
||||
show-debug? (contains? layout :debug-panel)
|
||||
new-css? (mf/use-ctx ctx/new-css-system)
|
||||
|
||||
{:keys [on-pointer-down on-lost-pointer-capture on-pointer-move parent-ref size]}
|
||||
(use-resize-hook :left-sidebar 255 255 500 :x false :left)
|
||||
|
||||
handle-collapse
|
||||
(mf/use-fn #(st/emit! (dw/toggle-layout-flag :collapse-left-sidebar)))
|
||||
|
||||
on-tab-change
|
||||
(mf/use-fn #(st/emit! (dw/go-to-layout %)))
|
||||
]
|
||||
|
||||
[:aside {:ref parent-ref
|
||||
:class (if ^boolean new-css?
|
||||
(dom/classnames (css :left-settings-bar) true)
|
||||
(dom/classnames :settings-bar true
|
||||
:settings-bar-left true
|
||||
:two-row (<= size 300)
|
||||
:three-row (and (> size 300) (<= size 400))
|
||||
:four-row (> size 400)))
|
||||
:style #js {"--width" (dm/str size "px")}}
|
||||
|
||||
[:div {:on-pointer-down on-pointer-down
|
||||
:on-lost-pointer-capture on-lost-pointer-capture
|
||||
:on-pointer-move on-pointer-move
|
||||
:class (if ^boolean new-css?
|
||||
(dom/classnames (css :resize-area) true)
|
||||
(dom/classnames :resize-area true))}]
|
||||
[:div {:class (if ^boolean new-css?
|
||||
(dom/classnames (css :settings-bar-inside) true)
|
||||
(dom/classnames :settings-bar-inside true))}
|
||||
(cond
|
||||
(true? shortcuts?)
|
||||
[:& shortcuts-container]
|
||||
|
||||
(true? show-debug?)
|
||||
[:& debug-panel]
|
||||
|
||||
:else
|
||||
(if ^boolean new-css?
|
||||
[:& tab-container
|
||||
{:on-change-tab on-tab-change
|
||||
:selected section
|
||||
:shortcuts? shortcuts?
|
||||
:collapsable? true
|
||||
:handle-collapse handle-collapse}
|
||||
|
||||
[:& tab-element {:id :layers :title (tr "workspace.sidebar.layers")}
|
||||
[:div {:class (dom/classnames (css :layers-tab) true)}
|
||||
[:& sitemap {:layout layout}]
|
||||
[:& layers-toolbox {:size-parent size}]]]
|
||||
|
||||
(when-not ^boolean mode-inspect?
|
||||
[:& tab-element {:id :assets :title (tr "workspace.toolbar.assets")}
|
||||
[:& assets-toolbox]])]
|
||||
|
||||
[:*
|
||||
[:button.collapse-sidebar
|
||||
{:on-click handle-collapse
|
||||
:aria-label (tr "workspace.sidebar.collapse")}
|
||||
i/arrow-slide]
|
||||
|
||||
[:& tab-container
|
||||
{:on-change-tab on-tab-change
|
||||
:selected section
|
||||
:shortcuts? shortcuts?
|
||||
:collapsable? true
|
||||
:handle-collapse handle-collapse}
|
||||
|
||||
[:& tab-element {:id :layers :title (tr "workspace.sidebar.layers")}
|
||||
[:div {:class (dom/classnames :layers-tab true)}
|
||||
[:& sitemap {:layout layout}]
|
||||
[:& layers-toolbox {:size-parent size}]]]
|
||||
|
||||
(when-not ^boolean mode-inspect?
|
||||
[:& tab-element {:id :assets :title (tr "workspace.toolbar.assets")}
|
||||
[:& assets-toolbox]])]]))]]))
|
||||
|
||||
;; --- Right Sidebar (Component)
|
||||
|
||||
(mf/defc right-sidebar
|
||||
{::mf/wrap-props false
|
||||
::mf/wrap [mf/memo]}
|
||||
[{:keys [layout section] :as props}]
|
||||
(let [drawing-tool (:tool (mf/deref refs/workspace-drawing))
|
||||
|
||||
is-comments? (= drawing-tool :comments)
|
||||
is-history? (contains? layout :document-history)
|
||||
is-inspect? (= section :inspect)
|
||||
|
||||
expanded? (mf/deref refs/inspect-expanded)
|
||||
can-be-expanded? (and (not is-comments?)
|
||||
(not is-history?)
|
||||
is-inspect?)]
|
||||
|
||||
(mf/with-effect [can-be-expanded?]
|
||||
(when (not can-be-expanded?)
|
||||
(st/emit! (dw/set-inspect-expanded false))))
|
||||
|
||||
[:aside.settings-bar.settings-bar-right {:class (when (and can-be-expanded? expanded?) "expanded")}
|
||||
[:div.settings-bar-inside
|
||||
(cond
|
||||
(true? is-comments?)
|
||||
[:& comments-sidebar]
|
||||
|
||||
(true? is-history?)
|
||||
[:& history-toolbox]
|
||||
|
||||
:else
|
||||
[:> options-toolbox props])]]))
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -18,15 +18,16 @@
|
|||
(mf/defc collapsed-button
|
||||
{::mf/wrap-props false}
|
||||
[]
|
||||
(let [new-css-system (mf/use-ctx ctx/new-css-system)]
|
||||
(if new-css-system
|
||||
(let [new-css? (mf/use-ctx ctx/new-css-system)
|
||||
on-click (mf/use-fn #(st/emit! (dw/toggle-layout-flag :collapse-left-sidebar)))]
|
||||
(if ^boolean new-css?
|
||||
[:div {:class (dom/classnames (css :collapsed-sidebar) true)}
|
||||
[:div {:class (dom/classnames (css :collapsed-title) true)}
|
||||
[:button {:class (dom/classnames (css :collapsed-button) true)
|
||||
:on-click #(st/emit! (dw/toggle-layout-flag :collapse-left-sidebar))
|
||||
:on-click on-click
|
||||
:aria-label (tr "workspace.sidebar.expand")}
|
||||
i/arrow-refactor]]]
|
||||
[:button.collapse-sidebar.collapsed
|
||||
{:on-click #(st/emit! (dw/toggle-layout-flag :collapse-left-sidebar))
|
||||
{:on-click on-click
|
||||
:aria-label (tr "workspace.sidebar.expand")}
|
||||
i/arrow-slide])))
|
||||
i/arrow-slide])))
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
(:require-macros [app.main.style :refer [css]])
|
||||
(:require
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.libraries :as dwl]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.util.dom :as dom]
|
||||
|
|
@ -19,6 +18,7 @@
|
|||
|
||||
(def shape-for-rename-ref
|
||||
(l/derived (l/in [:workspace-local :shape-for-rename]) st/state))
|
||||
|
||||
(mf/defc layer-name
|
||||
[{:keys [shape on-start-edit disabled-double-click on-stop-edit name-ref depth parent-size selected? type-comp type-frame hidden] :as props}]
|
||||
(let [local (mf/use-state {})
|
||||
|
|
@ -28,27 +28,25 @@
|
|||
start-edit (fn []
|
||||
(when (not disabled-double-click)
|
||||
(on-start-edit)
|
||||
(swap! local assoc :edition true)))
|
||||
(swap! local assoc :edition true)
|
||||
(st/emit! (dw/start-rename-shape (:id shape)))))
|
||||
|
||||
accept-edit (fn []
|
||||
(let [name-input (mf/ref-val name-ref)
|
||||
name (str/trim (dom/get-value name-input))
|
||||
main-instance? (:main-instance? shape)]
|
||||
name (str/trim (dom/get-value name-input))]
|
||||
(on-stop-edit)
|
||||
(swap! local assoc :edition false)
|
||||
(st/emit! (dw/end-rename-shape))
|
||||
(when-not (str/empty? name)
|
||||
(if main-instance?
|
||||
(dwl/rename-component-and-main-instance (:component-id shape) (:id shape) name nil)
|
||||
(st/emit! (dw/update-shape (:id shape) {:name name}))))))
|
||||
(st/emit! (dw/end-rename-shape name))))
|
||||
|
||||
cancel-edit (fn []
|
||||
(on-stop-edit)
|
||||
(swap! local assoc :edition false)
|
||||
(st/emit! (dw/end-rename-shape)))
|
||||
(st/emit! (dw/end-rename-shape nil)))
|
||||
|
||||
on-key-down (fn [event]
|
||||
(when (kbd/enter? event) (accept-edit))
|
||||
(when (kbd/esc? event) (cancel-edit)))
|
||||
|
||||
space-for-icons 110
|
||||
parent-size (str (- parent-size space-for-icons) "px")]
|
||||
|
||||
|
|
@ -87,4 +85,4 @@
|
|||
:ref name-ref
|
||||
:on-double-click start-edit}
|
||||
(:name shape "")
|
||||
(when (seq (:touched shape)) " *")])))
|
||||
(when (seq (:touched shape)) " *")])))
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@
|
|||
[app.main.data.workspace.shortcuts :as sc]
|
||||
[app.main.data.workspace.texts :as dwt]
|
||||
[app.main.data.workspace.undo :as dwu]
|
||||
[app.main.fonts :as fonts]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.context :as ctx]
|
||||
|
|
@ -23,7 +22,6 @@
|
|||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.timers :as ts]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(mf/defc text-align-options
|
||||
|
|
@ -159,11 +157,6 @@
|
|||
:on-click #(handle-change % "line-through")}
|
||||
i/strikethrough]]))
|
||||
|
||||
(defn generate-typography-name
|
||||
[{:keys [font-id font-variant-id] :as typography}]
|
||||
(let [{:keys [name]} (fonts/get-font-data font-id)]
|
||||
(assoc typography :name (str name " " (str/title font-variant-id)))))
|
||||
|
||||
(mf/defc text-menu
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [ids type values] :as props}]
|
||||
|
|
@ -215,7 +208,7 @@
|
|||
dwt/text-spacing-attrs
|
||||
dwt/text-transform-attrs)))
|
||||
typography (merge txt/default-typography set-values)
|
||||
typography (generate-typography-name typography)
|
||||
typography (dwt/generate-typography-name typography)
|
||||
id (uuid/next)]
|
||||
(st/emit! (dwl/add-typography (assoc typography :id id) false))
|
||||
(run! #(emit-update! % {:typography-ref-id id
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
[app.common.text :as txt]
|
||||
[app.main.data.fonts :as fts]
|
||||
[app.main.data.shortcuts :as dsc]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.fonts :as fonts]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
|
|
@ -24,8 +25,6 @@
|
|||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.keyboard :as kbd]
|
||||
[app.util.object :as obj]
|
||||
[app.util.router :as rt]
|
||||
[app.util.strings :as ust]
|
||||
[app.util.timers :as tm]
|
||||
[cuerdas.core :as str]
|
||||
|
|
@ -38,7 +37,7 @@
|
|||
(ust/format-precision value 2)))
|
||||
|
||||
(defn select-all [event]
|
||||
(dom/select-text! (dom/get-target event)))
|
||||
(some-> event dom/get-target dom/select-text!))
|
||||
|
||||
(defn- get-next-font
|
||||
[{:keys [id] :as current} fonts]
|
||||
|
|
@ -229,8 +228,8 @@
|
|||
[:div.fonts-list
|
||||
[:> rvt/AutoSizer {}
|
||||
(fn [props]
|
||||
(let [width (obj/get props "width")
|
||||
height (obj/get props "height")
|
||||
(let [width (unchecked-get props "width")
|
||||
height (unchecked-get props "height")
|
||||
render #(row-renderer fonts @selected on-select-and-close %)]
|
||||
(mf/html
|
||||
[:> rvt/List #js {:height height
|
||||
|
|
@ -241,9 +240,9 @@
|
|||
:rowRenderer render}])))]]]]))
|
||||
(defn row-renderer
|
||||
[fonts selected on-select props]
|
||||
(let [index (obj/get props "index")
|
||||
key (obj/get props "key")
|
||||
style (obj/get props "style")
|
||||
(let [index (unchecked-get props "index")
|
||||
key (unchecked-get props "key")
|
||||
style (unchecked-get props "style")
|
||||
font (nth fonts index)]
|
||||
(mf/html
|
||||
[:& font-item {:key key
|
||||
|
|
@ -253,7 +252,8 @@
|
|||
:current? (= (:id font) (:id selected))}])))
|
||||
|
||||
(mf/defc font-options
|
||||
[{:keys [values on-change on-blur show-recent] :as props}]
|
||||
{::mf/wrap-props false}
|
||||
[{:keys [values on-change on-blur show-recent]}]
|
||||
(let [{:keys [font-id font-size font-variant-id]} values
|
||||
|
||||
font-id (or font-id (:font-id txt/default-text-attrs))
|
||||
|
|
@ -371,7 +371,8 @@
|
|||
|
||||
|
||||
(mf/defc spacing-options
|
||||
[{:keys [values on-change on-blur] :as props}]
|
||||
{::mf/wrap-props false}
|
||||
[{:keys [values on-change on-blur]}]
|
||||
(let [{:keys [line-height
|
||||
letter-spacing]} values
|
||||
|
||||
|
|
@ -416,7 +417,8 @@
|
|||
:on-blur on-blur}]]]))
|
||||
|
||||
(mf/defc text-transform-options
|
||||
[{:keys [values on-change on-blur] :as props}]
|
||||
{::mf/wrap-props false}
|
||||
[{:keys [values on-change on-blur]}]
|
||||
(let [text-transform (or (:text-transform values) "none")
|
||||
handle-change
|
||||
(fn [_ type]
|
||||
|
|
@ -446,6 +448,7 @@
|
|||
i/titlecase]]))
|
||||
|
||||
(mf/defc typography-options
|
||||
{::mf/wrap-props false}
|
||||
[{:keys [ids editor values on-change on-blur show-recent]}]
|
||||
(let [opts #js {:editor editor
|
||||
:ids ids
|
||||
|
|
@ -460,19 +463,18 @@
|
|||
[:div.row-flex
|
||||
[:> text-transform-options opts]]]))
|
||||
|
||||
|
||||
;; TODO: this need to be refactored, right now combines too much logic
|
||||
;; and has a dropdown that behaves like a modal but is not a modal.
|
||||
;; In summary, this need to a good UX/UI/IMPL rework.
|
||||
|
||||
(mf/defc typography-entry
|
||||
[{:keys [typography local? selected? on-click on-change on-detach on-context-menu editing? focus-name? file open?]}]
|
||||
(let [hover-detach (mf/use-state false)
|
||||
{::mf/wrap-props false}
|
||||
[{:keys [file-id typography local? selected? on-click on-change on-detach on-context-menu editing? focus-name? external-open*]}]
|
||||
(let [hover-detach* (mf/use-state false)
|
||||
hover-detach? (deref hover-detach*)
|
||||
|
||||
name-input-ref (mf/use-ref)
|
||||
on-change-ref (mf/use-ref nil)
|
||||
workspace-read-only? (mf/use-ctx ctx/workspace-read-only?)
|
||||
editable? (and local? (not workspace-read-only?))
|
||||
open? (if (nil? open?) (mf/use-state editing?) open?)
|
||||
read-only? (mf/use-ctx ctx/workspace-read-only?)
|
||||
editable? (and local? (not read-only?))
|
||||
|
||||
open* (mf/use-state editing?)
|
||||
open? (deref open*)
|
||||
|
||||
on-name-blur
|
||||
(mf/use-callback
|
||||
|
|
@ -480,11 +482,36 @@
|
|||
(fn [event]
|
||||
(let [name (dom/get-target-val event)]
|
||||
(when-not (str/blank? name)
|
||||
(on-change {:name name})))))]
|
||||
(on-change {:name name})))))
|
||||
|
||||
on-pointer-enter
|
||||
(mf/use-fn #(reset! hover-detach* true))
|
||||
|
||||
on-pointer-leave
|
||||
(mf/use-fn #(reset! hover-detach* false))
|
||||
|
||||
on-open
|
||||
(mf/use-fn #(reset! open* true))
|
||||
|
||||
on-close
|
||||
(mf/use-fn #(reset! open* false))
|
||||
|
||||
navigate-to-library
|
||||
(mf/use-fn
|
||||
(mf/deps file-id)
|
||||
(fn []
|
||||
(when file-id
|
||||
(st/emit! (dw/navigate-to-library file-id)))))
|
||||
|
||||
]
|
||||
|
||||
(mf/with-effect [editing?]
|
||||
(when editing?
|
||||
(reset! open? editing?)))
|
||||
(reset! open* editing?)))
|
||||
|
||||
(mf/with-effect [open?]
|
||||
(when (some? external-open*)
|
||||
(reset! external-open* open?)))
|
||||
|
||||
(mf/with-effect [focus-name?]
|
||||
(when focus-name?
|
||||
|
|
@ -493,15 +520,12 @@
|
|||
(dom/focus! node)
|
||||
(dom/select-text! node)))))
|
||||
|
||||
(mf/with-effect [on-change]
|
||||
(mf/set-ref-val! on-change-ref {:on-change on-change}))
|
||||
|
||||
[:*
|
||||
[:div.element-set-options-group.typography-entry
|
||||
{:class (when selected? "selected")
|
||||
:style {:display (when @open? "none")}}
|
||||
{:class (when ^boolean selected? "selected")
|
||||
:style {:display (when ^boolean open? "none")}}
|
||||
[:div.typography-selection-wrapper
|
||||
{:class (when on-click "is-selectable")
|
||||
{:class (when ^boolean on-click "is-selectable")
|
||||
:on-click on-click
|
||||
:on-context-menu on-context-menu}
|
||||
[:div.typography-sample
|
||||
|
|
@ -511,20 +535,36 @@
|
|||
(tr "workspace.assets.typography.sample")]
|
||||
[:div.typography-name {:title (:name typography)}(:name typography)]]
|
||||
[:div.element-set-actions
|
||||
(when on-detach
|
||||
(when ^boolean on-detach
|
||||
[:div.element-set-actions-button
|
||||
{:on-pointer-enter #(reset! hover-detach true)
|
||||
:on-pointer-leave #(reset! hover-detach false)
|
||||
{:on-pointer-enter on-pointer-enter
|
||||
:on-pointer-leave on-pointer-leave
|
||||
:on-click on-detach}
|
||||
(if @hover-detach i/unchain i/chain)])
|
||||
(if ^boolean hover-detach? i/unchain i/chain)])
|
||||
|
||||
[:div.element-set-actions-button
|
||||
{:on-click #(reset! open? true)}
|
||||
{:on-click on-open}
|
||||
i/actions]]]
|
||||
|
||||
[:& advanced-options {:visible? @open?
|
||||
:on-close #(reset! open? false)}
|
||||
(if (not editable?)
|
||||
[:& advanced-options {:visible? open? :on-close on-close}
|
||||
(if ^boolean editable?
|
||||
[:*
|
||||
[:div.element-set-content
|
||||
[:div.row-flex
|
||||
[:input.element-name.adv-typography-name
|
||||
{:type "text"
|
||||
:ref name-input-ref
|
||||
:default-value (:name typography)
|
||||
:on-blur on-name-blur}]
|
||||
|
||||
[:div.element-set-actions-button
|
||||
{:on-click on-close}
|
||||
i/actions]]]
|
||||
|
||||
[:& typography-options {:values typography
|
||||
:on-change on-change
|
||||
:show-recent false}]]
|
||||
|
||||
[:div.element-set-content.typography-read-only-data
|
||||
[:div.row-flex.typography-name
|
||||
[:span {:title (:name typography)} (:name typography)]]
|
||||
|
|
@ -534,7 +574,7 @@
|
|||
[:span (:font-id typography)]]
|
||||
|
||||
[:div.element-set-actions-button.actions-inside
|
||||
{:on-click #(reset! open? false)}
|
||||
{:on-click on-close}
|
||||
i/actions]
|
||||
|
||||
[:div.row-flex
|
||||
|
|
@ -560,25 +600,7 @@
|
|||
(when-not local?
|
||||
[:div.row-flex
|
||||
[:a.go-to-lib-button
|
||||
{:on-click #(st/emit! (rt/nav-new-window* {:rname :workspace
|
||||
:path-params {:project-id (:project-id file)
|
||||
:file-id (:id file)}
|
||||
:query-params {:page-id (get-in file [:data :pages 0])}}))}
|
||||
{:on-click navigate-to-library}
|
||||
(tr "workspace.assets.typography.go-to-edit")]])]
|
||||
|
||||
[:*
|
||||
[:div.element-set-content
|
||||
[:div.row-flex
|
||||
[:input.element-name.adv-typography-name
|
||||
{:type "text"
|
||||
:ref name-input-ref
|
||||
:default-value (:name typography)
|
||||
:on-blur on-name-blur}]
|
||||
|
||||
[:div.element-set-actions-button
|
||||
{:on-click #(reset! open? false)}
|
||||
i/actions]]]
|
||||
|
||||
[:& typography-options {:values typography
|
||||
:on-change on-change
|
||||
:show-recent false}]])]]))
|
||||
)]]))
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
(ns app.main.ui.workspace.sidebar.shortcuts
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.config :as cf]
|
||||
[app.main.data.dashboard.shortcuts]
|
||||
[app.main.data.events :as ev]
|
||||
|
|
@ -213,7 +214,7 @@
|
|||
[{:keys [content command] :as props}]
|
||||
(let [managed-list (if (coll? content)
|
||||
content
|
||||
(conj () content))
|
||||
(conj () content))
|
||||
chars-list (map ds/split-sc managed-list)
|
||||
last-element (last chars-list)
|
||||
short-char-list (if (= 1 (count chars-list))
|
||||
|
|
@ -224,13 +225,16 @@
|
|||
(for [chars short-char-list]
|
||||
[:*
|
||||
(for [char chars]
|
||||
[:& converted-chars {:char char :command command}])
|
||||
[:& converted-chars {:key (dm/str char "-" (name command))
|
||||
:char char
|
||||
:command command}])
|
||||
(when (not= chars penultimate) [:span.space ","])])
|
||||
(when (not= last-element penultimate)
|
||||
[:*
|
||||
[:span.space (tr "shortcuts.or")]
|
||||
(for [char last-element]
|
||||
[:& converted-chars {:char char
|
||||
[:& converted-chars {:key (dm/str char "-" (name command))
|
||||
:char char
|
||||
:command command}])])]))
|
||||
|
||||
(mf/defc shortcut-row
|
||||
|
|
@ -463,7 +467,7 @@
|
|||
(when (kbd/enter? event)
|
||||
(on-search-clear-click)
|
||||
(dom/focus! (dom/get-element "shortcut-search")))))]
|
||||
|
||||
|
||||
(mf/with-effect []
|
||||
(dom/focus! (dom/get-element "shortcut-search")))
|
||||
|
||||
|
|
|
|||
|
|
@ -1,151 +0,0 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.main.ui.workspace.sidebar.sidebar
|
||||
(:require-macros [app.main.style :refer [css]])
|
||||
(:require
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.tab-container :refer [tab-container tab-element]]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.hooks.resize :refer [use-resize-hook]]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.ui.workspace.comments :refer [comments-sidebar]]
|
||||
[app.main.ui.workspace.sidebar.assets :refer [assets-toolbox]]
|
||||
[app.main.ui.workspace.sidebar.debug :refer [debug-panel]]
|
||||
[app.main.ui.workspace.sidebar.history :refer [history-toolbox]]
|
||||
[app.main.ui.workspace.sidebar.layers :refer [layers-toolbox]]
|
||||
[app.main.ui.workspace.sidebar.options :refer [options-toolbox]]
|
||||
[app.main.ui.workspace.sidebar.shortcuts :refer [shortcuts-container]]
|
||||
[app.main.ui.workspace.sidebar.sitemap :refer [sitemap]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[app.util.object :as obj]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
;; --- Left Sidebar (Component)
|
||||
|
||||
(mf/defc left-sidebar
|
||||
{:wrap [mf/memo]}
|
||||
[{:keys [layout] :as props}]
|
||||
(let [options-mode (mf/deref refs/options-mode-global)
|
||||
mode-inspect? (= options-mode :inspect)
|
||||
section (cond (or mode-inspect? (contains? layout :layers)) :layers
|
||||
(contains? layout :assets) :assets)
|
||||
shortcuts? (contains? layout :shortcuts)
|
||||
show-debug? (contains? layout :debug-panel)
|
||||
new-css-system (mf/use-ctx ctx/new-css-system)
|
||||
{:keys [on-pointer-down on-lost-pointer-capture on-pointer-move parent-ref size]}
|
||||
(use-resize-hook :left-sidebar 255 255 500 :x false :left)
|
||||
|
||||
handle-collapse
|
||||
(fn []
|
||||
(st/emit! (dw/toggle-layout-flag :collapse-left-sidebar)))]
|
||||
|
||||
[:aside {:ref parent-ref
|
||||
:class (if new-css-system
|
||||
(dom/classnames (css :left-settings-bar) true)
|
||||
(dom/classnames :settings-bar true
|
||||
:settings-bar-left true
|
||||
:two-row (<= size 300)
|
||||
:three-row (and (> size 300) (<= size 400))
|
||||
:four-row (> size 400)))
|
||||
:style #js {"--width" (str size "px")}}
|
||||
|
||||
[:div {:on-pointer-down on-pointer-down
|
||||
:on-lost-pointer-capture on-lost-pointer-capture
|
||||
:on-pointer-move on-pointer-move
|
||||
:class (if new-css-system
|
||||
(dom/classnames (css :resize-area) true)
|
||||
(dom/classnames :resize-area true))}]
|
||||
[:div {:class (if new-css-system
|
||||
(dom/classnames (css :settings-bar-inside) true)
|
||||
(dom/classnames :settings-bar-inside true))}
|
||||
(cond
|
||||
shortcuts?
|
||||
[:& shortcuts-container]
|
||||
|
||||
show-debug?
|
||||
[:& debug-panel]
|
||||
|
||||
:else
|
||||
[:*
|
||||
|
||||
(if new-css-system
|
||||
[:& tab-container {:on-change-tab #(st/emit! (dw/go-to-layout %))
|
||||
:selected section
|
||||
:shortcuts? shortcuts?
|
||||
:collapsable? true
|
||||
:handle-collapse handle-collapse}
|
||||
[:& tab-element {:id :layers
|
||||
:title (tr "workspace.sidebar.layers")}
|
||||
[:div {:class (dom/classnames (css :layers-tab) true)}
|
||||
[:& sitemap {:layout layout}]
|
||||
[:& layers-toolbox {:size-parent size}]]]
|
||||
|
||||
(when-not mode-inspect?
|
||||
[:& tab-element {:id :assets :title (tr "workspace.toolbar.assets")}
|
||||
[:& assets-toolbox]])]
|
||||
|
||||
[:*
|
||||
[:button.collapse-sidebar
|
||||
{:on-click handle-collapse
|
||||
:aria-label (tr "workspace.sidebar.collapse")}
|
||||
i/arrow-slide]
|
||||
[:& tab-container {:on-change-tab #(st/emit! (dw/go-to-layout %))
|
||||
:selected section
|
||||
:shortcuts? shortcuts?
|
||||
:collapsable? true
|
||||
:handle-collapse handle-collapse}
|
||||
[:& tab-element {:id :layers
|
||||
:title (tr "workspace.sidebar.layers")}
|
||||
[:div {:class (dom/classnames :layers-tab true)}
|
||||
[:& sitemap {:layout layout}]
|
||||
[:& layers-toolbox {:size-parent size}]]]
|
||||
|
||||
(when-not mode-inspect?
|
||||
[:& tab-element {:id :assets :title (tr "workspace.toolbar.assets")}
|
||||
[:& assets-toolbox]])]])])]]))
|
||||
|
||||
;; --- Right Sidebar (Component)
|
||||
|
||||
(mf/defc right-sidebar
|
||||
{::mf/wrap-props false
|
||||
::mf/wrap [mf/memo]}
|
||||
[props]
|
||||
(let [layout (obj/get props "layout")
|
||||
section (obj/get props "section")
|
||||
drawing-tool (:tool (mf/deref refs/workspace-drawing))
|
||||
|
||||
is-comments? (= drawing-tool :comments)
|
||||
is-history? (contains? layout :document-history)
|
||||
is-inspect? (= section :inspect)
|
||||
|
||||
expanded? (mf/deref refs/inspect-expanded)
|
||||
can-be-expanded? (and
|
||||
(not is-comments?)
|
||||
(not is-history?)
|
||||
is-inspect?)]
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps can-be-expanded?)
|
||||
(fn []
|
||||
(when (not can-be-expanded?)
|
||||
(st/emit! (dw/set-inspect-expanded false)))))
|
||||
|
||||
[:aside.settings-bar.settings-bar-right {:class (when (and can-be-expanded? expanded?) "expanded")}
|
||||
[:div.settings-bar-inside
|
||||
(cond
|
||||
is-comments?
|
||||
[:& comments-sidebar]
|
||||
|
||||
is-history?
|
||||
[:& history-toolbox]
|
||||
|
||||
:else
|
||||
[:> options-toolbox props])]]))
|
||||
|
||||
|
|
@ -258,7 +258,6 @@
|
|||
(hooks/setup-viewport-modifiers modifiers base-objects)
|
||||
(hooks/setup-shortcuts node-editing? drawing-path? text-editing?)
|
||||
(hooks/setup-active-frames base-objects hover-ids selected active-frames zoom transform vbox)
|
||||
(hooks/setup-page-loaded page-id)
|
||||
|
||||
[:div.viewport
|
||||
[:div.viewport-overlays
|
||||
|
|
|
|||
|
|
@ -224,7 +224,7 @@
|
|||
|
||||
(defn on-context-menu
|
||||
[hover hover-ids workspace-read-only?]
|
||||
(mf/use-callback
|
||||
(mf/use-fn
|
||||
(mf/deps @hover @hover-ids workspace-read-only?)
|
||||
(fn [event]
|
||||
(if workspace-read-only?
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@
|
|||
[app.main.worker :as uw]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.globals :as globals]
|
||||
[app.util.timers :as timers]
|
||||
[beicon.core :as rx]
|
||||
[debug :refer [debug?]]
|
||||
[goog.events :as events]
|
||||
|
|
@ -55,22 +54,13 @@
|
|||
(events/unlistenByKey key))))))))
|
||||
|
||||
(defn setup-viewport-size [vport viewport-ref]
|
||||
(mf/use-layout-effect
|
||||
(mf/deps vport)
|
||||
(fn []
|
||||
(when-not vport
|
||||
(let [node (mf/ref-val viewport-ref)
|
||||
prnt (dom/get-parent node)]
|
||||
;; We schedule the event so it fires after `initialize-page` event
|
||||
(timers/schedule #(st/emit! (dw/initialize-viewport (dom/get-client-size prnt)))))))))
|
||||
(mf/with-effect [vport]
|
||||
(let [node (mf/ref-val viewport-ref)
|
||||
prnt (dom/get-parent node)
|
||||
size (dom/get-client-size prnt)]
|
||||
|
||||
|
||||
(defn setup-page-loaded [page-id]
|
||||
(mf/use-effect
|
||||
(mf/deps page-id)
|
||||
(fn []
|
||||
;; We schedule the event so it fires after `initialize-page` event
|
||||
(timers/schedule #(st/emit! (dw/page-loaded page-id))))))
|
||||
(when (not= size vport)
|
||||
(st/emit! (dw/initialize-viewport (dom/get-client-size prnt)))))))
|
||||
|
||||
(defn setup-cursor [cursor alt? mod? space? panning drawing-tool drawing-path? path-editing? z? workspace-read-only?]
|
||||
(mf/use-effect
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@
|
|||
|
||||
(defn point->viewport
|
||||
[pt]
|
||||
(let [zoom (dm/get-in @st/state [:workspace-local :zoom])]
|
||||
(let [zoom (dm/get-in @st/state [:workspace-local :zoom] 1)]
|
||||
(when (and (some? @viewport-ref)
|
||||
(some? @viewport-brect))
|
||||
(let [vbox (.. ^js @viewport-ref -viewBox -baseVal)
|
||||
|
|
|
|||
Loading…
Reference in New Issue