mirror of
https://github.com/PeWu/topola-viewer.git
synced 2026-02-18 02:55:48 +00:00
Introduced explicit application state
This commit is contained in:
parent
aaba839a46
commit
f2e3120dc9
142
src/app.tsx
142
src/app.tsx
@ -52,6 +52,14 @@ function ErrorPopup(props: ErrorPopupProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum AppState {
|
||||||
|
INITIAL,
|
||||||
|
LOADING,
|
||||||
|
ERROR,
|
||||||
|
SHOWING_CHART,
|
||||||
|
LOADING_MORE,
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Message types used in embedded mode.
|
* Message types used in embedded mode.
|
||||||
* When the parent is ready to receive messages, it sends PARENT_READY.
|
* When the parent is ready to receive messages, it sends PARENT_READY.
|
||||||
@ -91,7 +99,11 @@ class UploadedDataSource implements DataSource {
|
|||||||
isNewData(args: Arguments, state: State): boolean {
|
isNewData(args: Arguments, state: State): boolean {
|
||||||
return (
|
return (
|
||||||
args.hash !== state.hash ||
|
args.hash !== state.hash ||
|
||||||
!!(args.gedcom && !state.loading && !state.data)
|
!!(
|
||||||
|
args.gedcom &&
|
||||||
|
state.state !== AppState.LOADING &&
|
||||||
|
state.state !== AppState.SHOWING_CHART
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,6 +257,8 @@ function hasUpdatedValues<T>(state: T, changes: Partial<T> | undefined) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
|
/** State of the application. */
|
||||||
|
state: AppState;
|
||||||
/** Loaded data. */
|
/** Loaded data. */
|
||||||
data?: TopolaData;
|
data?: TopolaData;
|
||||||
/** Selected individual. */
|
/** Selected individual. */
|
||||||
@ -253,8 +267,6 @@ interface State {
|
|||||||
hash?: string;
|
hash?: string;
|
||||||
/** Error to display. */
|
/** Error to display. */
|
||||||
error?: string;
|
error?: string;
|
||||||
/** True if data is currently being loaded. */
|
|
||||||
loading: boolean;
|
|
||||||
/** URL of the data that is loaded or is being loaded. */
|
/** URL of the data that is loaded or is being loaded. */
|
||||||
url?: string;
|
url?: string;
|
||||||
/** Whether the side panel is shown. */
|
/** Whether the side panel is shown. */
|
||||||
@ -269,14 +281,13 @@ interface State {
|
|||||||
showErrorPopup: boolean;
|
showErrorPopup: boolean;
|
||||||
/** Source of the data. */
|
/** Source of the data. */
|
||||||
source?: DataSourceEnum;
|
source?: DataSourceEnum;
|
||||||
loadingMore?: boolean;
|
|
||||||
/** Freeze animations after initial chart render. */
|
/** Freeze animations after initial chart render. */
|
||||||
freezeAnimation?: boolean;
|
freezeAnimation?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class App extends React.Component<RouteComponentProps, {}> {
|
export class App extends React.Component<RouteComponentProps, {}> {
|
||||||
state: State = {
|
state: State = {
|
||||||
loading: false,
|
state: AppState.INITIAL,
|
||||||
embedded: false,
|
embedded: false,
|
||||||
standalone: true,
|
standalone: true,
|
||||||
chartType: ChartType.Hourglass,
|
chartType: ChartType.Hourglass,
|
||||||
@ -317,8 +328,8 @@ export class App extends React.Component<RouteComponentProps, {}> {
|
|||||||
private setError(error: string) {
|
private setError(error: string) {
|
||||||
this.setState(
|
this.setState(
|
||||||
Object.assign({}, this.state, {
|
Object.assign({}, this.state, {
|
||||||
error: error,
|
state: AppState.ERROR,
|
||||||
loading: false,
|
error,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -341,10 +352,9 @@ export class App extends React.Component<RouteComponentProps, {}> {
|
|||||||
// Set state with data.
|
// Set state with data.
|
||||||
this.setState(
|
this.setState(
|
||||||
Object.assign({}, this.state, {
|
Object.assign({}, this.state, {
|
||||||
|
state: AppState.SHOWING_CHART,
|
||||||
data,
|
data,
|
||||||
selection: getSelection(data.chartData),
|
selection: getSelection(data.chartData),
|
||||||
error: undefined,
|
|
||||||
loading: false,
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -360,14 +370,19 @@ export class App extends React.Component<RouteComponentProps, {}> {
|
|||||||
|
|
||||||
async componentDidUpdate() {
|
async componentDidUpdate() {
|
||||||
if (this.props.location.pathname !== '/view') {
|
if (this.props.location.pathname !== '/view') {
|
||||||
|
if (this.state.state !== AppState.INITIAL) {
|
||||||
|
this.setState(Object.assign({}, this.state, {state: AppState.INITIAL}));
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const args = getArguments(this.props.location);
|
const args = getArguments(this.props.location);
|
||||||
|
|
||||||
if (args.embedded && !this.state.embedded) {
|
if (args.embedded && !this.state.embedded) {
|
||||||
|
// Enter embedded mode.
|
||||||
this.setState(
|
this.setState(
|
||||||
Object.assign({}, this.state, {
|
Object.assign({}, this.state, {
|
||||||
|
state: AppState.LOADING,
|
||||||
embedded: true,
|
embedded: true,
|
||||||
standalone: false,
|
standalone: false,
|
||||||
showSidePanel: args.showSidePanel,
|
showSidePanel: args.showSidePanel,
|
||||||
@ -387,18 +402,16 @@ export class App extends React.Component<RouteComponentProps, {}> {
|
|||||||
if (!dataSource) {
|
if (!dataSource) {
|
||||||
this.props.history.replace({pathname: '/'});
|
this.props.history.replace({pathname: '/'});
|
||||||
} else if (
|
} else if (
|
||||||
(!this.state.loading && !this.state.data && !this.state.error) ||
|
this.state.state === AppState.INITIAL ||
|
||||||
args.source !== this.state.source ||
|
args.source !== this.state.source ||
|
||||||
dataSource.isNewData(args, this.state)
|
dataSource.isNewData(args, this.state)
|
||||||
) {
|
) {
|
||||||
// Set loading state.
|
// Set loading state.
|
||||||
this.setState(
|
this.setState(
|
||||||
Object.assign({}, this.state, {
|
Object.assign({}, this.state, {
|
||||||
data: undefined,
|
state: AppState.LOADING,
|
||||||
selection: {id: args.indi},
|
selection: {id: args.indi},
|
||||||
hash: args.hash,
|
hash: args.hash,
|
||||||
error: undefined,
|
|
||||||
loading: true,
|
|
||||||
url: args.url,
|
url: args.url,
|
||||||
standalone: args.standalone,
|
standalone: args.standalone,
|
||||||
chartType: args.chartType,
|
chartType: args.chartType,
|
||||||
@ -411,11 +424,10 @@ export class App extends React.Component<RouteComponentProps, {}> {
|
|||||||
// Set state with data.
|
// Set state with data.
|
||||||
this.setState(
|
this.setState(
|
||||||
Object.assign({}, this.state, {
|
Object.assign({}, this.state, {
|
||||||
|
state: AppState.SHOWING_CHART,
|
||||||
data,
|
data,
|
||||||
hash: args.hash,
|
hash: args.hash,
|
||||||
selection: getSelection(data.chartData, args.indi, args.generation),
|
selection: getSelection(data.chartData, args.indi, args.generation),
|
||||||
error: undefined,
|
|
||||||
loading: false,
|
|
||||||
url: args.url,
|
url: args.url,
|
||||||
showSidePanel: args.showSidePanel,
|
showSidePanel: args.showSidePanel,
|
||||||
standalone: args.standalone,
|
standalone: args.standalone,
|
||||||
@ -427,10 +439,13 @@ export class App extends React.Component<RouteComponentProps, {}> {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.setError(error.message);
|
this.setError(error.message);
|
||||||
}
|
}
|
||||||
} else if (this.state.data && this.state.selection) {
|
} else if (
|
||||||
|
this.state.state === AppState.SHOWING_CHART ||
|
||||||
|
this.state.state === AppState.LOADING_MORE
|
||||||
|
) {
|
||||||
// Update selection if it has changed in the URL.
|
// Update selection if it has changed in the URL.
|
||||||
const selection = getSelection(
|
const selection = getSelection(
|
||||||
this.state.data.chartData,
|
this.state.data!.chartData,
|
||||||
args.indi,
|
args.indi,
|
||||||
args.generation,
|
args.generation,
|
||||||
);
|
);
|
||||||
@ -439,7 +454,9 @@ export class App extends React.Component<RouteComponentProps, {}> {
|
|||||||
(!this.state.selection || this.state.selection.id !== selection.id);
|
(!this.state.selection || this.state.selection.id !== selection.id);
|
||||||
this.updateDisplay(selection, {
|
this.updateDisplay(selection, {
|
||||||
chartType: args.chartType,
|
chartType: args.chartType,
|
||||||
loadingMore: loadMoreFromWikitree || undefined,
|
state: loadMoreFromWikitree
|
||||||
|
? AppState.LOADING_MORE
|
||||||
|
: AppState.SHOWING_CHART,
|
||||||
});
|
});
|
||||||
if (loadMoreFromWikitree) {
|
if (loadMoreFromWikitree) {
|
||||||
try {
|
try {
|
||||||
@ -451,17 +468,15 @@ export class App extends React.Component<RouteComponentProps, {}> {
|
|||||||
);
|
);
|
||||||
this.setState(
|
this.setState(
|
||||||
Object.assign({}, this.state, {
|
Object.assign({}, this.state, {
|
||||||
|
state: AppState.SHOWING_CHART,
|
||||||
data,
|
data,
|
||||||
hash: args.hash,
|
hash: args.hash,
|
||||||
selection,
|
selection,
|
||||||
error: undefined,
|
|
||||||
loading: false,
|
|
||||||
url: args.url,
|
url: args.url,
|
||||||
showSidePanel: args.showSidePanel,
|
showSidePanel: args.showSidePanel,
|
||||||
standalone: args.standalone,
|
standalone: args.standalone,
|
||||||
chartType: args.chartType,
|
chartType: args.chartType,
|
||||||
source: args.source,
|
source: args.source,
|
||||||
loadingMore: false,
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -473,9 +488,7 @@ export class App extends React.Component<RouteComponentProps, {}> {
|
|||||||
},
|
},
|
||||||
{error},
|
{error},
|
||||||
),
|
),
|
||||||
{
|
{state: AppState.SHOWING_CHART},
|
||||||
loadingMore: false,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -561,7 +574,7 @@ export class App extends React.Component<RouteComponentProps, {}> {
|
|||||||
this.chartRef && this.chartRef.downloadSvg();
|
this.chartRef && this.chartRef.downloadSvg();
|
||||||
};
|
};
|
||||||
|
|
||||||
onDismissErrorPopup = () => {
|
private onDismissErrorPopup = () => {
|
||||||
this.setState(
|
this.setState(
|
||||||
Object.assign({}, this.state, {
|
Object.assign({}, this.state, {
|
||||||
showErrorPopup: false,
|
showErrorPopup: false,
|
||||||
@ -570,40 +583,45 @@ export class App extends React.Component<RouteComponentProps, {}> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private renderMainArea = () => {
|
private renderMainArea = () => {
|
||||||
if (this.state.data && this.state.selection) {
|
switch (this.state.state) {
|
||||||
return (
|
case AppState.SHOWING_CHART:
|
||||||
<div id="content">
|
case AppState.LOADING_MORE:
|
||||||
<ErrorPopup
|
return (
|
||||||
open={this.state.showErrorPopup}
|
<div id="content">
|
||||||
message={this.state.error}
|
<ErrorPopup
|
||||||
onDismiss={this.onDismissErrorPopup}
|
open={this.state.showErrorPopup}
|
||||||
/>
|
message={this.state.error}
|
||||||
{this.state.loadingMore ? (
|
onDismiss={this.onDismissErrorPopup}
|
||||||
<Loader active size="small" className="loading-more" />
|
/>
|
||||||
) : null}
|
{this.state.state === AppState.LOADING_MORE ? (
|
||||||
<Chart
|
<Loader active size="small" className="loading-more" />
|
||||||
data={this.state.data.chartData}
|
) : null}
|
||||||
selection={this.state.selection}
|
<Chart
|
||||||
chartType={this.state.chartType}
|
data={this.state.data!.chartData}
|
||||||
onSelection={this.onSelection}
|
selection={this.state.selection!}
|
||||||
freezeAnimation={this.state.freezeAnimation}
|
chartType={this.state.chartType}
|
||||||
ref={(ref) => (this.chartRef = ref)}
|
onSelection={this.onSelection}
|
||||||
/>
|
freezeAnimation={this.state.freezeAnimation}
|
||||||
{this.state.showSidePanel ? (
|
ref={(ref) => (this.chartRef = ref)}
|
||||||
<Responsive minWidth={768} id="sidePanel">
|
/>
|
||||||
<Details
|
{this.state.showSidePanel ? (
|
||||||
gedcom={this.state.data.gedcom}
|
<Responsive minWidth={768} id="sidePanel">
|
||||||
indi={this.state.selection.id}
|
<Details
|
||||||
/>
|
gedcom={this.state.data!.gedcom}
|
||||||
</Responsive>
|
indi={this.state.selection!.id}
|
||||||
) : null}
|
/>
|
||||||
</div>
|
</Responsive>
|
||||||
);
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
case AppState.ERROR:
|
||||||
|
return <ErrorMessage message={this.state.error!} />;
|
||||||
|
|
||||||
|
case AppState.INITIAL:
|
||||||
|
case AppState.LOADING:
|
||||||
|
return <Loader active size="large" />;
|
||||||
}
|
}
|
||||||
if (this.state.error) {
|
|
||||||
return <ErrorMessage message={this.state.error!} />;
|
|
||||||
}
|
|
||||||
return <Loader active size="large" />;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -618,11 +636,9 @@ export class App extends React.Component<RouteComponentProps, {}> {
|
|||||||
this.state.source !== DataSourceEnum.WIKITREE
|
this.state.source !== DataSourceEnum.WIKITREE
|
||||||
}
|
}
|
||||||
showingChart={
|
showingChart={
|
||||||
!!(
|
this.props.history.location.pathname === '/view' &&
|
||||||
this.props.history.location.pathname === '/view' &&
|
(this.state.state === AppState.SHOWING_CHART ||
|
||||||
this.state.data &&
|
this.state.state === AppState.LOADING_MORE)
|
||||||
this.state.selection
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
standalone={this.state.standalone}
|
standalone={this.state.standalone}
|
||||||
eventHandlers={{
|
eventHandlers={{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user