Added automatic refresh on mercure events

This commit is contained in:
Alejandro Celaya
2020-04-18 10:50:01 +02:00
parent 0f73cb9f8c
commit a22a1938c1
16 changed files with 132 additions and 54 deletions

View File

@@ -4,11 +4,11 @@ import { head, isEmpty, keys, values } from 'ramda';
import React, { useState, useEffect } from 'react';
import qs from 'qs';
import PropTypes from 'prop-types';
import { EventSourcePolyfill as EventSource } from 'event-source-polyfill';
import { serverType } from '../servers/prop-types';
import SortingDropdown from '../utils/SortingDropdown';
import { determineOrderDir } from '../utils/utils';
import { MercureInfoType } from '../mercure/reducers/mercureInfo';
import { bindToMercureTopic } from '../mercure/helpers';
import { shortUrlType } from './reducers/shortUrlsList';
import { shortUrlsListParamsType } from './reducers/shortUrlsListParams';
import './ShortUrlsList.scss';
@@ -114,27 +114,7 @@ const ShortUrlsList = (ShortUrlsRow) => {
return resetShortUrlParams;
}, []);
useEffect(() => {
const { mercureHubUrl, token, loading, error } = mercureInfo;
if (loading || error) {
return undefined;
}
const hubUrl = new URL(mercureHubUrl);
hubUrl.searchParams.append('topic', 'https://shlink.io/new-visit');
const es = new EventSource(hubUrl, {
headers: {
Authorization: `Bearer ${token}`,
},
});
// es.onmessage = pipe(JSON.parse, createNewVisit);
es.onmessage = ({ data }) => createNewVisit(JSON.parse(data));
return () => es.close();
}, [ mercureInfo ]);
useEffect(bindToMercureTopic(mercureInfo, 'https://shlink.io/new-visit', createNewVisit), [ mercureInfo ]);
return (
<React.Fragment>

View File

@@ -3,22 +3,28 @@ import PropTypes from 'prop-types';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faInfoCircle as infoIcon } from '@fortawesome/free-solid-svg-icons';
import { UncontrolledTooltip } from 'reactstrap';
import classNames from 'classnames';
import { serverType } from '../../servers/prop-types';
import { shortUrlType } from '../reducers/shortUrlsList';
import './ShortUrlVisitsCount.scss';
import VisitStatsLink from './VisitStatsLink';
import './ShortUrlVisitsCount.scss';
const propTypes = {
visitsCount: PropTypes.number.isRequired,
shortUrl: shortUrlType,
selectedServer: serverType,
active: PropTypes.bool,
};
const ShortUrlVisitsCount = ({ visitsCount, shortUrl, selectedServer }) => {
const ShortUrlVisitsCount = ({ visitsCount, shortUrl, selectedServer, active = false }) => {
const maxVisits = shortUrl && shortUrl.meta && shortUrl.meta.maxVisits;
const visitsLink = (
<VisitStatsLink selectedServer={selectedServer} shortUrl={shortUrl}>
<strong>{visitsCount}</strong>
<strong
className={classNames('short-url-visits-count__amount', { 'short-url-visits-count__amount--big': active })}
>
{visitsCount}
</strong>
</VisitStatsLink>
);

View File

@@ -1,3 +1,12 @@
.short-urls-visits-count__max-visits-control {
cursor: help;
}
.short-url-visits-count__amount {
transition: transform .3s ease;
display: inline-block;
}
.short-url-visits-count__amount--big {
transform: scale(1.5);
}

View File

@@ -1,5 +1,5 @@
import { isEmpty } from 'ramda';
import React from 'react';
import React, { useEffect, useRef } from 'react';
import Moment from 'react-moment';
import PropTypes from 'prop-types';
import { ExternalLink } from 'react-external-link';
@@ -26,7 +26,10 @@ const ShortUrlsRow = (
useStateFlagTimeout
) => {
const ShortUrlsRowComp = ({ shortUrl, selectedServer, refreshList, shortUrlsListParams }) => {
const [ copiedToClipboard, setCopiedToClipboard ] = useStateFlagTimeout(false);
const [ copiedToClipboard, setCopiedToClipboard ] = useStateFlagTimeout();
const [ active, setActive ] = useStateFlagTimeout(false, 500);
const isFirstRun = useRef(true);
const renderTags = (tags) => {
if (isEmpty(tags)) {
return <i className="indivisible"><small>No tags</small></i>;
@@ -44,6 +47,14 @@ const ShortUrlsRow = (
));
};
useEffect(() => {
if (isFirstRun.current) {
isFirstRun.current = false;
} else {
setActive(true);
}
}, [ shortUrl.visitsCount ]);
return (
<tr className="short-urls-row">
<td className="indivisible short-urls-row__cell" data-th="Created at: ">
@@ -69,6 +80,7 @@ const ShortUrlsRow = (
visitsCount={shortUrl.visitsCount}
shortUrl={shortUrl}
selectedServer={selectedServer}
active={active}
/>
</td>
<td className="short-urls-row__cell">

View File

@@ -35,6 +35,7 @@
}
}
}
.short-urls-row__cell--break {
word-break: break-all;
}
@@ -43,6 +44,10 @@
position: relative;
}
.short-urls-row__cell--big {
transform: scale(1.5);
}
.short-urls-row__copy-btn {
cursor: pointer;
font-size: 1.2rem;

View File

@@ -0,0 +1,9 @@
import { isNil } from 'ramda';
export const shortUrlMatches = (shortUrl, shortCode, domain) => {
if (isNil(domain)) {
return shortUrl.shortCode === shortCode && !shortUrl.domain;
}
return shortUrl.shortCode === shortCode && shortUrl.domain === domain;
};

View File

@@ -1,7 +1,8 @@
import { handleActions } from 'redux-actions';
import { assoc, assocPath, isNil, reject } from 'ramda';
import { assoc, assocPath, reject } from 'ramda';
import PropTypes from 'prop-types';
import { CREATE_SHORT_URL_VISIT } from '../../visits/reducers/shortUrlVisits';
import { shortUrlMatches } from '../helpers';
import { SHORT_URL_TAGS_EDITED } from './shortUrlTags';
import { SHORT_URL_DELETED } from './shortUrlDeletion';
import { SHORT_URL_META_EDITED, shortUrlMetaType } from './shortUrlMeta';
@@ -29,14 +30,6 @@ const initialState = {
error: false,
};
const shortUrlMatches = (shortUrl, shortCode, domain) => {
if (isNil(domain)) {
return shortUrl.shortCode === shortCode && !shortUrl.domain;
}
return shortUrl.shortCode === shortCode && shortUrl.domain === domain;
};
const setPropFromActionOnMatchingShortUrl = (prop) => (state, { shortCode, domain, [prop]: propValue }) => assocPath(
[ 'shortUrls', 'data' ],
state.shortUrls.data.map(
@@ -59,7 +52,7 @@ export default handleActions({
[SHORT_URL_EDITED]: setPropFromActionOnMatchingShortUrl('longUrl'),
[CREATE_SHORT_URL_VISIT]: (state, { shortUrl: { shortCode, domain, visitsCount } }) => assocPath(
[ 'shortUrls', 'data' ],
state.shortUrls.data.map(
state.shortUrls && state.shortUrls.data && state.shortUrls.data.map(
(shortUrl) => shortUrlMatches(shortUrl, shortCode, domain)
? assoc('visitsCount', visitsCount, shortUrl)
: shortUrl