mirror of
https://github.com/Sonarr/Sonarr
synced 2026-01-23 16:02:14 +01:00
Use react-query for Connections
This commit is contained in:
parent
0d80c093ff
commit
6d49b41dd2
19 changed files with 606 additions and 378 deletions
|
|
@ -73,8 +73,6 @@ export const useEnsureImportSeriesItems = (
|
|||
export const updateImportSeriesItem = (
|
||||
itemData: Partial<ImportSeriesItem> & Pick<ImportSeriesItem, 'id'>
|
||||
) => {
|
||||
console.info('\x1b[36m[MarkTest] updating item\x1b[0m', itemData);
|
||||
|
||||
importSeriesStore.setState((state) => {
|
||||
const existingItem = state.items[itemData.id];
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import ImportListExclusion from 'typings/ImportListExclusion';
|
|||
import ImportListOptionsSettings from 'typings/ImportListOptionsSettings';
|
||||
import Indexer from 'typings/Indexer';
|
||||
import IndexerFlag from 'typings/IndexerFlag';
|
||||
import Notification from 'typings/Notification';
|
||||
import DownloadClientOptions from 'typings/Settings/DownloadClientOptions';
|
||||
import General from 'typings/Settings/General';
|
||||
import IndexerOptions from 'typings/Settings/IndexerOptions';
|
||||
|
|
@ -92,12 +91,6 @@ export interface IndexerAppState
|
|||
isTestingAll: boolean;
|
||||
}
|
||||
|
||||
export interface NotificationAppState
|
||||
extends AppSectionState<Notification>,
|
||||
AppSectionDeleteState,
|
||||
AppSectionSaveState,
|
||||
AppSectionSchemaState<Presets<Notification>> {}
|
||||
|
||||
export interface CustomFormatAppState
|
||||
extends AppSectionState<CustomFormat>,
|
||||
AppSectionDeleteState,
|
||||
|
|
@ -144,7 +137,6 @@ interface SettingsAppState {
|
|||
metadata: MetadataAppState;
|
||||
naming: NamingAppState;
|
||||
namingExamples: NamingExamplesAppState;
|
||||
notifications: NotificationAppState;
|
||||
}
|
||||
|
||||
export default SettingsAppState;
|
||||
|
|
|
|||
91
frontend/src/Helpers/Hooks/usePendingFieldsStore.ts
Normal file
91
frontend/src/Helpers/Hooks/usePendingFieldsStore.ts
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { create, useStore } from 'zustand';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
|
||||
interface PendingFieldsStore {
|
||||
pendingFields: Map<string, unknown>;
|
||||
}
|
||||
|
||||
export const usePendingFieldsStore = () => {
|
||||
// eslint-disable-next-line react/hook-use-state
|
||||
const [store] = useState(() => {
|
||||
return create<PendingFieldsStore>()((_set) => {
|
||||
return {
|
||||
pendingFields: new Map(),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const setPendingField = useCallback(
|
||||
(name: string, value: unknown) => {
|
||||
store.setState((state) => {
|
||||
const newPendingFields = new Map(state.pendingFields);
|
||||
newPendingFields.set(name, value);
|
||||
|
||||
return {
|
||||
...state,
|
||||
pendingFields: newPendingFields,
|
||||
};
|
||||
});
|
||||
},
|
||||
[store]
|
||||
);
|
||||
|
||||
const setPendingFields = useCallback(
|
||||
(fieldProperties: Record<string, unknown>) => {
|
||||
store.setState((state) => {
|
||||
const newPendingFields = new Map(state.pendingFields);
|
||||
Object.entries(fieldProperties).forEach(([key, value]) => {
|
||||
newPendingFields.set(key, value);
|
||||
});
|
||||
return {
|
||||
...state,
|
||||
pendingFields: newPendingFields,
|
||||
};
|
||||
});
|
||||
},
|
||||
[store]
|
||||
);
|
||||
|
||||
const unsetPendingField = useCallback(
|
||||
(name: string) => {
|
||||
store.setState((state) => {
|
||||
const newPendingFields = new Map(state.pendingFields);
|
||||
newPendingFields.delete(name);
|
||||
return {
|
||||
...state,
|
||||
pendingFields: newPendingFields,
|
||||
};
|
||||
});
|
||||
},
|
||||
[store]
|
||||
);
|
||||
|
||||
const clearPendingFields = useCallback(() => {
|
||||
store.setState((state) => ({
|
||||
...state,
|
||||
pendingFields: new Map(),
|
||||
}));
|
||||
}, [store]);
|
||||
|
||||
const pendingFields = useStore(
|
||||
store,
|
||||
useShallow((state) => {
|
||||
return state.pendingFields;
|
||||
})
|
||||
);
|
||||
|
||||
const hasPendingFields = useMemo(() => {
|
||||
return pendingFields.size > 0;
|
||||
}, [pendingFields]);
|
||||
|
||||
return {
|
||||
store,
|
||||
pendingFields,
|
||||
setPendingField,
|
||||
setPendingFields,
|
||||
unsetPendingField,
|
||||
clearPendingFields,
|
||||
hasPendingFields,
|
||||
};
|
||||
};
|
||||
|
|
@ -1,13 +1,12 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import Button from 'Components/Link/Button';
|
||||
import Link from 'Components/Link/Link';
|
||||
import Menu from 'Components/Menu/Menu';
|
||||
import MenuContent from 'Components/Menu/MenuContent';
|
||||
import { sizes } from 'Helpers/Props';
|
||||
import { selectNotificationSchema } from 'Store/Actions/settingsActions';
|
||||
import Notification from 'typings/Notification';
|
||||
import { SelectedSchema } from 'Settings/useProviderSchema';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import { NotificationModel } from '../useConnections';
|
||||
import AddNotificationPresetMenuItem from './AddNotificationPresetMenuItem';
|
||||
import styles from './AddNotificationItem.css';
|
||||
|
||||
|
|
@ -15,8 +14,8 @@ interface AddNotificationItemProps {
|
|||
implementation: string;
|
||||
implementationName: string;
|
||||
infoLink: string;
|
||||
presets?: Notification[];
|
||||
onNotificationSelect: () => void;
|
||||
presets?: NotificationModel[];
|
||||
onNotificationSelect: (selectedScehema: SelectedSchema) => void;
|
||||
}
|
||||
|
||||
function AddNotificationItem({
|
||||
|
|
@ -26,19 +25,11 @@ function AddNotificationItem({
|
|||
presets,
|
||||
onNotificationSelect,
|
||||
}: AddNotificationItemProps) {
|
||||
const dispatch = useDispatch();
|
||||
const hasPresets = !!presets && !!presets.length;
|
||||
|
||||
const handleNotificationSelect = useCallback(() => {
|
||||
dispatch(
|
||||
selectNotificationSchema({
|
||||
implementation,
|
||||
implementationName,
|
||||
})
|
||||
);
|
||||
|
||||
onNotificationSelect();
|
||||
}, [implementation, implementationName, dispatch, onNotificationSelect]);
|
||||
onNotificationSelect({ implementation, implementationName });
|
||||
}, [implementation, implementationName, onNotificationSelect]);
|
||||
|
||||
return (
|
||||
<div className={styles.notification}>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import AppState from 'App/State/AppState';
|
||||
import React from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import Button from 'Components/Link/Button';
|
||||
|
|
@ -10,13 +8,14 @@ import ModalContent from 'Components/Modal/ModalContent';
|
|||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import { fetchNotificationSchema } from 'Store/Actions/settingsActions';
|
||||
import { SelectedSchema } from 'Settings/useProviderSchema';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import { useConnectionSchema } from '../useConnections';
|
||||
import AddNotificationItem from './AddNotificationItem';
|
||||
import styles from './AddNotificationModalContent.css';
|
||||
|
||||
export interface AddNotificationModalContentProps {
|
||||
onNotificationSelect: () => void;
|
||||
onNotificationSelect: (selectedScehema: SelectedSchema) => void;
|
||||
onModalClose: () => void;
|
||||
}
|
||||
|
||||
|
|
@ -24,27 +23,21 @@ function AddNotificationModalContent({
|
|||
onNotificationSelect,
|
||||
onModalClose,
|
||||
}: AddNotificationModalContentProps) {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { isSchemaFetching, isSchemaPopulated, schemaError, schema } =
|
||||
useSelector((state: AppState) => state.settings.notifications);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchNotificationSchema());
|
||||
}, [dispatch]);
|
||||
const { isSchemaFetching, isSchemaFetched, schemaError, schema } =
|
||||
useConnectionSchema();
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>{translate('AddNotification')}</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
{isSchemaFetching ? <LoadingIndicator /> : null}
|
||||
{isSchemaFetching && !isSchemaFetched ? <LoadingIndicator /> : null}
|
||||
|
||||
{!isSchemaFetching && !!schemaError ? (
|
||||
<Alert kind={kinds.DANGER}>{translate('AddNotificationError')}</Alert>
|
||||
) : null}
|
||||
|
||||
{isSchemaPopulated && !schemaError ? (
|
||||
{isSchemaFetched && !schemaError ? (
|
||||
<div>
|
||||
<Alert kind={kinds.INFO}>
|
||||
<div>{translate('SupportedNotifications')}</div>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import MenuItem from 'Components/Menu/MenuItem';
|
||||
import { selectNotificationSchema } from 'Store/Actions/settingsActions';
|
||||
import { SelectedSchema } from 'Settings/useProviderSchema';
|
||||
|
||||
interface AddNotificationPresetMenuItemProps {
|
||||
name: string;
|
||||
implementation: string;
|
||||
implementationName: string;
|
||||
onPress: () => void;
|
||||
onPress: (selectedScehema: SelectedSchema) => void;
|
||||
}
|
||||
|
||||
function AddNotificationPresetMenuItem({
|
||||
|
|
@ -17,19 +16,9 @@ function AddNotificationPresetMenuItem({
|
|||
onPress,
|
||||
...otherProps
|
||||
}: AddNotificationPresetMenuItemProps) {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handlePress = useCallback(() => {
|
||||
dispatch(
|
||||
selectNotificationSchema({
|
||||
implementation,
|
||||
implementationName,
|
||||
presetName: name,
|
||||
})
|
||||
);
|
||||
|
||||
onPress();
|
||||
}, [name, implementation, implementationName, dispatch, onPress]);
|
||||
onPress({ implementation, implementationName, presetName: name });
|
||||
}, [name, implementation, implementationName, onPress]);
|
||||
|
||||
return (
|
||||
<MenuItem {...otherProps} onPress={handlePress}>
|
||||
|
|
|
|||
|
|
@ -3,10 +3,6 @@ import { useDispatch } from 'react-redux';
|
|||
import Modal from 'Components/Modal/Modal';
|
||||
import { sizes } from 'Helpers/Props';
|
||||
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
||||
import {
|
||||
cancelSaveNotification,
|
||||
cancelTestNotification,
|
||||
} from 'Store/Actions/settingsActions';
|
||||
import EditNotificationModalContent, {
|
||||
EditNotificationModalContentProps,
|
||||
} from './EditNotificationModalContent';
|
||||
|
|
@ -26,8 +22,6 @@ function EditNotificationModal({
|
|||
|
||||
const handleModalClose = useCallback(() => {
|
||||
dispatch(clearPendingChanges({ section }));
|
||||
dispatch(cancelTestNotification({ section }));
|
||||
dispatch(cancelSaveNotification({ section }));
|
||||
onModalClose();
|
||||
}, [dispatch, onModalClose]);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
import React, { useCallback, useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { NotificationAppState } from 'App/State/SettingsAppState';
|
||||
import Alert from 'Components/Alert';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
|
|
@ -9,7 +7,6 @@ import FormLabel from 'Components/Form/FormLabel';
|
|||
import ProviderFieldFormGroup from 'Components/Form/ProviderFieldFormGroup';
|
||||
import Button from 'Components/Link/Button';
|
||||
import SpinnerErrorButton from 'Components/Link/SpinnerErrorButton';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
|
|
@ -18,48 +15,45 @@ import usePrevious from 'Helpers/Hooks/usePrevious';
|
|||
import { inputTypes, kinds } from 'Helpers/Props';
|
||||
import AdvancedSettingsButton from 'Settings/AdvancedSettingsButton';
|
||||
import { useShowAdvancedSettings } from 'Settings/advancedSettingsStore';
|
||||
import {
|
||||
saveNotification,
|
||||
setNotificationFieldValues,
|
||||
setNotificationValue,
|
||||
testNotification,
|
||||
} from 'Store/Actions/settingsActions';
|
||||
import { createProviderSettingsSelectorHook } from 'Store/Selectors/createProviderSettingsSelector';
|
||||
import { useManageConnection } from 'Settings/Notifications/useConnections';
|
||||
import { SelectedSchema } from 'Settings/useProviderSchema';
|
||||
import { EnhancedSelectInputChanged, InputChanged } from 'typings/inputs';
|
||||
import Notification from 'typings/Notification';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import NotificationEventItems from './NotificationEventItems';
|
||||
import styles from './EditNotificationModalContent.css';
|
||||
|
||||
export interface EditNotificationModalContentProps {
|
||||
id?: number;
|
||||
selectedSchema?: SelectedSchema;
|
||||
onModalClose: () => void;
|
||||
onDeleteNotificationPress?: () => void;
|
||||
}
|
||||
|
||||
function EditNotificationModalContent({
|
||||
id,
|
||||
selectedSchema,
|
||||
onModalClose,
|
||||
onDeleteNotificationPress,
|
||||
}: EditNotificationModalContentProps) {
|
||||
const dispatch = useDispatch();
|
||||
const showAdvancedSettings = useShowAdvancedSettings();
|
||||
|
||||
const result = useManageConnection(id, selectedSchema);
|
||||
const {
|
||||
isFetching,
|
||||
error,
|
||||
isSaving,
|
||||
isTesting = false,
|
||||
saveError,
|
||||
item,
|
||||
updateValue,
|
||||
saveProvider,
|
||||
isSaving,
|
||||
saveError,
|
||||
testProvider,
|
||||
isTesting,
|
||||
validationErrors,
|
||||
validationWarnings,
|
||||
} = useSelector(
|
||||
createProviderSettingsSelectorHook<Notification, NotificationAppState>(
|
||||
'notifications',
|
||||
id
|
||||
)
|
||||
);
|
||||
} = result;
|
||||
|
||||
// updateFieldValue is guaranteed to exist for NotificationModel since it extends Provider
|
||||
const { updateFieldValue } = result as typeof result & {
|
||||
updateFieldValue: (fieldProperties: Record<string, unknown>) => void;
|
||||
};
|
||||
|
||||
const wasSaving = usePrevious(isSaving);
|
||||
|
||||
|
|
@ -67,10 +61,10 @@ function EditNotificationModalContent({
|
|||
|
||||
const handleInputChange = useCallback(
|
||||
(change: InputChanged) => {
|
||||
// @ts-expect-error - actions are not typed
|
||||
dispatch(setNotificationValue(change));
|
||||
// @ts-expect-error - change is not yet typed
|
||||
updateValue(change.name, change.value);
|
||||
},
|
||||
[dispatch]
|
||||
[updateValue]
|
||||
);
|
||||
|
||||
const handleFieldChange = useCallback(
|
||||
|
|
@ -79,23 +73,18 @@ function EditNotificationModalContent({
|
|||
value,
|
||||
additionalProperties,
|
||||
}: EnhancedSelectInputChanged<unknown>) => {
|
||||
dispatch(
|
||||
// @ts-expect-error - actions are not typed
|
||||
setNotificationFieldValues({
|
||||
properties: { [name]: value, ...additionalProperties },
|
||||
})
|
||||
);
|
||||
updateFieldValue({ [name]: value, ...additionalProperties });
|
||||
},
|
||||
[dispatch]
|
||||
[updateFieldValue]
|
||||
);
|
||||
|
||||
const handleTestPress = useCallback(() => {
|
||||
dispatch(testNotification({ id }));
|
||||
}, [id, dispatch]);
|
||||
testProvider();
|
||||
}, [testProvider]);
|
||||
|
||||
const handleSavePress = useCallback(() => {
|
||||
dispatch(saveNotification({ id }));
|
||||
}, [id, dispatch]);
|
||||
saveProvider();
|
||||
}, [saveProvider]);
|
||||
|
||||
useEffect(() => {
|
||||
if (wasSaving && !isSaving && !saveError) {
|
||||
|
|
@ -112,65 +101,57 @@ function EditNotificationModalContent({
|
|||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
{isFetching ? <LoadingIndicator /> : null}
|
||||
<Form
|
||||
validationErrors={validationErrors}
|
||||
validationWarnings={validationWarnings}
|
||||
>
|
||||
{message ? (
|
||||
<Alert className={styles.message} kind={message.value.type}>
|
||||
{message.value.message}
|
||||
</Alert>
|
||||
) : null}
|
||||
|
||||
{!isFetching && !!error ? (
|
||||
<Alert kind={kinds.DANGER}>{translate('AddNotificationError')}</Alert>
|
||||
) : null}
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('Name')}</FormLabel>
|
||||
|
||||
{!isFetching && !error ? (
|
||||
<Form
|
||||
validationErrors={validationErrors}
|
||||
validationWarnings={validationWarnings}
|
||||
>
|
||||
{message ? (
|
||||
<Alert className={styles.message} kind={message.value.type}>
|
||||
{message.value.message}
|
||||
</Alert>
|
||||
) : null}
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('Name')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.TEXT}
|
||||
name="name"
|
||||
{...name}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<NotificationEventItems
|
||||
item={item}
|
||||
onInputChange={handleInputChange}
|
||||
<FormInputGroup
|
||||
type={inputTypes.TEXT}
|
||||
name="name"
|
||||
{...name}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('Tags')}</FormLabel>
|
||||
<NotificationEventItems
|
||||
item={item}
|
||||
onInputChange={handleInputChange}
|
||||
/>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.TAG}
|
||||
name="tags"
|
||||
helpText={translate('NotificationsTagsSeriesHelpText')}
|
||||
{...tags}
|
||||
onChange={handleInputChange}
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('Tags')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.TAG}
|
||||
name="tags"
|
||||
helpText={translate('NotificationsTagsSeriesHelpText')}
|
||||
{...tags}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
{fields.map((field) => {
|
||||
return (
|
||||
<ProviderFieldFormGroup
|
||||
key={field.name}
|
||||
{...field}
|
||||
advancedSettings={showAdvancedSettings}
|
||||
provider="notification"
|
||||
providerData={item}
|
||||
onChange={handleFieldChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
{fields.map((field) => {
|
||||
return (
|
||||
<ProviderFieldFormGroup
|
||||
key={field.name}
|
||||
{...field}
|
||||
advancedSettings={showAdvancedSettings}
|
||||
provider="notification"
|
||||
providerData={item}
|
||||
onChange={handleFieldChange}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Form>
|
||||
) : null}
|
||||
);
|
||||
})}
|
||||
</Form>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
import React, { useCallback, useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import Card from 'Components/Card';
|
||||
import Label from 'Components/Label';
|
||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
import TagList from 'Components/TagList';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import { deleteNotification } from 'Store/Actions/settingsActions';
|
||||
import { useTagList } from 'Tags/useTags';
|
||||
import NotificationModel from 'typings/Notification';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import { NotificationModel, useDeleteConnection } from '../useConnections';
|
||||
import EditNotificationModal from './EditNotificationModal';
|
||||
import styles from './Notification.css';
|
||||
|
||||
|
|
@ -43,8 +41,8 @@ function Notification({
|
|||
supportsOnManualInteractionRequired,
|
||||
tags,
|
||||
}: NotificationModel) {
|
||||
const dispatch = useDispatch();
|
||||
const tagList = useTagList();
|
||||
const { deleteConnection } = useDeleteConnection(id);
|
||||
|
||||
const [isEditNotificationModalOpen, setIsEditNotificationModalOpen] =
|
||||
useState(false);
|
||||
|
|
@ -69,8 +67,8 @@ function Notification({
|
|||
}, []);
|
||||
|
||||
const handleConfirmDeleteNotification = useCallback(() => {
|
||||
dispatch(deleteNotification({ id }));
|
||||
}, [id, dispatch]);
|
||||
deleteConnection();
|
||||
}, [deleteConnection]);
|
||||
|
||||
return (
|
||||
<Card
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@ import FormInputHelpText from 'Components/Form/FormInputHelpText';
|
|||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import { inputTypes } from 'Helpers/Props';
|
||||
import { CheckInputChanged } from 'typings/inputs';
|
||||
import Notification from 'typings/Notification';
|
||||
import { PendingSection } from 'typings/pending';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import { NotificationModel } from '../useConnections';
|
||||
import styles from './NotificationEventItems.css';
|
||||
|
||||
interface NotificationEventItemsProps {
|
||||
item: PendingSection<Notification>;
|
||||
item: PendingSection<NotificationModel>;
|
||||
onInputChange: (change: CheckInputChanged) => void;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,30 +1,24 @@
|
|||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { NotificationAppState } from 'App/State/SettingsAppState';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import Card from 'Components/Card';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import Icon from 'Components/Icon';
|
||||
import PageSectionContent from 'Components/Page/PageSectionContent';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import { fetchNotifications } from 'Store/Actions/settingsActions';
|
||||
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
||||
import NotificationModel from 'typings/Notification';
|
||||
import sortByProp from 'Utilities/Array/sortByProp';
|
||||
import { SelectedSchema } from 'Settings/useProviderSchema';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import { useConnections, useSortedConnections } from '../useConnections';
|
||||
import AddNotificationModal from './AddNotificationModal';
|
||||
import EditNotificationModal from './EditNotificationModal';
|
||||
import Notification from './Notification';
|
||||
import styles from './Notifications.css';
|
||||
|
||||
function Notifications() {
|
||||
const dispatch = useDispatch();
|
||||
const { error, isFetching, isFetched } = useConnections();
|
||||
const items = useSortedConnections();
|
||||
|
||||
const { error, isFetching, isPopulated, items } = useSelector(
|
||||
createSortedSectionSelector<NotificationModel, NotificationAppState>(
|
||||
'settings.notifications',
|
||||
sortByProp('name')
|
||||
)
|
||||
);
|
||||
const [selectedSchema, setSelectedSchema] = useState<
|
||||
SelectedSchema | undefined
|
||||
>(undefined);
|
||||
|
||||
const [isAddNotificationModalOpen, setIsAddNotificationModalOpen] =
|
||||
useState(false);
|
||||
|
|
@ -36,7 +30,8 @@ function Notifications() {
|
|||
setIsAddNotificationModalOpen(true);
|
||||
}, []);
|
||||
|
||||
const handleNotificationSelect = useCallback(() => {
|
||||
const handleNotificationSelect = useCallback((selected: SelectedSchema) => {
|
||||
setSelectedSchema(selected);
|
||||
setIsAddNotificationModalOpen(false);
|
||||
setIsEditNotificationModalOpen(true);
|
||||
}, []);
|
||||
|
|
@ -49,17 +44,13 @@ function Notifications() {
|
|||
setIsEditNotificationModalOpen(false);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchNotifications());
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<FieldSet legend={translate('Connections')}>
|
||||
<PageSectionContent
|
||||
errorMessage={translate('NotificationsLoadError')}
|
||||
error={error}
|
||||
isFetching={isFetching}
|
||||
isPopulated={isPopulated}
|
||||
isPopulated={isFetched}
|
||||
>
|
||||
<div className={styles.notifications}>
|
||||
{items.map((item) => (
|
||||
|
|
@ -84,6 +75,7 @@ function Notifications() {
|
|||
|
||||
<EditNotificationModal
|
||||
isOpen={isEditNotificationModalOpen}
|
||||
selectedSchema={selectedSchema}
|
||||
onModalClose={handleEditNotificationModalClose}
|
||||
/>
|
||||
</PageSectionContent>
|
||||
|
|
|
|||
133
frontend/src/Settings/Notifications/useConnections.ts
Normal file
133
frontend/src/Settings/Notifications/useConnections.ts
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
import { useMemo } from 'react';
|
||||
import {
|
||||
SelectedSchema,
|
||||
useProviderSchema,
|
||||
useSelectedSchema,
|
||||
} from 'Settings/useProviderSchema';
|
||||
import {
|
||||
useDeleteProvider,
|
||||
useManageProviderSettings,
|
||||
useProviderSettings,
|
||||
} from 'Settings/useProviderSettings';
|
||||
import Provider from 'typings/Provider';
|
||||
import { sortByProp } from 'Utilities/Array/sortByProp';
|
||||
|
||||
export interface NotificationModel extends Provider {
|
||||
enable: boolean;
|
||||
onGrab: boolean;
|
||||
onDownload: boolean;
|
||||
onUpgrade: boolean;
|
||||
onImportComplete: boolean;
|
||||
onRename: boolean;
|
||||
onSeriesAdd: boolean;
|
||||
onSeriesDelete: boolean;
|
||||
onEpisodeFileDelete: boolean;
|
||||
onEpisodeFileDeleteForUpgrade: boolean;
|
||||
onHealthIssue: boolean;
|
||||
includeHealthWarnings: boolean;
|
||||
onHealthRestored: boolean;
|
||||
onApplicationUpdate: boolean;
|
||||
onManualInteractionRequired: boolean;
|
||||
supportsOnGrab: boolean;
|
||||
supportsOnDownload: boolean;
|
||||
supportsOnUpgrade: boolean;
|
||||
supportsOnImportComplete: boolean;
|
||||
supportsOnRename: boolean;
|
||||
supportsOnSeriesAdd: boolean;
|
||||
supportsOnSeriesDelete: boolean;
|
||||
supportsOnEpisodeFileDelete: boolean;
|
||||
supportsOnEpisodeFileDeleteForUpgrade: boolean;
|
||||
supportsOnHealthIssue: boolean;
|
||||
supportsOnHealthRestored: boolean;
|
||||
supportsOnApplicationUpdate: boolean;
|
||||
supportsOnManualInteractionRequired: boolean;
|
||||
tags: number[];
|
||||
}
|
||||
|
||||
const PATH = '/connection';
|
||||
|
||||
export const useConnectionsWithIds = (ids: number[]) => {
|
||||
const allNotifications = useConnectionsData();
|
||||
|
||||
return allNotifications.filter((notification) =>
|
||||
ids.includes(notification.id)
|
||||
);
|
||||
};
|
||||
|
||||
export const useConnection = (id: number | undefined) => {
|
||||
const { data } = useConnections();
|
||||
|
||||
if (id === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return data.find((notification) => notification.id === id);
|
||||
};
|
||||
|
||||
export const useConnectionsData = () => {
|
||||
const { data } = useConnections();
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
export const useSortedConnections = () => {
|
||||
const { data } = useConnections();
|
||||
|
||||
return useMemo(() => data.sort(sortByProp('name')), [data]);
|
||||
};
|
||||
|
||||
export const useConnections = () => {
|
||||
return useProviderSettings<NotificationModel>({
|
||||
path: PATH,
|
||||
});
|
||||
};
|
||||
|
||||
export const useManageConnection = (
|
||||
id: number | undefined,
|
||||
selectedSchema?: SelectedSchema
|
||||
) => {
|
||||
const schema = useSelectedSchema<NotificationModel>(PATH, selectedSchema);
|
||||
|
||||
if (selectedSchema && !schema) {
|
||||
throw new Error('A selected schema is required to manage a notification');
|
||||
}
|
||||
|
||||
const manage = useManageProviderSettings<NotificationModel>(
|
||||
id,
|
||||
selectedSchema && schema
|
||||
? {
|
||||
...schema,
|
||||
name: schema.implementationName || '',
|
||||
onGrab: schema.supportsOnGrab || false,
|
||||
onDownload: schema.supportsOnDownload || false,
|
||||
onUpgrade: schema.supportsOnUpgrade || false,
|
||||
onImportComplete: schema.supportsOnImportComplete || false,
|
||||
onRename: schema.supportsOnRename || false,
|
||||
onSeriesAdd: schema.supportsOnSeriesAdd || false,
|
||||
onSeriesDelete: schema.supportsOnSeriesDelete || false,
|
||||
onEpisodeFileDelete: schema.supportsOnEpisodeFileDelete || false,
|
||||
onEpisodeFileDeleteForUpgrade:
|
||||
schema.supportsOnEpisodeFileDeleteForUpgrade || false,
|
||||
onApplicationUpdate: schema.supportsOnApplicationUpdate || false,
|
||||
onManualInteractionRequired:
|
||||
schema.supportsOnManualInteractionRequired || false,
|
||||
}
|
||||
: ({} as NotificationModel),
|
||||
PATH
|
||||
);
|
||||
|
||||
return manage;
|
||||
};
|
||||
|
||||
export const useDeleteConnection = (id: number) => {
|
||||
const result = useDeleteProvider<NotificationModel>(id, PATH);
|
||||
|
||||
return {
|
||||
...result,
|
||||
deleteConnection: result.deleteProvider,
|
||||
};
|
||||
};
|
||||
|
||||
export const useConnectionSchema = (enabled: boolean = true) => {
|
||||
return useProviderSchema<NotificationModel>(PATH, enabled);
|
||||
};
|
||||
|
|
@ -12,6 +12,7 @@ import ModalFooter from 'Components/Modal/ModalFooter';
|
|||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import useSeries from 'Series/useSeries';
|
||||
import { useConnectionsWithIds } from 'Settings/Notifications/useConnections';
|
||||
import { useReleaseProfilesWithIds } from 'Settings/Profiles/Release/useReleaseProfiles';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import TagDetailsDelayProfile from './TagDetailsDelayProfile';
|
||||
|
|
@ -96,14 +97,8 @@ function TagDetailsModalContent({
|
|||
)
|
||||
);
|
||||
|
||||
const notifications = useSelector(
|
||||
createMatchingItemSelector(
|
||||
notificationIds,
|
||||
(state: AppState) => state.settings.notifications.items
|
||||
)
|
||||
);
|
||||
|
||||
const releaseProfiles = useReleaseProfilesWithIds(releaseProfileIds);
|
||||
const notifications = useConnectionsWithIds(notificationIds);
|
||||
|
||||
const indexers = useSelector(
|
||||
createMatchingItemSelector(
|
||||
|
|
|
|||
|
|
@ -5,12 +5,13 @@ import Alert from 'Components/Alert';
|
|||
import FieldSet from 'Components/FieldSet';
|
||||
import PageSectionContent from 'Components/Page/PageSectionContent';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import { useConnections } from 'Settings/Notifications/useConnections';
|
||||
import { useReleaseProfiles } from 'Settings/Profiles/Release/useReleaseProfiles';
|
||||
import {
|
||||
fetchDelayProfiles,
|
||||
fetchDownloadClients,
|
||||
fetchImportLists,
|
||||
fetchIndexers,
|
||||
fetchNotifications,
|
||||
} from 'Store/Actions/settingsActions';
|
||||
import useTagDetails from 'Tags/useTagDetails';
|
||||
import useTags, { useSortedTagList } from 'Tags/useTags';
|
||||
|
|
@ -30,10 +31,12 @@ function Tags() {
|
|||
error: detailsError,
|
||||
} = useTagDetails();
|
||||
|
||||
useReleaseProfiles();
|
||||
useConnections();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchDelayProfiles());
|
||||
dispatch(fetchImportLists());
|
||||
dispatch(fetchNotifications());
|
||||
dispatch(fetchIndexers());
|
||||
dispatch(fetchDownloadClients());
|
||||
|
||||
|
|
|
|||
75
frontend/src/Settings/useProviderSchema.ts
Normal file
75
frontend/src/Settings/useProviderSchema.ts
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import { useMemo } from 'react';
|
||||
import ModelBase from 'App/ModelBase';
|
||||
import useApiQuery from 'Helpers/Hooks/useApiQuery';
|
||||
import Provider from 'typings/Provider';
|
||||
|
||||
type ProviderWithPresets<T> = T & {
|
||||
presets: T[];
|
||||
};
|
||||
|
||||
export interface SelectedSchema {
|
||||
implementation: string;
|
||||
implementationName: string;
|
||||
presetName?: string;
|
||||
}
|
||||
|
||||
export const useProviderSchema = <T extends ModelBase>(
|
||||
path: string,
|
||||
enabled: boolean = true
|
||||
) => {
|
||||
const { isFetching, isFetched, error, data } = useApiQuery<T[]>({
|
||||
path: `${path}/schema`,
|
||||
queryOptions: {
|
||||
enabled,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
isSchemaFetching: isFetching,
|
||||
isSchemaFetched: isFetched,
|
||||
schemaError: error,
|
||||
schema: data ?? ([] as T[]),
|
||||
};
|
||||
};
|
||||
|
||||
export const useSelectedSchema = <T extends Provider>(
|
||||
path: string,
|
||||
selectedSchema: SelectedSchema | undefined
|
||||
) => {
|
||||
const { schema } = useProviderSchema<T>(path, selectedSchema != null);
|
||||
|
||||
return useMemo(() => {
|
||||
if (!selectedSchema) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const selected = schema.find(
|
||||
(s: T) => s.implementation === selectedSchema.implementation
|
||||
);
|
||||
|
||||
if (!selected) {
|
||||
throw new Error(
|
||||
`Schema with implementation ${selectedSchema.implementation} not found`
|
||||
);
|
||||
}
|
||||
|
||||
if (selectedSchema.presetName == null) {
|
||||
return selected;
|
||||
}
|
||||
|
||||
const preset =
|
||||
'presets' in selected
|
||||
? (selected as ProviderWithPresets<T>).presets?.find(
|
||||
(p: T & { name: string }) => p.name === selectedSchema.presetName
|
||||
)
|
||||
: undefined;
|
||||
|
||||
if (!preset) {
|
||||
throw new Error(
|
||||
`Preset with name ${selectedSchema.presetName} not found for implementation ${selectedSchema.implementation}`
|
||||
);
|
||||
}
|
||||
|
||||
return preset;
|
||||
}, [schema, selectedSchema]);
|
||||
};
|
||||
|
|
@ -1,12 +1,37 @@
|
|||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import ModelBase from 'App/ModelBase';
|
||||
import useApiMutation from 'Helpers/Hooks/useApiMutation';
|
||||
import useApiQuery, { QueryOptions } from 'Helpers/Hooks/useApiQuery';
|
||||
import { usePendingChangesStore } from 'Helpers/Hooks/usePendingChangesStore';
|
||||
import { usePendingFieldsStore } from 'Helpers/Hooks/usePendingFieldsStore';
|
||||
import selectSettings from 'Store/Selectors/selectSettings';
|
||||
import { PendingSection } from 'typings/pending';
|
||||
import Provider from 'typings/Provider';
|
||||
import { ApiError } from 'Utilities/Fetch/fetchJson';
|
||||
|
||||
export const useProvider = <T extends ModelBase>(
|
||||
interface ManageProviderSettings<T extends ModelBase>
|
||||
extends Omit<ReturnType<typeof selectSettings<T>>, 'settings'> {
|
||||
item: PendingSection<T>;
|
||||
updateValue: <K extends keyof T>(key: K, value: T[K]) => void;
|
||||
saveProvider: () => void;
|
||||
isSaving: boolean;
|
||||
saveError: ApiError | null;
|
||||
testProvider: () => void;
|
||||
isTesting: boolean;
|
||||
updateFieldValue?: (fieldProperties: Record<string, unknown>) => void;
|
||||
}
|
||||
|
||||
const isProviderWithFields = (provider: unknown): provider is Provider => {
|
||||
return (
|
||||
typeof provider === 'object' &&
|
||||
provider !== null &&
|
||||
'fields' in provider &&
|
||||
Array.isArray((provider as Provider).fields)
|
||||
);
|
||||
};
|
||||
|
||||
export const useProviderWithDefault = <T extends ModelBase>(
|
||||
id: number | undefined,
|
||||
defaultProvider: T,
|
||||
path: string
|
||||
|
|
@ -42,7 +67,8 @@ export const useProviderSettings = <T extends ModelBase>(
|
|||
export const useSaveProviderSettings = <T extends ModelBase>(
|
||||
id: number,
|
||||
path: string,
|
||||
onSuccess?: () => void
|
||||
onSuccess?: (updatedSettings: T) => void,
|
||||
onError?: (error: ApiError) => void
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
|
@ -60,8 +86,9 @@ export const useSaveProviderSettings = <T extends ModelBase>(
|
|||
|
||||
return [...oldData, updatedSettings];
|
||||
});
|
||||
onSuccess?.();
|
||||
onSuccess?.(updatedSettings);
|
||||
},
|
||||
onError,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -72,12 +99,34 @@ export const useSaveProviderSettings = <T extends ModelBase>(
|
|||
};
|
||||
};
|
||||
|
||||
export const useTestProvider = <T extends ModelBase>(
|
||||
path: string,
|
||||
onSuccess?: () => void,
|
||||
onError?: (error: ApiError) => void
|
||||
) => {
|
||||
const { mutate, isPending, error } = useApiMutation<void, T>({
|
||||
path: `${path}/test`,
|
||||
method: 'POST',
|
||||
mutationOptions: {
|
||||
onSuccess,
|
||||
onError,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
test: mutate,
|
||||
isTesting: isPending,
|
||||
testError: error,
|
||||
};
|
||||
};
|
||||
|
||||
export const useManageProviderSettings = <T extends ModelBase>(
|
||||
id: number | undefined,
|
||||
defaultProvider: T,
|
||||
path: string
|
||||
) => {
|
||||
const provider = useProvider<T>(id, defaultProvider, path);
|
||||
): ManageProviderSettings<T> => {
|
||||
const provider = useProviderWithDefault<T>(id, defaultProvider, path);
|
||||
const [mutationError, setMutationError] = useState<ApiError | null>(null);
|
||||
|
||||
const {
|
||||
pendingChanges,
|
||||
|
|
@ -86,24 +135,111 @@ export const useManageProviderSettings = <T extends ModelBase>(
|
|||
clearPendingChanges,
|
||||
} = usePendingChangesStore<T>({});
|
||||
|
||||
const { save, isSaving, saveError } = useSaveProviderSettings<T>(
|
||||
const {
|
||||
pendingFields,
|
||||
setPendingFields,
|
||||
clearPendingFields,
|
||||
hasPendingFields,
|
||||
} = usePendingFieldsStore();
|
||||
|
||||
const handleSaveSuccess = useCallback(() => {
|
||||
setMutationError(null);
|
||||
clearPendingChanges();
|
||||
clearPendingFields();
|
||||
}, [clearPendingChanges, clearPendingFields]);
|
||||
|
||||
const handleTestSuccess = useCallback(() => {
|
||||
setMutationError(null);
|
||||
}, []);
|
||||
|
||||
const { save, isSaving } = useSaveProviderSettings<T>(
|
||||
provider.id,
|
||||
path,
|
||||
clearPendingChanges
|
||||
handleSaveSuccess,
|
||||
setMutationError
|
||||
);
|
||||
|
||||
const { test, isTesting } = useTestProvider<T>(
|
||||
path,
|
||||
handleTestSuccess,
|
||||
setMutationError
|
||||
);
|
||||
|
||||
const { settings: item, ...settings } = useMemo(() => {
|
||||
return selectSettings<T>(provider, pendingChanges, saveError);
|
||||
}, [provider, pendingChanges, saveError]);
|
||||
// Create a combined pending changes object that includes fields
|
||||
const combinedPendingChanges = hasPendingFields
|
||||
? {
|
||||
...pendingChanges,
|
||||
fields: Object.fromEntries(pendingFields),
|
||||
}
|
||||
: pendingChanges;
|
||||
|
||||
return selectSettings<T>(provider, combinedPendingChanges, mutationError);
|
||||
}, [
|
||||
provider,
|
||||
pendingChanges,
|
||||
pendingFields,
|
||||
hasPendingFields,
|
||||
mutationError,
|
||||
]);
|
||||
|
||||
const saveProvider = useCallback(() => {
|
||||
const updatedSettings = {
|
||||
let updatedSettings: T = {
|
||||
...provider,
|
||||
...pendingChanges,
|
||||
};
|
||||
|
||||
// If there are pending field changes and the provider has fields
|
||||
if (isProviderWithFields(provider)) {
|
||||
const fields = provider.fields.map((field) => {
|
||||
if (pendingFields.has(field.name)) {
|
||||
return {
|
||||
name: field.name,
|
||||
value: pendingFields.get(field.name),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
name: field.name,
|
||||
value: field.value,
|
||||
};
|
||||
});
|
||||
|
||||
updatedSettings = {
|
||||
...updatedSettings,
|
||||
fields,
|
||||
} as T;
|
||||
}
|
||||
|
||||
save(updatedSettings);
|
||||
}, [provider, pendingChanges, save]);
|
||||
}, [provider, pendingChanges, pendingFields, save]);
|
||||
|
||||
const testProvider = useCallback(() => {
|
||||
let updatedSettings: T = {
|
||||
...provider,
|
||||
...pendingChanges,
|
||||
};
|
||||
|
||||
// If there are pending field changes and the provider has fields
|
||||
if (isProviderWithFields(provider)) {
|
||||
const fields = provider.fields.map((field) => {
|
||||
if (pendingFields.has(field.name)) {
|
||||
return {
|
||||
...field,
|
||||
value: pendingFields.get(field.name),
|
||||
};
|
||||
}
|
||||
return field;
|
||||
});
|
||||
|
||||
updatedSettings = {
|
||||
...updatedSettings,
|
||||
fields,
|
||||
} as T;
|
||||
}
|
||||
|
||||
test(updatedSettings);
|
||||
}, [provider, pendingChanges, pendingFields, test]);
|
||||
|
||||
const updateValue = useCallback(
|
||||
<K extends keyof T>(key: K, value: T[K]) => {
|
||||
|
|
@ -116,14 +252,54 @@ export const useManageProviderSettings = <T extends ModelBase>(
|
|||
[provider, setPendingChange, unsetPendingChange]
|
||||
);
|
||||
|
||||
return {
|
||||
const hasFields = useMemo(() => {
|
||||
return 'fields' in provider && Array.isArray(provider.fields);
|
||||
}, [provider]);
|
||||
|
||||
const updateFieldValue = useCallback(
|
||||
(fieldProperties: Record<string, unknown>) => {
|
||||
if (!isProviderWithFields(provider)) {
|
||||
throw new Error('updateFieldValue called on provider without fields');
|
||||
}
|
||||
|
||||
const providerFields = provider.fields;
|
||||
const currentFields = pendingFields;
|
||||
const newFields = { ...currentFields, ...fieldProperties };
|
||||
|
||||
// Check if the new fields are different from the provider's current fields
|
||||
const hasChanges = Object.entries(newFields).some(([key, value]) => {
|
||||
const currentField = providerFields.find((f) => f.name === key);
|
||||
return currentField?.value !== value;
|
||||
});
|
||||
|
||||
if (hasChanges) {
|
||||
setPendingFields(newFields);
|
||||
} else {
|
||||
clearPendingFields();
|
||||
}
|
||||
},
|
||||
[pendingFields, provider, setPendingFields, clearPendingFields]
|
||||
);
|
||||
|
||||
const baseReturn = {
|
||||
...settings,
|
||||
item,
|
||||
updateValue,
|
||||
saveProvider,
|
||||
isSaving,
|
||||
saveError,
|
||||
saveError: mutationError,
|
||||
testProvider,
|
||||
isTesting,
|
||||
};
|
||||
|
||||
if (hasFields) {
|
||||
return {
|
||||
...baseReturn,
|
||||
updateFieldValue,
|
||||
};
|
||||
}
|
||||
|
||||
return baseReturn;
|
||||
};
|
||||
|
||||
export const useDeleteProvider = <T extends ModelBase>(
|
||||
|
|
|
|||
|
|
@ -1,133 +0,0 @@
|
|||
import { createAction } from 'redux-actions';
|
||||
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
|
||||
import createFetchSchemaHandler from 'Store/Actions/Creators/createFetchSchemaHandler';
|
||||
import createRemoveItemHandler from 'Store/Actions/Creators/createRemoveItemHandler';
|
||||
import createSaveProviderHandler, { createCancelSaveProviderHandler } from 'Store/Actions/Creators/createSaveProviderHandler';
|
||||
import createTestProviderHandler, { createCancelTestProviderHandler } from 'Store/Actions/Creators/createTestProviderHandler';
|
||||
import createSetProviderFieldValueReducer from 'Store/Actions/Creators/Reducers/createSetProviderFieldValueReducer';
|
||||
import createSetProviderFieldValuesReducer from 'Store/Actions/Creators/Reducers/createSetProviderFieldValuesReducer';
|
||||
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
|
||||
import { createThunk } from 'Store/thunks';
|
||||
import selectProviderSchema from 'Utilities/State/selectProviderSchema';
|
||||
|
||||
//
|
||||
// Variables
|
||||
|
||||
const section = 'settings.notifications';
|
||||
|
||||
//
|
||||
// Actions Types
|
||||
|
||||
export const FETCH_NOTIFICATIONS = 'settings/notifications/fetchNotifications';
|
||||
export const FETCH_NOTIFICATION_SCHEMA = 'settings/notifications/fetchNotificationSchema';
|
||||
export const SELECT_NOTIFICATION_SCHEMA = 'settings/notifications/selectNotificationSchema';
|
||||
export const SET_NOTIFICATION_VALUE = 'settings/notifications/setNotificationValue';
|
||||
export const SET_NOTIFICATION_FIELD_VALUE = 'settings/notifications/setNotificationFieldValue';
|
||||
export const SET_NOTIFICATION_FIELD_VALUES = 'settings/notifications/setNotificationFieldValues';
|
||||
export const SAVE_NOTIFICATION = 'settings/notifications/saveNotification';
|
||||
export const CANCEL_SAVE_NOTIFICATION = 'settings/notifications/cancelSaveNotification';
|
||||
export const DELETE_NOTIFICATION = 'settings/notifications/deleteNotification';
|
||||
export const TEST_NOTIFICATION = 'settings/notifications/testNotification';
|
||||
export const CANCEL_TEST_NOTIFICATION = 'settings/notifications/cancelTestNotification';
|
||||
|
||||
//
|
||||
// Action Creators
|
||||
|
||||
export const fetchNotifications = createThunk(FETCH_NOTIFICATIONS);
|
||||
export const fetchNotificationSchema = createThunk(FETCH_NOTIFICATION_SCHEMA);
|
||||
export const selectNotificationSchema = createAction(SELECT_NOTIFICATION_SCHEMA);
|
||||
|
||||
export const saveNotification = createThunk(SAVE_NOTIFICATION);
|
||||
export const cancelSaveNotification = createThunk(CANCEL_SAVE_NOTIFICATION);
|
||||
export const deleteNotification = createThunk(DELETE_NOTIFICATION);
|
||||
export const testNotification = createThunk(TEST_NOTIFICATION);
|
||||
export const cancelTestNotification = createThunk(CANCEL_TEST_NOTIFICATION);
|
||||
|
||||
export const setNotificationValue = createAction(SET_NOTIFICATION_VALUE, (payload) => {
|
||||
return {
|
||||
section,
|
||||
...payload
|
||||
};
|
||||
});
|
||||
|
||||
export const setNotificationFieldValue = createAction(SET_NOTIFICATION_FIELD_VALUE, (payload) => {
|
||||
return {
|
||||
section,
|
||||
...payload
|
||||
};
|
||||
});
|
||||
|
||||
export const setNotificationFieldValues = createAction(SET_NOTIFICATION_FIELD_VALUES, (payload) => {
|
||||
return {
|
||||
section,
|
||||
...payload
|
||||
};
|
||||
});
|
||||
|
||||
//
|
||||
// Details
|
||||
|
||||
export default {
|
||||
|
||||
//
|
||||
// State
|
||||
|
||||
defaultState: {
|
||||
isFetching: false,
|
||||
isPopulated: false,
|
||||
error: null,
|
||||
isSchemaFetching: false,
|
||||
isSchemaPopulated: false,
|
||||
schemaError: null,
|
||||
schema: [],
|
||||
selectedSchema: {},
|
||||
isSaving: false,
|
||||
saveError: null,
|
||||
isTesting: false,
|
||||
items: [],
|
||||
pendingChanges: {}
|
||||
},
|
||||
|
||||
//
|
||||
// Action Handlers
|
||||
|
||||
actionHandlers: {
|
||||
[FETCH_NOTIFICATIONS]: createFetchHandler(section, '/notification'),
|
||||
[FETCH_NOTIFICATION_SCHEMA]: createFetchSchemaHandler(section, '/notification/schema'),
|
||||
|
||||
[SAVE_NOTIFICATION]: createSaveProviderHandler(section, '/notification'),
|
||||
[CANCEL_SAVE_NOTIFICATION]: createCancelSaveProviderHandler(section),
|
||||
[DELETE_NOTIFICATION]: createRemoveItemHandler(section, '/notification'),
|
||||
[TEST_NOTIFICATION]: createTestProviderHandler(section, '/notification'),
|
||||
[CANCEL_TEST_NOTIFICATION]: createCancelTestProviderHandler(section)
|
||||
},
|
||||
|
||||
//
|
||||
// Reducers
|
||||
|
||||
reducers: {
|
||||
[SET_NOTIFICATION_VALUE]: createSetSettingValueReducer(section),
|
||||
[SET_NOTIFICATION_FIELD_VALUE]: createSetProviderFieldValueReducer(section),
|
||||
[SET_NOTIFICATION_FIELD_VALUES]: createSetProviderFieldValuesReducer(section),
|
||||
|
||||
[SELECT_NOTIFICATION_SCHEMA]: (state, { payload }) => {
|
||||
return selectProviderSchema(state, section, payload, (selectedSchema) => {
|
||||
selectedSchema.name = selectedSchema.implementationName;
|
||||
selectedSchema.onGrab = selectedSchema.supportsOnGrab;
|
||||
selectedSchema.onDownload = selectedSchema.supportsOnDownload;
|
||||
selectedSchema.onUpgrade = selectedSchema.supportsOnUpgrade;
|
||||
selectedSchema.onImportComplete = selectedSchema.supportsOnImportComplete;
|
||||
selectedSchema.onRename = selectedSchema.supportsOnRename;
|
||||
selectedSchema.onSeriesAdd = selectedSchema.supportsOnSeriesAdd;
|
||||
selectedSchema.onSeriesDelete = selectedSchema.supportsOnSeriesDelete;
|
||||
selectedSchema.onEpisodeFileDelete = selectedSchema.supportsOnEpisodeFileDelete;
|
||||
selectedSchema.onEpisodeFileDeleteForUpgrade = selectedSchema.supportsOnEpisodeFileDeleteForUpgrade;
|
||||
selectedSchema.onApplicationUpdate = selectedSchema.supportsOnApplicationUpdate;
|
||||
selectedSchema.onManualInteractionRequired = selectedSchema.supportsOnManualInteractionRequired;
|
||||
|
||||
return selectedSchema;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
|
@ -19,7 +19,6 @@ import mediaManagement from './Settings/mediaManagement';
|
|||
import metadata from './Settings/metadata';
|
||||
import naming from './Settings/naming';
|
||||
import namingExamples from './Settings/namingExamples';
|
||||
import notifications from './Settings/notifications';
|
||||
|
||||
export * from './Settings/autoTaggingSpecifications';
|
||||
export * from './Settings/autoTaggings';
|
||||
|
|
@ -40,7 +39,6 @@ export * from './Settings/mediaManagement';
|
|||
export * from './Settings/metadata';
|
||||
export * from './Settings/naming';
|
||||
export * from './Settings/namingExamples';
|
||||
export * from './Settings/notifications';
|
||||
|
||||
//
|
||||
// Variables
|
||||
|
|
@ -70,8 +68,7 @@ export const defaultState = {
|
|||
mediaManagement: mediaManagement.defaultState,
|
||||
metadata: metadata.defaultState,
|
||||
naming: naming.defaultState,
|
||||
namingExamples: namingExamples.defaultState,
|
||||
notifications: notifications.defaultState
|
||||
namingExamples: namingExamples.defaultState
|
||||
};
|
||||
|
||||
export const persistState = [
|
||||
|
|
@ -100,8 +97,7 @@ export const actionHandlers = handleThunks({
|
|||
...mediaManagement.actionHandlers,
|
||||
...metadata.actionHandlers,
|
||||
...naming.actionHandlers,
|
||||
...namingExamples.actionHandlers,
|
||||
...notifications.actionHandlers
|
||||
...namingExamples.actionHandlers
|
||||
});
|
||||
|
||||
//
|
||||
|
|
@ -126,7 +122,6 @@ export const reducers = createHandleActions({
|
|||
...mediaManagement.reducers,
|
||||
...metadata.reducers,
|
||||
...naming.reducers,
|
||||
...namingExamples.reducers,
|
||||
...notifications.reducers
|
||||
...namingExamples.reducers
|
||||
|
||||
}, defaultState, section);
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
import Provider from './Provider';
|
||||
|
||||
interface Notification extends Provider {
|
||||
enable: boolean;
|
||||
onGrab: boolean;
|
||||
onDownload: boolean;
|
||||
onUpgrade: boolean;
|
||||
onImportComplete: boolean;
|
||||
onRename: boolean;
|
||||
onSeriesAdd: boolean;
|
||||
onSeriesDelete: boolean;
|
||||
onEpisodeFileDelete: boolean;
|
||||
onEpisodeFileDeleteForUpgrade: boolean;
|
||||
onHealthIssue: boolean;
|
||||
includeHealthWarnings: boolean;
|
||||
onHealthRestored: boolean;
|
||||
onApplicationUpdate: boolean;
|
||||
onManualInteractionRequired: boolean;
|
||||
supportsOnGrab: boolean;
|
||||
supportsOnDownload: boolean;
|
||||
supportsOnUpgrade: boolean;
|
||||
supportsOnImportComplete: boolean;
|
||||
supportsOnRename: boolean;
|
||||
supportsOnSeriesAdd: boolean;
|
||||
supportsOnSeriesDelete: boolean;
|
||||
supportsOnEpisodeFileDelete: boolean;
|
||||
supportsOnEpisodeFileDeleteForUpgrade: boolean;
|
||||
supportsOnHealthIssue: boolean;
|
||||
supportsOnHealthRestored: boolean;
|
||||
supportsOnApplicationUpdate: boolean;
|
||||
supportsOnManualInteractionRequired: boolean;
|
||||
tags: number[];
|
||||
}
|
||||
|
||||
export default Notification;
|
||||
Loading…
Reference in a new issue