forked from OctoWoW/OctoLauncher
132 lines
3.6 KiB
TypeScript
132 lines
3.6 KiB
TypeScript
import { useState } from 'react';
|
|
import { Plus, RefreshCw, Search } from 'lucide-react';
|
|
|
|
import { type AddonData, type AddonsStatus } from '~main/types';
|
|
import { api } from '~renderer/utils/api';
|
|
import TextButton from '~renderer/components/styled/TextButton';
|
|
import useScrollHint from '~renderer/utils/useScrollHint';
|
|
|
|
import DialogButton from '../styled/DialogButton';
|
|
import IconSpinner from '../styled/IconSpinner';
|
|
|
|
import AddonList from './addons/AddonList';
|
|
import { type Dependencies } from './addons/AddonListItem';
|
|
import CustomAddonDialog from './addons/CustomAddonDialog';
|
|
|
|
const localeFilter = (l: AddonData[], filter: string) => {
|
|
const seen = new Set<string>();
|
|
const deduped = l.filter(a => {
|
|
if (seen.has(a.folder)) return false;
|
|
seen.add(a.folder);
|
|
return true;
|
|
});
|
|
return deduped
|
|
.filter(
|
|
a =>
|
|
a.folder.toLocaleLowerCase().indexOf(filter.toLocaleLowerCase()) !== -1
|
|
)
|
|
.sort((a, b) => a.folder.localeCompare(b.folder));
|
|
};
|
|
|
|
const AddonsTab = () => {
|
|
const [data, setData] = useState<AddonsStatus>({
|
|
state: 'verifying',
|
|
addons: {},
|
|
available: []
|
|
});
|
|
api.addons.observe.useSubscription(undefined, { onData: setData });
|
|
|
|
const isUpdateAvailable = Object.values(data.addons).some(
|
|
a => a.status === 'outOfDate' || a.status === 'downloading'
|
|
);
|
|
const dependencies: Dependencies = Object.fromEntries([
|
|
...data.available.map(a => [a.folder, 'available']),
|
|
...Object.values(data.addons).map(a => [
|
|
a.folder,
|
|
a.progress ?? (a.status === 'upToDate' ? 'installed' : 'available')
|
|
])
|
|
]);
|
|
|
|
const [filter, setFilter] = useState('');
|
|
|
|
const verify = api.addons.verify.useMutation();
|
|
const update = api.addons.update.useMutation();
|
|
|
|
const scrollRef = useScrollHint<HTMLDivElement>();
|
|
|
|
return (
|
|
<div className="tw-surface relative flex min-h-0 flex-grow flex-col gap-3">
|
|
<div
|
|
ref={scrollRef}
|
|
className="relative -m-4 -mb-3 flex flex-grow flex-col gap-3 overflow-y-auto overflow-x-hidden p-4 pb-3"
|
|
>
|
|
<AddonList
|
|
title="Installed"
|
|
addons={localeFilter(Object.values(data.addons), filter)}
|
|
dependencies={dependencies}
|
|
/>
|
|
<AddonList
|
|
title="Available"
|
|
addons={localeFilter(
|
|
data.available.filter(a => !(a.folder in data.addons)),
|
|
filter
|
|
)}
|
|
dependencies={dependencies}
|
|
/>
|
|
</div>
|
|
<hr />
|
|
<div className="-mb-4 -mt-3 grid grid-cols-[1fr_1fr_1fr] items-center justify-between gap-2 py-2">
|
|
<TextButton
|
|
onClick={() => verify.mutateAsync()}
|
|
className="-ml-2 text-blueGray"
|
|
icon={RefreshCw}
|
|
size={18}
|
|
loading={data.state !== 'done'}
|
|
>
|
|
Check for updates
|
|
</TextButton>
|
|
<DialogButton
|
|
clickAway
|
|
dialog={close => <CustomAddonDialog close={close} />}
|
|
>
|
|
{open => (
|
|
<TextButton
|
|
icon={Plus}
|
|
size={18}
|
|
onClick={open}
|
|
className="s1 text-pink"
|
|
>
|
|
Add custom git addon
|
|
</TextButton>
|
|
)}
|
|
</DialogButton>
|
|
{data.state === 'verifying' ? (
|
|
<IconSpinner size={18} className="justify-self-end" />
|
|
) : isUpdateAvailable ? (
|
|
<TextButton
|
|
onClick={() => update.mutateAsync({})}
|
|
className="justify-self-end text-warmGreen"
|
|
>
|
|
Update all
|
|
</TextButton>
|
|
) : (
|
|
<p className="s1 justify-self-end text-blueGray">
|
|
Everything is up to date.
|
|
</p>
|
|
)}
|
|
</div>
|
|
<div className="absolute right-3 top-3">
|
|
<div className="flex items-center gap-1 border-b border-blueGray bg-darkGray/70 p-1 hocus:border-orange">
|
|
<input
|
|
className="cursor-text bg-inherit"
|
|
value={filter}
|
|
onChange={e => setFilter(e.target.value)}
|
|
/>
|
|
<Search size={18} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
export default AddonsTab;
|