mirror of
https://github.com/Sonarr/Sonarr.git
synced 2026-04-18 21:35:27 -04:00
Use react-query for Quality Definitions
This commit is contained in:
@@ -7,7 +7,6 @@ import AppSectionState, {
|
||||
PagedAppSectionState,
|
||||
} from 'App/State/AppSectionState';
|
||||
import Language from 'Language/Language';
|
||||
import { QualityProfileModel } from 'Settings/Profiles/Quality/useQualityProfiles';
|
||||
import AutoTagging, { AutoTaggingSpecification } from 'typings/AutoTagging';
|
||||
import CustomFormat from 'typings/CustomFormat';
|
||||
import CustomFormatSpecification from 'typings/CustomFormatSpecification';
|
||||
@@ -19,7 +18,6 @@ import ImportListOptionsSettings from 'typings/ImportListOptionsSettings';
|
||||
import Indexer from 'typings/Indexer';
|
||||
import IndexerFlag from 'typings/IndexerFlag';
|
||||
import Notification from 'typings/Notification';
|
||||
import QualityDefinition from 'typings/QualityDefinition';
|
||||
import DownloadClientOptions from 'typings/Settings/DownloadClientOptions';
|
||||
import General from 'typings/Settings/General';
|
||||
import IndexerOptions from 'typings/Settings/IndexerOptions';
|
||||
@@ -100,14 +98,6 @@ export interface NotificationAppState
|
||||
AppSectionSaveState,
|
||||
AppSectionSchemaState<Presets<Notification>> {}
|
||||
|
||||
export interface QualityDefinitionsAppState
|
||||
extends AppSectionState<QualityDefinition>,
|
||||
AppSectionSaveState {
|
||||
pendingChanges: {
|
||||
[key: number]: Partial<QualityProfileModel>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface CustomFormatAppState
|
||||
extends AppSectionState<CustomFormat>,
|
||||
AppSectionDeleteState,
|
||||
@@ -155,7 +145,6 @@ interface SettingsAppState {
|
||||
naming: NamingAppState;
|
||||
namingExamples: NamingExamplesAppState;
|
||||
notifications: NotificationAppState;
|
||||
qualityDefinitions: QualityDefinitionsAppState;
|
||||
}
|
||||
|
||||
export default SettingsAppState;
|
||||
|
||||
@@ -15,7 +15,6 @@ import { EpisodeFile } from 'EpisodeFile/EpisodeFile';
|
||||
import { PagedQueryResponse } from 'Helpers/Hooks/usePagedApiQuery';
|
||||
import Series from 'Series/Series';
|
||||
import { removeItem, updateItem } from 'Store/Actions/baseActions';
|
||||
import { fetchQualityDefinitions } from 'Store/Actions/settingsActions';
|
||||
import { repopulatePage } from 'Utilities/pagePopulator';
|
||||
import SignalRLogger from 'Utilities/SignalRLogger';
|
||||
|
||||
@@ -291,7 +290,11 @@ function SignalRListener() {
|
||||
}
|
||||
|
||||
if (name === 'qualitydefinition') {
|
||||
dispatch(fetchQualityDefinitions());
|
||||
if (version < 5) {
|
||||
return;
|
||||
}
|
||||
|
||||
queryClient.invalidateQueries({ queryKey: ['/qualitydefinition'] });
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { create, useStore } from 'zustand';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
|
||||
@@ -23,34 +23,40 @@ export const usePendingChangesStore = <T extends object>(
|
||||
});
|
||||
});
|
||||
|
||||
const setPendingChange = <K extends keyof T>(key: K, value: T[K]) => {
|
||||
store.setState((state) => ({
|
||||
...state,
|
||||
pendingChanges: {
|
||||
...state.pendingChanges,
|
||||
[key]: value,
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
const unsetPendingChange = <K extends keyof T>(key: K) => {
|
||||
store.setState((state) => {
|
||||
const newPendingChanges = { ...state.pendingChanges };
|
||||
delete newPendingChanges[key];
|
||||
|
||||
return {
|
||||
const setPendingChange = useCallback(
|
||||
<K extends keyof T>(key: K, value: T[K]) => {
|
||||
store.setState((state) => ({
|
||||
...state,
|
||||
pendingChanges: newPendingChanges,
|
||||
};
|
||||
});
|
||||
};
|
||||
pendingChanges: {
|
||||
...state.pendingChanges,
|
||||
[key]: value,
|
||||
},
|
||||
}));
|
||||
},
|
||||
[store]
|
||||
);
|
||||
|
||||
const clearPendingChanges = () => {
|
||||
const unsetPendingChange = useCallback(
|
||||
<K extends keyof T>(key: K) => {
|
||||
store.setState((state) => {
|
||||
const newPendingChanges = { ...state.pendingChanges };
|
||||
delete newPendingChanges[key];
|
||||
|
||||
return {
|
||||
...state,
|
||||
pendingChanges: newPendingChanges,
|
||||
};
|
||||
});
|
||||
},
|
||||
[store]
|
||||
);
|
||||
|
||||
const clearPendingChanges = useCallback(() => {
|
||||
store.setState((state) => ({
|
||||
...state,
|
||||
pendingChanges: {},
|
||||
}));
|
||||
};
|
||||
}, [store]);
|
||||
|
||||
const pendingChanges = useStore(
|
||||
store,
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { create, useStore } from 'zustand';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
|
||||
interface PendingItemsStore<T extends { id: number }> {
|
||||
pendingItems: Map<number, Partial<T>>;
|
||||
}
|
||||
|
||||
export const usePendingItemsStore = <T extends { id: number }>() => {
|
||||
// eslint-disable-next-line react/hook-use-state
|
||||
const [store] = useState(() => {
|
||||
return create<PendingItemsStore<T>>()((_set) => {
|
||||
return {
|
||||
pendingItems: new Map(),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const setPendingItem = useCallback(
|
||||
<K extends keyof T>(id: number, key: K, value: T[K], originalItem?: T) => {
|
||||
store.setState((state) => {
|
||||
const newPendingItems = new Map(state.pendingItems);
|
||||
const existingChanges = newPendingItems.get(id) || {};
|
||||
|
||||
// If the value matches the original, remove it from pending changes
|
||||
if (originalItem && originalItem[key] === value) {
|
||||
const { [key]: removed, ...rest } = existingChanges;
|
||||
|
||||
if (Object.keys(rest).length === 0) {
|
||||
newPendingItems.delete(id);
|
||||
} else {
|
||||
newPendingItems.set(id, rest);
|
||||
}
|
||||
} else {
|
||||
newPendingItems.set(id, {
|
||||
...existingChanges,
|
||||
[key]: value,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
pendingItems: newPendingItems,
|
||||
};
|
||||
});
|
||||
},
|
||||
[store]
|
||||
);
|
||||
|
||||
const unsetPendingItem = useCallback(
|
||||
(id: number) => {
|
||||
store.setState((state) => {
|
||||
const newPendingItems = new Map(state.pendingItems);
|
||||
newPendingItems.delete(id);
|
||||
|
||||
return {
|
||||
...state,
|
||||
pendingItems: newPendingItems,
|
||||
};
|
||||
});
|
||||
},
|
||||
[store]
|
||||
);
|
||||
|
||||
const clearPendingItems = useCallback(() => {
|
||||
store.setState((state) => ({
|
||||
...state,
|
||||
pendingItems: new Map(),
|
||||
}));
|
||||
}, [store]);
|
||||
|
||||
const pendingItems = useStore(
|
||||
store,
|
||||
useShallow((state) => state.pendingItems)
|
||||
);
|
||||
|
||||
const getItemsWithPendingChanges = useCallback(
|
||||
(originalItems: T[]): T[] => {
|
||||
return originalItems.map((originalItem) => {
|
||||
const pendingChanges = pendingItems.get(originalItem.id);
|
||||
|
||||
return pendingChanges
|
||||
? { ...originalItem, ...pendingChanges }
|
||||
: originalItem;
|
||||
});
|
||||
},
|
||||
[pendingItems]
|
||||
);
|
||||
|
||||
const hasPendingChanges = useMemo(() => {
|
||||
return pendingItems.size > 0;
|
||||
}, [pendingItems]);
|
||||
|
||||
const getPendingChangesForSave = useCallback(
|
||||
(originalItems: T[]): T[] => {
|
||||
return originalItems.reduce<T[]>((acc, originalItem) => {
|
||||
const pendingChanges = pendingItems.get(originalItem.id);
|
||||
|
||||
if (pendingChanges) {
|
||||
acc.push({
|
||||
...originalItem,
|
||||
...pendingChanges,
|
||||
});
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
},
|
||||
[pendingItems]
|
||||
);
|
||||
|
||||
return {
|
||||
store,
|
||||
setPendingItem,
|
||||
unsetPendingItem,
|
||||
clearPendingItems,
|
||||
getItemsWithPendingChanges,
|
||||
getPendingChangesForSave,
|
||||
hasPendingChanges,
|
||||
};
|
||||
};
|
||||
+2
-2
@@ -1,11 +1,11 @@
|
||||
import ModelBase from 'App/ModelBase';
|
||||
import Quality from './Quality';
|
||||
|
||||
export default interface QualityDefinition {
|
||||
export default interface QualityDefinitionModel extends ModelBase {
|
||||
quality: Quality;
|
||||
title: string;
|
||||
weight: number;
|
||||
minSize: number;
|
||||
maxSize: number;
|
||||
preferredSize: number;
|
||||
id: number;
|
||||
}
|
||||
@@ -1,31 +1,29 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import TextInput from 'Components/Form/TextInput';
|
||||
import Quality from 'Quality/Quality';
|
||||
import { setQualityDefinitionValue } from 'Store/Actions/settingsActions';
|
||||
import { useManageQualityDefinitions } from './useQualityDefinitions';
|
||||
import styles from './QualityDefinition.css';
|
||||
|
||||
interface QualityDefinitionProps {
|
||||
id: number;
|
||||
quality: Quality;
|
||||
title: string;
|
||||
updateDefinition: ReturnType<
|
||||
typeof useManageQualityDefinitions
|
||||
>['updateDefinition'];
|
||||
}
|
||||
|
||||
function QualityDefinition(props: QualityDefinitionProps) {
|
||||
const { id, quality, title } = props;
|
||||
const dispatch = useDispatch();
|
||||
|
||||
function QualityDefinition({
|
||||
id,
|
||||
quality,
|
||||
title,
|
||||
updateDefinition,
|
||||
}: QualityDefinitionProps) {
|
||||
const handleTitleChange = useCallback(
|
||||
({ value }: { value: string }) => {
|
||||
dispatch(
|
||||
setQualityDefinitionValue({
|
||||
id,
|
||||
name: 'title',
|
||||
value,
|
||||
})
|
||||
);
|
||||
updateDefinition(id, 'title', value);
|
||||
},
|
||||
[id, dispatch]
|
||||
[id, updateDefinition]
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,42 +1,17 @@
|
||||
import { isEmpty } from 'lodash';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import AppState from 'App/State/AppState';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import PageSectionContent from 'Components/Page/PageSectionContent';
|
||||
import usePrevious from 'Helpers/Hooks/usePrevious';
|
||||
import {
|
||||
fetchQualityDefinitions,
|
||||
saveQualityDefinitions,
|
||||
} from 'Store/Actions/settingsActions';
|
||||
import {
|
||||
OnChildStateChange,
|
||||
SetChildSave,
|
||||
} from 'typings/Settings/SettingsState';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import QualityDefinition from './QualityDefinition';
|
||||
import { useManageQualityDefinitions } from './useQualityDefinitions';
|
||||
import styles from './QualityDefinitions.css';
|
||||
|
||||
function createQualityDefinitionsSelector() {
|
||||
return createSelector(
|
||||
(state: AppState) => state.settings.qualityDefinitions,
|
||||
(qualityDefinitions) => {
|
||||
const items = qualityDefinitions.items.map((item) => {
|
||||
const pendingChanges = qualityDefinitions.pendingChanges[item.id] || {};
|
||||
|
||||
return Object.assign({}, item, pendingChanges);
|
||||
});
|
||||
|
||||
return {
|
||||
...qualityDefinitions,
|
||||
items,
|
||||
hasPendingChanges: !isEmpty(qualityDefinitions.pendingChanges),
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
interface QualityDefinitionsProps {
|
||||
isResettingQualityDefinitions: boolean;
|
||||
setChildSave: SetChildSave;
|
||||
@@ -48,21 +23,27 @@ function QualityDefinitions({
|
||||
setChildSave,
|
||||
onChildStateChange,
|
||||
}: QualityDefinitionsProps) {
|
||||
const dispatch = useDispatch();
|
||||
const { items, isFetching, isPopulated, isSaving, error, hasPendingChanges } =
|
||||
useSelector(createQualityDefinitionsSelector());
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
items,
|
||||
isFetching,
|
||||
isFetched,
|
||||
isSaving,
|
||||
error,
|
||||
hasPendingChanges,
|
||||
updateDefinition,
|
||||
saveQualityDefinitions,
|
||||
} = useManageQualityDefinitions();
|
||||
|
||||
const wasResettingQualityDefinitions = usePrevious(
|
||||
isResettingQualityDefinitions
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchQualityDefinitions());
|
||||
|
||||
setChildSave(() => {
|
||||
dispatch(saveQualityDefinitions());
|
||||
saveQualityDefinitions();
|
||||
});
|
||||
}, [dispatch, setChildSave]);
|
||||
}, [saveQualityDefinitions, setChildSave]);
|
||||
|
||||
useEffect(() => {
|
||||
onChildStateChange({
|
||||
@@ -73,16 +54,20 @@ function QualityDefinitions({
|
||||
|
||||
useEffect(() => {
|
||||
if (wasResettingQualityDefinitions && !isResettingQualityDefinitions) {
|
||||
dispatch(fetchQualityDefinitions());
|
||||
queryClient.invalidateQueries({ queryKey: ['/qualitydefinition'] });
|
||||
}
|
||||
}, [isResettingQualityDefinitions, wasResettingQualityDefinitions, dispatch]);
|
||||
}, [
|
||||
isResettingQualityDefinitions,
|
||||
wasResettingQualityDefinitions,
|
||||
queryClient,
|
||||
]);
|
||||
|
||||
return (
|
||||
<FieldSet legend={translate('QualityDefinitions')}>
|
||||
<PageSectionContent
|
||||
errorMessage={translate('QualityDefinitionsLoadError')}
|
||||
isFetching={isFetching}
|
||||
isPopulated={isPopulated}
|
||||
isPopulated={isFetched}
|
||||
error={error}
|
||||
>
|
||||
<div className={styles.header}>
|
||||
@@ -92,7 +77,13 @@ function QualityDefinitions({
|
||||
|
||||
<div className={styles.definitions}>
|
||||
{items.map((item) => {
|
||||
return <QualityDefinition key={item.id} {...item} />;
|
||||
return (
|
||||
<QualityDefinition
|
||||
key={item.id}
|
||||
{...item}
|
||||
updateDefinition={updateDefinition}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</PageSectionContent>
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import useApiMutation from 'Helpers/Hooks/useApiMutation';
|
||||
import useApiQuery from 'Helpers/Hooks/useApiQuery';
|
||||
import { usePendingItemsStore } from 'Helpers/Hooks/usePendingItemsStore';
|
||||
import QualityDefinitionModel from 'Quality/QualityDefinitionModel';
|
||||
import { useSaveSettings } from 'Settings/useSettings';
|
||||
|
||||
const PATH = '/qualitydefinition';
|
||||
const DEFAULT_QUALITY_DEFINITIONS: QualityDefinitionModel[] = [];
|
||||
|
||||
export const useQualityDefinitions = () => {
|
||||
const result = useApiQuery<QualityDefinitionModel[]>({
|
||||
path: PATH,
|
||||
});
|
||||
|
||||
return {
|
||||
...result,
|
||||
data: result.data ?? DEFAULT_QUALITY_DEFINITIONS,
|
||||
};
|
||||
};
|
||||
|
||||
export const useSaveQualityDefinitions = (onSuccess?: () => void) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { mutate, isPending, error } = useApiMutation<
|
||||
QualityDefinitionModel[],
|
||||
QualityDefinitionModel[]
|
||||
>({
|
||||
path: PATH,
|
||||
method: 'PUT',
|
||||
mutationOptions: {
|
||||
onSuccess: (updatedSettings: QualityDefinitionModel[]) => {
|
||||
queryClient.setQueryData<QualityDefinitionModel[]>(
|
||||
[PATH],
|
||||
updatedSettings
|
||||
);
|
||||
onSuccess?.();
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
save: mutate,
|
||||
isSaving: isPending,
|
||||
saveError: error,
|
||||
};
|
||||
};
|
||||
|
||||
export const useManageQualityDefinitions = () => {
|
||||
const { data, isFetching, isFetched, error } = useQualityDefinitions();
|
||||
const {
|
||||
setPendingItem,
|
||||
clearPendingItems,
|
||||
getItemsWithPendingChanges,
|
||||
getPendingChangesForSave,
|
||||
hasPendingChanges,
|
||||
} = usePendingItemsStore<QualityDefinitionModel>();
|
||||
|
||||
const { save, isSaving, saveError } = useSaveSettings(
|
||||
PATH,
|
||||
clearPendingItems
|
||||
);
|
||||
|
||||
const settings = useMemo(() => {
|
||||
return {
|
||||
items: getItemsWithPendingChanges(data),
|
||||
hasPendingChanges,
|
||||
};
|
||||
}, [data, getItemsWithPendingChanges, hasPendingChanges]);
|
||||
|
||||
const saveQualityDefinitions = useCallback(() => {
|
||||
const updatedSettings = getPendingChangesForSave(data);
|
||||
save(updatedSettings);
|
||||
}, [data, getPendingChangesForSave, save]);
|
||||
|
||||
const updateDefinition = useCallback(
|
||||
<K extends keyof QualityDefinitionModel>(
|
||||
id: number,
|
||||
key: keyof QualityDefinitionModel,
|
||||
value: QualityDefinitionModel[K]
|
||||
) => {
|
||||
const originalItem = data.find((def) => def.id === id);
|
||||
setPendingItem(id, key, value, originalItem);
|
||||
},
|
||||
[data, setPendingItem]
|
||||
);
|
||||
|
||||
return {
|
||||
...settings,
|
||||
updateDefinition,
|
||||
saveQualityDefinitions,
|
||||
isFetching,
|
||||
isFetched,
|
||||
isSaving,
|
||||
error,
|
||||
saveError,
|
||||
};
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import CommandNames from 'Commands/CommandNames';
|
||||
import { useCommandExecuting } from 'Commands/useCommands';
|
||||
import { useCommandExecuting, useExecuteCommand } from 'Commands/useCommands';
|
||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||
@@ -13,9 +14,9 @@ import {
|
||||
} from 'typings/Settings/SettingsState';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import QualityDefinitions from './Definition/QualityDefinitions';
|
||||
import ResetQualityDefinitionsModal from './Reset/ResetQualityDefinitionsModal';
|
||||
|
||||
function Quality() {
|
||||
const executeCommand = useExecuteCommand();
|
||||
const isResettingQualityDefinitions = useCommandExecuting(
|
||||
CommandNames.ResetQualityDefinitions
|
||||
);
|
||||
@@ -51,6 +52,15 @@ function Quality() {
|
||||
setIsConfirmQualityDefinitionResetModalOpen(false);
|
||||
}, []);
|
||||
|
||||
const handleResetQualityDefinitionsConfirmed = useCallback(() => {
|
||||
executeCommand({
|
||||
name: CommandNames.ResetQualityDefinitions,
|
||||
resetTitles: true,
|
||||
});
|
||||
|
||||
setIsConfirmQualityDefinitionResetModalOpen(false);
|
||||
}, [executeCommand]);
|
||||
|
||||
const handleSavePress = useCallback(() => {
|
||||
saveDefinitions.current?.();
|
||||
}, []);
|
||||
@@ -68,13 +78,13 @@ function Quality() {
|
||||
label={translate('ResetDefinitions')}
|
||||
iconName={icons.REFRESH}
|
||||
isSpinning={isResettingQualityDefinitions}
|
||||
isDisabled={isResettingQualityDefinitions}
|
||||
onPress={handleResetQualityDefinitionsPress}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
onSavePress={handleSavePress}
|
||||
/>
|
||||
|
||||
<PageContentBody>
|
||||
<QualityDefinitions
|
||||
isResettingQualityDefinitions={isResettingQualityDefinitions}
|
||||
@@ -83,9 +93,14 @@ function Quality() {
|
||||
/>
|
||||
</PageContentBody>
|
||||
|
||||
<ResetQualityDefinitionsModal
|
||||
<ConfirmModal
|
||||
isOpen={isConfirmQualityDefinitionResetModalOpen}
|
||||
onModalClose={handleCloseResetQualityDefinitionsModal}
|
||||
kind="danger"
|
||||
title={translate('ResetQualityDefinitions')}
|
||||
message={translate('ResetQualityDefinitionsMessageText')}
|
||||
confirmLabel={translate('Reset')}
|
||||
onConfirm={handleResetQualityDefinitionsConfirmed}
|
||||
onCancel={handleCloseResetQualityDefinitionsModal}
|
||||
/>
|
||||
</PageContent>
|
||||
);
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
import React from 'react';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import { sizes } from 'Helpers/Props';
|
||||
import ResetQualityDefinitionsModalContent from './ResetQualityDefinitionsModalContent';
|
||||
|
||||
interface ResetQualityDefinitionsModalProps {
|
||||
isOpen: boolean;
|
||||
onModalClose: () => void;
|
||||
}
|
||||
|
||||
function ResetQualityDefinitionsModal({
|
||||
isOpen,
|
||||
onModalClose,
|
||||
}: ResetQualityDefinitionsModalProps) {
|
||||
return (
|
||||
<Modal isOpen={isOpen} size={sizes.MEDIUM} onModalClose={onModalClose}>
|
||||
<ResetQualityDefinitionsModalContent onModalClose={onModalClose} />
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default ResetQualityDefinitionsModal;
|
||||
@@ -1,3 +0,0 @@
|
||||
.messageContainer {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
// This file is automatically generated.
|
||||
// Please do not change this file!
|
||||
interface CssExports {
|
||||
'messageContainer': string;
|
||||
}
|
||||
export const cssExports: CssExports;
|
||||
export default cssExports;
|
||||
@@ -1,87 +0,0 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import CommandNames from 'Commands/CommandNames';
|
||||
import { useCommandExecuting, useExecuteCommand } from 'Commands/useCommands';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import Button from 'Components/Link/Button';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { inputTypes, kinds } from 'Helpers/Props';
|
||||
import { InputChanged } from 'typings/inputs';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './ResetQualityDefinitionsModalContent.css';
|
||||
|
||||
interface ResetQualityDefinitionsModalContentProps {
|
||||
onModalClose: () => void;
|
||||
}
|
||||
|
||||
function ResetQualityDefinitionsModalContent({
|
||||
onModalClose,
|
||||
}: ResetQualityDefinitionsModalContentProps) {
|
||||
const executeCommand = useExecuteCommand();
|
||||
const isResettingQualityDefinitions = useCommandExecuting(
|
||||
CommandNames.ResetQualityDefinitions
|
||||
);
|
||||
|
||||
const [resetDefinitionTitles, setResetDefinitionTitles] = useState(false);
|
||||
|
||||
const handleResetDefinitionTitlesChange = useCallback(
|
||||
({ value }: InputChanged<boolean>) => {
|
||||
setResetDefinitionTitles(value);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const handleResetQualityDefinitionsConfirmed = useCallback(() => {
|
||||
const resetTitles = resetDefinitionTitles;
|
||||
|
||||
setResetDefinitionTitles(false);
|
||||
|
||||
executeCommand({
|
||||
name: CommandNames.ResetQualityDefinitions,
|
||||
resetTitles,
|
||||
});
|
||||
onModalClose();
|
||||
}, [resetDefinitionTitles, executeCommand, onModalClose]);
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>{translate('ResetQualityDefinitions')}</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<div className={styles.messageContainer}>
|
||||
{translate('ResetQualityDefinitionsMessageText')}
|
||||
</div>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('ResetTitles')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="resetDefinitionTitles"
|
||||
value={resetDefinitionTitles}
|
||||
helpText={translate('ResetDefinitionTitlesHelpText')}
|
||||
onChange={handleResetDefinitionTitlesChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button onPress={onModalClose}>{translate('Cancel')}</Button>
|
||||
|
||||
<Button
|
||||
kind={kinds.DANGER}
|
||||
isDisabled={isResettingQualityDefinitions}
|
||||
onPress={handleResetQualityDefinitionsConfirmed}
|
||||
>
|
||||
{translate('Reset')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
);
|
||||
}
|
||||
|
||||
export default ResetQualityDefinitionsModalContent;
|
||||
@@ -1,137 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import { createAction } from 'redux-actions';
|
||||
import { batchActions } from 'redux-batched-actions';
|
||||
import { clearPendingChanges, set, update } from 'Store/Actions/baseActions';
|
||||
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
|
||||
import createSaveHandler from 'Store/Actions/Creators/createSaveHandler';
|
||||
import { createThunk } from 'Store/thunks';
|
||||
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
||||
import getSectionState from 'Utilities/State/getSectionState';
|
||||
import updateSectionState from 'Utilities/State/updateSectionState';
|
||||
|
||||
//
|
||||
// Variables
|
||||
|
||||
const section = 'settings.qualityDefinitions';
|
||||
|
||||
//
|
||||
// Actions Types
|
||||
|
||||
export const FETCH_QUALITY_DEFINITIONS = 'settings/qualityDefinitions/fetchQualityDefinitions';
|
||||
export const SAVE_QUALITY_DEFINITIONS = 'settings/qualityDefinitions/saveQualityDefinitions';
|
||||
export const SET_QUALITY_DEFINITION_VALUE = 'settings/qualityDefinitions/setQualityDefinitionValue';
|
||||
|
||||
//
|
||||
// Action Creators
|
||||
|
||||
export const fetchQualityDefinitions = createThunk(FETCH_QUALITY_DEFINITIONS);
|
||||
export const saveQualityDefinitions = createThunk(SAVE_QUALITY_DEFINITIONS);
|
||||
|
||||
export const setQualityDefinitionValue = createAction(SET_QUALITY_DEFINITION_VALUE);
|
||||
|
||||
//
|
||||
// Details
|
||||
|
||||
export default {
|
||||
|
||||
//
|
||||
// State
|
||||
|
||||
defaultState: {
|
||||
isFetching: false,
|
||||
isPopulated: false,
|
||||
error: null,
|
||||
items: [],
|
||||
isSaving: false,
|
||||
saveError: null,
|
||||
pendingChanges: {}
|
||||
},
|
||||
|
||||
//
|
||||
// Action Handlers
|
||||
|
||||
actionHandlers: {
|
||||
[FETCH_QUALITY_DEFINITIONS]: createFetchHandler(section, '/qualitydefinition'),
|
||||
[SAVE_QUALITY_DEFINITIONS]: createSaveHandler(section, '/qualitydefinition'),
|
||||
|
||||
[SAVE_QUALITY_DEFINITIONS]: function(getState, payload, dispatch) {
|
||||
const qualityDefinitions = getState().settings.qualityDefinitions;
|
||||
|
||||
const upatedDefinitions = Object.keys(qualityDefinitions.pendingChanges).map((key) => {
|
||||
const id = parseInt(key);
|
||||
const pendingChanges = qualityDefinitions.pendingChanges[id] || {};
|
||||
const item = _.find(qualityDefinitions.items, { id });
|
||||
|
||||
return Object.assign({}, item, pendingChanges);
|
||||
});
|
||||
|
||||
// If there is nothing to save don't bother isSaving
|
||||
if (!upatedDefinitions || !upatedDefinitions.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(set({
|
||||
section,
|
||||
isSaving: true
|
||||
}));
|
||||
|
||||
const promise = createAjaxRequest({
|
||||
method: 'PUT',
|
||||
url: '/qualitydefinition/update',
|
||||
data: JSON.stringify(upatedDefinitions),
|
||||
contentType: 'application/json',
|
||||
dataType: 'json'
|
||||
}).request;
|
||||
|
||||
promise.done((data) => {
|
||||
dispatch(batchActions([
|
||||
set({
|
||||
section,
|
||||
isSaving: false,
|
||||
saveError: null
|
||||
}),
|
||||
|
||||
update({ section, data }),
|
||||
clearPendingChanges({ section })
|
||||
]));
|
||||
});
|
||||
|
||||
promise.fail((xhr) => {
|
||||
dispatch(set({
|
||||
section,
|
||||
isSaving: false,
|
||||
saveError: xhr
|
||||
}));
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
//
|
||||
// Reducers
|
||||
|
||||
reducers: {
|
||||
[SET_QUALITY_DEFINITION_VALUE]: function(state, { payload }) {
|
||||
const { id, name, value } = payload;
|
||||
const newState = getSectionState(state, section);
|
||||
newState.pendingChanges = _.cloneDeep(newState.pendingChanges);
|
||||
|
||||
const pendingState = newState.pendingChanges[id] || {};
|
||||
const currentValue = _.find(newState.items, { id })[name];
|
||||
|
||||
if (currentValue === value) {
|
||||
delete pendingState[name];
|
||||
} else {
|
||||
pendingState[name] = value;
|
||||
}
|
||||
|
||||
if (_.isEmpty(pendingState)) {
|
||||
delete newState.pendingChanges[id];
|
||||
} else {
|
||||
newState.pendingChanges[id] = pendingState;
|
||||
}
|
||||
|
||||
return updateSectionState(state, section, newState);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
@@ -20,7 +20,6 @@ import metadata from './Settings/metadata';
|
||||
import naming from './Settings/naming';
|
||||
import namingExamples from './Settings/namingExamples';
|
||||
import notifications from './Settings/notifications';
|
||||
import qualityDefinitions from './Settings/qualityDefinitions';
|
||||
|
||||
export * from './Settings/autoTaggingSpecifications';
|
||||
export * from './Settings/autoTaggings';
|
||||
@@ -42,7 +41,6 @@ export * from './Settings/metadata';
|
||||
export * from './Settings/naming';
|
||||
export * from './Settings/namingExamples';
|
||||
export * from './Settings/notifications';
|
||||
export * from './Settings/qualityDefinitions';
|
||||
|
||||
//
|
||||
// Variables
|
||||
@@ -73,8 +71,7 @@ export const defaultState = {
|
||||
metadata: metadata.defaultState,
|
||||
naming: naming.defaultState,
|
||||
namingExamples: namingExamples.defaultState,
|
||||
notifications: notifications.defaultState,
|
||||
qualityDefinitions: qualityDefinitions.defaultState
|
||||
notifications: notifications.defaultState
|
||||
};
|
||||
|
||||
export const persistState = [
|
||||
@@ -104,8 +101,7 @@ export const actionHandlers = handleThunks({
|
||||
...metadata.actionHandlers,
|
||||
...naming.actionHandlers,
|
||||
...namingExamples.actionHandlers,
|
||||
...notifications.actionHandlers,
|
||||
...qualityDefinitions.actionHandlers
|
||||
...notifications.actionHandlers
|
||||
});
|
||||
|
||||
//
|
||||
@@ -131,7 +127,6 @@ export const reducers = createHandleActions({
|
||||
...metadata.reducers,
|
||||
...naming.reducers,
|
||||
...namingExamples.reducers,
|
||||
...notifications.reducers,
|
||||
...qualityDefinitions.reducers
|
||||
...notifications.reducers
|
||||
|
||||
}, defaultState, section);
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import Quality from 'Quality/Quality';
|
||||
|
||||
interface QualityDefinition {
|
||||
id: number;
|
||||
quality: Quality;
|
||||
title: string;
|
||||
weight: number;
|
||||
}
|
||||
|
||||
export default QualityDefinition;
|
||||
Reference in New Issue
Block a user