diff options
| author | Travis Lane <63308171+Tormak9970@users.noreply.github.com> | 2023-04-03 17:21:31 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-04-03 14:21:31 -0700 |
| commit | 0f36e87ccea0d9bf2a3db8ee858f27d9d1b2d796 (patch) | |
| tree | e3018e7096401d387923f388637c5a45f6fa6185 /frontend/src | |
| parent | fd325ef1cc1d3e78b5e7686819e05606cc79d963 (diff) | |
| download | decky-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.tsx | 19 | ||||
| -rw-r--r-- | frontend/src/components/PluginView.tsx | 16 | ||||
| -rw-r--r-- | frontend/src/components/settings/pages/plugin_list/index.tsx | 128 | ||||
| -rw-r--r-- | frontend/src/index.tsx | 1 | ||||
| -rw-r--r-- | frontend/src/plugin-loader.tsx | 6 |
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() { |
