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
This commit is contained in:
Travis Lane
2023-04-03 17:21:31 -04:00
committed by GitHub
parent fd325ef1cc
commit 0f36e87cce
10 changed files with 920 additions and 803 deletions
+1 -1
View File
@@ -4,4 +4,4 @@
"deckpass" : "ssap",
"deckkey" : "-i ${env:HOME}/.ssh/id_rsa",
"deckdir" : "/home/deck"
}
}
+11 -1
View File
@@ -30,10 +30,11 @@ class PluginInstallContext:
self.hash = hash
class PluginBrowser:
def __init__(self, plugin_path, plugins, loader) -> None:
def __init__(self, plugin_path, plugins, loader, settings) -> None:
self.plugin_path = plugin_path
self.plugins = plugins
self.loader = loader
self.settings = settings
self.install_requests = {}
def _unzip_to_plugin_dir(self, zip, name, hash):
@@ -123,6 +124,10 @@ class PluginBrowser:
logger.debug("Plugin %s was stopped", name)
del self.plugins[name]
logger.debug("Plugin %s was removed from the dictionary", name)
current_plugin_order = self.settings.getSetting("pluginOrder")
current_plugin_order.remove(name)
self.settings.setSetting("pluginOrder", current_plugin_order)
logger.debug("Plugin %s was removed from the pluginOrder setting", name)
logger.debug("removing files %s" % str(name))
rmtree(plugin_dir)
except FileNotFoundError:
@@ -170,6 +175,11 @@ class PluginBrowser:
self.loader.plugins[name].stop()
self.loader.plugins.pop(name, None)
await sleep(1)
current_plugin_order = self.settings.getSetting("pluginOrder")
current_plugin_order.append(name)
self.settings.setSetting("pluginOrder", current_plugin_order)
logger.debug("Plugin %s was added to the pluginOrder setting", name)
self.loader.import_plugin(path.join(plugin_dir, "main.py"), plugin_folder)
else:
logger.fatal(f"Failed Downloading Remote Binaries")
+4 -1
View File
@@ -71,8 +71,8 @@ class PluginManager:
)
})
self.plugin_loader = Loader(self.web_app, CONFIG["plugin_path"], self.loop, CONFIG["live_reload"])
self.plugin_browser = PluginBrowser(CONFIG["plugin_path"], self.plugin_loader.plugins, self.plugin_loader)
self.settings = SettingsManager("loader", path.join(HOMEBREW_PATH, "settings"))
self.plugin_browser = PluginBrowser(CONFIG["plugin_path"], self.plugin_loader.plugins, self.plugin_loader, self.settings)
self.utilities = Utilities(self)
self.updater = Updater(self)
@@ -109,6 +109,9 @@ class PluginManager:
logger.debug("Loading plugins")
self.plugin_loader.import_plugins()
# await inject_to_tab("SP", "window.syncDeckyPlugins();")
if self.settings.getSetting("pluginOrder", None) == None:
self.settings.setSetting("pluginOrder", list(self.plugin_loader.plugins.keys()))
logger.debug("Did not find pluginOrder setting, set it to default")
async def loader_reinjector(self):
while True:
+13 -13
View File
@@ -12,28 +12,28 @@
},
"devDependencies": {
"@rollup/plugin-commonjs": "^21.1.0",
"@rollup/plugin-image": "^3.0.1",
"@rollup/plugin-image": "^3.0.2",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^13.3.0",
"@rollup/plugin-replace": "^4.0.0",
"@rollup/plugin-typescript": "^8.3.3",
"@rollup/plugin-typescript": "^8.5.0",
"@types/react": "16.14.0",
"@types/react-file-icon": "^1.0.1",
"@types/react-router": "5.1.18",
"@types/webpack": "^5.28.0",
"husky": "^8.0.1",
"@types/webpack": "^5.28.1",
"husky": "^8.0.3",
"import-sort-style-module": "^6.0.0",
"inquirer": "^8.2.4",
"prettier": "^2.7.1",
"inquirer": "^8.2.5",
"prettier": "^2.8.7",
"prettier-plugin-import-sort": "^0.0.7",
"react": "16.14.0",
"react-dom": "16.14.0",
"rollup": "^2.76.0",
"rollup": "^2.79.1",
"rollup-plugin-delete": "^2.0.0",
"rollup-plugin-external-globals": "^0.6.1",
"rollup-plugin-polyfill-node": "^0.10.2",
"tslib": "^2.4.0",
"typescript": "^4.7.4"
"tslib": "^2.5.0",
"typescript": "^4.9.5"
},
"importSort": {
".js, .jsx, .ts, .tsx": {
@@ -42,10 +42,10 @@
}
},
"dependencies": {
"decky-frontend-lib": "^3.19.1",
"react-file-icon": "^1.2.0",
"react-icons": "^4.4.0",
"react-markdown": "^8.0.3",
"decky-frontend-lib": "^3.20.0",
"react-file-icon": "^1.3.0",
"react-icons": "^4.8.0",
"react-markdown": "^8.0.6",
"remark-gfm": "^3.0.1"
}
}
+772 -736
View File
File diff suppressed because it is too large Load Diff
+18 -1
View File
@@ -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>
+13 -3
View File
@@ -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}>
@@ -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>
);
+1
View File
@@ -56,6 +56,7 @@ declare global {
if (!window.DeckyPluginLoader.hasPlugin(plugin.name))
window.DeckyPluginLoader?.importPlugin(plugin.name, plugin.version);
}
window.DeckyPluginLoader.checkPluginUpdates();
};
+6
View File
@@ -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() {