mirror of
https://github.com/PeWu/topola-viewer.git
synced 2026-05-27 07:36:18 +00:00
Added menu option to display the relatives chart
This commit is contained in:
9
package-lock.json
generated
9
package-lock.json
generated
@@ -8060,8 +8060,7 @@
|
|||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "0.0.8",
|
"version": "0.0.8",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"minipass": {
|
"minipass": {
|
||||||
"version": "2.2.4",
|
"version": "2.2.4",
|
||||||
@@ -17998,9 +17997,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"topola": {
|
"topola": {
|
||||||
"version": "2.2.9",
|
"version": "2.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/topola/-/topola-2.2.9.tgz",
|
"resolved": "https://registry.npmjs.org/topola/-/topola-2.3.4.tgz",
|
||||||
"integrity": "sha512-bMS3RxmB4P9JmwaiycnbgguIRaXUUAnASyUqGc1GNmloRUBM9yZgGWQMZ4xkFHE7Otr4IiyMp8wOwrXahYLaYQ==",
|
"integrity": "sha512-eJpy19T6xFkrLgf1NGAMkgmLoswB00hY206LQZWiQRURr0+p0fdGAmNQKkWLba/e/MT3H/Qe/SSnAvRmHhFobA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"d3": "^5.4.0",
|
"d3": "^5.4.0",
|
||||||
"d3-flextree": "^2.1.1",
|
"d3-flextree": "^2.1.1",
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
"react-router-dom": "^4.3.1",
|
"react-router-dom": "^4.3.1",
|
||||||
"semantic-ui-css": "^2.4.1",
|
"semantic-ui-css": "^2.4.1",
|
||||||
"semantic-ui-react": "^0.84.0",
|
"semantic-ui-react": "^0.84.0",
|
||||||
"topola": "^2.2.9"
|
"topola": "^2.3.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/array.prototype.flatmap": "^1.2.0",
|
"@types/array.prototype.flatmap": "^1.2.0",
|
||||||
|
|||||||
38
src/app.tsx
38
src/app.tsx
@@ -1,7 +1,7 @@
|
|||||||
import * as queryString from 'query-string';
|
import * as queryString from 'query-string';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {analyticsEvent} from './analytics';
|
import {analyticsEvent} from './analytics';
|
||||||
import {Chart} from './chart';
|
import {Chart, ChartType} from './chart';
|
||||||
import {Details} from './details';
|
import {Details} from './details';
|
||||||
import {getSelection, loadFromUrl, loadGedcom} from './load_data';
|
import {getSelection, loadFromUrl, loadGedcom} from './load_data';
|
||||||
import {IndiInfo} from 'topola';
|
import {IndiInfo} from 'topola';
|
||||||
@@ -53,20 +53,27 @@ interface State {
|
|||||||
hash?: string;
|
hash?: string;
|
||||||
/** Error to display. */
|
/** Error to display. */
|
||||||
error?: string;
|
error?: string;
|
||||||
/** True if currently loading. */
|
/** True if data is currently being loaded. */
|
||||||
loading: boolean;
|
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 shoen. */
|
/** Whether the side panel is shown. */
|
||||||
showSidePanel?: boolean;
|
showSidePanel?: boolean;
|
||||||
/** Whether the app is in embedded mode, i.e. embedded in an iframe. */
|
/** Whether the app is in embedded mode, i.e. embedded in an iframe. */
|
||||||
embedded: boolean;
|
embedded: boolean;
|
||||||
/** Whether the app is in standalone mode, i.e. showing 'open file' menus */
|
/** Whether the app is in standalone mode, i.e. showing 'open file' menus. */
|
||||||
standalone: boolean;
|
standalone: boolean;
|
||||||
|
/** Type of displayed chart. */
|
||||||
|
chartType: ChartType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class App extends React.Component<RouteComponentProps, {}> {
|
export class App extends React.Component<RouteComponentProps, {}> {
|
||||||
state: State = {loading: false, embedded: false, standalone: true};
|
state: State = {
|
||||||
|
loading: false,
|
||||||
|
embedded: false,
|
||||||
|
standalone: true,
|
||||||
|
chartType: ChartType.Hourglass,
|
||||||
|
};
|
||||||
chartRef: Chart | null = null;
|
chartRef: Chart | null = null;
|
||||||
|
|
||||||
private isNewData(
|
private isNewData(
|
||||||
@@ -81,16 +88,18 @@ export class App extends React.Component<RouteComponentProps, {}> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sets the state with a new individual selection. */
|
/** Sets the state with a new individual selection and chart type. */
|
||||||
private updateSelection(selection: IndiInfo) {
|
private updateDisplay(selection: IndiInfo, chartType?: ChartType) {
|
||||||
if (
|
if (
|
||||||
!this.state.selection ||
|
!this.state.selection ||
|
||||||
this.state.selection.id !== selection.id ||
|
this.state.selection.id !== selection.id ||
|
||||||
this.state.selection!.generation !== selection.generation
|
this.state.selection!.generation !== selection.generation ||
|
||||||
|
(chartType !== undefined && chartType !== this.state.chartType)
|
||||||
) {
|
) {
|
||||||
this.setState(
|
this.setState(
|
||||||
Object.assign({}, this.state, {
|
Object.assign({}, this.state, {
|
||||||
selection,
|
selection,
|
||||||
|
chartType: chartType !== undefined ? chartType : this.state.chartType,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -179,6 +188,10 @@ export class App extends React.Component<RouteComponentProps, {}> {
|
|||||||
const hash = getParam('file');
|
const hash = getParam('file');
|
||||||
const handleCors = getParam('handleCors') !== 'false'; // True by default.
|
const handleCors = getParam('handleCors') !== 'false'; // True by default.
|
||||||
const standalone = getParam('standalone') !== 'false'; // True by default.
|
const standalone = getParam('standalone') !== 'false'; // True by default.
|
||||||
|
const view = getParam('view');
|
||||||
|
// Hourglass is the default view.
|
||||||
|
const chartType =
|
||||||
|
view === 'relatives' ? ChartType.Relatives : ChartType.Hourglass;
|
||||||
|
|
||||||
const gedcom = this.props.location.state && this.props.location.state.data;
|
const gedcom = this.props.location.state && this.props.location.state.data;
|
||||||
const images =
|
const images =
|
||||||
@@ -198,6 +211,7 @@ export class App extends React.Component<RouteComponentProps, {}> {
|
|||||||
loading: true,
|
loading: true,
|
||||||
url,
|
url,
|
||||||
standalone,
|
standalone,
|
||||||
|
chartType,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const data = hash
|
const data = hash
|
||||||
@@ -221,6 +235,7 @@ export class App extends React.Component<RouteComponentProps, {}> {
|
|||||||
url,
|
url,
|
||||||
showSidePanel,
|
showSidePanel,
|
||||||
standalone,
|
standalone,
|
||||||
|
chartType,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -234,7 +249,7 @@ export class App extends React.Component<RouteComponentProps, {}> {
|
|||||||
indi,
|
indi,
|
||||||
generation,
|
generation,
|
||||||
);
|
);
|
||||||
this.updateSelection(selection);
|
this.updateDisplay(selection, chartType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,7 +261,7 @@ export class App extends React.Component<RouteComponentProps, {}> {
|
|||||||
analyticsEvent('selection_changed');
|
analyticsEvent('selection_changed');
|
||||||
if (this.state.embedded) {
|
if (this.state.embedded) {
|
||||||
// In embedded mode the URL doesn't change.
|
// In embedded mode the URL doesn't change.
|
||||||
this.updateSelection(selection);
|
this.updateDisplay(selection);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const location = this.props.location;
|
const location = this.props.location;
|
||||||
@@ -263,8 +278,9 @@ export class App extends React.Component<RouteComponentProps, {}> {
|
|||||||
<div id="content">
|
<div id="content">
|
||||||
<Chart
|
<Chart
|
||||||
data={this.state.data.chartData}
|
data={this.state.data.chartData}
|
||||||
onSelection={this.onSelection}
|
|
||||||
selection={this.state.selection}
|
selection={this.state.selection}
|
||||||
|
chartType={this.state.chartType}
|
||||||
|
onSelection={this.onSelection}
|
||||||
ref={(ref) => (this.chartRef = ref)}
|
ref={(ref) => (this.chartRef = ref)}
|
||||||
/>
|
/>
|
||||||
{this.state.showSidePanel ? (
|
{this.state.showSidePanel ? (
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
createChart,
|
createChart,
|
||||||
DetailedRenderer,
|
DetailedRenderer,
|
||||||
HourglassChart,
|
HourglassChart,
|
||||||
|
RelativesChart,
|
||||||
} from 'topola';
|
} from 'topola';
|
||||||
|
|
||||||
/** Called when the view is dragged with the mouse. */
|
/** Called when the view is dragged with the mouse. */
|
||||||
@@ -101,9 +102,16 @@ function canvasToBlob(canvas: HTMLCanvasElement, type: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Supported chart types. */
|
||||||
|
export enum ChartType {
|
||||||
|
Hourglass,
|
||||||
|
Relatives,
|
||||||
|
}
|
||||||
|
|
||||||
export interface ChartProps {
|
export interface ChartProps {
|
||||||
data: JsonGedcomData;
|
data: JsonGedcomData;
|
||||||
selection: IndiInfo;
|
selection: IndiInfo;
|
||||||
|
chartType: ChartType;
|
||||||
onSelection: (indiInfo: IndiInfo) => void;
|
onSelection: (indiInfo: IndiInfo) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,6 +119,18 @@ export interface ChartProps {
|
|||||||
export class Chart extends React.PureComponent<ChartProps, {}> {
|
export class Chart extends React.PureComponent<ChartProps, {}> {
|
||||||
private chart?: ChartHandle;
|
private chart?: ChartHandle;
|
||||||
|
|
||||||
|
private getChartType() {
|
||||||
|
switch (this.props.chartType) {
|
||||||
|
case ChartType.Hourglass:
|
||||||
|
return HourglassChart;
|
||||||
|
case ChartType.Relatives:
|
||||||
|
return RelativesChart;
|
||||||
|
default:
|
||||||
|
// Fall back to hourglass chart.
|
||||||
|
return HourglassChart;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the chart or performs a transition animation to a new state.
|
* Renders the chart or performs a transition animation to a new state.
|
||||||
* If indiInfo is not given, it means that it is the initial render and no
|
* If indiInfo is not given, it means that it is the initial render and no
|
||||||
@@ -121,7 +141,7 @@ export class Chart extends React.PureComponent<ChartProps, {}> {
|
|||||||
(d3.select('#chart').node() as HTMLElement).innerHTML = '';
|
(d3.select('#chart').node() as HTMLElement).innerHTML = '';
|
||||||
this.chart = createChart({
|
this.chart = createChart({
|
||||||
json: this.props.data,
|
json: this.props.data,
|
||||||
chartType: HourglassChart,
|
chartType: this.getChartType(),
|
||||||
renderer: DetailedRenderer,
|
renderer: DetailedRenderer,
|
||||||
svgSelector: '#chart',
|
svgSelector: '#chart',
|
||||||
indiCallback: (info) => this.props.onSelection(info),
|
indiCallback: (info) => this.props.onSelection(info),
|
||||||
@@ -192,7 +212,10 @@ export class Chart extends React.PureComponent<ChartProps, {}> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps: ChartProps) {
|
componentDidUpdate(prevProps: ChartProps) {
|
||||||
this.renderChart({initialRender: this.props.data !== prevProps.data});
|
const initialRender =
|
||||||
|
this.props.data !== prevProps.data ||
|
||||||
|
this.props.chartType !== prevProps.chartType;
|
||||||
|
this.renderChart({initialRender});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Make intl appear in this.context. */
|
/** Make intl appear in this.context. */
|
||||||
|
|||||||
@@ -184,6 +184,16 @@ export class TopBar extends React.Component<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
changeView(view: string) {
|
||||||
|
const location = this.props.location;
|
||||||
|
const search = queryString.parse(location.search);
|
||||||
|
if (search.view !== view) {
|
||||||
|
search.view = view;
|
||||||
|
location.search = queryString.stringify(search);
|
||||||
|
this.props.history.push(location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.initializeSearchIndex();
|
this.initializeSearchIndex();
|
||||||
}
|
}
|
||||||
@@ -259,6 +269,7 @@ export class TopBar extends React.Component<
|
|||||||
<Icon name="print" />
|
<Icon name="print" />
|
||||||
<FormattedMessage id="menu.print" defaultMessage="Print" />
|
<FormattedMessage id="menu.print" defaultMessage="Print" />
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
|
||||||
<Dropdown
|
<Dropdown
|
||||||
trigger={
|
trigger={
|
||||||
<div>
|
<div>
|
||||||
@@ -280,6 +291,34 @@ export class TopBar extends React.Component<
|
|||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
</Dropdown.Menu>
|
</Dropdown.Menu>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
|
||||||
|
<Dropdown
|
||||||
|
trigger={
|
||||||
|
<div>
|
||||||
|
<Icon name="eye" />
|
||||||
|
<FormattedMessage id="menu.view" defaultMessage="View" />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
className="item"
|
||||||
|
>
|
||||||
|
<Dropdown.Menu>
|
||||||
|
<Dropdown.Item onClick={() => this.changeView('hourglass')}>
|
||||||
|
<Icon name="hourglass" />
|
||||||
|
<FormattedMessage
|
||||||
|
id="menu.hourglass"
|
||||||
|
defaultMessage="Hourglass chart"
|
||||||
|
/>
|
||||||
|
</Dropdown.Item>
|
||||||
|
<Dropdown.Item onClick={() => this.changeView('relatives')}>
|
||||||
|
<Icon name="users" />
|
||||||
|
<FormattedMessage
|
||||||
|
id="menu.relatives"
|
||||||
|
defaultMessage="All relatives"
|
||||||
|
/>
|
||||||
|
</Dropdown.Item>
|
||||||
|
</Dropdown.Menu>
|
||||||
|
</Dropdown>
|
||||||
|
|
||||||
<Search
|
<Search
|
||||||
onSearchChange={debounce(
|
onSearchChange={debounce(
|
||||||
(_: React.MouseEvent<HTMLElement>, data: SearchProps) =>
|
(_: React.MouseEvent<HTMLElement>, data: SearchProps) =>
|
||||||
|
|||||||
@@ -6,6 +6,9 @@
|
|||||||
"menu.pdf_file": "Plik PDF",
|
"menu.pdf_file": "Plik PDF",
|
||||||
"menu.png_file": "Plik PNG",
|
"menu.png_file": "Plik PNG",
|
||||||
"menu.svg_file": "Plik SVG",
|
"menu.svg_file": "Plik SVG",
|
||||||
|
"menu.view": "Widok",
|
||||||
|
"menu.hourglass": "Wykres klepsydrowy",
|
||||||
|
"menu.relatives": "Wszyscy krewni",
|
||||||
"menu.github": "Źródła na GitHub",
|
"menu.github": "Źródła na GitHub",
|
||||||
"menu.powered_by": "Topola Genealogy",
|
"menu.powered_by": "Topola Genealogy",
|
||||||
"menu.search.placeholder": "Szukaj osoby",
|
"menu.search.placeholder": "Szukaj osoby",
|
||||||
|
|||||||
Reference in New Issue
Block a user