summaryrefslogtreecommitdiff
path: root/frontend/src
diff options
context:
space:
mode:
authorTravis Lane <63308171+Tormak9970@users.noreply.github.com>2023-04-03 17:21:31 -0400
committerGitHub <noreply@github.com>2023-04-03 14:21:31 -0700
commit0f36e87ccea0d9bf2a3db8ee858f27d9d1b2d796 (patch)
treee3018e7096401d387923f388637c5a45f6fa6185 /frontend/src
parentfd325ef1cc1d3e78b5e7686819e05606cc79d963 (diff)
downloaddecky-loader-0f36e87ccea0d9bf2a3db8ee858f27d9d1b2d796.tar.gz
decky-loader-0f36e87ccea0d9bf2a3db8ee858f27d9d1b2d796.zip
Add plugin reordering (#378)
* feat: started work on saving plugin order * feat: implemented local ReorderableList * feat: reoder complete except for usage of DFL * switched to using dfl reorderableList * fix: added missing file and removed frag * updated to newest dfl * Update defsettings.json * fix: plugin order was missing on init * fix: now await pluginOrder * fix: moved the plugin-order load to plugin-loader * chore: v6 and dfl bump
Diffstat (limited to 'frontend/src')
-rw-r--r--frontend/src/components/DeckyState.tsx19
-rw-r--r--frontend/src/components/PluginView.tsx16
-rw-r--r--frontend/src/components/settings/pages/plugin_list/index.tsx128
-rw-r--r--frontend/src/index.tsx1
-rw-r--r--frontend/src/plugin-loader.tsx6
5 files changed, 119 insertions, 51 deletions
diff --git a/frontend/src/components/DeckyState.tsx b/frontend/src/components/DeckyState.tsx
index 283fb118..67d4b78e 100644
--- a/frontend/src/components/DeckyState.tsx
+++ b/frontend/src/components/DeckyState.tsx
@@ -6,6 +6,7 @@ import { VerInfo } from '../updater';
interface PublicDeckyState {
plugins: Plugin[];
+ pluginOrder: string[];
activePlugin: Plugin | null;
updates: PluginUpdateMapping | null;
hasLoaderUpdate?: boolean;
@@ -15,6 +16,7 @@ interface PublicDeckyState {
export class DeckyState {
private _plugins: Plugin[] = [];
+ private _pluginOrder: string[] = [];
private _activePlugin: Plugin | null = null;
private _updates: PluginUpdateMapping | null = null;
private _hasLoaderUpdate: boolean = false;
@@ -26,6 +28,7 @@ export class DeckyState {
publicState(): PublicDeckyState {
return {
plugins: this._plugins,
+ pluginOrder: this._pluginOrder,
activePlugin: this._activePlugin,
updates: this._updates,
hasLoaderUpdate: this._hasLoaderUpdate,
@@ -44,6 +47,11 @@ export class DeckyState {
this.notifyUpdate();
}
+ setPluginOrder(pluginOrder: string[]) {
+ this._pluginOrder = pluginOrder;
+ this.notifyUpdate();
+ }
+
setActivePlugin(name: string) {
this._activePlugin = this._plugins.find((plugin) => plugin.name === name) ?? null;
this.notifyUpdate();
@@ -78,6 +86,7 @@ interface DeckyStateContext extends PublicDeckyState {
setVersionInfo(versionInfo: VerInfo): void;
setIsLoaderUpdating(hasUpdate: boolean): void;
setActivePlugin(name: string): void;
+ setPluginOrder(pluginOrder: string[]): void;
closeActivePlugin(): void;
}
@@ -106,10 +115,18 @@ export const DeckyStateContextProvider: FC<Props> = ({ children, deckyState }) =
const setVersionInfo = (versionInfo: VerInfo) => deckyState.setVersionInfo(versionInfo);
const setActivePlugin = (name: string) => deckyState.setActivePlugin(name);
const closeActivePlugin = () => deckyState.closeActivePlugin();
+ const setPluginOrder = (pluginOrder: string[]) => deckyState.setPluginOrder(pluginOrder);
return (
<DeckyStateContext.Provider
- value={{ ...publicDeckyState, setIsLoaderUpdating, setVersionInfo, setActivePlugin, closeActivePlugin }}
+ value={{
+ ...publicDeckyState,
+ setIsLoaderUpdating,
+ setVersionInfo,
+ setActivePlugin,
+ closeActivePlugin,
+ setPluginOrder,
+ }}
>
{children}
</DeckyStateContext.Provider>
diff --git a/frontend/src/components/PluginView.tsx b/frontend/src/components/PluginView.tsx
index 630cc962..3ecc2c86 100644
--- a/frontend/src/components/PluginView.tsx
+++ b/frontend/src/components/PluginView.tsx
@@ -7,17 +7,27 @@ import {
scrollClasses,
staticClasses,
} from 'decky-frontend-lib';
-import { VFC } from 'react';
+import { VFC, useEffect, useState } from 'react';
+import { Plugin } from '../plugin';
import { useDeckyState } from './DeckyState';
import NotificationBadge from './NotificationBadge';
import { useQuickAccessVisible } from './QuickAccessVisibleState';
import TitleView from './TitleView';
const PluginView: VFC = () => {
- const { plugins, updates, activePlugin, setActivePlugin, closeActivePlugin } = useDeckyState();
+ const { plugins, updates, activePlugin, pluginOrder, setActivePlugin, closeActivePlugin } = useDeckyState();
const visible = useQuickAccessVisible();
+ const [pluginList, setPluginList] = useState<Plugin[]>(
+ plugins.sort((a, b) => pluginOrder.indexOf(a.name) - pluginOrder.indexOf(b.name)),
+ );
+
+ useEffect(() => {
+ setPluginList(plugins.sort((a, b) => pluginOrder.indexOf(a.name) - pluginOrder.indexOf(b.name)));
+ console.log('updating PluginView after changes');
+ }, [plugins, pluginOrder]);
+
if (activePlugin) {
return (
<Focusable onCancelButton={closeActivePlugin}>
@@ -36,7 +46,7 @@ const PluginView: VFC = () => {
<TitleView />
<div className={joinClassNames(staticClasses.TabGroupPanel, scrollClasses.ScrollPanel, scrollClasses.ScrollY)}>
<PanelSection>
- {plugins
+ {pluginList
.filter((p) => p.content)
.map(({ name, icon }) => (
<PanelSectionRow key={name}>
diff --git a/frontend/src/components/settings/pages/plugin_list/index.tsx b/frontend/src/components/settings/pages/plugin_list/index.tsx
index 48894031..d9a85e9f 100644
--- a/frontend/src/components/settings/pages/plugin_list/index.tsx
+++ b/frontend/src/components/settings/pages/plugin_list/index.tsx
@@ -2,24 +2,93 @@ import {
DialogBody,
DialogButton,
DialogControlsSection,
- Focusable,
+ GamepadEvent,
Menu,
MenuItem,
+ ReorderableEntry,
+ ReorderableList,
showContextMenu,
} from 'decky-frontend-lib';
-import { useEffect } from 'react';
+import { useEffect, useState } from 'react';
import { FaDownload, FaEllipsisH } from 'react-icons/fa';
-import { requestPluginInstall } from '../../../../store';
+import { StorePluginVersion, requestPluginInstall } from '../../../../store';
+import { useSetting } from '../../../../utils/hooks/useSetting';
import { useDeckyState } from '../../../DeckyState';
+function PluginInteractables(props: { entry: ReorderableEntry<PluginData> }) {
+ const data = props.entry.data;
+
+ const showCtxMenu = (e: MouseEvent | GamepadEvent) => {
+ showContextMenu(
+ <Menu label="Plugin Actions">
+ <MenuItem onSelected={() => window.DeckyPluginLoader.importPlugin(props.entry.label, data?.version)}>
+ Reload
+ </MenuItem>
+ <MenuItem onSelected={() => window.DeckyPluginLoader.uninstallPlugin(props.entry.label)}>Uninstall</MenuItem>
+ </Menu>,
+ e.currentTarget ?? window,
+ );
+ };
+
+ return (
+ <>
+ {data?.update && (
+ <DialogButton
+ style={{ height: '40px', minWidth: '60px', marginRight: '10px' }}
+ onClick={() => requestPluginInstall(props.entry.label, data?.update as StorePluginVersion)}
+ onOKButton={() => requestPluginInstall(props.entry.label, data?.update as StorePluginVersion)}
+ >
+ <div style={{ display: 'flex', flexDirection: 'row' }}>
+ Update to {data?.update?.name}
+ <FaDownload style={{ paddingLeft: '2rem' }} />
+ </div>
+ </DialogButton>
+ )}
+ <DialogButton
+ style={{ height: '40px', width: '40px', padding: '10px 12px', minWidth: '40px' }}
+ onClick={showCtxMenu}
+ onOKButton={showCtxMenu}
+ >
+ <FaEllipsisH />
+ </DialogButton>
+ </>
+ );
+}
+
+type PluginData = {
+ update?: StorePluginVersion;
+ version?: string;
+};
+
export default function PluginList() {
- const { plugins, updates } = useDeckyState();
+ const { plugins, updates, pluginOrder, setPluginOrder } = useDeckyState();
+ const [_, setPluginOrderSetting] = useSetting<string[]>(
+ 'pluginOrder',
+ plugins.map((plugin) => plugin.name),
+ );
useEffect(() => {
window.DeckyPluginLoader.checkPluginUpdates();
}, []);
+ const [pluginEntries, setPluginEntries] = useState<ReorderableEntry<PluginData>[]>([]);
+
+ useEffect(() => {
+ setPluginEntries(
+ plugins.map((plugin) => {
+ return {
+ label: plugin.name,
+ data: {
+ update: updates?.get(plugin.name),
+ version: plugin.version,
+ },
+ position: pluginOrder.indexOf(plugin.name),
+ };
+ }),
+ );
+ }, [plugins, updates]);
+
if (plugins.length === 0) {
return (
<div>
@@ -28,52 +97,17 @@ export default function PluginList() {
);
}
+ function onSave(entries: ReorderableEntry<PluginData>[]) {
+ const newOrder = entries.map((entry) => entry.label);
+ console.log(newOrder);
+ setPluginOrder(newOrder);
+ setPluginOrderSetting(newOrder);
+ }
+
return (
<DialogBody>
<DialogControlsSection>
- <ul style={{ listStyleType: 'none', padding: '0' }}>
- {plugins.map(({ name, version }) => {
- const update = updates?.get(name);
- return (
- <li style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', paddingBottom: '10px' }}>
- <span>
- {name} <span style={{ opacity: '50%' }}>{'(' + version + ')'}</span>
- </span>
- <Focusable style={{ marginLeft: 'auto', boxShadow: 'none', display: 'flex', justifyContent: 'right' }}>
- {update && (
- <DialogButton
- style={{ height: '40px', minWidth: '60px', marginRight: '10px' }}
- onClick={() => requestPluginInstall(name, update)}
- >
- <div style={{ display: 'flex', flexDirection: 'row' }}>
- Update to {update.name}
- <FaDownload style={{ paddingLeft: '2rem' }} />
- </div>
- </DialogButton>
- )}
- <DialogButton
- style={{ height: '40px', width: '40px', padding: '10px 12px', minWidth: '40px' }}
- onClick={(e: MouseEvent) =>
- showContextMenu(
- <Menu label="Plugin Actions">
- <MenuItem onSelected={() => window.DeckyPluginLoader.importPlugin(name, version)}>
- Reload
- </MenuItem>
- <MenuItem onSelected={() => window.DeckyPluginLoader.uninstallPlugin(name)}>
- Uninstall
- </MenuItem>
- </Menu>,
- e.currentTarget ?? window,
- )
- }
- >
- <FaEllipsisH />
- </DialogButton>
- </Focusable>
- </li>
- );
- })}
- </ul>
+ <ReorderableList<PluginData> entries={pluginEntries} onSave={onSave} interactables={PluginInteractables} />
</DialogControlsSection>
</DialogBody>
);
diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx
index f8e1df31..eafa9616 100644
--- a/frontend/src/index.tsx
+++ b/frontend/src/index.tsx
@@ -56,6 +56,7 @@ declare global {
if (!window.DeckyPluginLoader.hasPlugin(plugin.name))
window.DeckyPluginLoader?.importPlugin(plugin.name, plugin.version);
}
+
window.DeckyPluginLoader.checkPluginUpdates();
};
diff --git a/frontend/src/plugin-loader.tsx b/frontend/src/plugin-loader.tsx
index 5b640758..a3381de7 100644
--- a/frontend/src/plugin-loader.tsx
+++ b/frontend/src/plugin-loader.tsx
@@ -169,6 +169,12 @@ class PluginLoader extends Logger {
getSetting('developer.enabled', false).then((val) => {
if (val) import('./developer').then((developer) => developer.startup());
});
+
+ //* Grab and set plugin order
+ getSetting<string[]>('pluginOrder', []).then((pluginOrder) => {
+ console.log(pluginOrder);
+ this.deckyState.setPluginOrder(pluginOrder);
+ });
}
public deinit() {