mirror of
https://github.com/Sonarr/Sonarr.git
synced 2026-04-26 22:56:23 -04:00
Convert Page components to TypeScript
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import AppState from 'App/State/AppState';
|
||||
import { fetchTranslations } from 'Store/Actions/appActions';
|
||||
import { fetchCustomFilters } from 'Store/Actions/customFilterActions';
|
||||
import { fetchSeries } from 'Store/Actions/seriesActions';
|
||||
import {
|
||||
fetchImportLists,
|
||||
fetchIndexerFlags,
|
||||
fetchLanguages,
|
||||
fetchQualityProfiles,
|
||||
fetchUISettings,
|
||||
} from 'Store/Actions/settingsActions';
|
||||
import { fetchStatus } from 'Store/Actions/systemActions';
|
||||
import { fetchTags } from 'Store/Actions/tagActions';
|
||||
|
||||
const createErrorsSelector = () =>
|
||||
createSelector(
|
||||
(state: AppState) => state.series.error,
|
||||
(state: AppState) => state.customFilters.error,
|
||||
(state: AppState) => state.tags.error,
|
||||
(state: AppState) => state.settings.ui.error,
|
||||
(state: AppState) => state.settings.qualityProfiles.error,
|
||||
(state: AppState) => state.settings.languages.error,
|
||||
(state: AppState) => state.settings.importLists.error,
|
||||
(state: AppState) => state.settings.indexerFlags.error,
|
||||
(state: AppState) => state.system.status.error,
|
||||
(state: AppState) => state.app.translations.error,
|
||||
(
|
||||
seriesError,
|
||||
customFiltersError,
|
||||
tagsError,
|
||||
uiSettingsError,
|
||||
qualityProfilesError,
|
||||
languagesError,
|
||||
importListsError,
|
||||
indexerFlagsError,
|
||||
systemStatusError,
|
||||
translationsError
|
||||
) => {
|
||||
const hasError = !!(
|
||||
seriesError ||
|
||||
customFiltersError ||
|
||||
tagsError ||
|
||||
uiSettingsError ||
|
||||
qualityProfilesError ||
|
||||
languagesError ||
|
||||
importListsError ||
|
||||
indexerFlagsError ||
|
||||
systemStatusError ||
|
||||
translationsError
|
||||
);
|
||||
|
||||
return {
|
||||
hasError,
|
||||
errors: {
|
||||
seriesError,
|
||||
customFiltersError,
|
||||
tagsError,
|
||||
uiSettingsError,
|
||||
qualityProfilesError,
|
||||
languagesError,
|
||||
importListsError,
|
||||
indexerFlagsError,
|
||||
systemStatusError,
|
||||
translationsError,
|
||||
},
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const useAppPage = () => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const isPopulated = useSelector(
|
||||
(state: AppState) =>
|
||||
state.series.isPopulated &&
|
||||
state.customFilters.isPopulated &&
|
||||
state.tags.isPopulated &&
|
||||
state.settings.ui.isPopulated &&
|
||||
state.settings.qualityProfiles.isPopulated &&
|
||||
state.settings.languages.isPopulated &&
|
||||
state.settings.importLists.isPopulated &&
|
||||
state.settings.indexerFlags.isPopulated &&
|
||||
state.system.status.isPopulated &&
|
||||
state.app.translations.isPopulated
|
||||
);
|
||||
|
||||
const { hasError, errors } = useSelector(createErrorsSelector());
|
||||
|
||||
const isLocalStorageSupported = useMemo(() => {
|
||||
const key = 'sonarrTest';
|
||||
|
||||
try {
|
||||
localStorage.setItem(key, key);
|
||||
localStorage.removeItem(key);
|
||||
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchSeries());
|
||||
dispatch(fetchCustomFilters());
|
||||
dispatch(fetchTags());
|
||||
dispatch(fetchQualityProfiles());
|
||||
dispatch(fetchLanguages());
|
||||
dispatch(fetchImportLists());
|
||||
dispatch(fetchIndexerFlags());
|
||||
dispatch(fetchUISettings());
|
||||
dispatch(fetchStatus());
|
||||
dispatch(fetchTranslations());
|
||||
}, [dispatch]);
|
||||
|
||||
return useMemo(() => {
|
||||
return { errors, hasError, isLocalStorageSupported, isPopulated };
|
||||
}, [errors, hasError, isLocalStorageSupported, isPopulated]);
|
||||
};
|
||||
|
||||
export default useAppPage;
|
||||
@@ -0,0 +1,122 @@
|
||||
import Mousetrap, { MousetrapInstance } from 'mousetrap';
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
||||
export interface Shortcut {
|
||||
key: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface BindingOptions {
|
||||
isGlobal?: boolean;
|
||||
}
|
||||
|
||||
export const shortcuts = {
|
||||
openKeyboardShortcutsModal: {
|
||||
key: '?',
|
||||
get name() {
|
||||
return translate('KeyboardShortcutsOpenModal');
|
||||
},
|
||||
},
|
||||
|
||||
closeModal: {
|
||||
key: 'Esc',
|
||||
get name() {
|
||||
return translate('KeyboardShortcutsCloseModal');
|
||||
},
|
||||
},
|
||||
|
||||
acceptConfirmModal: {
|
||||
key: 'Enter',
|
||||
get name() {
|
||||
return translate('KeyboardShortcutsConfirmModal');
|
||||
},
|
||||
},
|
||||
|
||||
focusSeriesSearchInput: {
|
||||
key: 's',
|
||||
get name() {
|
||||
return translate('KeyboardShortcutsFocusSearchBox');
|
||||
},
|
||||
},
|
||||
|
||||
saveSettings: {
|
||||
key: 'mod+s',
|
||||
get name() {
|
||||
return translate('KeyboardShortcutsSaveSettings');
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function useKeyboardShortcuts() {
|
||||
const bindings = useRef<Record<string, BindingOptions>>({});
|
||||
const mouseTrap = useRef<MousetrapInstance | null>();
|
||||
|
||||
const handleStop = useCallback(
|
||||
(_e: Mousetrap.ExtendedKeyboardEvent, element: Element, combo: string) => {
|
||||
const binding = bindings.current[combo];
|
||||
|
||||
if (!binding || binding.isGlobal) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
element.tagName === 'INPUT' ||
|
||||
element.tagName === 'SELECT' ||
|
||||
element.tagName === 'TEXTAREA' ||
|
||||
('contentEditable' in element && element.contentEditable === 'true')
|
||||
);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const bindShortcut = useCallback(
|
||||
(
|
||||
shortcutKey: keyof typeof shortcuts,
|
||||
callback: (e: Mousetrap.ExtendedKeyboardEvent, combo: string) => void,
|
||||
options: BindingOptions = {}
|
||||
) => {
|
||||
const shortcut = shortcuts[shortcutKey];
|
||||
|
||||
mouseTrap.current?.bind(shortcut.key, callback);
|
||||
bindings.current[shortcut.key] = options;
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const unbindShortcut = useCallback((shortcutKey: keyof typeof shortcuts) => {
|
||||
const shortcut = shortcuts[shortcutKey];
|
||||
|
||||
delete bindings.current[shortcut.key];
|
||||
mouseTrap.current?.unbind(shortcut.key);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
mouseTrap.current = new Mousetrap();
|
||||
mouseTrap.current.stopCallback = handleStop;
|
||||
|
||||
const localMouseTrap = mouseTrap.current;
|
||||
|
||||
return () => {
|
||||
const keys = Object.keys(bindings.current);
|
||||
|
||||
if (!keys.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
keys.forEach((binding) => {
|
||||
localMouseTrap.unbind(binding);
|
||||
});
|
||||
|
||||
bindings.current = {};
|
||||
mouseTrap.current = null;
|
||||
};
|
||||
}, [handleStop]);
|
||||
|
||||
return useMemo(
|
||||
() => ({ bindShortcut, unbindShortcut }),
|
||||
[bindShortcut, unbindShortcut]
|
||||
);
|
||||
}
|
||||
|
||||
export default useKeyboardShortcuts;
|
||||
Reference in New Issue
Block a user