diff --git a/backend/src/app/rpc/mutations/files.clj b/backend/src/app/rpc/mutations/files.clj index ccdacf86a..e4768e78e 100644 --- a/backend/src/app/rpc/mutations/files.clj +++ b/backend/src/app/rpc/mutations/files.clj @@ -46,7 +46,7 @@ (s/def ::is-shared ::us/boolean) (s/def ::create-file (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 [{:keys [pool] :as cfg} {:keys [profile-id project-id] :as params}] @@ -66,11 +66,12 @@ (defn create-file [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} :as params}] (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 (d/without-nils {:id id @@ -317,10 +318,11 @@ (s/def ::session-id ::us/uuid) (s/def ::revn ::us/integer) +(s/def ::components-v2 ::us/boolean) (s/def ::update-file (s/and (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] (or (contains? o :changes) (contains? o :changes-with-metadata))))) @@ -357,7 +359,8 @@ (simpl/del-object backend 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) (:revn file)) @@ -382,12 +385,18 @@ (update :data (fn [data] ;; Trace the length of bytes of processed data (mtx/run! metrics {:id :update-file-bytes-processed :inc (alength data)}) - (-> data - (blob/decode) - (assoc :id (:id file)) - (pmg/migrate-data) - (cp/process-changes changes) - (blob/encode)))))] + (cond-> data + :always + (-> (blob/decode) + (assoc :id (:id file)) + (pmg/migrate-data)) + + components-v2 + (ctf/migrate-to-components-v2) + + :always + (-> (cp/process-changes changes) + (blob/encode))))))] ;; Insert change to the xlog (db/insert! conn :file-change {:id (uuid/next) diff --git a/backend/src/app/rpc/queries/files.clj b/backend/src/app/rpc/queries/files.clj index 0fd9431e0..95ae1c0dc 100644 --- a/backend/src/app/rpc/queries/files.clj +++ b/backend/src/app/rpc/queries/files.clj @@ -13,6 +13,7 @@ [app.common.pages.helpers :as cph] [app.common.pages.migrations :as pmg] [app.common.spec :as us] + [app.common.types.file :as ctf] [app.common.types.shape-tree :as ctt] [app.db :as db] [app.db.sql :as sql] @@ -27,7 +28,6 @@ [cuerdas.core :as str])) (declare decode-row) -(declare decode-row-xf) ;; --- Helpers & Specs @@ -39,6 +39,7 @@ (s/def ::profile-id ::us/uuid) (s/def ::team-id ::us/uuid) (s/def ::search-term ::us/string) +(s/def ::components-v2 ::us/boolean) ;; --- Query: File Permissions @@ -123,8 +124,7 @@ (defn check-comment-permissions! [conn profile-id file-id share-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) (ex/raise :type :not-found :code :object-not-found @@ -227,20 +227,29 @@ (d/index-by :object-id :data)))))) (defn retrieve-file - [{:keys [pool] :as cfg} id] - (->> (db/get-by-id pool :file id) - (decode-row) - (pmg/migrate-file))) + [{:keys [pool] :as cfg} id components-v2] + (let [file (->> (db/get-by-id pool :file id) + (decode-row) + (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/keys :req-un [::profile-id ::id])) + (s/keys :req-un [::profile-id ::id] + :opt-un [::components-v2])) (sv/defmethod ::file "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)] (check-read-permissions! perms) - (let [file (retrieve-file cfg id) + (let [file (retrieve-file cfg id components-v2) thumbs (retrieve-object-thumbnails cfg id)] (-> file (assoc :thumbnails thumbs) @@ -269,7 +278,7 @@ (s/def ::page (s/and (s/keys :req-un [::profile-id ::file-id] - :opt-un [::page-id ::object-id]) + :opt-un [::page-id ::object-id ::components-v2]) (fn [obj] (if (contains? obj :object-id) (contains? obj :page-id) @@ -285,9 +294,9 @@ mandatory. 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) - (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 (get-in file [:data :pages-index page-id])] @@ -374,14 +383,15 @@ (update :objects assoc-thumbnails page-id thumbs))))) (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 "Retrieves the data for generate the thumbnail of the file. Used 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) - (let [file (retrieve-file cfg file-id)] + (let [file (retrieve-file cfg file-id components-v2)] {:file-id file-id :revn (:revn file) :page (get-file-thumbnail-data cfg file)})) @@ -523,7 +533,3 @@ (cond-> row changes (assoc :changes (blob/decode changes)) data (assoc :data (blob/decode data))))) - -(def decode-row-xf - (comp (map decode-row) - (map pmg/migrate-file))) diff --git a/backend/src/app/rpc/queries/viewer.clj b/backend/src/app/rpc/queries/viewer.clj index 681b8ef47..03d9c9342 100644 --- a/backend/src/app/rpc/queries/viewer.clj +++ b/backend/src/app/rpc/queries/viewer.clj @@ -23,8 +23,8 @@ (db/get-by-id pool :project id {:columns [:id :name :team-id]})) (defn- retrieve-bundle - [{:keys [pool] :as cfg} file-id profile-id] - (p/let [file (files/retrieve-file cfg file-id) + [{:keys [pool] :as cfg} file-id profile-id components-v2] + (p/let [file (files/retrieve-file cfg file-id components-v2) project (retrieve-project pool (:project-id file)) libs (files/retrieve-file-libraries cfg false file-id) users (comments/retrieve-file-comments-users pool file-id profile-id) @@ -47,14 +47,14 @@ (s/def ::share-id ::us/uuid) (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} - [{: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) perms (files/get-permissions pool profile-id file-id share-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-in [:file :thumbnails] thumbs))] diff --git a/backend/test/app/services_files_test.clj b/backend/test/app/services_files_test.clj index aa188b215..c8ab0c25c 100644 --- a/backend/test/app/services_files_test.clj +++ b/backend/test/app/services_files_test.clj @@ -32,7 +32,8 @@ :project-id proj-id :id file-id :name "foobar" - :is-shared false} + :is-shared false + :components-v2 true} out (th/mutation! data)] ;; (th/print-result! out) @@ -71,7 +72,8 @@ (t/testing "query single file without users" (let [data {::th/type :file :profile-id (:id prof) - :id file-id} + :id file-id + :components-v2 true} out (th/query! data)] ;; (th/print-result! out) @@ -95,7 +97,8 @@ (t/testing "query single file after delete" (let [data {::th/type :file :profile-id (:id prof) - :id file-id} + :id file-id + :components-v2 true} out (th/query! data)] ;; (th/print-result! out) @@ -143,6 +146,7 @@ :session-id (uuid/random) :profile-id profile-id :revn revn + :components-v2 true :changes changes} out (th/mutation! params)] (t/is (nil? (:error out))) @@ -171,6 +175,7 @@ :id shid :parent-id uuid/zero :frame-id uuid/zero + :components-v2 true :obj {:id shid :name "image" :frame-id uuid/zero @@ -246,7 +251,8 @@ :profile-id (:id profile2) :project-id (:default-project-id profile1) :name "foobar" - :is-shared false} + :is-shared false + :components-v2 true} out (th/mutation! data) error (:error out)] @@ -462,6 +468,7 @@ (th/update-file* {:file-id (:id file) :profile-id (:id prof) :revn 0 + :components-v2 true :changes changes}) (t/testing "RPC page query (rendering purposes)" @@ -469,7 +476,8 @@ ;; Query :page RPC method without passing page-id (let [data {::th/type :page :profile-id (:id prof) - :file-id (:id file)} + :file-id (:id file) + :components-v2 true} {:keys [error result] :as out} (th/query! data)] ;; (th/print-result! out) @@ -485,7 +493,8 @@ (let [data {::th/type :page :profile-id (:id prof) :file-id (:id file) - :page-id page-id} + :page-id page-id + :components-v2 true} {:keys [error result] :as out} (th/query! data)] ;; (th/print-result! out) (t/is (map? result)) @@ -501,7 +510,8 @@ :profile-id (:id prof) :file-id (:id file) :page-id page-id - :object-id frame1-id} + :object-id frame1-id + :components-v2 true} {:keys [error result] :as out} (th/query! data)] ;; (th/print-result! out) (t/is (map? result)) @@ -516,7 +526,8 @@ (let [data {::th/type :page :profile-id (:id prof) :file-id (:id file) - :object-id frame1-id} + :object-id frame1-id + :components-v2 true} {:keys [error result] :as out} (th/query! data)] ;; (th/print-result! out) (t/is (= :validation (th/ex-type error))) @@ -537,7 +548,8 @@ ;; Check the result (let [data {::th/type :file-data-for-thumbnail :profile-id (:id prof) - :file-id (:id file)} + :file-id (:id file) + :components-v2 true} {:keys [error result] :as out} (th/query! data)] ;; (th/print-result! out) (t/is (map? result)) @@ -562,7 +574,8 @@ ;; Check the result (let [data {::th/type :file-data-for-thumbnail :profile-id (:id prof) - :file-id (:id file)} + :file-id (:id file) + :components-v2 true} {:keys [error result] :as out} (th/query! data)] ;; (th/print-result! out) (t/is (map? result)) diff --git a/backend/test/app/services_viewer_test.clj b/backend/test/app/services_viewer_test.clj index e8a01c255..86ad9189f 100644 --- a/backend/test/app/services_viewer_test.clj +++ b/backend/test/app/services_viewer_test.clj @@ -30,7 +30,8 @@ (let [data {::th/type :view-only-bundle :profile-id (:id prof) :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)] @@ -63,7 +64,8 @@ (let [data {::th/type :view-only-bundle :profile-id (:id prof2) :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)] ;; (th/print-result! out) @@ -78,7 +80,8 @@ :profile-id (:id prof2) :share-id @share-id :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)] ;; (th/print-result! out) @@ -93,7 +96,8 @@ (let [data {::th/type :view-only-bundle :share-id @share-id :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)] ;; (th/print-result! out) diff --git a/backend/test/app/test_helpers.clj b/backend/test/app/test_helpers.clj index 8849ac3cc..8f436ea02 100644 --- a/backend/test/app/test_helpers.clj +++ b/backend/test/app/test_helpers.clj @@ -164,7 +164,8 @@ (us/assert uuid? project-id) (#'files/create-file conn (merge {:id (mk-uuid "file" i) - :name (str "file" i)} + :name (str "file" i) + :components-v2 true} params)))) (defn mark-file-deleted* @@ -249,6 +250,7 @@ :metrics metrics} {:file file :revn revn + :components-v2 true :changes changes :session-id session-id :profile-id profile-id})))) diff --git a/common/src/app/common/pages/changes.cljc b/common/src/app/common/pages/changes.cljc index f47ed59a2..6916500a1 100644 --- a/common/src/app/common/pages/changes.cljc +++ b/common/src/app/common/pages/changes.cljc @@ -465,15 +465,20 @@ (defmulti components-changed (fn [_ change] (:type change))) (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 (let [page (ctpl/get-page file-data page-id) shape-and-parents (map #(ctn/get-shape page %) (into [id] (cph/get-parent-ids (:objects page) id))) - any-set? (some #(= (:type %) :set) operations)] - (when any-set? + need-sync? (fn [operation] + ; 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 - (filter #(:main-instance? %)) + (filter #(:main-instance? %)) ; Select shapes that are main component instances (map :id))))))) (defmethod components-changed :default diff --git a/common/src/app/common/pages/changes_builder.cljc b/common/src/app/common/pages/changes_builder.cljc index 3652bed1c..671e6a636 100644 --- a/common/src/app/common/pages/changes_builder.cljc +++ b/common/src/app/common/pages/changes_builder.cljc @@ -50,7 +50,7 @@ (defn with-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))] (vary-meta changes assoc ::file-data file-data ::applied-changes-count 0))) diff --git a/common/src/app/common/pages/common.cljc b/common/src/app/common/pages/common.cljc index cd9e94ee7..1b73deac2 100644 --- a/common/src/app/common/pages/common.cljc +++ b/common/src/app/common/pages/common.cljc @@ -9,7 +9,7 @@ [app.common.colors :as clr] [app.common.uuid :as uuid])) -(def file-version 20) +(def file-version 19) (def default-color clr/gray-20) (def root uuid/zero) diff --git a/common/src/app/common/pages/migrations.cljc b/common/src/app/common/pages/migrations.cljc index d7cb9a863..c1ee1dbd6 100644 --- a/common/src/app/common/pages/migrations.cljc +++ b/common/src/app/common/pages/migrations.cljc @@ -440,68 +440,5 @@ (update :pages-index 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 ;; and stroke props. This should be done for >1.14.x version. diff --git a/common/src/app/common/types/components_list.cljc b/common/src/app/common/types/components_list.cljc index 6fb2b0872..cb4bd70ae 100644 --- a/common/src/app/common/types/components_list.cljc +++ b/common/src/app/common/types/components_list.cljc @@ -14,13 +14,19 @@ (defn add-component [file-data id name path main-instance-id main-instance-page shapes] - (assoc-in file-data [:components id] - {:id id - :name name - :path path - :main-instance-id main-instance-id - :main-instance-page main-instance-page - :objects (d/index-by :id shapes)})) + (let [components-v2 (get-in file-data [:options :components-v2])] + (cond-> file-data + :always + (assoc-in [:components id] + {:id id + :name name + :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 [file-data component-id] diff --git a/common/src/app/common/types/container.cljc b/common/src/app/common/types/container.cljc index a0f63f0f1..d58c05e3a 100644 --- a/common/src/app/common/types/container.cljc +++ b/common/src/app/common/types/container.cljc @@ -65,7 +65,7 @@ "Clone the shape and all children. Generate new ids and detach from parent and frame. Update the original shapes to have links to the new ones." - [shape objects file-id] + [shape objects file-id components-v2] (assert (nil? (:component-id shape))) (assert (nil? (:component-file shape))) (assert (nil? (:shape-ref shape))) @@ -94,8 +94,10 @@ (nil? (:parent-id new-shape)) (assoc :component-id (:id new-shape) :component-file file-id - :component-root? true - :main-instance? true) + :component-root? true) + + components-v2 + (assoc :main-instance? true) (some? (:parent-id new-shape)) (dissoc :component-root?)))] @@ -103,6 +105,9 @@ (ctst/clone-object shape nil objects update-new-shape update-original-shape))) (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?] (let [component-shape (get-shape component (:id component)) diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index fb51d9fb7..4a84cf67e 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -83,14 +83,17 @@ :pages-index {}}) (defn make-file-data - ([file-id] - (make-file-data file-id (uuid/next))) + ([file-id components-v2] + (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")] - (-> empty-file-data - (assoc :id file-id) - (ctpl/add-page page))))) + (cond-> (-> empty-file-data + (assoc :id file-id) + (ctpl/add-page page)) + + components-v2 + (assoc-in [:options :components-v2] true))))) ;; Helpers @@ -162,9 +165,9 @@ assets-seq))) (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 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))] (if (some? library-page) (let [compare-pos (fn [pos shape] @@ -180,6 +183,74 @@ (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)])))) +(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 [file-data library-data used-components] (let [grid-gap 50 diff --git a/common/test/app/common/pages_test.cljc b/common/test/app/common/pages_test.cljc index 7de27ffae..89264a93d 100644 --- a/common/test/app/common/pages_test.cljc +++ b/common/test/app/common/pages_test.cljc @@ -15,7 +15,7 @@ (t/deftest process-change-set-option (let [file-id (uuid/custom 2 2) 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" (let [chg {:type :set-option :page-id page-id @@ -81,7 +81,7 @@ (t/deftest process-change-add-obj (let [file-id (uuid/custom 2 2) 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-b (uuid/custom 2 2) id-c (uuid/custom 2 3)] @@ -135,7 +135,7 @@ (t/deftest process-change-mod-obj (let [file-id (uuid/custom 2 2) 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" (let [chg {:type :mod-obj :page-id page-id @@ -162,7 +162,7 @@ (let [file-id (uuid/custom 2 2) page-id (uuid/custom 1 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 (assoc-in [:pages-index page-id :objects uuid/zero :shapes] [id]) (assoc-in [:pages-index page-id :objects id] @@ -206,7 +206,7 @@ file-id (uuid/custom 2 2) 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] #(-> % @@ -450,7 +450,7 @@ :obj {:type :rect :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)] (t/testing "preserve order on multiple shape mov 1" @@ -557,7 +557,7 @@ :parent-id group-1-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)] (t/testing "case 1" diff --git a/common/test/app/common/test_helpers/components.cljc b/common/test/app/common/test_helpers/components.cljc index 5cadf9a7d..843b74a4e 100644 --- a/common/test/app/common/test_helpers/components.cljc +++ b/common/test/app/common/test_helpers/components.cljc @@ -1,7 +1,6 @@ (ns app.common.test-helpers.components (:require - [cljs.test :as t :include-macros true] - [cljs.pprint :refer [pprint]] + [clojure.test :as t] [app.common.pages.helpers :as cph] [app.common.types.component :as ctk] [app.common.types.container :as ctn])) diff --git a/common/test/app/common/test_helpers/files.cljc b/common/test/app/common/test_helpers/files.cljc index d6b684d25..85d4b980d 100644 --- a/common/test/app/common/test_helpers/files.cljc +++ b/common/test/app/common/test_helpers/files.cljc @@ -31,7 +31,7 @@ ([file-id page-id props] (merge {:id file-id :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))) (defn sample-shape @@ -68,9 +68,10 @@ (let [page (ctpl/get-page file-data page-id) [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) - (:id file))] + (:id file) + true)] (swap! idmap assoc label (:id component-shape)) (-> file-data diff --git a/common/test/app/common/types/file_test.cljc b/common/test/app/common/types/file_test.cljc index 5c5f8527f..908c2b3e1 100644 --- a/common/test/app/common/types/file_test.cljc +++ b/common/test/app/common/types/file_test.cljc @@ -110,6 +110,7 @@ (t/is (= (:name p-group) "Group1")) (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 (ctk/is-main-of? c-group1 p-group)) diff --git a/frontend/resources/styles/main/partials/modal.scss b/frontend/resources/styles/main/partials/modal.scss index 4f0161563..98c35746a 100644 --- a/frontend/resources/styles/main/partials/modal.scss +++ b/frontend/resources/styles/main/partials/modal.scss @@ -179,7 +179,8 @@ } } -.confirm-dialog { +.confirm-dialog, +.alert-dialog { background-color: $color-white; p { diff --git a/frontend/src/app/main.cljs b/frontend/src/app/main.cljs index a7e38e185..a93eaf2a0 100644 --- a/frontend/src/app/main.cljs +++ b/frontend/src/app/main.cljs @@ -16,6 +16,7 @@ [app.main.sentry :as sentry] [app.main.store :as st] [app.main.ui :as ui] + [app.main.ui.alert] [app.main.ui.confirm] [app.main.ui.modal :refer [modal]] [app.main.ui.routes :as rt] diff --git a/frontend/src/app/main/data/dashboard.cljs b/frontend/src/app/main/data/dashboard.cljs index 93efdb0ba..ba61e6524 100644 --- a/frontend/src/app/main/data/dashboard.cljs +++ b/frontend/src/app/main/data/dashboard.cljs @@ -13,6 +13,7 @@ [app.main.data.fonts :as df] [app.main.data.media :as di] [app.main.data.users :as du] + [app.main.features :as features] [app.main.repo :as rp] [app.util.i18n :as i18n :refer [tr]] [app.util.router :as rt] @@ -718,12 +719,13 @@ (-deref [_] {:project-id project-id}) ptk/WatchEvent - (watch [it _ _] + (watch [it state _] (let [{:keys [on-success on-error] :or {on-success identity on-error rx/throw}} (meta params) 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) (rx/tap on-success) diff --git a/frontend/src/app/main/data/viewer.cljs b/frontend/src/app/main/data/viewer.cljs index 896c49390..bd3a77457 100644 --- a/frontend/src/app/main/data/viewer.cljs +++ b/frontend/src/app/main/data/viewer.cljs @@ -14,6 +14,7 @@ [app.common.types.shape.interactions :as ctsi] [app.main.data.comments :as dcm] [app.main.data.fonts :as df] + [app.main.features :as features] [app.main.repo :as rp] [app.util.globals :as ug] [app.util.router :as rt] @@ -100,9 +101,15 @@ (us/assert ::fetch-bundle-params params) (ptk/reify ::fetch-file ptk/WatchEvent - (watch [_ _ _] - (let [params' (cond-> {:file-id file-id} - (uuid? share-id) (assoc :share-id share-id))] + (watch [_ state _] + (let [components-v2 (features/active-feature? state :components-v2) + 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') (rx/mapcat (fn [{:keys [fonts] :as bundle}] diff --git a/frontend/src/app/main/data/workspace/changes.cljs b/frontend/src/app/main/data/workspace/changes.cljs index a1f4da573..488c5ec2b 100644 --- a/frontend/src/app/main/data/workspace/changes.cljs +++ b/frontend/src/app/main/data/workspace/changes.cljs @@ -192,6 +192,7 @@ process-page-changes (fn [[page-id _changes]] (update-indices page-id redo-changes))] + (rx/concat (rx/from (map process-page-changes changes-by-pages)) diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 8694a905d..3b05d5f91 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -30,6 +30,7 @@ [app.main.data.workspace.selection :as dws] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.undo :as dwu] + [app.main.features :as features] [app.main.refs :as refs] [app.main.repo :as rp] [app.main.store :as st] @@ -283,7 +284,7 @@ (defn- add-component2 "This is the second step of the component creation." - [selected] + [selected components-v2] (ptk/reify ::add-component2 IDeref (-deref [_] {:num-shapes (count selected)}) @@ -296,7 +297,7 @@ shapes (dwg/shapes-for-grouping objects selected)] (when-not (empty? shapes) (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)) (rx/of (dch/commit-changes changes) (dws/select-shapes (d/ordered-set (:id group))))))))))) @@ -310,10 +311,11 @@ (ptk/reify ::add-component ptk/WatchEvent (watch [_ state _] - (let [objects (wsh/lookup-page-objects state) - selected (->> (wsh/lookup-selected state) - (cph/clean-loops objects))] - (rx/of (add-component2 selected)))))) + (let [objects (wsh/lookup-page-objects state) + selected (->> (wsh/lookup-selected state) + (cph/clean-loops objects)) + components-v2 (features/active-feature? state :components-v2)] + (rx/of (add-component2 selected components-v2)))))) (defn rename-component "Rename the component with the given id, in the current file library." @@ -357,8 +359,12 @@ unames (into #{} (map :name) all-components) new-name (ctst/generate-unique-name unames (:name component)) - main-instance-page (wsh/lookup-page state (:main-instance-page component)) - main-instance-shape (ctn/get-shape main-instance-page (:main-instance-id component)) + components-v2 (features/active-feature? state :components-v2) + + 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-main-instance-shape new-main-instance-shapes] @@ -606,7 +612,11 @@ "Synchronize the given file from the given library. Walk through all 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 - 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] (sync-file file-id library-id nil nil)) ([file-id library-id asset-type asset-id] @@ -752,8 +762,10 @@ [] (ptk/reify ::watch-component-changes ptk/WatchEvent - (watch [_ _ stream] - (let [stopper + (watch [_ state stream] + (let [components-v2 (features/active-feature? state :components-v2) + + stopper (->> stream (rx/filter #(or (= :app.main.data.workspace/finalize-page (ptk/type %)) (= ::watch-component-changes (ptk/type %))))) @@ -775,16 +787,16 @@ components-changed (reduce #(into %1 (ch/components-changed data %2)) #{} changes)] - (js/console.log "components-changed" (clj->js components-changed)) (when (d/not-empty? components-changed) (apply st/emit! (map #(update-component-sync % (:id data)) components-changed)))))] - (->> change-str - (rx/with-latest-from workspace-data-str) - (rx/map check-changes) - (rx/take-until stopper)))))) + (when components-v2 + (->> change-str + (rx/with-latest-from workspace-data-str) + (rx/map check-changes) + (rx/take-until stopper))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Backend interactions diff --git a/frontend/src/app/main/data/workspace/libraries_helpers.cljs b/frontend/src/app/main/data/workspace/libraries_helpers.cljs index 14a57309f..195caf140 100644 --- a/frontend/src/app/main/data/workspace/libraries_helpers.cljs +++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs @@ -62,7 +62,7 @@ "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, 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) (:component-id (first shapes))) [(first shapes) (pcb/empty-changes it)] @@ -77,7 +77,7 @@ (dwg/prepare-create-group it objects page-id shapes name true)) [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 (pcb/add-component (:id new-shape) @@ -106,13 +106,14 @@ [new-instance-shape new-instance-shapes] - (ctn/make-component-instance main-instance-page - {:id (:id new-component-shape) - :name (:name new-component-shape) - :objects (d/index-by :id new-component-shapes)} - (:component-file main-instance-shape) - position - false)] + (when (and (some? main-instance-page) (some? main-instance-shape)) + (ctn/make-component-instance main-instance-page + {:id (:id new-component-shape) + :name (:name new-component-shape) + :objects (d/index-by :id new-component-shapes)} + (:component-file main-instance-shape) + position + false))] [new-component-shape new-component-shapes new-instance-shape new-instance-shapes])) @@ -254,7 +255,6 @@ (and (if (nil? component-id) (ctk/uses-library-components? shape library-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 (defmethod uses-assets? :colors diff --git a/frontend/src/app/main/data/workspace/persistence.cljs b/frontend/src/app/main/data/workspace/persistence.cljs index 445f5001c..1d1e1cc36 100644 --- a/frontend/src/app/main/data/workspace/persistence.cljs +++ b/frontend/src/app/main/data/workspace/persistence.cljs @@ -16,12 +16,15 @@ [app.config :as cf] [app.main.data.dashboard :as dd] [app.main.data.fonts :as df] + [app.main.data.modal :as modal] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.thumbnails :as dwt] + [app.main.features :as features] [app.main.repo :as rp] [app.main.store :as st] [app.util.http :as http] + [app.util.i18n :as i18n :refer [tr]] [app.util.router :as rt] [app.util.time :as dt] [beicon.core :as rx] @@ -124,8 +127,7 @@ (rx/map persist-synchronous-changes) (rx/take-until (rx/delay 100 stoper)) (rx/finalize (fn [] - (log/debug :hint "finalize persistence: synchronous save loop")))) - ))))) + (log/debug :hint "finalize persistence: synchronous save loop"))))))))) (defn persist-changes [file-id changes] @@ -134,12 +136,14 @@ (ptk/reify ::persist-changes ptk/WatchEvent (watch [_ state _] - (let [sid (:session-id state) - file (get state :workspace-file) - params {:id (:id file) - :revn (:revn file) - :session-id sid - :changes-with-metadata (into [] changes)}] + (let [components-v2 (features/active-feature? state :components-v2) + sid (:session-id state) + file (get state :workspace-file) + params {:id (:id file) + :revn (:revn file) + :session-id sid + :changes-with-metadata (into [] changes) + :components-v2 components-v2}] (when (= file-id (:id params)) (->> (rp/mutation :update-file params) @@ -175,13 +179,15 @@ (ptk/reify ::persist-synchronous-changes ptk/WatchEvent (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]) params {:id (:id file) :revn (:revn file) :session-id sid - :changes changes}] + :changes changes + :components-v2 components-v2}] (when (:id params) (->> (rp/mutation :update-file params) @@ -261,8 +267,9 @@ (ptk/reify ::fetch-bundle ptk/WatchEvent (watch [_ state _] - (let [share-id (-> state :viewer-local :share-id)] - (->> (rx/zip (rp/query :file-raw {:id file-id}) + (let [share-id (-> state :viewer-local :share-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 :project {:id project-id}) (rp/query :file-libraries {:file-id file-id}) @@ -276,8 +283,16 @@ :file-comments-users file-comments-users})) (rx/mapcat (fn [{:keys [project] :as 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 diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs index e33522b54..726997269 100644 --- a/frontend/src/app/main/data/workspace/shapes.cljs +++ b/frontend/src/app/main/data/workspace/shapes.cljs @@ -13,7 +13,6 @@ [app.common.pages.changes-builder :as pcb] [app.common.pages.helpers :as cph] [app.common.spec :as us] - [app.common.types.component :as ctk] [app.common.types.page :as ctp] [app.common.types.shape :as cts] [app.common.types.shape.interactions :as ctsi] @@ -24,6 +23,7 @@ [app.main.data.workspace.selection :as dws] [app.main.data.workspace.shape-layout :as dwsl] [app.main.data.workspace.state-helpers :as wsh] + [app.main.features :as features] [app.main.streams :as ms] [beicon.core :as rx] [cljs.spec.alpha :as s] @@ -148,7 +148,7 @@ ids (cph/clean-loops objects ids) lookup (d/getf objects) - local-library {file-id {:data file}} + components-v2 (features/active-feature? state :components-v2) groups-to-unmask (reduce (fn [group-ids id] @@ -221,23 +221,16 @@ (into (d/ordered-set) (find-all-empty-parents #{})) components-to-delete - (reduce (fn [components id] - (let [shape (get objects id) - - component - (when (and (:component-id shape) (:component-file shape)) - ;; Only local components may have main instances - (cph/get-component local-library (:component-file shape) (:component-id shape))) - - main-instance? - (when component - (ctk/is-main-instance? (:id shape) (:id page) component))] - - (if main-instance? - (conj components (:component-id shape)) - components))) - [] - (into ids all-children)) + (if components-v2 + (reduce (fn [components id] + (let [shape (get objects id)] + (if (and (= (:component-file shape) file-id) ;; Main instances should exist only in local file + (:main-instance? shape)) ;; but check anyway + (conj components (:component-id shape)) + components))) + [] + (into ids all-children)) + []) changes (-> (pcb/empty-changes it page-id) (pcb/with-page page) diff --git a/frontend/src/app/main/ui/features.cljs b/frontend/src/app/main/features.cljs similarity index 85% rename from frontend/src/app/main/ui/features.cljs rename to frontend/src/app/main/features.cljs index 30f0ebb59..236314cb8 100644 --- a/frontend/src/app/main/ui/features.cljs +++ b/frontend/src/app/main/features.cljs @@ -4,7 +4,7 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.main.ui.features +(ns app.main.features (:require [app.common.data :as d] [app.common.logging :as log] @@ -15,9 +15,9 @@ (log/set-level! :debug) -(def features-list #{:auto-layout}) +(def features-list #{:auto-layout :components-v2}) -(defn toggle-feature +(defn- toggle-feature [feature] (ptk/reify ::toggle-feature ptk/UpdateEvent @@ -41,6 +41,13 @@ (assert (contains? features-list feature) "Not supported 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 (l/derived :features st/state)) diff --git a/frontend/src/app/main/ui/alert.cljs b/frontend/src/app/main/ui/alert.cljs new file mode 100644 index 000000000..1dcf78640 --- /dev/null +++ b/frontend/src/app/main/ui/alert.cljs @@ -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}]]]]])) diff --git a/frontend/src/app/main/ui/context.cljs b/frontend/src/app/main/ui/context.cljs index 7f2f191ac..8ec9e69fa 100644 --- a/frontend/src/app/main/ui/context.cljs +++ b/frontend/src/app/main/ui/context.cljs @@ -25,3 +25,4 @@ (def scroll-ctx (mf/create-context nil)) (def active-frames-ctx (mf/create-context nil)) (def render-thumbnails (mf/create-context nil)) +(def components-v2 (mf/create-context nil)) diff --git a/frontend/src/app/main/ui/dashboard/grid.cljs b/frontend/src/app/main/ui/dashboard/grid.cljs index f4057a60a..d852322f9 100644 --- a/frontend/src/app/main/ui/dashboard/grid.cljs +++ b/frontend/src/app/main/ui/dashboard/grid.cljs @@ -10,6 +10,7 @@ [app.common.math :as mth] [app.main.data.dashboard :as dd] [app.main.data.messages :as dm] + [app.main.features :as features] [app.main.fonts :as fonts] [app.main.refs :as refs] [app.main.store :as st] @@ -36,9 +37,11 @@ (defn ask-for-thumbnail "Creates some hooks to handle the files thumbnails cache" [file] - (wrk/ask! {:cmd :thumbnails/generate - :revn (:revn file) - :file-id (:id file)})) + (let [components-v2 (features/active-feature? :components-v2)] + (wrk/ask! {:cmd :thumbnails/generate + :revn (:revn file) + :file-id (:id file) + :components-v2 components-v2}))) (mf/defc grid-item-thumbnail {::mf/wrap [mf/memo]} diff --git a/frontend/src/app/main/ui/workspace.cljs b/frontend/src/app/main/ui/workspace.cljs index 6068c1799..18cc78118 100644 --- a/frontend/src/app/main/ui/workspace.cljs +++ b/frontend/src/app/main/ui/workspace.cljs @@ -11,6 +11,7 @@ [app.main.data.messages :as msg] [app.main.data.workspace :as dw] [app.main.data.workspace.persistence :as dwp] + [app.main.features :as features] [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.context :as ctx] @@ -114,13 +115,12 @@ (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) - wglobal (mf/deref refs/workspace-global) - libraries (mf/deref refs/workspace-libraries) - local-library (mf/deref refs/workspace-local-library) + (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) + + components-v2 (features/use-feature :components-v2) background-color (:background-color wglobal)] @@ -148,9 +148,7 @@ [:& (mf/provider ctx/current-team-id) {:value (:team-id project)} [:& (mf/provider ctx/current-project-id) {:value (:id project)} [:& (mf/provider ctx/current-page-id) {:value page-id} - [:& (mf/provider ctx/libraries) {:value (assoc libraries - (:id local-library) - {:data local-library})} + [:& (mf/provider ctx/components-v2) {:value components-v2} [:section#workspace {:style {:background-color background-color}} (when (not (:hide-ui layout)) [:& header {:file file @@ -169,5 +167,3 @@ :layout layout}] [:& workspace-loader])]]]]]])) - - diff --git a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs index 0cdd5193c..82d001351 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs @@ -9,7 +9,6 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.pages.helpers :as cph] - [app.common.types.component :as ctk] [app.common.uuid :as uuid] [app.main.data.workspace :as dw] [app.main.data.workspace.collapse :as dwc] @@ -87,7 +86,7 @@ (when (seq (:touched shape)) " *")]))) (mf/defc layer-item - [{:keys [index page item selected objects] :as props}] + [{:keys [index item selected objects] :as props}] (let [id (:id item) blocked? (:blocked item) hidden? (:hidden item) @@ -103,11 +102,10 @@ container? (or (cph/frame-shape? item) (cph/group-shape? item)) - libraries (mf/use-ctx ctx/libraries) - component (when (and (:component-id item) (:component-file item)) - (cph/get-component libraries (:component-file item) (:component-id item))) - main-instance? (when component - (ctk/is-main-instance? (:id item) (:id page) component)) + components-v2 (mf/use-ctx ctx/components-v2) + main-instance? (if components-v2 + (:main-instance? item) + true) toggle-collapse (mf/use-fn @@ -277,8 +275,7 @@ (for [[index id] (reverse (d/enumerate (:shapes item)))] (when-let [item (get objects id)] [:& layer-item - {:page page - :item item + {:item item :selected selected :index index :objects objects @@ -299,9 +296,8 @@ {::mf/wrap [#(mf/memo % =) #(mf/throttle % 200)]} [{:keys [objects] :as props}] - (let [page (mf/deref refs/workspace-page) - selected (mf/deref refs/selected-shapes) - selected (hooks/use-equal-memo selected) + (let [selected (mf/deref refs/selected-shapes) + selected (hooks/use-equal-memo selected) root (get objects uuid/zero)] [:ul.element-list [:& hooks/sortable-container {} @@ -315,8 +311,7 @@ :objects objects :key id}] [:& layer-item - {:page page - :item obj + {:item obj :selected selected :index index :objects objects @@ -326,16 +321,14 @@ {::mf/wrap [#(mf/memo % =) #(mf/throttle % 200)]} [{:keys [objects] :as props}] - (let [page (mf/deref refs/workspace-page) - selected (mf/deref refs/selected-shapes) + (let [selected (mf/deref refs/selected-shapes) selected (hooks/use-equal-memo selected) root (get objects uuid/zero)] [:ul.element-list (for [[index id] (d/enumerate (:shapes root))] (when-let [obj (get objects id)] [:& layer-item - {:page page - :item obj + {:item obj :selected selected :index index :objects objects diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs index 4f4ed1607..34e044ae9 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs @@ -6,8 +6,6 @@ (ns app.main.ui.workspace.sidebar.options.menus.component (:require - [app.common.pages.helpers :as cph] - [app.common.types.component :as ctk] [app.main.data.modal :as modal] [app.main.data.workspace :as dw] [app.main.data.workspace.libraries :as dwl] @@ -19,13 +17,12 @@ [app.util.i18n :as i18n :refer [tr]] [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 [{:keys [ids values shape-name] :as props}] (let [current-file-id (mf/use-ctx ctx/current-file-id) - current-page-id (mf/use-ctx ctx/current-page-id) - libraries (mf/use-ctx ctx/libraries) + components-v2 (mf/use-ctx ctx/components-v2) id (first ids) local (mf/use-state {:menu-open false}) @@ -33,10 +30,9 @@ component-id (:component-id values) library-id (:component-file values) show? (some? component-id) - - component (when (and component-id library-id) - (cph/get-component libraries library-id component-id)) - main-instance? (ctk/is-main-instance? id current-page-id component) + main-instance? (if components-v2 + (:main-instance? values) + true) on-menu-click (mf/use-callback diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs index 6a6957c25..cbd5d7115 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs @@ -6,8 +6,8 @@ (ns app.main.ui.workspace.sidebar.options.shapes.frame (:require + [app.main.features :as features] [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.constraints :refer [constraint-attrs constraints-menu]] [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs-shape fill-menu]] diff --git a/frontend/src/app/render.cljs b/frontend/src/app/render.cljs index 872e0f247..8e4f98182 100644 --- a/frontend/src/app/render.cljs +++ b/frontend/src/app/render.cljs @@ -13,6 +13,7 @@ [app.common.uri :as u] [app.config :as cf] [app.main.data.fonts :as df] + [app.main.features :as features] [app.main.render :as render] [app.main.repo :as repo] [app.main.store :as st] @@ -99,22 +100,24 @@ (mf/defc object-svg [{:keys [page-id file-id object-id render-embed?]}] - (let [fetch-state (mf/use-fn - (mf/deps file-id page-id object-id) - (fn [] - (->> (rx/zip - (repo/query! :font-variants {:file-id file-id}) - (repo/query! :page {:file-id file-id - :page-id page-id - :object-id object-id})) - (rx/tap (fn [[fonts]] - (when (seq fonts) - (st/emit! (df/fonts-fetched fonts))))) - (rx/map (comp :objects second)) - (rx/map (fn [objects] - (let [objects (render/adapt-objects-for-shape objects object-id)] - {:objects objects - :object (get objects object-id)})))))) + (let [components-v2 (features/use-feature :components-v2) + fetch-state (mf/use-fn + (mf/deps file-id page-id object-id) + (fn [] + (->> (rx/zip + (repo/query! :font-variants {:file-id file-id}) + (repo/query! :page {:file-id file-id + :page-id page-id + :object-id object-id + :components-v2 components-v2})) + (rx/tap (fn [[fonts]] + (when (seq fonts) + (st/emit! (df/fonts-fetched fonts))))) + (rx/map (comp :objects second)) + (rx/map (fn [objects] + (let [objects (render/adapt-objects-for-shape objects object-id)] + {:objects objects + :object (get objects object-id)})))))) {:keys [objects object]} (use-resource fetch-state)] @@ -124,8 +127,8 @@ (when object (dom/set-page-style! {:size (str/concat - (mth/ceil (:width object)) "px " - (mth/ceil (:height object)) "px")}))) + (mth/ceil (:width object)) "px " + (mth/ceil (:height object)) "px")}))) (when objects [:& render/object-svg @@ -135,17 +138,19 @@ (mf/defc objects-svg [{:keys [page-id file-id object-ids render-embed?]}] - (let [fetch-state (mf/use-fn - (mf/deps file-id page-id) - (fn [] - (->> (rx/zip - (repo/query! :font-variants {:file-id file-id}) - (repo/query! :page {:file-id file-id - :page-id page-id})) - (rx/tap (fn [[fonts]] - (when (seq fonts) - (st/emit! (df/fonts-fetched fonts))))) - (rx/map (comp :objects second))))) + (let [components-v2 (features/use-feature :components-v2) + fetch-state (mf/use-fn + (mf/deps file-id page-id) + (fn [] + (->> (rx/zip + (repo/query! :font-variants {:file-id file-id}) + (repo/query! :page {:file-id file-id + :page-id page-id + :components-v2 components-v2})) + (rx/tap (fn [[fonts]] + (when (seq fonts) + (st/emit! (df/fonts-fetched fonts))))) + (rx/map (comp :objects second))))) objects (use-resource fetch-state)] diff --git a/frontend/src/app/worker/thumbnails.cljs b/frontend/src/app/worker/thumbnails.cljs index cc28169be..1dc58811c 100644 --- a/frontend/src/app/worker/thumbnails.cljs +++ b/frontend/src/app/worker/thumbnails.cljs @@ -48,11 +48,12 @@ (= :request-body-too-large code))) (defn- request-data-for-thumbnail - [file-id revn] + [file-id revn components-v2] (let [path "api/rpc/query/file-data-for-thumbnail" params {:file-id file-id :revn revn - :strip-frames-with-thumbnails true} + :strip-frames-with-thumbnails true + :components-v2 components-v2} request {:method :get :uri (u/join (cfg/get-public-uri) path) :credentials "include" @@ -107,18 +108,18 @@ (rx/map (constantly params))))) (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]}] {:data data :fonts (:fonts props)}) (on-cache-miss [_] - (->> (request-data-for-thumbnail file-id revn) + (->> (request-data-for-thumbnail file-id revn components-v2) (rx/map render-thumbnail) (rx/mapcat persist-thumbnail)))] (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)) (->> (request-thumbnail file-id revn) (rx/catch not-found? on-cache-miss) diff --git a/frontend/src/features.cljs b/frontend/src/features.cljs index 353504a1e..f0e9af722 100644 --- a/frontend/src/features.cljs +++ b/frontend/src/features.cljs @@ -7,8 +7,11 @@ ;; This namespace is only to export the functions for toggle features (ns features (:require - [app.main.ui.features :as features])) + [app.main.features :as features])) (defn ^:export autolayout [] (features/toggle-feature! :auto-layout)) +(defn ^:export components-v2 [] + (features/toggle-feature! :components-v2)) + diff --git a/frontend/test/app/test_helpers/pages.cljs b/frontend/test/app/test_helpers/pages.cljs index a061ecc43..e66974d01 100644 --- a/frontend/test/app/test_helpers/pages.cljs +++ b/frontend/test/app/test_helpers/pages.cljs @@ -105,7 +105,8 @@ shapes (:objects page) (:id page) - current-file-id)] + current-file-id + true)] (swap! idmap assoc instance-label (:id group) component-label (:id component-root)) diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 172fe1f44..2bd88884b 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -639,6 +639,14 @@ msgstr "Your name" msgid "dashboard.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 msgid "ds.component-subtitle" msgstr "Components to update:" @@ -718,6 +726,10 @@ msgstr "LDAP authentication is disabled." msgid "errors.media-format-unsupported" 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 msgid "errors.media-too-large" msgstr "The image is too large to be inserted (must be under 5mb)." diff --git a/frontend/translations/es.po b/frontend/translations/es.po index e000b6145..a6c55af0d 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -656,6 +656,14 @@ msgstr "Tu nombre" msgid "dashboard.your-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 msgid "ds.component-subtitle" msgstr "Componentes a actualizar:" @@ -736,6 +744,10 @@ msgstr "La autheticacion via LDAP esta deshabilitada." msgid "errors.media-format-unsupported" 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 msgid "errors.media-too-large" msgstr "La imagen es demasiado grande (debe tener menos de 5mb)."