mirror of
https://github.com/PeWu/topola-viewer.git
synced 2026-05-27 07:36:18 +00:00
Use useHistory and useLocation hooks
This commit is contained in:
37
src/app.tsx
37
src/app.tsx
@@ -11,10 +11,11 @@ import {IndiInfo} from 'topola';
|
|||||||
import {Intro} from './intro';
|
import {Intro} from './intro';
|
||||||
import {Loader, Message, Portal, Tab} from 'semantic-ui-react';
|
import {Loader, Message, Portal, Tab} from 'semantic-ui-react';
|
||||||
import {Media} from './util/media';
|
import {Media} from './util/media';
|
||||||
import {Redirect, Route, RouteComponentProps, Switch} from 'react-router-dom';
|
import {Redirect, Route, Switch} from 'react-router-dom';
|
||||||
import {TopBar} from './menu/top_bar';
|
import {TopBar} from './menu/top_bar';
|
||||||
import {TopolaData} from './util/gedcom_util';
|
import {TopolaData} from './util/gedcom_util';
|
||||||
import {useEffect, useState} from 'react';
|
import {useEffect, useState} from 'react';
|
||||||
|
import {useHistory, useLocation} from 'react-router';
|
||||||
import {
|
import {
|
||||||
Chart,
|
Chart,
|
||||||
ChartType,
|
ChartType,
|
||||||
@@ -104,7 +105,7 @@ interface Arguments {
|
|||||||
selection?: IndiInfo;
|
selection?: IndiInfo;
|
||||||
chartType: ChartType;
|
chartType: ChartType;
|
||||||
standalone: boolean;
|
standalone: boolean;
|
||||||
freezeAnimation?: boolean;
|
freezeAnimation: boolean;
|
||||||
showSidePanel: boolean;
|
showSidePanel: boolean;
|
||||||
config: Config;
|
config: Config;
|
||||||
}
|
}
|
||||||
@@ -171,20 +172,7 @@ function getArguments(location: H.Location<any>): Arguments {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export function App() {
|
||||||
* Returs true if the changes object has values that are different than those
|
|
||||||
* in state.
|
|
||||||
*/
|
|
||||||
function hasUpdatedValues<T>(state: T, changes: Partial<T> | undefined) {
|
|
||||||
if (!changes) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return Object.entries(changes).some(
|
|
||||||
([key, value]) => value !== undefined && state[key] !== value,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function App(props: RouteComponentProps) {
|
|
||||||
/** State of the application. */
|
/** State of the application. */
|
||||||
const [state, setState] = useState<AppState>(AppState.INITIAL);
|
const [state, setState] = useState<AppState>(AppState.INITIAL);
|
||||||
/** Loaded data. */
|
/** Loaded data. */
|
||||||
@@ -208,6 +196,8 @@ export function App(props: RouteComponentProps) {
|
|||||||
const [config, setConfig] = useState(DEFALUT_CONFIG);
|
const [config, setConfig] = useState(DEFALUT_CONFIG);
|
||||||
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const history = useHistory();
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
/** Sets the state with a new individual selection and chart type. */
|
/** Sets the state with a new individual selection and chart type. */
|
||||||
function updateDisplay(newSelection: IndiInfo) {
|
function updateDisplay(newSelection: IndiInfo) {
|
||||||
@@ -296,17 +286,17 @@ export function App(props: RouteComponentProps) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
if (props.location.pathname !== '/view') {
|
if (location.pathname !== '/view') {
|
||||||
if (state !== AppState.INITIAL) {
|
if (state !== AppState.INITIAL) {
|
||||||
setState(AppState.INITIAL);
|
setState(AppState.INITIAL);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const args = getArguments(props.location);
|
const args = getArguments(location);
|
||||||
|
|
||||||
if (!args.sourceSpec) {
|
if (!args.sourceSpec) {
|
||||||
props.history.replace({pathname: '/'});
|
history.replace({pathname: '/'});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,6 +311,7 @@ export function App(props: RouteComponentProps) {
|
|||||||
setSelection(args.selection);
|
setSelection(args.selection);
|
||||||
setStandalone(args.standalone);
|
setStandalone(args.standalone);
|
||||||
setChartType(args.chartType);
|
setChartType(args.chartType);
|
||||||
|
setFreezeAnimation(args.freezeAnimation);
|
||||||
setConfig(args.config);
|
setConfig(args.config);
|
||||||
try {
|
try {
|
||||||
const data = await loadData(args.sourceSpec, args.selection);
|
const data = await loadData(args.sourceSpec, args.selection);
|
||||||
@@ -371,13 +362,12 @@ export function App(props: RouteComponentProps) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function updateUrl(args: queryString.ParsedQuery<any>) {
|
function updateUrl(args: queryString.ParsedQuery<any>) {
|
||||||
const location = props.location;
|
|
||||||
const search = queryString.parse(location.search);
|
const search = queryString.parse(location.search);
|
||||||
for (const key in args) {
|
for (const key in args) {
|
||||||
search[key] = args[key];
|
search[key] = args[key];
|
||||||
}
|
}
|
||||||
location.search = queryString.stringify(search);
|
location.search = queryString.stringify(search);
|
||||||
props.history.push(location);
|
history.push(location);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -516,15 +506,14 @@ export function App(props: RouteComponentProps) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Route
|
<Route
|
||||||
render={(props: RouteComponentProps) => (
|
render={() => (
|
||||||
<TopBar
|
<TopBar
|
||||||
{...props}
|
|
||||||
data={data?.chartData}
|
data={data?.chartData}
|
||||||
allowAllRelativesChart={
|
allowAllRelativesChart={
|
||||||
sourceSpec?.source !== DataSourceEnum.WIKITREE
|
sourceSpec?.source !== DataSourceEnum.WIKITREE
|
||||||
}
|
}
|
||||||
showingChart={
|
showingChart={
|
||||||
props.history.location.pathname === '/view' &&
|
history.location.pathname === '/view' &&
|
||||||
(state === AppState.SHOWING_CHART ||
|
(state === AppState.SHOWING_CHART ||
|
||||||
state === AppState.LOADING_MORE)
|
state === AppState.LOADING_MORE)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import {IndiInfo, JsonGedcomData} from 'topola';
|
|||||||
import {Link} from 'react-router-dom';
|
import {Link} from 'react-router-dom';
|
||||||
import {Media} from '../util/media';
|
import {Media} from '../util/media';
|
||||||
import {MenuType} from './menu_item';
|
import {MenuType} from './menu_item';
|
||||||
import {RouteComponentProps} from 'react-router-dom';
|
|
||||||
import {SearchBar} from './search';
|
import {SearchBar} from './search';
|
||||||
import {UploadMenu} from './upload_menu';
|
import {UploadMenu} from './upload_menu';
|
||||||
import {UrlMenu} from './url_menu';
|
import {UrlMenu} from './url_menu';
|
||||||
|
import {useHistory, useLocation} from 'react-router';
|
||||||
import {WikiTreeLoginMenu, WikiTreeMenu} from './wikitree_menu';
|
import {WikiTreeLoginMenu, WikiTreeMenu} from './wikitree_menu';
|
||||||
|
|
||||||
enum ScreenSize {
|
enum ScreenSize {
|
||||||
@@ -37,14 +37,16 @@ interface Props {
|
|||||||
showWikiTreeMenus: boolean;
|
showWikiTreeMenus: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TopBar(props: RouteComponentProps & Props) {
|
export function TopBar(props: Props) {
|
||||||
|
const history = useHistory();
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
function changeView(view: string) {
|
function changeView(view: string) {
|
||||||
const location = props.location;
|
|
||||||
const search = queryString.parse(location.search);
|
const search = queryString.parse(location.search);
|
||||||
if (search.view !== view) {
|
if (search.view !== view) {
|
||||||
search.view = view;
|
search.view = view;
|
||||||
location.search = queryString.stringify(search);
|
location.search = queryString.stringify(search);
|
||||||
props.history.push(location);
|
history.push(location);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import {analyticsEvent} from '../util/analytics';
|
|||||||
import {Dropdown, Icon, Menu} from 'semantic-ui-react';
|
import {Dropdown, Icon, Menu} from 'semantic-ui-react';
|
||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
import {MenuType} from './menu_item';
|
import {MenuType} from './menu_item';
|
||||||
import {RouteComponentProps} from 'react-router-dom';
|
|
||||||
import {SyntheticEvent} from 'react';
|
import {SyntheticEvent} from 'react';
|
||||||
|
import {useHistory, useLocation} from 'react-router';
|
||||||
|
|
||||||
function loadFileAsText(file: File): Promise<string> {
|
function loadFileAsText(file: File): Promise<string> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
@@ -27,7 +27,10 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Displays and handles the "Open file" menu. */
|
/** Displays and handles the "Open file" menu. */
|
||||||
export function UploadMenu(props: RouteComponentProps & Props) {
|
export function UploadMenu(props: Props) {
|
||||||
|
const history = useHistory();
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
async function handleUpload(event: SyntheticEvent<HTMLInputElement>) {
|
async function handleUpload(event: SyntheticEvent<HTMLInputElement>) {
|
||||||
const files = (event.target as HTMLInputElement).files;
|
const files = (event.target as HTMLInputElement).files;
|
||||||
if (!files || !files.length) {
|
if (!files || !files.length) {
|
||||||
@@ -68,9 +71,8 @@ export function UploadMenu(props: RouteComponentProps & Props) {
|
|||||||
|
|
||||||
// Use history.replace() when reuploading the same file and history.push() when loading
|
// Use history.replace() when reuploading the same file and history.push() when loading
|
||||||
// a new file.
|
// a new file.
|
||||||
const search = queryString.parse(props.location.search);
|
const search = queryString.parse(location.search);
|
||||||
const historyPush =
|
const historyPush = search.file === hash ? history.replace : history.push;
|
||||||
search.file === hash ? props.history.replace : props.history.push;
|
|
||||||
|
|
||||||
historyPush({
|
historyPush({
|
||||||
pathname: '/view',
|
pathname: '/view',
|
||||||
|
|||||||
@@ -3,18 +3,19 @@ import {analyticsEvent} from '../util/analytics';
|
|||||||
import {Button, Form, Header, Icon, Input, Modal} from 'semantic-ui-react';
|
import {Button, Form, Header, Icon, Input, Modal} from 'semantic-ui-react';
|
||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
import {MenuItem, MenuType} from './menu_item';
|
import {MenuItem, MenuType} from './menu_item';
|
||||||
import {RouteComponentProps} from 'react-router-dom';
|
|
||||||
import {useEffect, useRef, useState} from 'react';
|
import {useEffect, useRef, useState} from 'react';
|
||||||
|
import {useHistory} from 'react-router';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
menuType: MenuType;
|
menuType: MenuType;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Displays and handles the "Open URL" menu. */
|
/** Displays and handles the "Open URL" menu. */
|
||||||
export function UrlMenu(props: RouteComponentProps & Props) {
|
export function UrlMenu(props: Props) {
|
||||||
const [dialogOpen, setDialogOpen] = useState(false);
|
const [dialogOpen, setDialogOpen] = useState(false);
|
||||||
const [url, setUrl] = useState('');
|
const [url, setUrl] = useState('');
|
||||||
const inputRef = useRef<Input>(null);
|
const inputRef = useRef<Input>(null);
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (dialogOpen) {
|
if (dialogOpen) {
|
||||||
@@ -28,7 +29,7 @@ export function UrlMenu(props: RouteComponentProps & Props) {
|
|||||||
setDialogOpen(false);
|
setDialogOpen(false);
|
||||||
if (url) {
|
if (url) {
|
||||||
analyticsEvent('url_selected');
|
analyticsEvent('url_selected');
|
||||||
props.history.push({
|
history.push({
|
||||||
pathname: '/view',
|
pathname: '/view',
|
||||||
search: queryString.stringify({url}),
|
search: queryString.stringify({url}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,24 +5,20 @@ import {Button, Form, Header, Input, Modal} from 'semantic-ui-react';
|
|||||||
import {FormattedMessage, useIntl} from 'react-intl';
|
import {FormattedMessage, useIntl} from 'react-intl';
|
||||||
import {getLoggedInUserName} from '../datasource/wikitree';
|
import {getLoggedInUserName} from '../datasource/wikitree';
|
||||||
import {MenuItem, MenuType} from './menu_item';
|
import {MenuItem, MenuType} from './menu_item';
|
||||||
import {RouteComponentProps} from 'react-router-dom';
|
|
||||||
import {useEffect, useRef, useState} from 'react';
|
import {useEffect, useRef, useState} from 'react';
|
||||||
|
import {useHistory, useLocation} from 'react-router';
|
||||||
enum WikiTreeLoginState {
|
|
||||||
UNKNOWN,
|
|
||||||
NOT_LOGGED_IN,
|
|
||||||
LOGGED_IN,
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
menuType: MenuType;
|
menuType: MenuType;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Displays and handles the "Select WikiTree ID" menu. */
|
/** Displays and handles the "Select WikiTree ID" menu. */
|
||||||
export function WikiTreeMenu(props: RouteComponentProps & Props) {
|
export function WikiTreeMenu(props: Props) {
|
||||||
const [dialogOpen, setDialogOpen] = useState(false);
|
const [dialogOpen, setDialogOpen] = useState(false);
|
||||||
const [wikiTreeId, setWikiTreeId] = useState('');
|
const [wikiTreeId, setWikiTreeId] = useState('');
|
||||||
const inputRef = useRef<Input>(null);
|
const inputRef = useRef<Input>(null);
|
||||||
|
const history = useHistory();
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (dialogOpen) {
|
if (dialogOpen) {
|
||||||
@@ -38,10 +34,10 @@ export function WikiTreeMenu(props: RouteComponentProps & Props) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
analyticsEvent('wikitree_id_selected');
|
analyticsEvent('wikitree_id_selected');
|
||||||
const search = queryString.parse(props.location.search);
|
const search = queryString.parse(location.search);
|
||||||
const standalone =
|
const standalone =
|
||||||
search.standalone !== undefined ? search.standalone : true;
|
search.standalone !== undefined ? search.standalone : true;
|
||||||
props.history.push({
|
history.push({
|
||||||
pathname: '/view',
|
pathname: '/view',
|
||||||
search: queryString.stringify({
|
search: queryString.stringify({
|
||||||
indi: wikiTreeId,
|
indi: wikiTreeId,
|
||||||
|
|||||||
Reference in New Issue
Block a user