Convert Messages to TypeScript

pull/7640/head
Mark McDowall 2 months ago
parent 7c64911b6b
commit 0fdeb05663
No known key found for this signature in database

@ -11,6 +11,7 @@ import EpisodeFilesAppState from './EpisodeFilesAppState';
import EpisodesAppState from './EpisodesAppState';
import HistoryAppState from './HistoryAppState';
import InteractiveImportAppState from './InteractiveImportAppState';
import MessagesAppState from './MessagesAppState';
import OAuthAppState from './OAuthAppState';
import OrganizePreviewAppState from './OrganizePreviewAppState';
import ParseAppState from './ParseAppState';
@ -76,6 +77,7 @@ export interface AppSectionState {
error?: Error;
isPopulated: boolean;
};
messages: MessagesAppState;
}
interface AppState {

@ -0,0 +1,15 @@
import ModelBase from 'App/ModelBase';
import AppSectionState from 'App/State/AppSectionState';
export type MessageType = 'error' | 'info' | 'success' | 'warning';
export interface Message extends ModelBase {
hideAfter: number;
message: string;
name: string;
type: MessageType;
}
type MessagesAppState = AppSectionState<Message>;
export default MessagesAppState;

@ -1,70 +0,0 @@
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
import Icon from 'Components/Icon';
import { icons } from 'Helpers/Props';
import styles from './Message.css';
function getIconName(name) {
switch (name) {
case 'ApplicationUpdate':
return icons.RESTART;
case 'Backup':
return icons.BACKUP;
case 'CheckHealth':
return icons.HEALTH;
case 'EpisodeSearch':
return icons.SEARCH;
case 'Housekeeping':
return icons.HOUSEKEEPING;
case 'RefreshSeries':
return icons.REFRESH;
case 'RssSync':
return icons.RSS;
case 'SeasonSearch':
return icons.SEARCH;
case 'SeriesSearch':
return icons.SEARCH;
case 'UpdateSceneMapping':
return icons.REFRESH;
default:
return icons.SPINNER;
}
}
function Message(props) {
const {
name,
message,
type
} = props;
return (
<div className={classNames(
styles.message,
styles[type]
)}
>
<div className={styles.iconContainer}>
<Icon
name={getIconName(name)}
title={name}
/>
</div>
<div
className={styles.text}
>
{message}
</div>
</div>
);
}
Message.propTypes = {
name: PropTypes.string.isRequired,
message: PropTypes.string.isRequired,
type: PropTypes.string.isRequired
};
export default Message;

@ -0,0 +1,76 @@
import classNames from 'classnames';
import React, { useEffect, useMemo, useRef } from 'react';
import { useDispatch } from 'react-redux';
import { MessageType } from 'App/State/MessagesAppState';
import Icon, { IconName } from 'Components/Icon';
import { icons } from 'Helpers/Props';
import { hideMessage } from 'Store/Actions/appActions';
import styles from './Message.css';
interface MessageProps {
id: number;
hideAfter: number;
name: string;
message: string;
type: Extract<MessageType, keyof typeof styles>;
}
function Message({ id, hideAfter, name, message, type }: MessageProps) {
const dispatch = useDispatch();
const dismissTimeout = useRef<ReturnType<typeof setTimeout>>();
const icon: IconName = useMemo(() => {
switch (name) {
case 'ApplicationUpdate':
return icons.RESTART;
case 'Backup':
return icons.BACKUP;
case 'CheckHealth':
return icons.HEALTH;
case 'EpisodeSearch':
return icons.SEARCH;
case 'Housekeeping':
return icons.HOUSEKEEPING;
case 'RefreshSeries':
return icons.REFRESH;
case 'RssSync':
return icons.RSS;
case 'SeasonSearch':
return icons.SEARCH;
case 'SeriesSearch':
return icons.SEARCH;
case 'UpdateSceneMapping':
return icons.REFRESH;
default:
return icons.SPINNER;
}
}, [name]);
useEffect(() => {
if (hideAfter) {
dismissTimeout.current = setTimeout(() => {
dispatch(hideMessage({ id }));
dismissTimeout.current = undefined;
}, hideAfter * 1000);
}
return () => {
if (dismissTimeout.current) {
clearTimeout(dismissTimeout.current);
}
};
}, [id, hideAfter, message, type, dispatch]);
return (
<div className={classNames(styles.message, styles[type])}>
<div className={styles.iconContainer}>
<Icon name={icon} title={name} />
</div>
<div className={styles.text}>{message}</div>
</div>
);
}
export default Message;

@ -1,67 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { hideMessage } from 'Store/Actions/appActions';
import Message from './Message';
const mapDispatchToProps = {
hideMessage
};
class MessageConnector extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this._hideTimeoutId = null;
this.scheduleHideMessage(props.hideAfter);
}
componentDidUpdate() {
this.scheduleHideMessage(this.props.hideAfter);
}
//
// Control
scheduleHideMessage = (hideAfter) => {
if (this._hideTimeoutId) {
clearTimeout(this._hideTimeoutId);
}
if (hideAfter) {
this._hideTimeoutId = setTimeout(this.hideMessage, hideAfter * 1000);
}
};
hideMessage = () => {
this.props.hideMessage({ id: this.props.id });
};
//
// Render
render() {
return (
<Message
{...this.props}
/>
);
}
}
MessageConnector.propTypes = {
id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
hideAfter: PropTypes.number.isRequired,
hideMessage: PropTypes.func.isRequired
};
MessageConnector.defaultProps = {
// Hide messages after 60 seconds if there is no activity
// hideAfter: 60
};
export default connect(undefined, mapDispatchToProps)(MessageConnector);

@ -1,27 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import MessageConnector from './MessageConnector';
import styles from './Messages.css';
function Messages({ messages }) {
return (
<div className={styles.messages}>
{
messages.map((message) => {
return (
<MessageConnector
key={message.id}
{...message}
/>
);
})
}
</div>
);
}
Messages.propTypes = {
messages: PropTypes.arrayOf(PropTypes.object).isRequired
};
export default Messages;

@ -0,0 +1,28 @@
import React, { useMemo } from 'react';
import { useSelector } from 'react-redux';
import AppState from 'App/State/AppState';
import { Message as MessageModel } from 'App/State/MessagesAppState';
import Message from './Message';
import styles from './Messages.css';
function Messages() {
const items = useSelector((state: AppState) => state.app.messages.items);
const messages = useMemo(() => {
return items.reduce<MessageModel[]>((acc, item) => {
acc.unshift(item);
return acc;
}, []);
}, [items]);
return (
<div className={styles.messages}>
{messages.map((message) => {
return <Message key={message.id} {...message} />;
})}
</div>
);
}
export default Messages;

@ -1,16 +0,0 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import Messages from './Messages';
function createMapStateToProps() {
return createSelector(
(state) => state.app.messages.items,
(messages) => {
return {
messages: messages.slice().reverse()
};
}
);
}
export default connect(createMapStateToProps)(Messages);

@ -19,7 +19,7 @@ import { setIsSidebarVisible } from 'Store/Actions/appActions';
import dimensions from 'Styles/Variables/dimensions';
import HealthStatus from 'System/Status/Health/HealthStatus';
import translate from 'Utilities/String/translate';
import MessagesConnector from './Messages/MessagesConnector';
import Messages from './Messages/Messages';
import PageSidebarItem from './PageSidebarItem';
import styles from './PageSidebar.css';
@ -511,7 +511,7 @@ function PageSidebar({ isSidebarVisible, isSmallScreen }: PageSidebarProps) {
})}
</div>
<MessagesConnector />
<Messages />
</ScrollerComponent>
</div>
);

Loading…
Cancel
Save