Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

♻️ Use context for ticker and settings #410

Merged
merged 3 commits into from
Nov 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 8 additions & 82 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,87 +1,13 @@
import { FC, useEffect, useState } from 'react'
import { Container, Dimmer, Loader } from 'semantic-ui-react'
import { Settings, Ticker } from './lib/types'
import { getInit } from './lib/api'
import ErrorView from './views/ErrorView'
import ActiveView from './views/ActiveView'
import InactiveView from './views/InactiveView'
import { FC } from 'react'
import { TickerProvider } from './components/useTicker'
import Ticker from './Ticker'

const App: FC = () => {
const [ticker, setTicker] = useState<Ticker | null>(null)
const [settings, setSettings] = useState<Settings>()
const [isLoading, setIsLoading] = useState<boolean>(true)
const [isOffline, setIsOffline] = useState<boolean>(false)
const [gotError, setGotError] = useState<boolean>(false)

const fetchInit = () => {
getInit()
.then(response => {
if (response.data.settings) {
setSettings(response.data.settings)
}

if (response.data.ticker?.active) {
setTicker(response.data.ticker)
}

setIsLoading(false)
})
.catch(error => {
if (error instanceof TypeError) {
setIsOffline(true)
} else {
setGotError(true)
}
setIsLoading(false)
})
}

useEffect(() => {
fetchInit()
// This should only be executed once on load (~ componentDidMount)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

useEffect(() => {
if (ticker?.title) {
document.title = ticker.title
}
}, [ticker])

if (isLoading) {
return (
<Container>
<Dimmer active>
<Loader content="Loading" size="large" />
</Dimmer>
</Container>
)
}

if (gotError) {
return (
<ErrorView message="There seems to be a problem connecting to the server." />
)
}

if (isOffline) {
return <ErrorView message="It seems that you are offline." />
}

if (ticker?.active) {
return (
<ActiveView
refreshInterval={settings?.refresh_interval || 0}
ticker={ticker}
/>
)
}

if (ticker === null && settings?.inactive_settings !== undefined) {
return <InactiveView settings={settings.inactive_settings} />
}

return <div>...</div>
return (
<TickerProvider>
<Ticker />
</TickerProvider>
)
}

export default App
25 changes: 17 additions & 8 deletions src/App.test.tsx → src/Ticker.test.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import App from './App'
import Ticker from './Ticker'
import { render, screen } from '@testing-library/react'
import * as api from './lib/api'
import { Settings, Ticker } from './lib/types'
import { Settings, Ticker as TickerType } from './lib/types'
import { TickerProvider } from './components/useTicker'

describe('App', function () {
describe('Ticker', function () {
const initSettings = {
refresh_interval: 1000,
inactive_settings: {
Expand All @@ -30,11 +31,19 @@ describe('App', function () {
twitter: 'systemli',
facebook: 'betternot',
},
} as Ticker
} as TickerType

const renderTicker = () => {
return render(
<TickerProvider>
<Ticker />
</TickerProvider>
)
}

test('renders OfflineView', async function () {
jest.spyOn(api, 'getInit').mockRejectedValue(new TypeError())
render(<App />)
renderTicker()

expect(screen.getByText('Loading')).toBeInTheDocument()

Expand All @@ -51,7 +60,7 @@ describe('App', function () {
'The server responses with an error: Internal Server Error (500)'
)
)
render(<App />)
renderTicker()

expect(screen.getByText('Loading')).toBeInTheDocument()

Expand All @@ -69,7 +78,7 @@ describe('App', function () {
ticker: null,
},
})
render(<App />)
renderTicker()

expect(screen.getByText('Loading')).toBeInTheDocument()

Expand All @@ -96,7 +105,7 @@ describe('App', function () {
window.IntersectionObserver = jest
.fn()
.mockImplementation(intersectionObserverMock)
render(<App />)
renderTicker()

expect(screen.getByText('Loading')).toBeInTheDocument()

Expand Down
48 changes: 48 additions & 0 deletions src/Ticker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { FC, useEffect } from 'react'
import { Container, Dimmer, Loader } from 'semantic-ui-react'
import ErrorView from './views/ErrorView'
import ActiveView from './views/ActiveView'
import InactiveView from './views/InactiveView'
import useTicker from './components/useTicker'

const Ticker: FC = () => {
const { ticker, settings, isLoading, isOffline, hasError } = useTicker()

useEffect(() => {
if (ticker?.title) {
document.title = ticker.title
}
}, [ticker])

if (isLoading) {
return (
<Container>
<Dimmer active>
<Loader content="Loading" size="large" />
</Dimmer>
</Container>
)
}

if (hasError) {
return (
<ErrorView message="There seems to be a problem connecting to the server." />
)
}

if (isOffline) {
return <ErrorView message="It seems that you are offline." />
}

if (ticker?.active) {
return <ActiveView />
}

if (ticker === null && settings?.inactive_settings !== undefined) {
return <InactiveView settings={settings.inactive_settings} />
}

return <div>...</div>
}

export default Ticker
42 changes: 24 additions & 18 deletions src/components/About.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,50 +3,56 @@ import ReactMarkdown from 'react-markdown'
import { Button, Card, Icon, List, Modal } from 'semantic-ui-react'
import Credits from './Credits'
import DescriptionItem from './DescriptionItem'
import { DescriptionTypes, Ticker } from '../lib/types'
import { DescriptionTypes } from '../lib/types'
import { getAtomFeedUrl, getRssFeedUrl } from '../lib/api'
import useTicker from './useTicker'

interface Props {
ticker: Ticker
isModal?: boolean
}

const About: FC<Props> = props => {
const About: FC<Props> = ({ isModal }) => {
const { ticker } = useTicker()

if (!ticker) {
return null
}

const renderDescriptionList = () => (
<List>
{props.ticker.information.author && (
{ticker.information.author && (
<DescriptionItem
info={props.ticker.information.author}
info={ticker.information.author}
type={DescriptionTypes.Author}
/>
)}
{props.ticker.information.email && (
{ticker.information.email && (
<DescriptionItem
info={props.ticker.information.email}
info={ticker.information.email}
type={DescriptionTypes.Email}
/>
)}
{props.ticker.information.url && (
{ticker.information.url && (
<DescriptionItem
info={props.ticker.information.url}
info={ticker.information.url}
type={DescriptionTypes.Homepage}
/>
)}
{props.ticker.information.twitter && (
{ticker.information.twitter && (
<DescriptionItem
info={props.ticker.information.twitter}
info={ticker.information.twitter}
type={DescriptionTypes.Twitter}
/>
)}
{props.ticker.information.facebook && (
{ticker.information.facebook && (
<DescriptionItem
info={props.ticker.information.facebook}
info={ticker.information.facebook}
type={DescriptionTypes.Facebook}
/>
)}
{props.ticker.information.telegram && (
{ticker.information.telegram && (
<DescriptionItem
info={props.ticker.information.telegram}
info={ticker.information.telegram}
type={DescriptionTypes.Telegram}
/>
)}
Expand All @@ -60,7 +66,7 @@ const About: FC<Props> = props => {
</List>
)

if (props.isModal) {
if (isModal) {
return (
<Modal
closeIcon
Expand All @@ -73,7 +79,7 @@ const About: FC<Props> = props => {
>
<Modal.Header>About</Modal.Header>
<Modal.Content>
<ReactMarkdown>{props.ticker.description}</ReactMarkdown>
<ReactMarkdown>{ticker.description || ''}</ReactMarkdown>
</Modal.Content>
<Modal.Content>
{renderDescriptionList()}
Expand All @@ -90,7 +96,7 @@ const About: FC<Props> = props => {
<Card.Header>About</Card.Header>
</Card.Content>
<Card.Content>
<ReactMarkdown>{props.ticker.description}</ReactMarkdown>
<ReactMarkdown>{ticker.description || ''}</ReactMarkdown>
</Card.Content>
<Card.Content>{renderDescriptionList()}</Card.Content>
</Card>
Expand Down
12 changes: 7 additions & 5 deletions src/components/DynamicMetaTags.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { FC } from 'react'
import { Helmet } from 'react-helmet'
import { Ticker } from '../lib/types'
import useTicker from './useTicker'

interface Props {
ticker: Ticker
}
const DynamicMetaTags: FC = () => {
const { ticker } = useTicker()

if (!ticker) {
return null
}

const DynamicMetaTags: FC<Props> = ({ ticker }) => {
return (
<Helmet>
<meta content={ticker.description} name="description" />
Expand Down
2 changes: 1 addition & 1 deletion src/components/Map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const Map: FC<Props> = props => {

if (
features.length === 1 &&
// FIXME: Type is currently not defined by DefinitelyTyped?
// Type is currently not defined by DefinitelyTyped
// @ts-ignore
features[0].feature.geometry.type === 'Point'
) {
Expand Down
2 changes: 1 addition & 1 deletion src/components/MessageList.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ describe('MessageList', function () {
.fn()
.mockImplementation(intersectionObserverMock)

render(<MessageList refreshInterval={10} />)
render(<MessageList />)

expect(screen.getByText('Loading messages')).toBeInTheDocument()
expect(
Expand Down
16 changes: 9 additions & 7 deletions src/components/MessageList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@ import { Dimmer, Header, Icon, Loader, Segment } from 'semantic-ui-react'
import { Message as MessageType } from '../lib/types'
import Message from './Message'
import { getTimeline } from '../lib/api'
import useTicker from './useTicker'

interface Props {
refreshInterval: number
}

const MessageList: FC<Props> = props => {
const MessageList: FC = () => {
const [isLoading, setIsLoading] = useState<boolean>(true)
const [messages, setMessages] = useState<MessageType[]>([])
const [lastMessageReceived, setLastMessageReceived] = useState<boolean>(false)

const { settings } = useTicker()

const loadMoreSpinnerRef = useRef<HTMLDivElement>(null)

const fetchMessages = useCallback(() => {
Expand Down Expand Up @@ -98,10 +97,13 @@ const MessageList: FC<Props> = props => {

// periodically fetch new messages
useEffect(() => {
const interval = setInterval(() => fetchMessages(), props.refreshInterval)
const interval = setInterval(
() => fetchMessages(),
settings?.refresh_interval || 60000
)

return () => clearInterval(interval)
}, [fetchMessages, messages, props.refreshInterval])
}, [fetchMessages, messages, settings?.refresh_interval])

const renderPlaceholder = () => (
<Segment placeholder>
Expand Down
Loading