mirror of
https://github.com/Radarr/Radarr.git
synced 2024-11-19 17:32:38 +01:00
Convert Import List Options to TypeScript
Co-authored-by: The Dark <12370876+CheAle14@users.noreply.github.com>
This commit is contained in:
parent
d25bcdb043
commit
1f5a84d202
@ -7,6 +7,7 @@ import AppSectionState, {
|
|||||||
import Language from 'Language/Language';
|
import Language from 'Language/Language';
|
||||||
import DownloadClient from 'typings/DownloadClient';
|
import DownloadClient from 'typings/DownloadClient';
|
||||||
import ImportList from 'typings/ImportList';
|
import ImportList from 'typings/ImportList';
|
||||||
|
import ImportListOptionsSettings from 'typings/ImportListOptionsSettings';
|
||||||
import Indexer from 'typings/Indexer';
|
import Indexer from 'typings/Indexer';
|
||||||
import IndexerFlag from 'typings/IndexerFlag';
|
import IndexerFlag from 'typings/IndexerFlag';
|
||||||
import Notification from 'typings/Notification';
|
import Notification from 'typings/Notification';
|
||||||
@ -36,12 +37,18 @@ export interface QualityProfilesAppState
|
|||||||
extends AppSectionState<QualityProfile>,
|
extends AppSectionState<QualityProfile>,
|
||||||
AppSectionSchemaState<QualityProfile> {}
|
AppSectionSchemaState<QualityProfile> {}
|
||||||
|
|
||||||
|
export interface ImportListOptionsSettingsAppState
|
||||||
|
extends AppSectionItemState<ImportListOptionsSettings>,
|
||||||
|
AppSectionSaveState {}
|
||||||
|
|
||||||
export type IndexerFlagSettingsAppState = AppSectionState<IndexerFlag>;
|
export type IndexerFlagSettingsAppState = AppSectionState<IndexerFlag>;
|
||||||
export type LanguageSettingsAppState = AppSectionState<Language>;
|
export type LanguageSettingsAppState = AppSectionState<Language>;
|
||||||
export type UiSettingsAppState = AppSectionItemState<UiSettings>;
|
export type UiSettingsAppState = AppSectionItemState<UiSettings>;
|
||||||
|
|
||||||
interface SettingsAppState {
|
interface SettingsAppState {
|
||||||
|
advancedSettings: boolean;
|
||||||
downloadClients: DownloadClientAppState;
|
downloadClients: DownloadClientAppState;
|
||||||
|
importListOptions: ImportListOptionsSettingsAppState;
|
||||||
importLists: ImportListAppState;
|
importLists: ImportListAppState;
|
||||||
indexerFlags: IndexerFlagSettingsAppState;
|
indexerFlags: IndexerFlagSettingsAppState;
|
||||||
indexers: IndexerAppState;
|
indexers: IndexerAppState;
|
||||||
|
@ -10,7 +10,7 @@ import translate from 'Utilities/String/translate';
|
|||||||
import ImportListExclusionsConnector from './ImportListExclusions/ImportListExclusionsConnector';
|
import ImportListExclusionsConnector from './ImportListExclusions/ImportListExclusionsConnector';
|
||||||
import ImportListsConnector from './ImportLists/ImportListsConnector';
|
import ImportListsConnector from './ImportLists/ImportListsConnector';
|
||||||
import ManageImportListsModal from './ImportLists/Manage/ManageImportListsModal';
|
import ManageImportListsModal from './ImportLists/Manage/ManageImportListsModal';
|
||||||
import ImportListOptionsConnector from './Options/ImportListOptionsConnector';
|
import ImportListOptions from './Options/ImportListOptions';
|
||||||
|
|
||||||
class ImportListSettings extends Component {
|
class ImportListSettings extends Component {
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ class ImportListSettings extends Component {
|
|||||||
//
|
//
|
||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
onChildMounted = (saveCallback) => {
|
setChildSave = (saveCallback) => {
|
||||||
this._saveCallback = saveCallback;
|
this._saveCallback = saveCallback;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -54,8 +54,8 @@ class ImportListSettings extends Component {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Render
|
|
||||||
//
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
@ -98,8 +98,8 @@ class ImportListSettings extends Component {
|
|||||||
<PageContentBody>
|
<PageContentBody>
|
||||||
<ImportListsConnector />
|
<ImportListsConnector />
|
||||||
|
|
||||||
<ImportListOptionsConnector
|
<ImportListOptions
|
||||||
onChildMounted={this.onChildMounted}
|
setChildSave={this.setChildSave}
|
||||||
onChildStateChange={this.onChildStateChange}
|
onChildStateChange={this.onChildStateChange}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -109,7 +109,6 @@ class ImportListSettings extends Component {
|
|||||||
isOpen={isManageImportListsOpen}
|
isOpen={isManageImportListsOpen}
|
||||||
onModalClose={this.onManageImportListsModalClose}
|
onModalClose={this.onManageImportListsModalClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</PageContentBody>
|
</PageContentBody>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
);
|
);
|
||||||
|
@ -1,80 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import Alert from 'Components/Alert';
|
|
||||||
import FieldSet from 'Components/FieldSet';
|
|
||||||
import Form from 'Components/Form/Form';
|
|
||||||
import FormGroup from 'Components/Form/FormGroup';
|
|
||||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
|
||||||
import FormLabel from 'Components/Form/FormLabel';
|
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
|
||||||
import { inputTypes, kinds } from 'Helpers/Props';
|
|
||||||
import translate from 'Utilities/String/translate';
|
|
||||||
|
|
||||||
function ImportListOptions(props) {
|
|
||||||
const {
|
|
||||||
advancedSettings,
|
|
||||||
isFetching,
|
|
||||||
error,
|
|
||||||
settings,
|
|
||||||
hasSettings,
|
|
||||||
onInputChange
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const cleanLibraryLevelOptions = [
|
|
||||||
{ key: 'disabled', value: translate('Disabled') },
|
|
||||||
{ key: 'logOnly', value: translate('LogOnly') },
|
|
||||||
{ key: 'keepAndUnmonitor', value: translate('KeepAndUnmonitorMovie') },
|
|
||||||
{ key: 'removeAndKeep', value: translate('RemoveMovieAndKeepFiles') },
|
|
||||||
{ key: 'removeAndDelete', value: translate('RemoveMovieAndDeleteFiles') }
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
advancedSettings &&
|
|
||||||
<FieldSet legend={translate('Options')}>
|
|
||||||
{
|
|
||||||
isFetching &&
|
|
||||||
<LoadingIndicator />
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
!isFetching && error &&
|
|
||||||
<Alert kind={kinds.DANGER}>
|
|
||||||
{translate('ListOptionsLoadError')}
|
|
||||||
</Alert>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
hasSettings && !isFetching && !error &&
|
|
||||||
<Form>
|
|
||||||
<FormGroup
|
|
||||||
advancedSettings={advancedSettings}
|
|
||||||
isAdvanced={true}
|
|
||||||
>
|
|
||||||
<FormLabel>{translate('CleanLibraryLevel')}</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.SELECT}
|
|
||||||
name="listSyncLevel"
|
|
||||||
values={cleanLibraryLevelOptions}
|
|
||||||
helpText={translate('ListSyncLevelHelpText')}
|
|
||||||
helpTextWarning={settings.listSyncLevel.value === 'removeAndDelete' ? translate('ListSyncLevelHelpTextWarning') : undefined}
|
|
||||||
onChange={onInputChange}
|
|
||||||
{...settings.listSyncLevel}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
</Form>
|
|
||||||
}
|
|
||||||
</FieldSet>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImportListOptions.propTypes = {
|
|
||||||
advancedSettings: PropTypes.bool.isRequired,
|
|
||||||
isFetching: PropTypes.bool.isRequired,
|
|
||||||
error: PropTypes.object,
|
|
||||||
settings: PropTypes.object.isRequired,
|
|
||||||
hasSettings: PropTypes.bool.isRequired,
|
|
||||||
onInputChange: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ImportListOptions;
|
|
130
frontend/src/Settings/ImportLists/Options/ImportListOptions.tsx
Normal file
130
frontend/src/Settings/ImportLists/Options/ImportListOptions.tsx
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
import React, { useCallback, useEffect } from 'react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import AppState from 'App/State/AppState';
|
||||||
|
import Alert from 'Components/Alert';
|
||||||
|
import FieldSet from 'Components/FieldSet';
|
||||||
|
import Form from 'Components/Form/Form';
|
||||||
|
import FormGroup from 'Components/Form/FormGroup';
|
||||||
|
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||||
|
import FormLabel from 'Components/Form/FormLabel';
|
||||||
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
|
import { inputTypes, kinds } from 'Helpers/Props';
|
||||||
|
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
||||||
|
import {
|
||||||
|
fetchImportListOptions,
|
||||||
|
saveImportListOptions,
|
||||||
|
setImportListOptionsValue,
|
||||||
|
} from 'Store/Actions/settingsActions';
|
||||||
|
import createSettingsSectionSelector from 'Store/Selectors/createSettingsSectionSelector';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
|
||||||
|
const SECTION = 'importListOptions';
|
||||||
|
const cleanLibraryLevelOptions = [
|
||||||
|
{ key: 'disabled', value: () => translate('Disabled') },
|
||||||
|
{ key: 'logOnly', value: () => translate('LogOnly') },
|
||||||
|
{ key: 'keepAndUnmonitor', value: () => translate('KeepAndUnmonitorMovie') },
|
||||||
|
{ key: 'removeAndKeep', value: () => translate('RemoveMovieAndKeepFiles') },
|
||||||
|
{
|
||||||
|
key: 'removeAndDelete',
|
||||||
|
value: () => translate('RemoveMovieAndDeleteFiles'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function createImportListOptionsSelector() {
|
||||||
|
return createSelector(
|
||||||
|
(state: AppState) => state.settings.advancedSettings,
|
||||||
|
createSettingsSectionSelector(SECTION),
|
||||||
|
(advancedSettings, sectionSettings) => {
|
||||||
|
return {
|
||||||
|
advancedSettings,
|
||||||
|
save: sectionSettings.isSaving,
|
||||||
|
...sectionSettings,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportListOptionsPageProps {
|
||||||
|
setChildSave(saveCallback: () => void): void;
|
||||||
|
onChildStateChange(payload: unknown): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ImportListOptions(props: ImportListOptionsPageProps) {
|
||||||
|
const { setChildSave, onChildStateChange } = props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
isSaving,
|
||||||
|
hasPendingChanges,
|
||||||
|
advancedSettings,
|
||||||
|
isFetching,
|
||||||
|
error,
|
||||||
|
settings,
|
||||||
|
hasSettings,
|
||||||
|
} = useSelector(createImportListOptionsSelector());
|
||||||
|
|
||||||
|
const { listSyncLevel } = settings;
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const onInputChange = useCallback(
|
||||||
|
({ name, value }: { name: string; value: unknown }) => {
|
||||||
|
// @ts-expect-error 'setImportListOptionsValue' isn't typed yet
|
||||||
|
dispatch(setImportListOptionsValue({ name, value }));
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(fetchImportListOptions());
|
||||||
|
setChildSave(() => dispatch(saveImportListOptions()));
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
dispatch(clearPendingChanges({ section: SECTION }));
|
||||||
|
};
|
||||||
|
}, [dispatch, setChildSave]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onChildStateChange({
|
||||||
|
isSaving,
|
||||||
|
hasPendingChanges,
|
||||||
|
});
|
||||||
|
}, [onChildStateChange, isSaving, hasPendingChanges]);
|
||||||
|
|
||||||
|
const translatedLevelOptions = cleanLibraryLevelOptions.map(
|
||||||
|
({ key, value }) => {
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
value: value(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return advancedSettings ? (
|
||||||
|
<FieldSet legend={translate('Options')}>
|
||||||
|
{isFetching ? <LoadingIndicator /> : null}
|
||||||
|
|
||||||
|
{!isFetching && error ? (
|
||||||
|
<Alert kind={kinds.DANGER}>{translate('ListOptionsLoadError')}</Alert>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{hasSettings && !isFetching && !error ? (
|
||||||
|
<Form>
|
||||||
|
<FormGroup advancedSettings={advancedSettings} isAdvanced={true}>
|
||||||
|
<FormLabel>{translate('CleanLibraryLevel')}</FormLabel>
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.SELECT}
|
||||||
|
name="listSyncLevel"
|
||||||
|
values={translatedLevelOptions}
|
||||||
|
helpText={translate('ListSyncLevelHelpText')}
|
||||||
|
onChange={onInputChange}
|
||||||
|
{...listSyncLevel}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</Form>
|
||||||
|
) : null}
|
||||||
|
</FieldSet>
|
||||||
|
) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ImportListOptions;
|
@ -1,101 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
|
||||||
import { fetchImportListOptions, saveImportListOptions, setImportListOptionsValue } from 'Store/Actions/settingsActions';
|
|
||||||
import createSettingsSectionSelector from 'Store/Selectors/createSettingsSectionSelector';
|
|
||||||
import ImportListOptions from './ImportListOptions';
|
|
||||||
|
|
||||||
const SECTION = 'importListOptions';
|
|
||||||
|
|
||||||
function createMapStateToProps() {
|
|
||||||
return createSelector(
|
|
||||||
(state) => state.settings.advancedSettings,
|
|
||||||
createSettingsSectionSelector(SECTION),
|
|
||||||
(advancedSettings, sectionSettings) => {
|
|
||||||
return {
|
|
||||||
advancedSettings,
|
|
||||||
...sectionSettings
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
|
||||||
dispatchFetchImportListOptions: fetchImportListOptions,
|
|
||||||
dispatchSetImportListOptionsValue: setImportListOptionsValue,
|
|
||||||
dispatchSaveImportListOptions: saveImportListOptions,
|
|
||||||
dispatchClearPendingChanges: clearPendingChanges
|
|
||||||
};
|
|
||||||
|
|
||||||
class ImportListOptionsConnector extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Lifecycle
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
const {
|
|
||||||
dispatchFetchImportListOptions,
|
|
||||||
dispatchSaveImportListOptions,
|
|
||||||
onChildMounted
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
dispatchFetchImportListOptions();
|
|
||||||
onChildMounted(dispatchSaveImportListOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
|
||||||
const {
|
|
||||||
hasPendingChanges,
|
|
||||||
isSaving,
|
|
||||||
onChildStateChange
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
if (
|
|
||||||
prevProps.isSaving !== isSaving ||
|
|
||||||
prevProps.hasPendingChanges !== hasPendingChanges
|
|
||||||
) {
|
|
||||||
onChildStateChange({
|
|
||||||
isSaving,
|
|
||||||
hasPendingChanges
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.props.dispatchClearPendingChanges({ section: SECTION });
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onInputChange = ({ name, value }) => {
|
|
||||||
this.props.dispatchSetImportListOptionsValue({ name, value });
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<ImportListOptions
|
|
||||||
onInputChange={this.onInputChange}
|
|
||||||
{...this.props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImportListOptionsConnector.propTypes = {
|
|
||||||
isSaving: PropTypes.bool.isRequired,
|
|
||||||
hasPendingChanges: PropTypes.bool.isRequired,
|
|
||||||
dispatchFetchImportListOptions: PropTypes.func.isRequired,
|
|
||||||
dispatchSetImportListOptionsValue: PropTypes.func.isRequired,
|
|
||||||
dispatchSaveImportListOptions: PropTypes.func.isRequired,
|
|
||||||
dispatchClearPendingChanges: PropTypes.func.isRequired,
|
|
||||||
onChildMounted: PropTypes.func.isRequired,
|
|
||||||
onChildStateChange: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(createMapStateToProps, mapDispatchToProps)(ImportListOptionsConnector);
|
|
@ -1,32 +0,0 @@
|
|||||||
import { createSelector } from 'reselect';
|
|
||||||
import selectSettings from 'Store/Selectors/selectSettings';
|
|
||||||
|
|
||||||
function createSettingsSectionSelector(section) {
|
|
||||||
return createSelector(
|
|
||||||
(state) => state.settings[section],
|
|
||||||
(sectionSettings) => {
|
|
||||||
const {
|
|
||||||
isFetching,
|
|
||||||
isPopulated,
|
|
||||||
error,
|
|
||||||
item,
|
|
||||||
pendingChanges,
|
|
||||||
isSaving,
|
|
||||||
saveError
|
|
||||||
} = sectionSettings;
|
|
||||||
|
|
||||||
const settings = selectSettings(item, pendingChanges, saveError);
|
|
||||||
|
|
||||||
return {
|
|
||||||
isFetching,
|
|
||||||
isPopulated,
|
|
||||||
error,
|
|
||||||
isSaving,
|
|
||||||
saveError,
|
|
||||||
...settings
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default createSettingsSectionSelector;
|
|
@ -0,0 +1,49 @@
|
|||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import AppSectionState, {
|
||||||
|
AppSectionItemState,
|
||||||
|
} from 'App/State/AppSectionState';
|
||||||
|
import AppState from 'App/State/AppState';
|
||||||
|
import selectSettings from 'Store/Selectors/selectSettings';
|
||||||
|
import { PendingSection } from 'typings/pending';
|
||||||
|
|
||||||
|
type SettingNames = keyof Omit<AppState['settings'], 'advancedSettings'>;
|
||||||
|
type GetSectionState<Name extends SettingNames> = AppState['settings'][Name];
|
||||||
|
type GetSettingsSectionItemType<Name extends SettingNames> =
|
||||||
|
GetSectionState<Name> extends AppSectionItemState<infer R>
|
||||||
|
? R
|
||||||
|
: GetSectionState<Name> extends AppSectionState<infer R>
|
||||||
|
? R
|
||||||
|
: never;
|
||||||
|
|
||||||
|
type AppStateWithPending<Name extends SettingNames> = {
|
||||||
|
item?: GetSettingsSectionItemType<Name>;
|
||||||
|
pendingChanges?: Partial<GetSettingsSectionItemType<Name>>;
|
||||||
|
saveError?: Error;
|
||||||
|
} & GetSectionState<Name>;
|
||||||
|
|
||||||
|
function createSettingsSectionSelector<Name extends SettingNames>(
|
||||||
|
section: Name
|
||||||
|
) {
|
||||||
|
return createSelector(
|
||||||
|
(state: AppState) => state.settings[section],
|
||||||
|
(sectionSettings) => {
|
||||||
|
const { item, pendingChanges, saveError, ...other } =
|
||||||
|
sectionSettings as AppStateWithPending<Name>;
|
||||||
|
|
||||||
|
const { settings, ...rest } = selectSettings(
|
||||||
|
item,
|
||||||
|
pendingChanges,
|
||||||
|
saveError
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...other,
|
||||||
|
saveError,
|
||||||
|
settings: settings as PendingSection<GetSettingsSectionItemType<Name>>,
|
||||||
|
...rest,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default createSettingsSectionSelector;
|
10
frontend/src/typings/ImportListOptionsSettings.ts
Normal file
10
frontend/src/typings/ImportListOptionsSettings.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
export type ListSyncLevel =
|
||||||
|
| 'disabled'
|
||||||
|
| 'logOnly'
|
||||||
|
| 'keepAndUnmonitor'
|
||||||
|
| 'removeAndKeep'
|
||||||
|
| 'removeAndDelete';
|
||||||
|
|
||||||
|
export default interface ImportListOptionsSettings {
|
||||||
|
listSyncLevel: ListSyncLevel;
|
||||||
|
}
|
23
frontend/src/typings/pending.ts
Normal file
23
frontend/src/typings/pending.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
export interface ValidationFailure {
|
||||||
|
propertyName: string;
|
||||||
|
errorMessage: string;
|
||||||
|
severity: 'error' | 'warning';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ValidationError extends ValidationFailure {
|
||||||
|
isWarning: false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ValidationWarning extends ValidationFailure {
|
||||||
|
isWarning: true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Pending<T> {
|
||||||
|
value: T;
|
||||||
|
errors: ValidationError[];
|
||||||
|
warnings: ValidationWarning[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PendingSection<T> = {
|
||||||
|
[K in keyof T]: Pending<T[K]>;
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user