Skip to content

Commit

Permalink
add tiyo plugin settings modal
Browse files Browse the repository at this point in the history
  • Loading branch information
xgi committed May 7, 2024
1 parent fff665a commit 6aed12a
Show file tree
Hide file tree
Showing 2 changed files with 211 additions and 25 deletions.
175 changes: 175 additions & 0 deletions src/renderer/components/plugins/PluginSettingsModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import React, { useEffect, useState } from 'react';
const { ipcRenderer } = require('electron');
import {
Accordion,
Button,
Center,
Group,
Input,
Loader,
Modal,
Stack,
Switch,
Text,
} from '@mantine/core';
import ipcChannels from '@/common/constants/ipcChannels.json';
import storeKeys from '@/common/constants/storeKeys.json';
import persistantStore from '../../util/persistantStore';
import { ExtensionMetadata, SettingType } from '@tiyo/common';

type SettingTypes = {
[key: string]: SettingType;
};

type Settings = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any;
};

type SettingTypesMap = { [extensionId: string]: SettingTypes };
type SettingsMap = { [extensionId: string]: Settings };

type Props = {
visible: boolean;
toggleVisible: () => void;
};

const PluginSettingsModal: React.FC<Props> = (props: Props) => {
const [loading, setLoading] = useState(true);
const [extensions, setExtensions] = useState<ExtensionMetadata[]>([]);
const [settingTypesMap, setSettingTypesMap] = useState<SettingTypesMap>({});
const [settingsMap, setSettingsMap] = useState<SettingsMap>({});

const loadExtensionSettings = async () => {
const newExtensions: ExtensionMetadata[] = await ipcRenderer.invoke(
ipcChannels.EXTENSION_MANAGER.GET_ALL,
);

const newSettingTypesMap: SettingTypesMap = {};
const newSettingsMap: SettingsMap = {};

for (const extension of newExtensions) {
const settingTypes = await ipcRenderer.invoke(
ipcChannels.EXTENSION.GET_SETTING_TYPES,
extension.id,
);

if (Object.keys(settingTypes).length > 0) {
const settings = await ipcRenderer.invoke(ipcChannels.EXTENSION.GET_SETTINGS, extension.id);
newSettingTypesMap[extension.id] = settingTypes;
newSettingsMap[extension.id] = settings;
}
}

setExtensions(newExtensions);
setSettingTypesMap(newSettingTypesMap);
setSettingsMap(newSettingsMap);
};

const updateSetting = (extensionId: string, key: string, value: unknown) => {
const newSettingsMap = { ...settingsMap };

newSettingsMap[extensionId][key] = value;
setSettingsMap(newSettingsMap);
};

const saveExtensionSettings = async () => {
for (const extension of extensions) {
await ipcRenderer.invoke(
ipcChannels.EXTENSION.SET_SETTINGS,
extension.id,
settingsMap[extension.id],
);
persistantStore.write(
`${storeKeys.EXTENSION_SETTINGS_PREFIX}${extension.id}`,
JSON.stringify(settingsMap[extension.id]),
);
}
};

const renderControl = (
settingType: SettingType,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
curVal: any,
onChangeFn: (newValue: unknown) => void,
) => {
switch (settingType) {
case SettingType.BOOLEAN:
return <Switch defaultChecked={curVal} onChange={(e) => onChangeFn(e.target.checked)} />;
case SettingType.STRING:
return (
<Input
value={curVal}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onChangeFn(e.target.value)}
/>
);
default:
return <></>;
}
};

const renderRows = () => {
return Object.entries(settingTypesMap).map(([extensionId, settingTypes]) => {
const extensionName = extensions.find((ext) => ext.id === extensionId)?.name;
const settingKeys = Object.keys(settingTypes);

return (
<Accordion.Item key={extensionId} value={extensionId}>
<Accordion.Control>{extensionName}</Accordion.Control>
<Accordion.Panel>
{settingKeys.map((key) => (
<Group justify="space-between" mb="xs" key={key}>
<Text>{key}</Text>
{renderControl(
settingTypes[key],
settingsMap[extensionId][key],
(newValue: unknown) => updateSetting(extensionId, key, newValue),
)}
</Group>
))}
</Accordion.Panel>
</Accordion.Item>
);
});
};

useEffect(() => {
if (props.visible) {
setLoading(true);
loadExtensionSettings()
.finally(() => setLoading(false))
.catch(console.error);
}
}, [props.visible]);

return (
<Modal
title="Tiyo Settings"
opened={props.visible}
onClose={props.toggleVisible}
closeOnClickOutside={false}
>
{loading ? (
<Center h="100%" mx="auto">
<Stack align="center">
<Loader />
</Stack>
</Center>
) : (
<>
<Accordion>{renderRows()}</Accordion>
<Group justify="flex-end" mt="sm">
<Button variant="default" onClick={props.toggleVisible}>
Cancel
</Button>
<Button onClick={() => saveExtensionSettings().then(() => props.toggleVisible())}>
Save Settings
</Button>
</Group>
</>
)}
</Modal>
);
};

export default PluginSettingsModal;
61 changes: 36 additions & 25 deletions src/renderer/components/plugins/Plugins.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ const { ipcRenderer } = require('electron');
import { gt } from 'semver';
import { useListState } from '@mantine/hooks';
import ipcChannels from '@/common/constants/ipcChannels.json';
import PluginSettingsModal from './PluginSettingsModal';

// eslint-disable-next-line @typescript-eslint/ban-types
type Props = {};

const Plugins: React.FC<Props> = () => {
const [currentTiyoVersion, setCurrentTiyoVersion] = useState<string | undefined>(undefined);
const [availableTiyoVersion, setAvailableTiyoVersion] = useState<string | undefined>(undefined);
const [showingSettingsModal, setShowingSettingsModal] = useState(false);

const [installingPlugins, installingPluginsHandlers] = useListState<string>([]);
const [refreshing, setRefreshing] = useState(false);
Expand Down Expand Up @@ -78,6 +80,11 @@ const Plugins: React.FC<Props> = () => {

return (
<>
<PluginSettingsModal
visible={showingSettingsModal}
toggleVisible={() => setShowingSettingsModal(!showingSettingsModal)}
/>

<Group align="left" mb="md" gap="sm" wrap="nowrap">
<Button loading={refreshing} onClick={() => refreshMetadata()}>
Check for Updates
Expand All @@ -93,27 +100,27 @@ const Plugins: React.FC<Props> = () => {
</Group>

<Table>
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<th>
<Table.Thead>
<Table.Tr>
<Table.Th>Name</Table.Th>
<Table.Th>Description</Table.Th>
<Table.Th>
<Text ta="center">Version</Text>
</th>
<th> </th>
</tr>
</thead>
<tbody>
<tr>
<td>
</Table.Th>
<Table.Th> </Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
<Table.Tr>
<Table.Td>
<Text size="md">Tiyo Extension Manager</Text>
</td>
<td>
</Table.Td>
<Table.Td>
<Text size="md">
Adds support for importing content from other sources, including 3rd-party websites.
</Text>
</td>
<td>
</Table.Td>
<Table.Td>
<Text size="md" ta="center">
{availableTiyoVersion === currentTiyoVersion || !currentTiyoVersion ? (
availableTiyoVersion
Expand All @@ -123,16 +130,20 @@ const Plugins: React.FC<Props> = () => {
</>
)}
</Text>
</td>
<td>
<Group gap="xs">
</Table.Td>
<Table.Td>
<Group gap="xs" wrap="nowrap">
{currentTiyoVersion !== undefined ? (
<Button variant="default" onClick={() => setShowingSettingsModal(true)}>
Settings
</Button>
) : undefined}

{tiyoCanUpdate ? (
<Button onClick={() => handleInstall('@tiyo/core', availableTiyoVersion)}>
Update
</Button>
) : (
''
)}
) : undefined}
{currentTiyoVersion === undefined && availableTiyoVersion !== undefined ? (
<Button
loading={installingPlugins.includes('@tiyo/core')}
Expand All @@ -147,9 +158,9 @@ const Plugins: React.FC<Props> = () => {
</Button>
)}
</Group>
</td>
</tr>
</tbody>
</Table.Td>
</Table.Tr>
</Table.Tbody>
</Table>
</>
);
Expand Down

0 comments on commit 6aed12a

Please sign in to comment.