mirror of
https://github.com/SteamDeckHomebrew/decky-loader.git
synced 2026-07-11 21:02:00 +00:00
[Feature] Freeze updates for devs (#582)
This commit is contained in:
committed by
Party Wumpus
parent
dc1697d049
commit
c2ebc78836
@@ -289,12 +289,16 @@ class PluginBrowser:
|
|||||||
Args:
|
Args:
|
||||||
name (string): The name of the plugin
|
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", [])
|
hidden_plugins = self.settings.getSetting("hiddenPlugins", [])
|
||||||
if name in hidden_plugins:
|
if name in hidden_plugins:
|
||||||
hidden_plugins.remove(name)
|
hidden_plugins.remove(name)
|
||||||
self.settings.setSetting("hiddenPlugins", hidden_plugins)
|
self.settings.setSetting("hiddenPlugins", hidden_plugins)
|
||||||
|
|
||||||
|
|
||||||
plugin_order = self.settings.getSetting("pluginOrder", [])
|
plugin_order = self.settings.getSetting("pluginOrder", [])
|
||||||
|
|
||||||
if name in plugin_order:
|
if name in plugin_order:
|
||||||
|
|||||||
@@ -99,12 +99,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"PluginListIndex": {
|
"PluginListIndex": {
|
||||||
|
"freeze": "Freeze updates",
|
||||||
"hide": "Quick access: Hide",
|
"hide": "Quick access: Hide",
|
||||||
"no_plugin": "No plugins installed!",
|
"no_plugin": "No plugins installed!",
|
||||||
"plugin_actions": "Plugin Actions",
|
"plugin_actions": "Plugin Actions",
|
||||||
"reinstall": "Reinstall",
|
"reinstall": "Reinstall",
|
||||||
"reload": "Reload",
|
"reload": "Reload",
|
||||||
"show": "Quick access: Show",
|
"show": "Quick access: Show",
|
||||||
|
"unfreeze": "Allow updates",
|
||||||
"uninstall": "Uninstall",
|
"uninstall": "Uninstall",
|
||||||
"update_all_one": "Update 1 plugin",
|
"update_all_one": "Update 1 plugin",
|
||||||
"update_all_other": "Update {{count}} plugins",
|
"update_all_other": "Update {{count}} plugins",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { VerInfo } from '../updater';
|
|||||||
interface PublicDeckyState {
|
interface PublicDeckyState {
|
||||||
plugins: Plugin[];
|
plugins: Plugin[];
|
||||||
pluginOrder: string[];
|
pluginOrder: string[];
|
||||||
|
frozenPlugins: string[];
|
||||||
hiddenPlugins: string[];
|
hiddenPlugins: string[];
|
||||||
activePlugin: Plugin | null;
|
activePlugin: Plugin | null;
|
||||||
updates: PluginUpdateMapping | null;
|
updates: PluginUpdateMapping | null;
|
||||||
@@ -26,6 +27,7 @@ export interface UserInfo {
|
|||||||
export class DeckyState {
|
export class DeckyState {
|
||||||
private _plugins: Plugin[] = [];
|
private _plugins: Plugin[] = [];
|
||||||
private _pluginOrder: string[] = [];
|
private _pluginOrder: string[] = [];
|
||||||
|
private _frozenPlugins: string[] = [];
|
||||||
private _hiddenPlugins: string[] = [];
|
private _hiddenPlugins: string[] = [];
|
||||||
private _activePlugin: Plugin | null = null;
|
private _activePlugin: Plugin | null = null;
|
||||||
private _updates: PluginUpdateMapping | null = null;
|
private _updates: PluginUpdateMapping | null = null;
|
||||||
@@ -41,6 +43,7 @@ export class DeckyState {
|
|||||||
return {
|
return {
|
||||||
plugins: this._plugins,
|
plugins: this._plugins,
|
||||||
pluginOrder: this._pluginOrder,
|
pluginOrder: this._pluginOrder,
|
||||||
|
frozenPlugins: this._frozenPlugins,
|
||||||
hiddenPlugins: this._hiddenPlugins,
|
hiddenPlugins: this._hiddenPlugins,
|
||||||
activePlugin: this._activePlugin,
|
activePlugin: this._activePlugin,
|
||||||
updates: this._updates,
|
updates: this._updates,
|
||||||
@@ -67,6 +70,11 @@ export class DeckyState {
|
|||||||
this.notifyUpdate();
|
this.notifyUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setFrozenPlugins(frozenPlugins: string[]) {
|
||||||
|
this._frozenPlugins = frozenPlugins;
|
||||||
|
this.notifyUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
setHiddenPlugins(hiddenPlugins: string[]) {
|
setHiddenPlugins(hiddenPlugins: string[]) {
|
||||||
this._hiddenPlugins = hiddenPlugins;
|
this._hiddenPlugins = hiddenPlugins;
|
||||||
this.notifyUpdate();
|
this.notifyUpdate();
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ const PluginUninstallModal: FC<PluginUninstallModalProps> = ({ name, title, butt
|
|||||||
await uninstallPlugin(name);
|
await uninstallPlugin(name);
|
||||||
// uninstalling a plugin resets the hidden setting for it server-side
|
// 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
|
// 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();
|
await DeckyPluginLoader.hiddenPluginsService.invalidate();
|
||||||
}}
|
}}
|
||||||
strTitle={title}
|
strTitle={title}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export default function SettingsPage() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('SettingsIndex.plugins_title'),
|
title: t('SettingsIndex.plugins_title'),
|
||||||
content: <PluginList />,
|
content: <PluginList isDeveloper={isDeveloper} />,
|
||||||
route: '/decky/settings/plugins',
|
route: '/decky/settings/plugins',
|
||||||
icon: <FaPlug />,
|
icon: <FaPlug />,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,18 +1,34 @@
|
|||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaEyeSlash } from 'react-icons/fa';
|
import { FaEyeSlash, FaLock } from 'react-icons/fa';
|
||||||
|
|
||||||
interface PluginListLabelProps {
|
interface PluginListLabelProps {
|
||||||
|
frozen: boolean;
|
||||||
hidden: boolean;
|
hidden: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
version?: string;
|
version?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PluginListLabel: FC<PluginListLabelProps> = ({ name, hidden, version }) => {
|
const PluginListLabel: FC<PluginListLabelProps> = ({ name, frozen, hidden, version }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
|
<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 && (
|
{hidden && (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -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');
|
const reloadPluginBackend = DeckyBackend.callable<[pluginName: string], void>('loader/reload_plugin');
|
||||||
|
|
||||||
@@ -45,7 +54,7 @@ function PluginInteractables(props: { entry: ReorderableEntry<PluginTableData> }
|
|||||||
return null;
|
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) => {
|
const showCtxMenu = (e: MouseEvent | GamepadEvent) => {
|
||||||
showContextMenu(
|
showContextMenu(
|
||||||
@@ -80,6 +89,11 @@ function PluginInteractables(props: { entry: ReorderableEntry<PluginTableData> }
|
|||||||
) : (
|
) : (
|
||||||
<MenuItem onSelected={onHide}>{t('PluginListIndex.hide')}</MenuItem>
|
<MenuItem onSelected={onHide}>{t('PluginListIndex.hide')}</MenuItem>
|
||||||
)}
|
)}
|
||||||
|
{frozen ? (
|
||||||
|
<MenuItem onSelected={onUnfreeze}>{t('PluginListIndex.unfreeze')}</MenuItem>
|
||||||
|
) : (
|
||||||
|
isDeveloper && <MenuItem onSelected={onFreeze}>{t('PluginListIndex.freeze')}</MenuItem>
|
||||||
|
)}
|
||||||
</Menu>,
|
</Menu>,
|
||||||
e.currentTarget ?? window,
|
e.currentTarget ?? window,
|
||||||
);
|
);
|
||||||
@@ -134,8 +148,8 @@ type PluginData = {
|
|||||||
version?: string;
|
version?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function PluginList() {
|
export default function PluginList({ isDeveloper }: { isDeveloper: boolean }) {
|
||||||
const { plugins, updates, pluginOrder, setPluginOrder, hiddenPlugins } = useDeckyState();
|
const { plugins, updates, pluginOrder, setPluginOrder, frozenPlugins, hiddenPlugins } = useDeckyState();
|
||||||
const [_, setPluginOrderSetting] = useSetting<string[]>(
|
const [_, setPluginOrderSetting] = useSetting<string[]>(
|
||||||
'pluginOrder',
|
'pluginOrder',
|
||||||
plugins.map((plugin) => plugin.name),
|
plugins.map((plugin) => plugin.name),
|
||||||
@@ -148,20 +162,26 @@ export default function PluginList() {
|
|||||||
|
|
||||||
const [pluginEntries, setPluginEntries] = useState<ReorderableEntry<PluginTableData>[]>([]);
|
const [pluginEntries, setPluginEntries] = useState<ReorderableEntry<PluginTableData>[]>([]);
|
||||||
const hiddenPluginsService = DeckyPluginLoader.hiddenPluginsService;
|
const hiddenPluginsService = DeckyPluginLoader.hiddenPluginsService;
|
||||||
|
const frozenPluginsService = DeckyPluginLoader.frozenPluginsService;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setPluginEntries(
|
setPluginEntries(
|
||||||
plugins.map(({ name, version }) => {
|
plugins.map(({ name, version }) => {
|
||||||
|
const frozen = frozenPlugins.includes(name);
|
||||||
const hidden = hiddenPlugins.includes(name);
|
const hidden = hiddenPlugins.includes(name);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
label: <PluginListLabel name={name} hidden={hidden} version={version} />,
|
label: <PluginListLabel name={name} frozen={frozen} hidden={hidden} version={version} />,
|
||||||
position: pluginOrder.indexOf(name),
|
position: pluginOrder.indexOf(name),
|
||||||
data: {
|
data: {
|
||||||
name,
|
name,
|
||||||
|
frozen,
|
||||||
hidden,
|
hidden,
|
||||||
|
isDeveloper,
|
||||||
version,
|
version,
|
||||||
update: updates?.get(name),
|
update: updates?.get(name),
|
||||||
|
onFreeze: () => frozenPluginsService.update([...frozenPlugins, name]),
|
||||||
|
onUnfreeze: () => frozenPluginsService.update(frozenPlugins.filter((pluginName) => name !== pluginName)),
|
||||||
onHide: () => hiddenPluginsService.update([...hiddenPlugins, name]),
|
onHide: () => hiddenPluginsService.update([...hiddenPlugins, name]),
|
||||||
onShow: () => hiddenPluginsService.update(hiddenPlugins.filter((pluginName) => name !== pluginName)),
|
onShow: () => hiddenPluginsService.update(hiddenPlugins.filter((pluginName) => name !== pluginName)),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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', []));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ import PluginUninstallModal from './components/modals/PluginUninstallModal';
|
|||||||
import NotificationBadge from './components/NotificationBadge';
|
import NotificationBadge from './components/NotificationBadge';
|
||||||
import PluginView from './components/PluginView';
|
import PluginView from './components/PluginView';
|
||||||
import WithSuspense from './components/WithSuspense';
|
import WithSuspense from './components/WithSuspense';
|
||||||
|
import { FrozenPluginService } from './frozen-plugins-service';
|
||||||
import { HiddenPluginsService } from './hidden-plugins-service';
|
import { HiddenPluginsService } from './hidden-plugins-service';
|
||||||
import Logger from './logger';
|
import Logger from './logger';
|
||||||
import { NotificationService } from './notification-service';
|
import { NotificationService } from './notification-service';
|
||||||
@@ -47,6 +48,7 @@ class PluginLoader extends Logger {
|
|||||||
public toaster: Toaster = new Toaster();
|
public toaster: Toaster = new Toaster();
|
||||||
private deckyState: DeckyState = new DeckyState();
|
private deckyState: DeckyState = new DeckyState();
|
||||||
|
|
||||||
|
public frozenPluginsService = new FrozenPluginService(this.deckyState);
|
||||||
public hiddenPluginsService = new HiddenPluginsService(this.deckyState);
|
public hiddenPluginsService = new HiddenPluginsService(this.deckyState);
|
||||||
public notificationService = new NotificationService(this.deckyState);
|
public notificationService = new NotificationService(this.deckyState);
|
||||||
|
|
||||||
@@ -162,7 +164,9 @@ class PluginLoader extends Logger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async checkPluginUpdates() {
|
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);
|
this.deckyState.setUpdates(updates);
|
||||||
return updates;
|
return updates;
|
||||||
}
|
}
|
||||||
@@ -242,6 +246,7 @@ class PluginLoader extends Logger {
|
|||||||
this.deckyState.setPluginOrder(pluginOrder);
|
this.deckyState.setPluginOrder(pluginOrder);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.frozenPluginsService.init();
|
||||||
this.hiddenPluginsService.init();
|
this.hiddenPluginsService.init();
|
||||||
this.notificationService.init();
|
this.notificationService.init();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user