mirror of
https://github.com/logseq/logseq.git
synced 2026-06-01 19:01:22 +00:00
enhance(plugins): uninstall unused plugins (#12603)
* feat(plugin): add bulk removal of disabled plugins and related UI components * enhance(plugin): add bulk removal feature for disabled plugins and themes * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * enhance(plugin): update bulk remove disabled plugins and themes messages --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -26,6 +26,10 @@
|
||||
|
||||
(declare open-waiting-updates-modal!)
|
||||
(defonce PER-PAGE-SIZE 15)
|
||||
(defonce DISABLED-PLUGINS-CLEANUP-THRESHOLD 100)
|
||||
(defonce DISABLED-PLUGINS-CLEANUP-SNOOZE-MS (* 1000 60 60 24 30))
|
||||
(defonce DISABLED-PLUGINS-CLEANUP-NOTIFICATION-ID :lsp-disabled-plugins-cleanup-warning)
|
||||
(defonce DISABLED-PLUGINS-CLEANUP-SNOOZED-AT-KEY :lsp-disabled-plugins-cleanup-snoozed-at)
|
||||
|
||||
(def *dirties-toggle-items (atom {}))
|
||||
|
||||
@@ -254,6 +258,15 @@
|
||||
(t :plugin/installing)]
|
||||
(t :plugin/install)))]]])
|
||||
|
||||
(defn- set-plugin-disabled!
|
||||
[id disabled?]
|
||||
(-> (js-invoke js/LSPluginCore (if disabled? "disable" "enable") id)
|
||||
(p/then (fn []
|
||||
(when-let [^js settings (and disabled?
|
||||
(some-> (plugin-handler/get-plugin-inst id) (.-settings)))]
|
||||
(.set settings "disabled-since" (js/Date.now)))))
|
||||
(p/catch #(js/console.error %))))
|
||||
|
||||
(rum/defc card-ctls-of-installed < rum/static
|
||||
[id name url sponsors unpacked? disabled?
|
||||
installing-or-updating? has-other-pending?
|
||||
@@ -312,7 +325,7 @@
|
||||
|
||||
(ui/toggle (not disabled?)
|
||||
(fn []
|
||||
(js-invoke js/LSPluginCore (if disabled? "enable" "disable") id)
|
||||
(set-plugin-disabled! id (not disabled?))
|
||||
(when (nil? (get @*dirties-toggle-items (keyword id)))
|
||||
(swap! *dirties-toggle-items assoc (keyword id) (not disabled?))))
|
||||
true)]])
|
||||
@@ -594,6 +607,168 @@
|
||||
[:span.pr-3.opacity-80 text]
|
||||
(ui/toggle enabled #() true)]))
|
||||
|
||||
(defn- disabled-plugin-sort-key
|
||||
[{:keys [id name title settings]}]
|
||||
(let [disabled-since (:disabled-since settings)
|
||||
plugin-name (or title name id)]
|
||||
[(if (number? disabled-since) 1 0)
|
||||
(or disabled-since 0)
|
||||
(util/safe-lower-case plugin-name)
|
||||
id]))
|
||||
|
||||
(defn- plugin-in-category?
|
||||
[category plugin]
|
||||
(case category
|
||||
:all true
|
||||
:plugins (not (:theme plugin))
|
||||
:themes (:theme plugin)))
|
||||
|
||||
(defn- get-disabled-plugins-for-removal
|
||||
[category]
|
||||
(->> (vals (state/sub [:plugin/installed-plugins]))
|
||||
(filter #(and (plugin-in-category? category %)
|
||||
(get-in % [:settings :disabled])))
|
||||
(sort-by disabled-plugin-sort-key)))
|
||||
|
||||
(defn- unregister-plugins-sequentially!
|
||||
[plugin-ids]
|
||||
(reduce
|
||||
(fn [chain id]
|
||||
(p/then chain
|
||||
(fn []
|
||||
(p/let [_ (plugin-common-handler/unregister-plugin id)]
|
||||
(when (util/electron?)
|
||||
(plugin-config-handler/remove-plugin id))))))
|
||||
(.resolve js/Promise nil)
|
||||
plugin-ids))
|
||||
|
||||
(rum/defc bulk-remove-disabled-plugins-container
|
||||
[category]
|
||||
(let [plugins (get-disabled-plugins-for-removal category)
|
||||
plugin-ids (mapv :id plugins)
|
||||
[selected-ids set-selected-ids!] (rum/use-state (set (take 20 plugin-ids)))
|
||||
[pending? set-pending!] (rum/use-state false)
|
||||
selected-plugin-ids (->> plugins
|
||||
(map :id)
|
||||
(filter selected-ids)
|
||||
vec)
|
||||
all-selected? (and (seq plugin-ids)
|
||||
(= (count selected-plugin-ids) (count plugin-ids)))
|
||||
toggle-selected! (fn [id checked?]
|
||||
(set-selected-ids!
|
||||
((if checked? conj disj) selected-ids id)))
|
||||
remove-selected! (fn []
|
||||
(when (and (seq selected-plugin-ids)
|
||||
(not pending?))
|
||||
(-> (shui/dialog-confirm!
|
||||
[:b (t :plugin/bulk-remove-disabled-delete-alert (count selected-plugin-ids))]
|
||||
{:cancel-label (t :ui/cancel)
|
||||
:ok-label (t :ui/delete)})
|
||||
(p/then (fn []
|
||||
(set-pending! true)
|
||||
(-> (unregister-plugins-sequentially! selected-plugin-ids)
|
||||
(p/then (fn []
|
||||
(notification/show!
|
||||
(t :plugin/bulk-remove-disabled-success (count selected-plugin-ids))
|
||||
:success)
|
||||
(shui/dialog-close!)))
|
||||
(p/catch (fn [e]
|
||||
(notification/show! (str e) :error)))
|
||||
(p/finally #(set-pending! false))))))))]
|
||||
[:div.p-4.flex.flex-col.gap-3
|
||||
[:h1.text-xl.font-bold (t :plugin/bulk-remove-disabled-title)]
|
||||
(if (seq plugins)
|
||||
[:<>
|
||||
[:p.opacity-70.text-sm (t :plugin/bulk-remove-disabled-desc)]
|
||||
[:ul.max-h-96.overflow-y-auto.flex.flex-col.gap-2.ml-0
|
||||
(for [{:keys [id name title version icon]} plugins
|
||||
:let [selected? (contains? selected-ids id)]]
|
||||
[:li.flex.items-center.gap-3.rounded-md.border.p-2.select-none
|
||||
{:key id
|
||||
:class (str "cursor-pointer "
|
||||
(if selected?
|
||||
"border-primary bg-base-3"
|
||||
"border-transparent bg-base-2 hover:bg-base-3"))
|
||||
:on-click #(when-not pending?
|
||||
(toggle-selected! id (not selected?)))}
|
||||
(shui/checkbox
|
||||
{:checked selected?
|
||||
:disabled pending?
|
||||
:on-click #(.stopPropagation %)
|
||||
:on-checked-change #(toggle-selected! id (true? %))})
|
||||
[:span.flex.h-10.w-10.shrink-0.items-center.justify-center.overflow-hidden.rounded.bg-base-3
|
||||
(if (and icon (not (string/blank? icon)))
|
||||
[:img {:src icon
|
||||
:class "h-full w-full object-contain"}]
|
||||
[:span.flex.h-6.w-6.items-center.justify-center.overflow-hidden
|
||||
svg/folder])]
|
||||
[:div.flex-1.overflow-hidden
|
||||
[:div.font-medium.truncate (or title name id)]
|
||||
[:div.text-xs.opacity-60.truncate (str "ID: " id)]]
|
||||
(when version
|
||||
[:small.opacity-50.shrink-0 version])])]
|
||||
[:div.flex.items-center.justify-between.gap-2
|
||||
[:div.flex.gap-2
|
||||
(shui/button {:variant :ghost
|
||||
:disabled (or pending? all-selected?)
|
||||
:on-click #(set-selected-ids! (set plugin-ids))}
|
||||
(t :plugin/bulk-remove-disabled-select-all))
|
||||
(shui/button {:variant :ghost
|
||||
:disabled (or pending? (empty? selected-plugin-ids))
|
||||
:on-click #(set-selected-ids! #{})}
|
||||
(t :plugin/bulk-remove-disabled-clear-selection))]
|
||||
[:div.flex.gap-2
|
||||
(shui/button {:variant :ghost
|
||||
:disabled pending?
|
||||
:on-click #(shui/dialog-close!)}
|
||||
(t :ui/cancel))
|
||||
(shui/button {:disabled (or pending? (empty? selected-plugin-ids))
|
||||
:on-click remove-selected!}
|
||||
(if pending?
|
||||
(ui/loading (t :plugin/uninstall))
|
||||
(t :plugin/bulk-remove-disabled-confirm (count selected-plugin-ids))))]]]
|
||||
[:div.flex.items-center.justify-center.py-8.opacity-50
|
||||
(t :plugin/bulk-remove-disabled-empty)])]))
|
||||
|
||||
(defn- disabled-plugins-cleanup-snoozed?
|
||||
[]
|
||||
(let [snoozed-at (storage/get DISABLED-PLUGINS-CLEANUP-SNOOZED-AT-KEY)]
|
||||
(and (number? snoozed-at)
|
||||
(< (- (js/Date.now) snoozed-at) DISABLED-PLUGINS-CLEANUP-SNOOZE-MS))))
|
||||
|
||||
(defn- open-bulk-remove-disabled-plugins-dialog!
|
||||
[category]
|
||||
(notification/clear! DISABLED-PLUGINS-CLEANUP-NOTIFICATION-ID)
|
||||
(shui/dialog-open!
|
||||
(fn []
|
||||
(bulk-remove-disabled-plugins-container category))))
|
||||
|
||||
(defn- snooze-disabled-plugins-cleanup-warning!
|
||||
[]
|
||||
(storage/set DISABLED-PLUGINS-CLEANUP-SNOOZED-AT-KEY (js/Date.now))
|
||||
(notification/clear! DISABLED-PLUGINS-CLEANUP-NOTIFICATION-ID))
|
||||
|
||||
(defn- show-disabled-plugins-cleanup-warning!
|
||||
[]
|
||||
(let [disabled-count (count (get-disabled-plugins-for-removal :all))]
|
||||
(when (and (>= disabled-count DISABLED-PLUGINS-CLEANUP-THRESHOLD)
|
||||
(not (disabled-plugins-cleanup-snoozed?)))
|
||||
(notification/show!
|
||||
[:div.flex.flex-col.gap-2
|
||||
[:div (t :plugin/disabled-cleanup-warning-title disabled-count)]
|
||||
[:div.opacity-70 (t :plugin/disabled-cleanup-warning-desc)]
|
||||
[:div.flex.gap-2.pt-1
|
||||
(ui/button (t :plugin/disabled-cleanup-warning-clean-now)
|
||||
:small? true
|
||||
:on-click #(open-bulk-remove-disabled-plugins-dialog! :all))
|
||||
(ui/button (t :plugin/disabled-cleanup-warning-later)
|
||||
:small? true
|
||||
:variant :ghost
|
||||
:on-click snooze-disabled-plugins-cleanup-warning!)]]
|
||||
:warning
|
||||
false
|
||||
DISABLED-PLUGINS-CLEANUP-NOTIFICATION-ID))))
|
||||
|
||||
(rum/defc ^:large-vars/cleanup-todo panel-control-tabs < rum/static
|
||||
[search-key *search-key category *category
|
||||
sort-by *sort-by filter-by *filter-by total-nums
|
||||
@@ -714,8 +889,12 @@
|
||||
(let [items (concat (if market?
|
||||
[{:title [:span.flex.items-center.gap-1 (ui/icon "rotate-clockwise") (t :plugin/refresh-lists)]
|
||||
:options {:on-click #(reload-market-fn)}}]
|
||||
[{:title [:span.flex.items-center.gap-1 (ui/icon "rotate-clockwise") (t :plugin/check-all-updates)]
|
||||
:options {:on-click #(plugin-handler/user-check-enabled-for-updates! (not= :plugins category))}}])
|
||||
(concat
|
||||
[{:title [:span.flex.items-center.gap-1 (ui/icon "rotate-clockwise") (t :plugin/check-all-updates)]
|
||||
:options {:on-click #(plugin-handler/user-check-enabled-for-updates! (not= :plugins category))}}]
|
||||
(when (contains? #{:plugins :themes} category)
|
||||
[{:title [:span.flex.items-center.gap-1 (ui/icon "trash") (t :plugin/bulk-remove-disabled)]
|
||||
:options {:on-click #(open-bulk-remove-disabled-plugins-dialog! category)}}])))
|
||||
|
||||
(when (util/electron?)
|
||||
[{:title [:span.flex.items-center.gap-1 (ui/icon "world") (t :settings.advanced/network-proxy)]
|
||||
@@ -1438,6 +1617,7 @@
|
||||
(rum/defc updates-notifications-impl
|
||||
[check-pending? auto-checking? online?]
|
||||
(let [[uid, set-uid] (rum/use-state nil)
|
||||
[cleanup-warning-pending? set-cleanup-warning-pending?!] (rum/use-state false)
|
||||
[sub-content, _set-sub-content!] (rum-utils/use-atom *updates-sub-content)
|
||||
notify! (fn [content status]
|
||||
(if auto-checking?
|
||||
@@ -1459,22 +1639,38 @@
|
||||
(when uid (notification/clear! uid))))
|
||||
[check-pending? sub-content])
|
||||
|
||||
(hooks/use-effect!
|
||||
(fn []
|
||||
(when (and cleanup-warning-pending?
|
||||
(not auto-checking?))
|
||||
(set-cleanup-warning-pending?! false)
|
||||
(show-disabled-plugins-cleanup-warning!)))
|
||||
[cleanup-warning-pending? auto-checking?])
|
||||
|
||||
(hooks/use-effect!
|
||||
;; scheduler for auto updates
|
||||
(fn []
|
||||
(when online?
|
||||
(let [last-updates (storage/get :lsp-last-auto-updates)]
|
||||
(when (and (not (false? last-updates))
|
||||
(or (true? last-updates)
|
||||
(not (number? last-updates))
|
||||
;; interval 12 hours
|
||||
(> (- (js/Date.now) last-updates) (* 60 60 12 1000))))
|
||||
(let [update-timer (js/setTimeout
|
||||
(fn []
|
||||
(plugin-handler/auto-check-enabled-for-updates!)
|
||||
(storage/set :lsp-last-auto-updates (js/Date.now)))
|
||||
(if (util/electron?) 3000 (* 60 1000)))]
|
||||
#(js/clearTimeout update-timer))))))
|
||||
(let [auto-update-delay (if (util/electron?) 3000 (* 60 1000))
|
||||
last-updates (storage/get :lsp-last-auto-updates)
|
||||
should-auto-update? (and (not (false? last-updates))
|
||||
(or (true? last-updates)
|
||||
(not (number? last-updates))
|
||||
;; interval 12 hours
|
||||
(> (- (js/Date.now) last-updates) (* 60 60 12 1000))))
|
||||
cleanup-warning-timer (when-not should-auto-update?
|
||||
(js/setTimeout #(set-cleanup-warning-pending?! true)
|
||||
(+ auto-update-delay 1000)))
|
||||
update-timer (when should-auto-update?
|
||||
(js/setTimeout
|
||||
(fn []
|
||||
(plugin-handler/auto-check-enabled-for-updates!)
|
||||
(storage/set :lsp-last-auto-updates (js/Date.now))
|
||||
(set-cleanup-warning-pending?! true))
|
||||
auto-update-delay))]
|
||||
#(do
|
||||
(some-> update-timer (js/clearTimeout))
|
||||
(some-> cleanup-warning-timer (js/clearTimeout))))))
|
||||
[online?])
|
||||
|
||||
[:<>]))
|
||||
|
||||
@@ -1126,6 +1126,17 @@
|
||||
:remove disj)]
|
||||
(save-plugin-preferences! {:pinnedToolbarItems (op-fn pinned (name key))}))))
|
||||
|
||||
(defn- remove-pinned-toolbar-items-of-plugin!
|
||||
[pid]
|
||||
(let [prefix (str (name pid) ":")
|
||||
pinned (state/sub [:plugin/preferences :pinnedToolbarItems])
|
||||
pinned (if (sequential? pinned) (vec pinned) [])
|
||||
updated-pinned (->> pinned
|
||||
(remove #(and (string? %) (string/starts-with? % prefix)))
|
||||
vec)]
|
||||
(when (not= pinned updated-pinned)
|
||||
(save-plugin-preferences! {:pinnedToolbarItems updated-pinned}))))
|
||||
|
||||
(defn hook-lifecycle-fn!
|
||||
[type f & args]
|
||||
(when (and type (fn? f))
|
||||
@@ -1232,6 +1243,7 @@
|
||||
(let [pid (keyword pid)]
|
||||
;; effects
|
||||
(unregister-plugin-themes pid)
|
||||
(remove-pinned-toolbar-items-of-plugin! pid)
|
||||
;; plugins
|
||||
(swap! state/state medley/dissoc-in [:plugin/installed-plugins pid])
|
||||
;; commands
|
||||
|
||||
Reference in New Issue
Block a user