diff --git a/packages/page-calendar/src/DayHour.tsx b/packages/page-calendar/src/DayHour.tsx index aeb197fb76..6869c1f214 100644 --- a/packages/page-calendar/src/DayHour.tsx +++ b/packages/page-calendar/src/DayHour.tsx @@ -1,7 +1,7 @@ // Copyright 2017-2020 @polkadot/app-calendar authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { EntryInfo } from './types'; +import type { EntryInfoTyped } from './types'; import React, { useMemo } from 'react'; import styled from 'styled-components'; @@ -14,23 +14,25 @@ interface Props { hour: number; index: number; minutes: number; - scheduled: EntryInfo[]; + scheduled: EntryInfoTyped[]; } const MN_TO_MS = 60 * 1000; const HR_TO_MS = 60 * MN_TO_MS; +function filterEntries (date: Date, minutes: number, index: number, scheduled: EntryInfoTyped[]): EntryInfoTyped[] { + const start = date.getTime() + (index * HR_TO_MS); + const end = start + HR_TO_MS; + const explicit = start + (minutes * MN_TO_MS); + + return scheduled + .filter(({ dateTime }) => dateTime >= explicit && dateTime < end) + .sort((a, b) => (a.dateTime - b.dateTime) || a.type.localeCompare(b.type)); +} + function DayHour ({ className = '', date, hour, index, minutes, scheduled }: Props): React.ReactElement | null { const filtered = useMemo( - (): EntryInfo[] => { - const start = date.getTime() + (index * HR_TO_MS); - const end = start + HR_TO_MS; - const explicit = start + (minutes * MN_TO_MS); - - return scheduled - .filter(({ dateTime }) => dateTime >= explicit && dateTime < end) - .sort((a, b) => (a.dateTime - b.dateTime) || a.type.localeCompare(b.type)); - }, + () => filterEntries(date, minutes, index, scheduled), [date, index, minutes, scheduled] ); diff --git a/packages/page-calendar/src/DayItem.tsx b/packages/page-calendar/src/DayItem.tsx index b2923ea43a..7968543aba 100644 --- a/packages/page-calendar/src/DayItem.tsx +++ b/packages/page-calendar/src/DayItem.tsx @@ -1,7 +1,7 @@ // Copyright 2017-2020 @polkadot/app-calendar authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { EntryInfo } from './types'; +import type { EntryInfoTyped } from './types'; import React, { useMemo, useState, useCallback } from 'react'; import styled from 'styled-components'; @@ -13,7 +13,7 @@ import { useTranslation } from './translate'; interface Props { className?: string; - item: EntryInfo; + item: EntryInfoTyped; } // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -21,41 +21,43 @@ function assertUnreachable (x: never): never { throw new Error('We cannot get here'); } +function exportCalendar (date: Date, description: string): void { + 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); +} + function DayItem ({ className, item: { date, info, type } }: Props): React.ReactElement { const { t } = useTranslation(); const [description, setDescription] = useState(''); 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); - }, + () => exportCalendar(date, description), [description, date] ); @@ -180,7 +182,7 @@ export default React.memo(styled(DayItem)` padding: 0; position: absolute; right: 1.5rem; - + .ui--Icon { width: 0.7rem; height: 0.7rem; diff --git a/packages/page-calendar/src/types.ts b/packages/page-calendar/src/types.ts index b1eb5d0075..a83ce35cc1 100644 --- a/packages/page-calendar/src/types.ts +++ b/packages/page-calendar/src/types.ts @@ -1,7 +1,7 @@ // Copyright 2017-2020 @polkadot/app-calendar authors & contributors // SPDX-License-Identifier: Apache-2.0 -import BN from 'bn.js'; +import type BN from 'bn.js'; export type EntryType = 'councilElection' | 'councilMotion' | 'democracyDispatch' | 'democracyLaunch' | 'referendumDispatch' | 'referendumVote' | 'scheduler' | 'societyChallenge' | 'societyRotate'| 'stakingEpoch' | 'stakingEra' | 'stakingSlash' | 'treasurySpend'; @@ -12,6 +12,9 @@ export interface EntryInfo { dateTime: number; info: string | BN | null; isPending?: boolean; +} + +export interface EntryInfoTyped extends EntryInfo { type: EntryType; } diff --git a/packages/page-calendar/src/useScheduled.ts b/packages/page-calendar/src/useScheduled.ts index f8f14970b8..2dac5764b3 100644 --- a/packages/page-calendar/src/useScheduled.ts +++ b/packages/page-calendar/src/useScheduled.ts @@ -4,7 +4,7 @@ import type { DeriveCollectiveProposal, DeriveDispatch, DeriveReferendumExt, DeriveSessionProgress } from '@polkadot/api-derive/types'; import type { Option } from '@polkadot/types'; import type { BlockNumber, EraIndex, Scheduled, UnappliedSlash } from '@polkadot/types/interfaces'; -import type { EntryInfo, EntryType } from './types'; +import type { EntryInfo, EntryInfoTyped, EntryType } from './types'; import BN from 'bn.js'; import { useEffect, useState } from 'react'; @@ -38,8 +38,7 @@ function createConstDurations (bestNumber: BlockNumber, blockTime: number, items ...newDate(blocks, blockTime), blockNumber: bestNumber.add(blocks), blocks, - info: null, - type + info: null }]]; }); } @@ -58,8 +57,7 @@ function createCouncilMotions (bestNumber: BlockNumber, blockTime: number, motio ...newDate(blocks, blockTime), blockNumber: votes.end, blocks, - info: `${hashStr.substr(0, 6)}…${hashStr.substr(-4)}`, - type: 'councilMotion' + info: `${hashStr.substr(0, 6)}…${hashStr.substr(-4)}` }; }) .filter((item): item is EntryInfo => !!item) @@ -74,8 +72,7 @@ function createDispatches (bestNumber: BlockNumber, blockTime: number, dispatche ...newDate(blocks, blockTime), blockNumber: at, blocks, - info: index, - type: 'democracyDispatch' + info: index }]]; }); } @@ -89,16 +86,14 @@ function createReferendums (bestNumber: BlockNumber, blockTime: number, referend ...newDate(voteBlocks, blockTime), blockNumber: bestNumber.add(voteBlocks), blocks: voteBlocks, - info: index, - type: 'referendumVote' + info: index }]]); result.push(['referendumDispatch', [{ ...newDate(enactBlocks, blockTime), blockNumber: bestNumber.add(enactBlocks), blocks: enactBlocks, info: index, - isPending: true, - type: 'referendumDispatch' + isPending: true }]]); return result; @@ -121,8 +116,7 @@ function createStakingInfo (bestNumber: BlockNumber, blockTime: number, sessionI ...newDate(blocks, blockTime), blockNumber: bestNumber.add(blocks), blocks, - info: eraIndex, - type: 'stakingSlash' + info: eraIndex }; }) : []; @@ -132,15 +126,13 @@ function createStakingInfo (bestNumber: BlockNumber, blockTime: number, sessionI ...newDate(blocksSes, blockTime), blockNumber: bestNumber.add(blocksSes), blocks: blocksSes, - info: sessionInfo.currentIndex.add(BN_ONE), - type: 'stakingEpoch' + info: sessionInfo.currentIndex.add(BN_ONE) }]], ['stakingEra', [{ ...newDate(blocksEra, blockTime), blockNumber: bestNumber.add(blocksEra), blocks: blocksEra, - info: sessionInfo.activeEra.add(BN_ONE), - type: 'stakingEra' + info: sessionInfo.activeEra.add(BN_ONE) }]], ['stakingSlash', slashEras] ]; @@ -167,8 +159,7 @@ function createScheduled (bestNumber: BlockNumber, blockTime: number, scheduled: ? idOrNull.isAscii ? idOrNull.toUtf8() : idOrNull.toHex() - : null, - type: 'scheduler' + : null }); return items; @@ -176,10 +167,16 @@ function createScheduled (bestNumber: BlockNumber, blockTime: number, scheduled: }, [])]]; } -function addFiltered (state: EntryInfo[], types: [EntryType, EntryInfo[]][]): EntryInfo[] { - return types.reduce((state: EntryInfo[], [typeFilter, items]): EntryInfo[] => - state.filter(({ type }) => type !== typeFilter).concat(...items), state - ); +function addFiltered (state: EntryInfoTyped[], types: [EntryType, EntryInfo[]][]): EntryInfoTyped[] { + return types.reduce((state: EntryInfoTyped[], [typeFilter, items]): EntryInfoTyped[] => { + return state + .filter(({ type }) => type !== typeFilter) + .concat(...items.map((item): EntryInfoTyped => { + (item as EntryInfoTyped).type = typeFilter; + + return item as EntryInfoTyped; + })); + }, state); } // TODO council votes, tips closing @@ -193,7 +190,7 @@ export default function useScheduled (): EntryInfo[] { const scheduled = useCall(api.query.scheduler?.agenda.entries); const sessionInfo = useCall(api.query.staking && api.derive.session?.progress); const slashes = useCall(api.query.staking?.unappliedSlashes.entries); - const [state, setState] = useState([]); + const [state, setState] = useState([]); useEffect((): void => { bestNumber && dispatches && setState((state) =>