diff --git a/src/app.tsx b/src/app.tsx index 02f232f..892df7d 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -5,7 +5,7 @@ import {Changelog} from './changelog'; import {DataSourceEnum, SourceSelection} from './datasource/data_source'; import {Details} from './details/details'; import {EmbeddedDataSource, EmbeddedSourceSpec} from './datasource/embedded'; -import {FormattedMessage, injectIntl, WrappedComponentProps} from 'react-intl'; +import {FormattedMessage, useIntl} from 'react-intl'; import {getI18nMessage} from './util/error_i18n'; import {IndiInfo} from 'topola'; import {Intro} from './intro'; @@ -184,7 +184,7 @@ function hasUpdatedValues(state: T, changes: Partial | undefined) { ); } -function AppComponent(props: RouteComponentProps & WrappedComponentProps) { +export function App(props: RouteComponentProps) { /** State of the application. */ const [state, setState] = useState(AppState.INITIAL); /** Loaded data. */ @@ -207,6 +207,8 @@ function AppComponent(props: RouteComponentProps & WrappedComponentProps) { const [freezeAnimation, setFreezeAnimation] = useState(false); const [config, setConfig] = useState(DEFALUT_CONFIG); + const intl = useIntl(); + /** Sets the state with a new individual selection and chart type. */ function updateDisplay(newSelection: IndiInfo) { if ( @@ -226,7 +228,7 @@ function AppComponent(props: RouteComponentProps & WrappedComponentProps) { const uploadedDataSource = new UploadedDataSource(); const gedcomUrlDataSource = new GedcomUrlDataSource(); - const wikiTreeDataSource = new WikiTreeDataSource(props.intl); + const wikiTreeDataSource = new WikiTreeDataSource(intl); const embeddedDataSource = new EmbeddedDataSource(); function isNewData(newSourceSpec: DataSourceSpec, newSelection?: IndiInfo) { @@ -328,7 +330,7 @@ function AppComponent(props: RouteComponentProps & WrappedComponentProps) { setShowSidePanel(args.showSidePanel); setState(AppState.SHOWING_CHART); } catch (error: any) { - setErrorMessage(getI18nMessage(error, props.intl)); + setErrorMessage(getI18nMessage(error, intl)); } } else if ( state === AppState.SHOWING_CHART || @@ -346,7 +348,7 @@ function AppComponent(props: RouteComponentProps & WrappedComponentProps) { updateDisplay(newSelection); if (loadMoreFromWikitree) { try { - const data = await loadWikiTree(args.selection!.id, props.intl); + const data = await loadWikiTree(args.selection!.id, intl); const newSelection = getSelection(data.chartData, args.selection); setData(data); setSelection(newSelection); @@ -354,7 +356,7 @@ function AppComponent(props: RouteComponentProps & WrappedComponentProps) { } catch (error: any) { setState(AppState.SHOWING_CHART); displayErrorPopup( - props.intl.formatMessage( + intl.formatMessage( { id: 'error.failed_wikitree_load_more', defaultMessage: 'Failed to load data from WikiTree. {error}', @@ -410,7 +412,7 @@ function AppComponent(props: RouteComponentProps & WrappedComponentProps) { await downloadPdf(); } catch (e) { displayErrorPopup( - props.intl.formatMessage({ + intl.formatMessage({ id: 'error.failed_pdf', defaultMessage: 'Failed to generate PDF file.' + @@ -426,7 +428,7 @@ function AppComponent(props: RouteComponentProps & WrappedComponentProps) { await downloadPng(); } catch (e) { displayErrorPopup( - props.intl.formatMessage({ + intl.formatMessage({ id: 'error.failed_png', defaultMessage: 'Failed to generate PNG file.' + @@ -451,7 +453,7 @@ function AppComponent(props: RouteComponentProps & WrappedComponentProps) { case AppState.LOADING_MORE: const sidePanelTabs = [ { - menuItem: props.intl.formatMessage({ + menuItem: intl.formatMessage({ id: 'tab.info', defaultMessage: 'Info', }), @@ -460,7 +462,7 @@ function AppComponent(props: RouteComponentProps & WrappedComponentProps) { ), }, { - menuItem: props.intl.formatMessage({ + menuItem: intl.formatMessage({ id: 'tab.settings', defaultMessage: 'Settings', }), @@ -546,5 +548,3 @@ function AppComponent(props: RouteComponentProps & WrappedComponentProps) { ); } - -export const App = injectIntl(AppComponent); diff --git a/src/chart.tsx b/src/chart.tsx index 7638e5d..8edd88f 100644 --- a/src/chart.tsx +++ b/src/chart.tsx @@ -1,6 +1,6 @@ import {ChartColors} from './config'; -import {injectIntl, WrappedComponentProps} from 'react-intl'; import {interpolateNumber} from 'd3-interpolate'; +import {IntlShape, useIntl} from 'react-intl'; import {max, min} from 'd3-array'; import {Media} from './util/media'; import {saveAs} from 'file-saver'; @@ -259,8 +259,6 @@ export interface ChartProps { colors?: ChartColors; } -type ChartComponentProps = ChartProps & WrappedComponentProps; - class ChartWrapper { private chart?: ChartHandle; /** Animation is in progress. */ @@ -270,7 +268,7 @@ class ChartWrapper { /** The d3 zoom behavior object. */ private zoomBehavior?: ZoomBehavior; /** Props that will be used for rerendering. */ - private rerenderProps?: ChartComponentProps; + private rerenderProps?: ChartProps; zoom(factor: number) { const parent = select('#svgContainer') as Selection; @@ -283,7 +281,8 @@ class ChartWrapper { * animation is performed. */ renderChart( - props: ChartComponentProps, + props: ChartProps, + intl: IntlShape, args: {initialRender: boolean; resetPosition: boolean} = { initialRender: false, resetPosition: false, @@ -312,7 +311,7 @@ class ChartWrapper { colors: chartColors.get(props.colors!), animate: true, updateSvgSize: false, - locale: props.intl.locale, + locale: intl.locale, }); } else { this.chart!.setData(props.data); @@ -391,7 +390,7 @@ class ChartWrapper { this.rerenderRequired = false; // Use `this.rerenderProps` instead of the props in scope because // the props may have been updated in the meantime. - this.renderChart(this.rerenderProps!, { + this.renderChart(this.rerenderProps!, intl, { initialRender: false, resetPosition: false, }); @@ -408,9 +407,10 @@ function usePrevious(value: T): T | undefined { return ref.current; } -function ChartComponent(props: ChartComponentProps) { +export function Chart(props: ChartProps) { const chartWrapper = useRef(new ChartWrapper()); const prevProps = usePrevious(props); + const intl = useIntl(); useEffect(() => { if (prevProps) { @@ -418,9 +418,12 @@ function ChartComponent(props: ChartComponentProps) { props.chartType !== prevProps?.chartType || props.colors !== prevProps?.colors; const resetPosition = props.chartType !== prevProps?.chartType; - chartWrapper.current.renderChart(props, {initialRender, resetPosition}); + chartWrapper.current.renderChart(props, intl, { + initialRender, + resetPosition, + }); } else { - chartWrapper.current.renderChart(props, { + chartWrapper.current.renderChart(props, intl, { initialRender: true, resetPosition: true, }); @@ -449,5 +452,3 @@ function ChartComponent(props: ChartComponentProps) { ); } - -export const Chart = injectIntl(ChartComponent); diff --git a/src/details/events.tsx b/src/details/events.tsx index 50f4fcb..cd33911 100644 --- a/src/details/events.tsx +++ b/src/details/events.tsx @@ -3,7 +3,7 @@ import {compareDates, translateDate} from '../util/date_util'; import {DateOrRange, getDate} from 'topola'; import {dereference, GedcomData, getData} from '../util/gedcom_util'; import {GedcomEntry} from 'parse-gedcom'; -import {injectIntl, IntlShape, WrappedComponentProps} from 'react-intl'; +import {IntlShape, useIntl} from 'react-intl'; import {MultilineText} from './multiline-text'; import {TranslatedTag} from './translated-tag'; @@ -184,13 +184,13 @@ function toFamilyEvents( }); } -function EventsComponent(props: Props & WrappedComponentProps) { +export function Events(props: Props) { + const intl = useIntl(); + const events = flatMap(EVENT_TAGS, (tag) => props.entries .filter((entry) => entry.tag === tag) - .map((eventEntry) => - toEvent(eventEntry, props.gedcom, props.indi, props.intl), - ) + .map((eventEntry) => toEvent(eventEntry, props.gedcom, props.indi, intl)) .flatMap((events) => events) .sort((event1, event2) => compareDates(event1.date, event2.date)) .map((event) => eventDetails(event)), @@ -208,5 +208,3 @@ function EventsComponent(props: Props & WrappedComponentProps) { } return null; } - -export const Events = injectIntl(EventsComponent); diff --git a/src/menu/search.tsx b/src/menu/search.tsx index caf7e20..fd94fac 100644 --- a/src/menu/search.tsx +++ b/src/menu/search.tsx @@ -3,10 +3,10 @@ import {analyticsEvent} from '../util/analytics'; import {buildSearchIndex, SearchIndex, SearchResult} from './search_index'; import {formatDateOrRange} from '../util/date_util'; import {IndiInfo, JsonGedcomData} from 'topola'; -import {injectIntl, WrappedComponentProps} from 'react-intl'; import {JsonIndi} from 'topola'; import {Search, SearchResultProps} from 'semantic-ui-react'; import {useEffect, useRef, useState} from 'react'; +import {useIntl} from 'react-intl'; function getNameLine(result: SearchResult) { const name = [result.indi.firstName, result.indi.lastName].join(' ').trim(); @@ -27,14 +27,15 @@ interface Props { } /** Displays and handles the search box in the top bar. */ -function SearchBarComponent(props: WrappedComponentProps & Props) { +export function SearchBar(props: Props) { const [searchResults, setSearchResults] = useState([]); const [searchString, setSearchString] = useState(''); const searchIndex = useRef(); + const intl = useIntl(); function getDescriptionLine(indi: JsonIndi) { - const birthDate = formatDateOrRange(indi.birth, props.intl); - const deathDate = formatDateOrRange(indi.death, props.intl); + const birthDate = formatDateOrRange(indi.birth, intl); + const deathDate = formatDateOrRange(indi.death, intl); if (!deathDate) { return birthDate; } @@ -86,11 +87,11 @@ function SearchBarComponent(props: WrappedComponentProps & Props) { onSearchChange={(_, data) => onChange(data.value!)} onResultSelect={(_, data) => handleResultSelect(data.result.id)} results={searchResults} - noResultsMessage={props.intl.formatMessage({ + noResultsMessage={intl.formatMessage({ id: 'menu.search.no_results', defaultMessage: 'No results found', })} - placeholder={props.intl.formatMessage({ + placeholder={intl.formatMessage({ id: 'menu.search.placeholder', defaultMessage: 'Search for people', })} @@ -100,4 +101,3 @@ function SearchBarComponent(props: WrappedComponentProps & Props) { /> ); } -export const SearchBar = injectIntl(SearchBarComponent); diff --git a/src/menu/wikitree_menu.tsx b/src/menu/wikitree_menu.tsx index 796d6a4..135f093 100644 --- a/src/menu/wikitree_menu.tsx +++ b/src/menu/wikitree_menu.tsx @@ -2,7 +2,7 @@ import * as queryString from 'query-string'; import wikitreeLogo from './wikitree.png'; import {analyticsEvent} from '../util/analytics'; import {Button, Form, Header, Input, Modal} from 'semantic-ui-react'; -import {FormattedMessage, injectIntl, WrappedComponentProps} from 'react-intl'; +import {FormattedMessage, useIntl} from 'react-intl'; import {getLoggedInUserName} from '../datasource/wikitree'; import {MenuItem, MenuType} from './menu_item'; import {RouteComponentProps} from 'react-router-dom'; @@ -153,9 +153,10 @@ export function WikiTreeMenu(props: RouteComponentProps & Props) { } /** Displays and handles the "Log in to WikiTree" menu. */ -function WikiTreeLoginMenuComponent(props: WrappedComponentProps & Props) { +export function WikiTreeLoginMenu(props: Props) { const formRef = useRef(null); const returnUrlRef = useRef(null); + const intl = useIntl(); /** * Redirect to the WikiTree Apps login page with a return URL pointing to @@ -194,7 +195,7 @@ function WikiTreeLoginMenuComponent(props: WrappedComponentProps & Props) { ); } - const tooltip = props.intl.formatMessage( + const tooltip = intl.formatMessage( { id: 'menu.wikitree_popup_username', defaultMessage: 'Logged in to WikiTree as {username}', @@ -211,4 +212,3 @@ function WikiTreeLoginMenuComponent(props: WrappedComponentProps & Props) { ); } -export const WikiTreeLoginMenu = injectIntl(WikiTreeLoginMenuComponent);