🎉 Make components-v2 an optional feature

This commit is contained in:
Andrés Moya 2022-07-12 13:52:48 +02:00
parent 1ef37281e6
commit a5bf1c03e7
40 changed files with 495 additions and 296 deletions

View file

@ -46,7 +46,7 @@
(s/def ::is-shared ::us/boolean) (s/def ::is-shared ::us/boolean)
(s/def ::create-file (s/def ::create-file
(s/keys :req-un [::profile-id ::name ::project-id] (s/keys :req-un [::profile-id ::name ::project-id]
:opt-un [::id ::is-shared])) :opt-un [::id ::is-shared ::components-v2]))
(sv/defmethod ::create-file (sv/defmethod ::create-file
[{:keys [pool] :as cfg} {:keys [profile-id project-id] :as params}] [{:keys [pool] :as cfg} {:keys [profile-id project-id] :as params}]
@ -66,11 +66,12 @@
(defn create-file (defn create-file
[conn {:keys [id name project-id is-shared data revn [conn {:keys [id name project-id is-shared data revn
modified-at deleted-at ignore-sync-until] modified-at deleted-at ignore-sync-until
components-v2]
:or {is-shared false revn 0} :or {is-shared false revn 0}
:as params}] :as params}]
(let [id (or id (:id data) (uuid/next)) (let [id (or id (:id data) (uuid/next))
data (or data (ctf/make-file-data id)) data (or data (ctf/make-file-data id components-v2))
file (db/insert! conn :file file (db/insert! conn :file
(d/without-nils (d/without-nils
{:id id {:id id
@ -317,10 +318,11 @@
(s/def ::session-id ::us/uuid) (s/def ::session-id ::us/uuid)
(s/def ::revn ::us/integer) (s/def ::revn ::us/integer)
(s/def ::components-v2 ::us/boolean)
(s/def ::update-file (s/def ::update-file
(s/and (s/and
(s/keys :req-un [::id ::session-id ::profile-id ::revn] (s/keys :req-un [::id ::session-id ::profile-id ::revn]
:opt-un [::changes ::changes-with-metadata]) :opt-un [::changes ::changes-with-metadata ::components-v2])
(fn [o] (fn [o]
(or (contains? o :changes) (or (contains? o :changes)
(contains? o :changes-with-metadata))))) (contains? o :changes-with-metadata)))))
@ -357,7 +359,8 @@
(simpl/del-object backend file)))) (simpl/del-object backend file))))
(defn- update-file (defn- update-file
[{:keys [conn metrics] :as cfg} {:keys [file changes changes-with-metadata session-id profile-id] :as params}] [{:keys [conn metrics] :as cfg}
{:keys [file changes changes-with-metadata session-id profile-id components-v2] :as params}]
(when (> (:revn params) (when (> (:revn params)
(:revn file)) (:revn file))
@ -382,12 +385,18 @@
(update :data (fn [data] (update :data (fn [data]
;; Trace the length of bytes of processed data ;; Trace the length of bytes of processed data
(mtx/run! metrics {:id :update-file-bytes-processed :inc (alength data)}) (mtx/run! metrics {:id :update-file-bytes-processed :inc (alength data)})
(-> data (cond-> data
(blob/decode) :always
(assoc :id (:id file)) (-> (blob/decode)
(pmg/migrate-data) (assoc :id (:id file))
(cp/process-changes changes) (pmg/migrate-data))
(blob/encode)))))]
components-v2
(ctf/migrate-to-components-v2)
:always
(-> (cp/process-changes changes)
(blob/encode))))))]
;; Insert change to the xlog ;; Insert change to the xlog
(db/insert! conn :file-change (db/insert! conn :file-change
{:id (uuid/next) {:id (uuid/next)

View file

@ -13,6 +13,7 @@
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.common.pages.migrations :as pmg] [app.common.pages.migrations :as pmg]
[app.common.spec :as us] [app.common.spec :as us]
[app.common.types.file :as ctf]
[app.common.types.shape-tree :as ctt] [app.common.types.shape-tree :as ctt]
[app.db :as db] [app.db :as db]
[app.db.sql :as sql] [app.db.sql :as sql]
@ -27,7 +28,6 @@
[cuerdas.core :as str])) [cuerdas.core :as str]))
(declare decode-row) (declare decode-row)
(declare decode-row-xf)
;; --- Helpers & Specs ;; --- Helpers & Specs
@ -39,6 +39,7 @@
(s/def ::profile-id ::us/uuid) (s/def ::profile-id ::us/uuid)
(s/def ::team-id ::us/uuid) (s/def ::team-id ::us/uuid)
(s/def ::search-term ::us/string) (s/def ::search-term ::us/string)
(s/def ::components-v2 ::us/boolean)
;; --- Query: File Permissions ;; --- Query: File Permissions
@ -123,8 +124,7 @@
(defn check-comment-permissions! (defn check-comment-permissions!
[conn profile-id file-id share-id] [conn profile-id file-id share-id]
(let [can-read (has-read-permissions? conn profile-id file-id) (let [can-read (has-read-permissions? conn profile-id file-id)
can-comment (has-comment-permissions? conn profile-id file-id share-id) can-comment (has-comment-permissions? conn profile-id file-id share-id)]
]
(when-not (or can-read can-comment) (when-not (or can-read can-comment)
(ex/raise :type :not-found (ex/raise :type :not-found
:code :object-not-found :code :object-not-found
@ -227,20 +227,29 @@
(d/index-by :object-id :data)))))) (d/index-by :object-id :data))))))
(defn retrieve-file (defn retrieve-file
[{:keys [pool] :as cfg} id] [{:keys [pool] :as cfg} id components-v2]
(->> (db/get-by-id pool :file id) (let [file (->> (db/get-by-id pool :file id)
(decode-row) (decode-row)
(pmg/migrate-file))) (pmg/migrate-file))]
(if components-v2
(update file :data ctf/migrate-to-components-v2)
(if (get-in file [:data :options :components-v2])
(ex/raise :type :restriction
:code :feature-disabled
:hint "tried to open a components-v2 file with feature disabled")
file))))
(s/def ::file (s/def ::file
(s/keys :req-un [::profile-id ::id])) (s/keys :req-un [::profile-id ::id]
:opt-un [::components-v2]))
(sv/defmethod ::file (sv/defmethod ::file
"Retrieve a file by its ID. Only authenticated users." "Retrieve a file by its ID. Only authenticated users."
[{:keys [pool] :as cfg} {:keys [profile-id id] :as params}] [{:keys [pool] :as cfg} {:keys [profile-id id components-v2] :as params}]
(let [perms (get-permissions pool profile-id id)] (let [perms (get-permissions pool profile-id id)]
(check-read-permissions! perms) (check-read-permissions! perms)
(let [file (retrieve-file cfg id) (let [file (retrieve-file cfg id components-v2)
thumbs (retrieve-object-thumbnails cfg id)] thumbs (retrieve-object-thumbnails cfg id)]
(-> file (-> file
(assoc :thumbnails thumbs) (assoc :thumbnails thumbs)
@ -269,7 +278,7 @@
(s/def ::page (s/def ::page
(s/and (s/and
(s/keys :req-un [::profile-id ::file-id] (s/keys :req-un [::profile-id ::file-id]
:opt-un [::page-id ::object-id]) :opt-un [::page-id ::object-id ::components-v2])
(fn [obj] (fn [obj]
(if (contains? obj :object-id) (if (contains? obj :object-id)
(contains? obj :page-id) (contains? obj :page-id)
@ -285,9 +294,9 @@
mandatory. mandatory.
Mainly used for rendering purposes." Mainly used for rendering purposes."
[{:keys [pool] :as cfg} {:keys [profile-id file-id page-id object-id] :as props}] [{:keys [pool] :as cfg} {:keys [profile-id file-id page-id object-id components-v2] :as props}]
(check-read-permissions! pool profile-id file-id) (check-read-permissions! pool profile-id file-id)
(let [file (retrieve-file cfg file-id) (let [file (retrieve-file cfg file-id components-v2)
page-id (or page-id (-> file :data :pages first)) page-id (or page-id (-> file :data :pages first))
page (get-in file [:data :pages-index page-id])] page (get-in file [:data :pages-index page-id])]
@ -374,14 +383,15 @@
(update :objects assoc-thumbnails page-id thumbs))))) (update :objects assoc-thumbnails page-id thumbs)))))
(s/def ::file-data-for-thumbnail (s/def ::file-data-for-thumbnail
(s/keys :req-un [::profile-id ::file-id])) (s/keys :req-un [::profile-id ::file-id]
:opt-in [::components-v2]))
(sv/defmethod ::file-data-for-thumbnail (sv/defmethod ::file-data-for-thumbnail
"Retrieves the data for generate the thumbnail of the file. Used "Retrieves the data for generate the thumbnail of the file. Used
mainly for render thumbnails on dashboard." mainly for render thumbnails on dashboard."
[{:keys [pool] :as cfg} {:keys [profile-id file-id] :as props}] [{:keys [pool] :as cfg} {:keys [profile-id file-id components-v2] :as props}]
(check-read-permissions! pool profile-id file-id) (check-read-permissions! pool profile-id file-id)
(let [file (retrieve-file cfg file-id)] (let [file (retrieve-file cfg file-id components-v2)]
{:file-id file-id {:file-id file-id
:revn (:revn file) :revn (:revn file)
:page (get-file-thumbnail-data cfg file)})) :page (get-file-thumbnail-data cfg file)}))
@ -523,7 +533,3 @@
(cond-> row (cond-> row
changes (assoc :changes (blob/decode changes)) changes (assoc :changes (blob/decode changes))
data (assoc :data (blob/decode data))))) data (assoc :data (blob/decode data)))))
(def decode-row-xf
(comp (map decode-row)
(map pmg/migrate-file)))

View file

@ -23,8 +23,8 @@
(db/get-by-id pool :project id {:columns [:id :name :team-id]})) (db/get-by-id pool :project id {:columns [:id :name :team-id]}))
(defn- retrieve-bundle (defn- retrieve-bundle
[{:keys [pool] :as cfg} file-id profile-id] [{:keys [pool] :as cfg} file-id profile-id components-v2]
(p/let [file (files/retrieve-file cfg file-id) (p/let [file (files/retrieve-file cfg file-id components-v2)
project (retrieve-project pool (:project-id file)) project (retrieve-project pool (:project-id file))
libs (files/retrieve-file-libraries cfg false file-id) libs (files/retrieve-file-libraries cfg false file-id)
users (comments/retrieve-file-comments-users pool file-id profile-id) users (comments/retrieve-file-comments-users pool file-id profile-id)
@ -47,14 +47,14 @@
(s/def ::share-id ::us/uuid) (s/def ::share-id ::us/uuid)
(s/def ::view-only-bundle (s/def ::view-only-bundle
(s/keys :req-un [::file-id] :opt-un [::profile-id ::share-id])) (s/keys :req-un [::file-id] :opt-un [::profile-id ::share-id ::components-v2]))
(sv/defmethod ::view-only-bundle {:auth false} (sv/defmethod ::view-only-bundle {:auth false}
[{:keys [pool] :as cfg} {:keys [profile-id file-id share-id] :as params}] [{:keys [pool] :as cfg} {:keys [profile-id file-id share-id components-v2] :as params}]
(p/let [slink (slnk/retrieve-share-link pool file-id share-id) (p/let [slink (slnk/retrieve-share-link pool file-id share-id)
perms (files/get-permissions pool profile-id file-id share-id) perms (files/get-permissions pool profile-id file-id share-id)
thumbs (files/retrieve-object-thumbnails cfg file-id) thumbs (files/retrieve-object-thumbnails cfg file-id)
bundle (p/-> (retrieve-bundle cfg file-id profile-id) bundle (p/-> (retrieve-bundle cfg file-id profile-id components-v2)
(assoc :permissions perms) (assoc :permissions perms)
(assoc-in [:file :thumbnails] thumbs))] (assoc-in [:file :thumbnails] thumbs))]

View file

@ -32,7 +32,8 @@
:project-id proj-id :project-id proj-id
:id file-id :id file-id
:name "foobar" :name "foobar"
:is-shared false} :is-shared false
:components-v2 true}
out (th/mutation! data)] out (th/mutation! data)]
;; (th/print-result! out) ;; (th/print-result! out)
@ -71,7 +72,8 @@
(t/testing "query single file without users" (t/testing "query single file without users"
(let [data {::th/type :file (let [data {::th/type :file
:profile-id (:id prof) :profile-id (:id prof)
:id file-id} :id file-id
:components-v2 true}
out (th/query! data)] out (th/query! data)]
;; (th/print-result! out) ;; (th/print-result! out)
@ -95,7 +97,8 @@
(t/testing "query single file after delete" (t/testing "query single file after delete"
(let [data {::th/type :file (let [data {::th/type :file
:profile-id (:id prof) :profile-id (:id prof)
:id file-id} :id file-id
:components-v2 true}
out (th/query! data)] out (th/query! data)]
;; (th/print-result! out) ;; (th/print-result! out)
@ -143,6 +146,7 @@
:session-id (uuid/random) :session-id (uuid/random)
:profile-id profile-id :profile-id profile-id
:revn revn :revn revn
:components-v2 true
:changes changes} :changes changes}
out (th/mutation! params)] out (th/mutation! params)]
(t/is (nil? (:error out))) (t/is (nil? (:error out)))
@ -171,6 +175,7 @@
:id shid :id shid
:parent-id uuid/zero :parent-id uuid/zero
:frame-id uuid/zero :frame-id uuid/zero
:components-v2 true
:obj {:id shid :obj {:id shid
:name "image" :name "image"
:frame-id uuid/zero :frame-id uuid/zero
@ -246,7 +251,8 @@
:profile-id (:id profile2) :profile-id (:id profile2)
:project-id (:default-project-id profile1) :project-id (:default-project-id profile1)
:name "foobar" :name "foobar"
:is-shared false} :is-shared false
:components-v2 true}
out (th/mutation! data) out (th/mutation! data)
error (:error out)] error (:error out)]
@ -462,6 +468,7 @@
(th/update-file* {:file-id (:id file) (th/update-file* {:file-id (:id file)
:profile-id (:id prof) :profile-id (:id prof)
:revn 0 :revn 0
:components-v2 true
:changes changes}) :changes changes})
(t/testing "RPC page query (rendering purposes)" (t/testing "RPC page query (rendering purposes)"
@ -469,7 +476,8 @@
;; Query :page RPC method without passing page-id ;; Query :page RPC method without passing page-id
(let [data {::th/type :page (let [data {::th/type :page
:profile-id (:id prof) :profile-id (:id prof)
:file-id (:id file)} :file-id (:id file)
:components-v2 true}
{:keys [error result] :as out} (th/query! data)] {:keys [error result] :as out} (th/query! data)]
;; (th/print-result! out) ;; (th/print-result! out)
@ -485,7 +493,8 @@
(let [data {::th/type :page (let [data {::th/type :page
:profile-id (:id prof) :profile-id (:id prof)
:file-id (:id file) :file-id (:id file)
:page-id page-id} :page-id page-id
:components-v2 true}
{:keys [error result] :as out} (th/query! data)] {:keys [error result] :as out} (th/query! data)]
;; (th/print-result! out) ;; (th/print-result! out)
(t/is (map? result)) (t/is (map? result))
@ -501,7 +510,8 @@
:profile-id (:id prof) :profile-id (:id prof)
:file-id (:id file) :file-id (:id file)
:page-id page-id :page-id page-id
:object-id frame1-id} :object-id frame1-id
:components-v2 true}
{:keys [error result] :as out} (th/query! data)] {:keys [error result] :as out} (th/query! data)]
;; (th/print-result! out) ;; (th/print-result! out)
(t/is (map? result)) (t/is (map? result))
@ -516,7 +526,8 @@
(let [data {::th/type :page (let [data {::th/type :page
:profile-id (:id prof) :profile-id (:id prof)
:file-id (:id file) :file-id (:id file)
:object-id frame1-id} :object-id frame1-id
:components-v2 true}
{:keys [error result] :as out} (th/query! data)] {:keys [error result] :as out} (th/query! data)]
;; (th/print-result! out) ;; (th/print-result! out)
(t/is (= :validation (th/ex-type error))) (t/is (= :validation (th/ex-type error)))
@ -537,7 +548,8 @@
;; Check the result ;; Check the result
(let [data {::th/type :file-data-for-thumbnail (let [data {::th/type :file-data-for-thumbnail
:profile-id (:id prof) :profile-id (:id prof)
:file-id (:id file)} :file-id (:id file)
:components-v2 true}
{:keys [error result] :as out} (th/query! data)] {:keys [error result] :as out} (th/query! data)]
;; (th/print-result! out) ;; (th/print-result! out)
(t/is (map? result)) (t/is (map? result))
@ -562,7 +574,8 @@
;; Check the result ;; Check the result
(let [data {::th/type :file-data-for-thumbnail (let [data {::th/type :file-data-for-thumbnail
:profile-id (:id prof) :profile-id (:id prof)
:file-id (:id file)} :file-id (:id file)
:components-v2 true}
{:keys [error result] :as out} (th/query! data)] {:keys [error result] :as out} (th/query! data)]
;; (th/print-result! out) ;; (th/print-result! out)
(t/is (map? result)) (t/is (map? result))

View file

@ -30,7 +30,8 @@
(let [data {::th/type :view-only-bundle (let [data {::th/type :view-only-bundle
:profile-id (:id prof) :profile-id (:id prof)
:file-id (:id file) :file-id (:id file)
:page-id (get-in file [:data :pages 0])} :page-id (get-in file [:data :pages 0])
:components-v2 true}
out (th/query! data)] out (th/query! data)]
@ -63,7 +64,8 @@
(let [data {::th/type :view-only-bundle (let [data {::th/type :view-only-bundle
:profile-id (:id prof2) :profile-id (:id prof2)
:file-id (:id file) :file-id (:id file)
:page-id (get-in file [:data :pages 0])} :page-id (get-in file [:data :pages 0])
:components-v2 true}
out (th/query! data)] out (th/query! data)]
;; (th/print-result! out) ;; (th/print-result! out)
@ -78,7 +80,8 @@
:profile-id (:id prof2) :profile-id (:id prof2)
:share-id @share-id :share-id @share-id
:file-id (:id file) :file-id (:id file)
:page-id (get-in file [:data :pages 0])} :page-id (get-in file [:data :pages 0])
:components-v2 true}
out (th/query! data)] out (th/query! data)]
;; (th/print-result! out) ;; (th/print-result! out)
@ -93,7 +96,8 @@
(let [data {::th/type :view-only-bundle (let [data {::th/type :view-only-bundle
:share-id @share-id :share-id @share-id
:file-id (:id file) :file-id (:id file)
:page-id (get-in file [:data :pages 0])} :page-id (get-in file [:data :pages 0])
:components-v2 true}
out (th/query! data)] out (th/query! data)]
;; (th/print-result! out) ;; (th/print-result! out)

View file

@ -164,7 +164,8 @@
(us/assert uuid? project-id) (us/assert uuid? project-id)
(#'files/create-file conn (#'files/create-file conn
(merge {:id (mk-uuid "file" i) (merge {:id (mk-uuid "file" i)
:name (str "file" i)} :name (str "file" i)
:components-v2 true}
params)))) params))))
(defn mark-file-deleted* (defn mark-file-deleted*
@ -249,6 +250,7 @@
:metrics metrics} :metrics metrics}
{:file file {:file file
:revn revn :revn revn
:components-v2 true
:changes changes :changes changes
:session-id session-id :session-id session-id
:profile-id profile-id})))) :profile-id profile-id}))))

View file

@ -465,15 +465,20 @@
(defmulti components-changed (fn [_ change] (:type change))) (defmulti components-changed (fn [_ change] (:type change)))
(defmethod components-changed :mod-obj (defmethod components-changed :mod-obj
[file-data {:keys [id page-id component-id operations]}] [file-data {:keys [id page-id _component-id operations]}]
(when page-id (when page-id
(let [page (ctpl/get-page file-data page-id) (let [page (ctpl/get-page file-data page-id)
shape-and-parents (map #(ctn/get-shape page %) shape-and-parents (map #(ctn/get-shape page %)
(into [id] (cph/get-parent-ids (:objects page) id))) (into [id] (cph/get-parent-ids (:objects page) id)))
any-set? (some #(= (:type %) :set) operations)] need-sync? (fn [operation]
(when any-set? ; We need to trigger a sync if the shape has changed any
; attribute that participates in components syncronization.
(and (= (:type operation) :set)
(component-sync-attrs (:attr operation))))
any-sync? (some need-sync? operations)]
(when any-sync?
(into #{} (->> shape-and-parents (into #{} (->> shape-and-parents
(filter #(:main-instance? %)) (filter #(:main-instance? %)) ; Select shapes that are main component instances
(map :id))))))) (map :id)))))))
(defmethod components-changed :default (defmethod components-changed :default

View file

@ -50,7 +50,7 @@
(defn with-objects (defn with-objects
[changes objects] [changes objects]
(let [file-data (-> (ctf/make-file-data (uuid/next) uuid/zero) (let [file-data (-> (ctf/make-file-data (uuid/next) uuid/zero true)
(assoc-in [:pages-index uuid/zero :objects] objects))] (assoc-in [:pages-index uuid/zero :objects] objects))]
(vary-meta changes assoc ::file-data file-data (vary-meta changes assoc ::file-data file-data
::applied-changes-count 0))) ::applied-changes-count 0)))

View file

@ -9,7 +9,7 @@
[app.common.colors :as clr] [app.common.colors :as clr]
[app.common.uuid :as uuid])) [app.common.uuid :as uuid]))
(def file-version 20) (def file-version 19)
(def default-color clr/gray-20) (def default-color clr/gray-20)
(def root uuid/zero) (def root uuid/zero)

View file

@ -440,68 +440,5 @@
(update :pages-index d/update-vals update-container) (update :pages-index d/update-vals update-container)
(update :components d/update-vals update-container)))) (update :components d/update-vals update-container))))
(defmethod migrate 20
[data]
(let [components (ctkl/components-seq data)]
(if (empty? components)
data
(let [grid-gap 50
[data page-id start-pos]
(ctf/get-or-add-library-page data grid-gap)
add-main-instance
(fn [data component position]
(let [page (ctpl/get-page data page-id)
[new-shape new-shapes]
(ctn/make-component-instance page
component
(:id data)
position
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))
update-component
(fn [component]
(assoc component
:main-instance-id (:id new-shape)
:main-instance-page page-id))]
(-> data
(ctpl/update-page page-id add-shapes)
(ctkl/update-component (:id component) update-component))))
add-instance-grid
(fn [data components]
(let [position-seq (ctst/generate-shape-grid
(map ctk/get-component-root components)
start-pos
grid-gap)]
(loop [data data
components-seq (seq components)
position-seq position-seq]
(let [component (first components-seq)
position (first position-seq)]
(if (nil? component)
data
(recur (add-main-instance data component position)
(rest components-seq)
(rest position-seq)))))))]
(add-instance-grid data (sort-by :name components))))))
;; TODO: pending to do a migration for delete already not used fill ;; TODO: pending to do a migration for delete already not used fill
;; and stroke props. This should be done for >1.14.x version. ;; and stroke props. This should be done for >1.14.x version.

View file

@ -14,13 +14,19 @@
(defn add-component (defn add-component
[file-data id name path main-instance-id main-instance-page shapes] [file-data id name path main-instance-id main-instance-page shapes]
(assoc-in file-data [:components id] (let [components-v2 (get-in file-data [:options :components-v2])]
{:id id (cond-> file-data
:name name :always
:path path (assoc-in [:components id]
:main-instance-id main-instance-id {:id id
:main-instance-page main-instance-page :name name
:objects (d/index-by :id shapes)})) :path path
:objects (d/index-by :id shapes)})
components-v2
(update-in [:components id] #(assoc %
:main-instance-id main-instance-id
:main-instance-page main-instance-page)))))
(defn get-component (defn get-component
[file-data component-id] [file-data component-id]

View file

@ -65,7 +65,7 @@
"Clone the shape and all children. Generate new ids and detach "Clone the shape and all children. Generate new ids and detach
from parent and frame. Update the original shapes to have links from parent and frame. Update the original shapes to have links
to the new ones." to the new ones."
[shape objects file-id] [shape objects file-id components-v2]
(assert (nil? (:component-id shape))) (assert (nil? (:component-id shape)))
(assert (nil? (:component-file shape))) (assert (nil? (:component-file shape)))
(assert (nil? (:shape-ref shape))) (assert (nil? (:shape-ref shape)))
@ -94,8 +94,10 @@
(nil? (:parent-id new-shape)) (nil? (:parent-id new-shape))
(assoc :component-id (:id new-shape) (assoc :component-id (:id new-shape)
:component-file file-id :component-file file-id
:component-root? true :component-root? true)
:main-instance? true)
components-v2
(assoc :main-instance? true)
(some? (:parent-id new-shape)) (some? (:parent-id new-shape))
(dissoc :component-root?)))] (dissoc :component-root?)))]
@ -103,6 +105,9 @@
(ctst/clone-object shape nil objects update-new-shape update-original-shape))) (ctst/clone-object shape nil objects update-new-shape update-original-shape)))
(defn make-component-instance (defn make-component-instance
"Clone the shapes of the component, generating new names and ids, and linking
each new shape to the corresponding one of the component. Place the new instance
coordinates in the given position."
[container component component-file-id position main-instance?] [container component component-file-id position main-instance?]
(let [component-shape (get-shape component (:id component)) (let [component-shape (get-shape component (:id component))

View file

@ -83,14 +83,17 @@
:pages-index {}}) :pages-index {}})
(defn make-file-data (defn make-file-data
([file-id] ([file-id components-v2]
(make-file-data file-id (uuid/next))) (make-file-data file-id (uuid/next) components-v2))
([file-id page-id] ([file-id page-id components-v2]
(let [page (ctp/make-empty-page page-id "Page-1")] (let [page (ctp/make-empty-page page-id "Page-1")]
(-> empty-file-data (cond-> (-> empty-file-data
(assoc :id file-id) (assoc :id file-id)
(ctpl/add-page page))))) (ctpl/add-page page))
components-v2
(assoc-in [:options :components-v2] true)))))
;; Helpers ;; Helpers
@ -162,9 +165,9 @@
assets-seq))) assets-seq)))
(defn get-or-add-library-page (defn get-or-add-library-page
[file-data grid-gap]
"If exists a page named 'Library page', get the id and calculate the position to start "If exists a page named 'Library page', get the id and calculate the position to start
adding new components. If not, create it and start at (0, 0)." adding new components. If not, create it and start at (0, 0)."
[file-data grid-gap]
(let [library-page (d/seek #(= (:name %) "Library page") (ctpl/pages-seq file-data))] (let [library-page (d/seek #(= (:name %) "Library page") (ctpl/pages-seq file-data))]
(if (some? library-page) (if (some? library-page)
(let [compare-pos (fn [pos shape] (let [compare-pos (fn [pos shape]
@ -180,6 +183,74 @@
(let [library-page (ctp/make-empty-page (uuid/next) "Library page")] (let [library-page (ctp/make-empty-page (uuid/next) "Library page")]
[(ctpl/add-page file-data library-page) (:id library-page) (gpt/point 0 0)])))) [(ctpl/add-page file-data library-page) (:id library-page) (gpt/point 0 0)]))))
(defn migrate-to-components-v2
"If there is any component in the file library, add a new 'Library Page' and generate
main instances for all components there. Mark the file with the :comonents-v2 option."
[file-data]
(let [components (ctkl/components-seq file-data)]
(if (or (empty? components)
(get-in file-data [:options :components-v2]))
(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)
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
(:id file-data)
position
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))
update-component
(fn [component]
(assoc component
:main-instance-id (:id new-shape)
:main-instance-page page-id))]
(-> 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 ctk/get-component-root 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)))))))]
(-> file-data
(add-instance-grid (sort-by :name components))
(assoc-in [:options :components-v2] true))))))
(defn- absorb-components (defn- absorb-components
[file-data library-data used-components] [file-data library-data used-components]
(let [grid-gap 50 (let [grid-gap 50

View file

@ -15,7 +15,7 @@
(t/deftest process-change-set-option (t/deftest process-change-set-option
(let [file-id (uuid/custom 2 2) (let [file-id (uuid/custom 2 2)
page-id (uuid/custom 1 1) page-id (uuid/custom 1 1)
data (ctf/make-file-data file-id page-id)] data (ctf/make-file-data file-id page-id true)]
(t/testing "Sets option single" (t/testing "Sets option single"
(let [chg {:type :set-option (let [chg {:type :set-option
:page-id page-id :page-id page-id
@ -81,7 +81,7 @@
(t/deftest process-change-add-obj (t/deftest process-change-add-obj
(let [file-id (uuid/custom 2 2) (let [file-id (uuid/custom 2 2)
page-id (uuid/custom 1 1) page-id (uuid/custom 1 1)
data (ctf/make-file-data file-id page-id) data (ctf/make-file-data file-id page-id true)
id-a (uuid/custom 2 1) id-a (uuid/custom 2 1)
id-b (uuid/custom 2 2) id-b (uuid/custom 2 2)
id-c (uuid/custom 2 3)] id-c (uuid/custom 2 3)]
@ -135,7 +135,7 @@
(t/deftest process-change-mod-obj (t/deftest process-change-mod-obj
(let [file-id (uuid/custom 2 2) (let [file-id (uuid/custom 2 2)
page-id (uuid/custom 1 1) page-id (uuid/custom 1 1)
data (ctf/make-file-data file-id page-id)] data (ctf/make-file-data file-id page-id true)]
(t/testing "simple mod-obj" (t/testing "simple mod-obj"
(let [chg {:type :mod-obj (let [chg {:type :mod-obj
:page-id page-id :page-id page-id
@ -162,7 +162,7 @@
(let [file-id (uuid/custom 2 2) (let [file-id (uuid/custom 2 2)
page-id (uuid/custom 1 1) page-id (uuid/custom 1 1)
id (uuid/custom 2 1) id (uuid/custom 2 1)
data (ctf/make-file-data file-id page-id) data (ctf/make-file-data file-id page-id true)
data (-> data data (-> data
(assoc-in [:pages-index page-id :objects uuid/zero :shapes] [id]) (assoc-in [:pages-index page-id :objects uuid/zero :shapes] [id])
(assoc-in [:pages-index page-id :objects id] (assoc-in [:pages-index page-id :objects id]
@ -206,7 +206,7 @@
file-id (uuid/custom 2 2) file-id (uuid/custom 2 2)
page-id (uuid/custom 1 1) page-id (uuid/custom 1 1)
data (ctf/make-file-data file-id page-id) data (ctf/make-file-data file-id page-id true)
data (update-in data [:pages-index page-id :objects] data (update-in data [:pages-index page-id :objects]
#(-> % #(-> %
@ -450,7 +450,7 @@
:obj {:type :rect :obj {:type :rect
:name "Shape 3"}} :name "Shape 3"}}
] ]
data (ctf/make-file-data file-id page-id) data (ctf/make-file-data file-id page-id true)
data (cp/process-changes data changes)] data (cp/process-changes data changes)]
(t/testing "preserve order on multiple shape mov 1" (t/testing "preserve order on multiple shape mov 1"
@ -557,7 +557,7 @@
:parent-id group-1-id :parent-id group-1-id
:shapes [shape-1-id shape-2-id]}] :shapes [shape-1-id shape-2-id]}]
data (ctf/make-file-data file-id page-id) data (ctf/make-file-data file-id page-id true)
data (cp/process-changes data changes)] data (cp/process-changes data changes)]
(t/testing "case 1" (t/testing "case 1"

View file

@ -1,7 +1,6 @@
(ns app.common.test-helpers.components (ns app.common.test-helpers.components
(:require (:require
[cljs.test :as t :include-macros true] [clojure.test :as t]
[cljs.pprint :refer [pprint]]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.common.types.component :as ctk] [app.common.types.component :as ctk]
[app.common.types.container :as ctn])) [app.common.types.container :as ctn]))

View file

@ -31,7 +31,7 @@
([file-id page-id props] ([file-id page-id props]
(merge {:id file-id (merge {:id file-id
:name (get props :name "File1") :name (get props :name "File1")
:data (ctf/make-file-data file-id page-id)} :data (ctf/make-file-data file-id page-id true)}
props))) props)))
(defn sample-shape (defn sample-shape
@ -68,9 +68,10 @@
(let [page (ctpl/get-page file-data page-id) (let [page (ctpl/get-page file-data page-id)
[component-shape component-shapes updated-shapes] [component-shape component-shapes updated-shapes]
(ctn/make-component-shape (ctn/get-shape page shape-id) (ctn/make-component-shape (ctn/get-shape page shape-id true)
(:objects page) (:objects page)
(:id file))] (:id file)
true)]
(swap! idmap assoc label (:id component-shape)) (swap! idmap assoc label (:id component-shape))
(-> file-data (-> file-data

View file

@ -110,6 +110,7 @@
(t/is (= (:name p-group) "Group1")) (t/is (= (:name p-group) "Group1"))
(t/is (ctk/instance-of? p-group file-id (:id component1))) (t/is (ctk/instance-of? p-group file-id (:id component1)))
(t/is (not (:main-instance? p-group)))
(t/is (not (ctk/is-main-instance? (:id p-group) file-page-id component1))) (t/is (not (ctk/is-main-instance? (:id p-group) file-page-id component1)))
(t/is (ctk/is-main-of? c-group1 p-group)) (t/is (ctk/is-main-of? c-group1 p-group))

View file

@ -179,7 +179,8 @@
} }
} }
.confirm-dialog { .confirm-dialog,
.alert-dialog {
background-color: $color-white; background-color: $color-white;
p { p {

View file

@ -16,6 +16,7 @@
[app.main.sentry :as sentry] [app.main.sentry :as sentry]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui :as ui] [app.main.ui :as ui]
[app.main.ui.alert]
[app.main.ui.confirm] [app.main.ui.confirm]
[app.main.ui.modal :refer [modal]] [app.main.ui.modal :refer [modal]]
[app.main.ui.routes :as rt] [app.main.ui.routes :as rt]

View file

@ -13,6 +13,7 @@
[app.main.data.fonts :as df] [app.main.data.fonts :as df]
[app.main.data.media :as di] [app.main.data.media :as di]
[app.main.data.users :as du] [app.main.data.users :as du]
[app.main.features :as features]
[app.main.repo :as rp] [app.main.repo :as rp]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
[app.util.router :as rt] [app.util.router :as rt]
@ -718,12 +719,13 @@
(-deref [_] {:project-id project-id}) (-deref [_] {:project-id project-id})
ptk/WatchEvent ptk/WatchEvent
(watch [it _ _] (watch [it state _]
(let [{:keys [on-success on-error] (let [{:keys [on-success on-error]
:or {on-success identity :or {on-success identity
on-error rx/throw}} (meta params) on-error rx/throw}} (meta params)
name (name (gensym (str (tr "dashboard.new-file-prefix") " "))) name (name (gensym (str (tr "dashboard.new-file-prefix") " ")))
params (assoc params :name name)] components-v2 (features/active-feature? state :components-v2)
params (assoc params :name name :components-v2 components-v2)]
(->> (rp/mutation! :create-file params) (->> (rp/mutation! :create-file params)
(rx/tap on-success) (rx/tap on-success)

View file

@ -14,6 +14,7 @@
[app.common.types.shape.interactions :as ctsi] [app.common.types.shape.interactions :as ctsi]
[app.main.data.comments :as dcm] [app.main.data.comments :as dcm]
[app.main.data.fonts :as df] [app.main.data.fonts :as df]
[app.main.features :as features]
[app.main.repo :as rp] [app.main.repo :as rp]
[app.util.globals :as ug] [app.util.globals :as ug]
[app.util.router :as rt] [app.util.router :as rt]
@ -100,9 +101,15 @@
(us/assert ::fetch-bundle-params params) (us/assert ::fetch-bundle-params params)
(ptk/reify ::fetch-file (ptk/reify ::fetch-file
ptk/WatchEvent ptk/WatchEvent
(watch [_ _ _] (watch [_ state _]
(let [params' (cond-> {:file-id file-id} (let [components-v2 (features/active-feature? state :components-v2)
(uuid? share-id) (assoc :share-id share-id))] params' (cond-> {:file-id file-id}
(uuid? share-id)
(assoc :share-id share-id)
:always
(assoc :components-v2 components-v2))]
(->> (rp/query :view-only-bundle params') (->> (rp/query :view-only-bundle params')
(rx/mapcat (rx/mapcat
(fn [{:keys [fonts] :as bundle}] (fn [{:keys [fonts] :as bundle}]

View file

@ -192,6 +192,7 @@
process-page-changes process-page-changes
(fn [[page-id _changes]] (fn [[page-id _changes]]
(update-indices page-id redo-changes))] (update-indices page-id redo-changes))]
(rx/concat (rx/concat
(rx/from (map process-page-changes changes-by-pages)) (rx/from (map process-page-changes changes-by-pages))

View file

@ -30,6 +30,7 @@
[app.main.data.workspace.selection :as dws] [app.main.data.workspace.selection :as dws]
[app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.state-helpers :as wsh]
[app.main.data.workspace.undo :as dwu] [app.main.data.workspace.undo :as dwu]
[app.main.features :as features]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.repo :as rp] [app.main.repo :as rp]
[app.main.store :as st] [app.main.store :as st]
@ -283,7 +284,7 @@
(defn- add-component2 (defn- add-component2
"This is the second step of the component creation." "This is the second step of the component creation."
[selected] [selected components-v2]
(ptk/reify ::add-component2 (ptk/reify ::add-component2
IDeref IDeref
(-deref [_] {:num-shapes (count selected)}) (-deref [_] {:num-shapes (count selected)})
@ -296,7 +297,7 @@
shapes (dwg/shapes-for-grouping objects selected)] shapes (dwg/shapes-for-grouping objects selected)]
(when-not (empty? shapes) (when-not (empty? shapes)
(let [[group _ changes] (let [[group _ changes]
(dwlh/generate-add-component it shapes objects page-id file-id)] (dwlh/generate-add-component it shapes objects page-id file-id components-v2)]
(when-not (empty? (:redo-changes changes)) (when-not (empty? (:redo-changes changes))
(rx/of (dch/commit-changes changes) (rx/of (dch/commit-changes changes)
(dws/select-shapes (d/ordered-set (:id group))))))))))) (dws/select-shapes (d/ordered-set (:id group)))))))))))
@ -310,10 +311,11 @@
(ptk/reify ::add-component (ptk/reify ::add-component
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
(let [objects (wsh/lookup-page-objects state) (let [objects (wsh/lookup-page-objects state)
selected (->> (wsh/lookup-selected state) selected (->> (wsh/lookup-selected state)
(cph/clean-loops objects))] (cph/clean-loops objects))
(rx/of (add-component2 selected)))))) components-v2 (features/active-feature? state :components-v2)]
(rx/of (add-component2 selected components-v2))))))
(defn rename-component (defn rename-component
"Rename the component with the given id, in the current file library." "Rename the component with the given id, in the current file library."
@ -357,8 +359,12 @@
unames (into #{} (map :name) all-components) unames (into #{} (map :name) all-components)
new-name (ctst/generate-unique-name unames (:name component)) new-name (ctst/generate-unique-name unames (:name component))
main-instance-page (wsh/lookup-page state (:main-instance-page component)) components-v2 (features/active-feature? state :components-v2)
main-instance-shape (ctn/get-shape main-instance-page (:main-instance-id component))
main-instance-page (when components-v2
(wsh/lookup-page state (:main-instance-page component)))
main-instance-shape (when components-v2
(ctn/get-shape main-instance-page (:main-instance-id component)))
[new-component-shape new-component-shapes [new-component-shape new-component-shapes
new-main-instance-shape new-main-instance-shapes] new-main-instance-shape new-main-instance-shapes]
@ -606,7 +612,11 @@
"Synchronize the given file from the given library. Walk through all "Synchronize the given file from the given library. Walk through all
shapes in all pages in the file that use some color, typography or shapes in all pages in the file that use some color, typography or
component of the library, and copy the new values to the shapes. Do component of the library, and copy the new values to the shapes. Do
it also for shapes inside components of the local file library." it also for shapes inside components of the local file library.
If it's known that only one asset has changed, you can give its
type and id, and only shapes that use it will be synced, thus avoiding
a lot of unneeded checks."
([file-id library-id] ([file-id library-id]
(sync-file file-id library-id nil nil)) (sync-file file-id library-id nil nil))
([file-id library-id asset-type asset-id] ([file-id library-id asset-type asset-id]
@ -752,8 +762,10 @@
[] []
(ptk/reify ::watch-component-changes (ptk/reify ::watch-component-changes
ptk/WatchEvent ptk/WatchEvent
(watch [_ _ stream] (watch [_ state stream]
(let [stopper (let [components-v2 (features/active-feature? state :components-v2)
stopper
(->> stream (->> stream
(rx/filter #(or (= :app.main.data.workspace/finalize-page (ptk/type %)) (rx/filter #(or (= :app.main.data.workspace/finalize-page (ptk/type %))
(= ::watch-component-changes (ptk/type %))))) (= ::watch-component-changes (ptk/type %)))))
@ -775,16 +787,16 @@
components-changed (reduce #(into %1 (ch/components-changed data %2)) components-changed (reduce #(into %1 (ch/components-changed data %2))
#{} #{}
changes)] changes)]
(js/console.log "components-changed" (clj->js components-changed))
(when (d/not-empty? components-changed) (when (d/not-empty? components-changed)
(apply st/emit! (apply st/emit!
(map #(update-component-sync % (:id data)) (map #(update-component-sync % (:id data))
components-changed)))))] components-changed)))))]
(->> change-str (when components-v2
(rx/with-latest-from workspace-data-str) (->> change-str
(rx/map check-changes) (rx/with-latest-from workspace-data-str)
(rx/take-until stopper)))))) (rx/map check-changes)
(rx/take-until stopper)))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Backend interactions ;; Backend interactions

View file

@ -62,7 +62,7 @@
"If there is exactly one id, and it's a group, use it as root. Otherwise, "If there is exactly one id, and it's a group, use it as root. Otherwise,
create a group that contains all ids. Then, make a component with it, create a group that contains all ids. Then, make a component with it,
and link all shapes to their corresponding one in the component." and link all shapes to their corresponding one in the component."
[it shapes objects page-id file-id] [it shapes objects page-id file-id components-v2]
(if (and (= (count shapes) 1) (if (and (= (count shapes) 1)
(:component-id (first shapes))) (:component-id (first shapes)))
[(first shapes) (pcb/empty-changes it)] [(first shapes) (pcb/empty-changes it)]
@ -77,7 +77,7 @@
(dwg/prepare-create-group it objects page-id shapes name true)) (dwg/prepare-create-group it objects page-id shapes name true))
[new-shape new-shapes updated-shapes] [new-shape new-shapes updated-shapes]
(ctn/make-component-shape group objects file-id) (ctn/make-component-shape group objects file-id components-v2)
changes (-> changes changes (-> changes
(pcb/add-component (:id new-shape) (pcb/add-component (:id new-shape)
@ -106,13 +106,14 @@
[new-instance-shape new-instance-shapes] [new-instance-shape new-instance-shapes]
(ctn/make-component-instance main-instance-page (when (and (some? main-instance-page) (some? main-instance-shape))
{:id (:id new-component-shape) (ctn/make-component-instance main-instance-page
:name (:name new-component-shape) {:id (:id new-component-shape)
:objects (d/index-by :id new-component-shapes)} :name (:name new-component-shape)
(:component-file main-instance-shape) :objects (d/index-by :id new-component-shapes)}
position (:component-file main-instance-shape)
false)] position
false))]
[new-component-shape new-component-shapes [new-component-shape new-component-shapes
new-instance-shape new-instance-shapes])) new-instance-shape new-instance-shapes]))
@ -254,7 +255,6 @@
(and (if (nil? component-id) (and (if (nil? component-id)
(ctk/uses-library-components? shape library-id) (ctk/uses-library-components? shape library-id)
(ctk/instance-of? shape library-id component-id)) (ctk/instance-of? shape library-id component-id))
(not (:main-instance? shape)) ; not need to sync the main instance (avoid infinite loop)
(or (:component-root? shape) (not page?)))) ; avoid nested components inside pages (or (:component-root? shape) (not page?)))) ; avoid nested components inside pages
(defmethod uses-assets? :colors (defmethod uses-assets? :colors

View file

@ -16,12 +16,15 @@
[app.config :as cf] [app.config :as cf]
[app.main.data.dashboard :as dd] [app.main.data.dashboard :as dd]
[app.main.data.fonts :as df] [app.main.data.fonts :as df]
[app.main.data.modal :as modal]
[app.main.data.workspace.changes :as dch] [app.main.data.workspace.changes :as dch]
[app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.state-helpers :as wsh]
[app.main.data.workspace.thumbnails :as dwt] [app.main.data.workspace.thumbnails :as dwt]
[app.main.features :as features]
[app.main.repo :as rp] [app.main.repo :as rp]
[app.main.store :as st] [app.main.store :as st]
[app.util.http :as http] [app.util.http :as http]
[app.util.i18n :as i18n :refer [tr]]
[app.util.router :as rt] [app.util.router :as rt]
[app.util.time :as dt] [app.util.time :as dt]
[beicon.core :as rx] [beicon.core :as rx]
@ -124,8 +127,7 @@
(rx/map persist-synchronous-changes) (rx/map persist-synchronous-changes)
(rx/take-until (rx/delay 100 stoper)) (rx/take-until (rx/delay 100 stoper))
(rx/finalize (fn [] (rx/finalize (fn []
(log/debug :hint "finalize persistence: synchronous save loop")))) (log/debug :hint "finalize persistence: synchronous save loop")))))))))
)))))
(defn persist-changes (defn persist-changes
[file-id changes] [file-id changes]
@ -134,12 +136,14 @@
(ptk/reify ::persist-changes (ptk/reify ::persist-changes
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
(let [sid (:session-id state) (let [components-v2 (features/active-feature? state :components-v2)
file (get state :workspace-file) sid (:session-id state)
params {:id (:id file) file (get state :workspace-file)
:revn (:revn file) params {:id (:id file)
:session-id sid :revn (:revn file)
:changes-with-metadata (into [] changes)}] :session-id sid
:changes-with-metadata (into [] changes)
:components-v2 components-v2}]
(when (= file-id (:id params)) (when (= file-id (:id params))
(->> (rp/mutation :update-file params) (->> (rp/mutation :update-file params)
@ -175,13 +179,15 @@
(ptk/reify ::persist-synchronous-changes (ptk/reify ::persist-synchronous-changes
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
(let [sid (:session-id state) (let [components-v2 (features/active-feature? state :components-v2)
sid (:session-id state)
file (get-in state [:workspace-libraries file-id]) file (get-in state [:workspace-libraries file-id])
params {:id (:id file) params {:id (:id file)
:revn (:revn file) :revn (:revn file)
:session-id sid :session-id sid
:changes changes}] :changes changes
:components-v2 components-v2}]
(when (:id params) (when (:id params)
(->> (rp/mutation :update-file params) (->> (rp/mutation :update-file params)
@ -261,8 +267,9 @@
(ptk/reify ::fetch-bundle (ptk/reify ::fetch-bundle
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
(let [share-id (-> state :viewer-local :share-id)] (let [share-id (-> state :viewer-local :share-id)
(->> (rx/zip (rp/query :file-raw {:id file-id}) components-v2 (features/active-feature? state :components-v2)]
(->> (rx/zip (rp/query :file-raw {:id file-id :components-v2 components-v2})
(rp/query :team-users {:file-id file-id}) (rp/query :team-users {:file-id file-id})
(rp/query :project {:id project-id}) (rp/query :project {:id project-id})
(rp/query :file-libraries {:file-id file-id}) (rp/query :file-libraries {:file-id file-id})
@ -276,8 +283,16 @@
:file-comments-users file-comments-users})) :file-comments-users file-comments-users}))
(rx/mapcat (fn [{:keys [project] :as bundle}] (rx/mapcat (fn [{:keys [project] :as bundle}]
(rx/of (ptk/data-event ::bundle-fetched bundle) (rx/of (ptk/data-event ::bundle-fetched bundle)
(df/load-team-fonts (:team-id project)))))))))) (df/load-team-fonts (:team-id project)))))
(rx/catch (fn [err]
(if (and (= (:type err) :restriction)
(= (:code err) :feature-disabled))
(let [team-id (:current-team-id state)]
(rx/of (modal/show
{:type :alert
:message (tr "errors.components-v2")
:on-accept #(st/emit! (rt/nav :dashboard-projects {:team-id team-id}))})))
(rx/throw err)))))))))
;; --- Helpers ;; --- Helpers

View file

@ -13,7 +13,6 @@
[app.common.pages.changes-builder :as pcb] [app.common.pages.changes-builder :as pcb]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.common.spec :as us] [app.common.spec :as us]
[app.common.types.component :as ctk]
[app.common.types.page :as ctp] [app.common.types.page :as ctp]
[app.common.types.shape :as cts] [app.common.types.shape :as cts]
[app.common.types.shape.interactions :as ctsi] [app.common.types.shape.interactions :as ctsi]
@ -24,6 +23,7 @@
[app.main.data.workspace.selection :as dws] [app.main.data.workspace.selection :as dws]
[app.main.data.workspace.shape-layout :as dwsl] [app.main.data.workspace.shape-layout :as dwsl]
[app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.state-helpers :as wsh]
[app.main.features :as features]
[app.main.streams :as ms] [app.main.streams :as ms]
[beicon.core :as rx] [beicon.core :as rx]
[cljs.spec.alpha :as s] [cljs.spec.alpha :as s]
@ -148,7 +148,7 @@
ids (cph/clean-loops objects ids) ids (cph/clean-loops objects ids)
lookup (d/getf objects) lookup (d/getf objects)
local-library {file-id {:data file}} components-v2 (features/active-feature? state :components-v2)
groups-to-unmask groups-to-unmask
(reduce (fn [group-ids id] (reduce (fn [group-ids id]
@ -221,23 +221,16 @@
(into (d/ordered-set) (find-all-empty-parents #{})) (into (d/ordered-set) (find-all-empty-parents #{}))
components-to-delete components-to-delete
(reduce (fn [components id] (if components-v2
(let [shape (get objects id) (reduce (fn [components id]
(let [shape (get objects id)]
component (if (and (= (:component-file shape) file-id) ;; Main instances should exist only in local file
(when (and (:component-id shape) (:component-file shape)) (:main-instance? shape)) ;; but check anyway
;; Only local components may have main instances (conj components (:component-id shape))
(cph/get-component local-library (:component-file shape) (:component-id shape))) components)))
[]
main-instance? (into ids all-children))
(when component [])
(ctk/is-main-instance? (:id shape) (:id page) component))]
(if main-instance?
(conj components (:component-id shape))
components)))
[]
(into ids all-children))
changes (-> (pcb/empty-changes it page-id) changes (-> (pcb/empty-changes it page-id)
(pcb/with-page page) (pcb/with-page page)

View file

@ -4,7 +4,7 @@
;; ;;
;; Copyright (c) UXBOX Labs SL ;; Copyright (c) UXBOX Labs SL
(ns app.main.ui.features (ns app.main.features
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.logging :as log] [app.common.logging :as log]
@ -15,9 +15,9 @@
(log/set-level! :debug) (log/set-level! :debug)
(def features-list #{:auto-layout}) (def features-list #{:auto-layout :components-v2})
(defn toggle-feature (defn- toggle-feature
[feature] [feature]
(ptk/reify ::toggle-feature (ptk/reify ::toggle-feature
ptk/UpdateEvent ptk/UpdateEvent
@ -41,6 +41,13 @@
(assert (contains? features-list feature) "Not supported feature") (assert (contains? features-list feature) "Not supported feature")
(st/emit! (toggle-feature feature))) (st/emit! (toggle-feature feature)))
(defn active-feature?
([feature]
(active-feature? @st/state feature))
([state feature]
(assert (contains? features-list feature) "Not supported feature")
(contains? (get state :features) feature)))
(def features (def features
(l/derived :features st/state)) (l/derived :features st/state))

View file

@ -0,0 +1,78 @@
;; 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) UXBOX Labs SL
(ns app.main.ui.alert
(:require
[app.main.data.modal :as modal]
[app.main.store :as st]
[app.main.ui.icons :as i]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr t]]
[app.util.keyboard :as k]
[goog.events :as events]
[rumext.alpha :as mf])
(:import goog.events.EventType))
(mf/defc alert-dialog
{::mf/register modal/components
::mf/register-as :alert}
[{:keys [message
scd-message
title
on-accept
hint
accept-label
accept-style] :as props}]
(let [locale (mf/deref i18n/locale)
on-accept (or on-accept identity)
message (or message (t locale "ds.alert-title"))
accept-label (or accept-label (tr "ds.alert-ok"))
accept-style (or accept-style :danger)
title (or title (t locale "ds.alert-title"))
accept-fn
(mf/use-callback
(fn [event]
(dom/prevent-default event)
(st/emit! (modal/hide))
(on-accept props)))]
(mf/with-effect
(letfn [(on-keydown [event]
(when (k/enter? event)
(dom/prevent-default event)
(dom/stop-propagation event)
(st/emit! (modal/hide))
(on-accept props)))]
(->> (events/listen js/document EventType.KEYDOWN on-keydown)
(partial events/unlistenByKey))))
[:div.modal-overlay
[:div.modal-container.alert-dialog
[:div.modal-header
[:div.modal-header-title
[:h2 title]]
[:div.modal-close-button
{:on-click accept-fn} i/close]]
[:div.modal-content
(when (and (string? message) (not= message ""))
[:h3 message])
(when (and (string? scd-message) (not= scd-message ""))
[:h3 scd-message])
(when (string? hint)
[:p hint])]
[:div.modal-footer
[:div.action-buttons
[:input.accept-button
{:class (dom/classnames
:danger (= accept-style :danger)
:primary (= accept-style :primary))
:type "button"
:value accept-label
:on-click accept-fn}]]]]]))

View file

@ -25,3 +25,4 @@
(def scroll-ctx (mf/create-context nil)) (def scroll-ctx (mf/create-context nil))
(def active-frames-ctx (mf/create-context nil)) (def active-frames-ctx (mf/create-context nil))
(def render-thumbnails (mf/create-context nil)) (def render-thumbnails (mf/create-context nil))
(def components-v2 (mf/create-context nil))

View file

@ -10,6 +10,7 @@
[app.common.math :as mth] [app.common.math :as mth]
[app.main.data.dashboard :as dd] [app.main.data.dashboard :as dd]
[app.main.data.messages :as dm] [app.main.data.messages :as dm]
[app.main.features :as features]
[app.main.fonts :as fonts] [app.main.fonts :as fonts]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
@ -36,9 +37,11 @@
(defn ask-for-thumbnail (defn ask-for-thumbnail
"Creates some hooks to handle the files thumbnails cache" "Creates some hooks to handle the files thumbnails cache"
[file] [file]
(wrk/ask! {:cmd :thumbnails/generate (let [components-v2 (features/active-feature? :components-v2)]
:revn (:revn file) (wrk/ask! {:cmd :thumbnails/generate
:file-id (:id file)})) :revn (:revn file)
:file-id (:id file)
:components-v2 components-v2})))
(mf/defc grid-item-thumbnail (mf/defc grid-item-thumbnail
{::mf/wrap [mf/memo]} {::mf/wrap [mf/memo]}

View file

@ -11,6 +11,7 @@
[app.main.data.messages :as msg] [app.main.data.messages :as msg]
[app.main.data.workspace :as dw] [app.main.data.workspace :as dw]
[app.main.data.workspace.persistence :as dwp] [app.main.data.workspace.persistence :as dwp]
[app.main.features :as features]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.context :as ctx] [app.main.ui.context :as ctx]
@ -114,13 +115,12 @@
(mf/defc workspace (mf/defc workspace
{::mf/wrap [mf/memo]} {::mf/wrap [mf/memo]}
[{:keys [project-id file-id page-id layout-name] :as props}] [{:keys [project-id file-id page-id layout-name] :as props}]
(let [file (mf/deref refs/workspace-file) (let [file (mf/deref refs/workspace-file)
project (mf/deref refs/workspace-project) project (mf/deref refs/workspace-project)
layout (mf/deref refs/workspace-layout) layout (mf/deref refs/workspace-layout)
wglobal (mf/deref refs/workspace-global) wglobal (mf/deref refs/workspace-global)
wglobal (mf/deref refs/workspace-global)
libraries (mf/deref refs/workspace-libraries) components-v2 (features/use-feature :components-v2)
local-library (mf/deref refs/workspace-local-library)
background-color (:background-color wglobal)] background-color (:background-color wglobal)]
@ -148,9 +148,7 @@
[:& (mf/provider ctx/current-team-id) {:value (:team-id project)} [:& (mf/provider ctx/current-team-id) {:value (:team-id project)}
[:& (mf/provider ctx/current-project-id) {:value (:id project)} [:& (mf/provider ctx/current-project-id) {:value (:id project)}
[:& (mf/provider ctx/current-page-id) {:value page-id} [:& (mf/provider ctx/current-page-id) {:value page-id}
[:& (mf/provider ctx/libraries) {:value (assoc libraries [:& (mf/provider ctx/components-v2) {:value components-v2}
(:id local-library)
{:data local-library})}
[:section#workspace {:style {:background-color background-color}} [:section#workspace {:style {:background-color background-color}}
(when (not (:hide-ui layout)) (when (not (:hide-ui layout))
[:& header {:file file [:& header {:file file
@ -169,5 +167,3 @@
:layout layout}] :layout layout}]
[:& workspace-loader])]]]]]])) [:& workspace-loader])]]]]]]))

View file

@ -9,7 +9,6 @@
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.common.types.component :as ctk]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.main.data.workspace :as dw] [app.main.data.workspace :as dw]
[app.main.data.workspace.collapse :as dwc] [app.main.data.workspace.collapse :as dwc]
@ -87,7 +86,7 @@
(when (seq (:touched shape)) " *")]))) (when (seq (:touched shape)) " *")])))
(mf/defc layer-item (mf/defc layer-item
[{:keys [index page item selected objects] :as props}] [{:keys [index item selected objects] :as props}]
(let [id (:id item) (let [id (:id item)
blocked? (:blocked item) blocked? (:blocked item)
hidden? (:hidden item) hidden? (:hidden item)
@ -103,11 +102,10 @@
container? (or (cph/frame-shape? item) container? (or (cph/frame-shape? item)
(cph/group-shape? item)) (cph/group-shape? item))
libraries (mf/use-ctx ctx/libraries) components-v2 (mf/use-ctx ctx/components-v2)
component (when (and (:component-id item) (:component-file item)) main-instance? (if components-v2
(cph/get-component libraries (:component-file item) (:component-id item))) (:main-instance? item)
main-instance? (when component true)
(ctk/is-main-instance? (:id item) (:id page) component))
toggle-collapse toggle-collapse
(mf/use-fn (mf/use-fn
@ -277,8 +275,7 @@
(for [[index id] (reverse (d/enumerate (:shapes item)))] (for [[index id] (reverse (d/enumerate (:shapes item)))]
(when-let [item (get objects id)] (when-let [item (get objects id)]
[:& layer-item [:& layer-item
{:page page {:item item
:item item
:selected selected :selected selected
:index index :index index
:objects objects :objects objects
@ -299,9 +296,8 @@
{::mf/wrap [#(mf/memo % =) {::mf/wrap [#(mf/memo % =)
#(mf/throttle % 200)]} #(mf/throttle % 200)]}
[{:keys [objects] :as props}] [{:keys [objects] :as props}]
(let [page (mf/deref refs/workspace-page) (let [selected (mf/deref refs/selected-shapes)
selected (mf/deref refs/selected-shapes) selected (hooks/use-equal-memo selected)
selected (hooks/use-equal-memo selected)
root (get objects uuid/zero)] root (get objects uuid/zero)]
[:ul.element-list [:ul.element-list
[:& hooks/sortable-container {} [:& hooks/sortable-container {}
@ -315,8 +311,7 @@
:objects objects :objects objects
:key id}] :key id}]
[:& layer-item [:& layer-item
{:page page {:item obj
:item obj
:selected selected :selected selected
:index index :index index
:objects objects :objects objects
@ -326,16 +321,14 @@
{::mf/wrap [#(mf/memo % =) {::mf/wrap [#(mf/memo % =)
#(mf/throttle % 200)]} #(mf/throttle % 200)]}
[{:keys [objects] :as props}] [{:keys [objects] :as props}]
(let [page (mf/deref refs/workspace-page) (let [selected (mf/deref refs/selected-shapes)
selected (mf/deref refs/selected-shapes)
selected (hooks/use-equal-memo selected) selected (hooks/use-equal-memo selected)
root (get objects uuid/zero)] root (get objects uuid/zero)]
[:ul.element-list [:ul.element-list
(for [[index id] (d/enumerate (:shapes root))] (for [[index id] (d/enumerate (:shapes root))]
(when-let [obj (get objects id)] (when-let [obj (get objects id)]
[:& layer-item [:& layer-item
{:page page {:item obj
:item obj
:selected selected :selected selected
:index index :index index
:objects objects :objects objects

View file

@ -6,8 +6,6 @@
(ns app.main.ui.workspace.sidebar.options.menus.component (ns app.main.ui.workspace.sidebar.options.menus.component
(:require (:require
[app.common.pages.helpers :as cph]
[app.common.types.component :as ctk]
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
[app.main.data.workspace :as dw] [app.main.data.workspace :as dw]
[app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.libraries :as dwl]
@ -19,13 +17,12 @@
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
[rumext.alpha :as mf])) [rumext.alpha :as mf]))
(def component-attrs [:component-id :component-file :shape-ref]) (def component-attrs [:component-id :component-file :shape-ref :main-instance?])
(mf/defc component-menu (mf/defc component-menu
[{:keys [ids values shape-name] :as props}] [{:keys [ids values shape-name] :as props}]
(let [current-file-id (mf/use-ctx ctx/current-file-id) (let [current-file-id (mf/use-ctx ctx/current-file-id)
current-page-id (mf/use-ctx ctx/current-page-id) components-v2 (mf/use-ctx ctx/components-v2)
libraries (mf/use-ctx ctx/libraries)
id (first ids) id (first ids)
local (mf/use-state {:menu-open false}) local (mf/use-state {:menu-open false})
@ -33,10 +30,9 @@
component-id (:component-id values) component-id (:component-id values)
library-id (:component-file values) library-id (:component-file values)
show? (some? component-id) show? (some? component-id)
main-instance? (if components-v2
component (when (and component-id library-id) (:main-instance? values)
(cph/get-component libraries library-id component-id)) true)
main-instance? (ctk/is-main-instance? id current-page-id component)
on-menu-click on-menu-click
(mf/use-callback (mf/use-callback

View file

@ -6,8 +6,8 @@
(ns app.main.ui.workspace.sidebar.options.shapes.frame (ns app.main.ui.workspace.sidebar.options.shapes.frame
(:require (:require
[app.main.features :as features]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.ui.features :as features]
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]]
[app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]] [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]]
[app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs-shape fill-menu]] [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs-shape fill-menu]]

View file

@ -13,6 +13,7 @@
[app.common.uri :as u] [app.common.uri :as u]
[app.config :as cf] [app.config :as cf]
[app.main.data.fonts :as df] [app.main.data.fonts :as df]
[app.main.features :as features]
[app.main.render :as render] [app.main.render :as render]
[app.main.repo :as repo] [app.main.repo :as repo]
[app.main.store :as st] [app.main.store :as st]
@ -99,22 +100,24 @@
(mf/defc object-svg (mf/defc object-svg
[{:keys [page-id file-id object-id render-embed?]}] [{:keys [page-id file-id object-id render-embed?]}]
(let [fetch-state (mf/use-fn (let [components-v2 (features/use-feature :components-v2)
(mf/deps file-id page-id object-id) fetch-state (mf/use-fn
(fn [] (mf/deps file-id page-id object-id)
(->> (rx/zip (fn []
(repo/query! :font-variants {:file-id file-id}) (->> (rx/zip
(repo/query! :page {:file-id file-id (repo/query! :font-variants {:file-id file-id})
:page-id page-id (repo/query! :page {:file-id file-id
:object-id object-id})) :page-id page-id
(rx/tap (fn [[fonts]] :object-id object-id
(when (seq fonts) :components-v2 components-v2}))
(st/emit! (df/fonts-fetched fonts))))) (rx/tap (fn [[fonts]]
(rx/map (comp :objects second)) (when (seq fonts)
(rx/map (fn [objects] (st/emit! (df/fonts-fetched fonts)))))
(let [objects (render/adapt-objects-for-shape objects object-id)] (rx/map (comp :objects second))
{:objects objects (rx/map (fn [objects]
:object (get objects object-id)})))))) (let [objects (render/adapt-objects-for-shape objects object-id)]
{:objects objects
:object (get objects object-id)}))))))
{:keys [objects object]} (use-resource fetch-state)] {:keys [objects object]} (use-resource fetch-state)]
@ -124,8 +127,8 @@
(when object (when object
(dom/set-page-style! (dom/set-page-style!
{:size (str/concat {:size (str/concat
(mth/ceil (:width object)) "px " (mth/ceil (:width object)) "px "
(mth/ceil (:height object)) "px")}))) (mth/ceil (:height object)) "px")})))
(when objects (when objects
[:& render/object-svg [:& render/object-svg
@ -135,17 +138,19 @@
(mf/defc objects-svg (mf/defc objects-svg
[{:keys [page-id file-id object-ids render-embed?]}] [{:keys [page-id file-id object-ids render-embed?]}]
(let [fetch-state (mf/use-fn (let [components-v2 (features/use-feature :components-v2)
(mf/deps file-id page-id) fetch-state (mf/use-fn
(fn [] (mf/deps file-id page-id)
(->> (rx/zip (fn []
(repo/query! :font-variants {:file-id file-id}) (->> (rx/zip
(repo/query! :page {:file-id file-id (repo/query! :font-variants {:file-id file-id})
:page-id page-id})) (repo/query! :page {:file-id file-id
(rx/tap (fn [[fonts]] :page-id page-id
(when (seq fonts) :components-v2 components-v2}))
(st/emit! (df/fonts-fetched fonts))))) (rx/tap (fn [[fonts]]
(rx/map (comp :objects second))))) (when (seq fonts)
(st/emit! (df/fonts-fetched fonts)))))
(rx/map (comp :objects second)))))
objects (use-resource fetch-state)] objects (use-resource fetch-state)]

View file

@ -48,11 +48,12 @@
(= :request-body-too-large code))) (= :request-body-too-large code)))
(defn- request-data-for-thumbnail (defn- request-data-for-thumbnail
[file-id revn] [file-id revn components-v2]
(let [path "api/rpc/query/file-data-for-thumbnail" (let [path "api/rpc/query/file-data-for-thumbnail"
params {:file-id file-id params {:file-id file-id
:revn revn :revn revn
:strip-frames-with-thumbnails true} :strip-frames-with-thumbnails true
:components-v2 components-v2}
request {:method :get request {:method :get
:uri (u/join (cfg/get-public-uri) path) :uri (u/join (cfg/get-public-uri) path)
:credentials "include" :credentials "include"
@ -107,18 +108,18 @@
(rx/map (constantly params))))) (rx/map (constantly params)))))
(defmethod impl/handler :thumbnails/generate (defmethod impl/handler :thumbnails/generate
[{:keys [file-id revn] :as message}] [{:keys [file-id revn components-v2] :as message}]
(letfn [(on-result [{:keys [data props]}] (letfn [(on-result [{:keys [data props]}]
{:data data {:data data
:fonts (:fonts props)}) :fonts (:fonts props)})
(on-cache-miss [_] (on-cache-miss [_]
(->> (request-data-for-thumbnail file-id revn) (->> (request-data-for-thumbnail file-id revn components-v2)
(rx/map render-thumbnail) (rx/map render-thumbnail)
(rx/mapcat persist-thumbnail)))] (rx/mapcat persist-thumbnail)))]
(if (debug? :disable-thumbnail-cache) (if (debug? :disable-thumbnail-cache)
(->> (request-data-for-thumbnail file-id revn) (->> (request-data-for-thumbnail file-id revn components-v2)
(rx/map render-thumbnail)) (rx/map render-thumbnail))
(->> (request-thumbnail file-id revn) (->> (request-thumbnail file-id revn)
(rx/catch not-found? on-cache-miss) (rx/catch not-found? on-cache-miss)

View file

@ -7,8 +7,11 @@
;; This namespace is only to export the functions for toggle features ;; This namespace is only to export the functions for toggle features
(ns features (ns features
(:require (:require
[app.main.ui.features :as features])) [app.main.features :as features]))
(defn ^:export autolayout [] (defn ^:export autolayout []
(features/toggle-feature! :auto-layout)) (features/toggle-feature! :auto-layout))
(defn ^:export components-v2 []
(features/toggle-feature! :components-v2))

View file

@ -105,7 +105,8 @@
shapes shapes
(:objects page) (:objects page)
(:id page) (:id page)
current-file-id)] current-file-id
true)]
(swap! idmap assoc instance-label (:id group) (swap! idmap assoc instance-label (:id group)
component-label (:id component-root)) component-label (:id component-root))

View file

@ -639,6 +639,14 @@ msgstr "Your name"
msgid "dashboard.your-penpot" msgid "dashboard.your-penpot"
msgstr "Your Penpot" msgstr "Your Penpot"
#: src/app/main/ui/alert.cljs
msgid "ds.alert-ok"
msgstr "Ok"
#: src/app/main/ui/alert.cljs
msgid "ds.alert-title"
msgstr "Attention"
#: src/app/main/ui/confirm.cljs #: src/app/main/ui/confirm.cljs
msgid "ds.component-subtitle" msgid "ds.component-subtitle"
msgstr "Components to update:" msgstr "Components to update:"
@ -718,6 +726,10 @@ msgstr "LDAP authentication is disabled."
msgid "errors.media-format-unsupported" msgid "errors.media-format-unsupported"
msgstr "The image format is not supported (must be svg, jpg or png)." msgstr "The image format is not supported (must be svg, jpg or png)."
#: src/app/main/data/workspace/persistence.cljs
msgid "errors.components-v2"
msgstr "This file has already used with Components V2 enabled."
#: src/app/main/data/workspace/persistence.cljs #: src/app/main/data/workspace/persistence.cljs
msgid "errors.media-too-large" msgid "errors.media-too-large"
msgstr "The image is too large to be inserted (must be under 5mb)." msgstr "The image is too large to be inserted (must be under 5mb)."

View file

@ -656,6 +656,14 @@ msgstr "Tu nombre"
msgid "dashboard.your-penpot" msgid "dashboard.your-penpot"
msgstr "Tu Penpot" msgstr "Tu Penpot"
#: src/app/main/ui/alert.cljs
msgid "ds.alert-ok"
msgstr "Ok"
#: src/app/main/ui/alert.cljs
msgid "ds.alert-title"
msgstr "Atención"
#: src/app/main/ui/confirm.cljs #: src/app/main/ui/confirm.cljs
msgid "ds.component-subtitle" msgid "ds.component-subtitle"
msgstr "Componentes a actualizar:" msgstr "Componentes a actualizar:"
@ -736,6 +744,10 @@ msgstr "La autheticacion via LDAP esta deshabilitada."
msgid "errors.media-format-unsupported" msgid "errors.media-format-unsupported"
msgstr "No se reconoce el formato de imagen (debe ser svg, jpg o png)." msgstr "No se reconoce el formato de imagen (debe ser svg, jpg o png)."
#: src/app/main/data/workspace/persistence.cljs
msgid "errors.components-v2"
msgstr "Este fichero ya se ha usado con los Componentes V2 activados."
#: src/app/main/data/workspace/persistence.cljs #: src/app/main/data/workspace/persistence.cljs
msgid "errors.media-too-large" msgid "errors.media-too-large"
msgstr "La imagen es demasiado grande (debe tener menos de 5mb)." msgstr "La imagen es demasiado grande (debe tener menos de 5mb)."