Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(handlers): refactor handlers to allow plugins development #32

Merged
merged 8 commits into from
Aug 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@
"@emotion/react": "11.11.1",
"@emotion/styled": "11.11.0",
"@redux-devtools/extension": "^3.2.2",
"allotment": "^1.19.3",
"electron-debug": "^3.2.0",
"electron-log": "^4.4.4",
"electron-store": "^8.0.1",
Expand Down
2 changes: 1 addition & 1 deletion release/app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "odeck",
"version": "0.0.4",
"version": "0.0.5",
"description": "A free and open-source alternative to StreamDeck",
"main": "./dist/main/main.js",
"author": {
Expand Down
51 changes: 51 additions & 0 deletions src/interfaces/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { IconType } from 'react-icons';
import KeyTypes from 'server/enums/keys.enum';
import { Socket } from 'socket.io';

interface IActionConfig {
bindings?: Array<string>;
Expand Down Expand Up @@ -75,3 +76,53 @@ interface ISelectDevice {
deviceId: string;
deviceType: 'ADMIN' | 'DECK';
}

interface RegisteredHandler {
config: any;
handler: { initialize: () => void };
}

interface KeyPressEvent {
keyPressed: IButtonKey;
socket: Socket;
}

interface InputProp {
defaultValue: string;
name: string;
}

type HandlerItem = {
readonly title: string;
readonly description?: string;
readonly defaults: Partial<IButtonKey>;
readonly icon: string;
};

type HandlerInput = {
readonly label: string;
readonly description: string;
readonly type: string;
readonly name: string;
readonly defaultValue: string;
readonly secret?: boolean;
readonly props: Record<string, unknown>;
};

interface HandlerConfig {
readonly groupKey: string;
readonly id: string;
readonly defaultActive: boolean;
readonly config: HandlerInput[];
handlers: Partial<{
[key in KeyTypes]: HandlerItem;
}>;
inputs: Partial<{
[key in KeyTypes]: HandlerInput[];
}>;
}

interface HandlerData {
id: string;
data: Record<string, unknown>;
}
2 changes: 2 additions & 0 deletions src/main/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ const createWindow = async () => {
* Add event listeners...
*/

app.commandLine.appendSwitch('ignore-certificate-errors');

app.on('window-all-closed', () => {
// Respect the OSX convention of having the application in memory even
// after all windows have been closed
Expand Down
15 changes: 11 additions & 4 deletions src/renderer/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ import { HTML5Backend } from 'react-dnd-html5-backend';
import { I18nextProvider } from 'react-i18next';
import SocketProvider from './context/socket.context';
import './App.css';
import Home from './pages/home';
import Device from './pages/device';
import theme from './theme';
import Start from './pages/start';
import Devices from './pages/home/devices';
import store from './redux/store';
import i18n from './i18n';
import HomePage from './pages/home';
import PluginsPage from './pages/home/plugins';
import AboutPage from './pages/home/about';

const AppRouter = () => {
const { setColorMode } = useColorMode();
Expand All @@ -22,8 +25,12 @@ const AppRouter = () => {
return (
<Router>
<Routes>
<Route path="/" element={<Start />} />
<Route path="/dashboard" element={<Home />} />
<Route path="/" element={<HomePage />}>
<Route index element={<Devices />} />
<Route path="plugins" element={<PluginsPage />} />
<Route path="about" element={<AboutPage />} />
</Route>
<Route path="dashboard" element={<Device />} />
</Routes>
</Router>
);
Expand Down
52 changes: 30 additions & 22 deletions src/renderer/components/DeleteWarnModal/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import {
AlertDialog,
AlertDialogBody,
AlertDialogContent,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogOverlay,
Button,
Modal,
ModalCloseButton,
Expand All @@ -9,14 +15,15 @@ import {
useDisclosure,
} from '@chakra-ui/react';
import { IDevice } from 'interfaces';
import { memo, useCallback } from 'react';
import { memo, useCallback, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { deleteDevice } from 'renderer/redux/ducks/devices';

const DeleteWarnModal = ({ device }: { device: IDevice }) => {
const { isOpen, onOpen, onClose } = useDisclosure();
const { t } = useTranslation('delete-warn');
const cancelRef = useRef<any>();

const dispatch = useDispatch();

Expand All @@ -26,34 +33,35 @@ const DeleteWarnModal = ({ device }: { device: IDevice }) => {

const handleRemoveDevice = useCallback(() => {
dispatch(deleteDevice(device));
}, [dispatch]);
}, [dispatch, device]);

return (
<>
<Button size="sm" variant="ghost" colorScheme="red" onClick={onOpen}>
{t('delete')}
</Button>
<AlertDialog
isOpen={isOpen}
leastDestructiveRef={cancelRef}
onClose={onClose}
>
<AlertDialogOverlay>
<AlertDialogContent>
<AlertDialogHeader fontSize="lg" fontWeight="bold">
{t('title')}
</AlertDialogHeader>

<Modal isOpen={isOpen} onClose={handleClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>{t('warn')}</ModalHeader>
<ModalCloseButton />

<ModalFooter>
<Button mr={3} variant="ghost" onClick={handleClose}>
{t('cancel')}
</Button>
<Button
variant="outline"
onClick={handleRemoveDevice}
colorScheme="red"
>
{t('confirm')}
</Button>
</ModalFooter>
</ModalContent>
</Modal>
<AlertDialogBody>{t('description')}</AlertDialogBody>

<AlertDialogFooter>
<Button onClick={onClose}>{t('cancel')}</Button>
<Button colorScheme="red" onClick={handleRemoveDevice} ml={3}>
{t('confirm')}
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogOverlay>
</AlertDialog>
</>
);
};
Expand Down
3 changes: 2 additions & 1 deletion src/renderer/components/Form/FormError/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React from 'react';
import { FormErrorMessageProps } from '@chakra-ui/react';
import styles from './styles.module.scss';

const FormError: React.FC = ({ children }) => {
const FormError: React.FC<FormErrorMessageProps> = ({ children }) => {
return <span className={styles.errorMessage}>{children}</span>;
};

Expand Down
10 changes: 7 additions & 3 deletions src/renderer/components/Form/FormLabel/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { FormLabel as ChakraFormLabel } from '@chakra-ui/react';
import React from 'react';
import {
FormLabel as ChakraFormLabel,
FormLabelProps as BaseFormLabelProps,
} from '@chakra-ui/react';
import React, { ReactElement } from 'react';
import styles from './styles.module.scss';

interface FormLabelProps {
interface FormLabelProps extends BaseFormLabelProps {
// eslint-disable-next-line react/require-default-props
required?: boolean;
children: string | ReactElement | undefined;
}

const FormLabel: React.FC<FormLabelProps> = ({
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/components/Form/InputBindings/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
FormLabel,
Button,
} from '@chakra-ui/react';
import useRecordKeybindings from 'hooks/useRecordKeybindings';
import useRecordKeybindings from 'renderer/hooks/useRecordKeybindings';

import React, { useCallback, useMemo } from 'react';
import { useFormContext } from 'react-hook-form';
Expand Down
11 changes: 8 additions & 3 deletions src/renderer/components/Form/SelectInput/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { FormControl, InputGroup, Select, SelectProps } from '@chakra-ui/react';
import {
FormControl,
FormLabel,
InputGroup,
Select,
SelectProps,
} from '@chakra-ui/react';
import React from 'react';
import { useFormContext } from 'react-hook-form';
import FormError from '../FormError';
import FormLabel from '../FormLabel';

interface SelectOptions {
key: string | number;
Expand Down Expand Up @@ -30,7 +35,7 @@ const SelectInput: React.FC<SelectInputProps & SelectProps> = ({

return (
<FormControl>
<FormLabel required={required}>{label}</FormLabel>
<FormLabel color="white">{label}</FormLabel>
<InputGroup>
<Select
data-testid={name}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.switchContainer {
display: flex;
flex-direction: column;
}
72 changes: 72 additions & 0 deletions src/renderer/components/Form/SwitchInput/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { FormControl, SwitchProps, Switch } from '@chakra-ui/react';
import React, { useCallback, memo, useMemo } from 'react';
import {
Controller,
useFormContext,
ControllerRenderProps,
FieldValues,
} from 'react-hook-form';
import FormError from '../FormError';
import styles from './SwitchInput.module.scss';
import FormLabel from '../FormLabel';

export interface SwitchInputProps extends SwitchProps {
label?: string;
name: string;
required?: boolean;
disabled?: boolean;
placeholder?: string;
}

const SwitchInput: React.FC<SwitchInputProps> = ({
label,
name,
required,
disabled,
defaultValue,
...rest
}) => {
const { control, formState } = useFormContext();

const renderInput = useCallback(
({ field }: { field: ControllerRenderProps<FieldValues, string> }) => (
<Switch
ref={field.ref}
onChange={field.onChange}
isChecked={field.value}
isInvalid={formState.errors[name]}
{...rest}
/>
),
[formState.errors, rest, name]
);

const rules = useMemo(
() => ({
disabled,
required,
}),
[disabled, required]
);

return (
<FormControl w="fit-content">
<FormLabel htmlFor={name} required={required}>
{label}
</FormLabel>
<div className={styles.switchContainer}>
<Controller
rules={rules}
control={control}
name={name}
render={renderInput}
defaultValue={defaultValue || false}
{...rest}
/>
<FormError>{formState.errors[name]?.message}</FormError>
</div>
</FormControl>
);
};

export default memo(SwitchInput);
2 changes: 1 addition & 1 deletion src/renderer/components/Form/TextInput/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const TextInput: React.FC<TextInputProps> = ({
isDisabled={disabled}
isRequired={required}
>
<FormLabel htmlFor={name} required={required}>
<FormLabel color="white" htmlFor={name}>
{label}
</FormLabel>
{hint && <FormHelperText marginBottom={4}>{hint}</FormHelperText>}
Expand Down
9 changes: 5 additions & 4 deletions src/renderer/components/Form/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React from 'react';
import { FormProvider } from 'react-hook-form';
import React, { ReactElement } from 'react';
import { FieldValues, FormProvider, UseFormReturn } from 'react-hook-form';

interface FormProps {
form: any;
onSubmit(values: unknown): any;
form: UseFormReturn<FieldValues, object>;
onSubmit(values: unknown): void;
children: ReactElement | ReactElement[];
}

const Form: React.FC<FormProps> = ({ children, form, onSubmit }) => {
Expand Down
Loading