summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Moore <andrewm.finewolf@gmail.com>2024-02-14 23:45:55 -0500
committerParty Wumpus <48649272+PartyWumpus@users.noreply.github.com>2024-02-20 21:39:43 +0000
commitc2ebc78836cafc96f03546e6dba5acfa86795d65 (patch)
tree0725e28b9af5f4ad3fce13f4ea35eeb87375ce50
parentdc1697d04961e1b413a43ad345c28e4fb3d5c03f (diff)
downloaddecky-loader-c2ebc78836cafc96f03546e6dba5acfa86795d65.tar.gz
decky-loader-c2ebc78836cafc96f03546e6dba5acfa86795d65.zip
[Feature] Freeze updates for devs (#582)
-rw-r--r--backend/decky_loader/browser.py6
-rw-r--r--backend/locales/en-US.json2
-rw-r--r--frontend/src/components/DeckyState.tsx8
-rw-r--r--frontend/src/components/modals/PluginUninstallModal.tsx1
-rw-r--r--frontend/src/components/settings/index.tsx2
-rw-r--r--frontend/src/components/settings/pages/plugin_list/PluginListLabel.tsx22
-rw-r--r--frontend/src/components/settings/pages/plugin_list/index.tsx30
-rw-r--r--frontend/src/frozen-plugins-service.tsx49
-rw-r--r--frontend/src/plugin-loader.tsx7
9 files changed, 116 insertions, 11 deletions
diff --git a/backend/decky_loader/browser.py b/backend/decky_loader/browser.py
index 35b36911..def81011 100644
--- a/backend/decky_loader/browser.py
+++ b/backend/decky_loader/browser.py
@@ -289,12 +289,16 @@ class PluginBrowser:
Args:
name (string): The name of the plugin
"""
+ frozen_plugins = self.settings.getSetting("frozenPlugins", [])
+ if name in frozen_plugins:
+ frozen_plugins.remove(name)
+ self.settings.setSetting("frozenPlugins", frozen_plugins)
+
hidden_plugins = self.settings.getSetting("hiddenPlugins", [])
if name in hidden_plugins:
hidden_plugins.remove(name)
self.settings.setSetting("hiddenPlugins", hidden_plugins)
-
plugin_order = self.settings.getSetting("pluginOrder", [])
if name in plugin_order:
diff --git a/backend/locales/en-US.json b/backend/locales/en-US.json
index ea0542ca..ca18f7da 100644
--- a/backend/locales/en-US.json
+++ b/backend/locales/en-US.json
@@ -99,12 +99,14 @@
}
},
"PluginListIndex": {
+ "freeze": "Freeze updates",
"hide": "Quick access: Hide",
"no_plugin": "No plugins installed!",
"plugin_actions": "Plugin Actions",
"reinstall": "Reinstall",
"reload": "Reload",
"show": "Quick access: Show",
+ "unfreeze": "Allow updates",
"uninstall": "Uninstall",
"update_all_one": "Update 1 plugin",
"update_all_other": "Update {{count}} plugins",
diff --git a/frontend/src/components/DeckyState.tsx b/frontend/src/components/DeckyState.tsx
index d20c8d86..749e27ce 100644
--- a/frontend/src/components/DeckyState.tsx
+++ b/frontend/src/components/DeckyState.tsx
@@ -8,6 +8,7 @@ import { VerInfo } from '../updater';
interface PublicDeckyState {
plugins: Plugin[];
pluginOrder: string[];
+ frozenPlugins: string[];
hiddenPlugins: string[];
activePlugin: Plugin | null;
updates: PluginUpdateMapping | null;
@@ -26,6 +27,7 @@ export interface UserInfo {
export class DeckyState {
private _plugins: Plugin[] = [];
private _pluginOrder: string[] = [];
+ private _frozenPlugins: string[] = [];
private _hiddenPlugins: string[] = [];
private _activePlugin: Plugin | null = null;
private _updates: PluginUpdateMapping | null = null;
@@ -41,6 +43,7 @@ export class DeckyState {
return {
plugins: this._plugins,
pluginOrder: this._pluginOrder,
+ frozenPlugins: this._frozenPlugins,
hiddenPlugins: this._hiddenPlugins,
activePlugin: this._activePlugin,
updates: this._updates,
@@ -67,6 +70,11 @@ export class DeckyState {
this.notifyUpdate();
}
+ setFrozenPlugins(frozenPlugins: string[]) {
+ this._frozenPlugins = frozenPlugins;
+ this.notifyUpdate();
+ }
+
setHiddenPlugins(hiddenPlugins: string[]) {
this._hiddenPlugins = hiddenPlugins;
this.notifyUpdate();
diff --git a/frontend/src/components/modals/PluginUninstallModal.tsx b/frontend/src/components/modals/PluginUninstallModal.tsx
index 3fc0a71f..087b634c 100644
--- a/frontend/src/components/modals/PluginUninstallModal.tsx
+++ b/frontend/src/components/modals/PluginUninstallModal.tsx
@@ -19,6 +19,7 @@ const PluginUninstallModal: FC<PluginUninstallModalProps> = ({ name, title, butt
await uninstallPlugin(name);
// uninstalling a plugin resets the hidden setting for it server-side
// we invalidate here so if you re-install it, you won't have an out-of-date hidden filter
+ await DeckyPluginLoader.frozenPluginsService.invalidate();
await DeckyPluginLoader.hiddenPluginsService.invalidate();
}}
strTitle={title}
diff --git a/frontend/src/components/settings/index.tsx b/frontend/src/components/settings/index.tsx
index 568a0a49..80400058 100644
--- a/frontend/src/components/settings/index.tsx
+++ b/frontend/src/components/settings/index.tsx
@@ -25,7 +25,7 @@ export default function SettingsPage() {
},
{
title: t('SettingsIndex.plugins_title'),
- content: <PluginList />,
+ content: <PluginList isDeveloper={isDeveloper} />,
route: '/decky/settings/plugins',
icon: <FaPlug />,
},
diff --git a/frontend/src/components/settings/pages/plugin_list/PluginListLabel.tsx b/frontend/src/components/settings/pages/plugin_list/PluginListLabel.tsx
index a49f808f..fec03e56 100644
--- a/frontend/src/components/settings/pages/plugin_list/PluginListLabel.tsx
+++ b/frontend/src/components/settings/pages/plugin_list/PluginListLabel.tsx
@@ -1,18 +1,34 @@
import { FC } from 'react';
import { useTranslation } from 'react-i18next';
-import { FaEyeSlash } from 'react-icons/fa';
+import { FaEyeSlash, FaLock } from 'react-icons/fa';
interface PluginListLabelProps {
+ frozen: boolean;
hidden: boolean;
name: string;
version?: string;
}
-const PluginListLabel: FC<PluginListLabelProps> = ({ name, hidden, version }) => {
+const PluginListLabel: FC<PluginListLabelProps> = ({ name, frozen, hidden, version }) => {
const { t } = useTranslation();
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
- <div>{version ? `${name} - ${version}` : name}</div>
+ <div>
+ {name}
+ {version && (
+ <>
+ {' - '}
+ <span style={{ color: frozen ? '#67707b' : 'inherit' }}>
+ {frozen && (
+ <>
+ <FaLock />{' '}
+ </>
+ )}
+ {version}
+ </span>
+ </>
+ )}
+ </div>
{hidden && (
<div
style={{
diff --git a/frontend/src/components/settings/pages/plugin_list/index.tsx b/frontend/src/components/settings/pages/plugin_list/index.tsx
index 0728b12d..d8a268ae 100644
--- a/frontend/src/components/settings/pages/plugin_list/index.tsx
+++ b/frontend/src/components/settings/pages/plugin_list/index.tsx
@@ -33,7 +33,16 @@ async function reinstallPlugin(pluginName: string, currentVersion?: string) {
}
}
-type PluginTableData = PluginData & { name: string; hidden: boolean; onHide(): void; onShow(): void };
+type PluginTableData = PluginData & {
+ name: string;
+ frozen: boolean;
+ onFreeze(): void;
+ onUnfreeze(): void;
+ hidden: boolean;
+ onHide(): void;
+ onShow(): void;
+ isDeveloper: boolean;
+};
const reloadPluginBackend = DeckyBackend.callable<[pluginName: string], void>('loader/reload_plugin');
@@ -45,7 +54,7 @@ function PluginInteractables(props: { entry: ReorderableEntry<PluginTableData> }
return null;
}
- const { name, update, version, onHide, onShow, hidden } = props.entry.data;
+ const { name, update, version, onHide, onShow, hidden, onFreeze, onUnfreeze, frozen, isDeveloper } = props.entry.data;
const showCtxMenu = (e: MouseEvent | GamepadEvent) => {
showContextMenu(
@@ -80,6 +89,11 @@ function PluginInteractables(props: { entry: ReorderableEntry<PluginTableData> }
) : (
<MenuItem onSelected={onHide}>{t('PluginListIndex.hide')}</MenuItem>
)}
+ {frozen ? (
+ <MenuItem onSelected={onUnfreeze}>{t('PluginListIndex.unfreeze')}</MenuItem>
+ ) : (
+ isDeveloper && <MenuItem onSelected={onFreeze}>{t('PluginListIndex.freeze')}</MenuItem>
+ )}
</Menu>,
e.currentTarget ?? window,
);
@@ -134,8 +148,8 @@ type PluginData = {
version?: string;
};
-export default function PluginList() {
- const { plugins, updates, pluginOrder, setPluginOrder, hiddenPlugins } = useDeckyState();
+export default function PluginList({ isDeveloper }: { isDeveloper: boolean }) {
+ const { plugins, updates, pluginOrder, setPluginOrder, frozenPlugins, hiddenPlugins } = useDeckyState();
const [_, setPluginOrderSetting] = useSetting<string[]>(
'pluginOrder',
plugins.map((plugin) => plugin.name),
@@ -148,20 +162,26 @@ export default function PluginList() {
const [pluginEntries, setPluginEntries] = useState<ReorderableEntry<PluginTableData>[]>([]);
const hiddenPluginsService = DeckyPluginLoader.hiddenPluginsService;
+ const frozenPluginsService = DeckyPluginLoader.frozenPluginsService;
useEffect(() => {
setPluginEntries(
plugins.map(({ name, version }) => {
+ const frozen = frozenPlugins.includes(name);
const hidden = hiddenPlugins.includes(name);
return {
- label: <PluginListLabel name={name} hidden={hidden} version={version} />,
+ label: <PluginListLabel name={name} frozen={frozen} hidden={hidden} version={version} />,
position: pluginOrder.indexOf(name),
data: {
name,
+ frozen,
hidden,
+ isDeveloper,
version,
update: updates?.get(name),
+ onFreeze: () => frozenPluginsService.update([...frozenPlugins, name]),
+ onUnfreeze: () => frozenPluginsService.update(frozenPlugins.filter((pluginName) => name !== pluginName)),
onHide: () => hiddenPluginsService.update([...hiddenPlugins, name]),
onShow: () => hiddenPluginsService.update(hiddenPlugins.filter((pluginName) => name !== pluginName)),
},
diff --git a/frontend/src/frozen-plugins-service.tsx b/frontend/src/frozen-plugins-service.tsx
new file mode 100644
index 00000000..fa43a99e
--- /dev/null
+++ b/frontend/src/frozen-plugins-service.tsx
@@ -0,0 +1,49 @@
+import { DeckyState } from './components/DeckyState';
+import { PluginUpdateMapping } from './store';
+import { getSetting, setSetting } from './utils/settings';
+
+/**
+ * A Service class for managing the state and actions related to the frozen plugins feature.
+ *
+ * It's mostly responsible for sending setting updates to the server and keeping the local state in sync.
+ */
+export class FrozenPluginService {
+ constructor(private deckyState: DeckyState) {}
+
+ init() {
+ getSetting<string[]>('frozenPlugins', []).then((frozenPlugins) => {
+ this.deckyState.setFrozenPlugins(frozenPlugins);
+ });
+ }
+
+ /**
+ * Sends the new frozen plugins list to the server and persists it locally in the decky state
+ *
+ * @param frozenPlugins The new list of frozen plugins
+ */
+ async update(frozenPlugins: string[]) {
+ await setSetting('frozenPlugins', frozenPlugins);
+ this.deckyState.setFrozenPlugins(frozenPlugins);
+
+ // Remove pending updates for frozen plugins
+ const updates = this.deckyState.publicState().updates;
+
+ if (updates) {
+ const filteredUpdates = new Map() as PluginUpdateMapping;
+ updates.forEach((v, k) => {
+ if (!frozenPlugins.includes(k)) {
+ filteredUpdates.set(k, v);
+ }
+ });
+
+ this.deckyState.setUpdates(filteredUpdates);
+ }
+ }
+
+ /**
+ * Refreshes the state of frozen plugins in the local state
+ */
+ async invalidate() {
+ this.deckyState.setFrozenPlugins(await getSetting('frozenPlugins', []));
+ }
+}
diff --git a/frontend/src/plugin-loader.tsx b/frontend/src/plugin-loader.tsx
index 7951e944..43073385 100644
--- a/frontend/src/plugin-loader.tsx
+++ b/frontend/src/plugin-loader.tsx
@@ -22,6 +22,7 @@ import PluginUninstallModal from './components/modals/PluginUninstallModal';
import NotificationBadge from './components/NotificationBadge';
import PluginView from './components/PluginView';
import WithSuspense from './components/WithSuspense';
+import { FrozenPluginService } from './frozen-plugins-service';
import { HiddenPluginsService } from './hidden-plugins-service';
import Logger from './logger';
import { NotificationService } from './notification-service';
@@ -47,6 +48,7 @@ class PluginLoader extends Logger {
public toaster: Toaster = new Toaster();
private deckyState: DeckyState = new DeckyState();
+ public frozenPluginsService = new FrozenPluginService(this.deckyState);
public hiddenPluginsService = new HiddenPluginsService(this.deckyState);
public notificationService = new NotificationService(this.deckyState);
@@ -162,7 +164,9 @@ class PluginLoader extends Logger {
}
public async checkPluginUpdates() {
- const updates = await checkForPluginUpdates(this.plugins);
+ const frozenPlugins = this.deckyState.publicState().frozenPlugins;
+
+ const updates = await checkForPluginUpdates(this.plugins.filter((p) => !frozenPlugins.includes(p.name)));
this.deckyState.setUpdates(updates);
return updates;
}
@@ -242,6 +246,7 @@ class PluginLoader extends Logger {
this.deckyState.setPluginOrder(pluginOrder);
});
+ this.frozenPluginsService.init();
this.hiddenPluginsService.init();
this.notificationService.init();
}