diff --git a/CHANGES.md b/CHANGES.md index 96049e356d..a7ba906908 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -41,6 +41,7 @@ - Fix long font names overlap [Taiga #11844](https://tree.taiga.io/project/penpot/issue/11844) - Fix paste behavior according to the selected element [Taiga #11979](https://tree.taiga.io/project/penpot/issue/11979) - Fix problem with export size [#7160](https://github.com/penpot/penpot/issues/7160) +- Fix multi level library dependencies [Taiga #12155](https://tree.taiga.io/project/penpot/issue/12155) ## 2.10.0 diff --git a/backend/src/app/rpc/commands/files.clj b/backend/src/app/rpc/commands/files.clj index d9d3f0bfd3..260e7f2fab 100644 --- a/backend/src/app/rpc/commands/files.clj +++ b/backend/src/app/rpc/commands/files.clj @@ -608,8 +608,16 @@ {:components components :variant-ids variant-ids})) +;;coalesce(string_agg(flr.library_file_id::text, ','), '') as library_file_ids (def ^:private sql:team-shared-files - "select f.id, + "with file_library_agg as ( + select flr.file_id, + coalesce(array_agg(flr.library_file_id) filter (where flr.library_file_id is not null), '{}') as library_file_ids + from file_library_rel flr + group by flr.file_id + ) + + select f.id, f.revn, f.vern, f.data, @@ -622,10 +630,12 @@ f.version, f.is_shared, ft.media_id, - p.team_id + p.team_id, + fla.library_file_ids from file as f inner join project as p on (p.id = f.project_id) left join file_thumbnail as ft on (ft.file_id = f.id and ft.revn = f.revn and ft.deleted_at is null) + left join file_library_agg as fla on fla.file_id = f.id where f.is_shared = true and f.deleted_at is null and p.deleted_at is null @@ -669,6 +679,8 @@ (dissoc :media-id) (assoc :thumbnail-id media-id)) (dissoc row :media-id)))) + (map (fn [row] + (update row :library-file-ids db/decode-pgarray #{}))) (map #(assoc % :library-summary (get-library-summary cfg %))) (map #(dissoc % :data)))))) @@ -1065,6 +1077,7 @@ [:library-id ::sm/uuid]]) (sv/defmethod ::link-file-to-library + "Link a file to a library. Returns the recursive list of libraries used by that library" {::doc/added "1.17" ::webhooks/event? true ::sm/params schema:link-file-to-library} @@ -1078,7 +1091,8 @@ (fn [{:keys [::db/conn]}] (check-edition-permissions! conn profile-id file-id) (check-edition-permissions! conn profile-id library-id) - (link-file-to-library conn params)))) + (link-file-to-library conn params) + (bfc/get-libraries cfg [library-id])))) ;; --- MUTATION COMMAND: unlink-file-from-library diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 6afccea347..21e529f1a4 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -90,7 +90,6 @@ (declare ^:private workspace-initialized) (declare ^:private fetch-libraries) -(declare ^:private libraries-fetched) ;; --- Initialize Workspace @@ -185,16 +184,6 @@ (update [_ state] (update state :files assoc (:id library) library)))) -(defn- libraries-fetched - [file-id libraries] - (ptk/reify ::libraries-fetched - ptk/UpdateEvent - (update [_ state] - (update state :files merge - (->> libraries - (map #(assoc % :library-of file-id)) - (d/index-by :id)))))) - (defn- fetch-libraries [file-id features] (ptk/reify ::fetch-libries @@ -204,7 +193,7 @@ (rx/mapcat (fn [libraries] (rx/concat - (rx/of (libraries-fetched file-id libraries)) + (rx/of (dwl/libraries-fetched file-id libraries)) (rx/merge (->> (rx/from libraries) (rx/merge-map diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 5eff7d1cec..a2f970a965 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -1381,6 +1381,32 @@ ;; --- Link and unlink Files +(defn libraries-fetched + [file-id libraries] + (ptk/reify ::libraries-fetched + ptk/UpdateEvent + (update [_ state] + (update state :files merge + (->> libraries + (map #(assoc % :library-of file-id)) + (d/index-by :id)))))) + +(defn- load-library-file + [file-id library-id] + (ptk/reify ::load-library-file + ptk/WatchEvent + (watch [_ state _] + (let [features (get state :features)] + (rx/merge + (->> (rp/cmd! :get-file {:id library-id :features features}) + (rx/merge-map fpmap/resolve-file) + (rx/map (fn [file] + (libraries-fetched file-id [file])))) + (->> (rp/cmd! :get-file-object-thumbnails {:file-id library-id :tag "component"}) + (rx/map (fn [thumbnails] + (fn [state] + (update state :thumbnails merge thumbnails)))))))))) + (defn link-file-to-library [file-id library-id] (ptk/reify ::attach-library @@ -1390,41 +1416,26 @@ :file-id file-id :library-id library-id}) - ;; NOTE: this event implements UpdateEvent protocol for perform an - ;; optimistic update state for make the UI feel more responsive. - ptk/UpdateEvent - (update [_ state] - (let [libraries (:workspace-shared-files state) - library (d/seek #(= (:id %) library-id) libraries)] - (if library - (update state :files assoc library-id - (-> library - (dissoc :library-summary) - (assoc :library-of file-id))) - state))) - ptk/WatchEvent (watch [_ state _] (let [libraries (:shared-files state) library (get libraries library-id) - features (get state :features) - variants-count (-> library :library-summary :components :variants-count)] + variants-count (-> library :library-summary :components :variants-count) + + loaded-libraries (->> (dsh/lookup-libraries state) + (remove (fn [[_ lib]] + (or (nil? (:data lib)) + (empty? (:data lib))))) + (map first) + set)] (rx/concat (rx/merge (->> (rp/cmd! :link-file-to-library {:file-id file-id :library-id library-id}) - (rx/ignore)) - (->> (rp/cmd! :get-file {:id library-id :features features}) - (rx/merge-map fpmap/resolve-file) - ;; FIXME: this should call the libraries-fetched event instead of ad-hoc assoc event - (rx/map (fn [file] - (assoc file :library-of file-id))) - (rx/map (fn [file] - (fn [state] - (assoc-in state [:files library-id] file))))) - (->> (rp/cmd! :get-file-object-thumbnails {:file-id library-id :tag "component"}) - (rx/map (fn [thumbnails] - (fn [state] - (update state :thumbnails merge thumbnails)))))) + (rx/merge-map (fn [libraries-to-load] + (as-> libraries-to-load $ + (remove loaded-libraries $) + (conj $ library-id) + (map #(load-library-file file-id %) $)))))) (rx/of (ptk/reify ::attach-library-finished)) (when (pos? variants-count) (->> (rp/cmd! :get-library-usage {:file-id library-id}) diff --git a/frontend/src/app/main/ui/workspace/libraries.cljs b/frontend/src/app/main/ui/workspace/libraries.cljs index eeea99f93f..1df95e74a1 100644 --- a/frontend/src/app/main/ui/workspace/libraries.cljs +++ b/frontend/src/app/main/ui/workspace/libraries.cljs @@ -30,6 +30,7 @@ [app.main.ui.components.search-bar :refer [search-bar*]] [app.main.ui.components.title-bar :refer [title-bar*]] [app.main.ui.context :as ctx] + [app.main.ui.ds.buttons.icon-button :refer [icon-button*]] [app.main.ui.ds.foundations.assets.icon :as i] [app.main.ui.ds.layout.tab-switcher :refer [tab-switcher*]] [app.main.ui.hooks :as h] @@ -48,9 +49,6 @@ (def ^:private add-icon (deprecated-icon/icon-xref :add (stl/css :add-icon))) -(def ^:private detach-icon - (deprecated-icon/icon-xref :detach (stl/css :detach-icon))) - (def ^:private library-icon (deprecated-icon/icon-xref :library (stl/css :library-icon))) @@ -199,7 +197,20 @@ empty-library? (empty-library? summary) selected (h/use-shared-state mdc/colorpalette-selected-broadcast-key :recent) + dependencies (mf/with-memo [shared-libraries] + (into {} (map (juxt :id :library-file-ids) (vals shared-libraries)))) + library-names (mf/with-memo [shared-libraries] + (into {} (map (fn [{:keys [id name]}] + [id name]) + (vals shared-libraries)))) + + find-connected-to + (mf/use-fn + (mf/deps dependencies) + (fn [library-id] + (->> dependencies + (keep (fn [[k v]] (when (contains? v library-id) k)))))) shared-libraries (mf/with-memo [shared-libraries linked-libraries file-id search-term] @@ -208,18 +219,29 @@ (remove #(= (:id %) file-id)) (remove #(contains? linked-libraries (:id %))) (filter #(matches-search (:name %) search-term)) + (map #(assoc % :connected-to (find-connected-to (:id %)))) + (map #(assoc % :connected-to-names (->> (:connected-to %) + (keep library-names)))) (sort-by (comp str/lower :name))))) linked-libraries - (mf/with-memo [linked-libraries] + (mf/with-memo [linked-libraries find-connected-to library-names] (->> (vals linked-libraries) + (map #(assoc % :connected-to (find-connected-to (:id %)))) + (map #(assoc % :connected-to-names (->> (:connected-to %) + (keep library-names)))) (sort-by (comp str/lower :name)))) + linked-libraries-ids (mf/with-memo [linked-libraries] + (into #{} (map :id) linked-libraries)) + + importing* (mf/use-state nil) sample-libraries [{:id "penpot-design-system", :name "Design system example"} {:id "wireframing-kit", :name "Wireframe library"} {:id "whiteboarding-kit", :name "Whiteboarding Kit"}] + change-search-term (mf/use-fn (fn [event] @@ -311,22 +333,30 @@ :value (tr "common.publish") :on-click publish}])] - (for [{:keys [id name data] :as library} linked-libraries] - [:div {:class (stl/css :section-list-item) - :key (dm/str id) - :data-testid "library-item"} - [:div {:class (stl/css :item-content)} - [:div {:class (stl/css :item-name)} name] - [:ul {:class (stl/css :item-contents)} - (let [summary (get-library-summary data)] - [:> library-description* {:summary summary}])]] + (for [{:keys [id name data connected-to connected-to-names] :as library} linked-libraries] + (let [disabled? (some #(contains? linked-libraries-ids %) connected-to)] + [:div {:class (stl/css :section-list-item) + :key (dm/str id) + :data-testid "library-item"} + [:div {:class (stl/css :item-content)} + [:div {:class (stl/css :item-name)} name] + [:ul {:class (stl/css :item-contents)} + (let [summary (get-library-summary data)] + [:* + [:> library-description* {:summary summary}] + (when (seq connected-to) + [:div {:class (stl/css :connected-to-wrapper)} + [:span "(" (tr "workspace.libraries.connected-to") " "] + [:span {:class (stl/css :connected-to-values)} (str/join ", " connected-to-names)] + [:span ")"]])])]] - [:button {:class (stl/css :item-button) - :type "button" - :title (tr "workspace.libraries.unlink-library-btn") - :data-library-id (dm/str id) - :on-click unlink-library} - detach-icon]])]] + [:> icon-button* {:type "button" + :aria-label (tr "workspace.libraries.unlink-library-btn") + :icon i/detach + :data-library-id (dm/str id) + :variant "secondary" + :disabled disabled? + :on-click unlink-library}]]))]] [:div {:class (stl/css :shared-section)} [:> title-bar* {:collapsable false diff --git a/frontend/src/app/main/ui/workspace/libraries.scss b/frontend/src/app/main/ui/workspace/libraries.scss index fc208dd237..c604371535 100644 --- a/frontend/src/app/main/ui/workspace/libraries.scss +++ b/frontend/src/app/main/ui/workspace/libraries.scss @@ -319,6 +319,14 @@ } } +.connected-to-wrapper { + display: block; +} + +.connected-to-values { + color: var(--color-foreground-primary); +} + // Modal Component v2 update .modal-v2-info { width: px2rem(664); diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 1d20bb73ff..e4f35c9086 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -5554,6 +5554,9 @@ msgstr "see all changes" msgid "workspace.libraries.updates" msgstr "UPDATES" +msgid "workspace.libraries.connected-to" +msgstr "Connected to" + #: src/app/main/ui/ds/notifications/shared/notification_pill.cljs:67, src/app/main/ui/ds/notifications/shared/notification_pill.cljs:72 msgid "workspace.notification-pill.detail" msgstr "Details" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index cc7b7845ee..e0830db85d 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -5583,6 +5583,9 @@ msgstr "ver todos los cambios" msgid "workspace.libraries.updates" msgstr "ACTUALIZACIONES" +msgid "workspace.libraries.connected-to" +msgstr "Conectada con" + #: src/app/main/ui/ds/notifications/shared/notification_pill.cljs:67, src/app/main/ui/ds/notifications/shared/notification_pill.cljs:72 msgid "workspace.notification-pill.detail" msgstr "Detalles"