123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185 |
- import { css, cx } from '@emotion/css';
- import React, { PureComponent } from 'react';
- import { Unsubscribable } from 'rxjs';
- import { PanelProps, DataFrameView, dateTimeFormat, GrafanaTheme2, textUtil } from '@grafana/data';
- import { RefreshEvent } from '@grafana/runtime';
- import { CustomScrollbar, stylesFactory } from '@grafana/ui';
- import config from 'app/core/config';
- import { DEFAULT_FEED_URL } from './constants';
- import { loadFeed } from './feed';
- import { PanelOptions } from './models.gen';
- import { NewsItem } from './types';
- import { feedToDataFrame } from './utils';
- interface Props extends PanelProps<PanelOptions> {}
- interface State {
- news?: DataFrameView<NewsItem>;
- isError?: boolean;
- }
- export class NewsPanel extends PureComponent<Props, State> {
- private refreshSubscription: Unsubscribable;
- constructor(props: Props) {
- super(props);
- this.refreshSubscription = this.props.eventBus.subscribe(RefreshEvent, this.loadChannel.bind(this));
- this.state = {};
- }
- componentDidMount(): void {
- this.loadChannel();
- }
- componentWillUnmount(): void {
- this.refreshSubscription.unsubscribe();
- }
- componentDidUpdate(prevProps: Props): void {
- if (this.props.options.feedUrl !== prevProps.options.feedUrl) {
- this.loadChannel();
- }
- }
- async loadChannel() {
- const { options } = this.props;
- try {
- const url = options.feedUrl || DEFAULT_FEED_URL;
- const feed = await loadFeed(url);
- const frame = feedToDataFrame(feed);
- this.setState({
- news: new DataFrameView<NewsItem>(frame),
- isError: false,
- });
- } catch (err) {
- console.error('Error Loading News', err);
- this.setState({
- news: undefined,
- isError: true,
- });
- }
- }
- render() {
- const { width } = this.props;
- const { showImage } = this.props.options;
- const { isError, news } = this.state;
- const styles = getStyles(config.theme2);
- const useWideLayout = width > 600;
- if (isError) {
- return <div>Error loading RSS feed.</div>;
- }
- if (!news) {
- return <div>Loading...</div>;
- }
- return (
- <CustomScrollbar autoHeightMin="100%" autoHeightMax="100%">
- {news.map((item, index) => {
- return (
- <article key={index} className={cx(styles.item, useWideLayout && styles.itemWide)}>
- {showImage && item.ogImage && (
- <a
- tabIndex={-1}
- href={textUtil.sanitizeUrl(item.link)}
- target="_blank"
- rel="noopener noreferrer"
- className={cx(styles.socialImage, useWideLayout && styles.socialImageWide)}
- aria-hidden
- >
- <img src={item.ogImage} alt={item.title} />
- </a>
- )}
- <div className={styles.body}>
- <time className={styles.date} dateTime={dateTimeFormat(item.date, { format: 'MMM DD' })}>
- {dateTimeFormat(item.date, { format: 'MMM DD' })}{' '}
- </time>
- <a
- className={styles.link}
- href={textUtil.sanitizeUrl(item.link)}
- target="_blank"
- rel="noopener noreferrer"
- >
- <h3 className={styles.title}>{item.title}</h3>
- </a>
- <div className={styles.content} dangerouslySetInnerHTML={{ __html: textUtil.sanitize(item.content) }} />
- </div>
- </article>
- );
- })}
- </CustomScrollbar>
- );
- }
- }
- const getStyles = stylesFactory((theme: GrafanaTheme2) => ({
- container: css`
- height: 100%;
- `,
- item: css`
- display: flex;
- padding: ${theme.spacing(1)};
- position: relative;
- margin-bottom: 4px;
- margin-right: ${theme.spacing(1)};
- border-bottom: 2px solid ${theme.colors.border.weak};
- background: ${theme.colors.background.primary};
- flex-direction: column;
- flex-shrink: 0;
- `,
- itemWide: css`
- flex-direction: row;
- `,
- body: css`
- display: flex;
- flex-direction: column;
- `,
- socialImage: css`
- display: flex;
- align-items: center;
- margin-bottom: ${theme.spacing(1)};
- > img {
- width: 100%;
- border-radius: ${theme.shape.borderRadius(2)} ${theme.shape.borderRadius(2)} 0 0;
- }
- `,
- socialImageWide: css`
- margin-right: ${theme.spacing(2)};
- margin-bottom: 0;
- > img {
- width: 250px;
- border-radius: ${theme.shape.borderRadius()};
- }
- `,
- link: css`
- color: ${theme.colors.text.link};
- display: inline-block;
- &:hover {
- color: ${theme.colors.text.link};
- text-decoration: underline;
- }
- `,
- title: css`
- font-size: 16px;
- margin-bottom: ${theme.spacing(0.5)};
- `,
- content: css`
- p {
- margin-bottom: 4px;
- color: ${theme.colors.text};
- }
- `,
- date: css`
- margin-bottom: ${theme.spacing(0.5)};
- font-weight: 500;
- border-radius: 0 0 0 3px;
- color: ${theme.colors.text.secondary};
- `,
- }));
|