mirror of
https://github.com/SteamDeckHomebrew/decky-loader.git
synced 2026-07-11 15:32:00 +00:00
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:
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user