add "save event to calendar" functionality on calendar page (#4068)

* add calendar event save

* some code beautification and dynamic filename

* fix lint errors

* fix PR comments

Co-authored-by: Nikolaos Kontakis <n.kontakis@intralot.com>
This commit is contained in:
Nikos Kontakis 2020-11-25 14:07:17 +02:00 committed by GitHub
parent f790eabe52
commit 29b02c416c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 97 additions and 18 deletions

View File

@ -3,9 +3,11 @@
import type { EntryInfo } from './types';
import React, { useMemo } from 'react';
import React, { useMemo, useState, useCallback } from 'react';
import styled from 'styled-components';
import { formatNumber, isString } from '@polkadot/util';
import { dateCalendarFormat } from './util';
import { Button } from '@polkadot/react-components';
import { useTranslation } from './translate';
@ -22,6 +24,41 @@ function assertUnreachable (x: never): never {
function DayItem ({ className, item: { date, info, type } }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const [description, setDescription] = useState<string>('');
const _exportCal = useCallback(
() => {
const startDate = dateCalendarFormat(date);
// For now just add 1 hour for each event
const endDate = dateCalendarFormat(new Date(new Date(date).setHours(new Date(date).getHours() + 1)));
const calData =
'BEGIN:VCALENDAR\n' +
'CALSCALE:GREGORIAN\n' +
'METHOD:PUBLISH\n' +
'PRODID:-//Test Cal//EN\n' +
'VERSION:2.0\n' +
'BEGIN:VEVENT\n' +
'UID:test-1\n' +
'DTSTART;VALUE=DATE:' + startDate + '\n' +
'DTEND;VALUE=DATE:' + endDate + '\n' +
'SUMMARY:' + description + '\n' +
'DESCRIPTION:' + description + '\n' +
'END:VEVENT\n' +
'END:VCALENDAR';
const fileNameIcs = encodeURI(description) + '.ics';
const data = new File([calData], fileNameIcs, { type: 'text/plain' });
const anchor = window.document.createElement('a');
anchor.href = window.URL.createObjectURL(data);
anchor.download = fileNameIcs;
document.body.appendChild(anchor);
anchor.click();
document.body.removeChild(anchor);
window.URL.revokeObjectURL(anchor.href);
},
[description, date]
);
const desc = useMemo(
(): React.ReactNode => {
const id: string | null = info && (
@ -46,54 +83,70 @@ function DayItem ({ className, item: { date, info, type } }: Props): React.React
: ['treasurySpend'].includes(type)
? <div className='itemLink'><a href='#/treasury'>{t<string>('via Treasury')}</a></div>
: undefined;
let s = '';
switch (type) {
case 'councilElection':
return <><div className='itemDesc'>{t<string>('Election of new council candidates')}</div>{typeLink}</>;
s = t<string>('Election of new council candidates');
break;
case 'councilMotion':
return <><div className='itemDesc'>{t<string>('Voting ends on council motion {{id}}', { replace: { id } })}</div>{typeLink}</>;
s = t<string>('Voting ends on council motion {{id}}', { replace: { id } });
break;
case 'democracyDispatch':
return <><div className='itemDesc'>{t<string>('Enactment of the result of referendum {{id}}', { replace: { id } })}</div>{typeLink}</>;
s = t<string>('Enactment of the result of referendum {{id}}', { replace: { id } });
break;
case 'democracyLaunch':
return <><div className='itemDesc'>{t<string>('Start of the next referendum voting period')}</div>{typeLink}</>;
s = t<string>('Start of the next referendum voting period');
break;
case 'referendumDispatch':
return <><div className='itemDesc'>{t<string>('Potential dispatch of referendum {{id}} (if passed)', { replace: { id } })}</div>{typeLink}</>;
s = t<string>('Potential dispatch of referendum {{id}} (if passed)', { replace: { id } });
break;
case 'referendumVote':
return <><div className='itemDesc'>{t<string>('Voting ends for referendum {{id}}', { replace: { id } })}</div>{typeLink}</>;
s = t<string>('Voting ends for referendum {{id}}', { replace: { id } });
break;
case 'scheduler':
return <><div className='itemDesc'>{
id
? t<string>('Execute named scheduled task {{id}}', { replace: { id } })
: t<string>('Execute anonymous scheduled task')
}</div>{typeLink}</>;
s = id
? t<string>('Execute named scheduled task {{id}}', { replace: { id } })
: t<string>('Execute anonymous scheduled task');
break;
case 'stakingEpoch':
return <><div className='itemDesc'>{t<string>('Start of a new staking session {{id}}', { replace: { id } })}</div>{typeLink}</>;
s = t<string>('Start of a new staking session {{id}}', { replace: { id } });
break;
case 'stakingEra':
return <><div className='itemDesc'>{t<string>('Start of a new staking era {{id}}', { replace: { id } })}</div>{typeLink}</>;
s = t<string>('Start of a new staking era {{id}}', { replace: { id } });
break;
case 'stakingSlash':
return <><div className='itemDesc'>{t<string>('Application of slashes from era {{id}}', { replace: { id } })}</div>{typeLink}</>;
s = t<string>('Application of slashes from era {{id}}', { replace: { id } });
break;
case 'treasurySpend':
return <><div className='itemDesc'>{t<string>('Start of next spending period')}</div>{typeLink}</>;
s = t<string>('Start of next spending period');
break;
case 'societyChallenge':
return <><div className='itemDesc'>{t<string>('Start of next membership challenge period')}</div>{typeLink}</>;
s = t<string>('Start of next membership challenge period');
break;
case 'societyRotate':
return <><div className='itemDesc'>{t<string>('Acceptance of new members and bids')}</div>{typeLink}</>;
s = t<string>('Acceptance of new members and bids');
break;
default:
return assertUnreachable(type);
}
setDescription(s);
return (<><div className='itemDesc'>{s}</div>{typeLink}</>);
},
[info, t, type]
);
@ -102,6 +155,13 @@ function DayItem ({ className, item: { date, info, type } }: Props): React.React
<div className={className}>
<div className='itemTime'>{date.toLocaleTimeString().split(':').slice(0, 2).join(':')}</div>
{desc}
{date && (
<Button
className='exportCal'
icon='calendar-plus'
onClick={_exportCal}
/>
)}
</div>
);
}
@ -116,6 +176,17 @@ export default React.memo(styled(DayItem)`
margin-left: 0.5rem;
}
.exportCal {
padding: 0;
position: absolute;
right: 1.5rem;
.ui--Icon {
width: 0.7rem;
height: 0.7rem;
}
}
.itemTime {
background: #999;
padding: 0 0.375rem;

View File

@ -54,3 +54,11 @@ export function getDateState (_dateMonth: Date, _dateSelected: Date): DateState
startClass: `start${DAYS[dateMonth.getDay()]}`
};
}
export function dateCalendarFormat (date: Date): string {
return new Date(date)
.toISOString()
.split('.')[0]
.replaceAll('-', '')
.replaceAll(':', '') + 'Z';
}