diff --git a/backend/src/app/migrations.clj b/backend/src/app/migrations.clj index 0c04c1aa95..e6e1c6b5ea 100644 --- a/backend/src/app/migrations.clj +++ b/backend/src/app/migrations.clj @@ -444,7 +444,10 @@ :fn (mg/resource "app/migrations/sql/0140-mod-file-change-table.sql")} {:name "0140-add-locked-by-column-to-file-change-table" - :fn (mg/resource "app/migrations/sql/0140-add-locked-by-column-to-file-change-table.sql")}]) + :fn (mg/resource "app/migrations/sql/0140-add-locked-by-column-to-file-change-table.sql")} + + {:name "0141-add-idx-to-file_library_rel" + :fn (mg/resource "app/migrations/sql/0141-add-idx-to-file_library_rel.sql")}]) (defn apply-migrations! [pool name migrations] diff --git a/backend/src/app/migrations/sql/0141-add-idx-to-file_library_rel.sql b/backend/src/app/migrations/sql/0141-add-idx-to-file_library_rel.sql new file mode 100644 index 0000000000..c749f1772e --- /dev/null +++ b/backend/src/app/migrations/sql/0141-add-idx-to-file_library_rel.sql @@ -0,0 +1,2 @@ +CREATE INDEX file_library_rel__library_file_id__idx + ON file_library_rel (library_file_id); diff --git a/backend/src/app/rpc/commands/files.clj b/backend/src/app/rpc/commands/files.clj index 38a518856e..4b4149603f 100644 --- a/backend/src/app/rpc/commands/files.clj +++ b/backend/src/app/rpc/commands/files.clj @@ -78,6 +78,7 @@ ;; --- FILE PERMISSIONS + (def ^:private sql:file-permissions "select fpr.is_owner, fpr.is_admin, @@ -460,8 +461,42 @@ (:has-libraries row))) +;; --- COMMAND QUERY: get-library-usage + + +(declare get-library-usage) + +(def schema:get-library-usage + [:map {:title "get-library-usage"} + [:file-id ::sm/uuid]]) +:sample +(sv/defmethod ::get-library-usage + "Gets the number of files that use the specified library." + {::doc/added "2.10.0" + ::sm/params schema:get-library-usage + ::sm/result ::sm/int} + [{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id]}] + (dm/with-open [conn (db/open pool)] + (check-read-permissions! pool profile-id file-id) + (get-library-usage conn file-id))) + +(def ^:private sql:get-library-usage + "SELECT COUNT(*) AS used + FROM file_library_rel AS flr + JOIN file AS fl ON (flr.library_file_id = fl.id) + WHERE flr.library_file_id = ?::uuid + AND (fl.deleted_at IS NULL OR + fl.deleted_at > now())") + +(defn- get-library-usage + [conn file-id] + (let [row (db/exec-one! conn [sql:get-library-usage file-id])] + {:used-in (:used row)})) + + ;; --- QUERY COMMAND: get-page + (defn- prune-objects "Given the page data and the object-id returns the page data with all other not needed objects removed from the `:objects` data @@ -551,6 +586,24 @@ ;; --- COMMAND QUERY: get-team-shared-files +(defn- components-and-variants + "Return a set with all the variant-ids, and a list of components, but with + only one component by variant" + [components] + (let [{:keys [variant-ids components]} + (reduce (fn [{:keys [variant-ids components] :as acc} {:keys [variant-id] :as component}] + (cond + (nil? variant-id) + {:variant-ids variant-ids :components (conj components component)} + (contains? variant-ids variant-id) + acc + :else + {:variant-ids (conj variant-ids variant-id) :components (conj components component)})) + {:variant-ids #{} :components []} + components)] + {:components components + :variant-ids variant-ids})) + (def ^:private sql:team-shared-files "select f.id, f.revn, @@ -584,10 +637,13 @@ :sample (into [] (take limit sorted-assets))}))] (binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg id)] - (let [load-objects (fn [component] - (ctf/load-component-objects data component)) - components-sample (-> (assets-sample (ctkl/components data) 4) - (update :sample #(mapv load-objects %)))] + (let [load-objects (fn [component] + (ctf/load-component-objects data component)) + comps-and-variants (components-and-variants (ctkl/components-seq data)) + components (into {} (map (juxt :id identity) (:components comps-and-variants))) + components-sample (-> (assets-sample components 4) + (update :sample #(mapv load-objects %)) + (assoc :variants-count (-> comps-and-variants :variant-ids count)))] {:components components-sample :media (assets-sample (:media data) 3) :colors (assets-sample (:colors data) 3) @@ -641,6 +697,7 @@ ;; --- COMMAND QUERY: Files that use this File library + (def ^:private sql:library-using-files "SELECT f.id, f.name @@ -713,6 +770,7 @@ ;; --- COMMAND QUERY: get-file-summary + (defn- get-file-summary [{:keys [::db/conn] :as cfg} {:keys [profile-id id project-id] :as params}] (check-read-permissions! conn profile-id id) @@ -730,11 +788,13 @@ (cfeat/check-file-features! (:features file))) (binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg id)] - {:name (:name file) - :components-count (count (ctkl/components-seq (:data file))) - :graphics-count (count (get-in file [:data :media] [])) - :colors-count (count (get-in file [:data :colors] [])) - :typography-count (count (get-in file [:data :typographies] []))}))) + (let [components-and-variants (components-and-variants (ctkl/components-seq (:data file)))] + {:name (:name file) + :components-count (-> components-and-variants :components count) + :variants-count (-> components-and-variants :variant-ids count) + :graphics-count (count (get-in file [:data :media] [])) + :colors-count (count (get-in file [:data :colors] [])) + :typography-count (count (get-in file [:data :typographies] []))})))) (sv/defmethod ::get-file-summary "Retrieve a file summary by its ID. Only authenticated users." @@ -746,6 +806,7 @@ ;; --- COMMAND QUERY: get-file-info + (defn- get-file-info [{:keys [::db/conn] :as cfg} {:keys [id] :as params}] (db/get* conn :file diff --git a/frontend/src/app/main/data/dashboard.cljs b/frontend/src/app/main/data/dashboard.cljs index 23625cb41d..11da2135a0 100644 --- a/frontend/src/app/main/data/dashboard.cljs +++ b/frontend/src/app/main/data/dashboard.cljs @@ -438,8 +438,14 @@ ptk/WatchEvent (watch [_ _ _] (let [params {:id id :is-shared is-shared}] - (->> (rp/cmd! :set-file-shared params) - (rx/ignore)))))) + (rx/concat + (->> (rp/cmd! :set-file-shared params) + (rx/ignore)) + (when is-shared + (->> (rp/cmd! :get-file-summary {:id id}) + (rx/map (fn [summary] + (when (pos? (:variants-count summary)) + (ptk/event ::ev/event {::ev/name "set-file-variants-shared" ::ev/origin "dashboard"}))))))))))) (defn set-file-thumbnail [file-id thumbnail-id] diff --git a/frontend/src/app/main/data/workspace/clipboard.cljs b/frontend/src/app/main/data/workspace/clipboard.cljs index 5fbbdae994..55ae0b371e 100644 --- a/frontend/src/app/main/data/workspace/clipboard.cljs +++ b/frontend/src/app/main/data/workspace/clipboard.cljs @@ -895,7 +895,10 @@ (let [parent-type (cfh/get-shape-type all-objects (:parent-id shape)) external-lib? (not= file-id (:component-file shape)) component (ctn/get-component-from-shape shape libraries) - origin "workspace:paste"] + origin "workspace:paste" + any-parent-is-variant (->> (cfh/get-parents-with-self all-objects (:parent-id shape)) + (some ctc/is-variant?) + boolean)] ;; NOTE: we don't emit the create-shape event all the time for ;; avoid send a lot of events (that are not necessary); this @@ -906,7 +909,8 @@ :is-external-library external-lib? :type (get shape :type) :parent-type parent-type - :is-variant (ctc/is-variant? component)}) + :is-variant (ctc/is-variant? component) + :any-parent-is-variant any-parent-is-variant}) (if (cfh/has-layout? objects (:parent-id shape)) (ev/event {::ev/name "layout-add-element" ::ev/origin origin diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index c4cb6bba4a..e642763f16 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -592,6 +592,9 @@ page libraries) component (ctn/get-component-from-shape new-shape libraries) + any-parent-is-variant (->> (cfh/get-parents-with-self objects (:parent-id new-shape)) + (some ctk/is-variant?) + boolean) undo-id (js/Symbol)] @@ -602,7 +605,8 @@ {::ev/name "use-library-component" ::ev/origin origin :external-library (not= file-id current-file-id) - :is-variant (ctk/is-variant? component)}) + :is-variant (ctk/is-variant? component) + :any-parent-is-variant any-parent-is-variant}) (dwu/start-undo-transaction undo-id) (dch/commit-changes changes) (ptk/data-event :layout/update {:ids [(:id new-shape)]}) @@ -1364,10 +1368,19 @@ (update-in state [:files id] assoc :is-shared is-shared)) ptk/WatchEvent - (watch [_ _ _] - (let [params {:id id :is-shared is-shared}] - (->> (rp/cmd! :set-file-shared params) - (rx/ignore)))))) + (watch [_ state _] + (let [params {:id id :is-shared is-shared}] + (rx/concat + (->> (rp/cmd! :set-file-shared params) + (rx/ignore)) + (when is-shared + (let [has-variants? (->> (dsh/lookup-file-data state) + :components + vals + (some ctk/is-variant?))] + (if has-variants? + (rx/of (ptk/event ::ev/event {::ev/name "set-file-variants-shared" ::ev/origin "workspace"})) + (rx/empty))))))))) ;; --- Link and unlink Files @@ -1395,7 +1408,10 @@ ptk/WatchEvent (watch [_ state _] - (let [features (get state :features)] + (let [libraries (:shared-files state) + library (get libraries library-id) + features (get state :features) + variants-count (-> library :library-summary :components :variants-count)] (rx/concat (rx/merge (->> (rp/cmd! :link-file-to-library {:file-id file-id :library-id library-id}) @@ -1412,7 +1428,15 @@ (rx/map (fn [thumbnails] (fn [state] (update state :thumbnails merge thumbnails)))))) - (rx/of (ptk/reify ::attach-library-finished))))))) + (rx/of (ptk/reify ::attach-library-finished)) + (when (pos? variants-count) + (->> (rp/cmd! :get-library-usage {:file-id library-id}) + (rx/map (fn [library-usage] + (ptk/event ::ev/event {::ev/name "attach-library-variants" + :file-id file-id + :library-id library-id + :variants-count variants-count + :library-used-in (:used-in library-usage)})))))))))) (defn unlink-file-from-library [file-id library-id] diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index 498dbd2bad..11a00dae61 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -486,7 +486,11 @@ parent-type (cfh/get-shape-type objects (:parent-id shape)) external-lib? (not= file-id (:component-file shape)) component (ctn/get-component-from-shape shape libraries) - origin "workspace:duplicate-shapes"] + origin "workspace:duplicate-shapes" + + any-parent-is-variant (->> (cfh/get-parents-with-self objects (:parent-id shape)) + (some ctk/is-variant?) + boolean)] ;; NOTE: we don't emit the create-shape event all the time for ;; avoid send a lot of events (that are not necessary); this @@ -497,7 +501,8 @@ :is-external-library external-lib? :type (get shape :type) :parent-type parent-type - :is-variant (ctk/is-variant? component)}) + :is-variant (ctk/is-variant? component) + :any-parent-is-variant any-parent-is-variant}) (if (cfh/has-layout? objects (:parent-id shape)) (ev/event {::ev/name "layout-add-element" ::ev/origin origin diff --git a/frontend/src/app/main/data/workspace/tokens/application.cljs b/frontend/src/app/main/data/workspace/tokens/application.cljs index 3deffead5f..fc384e7ee0 100644 --- a/frontend/src/app/main/data/workspace/tokens/application.cljs +++ b/frontend/src/app/main/data/workspace/tokens/application.cljs @@ -8,6 +8,7 @@ (:require [app.common.data :as d] [app.common.files.tokens :as cft] + [app.common.types.component :as ctk] [app.common.types.shape.layout :as ctsl] [app.common.types.shape.radius :as ctsr] [app.common.types.shape.token :as ctst] @@ -481,14 +482,14 @@ objects (dsh/lookup-page-objects state) selected-shapes (select-keys objects shape-ids) - shape-ids (or (->> selected-shapes - (filter (fn [[_ shape]] - (or - (and (ctsl/any-layout-immediate-child? objects shape) - (some ctt/spacing-margin-keys attributes)) - (ctt/any-appliable-attr? attributes (:type shape))))) - (keys)) - []) + shapes (->> selected-shapes + (filter (fn [[_ shape]] + (or + (and (ctsl/any-layout-immediate-child? objects shape) + (some ctt/spacing-margin-keys attributes)) + (ctt/any-appliable-attr? attributes (:type shape)))))) + shape-ids (d/nilv (keys shapes) []) + any-variant? (->> shapes (some ctk/is-variant?) boolean) resolved-value (get-in resolved-tokens [(cft/token-identifier token) :resolved-value]) tokenized-attributes (cft/attributes-map attributes token) @@ -497,7 +498,8 @@ (rx/of (st/emit! (ev/event {::ev/name "apply-tokens" :type type - :applyed-to attributes})) + :applyed-to attributes + :applied-to-variant any-variant?})) (dwu/start-undo-transaction undo-id) (dwsh/update-shapes shape-ids (fn [shape] (cond-> shape diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs index 28a683a36b..f0002e3f96 100644 --- a/frontend/src/app/main/ui/workspace/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/context_menu.cljs @@ -577,7 +577,9 @@ any-variant? (some ctk/is-variant? shapes) do-add-component (mf/use-fn #(st/emit! (dwl/add-component))) do-add-multiple-components (mf/use-fn #(st/emit! (dwl/add-multiple-components))) - do-combine-as-variants (mf/use-fn #(st/emit! (dwv/combine-as-variants))) + do-combine-as-variants (mf/use-fn #(st/emit! + (ptk/event ::ev/event {::ev/name "combine-as-variants" :trigger "context-menu-component"}) + (dwv/combine-as-variants))) do-add-variant (mf/use-fn (mf/deps shapes) #(st/emit! diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets/components.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets/components.cljs index d368e46e33..fa30328a40 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets/components.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets/components.cljs @@ -505,7 +505,9 @@ ids (into #{} (map :main-instance-id comps)) page-id (->> comps first :main-instance-page)] - (st/emit! (dwv/combine-as-variants ids {:page-id page-id}))))) + (st/emit! + (ptk/event ::ev/event {::ev/name "combine-as-variants" :trigger "context-menu-assets-group"}) + (dwv/combine-as-variants ids {:page-id page-id}))))) on-drag-start @@ -554,7 +556,9 @@ (let [page-id (->> selected-and-current-full first :main-instance-page) ids (into #{} (map :main-instance-id selected-full))] - (st/emit! (dwv/combine-as-variants ids {:page-id page-id})))))] + (st/emit! + (ptk/event ::ev/event {::ev/name "combine-as-variants" :trigger "context-menu-assets"}) + (dwv/combine-as-variants ids {:page-id page-id})))))] [:& cmm/asset-section {:file-id file-id :title (tr "workspace.assets.components") 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 165e01495d..6cc9da2aa8 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 @@ -909,7 +909,9 @@ on-combine-as-variants (mf/use-fn - #(st/emit! (dwv/combine-as-variants))) + #(st/emit! + (ptk/event ::ev/event {::ev/name "combine-as-variants" :trigger "button-design-tab"}) + (dwv/combine-as-variants))) ;; NOTE: function needed for force rerender from the bottom ;; components. This is because `component-annotation`