mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2026-04-27 23:16:58 -04:00
Convert App to TypeScript
(cherry picked from commit d6d90a64a39d3b9d3a95fb6b265517693a70fdd7) (cherry picked from commit 428569106499b5e3a463f1990ae2996d1ae4ab49) (cherry picked from commit d0e9504af0d88391a74e04b90638e4b2d99fb476) (cherry picked from commit ee80564dd427ca1dc14c192955efaa61f386ad44) (cherry picked from commit 76650af9fdc7ef06d13ce252986d21574903d293)
This commit is contained in:
@@ -1,20 +1,25 @@
|
||||
import { ConnectedRouter } from 'connected-react-router';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ConnectedRouter, ConnectedRouterProps } from 'connected-react-router';
|
||||
import React from 'react';
|
||||
import DocumentTitle from 'react-document-title';
|
||||
import { Provider } from 'react-redux';
|
||||
import { Store } from 'redux';
|
||||
import PageConnector from 'Components/Page/PageConnector';
|
||||
import ApplyTheme from './ApplyTheme';
|
||||
import AppRoutes from './AppRoutes';
|
||||
|
||||
function App({ store, history }) {
|
||||
interface AppProps {
|
||||
store: Store;
|
||||
history: ConnectedRouterProps['history'];
|
||||
}
|
||||
|
||||
function App({ store, history }: AppProps) {
|
||||
return (
|
||||
<DocumentTitle title={window.Prowlarr.instanceName}>
|
||||
<Provider store={store}>
|
||||
<ConnectedRouter history={history}>
|
||||
<ApplyTheme />
|
||||
<PageConnector>
|
||||
<AppRoutes app={App} />
|
||||
<AppRoutes />
|
||||
</PageConnector>
|
||||
</ConnectedRouter>
|
||||
</Provider>
|
||||
@@ -22,9 +27,4 @@ function App({ store, history }) {
|
||||
);
|
||||
}
|
||||
|
||||
App.propTypes = {
|
||||
store: PropTypes.object.isRequired,
|
||||
history: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default App;
|
||||
@@ -1,184 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { Redirect, Route } from 'react-router-dom';
|
||||
import NotFound from 'Components/NotFound';
|
||||
import Switch from 'Components/Router/Switch';
|
||||
import HistoryConnector from 'History/HistoryConnector';
|
||||
import IndexerIndex from 'Indexer/Index/IndexerIndex';
|
||||
import IndexerStats from 'Indexer/Stats/IndexerStats';
|
||||
import SearchIndexConnector from 'Search/SearchIndexConnector';
|
||||
import ApplicationSettings from 'Settings/Applications/ApplicationSettings';
|
||||
import DevelopmentSettingsConnector from 'Settings/Development/DevelopmentSettingsConnector';
|
||||
import DownloadClientSettingsConnector from 'Settings/DownloadClients/DownloadClientSettingsConnector';
|
||||
import GeneralSettingsConnector from 'Settings/General/GeneralSettingsConnector';
|
||||
import IndexerSettings from 'Settings/Indexers/IndexerSettings';
|
||||
import NotificationSettings from 'Settings/Notifications/NotificationSettings';
|
||||
import Settings from 'Settings/Settings';
|
||||
import TagSettings from 'Settings/Tags/TagSettings';
|
||||
import UISettingsConnector from 'Settings/UI/UISettingsConnector';
|
||||
import BackupsConnector from 'System/Backup/BackupsConnector';
|
||||
import LogsTableConnector from 'System/Events/LogsTableConnector';
|
||||
import Logs from 'System/Logs/Logs';
|
||||
import Status from 'System/Status/Status';
|
||||
import Tasks from 'System/Tasks/Tasks';
|
||||
import UpdatesConnector from 'System/Updates/UpdatesConnector';
|
||||
import getPathWithUrlBase from 'Utilities/getPathWithUrlBase';
|
||||
|
||||
function AppRoutes(props) {
|
||||
const {
|
||||
app
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
{/*
|
||||
Indexers
|
||||
*/}
|
||||
|
||||
<Route
|
||||
exact={true}
|
||||
path="/"
|
||||
component={IndexerIndex}
|
||||
/>
|
||||
|
||||
{
|
||||
window.Prowlarr.urlBase &&
|
||||
<Route
|
||||
exact={true}
|
||||
path="/"
|
||||
addUrlBase={false}
|
||||
render={() => {
|
||||
return (
|
||||
<Redirect
|
||||
to={getPathWithUrlBase('/')}
|
||||
component={app}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
|
||||
<Route
|
||||
path="/indexers/stats"
|
||||
component={IndexerStats}
|
||||
/>
|
||||
|
||||
{/*
|
||||
Search
|
||||
*/}
|
||||
|
||||
<Route
|
||||
path="/search"
|
||||
component={SearchIndexConnector}
|
||||
/>
|
||||
|
||||
{/*
|
||||
Activity
|
||||
*/}
|
||||
|
||||
<Route
|
||||
path="/history"
|
||||
component={HistoryConnector}
|
||||
/>
|
||||
|
||||
{/*
|
||||
Settings
|
||||
*/}
|
||||
|
||||
<Route
|
||||
exact={true}
|
||||
path="/settings"
|
||||
component={Settings}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/settings/indexers"
|
||||
component={IndexerSettings}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/settings/applications"
|
||||
component={ApplicationSettings}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/settings/downloadclients"
|
||||
component={DownloadClientSettingsConnector}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/settings/connect"
|
||||
component={NotificationSettings}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/settings/tags"
|
||||
component={TagSettings}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/settings/general"
|
||||
component={GeneralSettingsConnector}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/settings/ui"
|
||||
component={UISettingsConnector}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/settings/development"
|
||||
component={DevelopmentSettingsConnector}
|
||||
/>
|
||||
|
||||
{/*
|
||||
System
|
||||
*/}
|
||||
|
||||
<Route
|
||||
path="/system/status"
|
||||
component={Status}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/system/tasks"
|
||||
component={Tasks}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/system/backup"
|
||||
component={BackupsConnector}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/system/updates"
|
||||
component={UpdatesConnector}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/system/events"
|
||||
component={LogsTableConnector}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/system/logs/files"
|
||||
component={Logs}
|
||||
/>
|
||||
|
||||
{/*
|
||||
Not Found
|
||||
*/}
|
||||
|
||||
<Route
|
||||
path="*"
|
||||
component={NotFound}
|
||||
/>
|
||||
</Switch>
|
||||
);
|
||||
}
|
||||
|
||||
AppRoutes.propTypes = {
|
||||
app: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default AppRoutes;
|
||||
@@ -0,0 +1,115 @@
|
||||
import React from 'react';
|
||||
import { Redirect, Route } from 'react-router-dom';
|
||||
import NotFound from 'Components/NotFound';
|
||||
import Switch from 'Components/Router/Switch';
|
||||
import HistoryConnector from 'History/HistoryConnector';
|
||||
import IndexerIndex from 'Indexer/Index/IndexerIndex';
|
||||
import IndexerStats from 'Indexer/Stats/IndexerStats';
|
||||
import SearchIndexConnector from 'Search/SearchIndexConnector';
|
||||
import ApplicationSettings from 'Settings/Applications/ApplicationSettings';
|
||||
import DevelopmentSettingsConnector from 'Settings/Development/DevelopmentSettingsConnector';
|
||||
import DownloadClientSettingsConnector from 'Settings/DownloadClients/DownloadClientSettingsConnector';
|
||||
import GeneralSettingsConnector from 'Settings/General/GeneralSettingsConnector';
|
||||
import IndexerSettings from 'Settings/Indexers/IndexerSettings';
|
||||
import NotificationSettings from 'Settings/Notifications/NotificationSettings';
|
||||
import Settings from 'Settings/Settings';
|
||||
import TagSettings from 'Settings/Tags/TagSettings';
|
||||
import UISettingsConnector from 'Settings/UI/UISettingsConnector';
|
||||
import BackupsConnector from 'System/Backup/BackupsConnector';
|
||||
import LogsTableConnector from 'System/Events/LogsTableConnector';
|
||||
import Logs from 'System/Logs/Logs';
|
||||
import Status from 'System/Status/Status';
|
||||
import Tasks from 'System/Tasks/Tasks';
|
||||
import UpdatesConnector from 'System/Updates/UpdatesConnector';
|
||||
import getPathWithUrlBase from 'Utilities/getPathWithUrlBase';
|
||||
|
||||
function AppRoutes() {
|
||||
return (
|
||||
<Switch>
|
||||
{/*
|
||||
Indexers
|
||||
*/}
|
||||
|
||||
<Route exact={true} path="/" component={IndexerIndex} />
|
||||
|
||||
{window.Prowlarr.urlBase && (
|
||||
<Route
|
||||
exact={true}
|
||||
path="/"
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
addUrlBase={false}
|
||||
render={() => {
|
||||
return <Redirect to={getPathWithUrlBase('/')} />;
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Route path="/indexers/stats" component={IndexerStats} />
|
||||
|
||||
{/*
|
||||
Search
|
||||
*/}
|
||||
|
||||
<Route path="/search" component={SearchIndexConnector} />
|
||||
|
||||
{/*
|
||||
Activity
|
||||
*/}
|
||||
|
||||
<Route path="/history" component={HistoryConnector} />
|
||||
|
||||
{/*
|
||||
Settings
|
||||
*/}
|
||||
|
||||
<Route exact={true} path="/settings" component={Settings} />
|
||||
|
||||
<Route path="/settings/indexers" component={IndexerSettings} />
|
||||
|
||||
<Route path="/settings/applications" component={ApplicationSettings} />
|
||||
|
||||
<Route
|
||||
path="/settings/downloadclients"
|
||||
component={DownloadClientSettingsConnector}
|
||||
/>
|
||||
|
||||
<Route path="/settings/connect" component={NotificationSettings} />
|
||||
|
||||
<Route path="/settings/tags" component={TagSettings} />
|
||||
|
||||
<Route path="/settings/general" component={GeneralSettingsConnector} />
|
||||
|
||||
<Route path="/settings/ui" component={UISettingsConnector} />
|
||||
|
||||
<Route
|
||||
path="/settings/development"
|
||||
component={DevelopmentSettingsConnector}
|
||||
/>
|
||||
|
||||
{/*
|
||||
System
|
||||
*/}
|
||||
|
||||
<Route path="/system/status" component={Status} />
|
||||
|
||||
<Route path="/system/tasks" component={Tasks} />
|
||||
|
||||
<Route path="/system/backup" component={BackupsConnector} />
|
||||
|
||||
<Route path="/system/updates" component={UpdatesConnector} />
|
||||
|
||||
<Route path="/system/events" component={LogsTableConnector} />
|
||||
|
||||
<Route path="/system/logs/files" component={Logs} />
|
||||
|
||||
{/*
|
||||
Not Found
|
||||
*/}
|
||||
|
||||
<Route path="*" component={NotFound} />
|
||||
</Switch>
|
||||
);
|
||||
}
|
||||
|
||||
export default AppRoutes;
|
||||
@@ -1,30 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import AppUpdatedModalContentConnector from './AppUpdatedModalContentConnector';
|
||||
|
||||
function AppUpdatedModal(props) {
|
||||
const {
|
||||
isOpen,
|
||||
onModalClose
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
closeOnBackgroundClick={false}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
<AppUpdatedModalContentConnector
|
||||
onModalClose={onModalClose}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
AppUpdatedModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default AppUpdatedModal;
|
||||
@@ -0,0 +1,28 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import AppUpdatedModalContent from './AppUpdatedModalContent';
|
||||
|
||||
interface AppUpdatedModalProps {
|
||||
isOpen: boolean;
|
||||
onModalClose: (...args: unknown[]) => unknown;
|
||||
}
|
||||
|
||||
function AppUpdatedModal(props: AppUpdatedModalProps) {
|
||||
const { isOpen, onModalClose } = props;
|
||||
|
||||
const handleModalClose = useCallback(() => {
|
||||
location.reload();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
closeOnBackgroundClick={false}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
<AppUpdatedModalContent onModalClose={handleModalClose} />
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default AppUpdatedModal;
|
||||
@@ -1,12 +0,0 @@
|
||||
import { connect } from 'react-redux';
|
||||
import AppUpdatedModal from './AppUpdatedModal';
|
||||
|
||||
function createMapDispatchToProps(dispatch, props) {
|
||||
return {
|
||||
onModalClose() {
|
||||
location.reload();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(null, createMapDispatchToProps)(AppUpdatedModal);
|
||||
@@ -1,139 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Button from 'Components/Link/Button';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import InlineMarkdown from 'Components/Markdown/InlineMarkdown';
|
||||
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 { kinds } from 'Helpers/Props';
|
||||
import UpdateChanges from 'System/Updates/UpdateChanges';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './AppUpdatedModalContent.css';
|
||||
|
||||
function mergeUpdates(items, version, prevVersion) {
|
||||
let installedIndex = items.findIndex((u) => u.version === version);
|
||||
let installedPreviouslyIndex = items.findIndex((u) => u.version === prevVersion);
|
||||
|
||||
if (installedIndex === -1) {
|
||||
installedIndex = 0;
|
||||
}
|
||||
|
||||
if (installedPreviouslyIndex === -1) {
|
||||
installedPreviouslyIndex = items.length;
|
||||
} else if (installedPreviouslyIndex === installedIndex && items.length) {
|
||||
installedPreviouslyIndex += 1;
|
||||
}
|
||||
|
||||
const appliedUpdates = items.slice(installedIndex, installedPreviouslyIndex);
|
||||
|
||||
if (!appliedUpdates.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const appliedChanges = { new: [], fixed: [] };
|
||||
appliedUpdates.forEach((u) => {
|
||||
if (u.changes) {
|
||||
appliedChanges.new.push(... u.changes.new);
|
||||
appliedChanges.fixed.push(... u.changes.fixed);
|
||||
}
|
||||
});
|
||||
|
||||
const mergedUpdate = Object.assign({}, appliedUpdates[0], { changes: appliedChanges });
|
||||
|
||||
if (!appliedChanges.new.length && !appliedChanges.fixed.length) {
|
||||
mergedUpdate.changes = null;
|
||||
}
|
||||
|
||||
return mergedUpdate;
|
||||
}
|
||||
|
||||
function AppUpdatedModalContent(props) {
|
||||
const {
|
||||
version,
|
||||
prevVersion,
|
||||
isPopulated,
|
||||
error,
|
||||
items,
|
||||
onSeeChangesPress,
|
||||
onModalClose
|
||||
} = props;
|
||||
|
||||
const update = mergeUpdates(items, version, prevVersion);
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
{translate('AppUpdated')}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<div>
|
||||
<InlineMarkdown data={translate('AppUpdatedVersion', { version })} blockClassName={styles.version} />
|
||||
</div>
|
||||
|
||||
{
|
||||
isPopulated && !error && !!update &&
|
||||
<div>
|
||||
{
|
||||
!update.changes &&
|
||||
<div className={styles.maintenance}>{translate('MaintenanceRelease')}</div>
|
||||
}
|
||||
|
||||
{
|
||||
!!update.changes &&
|
||||
<div>
|
||||
<div className={styles.changes}>
|
||||
{translate('WhatsNew')}
|
||||
</div>
|
||||
|
||||
<UpdateChanges
|
||||
title={translate('New')}
|
||||
changes={update.changes.new}
|
||||
/>
|
||||
|
||||
<UpdateChanges
|
||||
title={translate('Fixed')}
|
||||
changes={update.changes.fixed}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
!isPopulated && !error &&
|
||||
<LoadingIndicator />
|
||||
}
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button
|
||||
onPress={onSeeChangesPress}
|
||||
>
|
||||
{translate('RecentChanges')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
kind={kinds.PRIMARY}
|
||||
onPress={onModalClose}
|
||||
>
|
||||
{translate('Reload')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
);
|
||||
}
|
||||
|
||||
AppUpdatedModalContent.propTypes = {
|
||||
version: PropTypes.string.isRequired,
|
||||
prevVersion: PropTypes.string,
|
||||
isPopulated: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onSeeChangesPress: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default AppUpdatedModalContent;
|
||||
@@ -0,0 +1,145 @@
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import Button from 'Components/Link/Button';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import InlineMarkdown from 'Components/Markdown/InlineMarkdown';
|
||||
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 usePrevious from 'Helpers/Hooks/usePrevious';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import { fetchUpdates } from 'Store/Actions/systemActions';
|
||||
import UpdateChanges from 'System/Updates/UpdateChanges';
|
||||
import Update from 'typings/Update';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import AppState from './State/AppState';
|
||||
import styles from './AppUpdatedModalContent.css';
|
||||
|
||||
function mergeUpdates(items: Update[], version: string, prevVersion?: string) {
|
||||
let installedIndex = items.findIndex((u) => u.version === version);
|
||||
let installedPreviouslyIndex = items.findIndex(
|
||||
(u) => u.version === prevVersion
|
||||
);
|
||||
|
||||
if (installedIndex === -1) {
|
||||
installedIndex = 0;
|
||||
}
|
||||
|
||||
if (installedPreviouslyIndex === -1) {
|
||||
installedPreviouslyIndex = items.length;
|
||||
} else if (installedPreviouslyIndex === installedIndex && items.length) {
|
||||
installedPreviouslyIndex += 1;
|
||||
}
|
||||
|
||||
const appliedUpdates = items.slice(installedIndex, installedPreviouslyIndex);
|
||||
|
||||
if (!appliedUpdates.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const appliedChanges: Update['changes'] = { new: [], fixed: [] };
|
||||
|
||||
appliedUpdates.forEach((u: Update) => {
|
||||
if (u.changes) {
|
||||
appliedChanges.new.push(...u.changes.new);
|
||||
appliedChanges.fixed.push(...u.changes.fixed);
|
||||
}
|
||||
});
|
||||
|
||||
const mergedUpdate: Update = Object.assign({}, appliedUpdates[0], {
|
||||
changes: appliedChanges,
|
||||
});
|
||||
|
||||
if (!appliedChanges.new.length && !appliedChanges.fixed.length) {
|
||||
mergedUpdate.changes = null;
|
||||
}
|
||||
|
||||
return mergedUpdate;
|
||||
}
|
||||
|
||||
interface AppUpdatedModalContentProps {
|
||||
onModalClose: () => void;
|
||||
}
|
||||
|
||||
function AppUpdatedModalContent(props: AppUpdatedModalContentProps) {
|
||||
const dispatch = useDispatch();
|
||||
const { version, prevVersion } = useSelector((state: AppState) => state.app);
|
||||
const { isPopulated, error, items } = useSelector(
|
||||
(state: AppState) => state.system.updates
|
||||
);
|
||||
const previousVersion = usePrevious(version);
|
||||
|
||||
const { onModalClose } = props;
|
||||
|
||||
const update = mergeUpdates(items, version, prevVersion);
|
||||
|
||||
const handleSeeChangesPress = useCallback(() => {
|
||||
window.location.href = `${window.Prowlarr.urlBase}/system/updates`;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchUpdates());
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (version !== previousVersion) {
|
||||
dispatch(fetchUpdates());
|
||||
}
|
||||
}, [version, previousVersion, dispatch]);
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>{translate('AppUpdated')}</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<div>
|
||||
<InlineMarkdown
|
||||
data={translate('AppUpdatedVersion', { version })}
|
||||
blockClassName={styles.version}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{isPopulated && !error && !!update ? (
|
||||
<div>
|
||||
{update.changes ? (
|
||||
<div className={styles.maintenance}>
|
||||
{translate('MaintenanceRelease')}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{update.changes ? (
|
||||
<div>
|
||||
<div className={styles.changes}>{translate('WhatsNew')}</div>
|
||||
|
||||
<UpdateChanges
|
||||
title={translate('New')}
|
||||
changes={update.changes.new}
|
||||
/>
|
||||
|
||||
<UpdateChanges
|
||||
title={translate('Fixed')}
|
||||
changes={update.changes.fixed}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{!isPopulated && !error ? <LoadingIndicator /> : null}
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button onPress={handleSeeChangesPress}>
|
||||
{translate('RecentChanges')}
|
||||
</Button>
|
||||
|
||||
<Button kind={kinds.PRIMARY} onPress={onModalClose}>
|
||||
{translate('Reload')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
);
|
||||
}
|
||||
|
||||
export default AppUpdatedModalContent;
|
||||
@@ -1,78 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { fetchUpdates } from 'Store/Actions/systemActions';
|
||||
import AppUpdatedModalContent from './AppUpdatedModalContent';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.app.version,
|
||||
(state) => state.app.prevVersion,
|
||||
(state) => state.system.updates,
|
||||
(version, prevVersion, updates) => {
|
||||
const {
|
||||
isPopulated,
|
||||
error,
|
||||
items
|
||||
} = updates;
|
||||
|
||||
return {
|
||||
version,
|
||||
prevVersion,
|
||||
isPopulated,
|
||||
error,
|
||||
items
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function createMapDispatchToProps(dispatch, props) {
|
||||
return {
|
||||
dispatchFetchUpdates() {
|
||||
dispatch(fetchUpdates());
|
||||
},
|
||||
|
||||
onSeeChangesPress() {
|
||||
window.location = `${window.Prowlarr.urlBase}/system/updates`;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class AppUpdatedModalContentConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
componentDidMount() {
|
||||
this.props.dispatchFetchUpdates();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.version !== this.props.version) {
|
||||
this.props.dispatchFetchUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
dispatchFetchUpdates,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<AppUpdatedModalContent {...otherProps} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AppUpdatedModalContentConnector.propTypes = {
|
||||
version: PropTypes.string.isRequired,
|
||||
dispatchFetchUpdates: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, createMapDispatchToProps)(AppUpdatedModalContentConnector);
|
||||
@@ -1,13 +1,9 @@
|
||||
import React, { Fragment, ReactNode, useCallback, useEffect } from 'react';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import themes from 'Styles/Themes';
|
||||
import AppState from './State/AppState';
|
||||
|
||||
interface ApplyThemeProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
function createThemeSelector() {
|
||||
return createSelector(
|
||||
(state: AppState) => state.settings.ui.item.theme || window.Prowlarr.theme,
|
||||
@@ -17,7 +13,7 @@ function createThemeSelector() {
|
||||
);
|
||||
}
|
||||
|
||||
function ApplyTheme({ children }: ApplyThemeProps) {
|
||||
function ApplyTheme() {
|
||||
const theme = useSelector(createThemeSelector());
|
||||
|
||||
const updateCSSVariables = useCallback(() => {
|
||||
@@ -31,7 +27,7 @@ function ApplyTheme({ children }: ApplyThemeProps) {
|
||||
updateCSSVariables();
|
||||
}, [updateCSSVariables, theme]);
|
||||
|
||||
return <Fragment>{children}</Fragment>;
|
||||
return null;
|
||||
}
|
||||
|
||||
export default ApplyTheme;
|
||||
|
||||
+16
-27
@@ -1,5 +1,4 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import Button from 'Components/Link/Button';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
@@ -10,36 +9,31 @@ import { kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './ConnectionLostModal.css';
|
||||
|
||||
function ConnectionLostModal(props) {
|
||||
const {
|
||||
isOpen,
|
||||
onModalClose
|
||||
} = props;
|
||||
interface ConnectionLostModalProps {
|
||||
isOpen: boolean;
|
||||
}
|
||||
|
||||
function ConnectionLostModal(props: ConnectionLostModalProps) {
|
||||
const { isOpen } = props;
|
||||
|
||||
const handleModalClose = useCallback(() => {
|
||||
location.reload();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
{translate('ConnectionLost')}
|
||||
</ModalHeader>
|
||||
<Modal isOpen={isOpen} onModalClose={handleModalClose}>
|
||||
<ModalContent onModalClose={handleModalClose}>
|
||||
<ModalHeader>{translate('ConnectionLost')}</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<div>
|
||||
{translate('ConnectionLostToBackend')}
|
||||
</div>
|
||||
<div>{translate('ConnectionLostToBackend')}</div>
|
||||
|
||||
<div className={styles.automatic}>
|
||||
{translate('ConnectionLostReconnect')}
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
kind={kinds.PRIMARY}
|
||||
onPress={onModalClose}
|
||||
>
|
||||
<Button kind={kinds.PRIMARY} onPress={handleModalClose}>
|
||||
{translate('Reload')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
@@ -48,9 +42,4 @@ function ConnectionLostModal(props) {
|
||||
);
|
||||
}
|
||||
|
||||
ConnectionLostModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default ConnectionLostModal;
|
||||
@@ -1,12 +0,0 @@
|
||||
import { connect } from 'react-redux';
|
||||
import ConnectionLostModal from './ConnectionLostModal';
|
||||
|
||||
function createMapDispatchToProps(dispatch, props) {
|
||||
return {
|
||||
onModalClose() {
|
||||
location.reload();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(undefined, createMapDispatchToProps)(ConnectionLostModal);
|
||||
@@ -1,5 +1,6 @@
|
||||
import Column from 'Components/Table/Column';
|
||||
import SortDirection from 'Helpers/Props/SortDirection';
|
||||
import { FilterBuilderProp } from './AppState';
|
||||
import { FilterBuilderProp, PropertyFilter } from './AppState';
|
||||
|
||||
export interface Error {
|
||||
responseJSON: {
|
||||
@@ -18,10 +19,18 @@ export interface AppSectionSaveState {
|
||||
}
|
||||
|
||||
export interface PagedAppSectionState {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
totalPages: number;
|
||||
totalRecords?: number;
|
||||
}
|
||||
export interface TableAppSectionState {
|
||||
columns: Column[];
|
||||
}
|
||||
|
||||
export interface AppSectionFilterState<T> {
|
||||
selectedFilterKey: string;
|
||||
filters: PropertyFilter[];
|
||||
filterBuilderProps: FilterBuilderProp<T>[];
|
||||
}
|
||||
|
||||
@@ -38,6 +47,7 @@ export interface AppSectionItemState<T> {
|
||||
isFetching: boolean;
|
||||
isPopulated: boolean;
|
||||
error: Error;
|
||||
pendingChanges: Partial<T>;
|
||||
item: T;
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,10 @@ export interface CustomFilter {
|
||||
}
|
||||
|
||||
export interface AppSectionState {
|
||||
isConnected: boolean;
|
||||
isReconnecting: boolean;
|
||||
version: string;
|
||||
prevVersion?: string;
|
||||
dimensions: {
|
||||
isSmallScreen: boolean;
|
||||
width: number;
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import SystemStatus from 'typings/SystemStatus';
|
||||
import { AppSectionItemState } from './AppSectionState';
|
||||
import Update from 'typings/Update';
|
||||
import AppSectionState, { AppSectionItemState } from './AppSectionState';
|
||||
|
||||
export type SystemStatusAppState = AppSectionItemState<SystemStatus>;
|
||||
export type UpdateAppState = AppSectionState<Update>;
|
||||
|
||||
interface SystemAppState {
|
||||
status: SystemStatusAppState;
|
||||
updates: UpdateAppState;
|
||||
}
|
||||
|
||||
export default SystemAppState;
|
||||
|
||||
Reference in New Issue
Block a user