mirror of
https://github.com/PeWu/topola-viewer.git
synced 2026-04-13 10:06:15 +00:00
Show changelog in intro page and when a new version is loaded
This commit is contained in:
@@ -1,8 +1,12 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2021-10-31
|
||||||
|
|
||||||
|
- Show changelog in intro page and when an upgraded version is loaded
|
||||||
|
|
||||||
## 2021-10-27
|
## 2021-10-27
|
||||||
|
|
||||||
- Showing events in details panel for WikiTree profiles
|
- Show events in details panel for WikiTree profiles
|
||||||
|
|
||||||
## 2021-10-26
|
## 2021-10-26
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ describe('Intro page', () => {
|
|||||||
cy.visit('/');
|
cy.visit('/');
|
||||||
});
|
});
|
||||||
it('displays intro text', () => {
|
it('displays intro text', () => {
|
||||||
cy.contains('Here are some examples');
|
cy.contains('Examples');
|
||||||
});
|
});
|
||||||
it('displays menu', () => {
|
it('displays menu', () => {
|
||||||
cy.contains('Open file');
|
cy.contains('Open file');
|
||||||
|
|||||||
1728
package-lock.json
generated
1728
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@@ -27,9 +27,13 @@
|
|||||||
"react-intl": "^5.15.5",
|
"react-intl": "^5.15.5",
|
||||||
"react-linkify": "^0.2.2",
|
"react-linkify": "^0.2.2",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
|
"rehype-stringify": "^9.0.2",
|
||||||
|
"remark-parse": "^10.0.0",
|
||||||
|
"remark-rehype": "^10.0.0",
|
||||||
"semantic-ui-css": "^2.4.1",
|
"semantic-ui-css": "^2.4.1",
|
||||||
"semantic-ui-react": "^2.0.3",
|
"semantic-ui-react": "^2.0.3",
|
||||||
"topola": "^3.5.0"
|
"topola": "^3.5.0",
|
||||||
|
"unified": "^10.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/array.prototype.flatmap": "^1.2.2",
|
"@types/array.prototype.flatmap": "^1.2.2",
|
||||||
@@ -59,8 +63,8 @@
|
|||||||
"typescript": "^4.2.3"
|
"typescript": "^4.2.3"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` REACT_APP_GIT_TIME=`git log -1 --format=%ci` react-scripts start",
|
"start": "REACT_APP_CHANGELOG=`cat CHANGELOG.md` REACT_APP_GIT_SHA=`git rev-parse --short HEAD` REACT_APP_GIT_TIME=`git log -1 --format=%ci` react-scripts start",
|
||||||
"build": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` REACT_APP_GIT_TIME=`git log -1 --format=%ci` react-scripts build",
|
"build": "REACT_APP_CHANGELOG=`cat CHANGELOG.md` REACT_APP_GIT_SHA=`git rev-parse --short HEAD` REACT_APP_GIT_TIME=`git log -1 --format=%ci` react-scripts build",
|
||||||
"test": "react-scripts test --env=jsdom",
|
"test": "react-scripts test --env=jsdom",
|
||||||
"prettier": "prettier --write src/**/*.{ts,tsx,json} && prettier --write src/*.{ts,tsx,json}",
|
"prettier": "prettier --write src/**/*.{ts,tsx,json} && prettier --write src/*.{ts,tsx,json}",
|
||||||
"predeploy": "npm run build",
|
"predeploy": "npm run build",
|
||||||
|
|||||||
16
src/app.tsx
16
src/app.tsx
@@ -2,14 +2,8 @@ import * as H from 'history';
|
|||||||
import * as queryString from 'query-string';
|
import * as queryString from 'query-string';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {analyticsEvent} from './util/analytics';
|
import {analyticsEvent} from './util/analytics';
|
||||||
|
import {Changelog} from './changelog';
|
||||||
import {Chart, ChartComponent, ChartType} from './chart';
|
import {Chart, ChartComponent, ChartType} from './chart';
|
||||||
import {
|
|
||||||
argsToConfig,
|
|
||||||
Config,
|
|
||||||
ConfigPanel,
|
|
||||||
configToArgs,
|
|
||||||
DEFALUT_CONFIG,
|
|
||||||
} from './config';
|
|
||||||
import {DataSourceEnum, SourceSelection} from './datasource/data_source';
|
import {DataSourceEnum, SourceSelection} from './datasource/data_source';
|
||||||
import {Details} from './details/details';
|
import {Details} from './details/details';
|
||||||
import {EmbeddedDataSource, EmbeddedSourceSpec} from './datasource/embedded';
|
import {EmbeddedDataSource, EmbeddedSourceSpec} from './datasource/embedded';
|
||||||
@@ -22,6 +16,13 @@ import {Media} from './util/media';
|
|||||||
import {Redirect, Route, RouteComponentProps, Switch} from 'react-router-dom';
|
import {Redirect, Route, RouteComponentProps, 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 {
|
||||||
|
argsToConfig,
|
||||||
|
Config,
|
||||||
|
ConfigPanel,
|
||||||
|
configToArgs,
|
||||||
|
DEFALUT_CONFIG,
|
||||||
|
} from './config';
|
||||||
import {
|
import {
|
||||||
getSelection,
|
getSelection,
|
||||||
UploadSourceSpec,
|
UploadSourceSpec,
|
||||||
@@ -536,6 +537,7 @@ class AppComponent extends React.Component<
|
|||||||
<Tab panes={sidePanelTabs} />
|
<Tab panes={sidePanelTabs} />
|
||||||
</Media>
|
</Media>
|
||||||
) : null}
|
) : null}
|
||||||
|
<Changelog />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
93
src/changelog.tsx
Normal file
93
src/changelog.tsx
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import rehypeStringify from 'rehype-stringify';
|
||||||
|
import remarkParse from 'remark-parse';
|
||||||
|
import remarkRehype from 'remark-rehype';
|
||||||
|
import {Button, Header, Modal} from 'semantic-ui-react';
|
||||||
|
import {unified} from 'unified';
|
||||||
|
import {useEffect, useState} from 'react';
|
||||||
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
|
const LAST_SEEN_VERSION_KEY = 'last_seen_version';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns changelog as raw HTML.
|
||||||
|
*
|
||||||
|
* @param maxVersions Max number of versions to include in changelog
|
||||||
|
* @param seenVersion Last seen app version
|
||||||
|
*/
|
||||||
|
export async function getChangelog(maxVersions: number, seenVersion?: string) {
|
||||||
|
const seenVersionDate = seenVersion
|
||||||
|
? Date.parse(seenVersion.slice(0, 10))
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
const changes = process.env
|
||||||
|
.REACT_APP_CHANGELOG!.split('##')
|
||||||
|
.slice(1, maxVersions + 1)
|
||||||
|
.map((notes) => {
|
||||||
|
const date = Date.parse(notes.split('\n')[0].trim());
|
||||||
|
return {date, notes: '####' + notes};
|
||||||
|
})
|
||||||
|
.filter((release) => release.date > seenVersionDate)
|
||||||
|
.map((release) => release.notes)
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
const parsedChanges = await unified()
|
||||||
|
.use(remarkParse)
|
||||||
|
.use(remarkRehype)
|
||||||
|
.use(rehypeStringify)
|
||||||
|
.process(changes);
|
||||||
|
return String(parsedChanges);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Stores in local storage the current app version as the last seen version. */
|
||||||
|
export function updateSeenVersion() {
|
||||||
|
localStorage.setItem(LAST_SEEN_VERSION_KEY, process.env.REACT_APP_GIT_TIME!);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows changelog entries if the user has seen an older version of
|
||||||
|
* Topola Viewer and is now seeing a newer one.
|
||||||
|
*/
|
||||||
|
export function Changelog() {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [changelog, setChangelog] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
const seenVersion = localStorage.getItem(LAST_SEEN_VERSION_KEY);
|
||||||
|
const currentVersion = process.env.REACT_APP_GIT_TIME!;
|
||||||
|
if (!seenVersion || seenVersion === currentVersion) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const changes = await getChangelog(3, seenVersion);
|
||||||
|
setChangelog(changes);
|
||||||
|
setOpen(!!changes);
|
||||||
|
updateSeenVersion();
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal open={open} centered={false}>
|
||||||
|
<Header>
|
||||||
|
<FormattedMessage
|
||||||
|
id="whats_new.title"
|
||||||
|
defaultMessage="What's new in this version?"
|
||||||
|
/>
|
||||||
|
</Header>
|
||||||
|
<Modal.Content className="limit-height">
|
||||||
|
<span dangerouslySetInnerHTML={{__html: changelog}} />
|
||||||
|
<a href="https://github.com/PeWu/topola-viewer/blob/master/CHANGELOG.md">
|
||||||
|
<FormattedMessage
|
||||||
|
id="intro.full_changelog"
|
||||||
|
defaultMessage="See full changelog"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</Modal.Content>
|
||||||
|
<Modal.Actions>
|
||||||
|
<Button primary onClick={() => setOpen(false)}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
</Modal.Actions>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -154,3 +154,8 @@ div.zoom {
|
|||||||
.ui.tabular.menu a {
|
.ui.tabular.menu a {
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.limit-height {
|
||||||
|
height: 300px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import * as queryString from 'query-string';
|
import * as queryString from 'query-string';
|
||||||
import * as React from 'react';
|
import {useEffect, useState} from 'react';
|
||||||
import logo from './topola.jpg';
|
import logo from './topola.jpg';
|
||||||
import {Card, Grid, Image} from 'semantic-ui-react';
|
import {Card, Grid, Image} from 'semantic-ui-react';
|
||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
import {Link} from 'react-router-dom';
|
import {Link} from 'react-router-dom';
|
||||||
import {Media} from './util/media';
|
import {Media} from './util/media';
|
||||||
|
import {getChangelog, updateSeenVersion} from './changelog';
|
||||||
|
|
||||||
/** Link that loads a GEDCOM file from URL. */
|
/** Link that loads a GEDCOM file from URL. */
|
||||||
function GedcomLink(props: {url: string; text: string}) {
|
function GedcomLink(props: {url: string; text: string}) {
|
||||||
@@ -21,9 +22,16 @@ function formatBuildDate(dateString: string) {
|
|||||||
return dateString.slice(0, 16);
|
return dateString.slice(0, 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The intro page. */
|
function Contents() {
|
||||||
export function Intro() {
|
const [changelog, setChangelog] = useState('');
|
||||||
const contents = (
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
setChangelog(await getChangelog(1));
|
||||||
|
updateSeenVersion();
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
<>
|
<>
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
@@ -44,14 +52,10 @@ export function Intro() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
|
||||||
<FormattedMessage
|
<h3>
|
||||||
id="intro.examples"
|
<FormattedMessage id="intro.examples" defaultMessage="Examples" />
|
||||||
defaultMessage={
|
</h3>
|
||||||
'Here are some examples from the web that you can view:'
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<GedcomLink
|
<GedcomLink
|
||||||
@@ -76,11 +80,21 @@ export function Intro() {
|
|||||||
)
|
)
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>
|
|
||||||
<b>
|
<h3>
|
||||||
|
<FormattedMessage id="intro.whats_new" defaultMessage="What's new" />
|
||||||
|
</h3>
|
||||||
|
<span dangerouslySetInnerHTML={{__html: changelog}} />
|
||||||
|
<a href="https://github.com/PeWu/topola-viewer/blob/master/CHANGELOG.md">
|
||||||
|
<FormattedMessage
|
||||||
|
id="intro.full_changelog"
|
||||||
|
defaultMessage="See full changelog"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<h3>
|
||||||
<FormattedMessage id="intro.privacy" defaultMessage="Privacy" />
|
<FormattedMessage id="intro.privacy" defaultMessage="Privacy" />
|
||||||
</b>
|
</h3>
|
||||||
{': '}
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="intro.privacy_note"
|
id="intro.privacy_note"
|
||||||
defaultMessage={
|
defaultMessage={
|
||||||
@@ -91,12 +105,10 @@ export function Intro() {
|
|||||||
' cross-site file loading in the browser (CORS).'
|
' cross-site file loading in the browser (CORS).'
|
||||||
}
|
}
|
||||||
values={{
|
values={{
|
||||||
link: (
|
link: <a href="https://topola-cors.herokuapp.com/">cors-anywhere</a>,
|
||||||
<a href="https://topola-cors.herokuapp.com/">cors-anywhere</a>
|
|
||||||
),
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</p>
|
|
||||||
<p className="ui right aligned version">
|
<p className="ui right aligned version">
|
||||||
version: {formatBuildDate(process.env.REACT_APP_GIT_TIME!)} (
|
version: {formatBuildDate(process.env.REACT_APP_GIT_TIME!)} (
|
||||||
<a
|
<a
|
||||||
@@ -108,7 +120,10 @@ export function Intro() {
|
|||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The intro page. */
|
||||||
|
export function Intro() {
|
||||||
return (
|
return (
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<div className="backgroundImage" />
|
<div className="backgroundImage" />
|
||||||
@@ -127,7 +142,9 @@ export function Intro() {
|
|||||||
<Grid.Column width={5}>
|
<Grid.Column width={5}>
|
||||||
<Image src={logo} alt="Topola logo" />
|
<Image src={logo} alt="Topola logo" />
|
||||||
</Grid.Column>
|
</Grid.Column>
|
||||||
<Grid.Column width={11}>{contents}</Grid.Column>
|
<Grid.Column width={11}>
|
||||||
|
<Contents />
|
||||||
|
</Grid.Column>
|
||||||
</Grid.Row>
|
</Grid.Row>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Media at="small">
|
<Media at="small">
|
||||||
@@ -138,7 +155,7 @@ export function Intro() {
|
|||||||
size="tiny"
|
size="tiny"
|
||||||
className="blockImage"
|
className="blockImage"
|
||||||
/>
|
/>
|
||||||
{contents}
|
<Contents />
|
||||||
</Media>
|
</Media>
|
||||||
</Card.Content>
|
</Card.Content>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -25,8 +25,10 @@
|
|||||||
"intro.title": "Topola Genealogy",
|
"intro.title": "Topola Genealogy",
|
||||||
"intro.description": "Topola Genealogy pozwala przeglądać drzewo genealogiczne w interaktywny sposób.",
|
"intro.description": "Topola Genealogy pozwala przeglądać drzewo genealogiczne w interaktywny sposób.",
|
||||||
"intro.instructions": "Kliknij OTWÓRZ PLIK lub OTWÓRZ URL, aby załadować plik GEDCOM. Większość programów genealogicznych posiada funkcję eksportu do pliku GEDCOM.",
|
"intro.instructions": "Kliknij OTWÓRZ PLIK lub OTWÓRZ URL, aby załadować plik GEDCOM. Większość programów genealogicznych posiada funkcję eksportu do pliku GEDCOM.",
|
||||||
"intro.examples": "Poniżej jest kilka przykładów znalezionych w Internecie:",
|
"intro.examples": "Przykłady",
|
||||||
"intro.from": "źródło:",
|
"intro.from": "źródło:",
|
||||||
|
"intro.whats_new": "Co nowego?",
|
||||||
|
"intro.full_changelog": "Zobacz pełną listę zmian",
|
||||||
"intro.privacy": "Prywatność",
|
"intro.privacy": "Prywatność",
|
||||||
"intro.privacy_note": "Używając funkcji \"Otwórz plik\", Twoje dane nie są nigdzie wysyłane i pozostają na Twoim komputerze. Używając funkcji \"Otwórz URL\", dane z podanego adresu przesyłane są przez usługę {link} w celu umożliwienia załadowania danych z innej domeny (CORS).",
|
"intro.privacy_note": "Używając funkcji \"Otwórz plik\", Twoje dane nie są nigdzie wysyłane i pozostają na Twoim komputerze. Używając funkcji \"Otwórz URL\", dane z podanego adresu przesyłane są przez usługę {link} w celu umożliwienia załadowania danych z innej domeny (CORS).",
|
||||||
"load_from_url.title": "Otwórz z adresu URL",
|
"load_from_url.title": "Otwórz z adresu URL",
|
||||||
|
|||||||
Reference in New Issue
Block a user