Merge pull request #3206 from penpot/niwinz-workspace-assets-component-performance

 Improve performance of workspace assets sidebar
This commit is contained in:
Alejandro 2023-05-26 08:10:53 +02:00 committed by GitHub
commit 6000dc251d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 3053 additions and 2471 deletions

View File

@ -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"}

View File

@ -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)

View File

@ -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

View File

@ -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]

View File

@ -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

View File

@ -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))))

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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]]

View File

@ -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]

View File

@ -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"}

View File

@ -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"

View File

@ -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#))))))))

View File

@ -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]

View File

@ -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)))

View File

@ -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)

View File

@ -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]}]

View File

@ -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]

View File

@ -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]

View File

@ -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]

View File

@ -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"
}

View File

@ -98,3 +98,6 @@
(fn [_ _ old-value current-value]
(when (not= old-value current-value)
(reinit))))
(set! (.-stackTraceLimit js/Error) 50)

View File

@ -43,4 +43,3 @@
(watch [_ _ _]
(->> (rp/cmd! :delete-share-link {:id id})
(rx/ignore)))))

View File

@ -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

View File

@ -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]

View File

@ -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)

View File

@ -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})

View File

@ -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)))))))

View File

@ -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)))

View File

@ -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

View File

@ -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})))

View File

@ -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

View File

@ -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

View File

@ -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])]]]))

View File

@ -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}])]]]]))

View File

@ -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

View File

@ -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])))

View File

@ -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)) " *")])))

View File

@ -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

View File

@ -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}]])]]))
)]]))

View File

@ -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")))

View File

@ -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])]]))

View File

@ -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

View File

@ -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?

View File

@ -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

View File

@ -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)