2023-05-19 23:14:55 +02:00
|
|
|
import React from "@modules/react";
|
|
|
|
import Strings from "@modules/strings";
|
|
|
|
import Events from "@modules/emitter";
|
|
|
|
import DataStore from "@modules/datastore";
|
|
|
|
import DiscordModules from "@modules/discordmodules";
|
2024-02-22 01:05:28 +01:00
|
|
|
import ipc from "@modules/ipc";
|
2019-06-10 00:37:46 +02:00
|
|
|
|
|
|
|
import SettingsTitle from "./title";
|
2019-06-28 01:50:20 +02:00
|
|
|
import AddonCard from "./addoncard";
|
2019-06-30 05:09:48 +02:00
|
|
|
import Dropdown from "./components/dropdown";
|
2019-06-29 06:47:56 +02:00
|
|
|
import Search from "./components/search";
|
|
|
|
|
2023-05-20 00:37:21 +02:00
|
|
|
import Modals from "@ui/modals";
|
|
|
|
import ErrorBoundary from "@ui/errorboundary";
|
|
|
|
|
|
|
|
import ListIcon from "@ui/icons/list";
|
|
|
|
import GridIcon from "@ui/icons/grid";
|
2024-02-22 06:06:30 +01:00
|
|
|
import FolderIcon from "@ui/icons/folder";
|
|
|
|
import CheckIcon from "@ui/icons/check";
|
|
|
|
import CloseIcon from "@ui/icons/close";
|
2023-05-20 00:37:21 +02:00
|
|
|
|
|
|
|
import NoResults from "@ui/blankslates/noresults";
|
|
|
|
import EmptyImage from "@ui/blankslates/emptyimage";
|
2020-11-04 01:45:36 +01:00
|
|
|
|
2023-03-09 02:08:48 +01:00
|
|
|
const {useState, useCallback, useEffect, useReducer, useMemo} = React;
|
|
|
|
|
2023-05-20 00:37:21 +02:00
|
|
|
|
2023-08-29 06:40:35 +02:00
|
|
|
const buildSortOptions = () => [
|
2023-03-09 02:08:48 +01:00
|
|
|
{label: Strings.Addons.name, value: "name"},
|
|
|
|
{label: Strings.Addons.author, value: "author"},
|
|
|
|
{label: Strings.Addons.version, value: "version"},
|
|
|
|
{label: Strings.Addons.added, value: "added"},
|
|
|
|
{label: Strings.Addons.modified, value: "modified"},
|
|
|
|
{label: Strings.Addons.isEnabled, value: "isEnabled"}
|
|
|
|
];
|
|
|
|
|
2023-08-29 06:40:35 +02:00
|
|
|
const buildDirectionOptions = () => [
|
2023-03-09 02:08:48 +01:00
|
|
|
{label: Strings.Sorting.ascending, value: true},
|
|
|
|
{label: Strings.Sorting.descending, value: false}
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
function openFolder(folder) {
|
2024-02-22 01:05:28 +01:00
|
|
|
ipc.openPath(folder);
|
2023-03-09 02:08:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
function blankslate(type, onClick) {
|
|
|
|
const message = Strings.Addons.blankSlateMessage.format({link: `https://betterdiscord.app/${type}s`, type}).toString();
|
|
|
|
return <EmptyImage title={Strings.Addons.blankSlateHeader.format({type})} message={message}>
|
|
|
|
<button className="bd-button" onClick={onClick}>{Strings.Addons.openFolder.format({type})}</button>
|
|
|
|
</EmptyImage>;
|
|
|
|
}
|
|
|
|
|
2024-02-22 06:06:30 +01:00
|
|
|
function makeBasicButton(title, children, action) {
|
|
|
|
return <DiscordModules.Tooltip color="primary" position="top" text={title}>
|
|
|
|
{(props) => <button {...props} className="bd-button" onClick={action}>{children}</button>}
|
|
|
|
</DiscordModules.Tooltip>;
|
|
|
|
}
|
|
|
|
|
2023-03-09 02:08:48 +01:00
|
|
|
function makeControlButton(title, children, action, selected = false) {
|
|
|
|
return <DiscordModules.Tooltip color="primary" position="top" text={title}>
|
|
|
|
{(props) => {
|
|
|
|
return <button {...props} className={"bd-button bd-view-button" + (selected ? " selected" : "")} onClick={action}>{children}</button>;
|
|
|
|
}}
|
|
|
|
</DiscordModules.Tooltip>;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getState(type, control, defaultValue) {
|
|
|
|
const addonlistControls = DataStore.getBDData("addonlistControls") || {};
|
|
|
|
if (!addonlistControls[type]) return defaultValue;
|
|
|
|
if (!addonlistControls[type].hasOwnProperty(control)) return defaultValue;
|
|
|
|
return addonlistControls[type][control];
|
|
|
|
}
|
|
|
|
|
|
|
|
function saveState(type, control, value) {
|
|
|
|
const addonlistControls = DataStore.getBDData("addonlistControls") || {};
|
|
|
|
if (!addonlistControls[type]) addonlistControls[type] = {};
|
|
|
|
addonlistControls[type][control] = value;
|
|
|
|
DataStore.setBDData("addonlistControls", addonlistControls);
|
|
|
|
}
|
|
|
|
|
|
|
|
function confirmDelete(addon) {
|
|
|
|
return new Promise(resolve => {
|
|
|
|
Modals.showConfirmationModal(Strings.Modals.confirmAction, Strings.Addons.confirmDelete.format({name: addon.name}), {
|
|
|
|
danger: true,
|
|
|
|
confirmText: Strings.Addons.deleteAddon,
|
|
|
|
onConfirm: () => {resolve(true);},
|
|
|
|
onCancel: () => {resolve(false);}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-02-22 06:06:30 +01:00
|
|
|
/**
|
|
|
|
* @param {function} action
|
|
|
|
* @param {string} type
|
|
|
|
* @returns
|
|
|
|
*/
|
|
|
|
function confirmEnable(action, type) {
|
|
|
|
/**
|
|
|
|
* @param {MouseEvent} event
|
|
|
|
*/
|
|
|
|
return function(event) {
|
|
|
|
if (event.shiftKey) return action();
|
|
|
|
Modals.showConfirmationModal(Strings.Modals.confirmAction, Strings.Addons.enableAllWarning.format({type: type.toLocaleLowerCase()}), {
|
|
|
|
confirmText: Strings.Modals.okay,
|
|
|
|
cancelText: Strings.Modals.cancel,
|
|
|
|
danger: true,
|
|
|
|
onConfirm: action,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-03-09 02:08:48 +01:00
|
|
|
|
2024-02-22 06:06:30 +01:00
|
|
|
export default function AddonList({prefix, type, title, folder, addonList, addonState, onChange, reload, editAddon, deleteAddon, enableAll, disableAll}) {
|
2023-03-09 02:08:48 +01:00
|
|
|
const [query, setQuery] = useState("");
|
|
|
|
const [sort, setSort] = useState(getState.bind(null, type, "sort", "name"));
|
2023-03-13 21:32:51 +01:00
|
|
|
const [ascending, setAscending] = useState(getState.bind(null, type, "ascending", true));
|
2023-03-09 02:08:48 +01:00
|
|
|
const [view, setView] = useState(getState.bind(null, type, "view", "list"));
|
2023-03-20 04:41:39 +01:00
|
|
|
const [forced, forceUpdate] = useReducer(x => x + 1, 0);
|
2023-03-09 02:08:48 +01:00
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
Events.on(`${prefix}-loaded`, forceUpdate);
|
|
|
|
Events.on(`${prefix}-unloaded`, forceUpdate);
|
|
|
|
return () => {
|
|
|
|
Events.off(`${prefix}-loaded`, forceUpdate);
|
|
|
|
Events.off(`${prefix}-unloaded`, forceUpdate);
|
|
|
|
};
|
2023-03-20 03:23:11 +01:00
|
|
|
}, [prefix]);
|
2023-03-09 02:08:48 +01:00
|
|
|
|
|
|
|
const changeView = useCallback((value) => {
|
|
|
|
saveState(type, "view", value);
|
|
|
|
setView(value);
|
2023-03-20 03:23:11 +01:00
|
|
|
}, [type]);
|
2023-03-09 02:08:48 +01:00
|
|
|
|
2023-03-20 03:23:11 +01:00
|
|
|
const listView = useCallback(() => changeView("list"), [changeView]);
|
|
|
|
const gridView = useCallback(() => changeView("grid"), [changeView]);
|
2023-03-09 02:08:48 +01:00
|
|
|
|
|
|
|
const changeDirection = useCallback((value) => {
|
|
|
|
saveState(type, "ascending", value);
|
|
|
|
setAscending(value);
|
2023-03-20 03:23:11 +01:00
|
|
|
}, [type]);
|
2023-03-09 02:08:48 +01:00
|
|
|
|
|
|
|
const changeSort = useCallback((value) => {
|
|
|
|
saveState(type, "sort", value);
|
|
|
|
setSort(value);
|
2023-03-20 03:23:11 +01:00
|
|
|
}, [type]);
|
2023-03-09 02:08:48 +01:00
|
|
|
|
|
|
|
const search = useCallback((e) => setQuery(e.target.value.toLocaleLowerCase()), []);
|
2023-03-20 03:23:11 +01:00
|
|
|
const triggerEdit = useCallback((id) => editAddon?.(id), [editAddon]);
|
2023-03-09 02:08:48 +01:00
|
|
|
const triggerDelete = useCallback(async (id) => {
|
|
|
|
const addon = addonList.find(a => a.id == id);
|
|
|
|
const shouldDelete = await confirmDelete(addon);
|
|
|
|
if (!shouldDelete) return;
|
|
|
|
if (deleteAddon) deleteAddon(addon);
|
2023-03-20 03:23:11 +01:00
|
|
|
}, [addonList, deleteAddon]);
|
2023-03-09 02:08:48 +01:00
|
|
|
|
|
|
|
const renderedCards = useMemo(() => {
|
|
|
|
let sorted = addonList.sort((a, b) => {
|
|
|
|
const sortByEnabled = sort === "isEnabled";
|
|
|
|
const first = sortByEnabled ? addonState[a.id] : a[sort];
|
|
|
|
const second = sortByEnabled ? addonState[b.id] : b[sort];
|
2022-06-27 00:29:59 +02:00
|
|
|
const stringSort = (str1, str2) => str1.toLocaleLowerCase().localeCompare(str2.toLocaleLowerCase());
|
|
|
|
if (typeof(first) == "string") return stringSort(first, second);
|
|
|
|
if (typeof(first) == "boolean") return (first === second) ? stringSort(a.name, b.name) : first ? -1 : 1;
|
2019-06-29 06:47:56 +02:00
|
|
|
if (first > second) return 1;
|
|
|
|
if (second > first) return -1;
|
|
|
|
return 0;
|
|
|
|
});
|
2023-03-09 02:08:48 +01:00
|
|
|
|
|
|
|
if (!ascending) sorted.reverse();
|
|
|
|
|
|
|
|
if (query) {
|
|
|
|
sorted = sorted.filter(addon => {
|
|
|
|
let matches = addon.name.toLocaleLowerCase().includes(query);
|
|
|
|
matches = matches || addon.author.toLocaleLowerCase().includes(query);
|
|
|
|
matches = matches || addon.description.toLocaleLowerCase().includes(query);
|
2020-11-04 01:45:36 +01:00
|
|
|
if (!matches) return false;
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
}
|
2020-11-04 03:06:07 +01:00
|
|
|
|
2023-03-09 02:08:48 +01:00
|
|
|
return sorted.map(addon => {
|
2020-11-04 03:06:07 +01:00
|
|
|
const hasSettings = addon.instance && typeof(addon.instance.getSettingsPanel) === "function";
|
|
|
|
const getSettings = hasSettings && addon.instance.getSettingsPanel.bind(addon.instance);
|
2024-02-22 06:06:30 +01:00
|
|
|
return <ErrorBoundary><AddonCard disabled={addon.partial} type={type} prefix={prefix} editAddon={() => triggerEdit(addon.id)} deleteAddon={() => triggerDelete(addon.id)} key={addon.id} enabled={addonState[addon.id]} addon={addon} onChange={onChange} reload={reload} hasSettings={hasSettings} getSettingsPanel={getSettings} /></ErrorBoundary>;
|
2020-11-04 03:06:07 +01:00
|
|
|
});
|
2024-02-22 06:06:30 +01:00
|
|
|
}, [addonList, addonState, onChange, reload, triggerDelete, triggerEdit, type, prefix, sort, ascending, query, forced]); // eslint-disable-line react-hooks/exhaustive-deps
|
2023-03-09 02:08:48 +01:00
|
|
|
|
|
|
|
const hasAddonsInstalled = addonList.length !== 0;
|
|
|
|
const isSearching = !!query;
|
|
|
|
const hasResults = renderedCards.length !== 0;
|
|
|
|
|
|
|
|
return [
|
2024-02-22 06:06:30 +01:00
|
|
|
<SettingsTitle key="title" text={isSearching ? `${title} - ${Strings.Addons.results.format({count: `${renderedCards.length}`})}` : title}>
|
|
|
|
<Search onChange={search} placeholder={`${Strings.Addons.search.format({type: `${renderedCards.length} ${title}`})}...`} />
|
|
|
|
</SettingsTitle>,
|
2023-03-09 02:08:48 +01:00
|
|
|
<div className={"bd-controls bd-addon-controls"}>
|
2024-02-22 06:06:30 +01:00
|
|
|
{/* <Search onChange={search} placeholder={`${Strings.Addons.search.format({type: title})}...`} /> */}
|
|
|
|
<div className="bd-controls-basic">
|
|
|
|
{makeBasicButton(Strings.Addons.openFolder.format({type: title}), <FolderIcon />, openFolder.bind(null, folder))}
|
|
|
|
{makeBasicButton(Strings.Addons.enableAll, <CheckIcon size="20px" />, confirmEnable(enableAll, title))}
|
|
|
|
{makeBasicButton(Strings.Addons.disableAll, <CloseIcon size="20px" />, disableAll)}
|
|
|
|
</div>
|
2023-03-09 02:08:48 +01:00
|
|
|
<div className="bd-controls-advanced">
|
|
|
|
<div className="bd-addon-dropdowns">
|
|
|
|
<div className="bd-select-wrapper">
|
|
|
|
<label className="bd-label">{Strings.Sorting.sortBy}:</label>
|
2023-08-29 06:40:35 +02:00
|
|
|
<Dropdown options={buildSortOptions()} value={sort} onChange={changeSort} style="transparent" />
|
2019-06-30 05:09:48 +02:00
|
|
|
</div>
|
2023-03-09 02:08:48 +01:00
|
|
|
<div className="bd-select-wrapper">
|
|
|
|
<label className="bd-label">{Strings.Sorting.order}:</label>
|
2023-08-29 06:40:35 +02:00
|
|
|
<Dropdown options={buildDirectionOptions()} value={ascending} onChange={changeDirection} style="transparent" />
|
2019-06-30 05:09:48 +02:00
|
|
|
</div>
|
2019-06-29 06:47:56 +02:00
|
|
|
</div>
|
2023-03-09 02:08:48 +01:00
|
|
|
<div className="bd-addon-views">
|
2023-08-29 06:40:35 +02:00
|
|
|
{makeControlButton(Strings.Addons.listView, <ListIcon />, listView, view === "list")}
|
|
|
|
{makeControlButton(Strings.Addons.gridView, <GridIcon />, gridView, view === "grid")}
|
2023-03-09 02:08:48 +01:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>,
|
|
|
|
!hasAddonsInstalled && blankslate(type, () => openFolder(folder)),
|
|
|
|
isSearching && !hasResults && hasAddonsInstalled && <NoResults />,
|
|
|
|
hasAddonsInstalled && <div key="addonList" className={"bd-addon-list" + (view == "grid" ? " bd-grid-view" : "")}>{renderedCards}</div>
|
|
|
|
];
|
2020-07-16 07:42:56 +02:00
|
|
|
}
|