diff --git a/asset-manifest.json b/asset-manifest.json index d10843e..b5b06e5 100644 --- a/asset-manifest.json +++ b/asset-manifest.json @@ -1,7 +1,7 @@ { "main.css": "/topola-viewer/static/css/main.445e4fcf.chunk.css", - "main.js": "/topola-viewer/static/js/main.7bd4856b.chunk.js", - "main.js.map": "/topola-viewer/static/js/main.7bd4856b.chunk.js.map", + "main.js": "/topola-viewer/static/js/main.ea01489b.chunk.js", + "main.js.map": "/topola-viewer/static/js/main.ea01489b.chunk.js.map", "static/css/1.a8a1ee3a.chunk.css": "/topola-viewer/static/css/1.a8a1ee3a.chunk.css", "static/js/1.8fa13ada.chunk.js": "/topola-viewer/static/js/1.8fa13ada.chunk.js", "static/js/1.8fa13ada.chunk.js.map": "/topola-viewer/static/js/1.8fa13ada.chunk.js.map", @@ -11,6 +11,6 @@ "static/css/main.445e4fcf.chunk.css.map": "/topola-viewer/static/css/main.445e4fcf.chunk.css.map", "static/css/1.a8a1ee3a.chunk.css.map": "/topola-viewer/static/css/1.a8a1ee3a.chunk.css.map", "index.html": "/topola-viewer/index.html", - "precache-manifest.a0eead2d57e24c81df390eb33038e2d7.js": "/topola-viewer/precache-manifest.a0eead2d57e24c81df390eb33038e2d7.js", + "precache-manifest.4f3816894a1095505451e64001ff9567.js": "/topola-viewer/precache-manifest.4f3816894a1095505451e64001ff9567.js", "service-worker.js": "/topola-viewer/service-worker.js" } \ No newline at end of file diff --git a/index.html b/index.html index 6551ae1..695065a 100644 --- a/index.html +++ b/index.html @@ -1 +1 @@ -Topola Genealogy Viewer
\ No newline at end of file +Topola Genealogy Viewer
\ No newline at end of file diff --git a/precache-manifest.a0eead2d57e24c81df390eb33038e2d7.js b/precache-manifest.4f3816894a1095505451e64001ff9567.js similarity index 92% rename from precache-manifest.a0eead2d57e24c81df390eb33038e2d7.js rename to precache-manifest.4f3816894a1095505451e64001ff9567.js index 7931294..de1a644 100644 --- a/precache-manifest.a0eead2d57e24c81df390eb33038e2d7.js +++ b/precache-manifest.4f3816894a1095505451e64001ff9567.js @@ -1,10 +1,10 @@ self.__precacheManifest = [ { - "revision": "962a1bf31c081691065fe333d9fa8105", - "url": "/topola-viewer/static/media/icons.962a1bf3.svg" + "revision": "ef60a4f6c25ef7f39f2d25a748dbecfe", + "url": "/topola-viewer/static/media/outline-icons.ef60a4f6.woff" }, { - "revision": "7bd4856bfbf32a3a6c64", + "revision": "ea01489b0eaf71ab02ef", "url": "/topola-viewer/static/css/main.445e4fcf.chunk.css" }, { @@ -44,8 +44,8 @@ self.__precacheManifest = [ "url": "/topola-viewer/static/media/outline-icons.701ae6ab.eot" }, { - "revision": "7bd4856bfbf32a3a6c64", - "url": "/topola-viewer/static/js/main.7bd4856b.chunk.js" + "revision": "ea01489b0eaf71ab02ef", + "url": "/topola-viewer/static/js/main.ea01489b.chunk.js" }, { "revision": "cd6c777f1945164224dee082abaea03a", @@ -55,26 +55,26 @@ self.__precacheManifest = [ "revision": "ad97afd3337e8cda302d10ff5a4026b8", "url": "/topola-viewer/static/media/outline-icons.ad97afd3.ttf" }, - { - "revision": "ef60a4f6c25ef7f39f2d25a748dbecfe", - "url": "/topola-viewer/static/media/outline-icons.ef60a4f6.woff" - }, { "revision": "82f60bd0b94a1ed68b1e6e309ce2e8c3", "url": "/topola-viewer/static/media/outline-icons.82f60bd0.svg" }, + { + "revision": "962a1bf31c081691065fe333d9fa8105", + "url": "/topola-viewer/static/media/icons.962a1bf3.svg" + }, { "revision": "13db00b7a34fee4d819ab7f9838cc428", "url": "/topola-viewer/static/media/brand-icons.13db00b7.eot" }, - { - "revision": "a046592bac8f2fd96e994733faf3858c", - "url": "/topola-viewer/static/media/brand-icons.a046592b.woff" - }, { "revision": "e8c322de9658cbeb8a774b6624167c2c", "url": "/topola-viewer/static/media/brand-icons.e8c322de.woff2" }, + { + "revision": "a046592bac8f2fd96e994733faf3858c", + "url": "/topola-viewer/static/media/brand-icons.a046592b.woff" + }, { "revision": "c5ebe0b32dc1b5cc449a76c4204d13bb", "url": "/topola-viewer/static/media/brand-icons.c5ebe0b3.ttf" @@ -84,7 +84,7 @@ self.__precacheManifest = [ "url": "/topola-viewer/static/css/1.a8a1ee3a.chunk.css" }, { - "revision": "bbb659f8af98a9e1fa204b7d9d7fab19", + "revision": "b8bee74494c20b680c5965f686a79bc6", "url": "/topola-viewer/index.html" } ]; \ No newline at end of file diff --git a/service-worker.js b/service-worker.js index 2053603..a538392 100644 --- a/service-worker.js +++ b/service-worker.js @@ -14,7 +14,7 @@ importScripts("https://storage.googleapis.com/workbox-cdn/releases/3.6.3/workbox-sw.js"); importScripts( - "/topola-viewer/precache-manifest.a0eead2d57e24c81df390eb33038e2d7.js" + "/topola-viewer/precache-manifest.4f3816894a1095505451e64001ff9567.js" ); workbox.clientsClaim(); diff --git a/static/js/main.7bd4856b.chunk.js b/static/js/main.7bd4856b.chunk.js deleted file mode 100644 index ca412ba..0000000 --- a/static/js/main.7bd4856b.chunk.js +++ /dev/null @@ -1,2 +0,0 @@ -(window.webpackJsonp=window.webpackJsonp||[]).push([[0],{223:function(e){e.exports={"menu.load_from_url":"Otw\xf3rz URL","menu.load_from_file":"Otw\xf3rz plik","menu.github":"\u0179r\xf3d\u0142a na GitHub","intro.title":"Topola Genealogy","intro.description":"Topola Genealogy pozwala przegl\u0105da\u0107 drzewo genealogiczne w interaktywny spos\xf3b.","intro.instructions":"Kliknij OTW\xd3RZ URL lub OTW\xd3RZ PLIK, aby za\u0142adowa\u0107 plik GEDCOM. Wi\u0119kszo\u015b\u0107 program\xf3w genealogicznych posiada funkcj\u0119 eksportu do pliku GEDCOM.","intro.examples":"Poni\u017cej jest kilka przyk\u0142ad\xf3w znalezionych w Internecie:","intro.from":"\u017ar\xf3d\u0142o:","intro.privacy":"Prywatno\u015b\u0107","intro.privacy_note":'U\u017cywaj\u0105c funkcji "Otw\xf3rz plik", Twoje dane nie s\u0105 nigdzie wysy\u0142ane i pozostaj\u0105 na Twoim komputerze. U\u017cywaj\u0105c funkcji "Otw\xf3rz URL", dane z podanego adresu przesy\u0142ane s\u0105 przez us\u0142ug\u0119 {link} w celu umo\u017cliwienia za\u0142adowania danych z innej domeny (CORS).',"load_from_url.title":"Otw\xf3rz z adresu URL","load_from_url.comment":"Dane z podanego adresu URL zostan\u0105 za\u0142adowane poprzez us\u0142ug\u0119 {link} w celu unikni\u0119cia problem\xf3w z CORS.","load_from_url.cancel":"Anuluj","load_from_url.load":"Otw\xf3rz"}},224:function(e,t){},260:function(e,t,a){e.exports=a(461)},272:function(e,t){},278:function(e,t){},455:function(e,t,a){},461:function(e,t,a){"use strict";a.r(t);var n=a(143),r=a(221),o=a(222),i=a(0),l=a(58),s=a(223),c=a(25),d=a(226),u=a(35),h=a(36),m=a(40),p=a(37),f=a(39),g=a(41),v=a(100),y=a.n(v),E=a(26),w=a(66);function b(){var e=E.select("#chart").node().parentElement;e.scrollLeft=-E.event.transform.x,e.scrollTop=-E.event.transform.y}function O(){var e=E.select("#chart").node().parentElement,t=e.scrollLeft+e.clientWidth/2,a=e.scrollTop+e.clientHeight/2;E.select(e).call(E.zoom().translateTo,t,a)}var j=function(e){function t(){var e,a;Object(u.a)(this,t);for(var n=arguments.length,r=new Array(n),o=0;o0&&void 0!==arguments[0]?arguments[0]:{initialRender:!1};t.initialRender&&(E.select("#chart").node().innerHTML="",this.chart=Object(w.createChart)({json:this.props.data,chartType:w.HourglassChart,renderer:w.DetailedRenderer,svgSelector:"#chart",indiCallback:function(t){return e.props.onSelection(t)},animate:!0,updateSvgSize:!1,locale:this.context.intl.locale}));var a=this.chart.render({startIndi:this.props.selection.id,baseGeneration:this.props.selection.generation}),n=E.select("#chart"),r=n.node().parentElement;E.select(r).on("scroll",O).call(E.zoom().scaleExtent([1,1]).translateExtent([[0,0],a.size]).on("zoom",b));var o,i,l=r.clientWidth/2-a.origin[0],s=r.clientHeight/2-a.origin[1],c=E.max([0,(r.clientWidth-a.size[0])/2]),d=E.max([0,(r.clientHeight-a.size[1])/2]),u=n.transition().delay(200).duration(500);(t.initialRender?n:u).attr("transform","translate(".concat(c,", ").concat(d,")")).attr("width",a.size[0]).attr("height",a.size[1]),t.initialRender?(r.scrollLeft=-l,r.scrollTop=-s):u.tween("scrollLeft",(i=-l,function(){var e=E.interpolateNumber(r.scrollLeft,i);return function(t){r.scrollLeft=e(t)}})).tween("scrollTop",(o=-s,function(){var e=E.interpolateNumber(r.scrollTop,o);return function(t){r.scrollTop=e(t)}}))}},{key:"componentDidMount",value:function(){this.renderChart({initialRender:!0})}},{key:"componentDidUpdate",value:function(e){this.renderChart({initialRender:this.props.data!==e.data})}},{key:"render",value:function(){return i.createElement("div",{id:"svgContainer"},i.createElement("svg",{id:"chart"}))}}]),t}(i.PureComponent);function k(e,t){if(!e.children)return e;var a=e.children.sort(function(e){var t=new Map;return e.indis.forEach(function(e){t[e.id]=e}),function(e,a){var n,r,o=(n=e)<(r=a)?-1:n>r?1:0,i=t[e],l=t[a],s=i&&i.birth,c=l&&l.birth,d=s&&(s.date||s.dateRange&&s.dateRange.from),u=c&&(c.date||c.dateRange&&c.dateRange.from);return d&&d.year&&u&&u.year?d.year!==u.year?d.year-u.year:d.month&&u.month?d.month!==u.month?d.month-u.month:d.day&&u.day&&d.day!==u.day?d.month-u.month:o:o:o}}(t));return Object.assign({},e,{children:a})}function z(e){if(!e.imageUrl||e.imageUrl.startsWith("http"))return e;var t=Object.assign({},e);return delete t.imageUrl,t}function C(e){var t=Object(w.gedcomToJson)(e);if(!t||!t.indis||!t.indis.length||!t.fams||!t.fams.length)throw new Error("Failed to read GEDCOM file");return function(e){var t=e.indis.map(z);return Object.assign({},e,{indis:t})}(function(e){var t=e.fams.map(function(t){return k(t,e)});return Object.assign({},e,{fams:t})}(t))}j.contextTypes={intl:c.d};var U=a(477),S=a(470);function R(e){return i.createElement(U.a,{negative:!0,className:"error"},i.createElement(U.a.Header,null,"Failed to load file"),i.createElement("p",null,e.message))}function _(e,t,a){return{id:t||e.indis[0].id,generation:a||0}}var L=function(e){function t(){var e,a;Object(u.a)(this,t);for(var n=arguments.length,r=new Array(n),o=0;o1&&void 0!==arguments[1]?arguments[1]:{},n=sessionStorage.getItem(e);if(n){var r=JSON.parse(n);this.setState(Object.assign({},this.state,{data:r,selection:_(r,a.indi,a.generation),loadedUrl:e,loading:!1,error:void 0,hash:void 0}))}else{this.setState(Object.assign({},this.state,{loading:!0,loadedUrl:e,data:void 0,error:void 0}));var o=a.handleCors?"https://cors-anywhere.herokuapp.com/"+e:e;window.fetch(o).then(function(e){return 200!==e.status?Promise.reject(new Error(e.statusText)):e.text()}).then(function(n){return t.setGedcom({gedcom:n,url:e,indi:a.indi,generation:a.generation})}).catch(function(e){return t.setState(Object.assign({},t.state,{error:e.message,loading:!1}))})}}},{key:"setGedcom",value:function(e){var t=y()(e.gedcom);try{var a=C(e.gedcom),n=JSON.stringify(a);sessionStorage.setItem(e.url||t,n),this.setState(Object.assign({},this.state,{data:a,selection:_(a,e.indi,e.generation),hash:t,loading:!1,loadedUrl:e.url,error:void 0}))}catch(r){this.setState(Object.assign({},this.state,{data:void 0,selection:void 0,hash:t,loading:!1,error:"Failed to read GEDCOM file",loadedUrl:e.url}))}}},{key:"componentDidMount",value:function(){this.componentDidUpdate()}},{key:"componentDidUpdate",value:function(){var e=this.props.location.state&&this.props.location.state.data,t=g.parse(this.props.location.search),a=function(e){var a=t[e];return"string"===typeof a?a:void 0},n=a("url"),r=a("indi"),o=Number(a("gen")),i=isNaN(o)?void 0:o,l=a("file"),s="false"!==a("handleCors");if(l&&l!==this.state.hash)if(e)this.setGedcom({gedcom:e,indi:r,generation:i});else{var c=sessionStorage.getItem(l);if(c){var d=JSON.parse(c);this.setState(Object.assign({},this.state,{data:d,hash:l,selection:_(d,r,i),error:void 0,loading:!1,loadedUrl:void 0}))}else this.props.history.replace({pathname:"/"})}else if(!this.state.loading&&n&&this.state.loadedUrl!==n)this.loadFromUrl(n,{indi:r,generation:i,handleCors:n.startsWith("http")&&s});else if(n||e||l===this.state.hash){if(this.state.data&&this.state.selection){var u=_(this.state.data,r,i);this.state.selection.id===u.id&&this.state.selection.generation===u.generation||this.setState(Object.assign({},this.state,{selection:u}))}}else this.props.history.replace({pathname:"/"})}},{key:"render",value:function(){return this.state.data&&this.state.selection?i.createElement(j,{data:this.state.data,onSelection:this.onSelection,selection:this.state.selection}):this.state.error?i.createElement(R,{message:this.state.error}):i.createElement(S.a,{active:!0,size:"large"})}}]),t}(i.Component),M=a(480),x=a(484),T=a(483),D=a(476),G=a(471);function F(e){return i.createElement(G.a,{to:{pathname:"/view",search:g.stringify({url:e.url})}},e.text)}var I=a(475),N=a(481),H=a(220),W=a(474),P=a(473),A=a(479),J=a(478),K=function(e){function t(){var e,a;Object(u.a)(this,t);for(var n=arguments.length,r=new Array(n),o=0;o void;\n}\n\n/** Component showing the genealogy chart and handling transition animations. */\nexport class Chart extends React.PureComponent {\n private chart?: ChartHandle;\n\n /**\n * Renders the chart or performs a transition animation to a new state.\n * If indiInfo is not given, it means that it is the initial render and no\n * animation is performed.\n */\n private renderChart(args: {initialRender: boolean} = {initialRender: false}) {\n if (args.initialRender) {\n (d3.select('#chart').node() as HTMLElement).innerHTML = '';\n this.chart = createChart({\n json: this.props.data,\n chartType: HourglassChart,\n renderer: DetailedRenderer,\n svgSelector: '#chart',\n indiCallback: (info) => this.props.onSelection(info),\n animate: true,\n updateSvgSize: false,\n locale: this.context.intl.locale,\n });\n }\n const chartInfo = this.chart!.render({\n startIndi: this.props.selection.id,\n baseGeneration: this.props.selection.generation,\n });\n const svg = d3.select('#chart');\n const parent = (svg.node() as HTMLElement).parentElement as Element;\n\n d3.select(parent)\n .on('scroll', scrolled)\n .call(\n d3\n .zoom()\n .scaleExtent([1, 1])\n .translateExtent([[0, 0], chartInfo.size])\n .on('zoom', zoomed),\n );\n\n const scrollTopTween = (scrollTop: number) => {\n return () => {\n const i = d3.interpolateNumber(parent.scrollTop, scrollTop);\n return (t: number) => {\n parent.scrollTop = i(t);\n };\n };\n };\n const scrollLeftTween = (scrollLeft: number) => {\n return () => {\n const i = d3.interpolateNumber(parent.scrollLeft, scrollLeft);\n return (t: number) => {\n parent.scrollLeft = i(t);\n };\n };\n };\n\n const dx = parent.clientWidth / 2 - chartInfo.origin[0];\n const dy = parent.clientHeight / 2 - chartInfo.origin[1];\n const offsetX = d3.max([0, (parent.clientWidth - chartInfo.size[0]) / 2]);\n const offsetY = d3.max([0, (parent.clientHeight - chartInfo.size[1]) / 2]);\n const svgTransition = svg\n .transition()\n .delay(200)\n .duration(500);\n const transition = args.initialRender ? svg : svgTransition;\n transition\n .attr('transform', `translate(${offsetX}, ${offsetY})`)\n .attr('width', chartInfo.size[0])\n .attr('height', chartInfo.size[1]);\n if (args.initialRender) {\n parent.scrollLeft = -dx;\n parent.scrollTop = -dy;\n } else {\n svgTransition\n .tween('scrollLeft', scrollLeftTween(-dx))\n .tween('scrollTop', scrollTopTween(-dy));\n }\n }\n\n componentDidMount() {\n this.renderChart({initialRender: true});\n }\n\n componentDidUpdate(prevProps: ChartProps) {\n this.renderChart({initialRender: this.props.data !== prevProps.data});\n }\n\n /** Make intl appear in this.context. */\n static contextTypes = {\n intl: intlShape,\n };\n\n render() {\n return (\n
\n \n
\n );\n }\n}\n","import {gedcomToJson, JsonFam, JsonGedcomData, JsonIndi} from 'topola';\n\nfunction strcmp(a: string, b: string) {\n if (a < b) {\n return -1;\n }\n if (a > b) {\n return 1;\n }\n return 0;\n}\n\n/** Birth date comparator for individuals. */\nfunction birthDatesComparator(gedcom: JsonGedcomData) {\n const idToIndiMap = new Map();\n gedcom.indis.forEach((indi) => {\n idToIndiMap[indi.id] = indi;\n });\n\n return (indiId1: string, indiId2: string) => {\n const idComparison = strcmp(indiId1, indiId2);\n const indi1: JsonIndi = idToIndiMap[indiId1];\n const indi2: JsonIndi = idToIndiMap[indiId2];\n const birth1 = indi1 && indi1.birth;\n const birth2 = indi2 && indi2.birth;\n const date1 =\n birth1 && (birth1.date || (birth1.dateRange && birth1.dateRange.from));\n const date2 =\n birth2 && (birth2.date || (birth2.dateRange && birth2.dateRange.from));\n if (!date1 || !date1.year || !date2 || !date2.year) {\n return idComparison;\n }\n if (date1.year !== date2.year) {\n return date1.year - date2.year;\n }\n if (!date1.month || !date2.month) {\n return idComparison;\n }\n if (date1.month !== date2.month) {\n return date1.month - date2.month;\n }\n if (date1.day && date2.day && date1.day !== date2.day) {\n return date1.month - date2.month;\n }\n return idComparison;\n };\n}\n\n/**\n * Sorts children by birth date in the given family.\n * Does not modify the input objects.\n */\nfunction sortFamilyChildren(fam: JsonFam, gedcom: JsonGedcomData): JsonFam {\n if (!fam.children) {\n return fam;\n }\n const newChildren = fam.children.sort(birthDatesComparator(gedcom));\n return Object.assign({}, fam, {children: newChildren});\n}\n\n/**\n * Sorts children by birth date.\n * Does not modify the input object.\n */\nfunction sortChildren(gedcom: JsonGedcomData): JsonGedcomData {\n const newFams = gedcom.fams.map((fam) => sortFamilyChildren(fam, gedcom));\n return Object.assign({}, gedcom, {fams: newFams});\n}\n\n/**\n * Removes images that are not HTTP links.\n * Does not modify the input object.\n */\nfunction filterImage(indi: JsonIndi): JsonIndi {\n if (!indi.imageUrl || indi.imageUrl.startsWith('http')) {\n return indi;\n }\n const newIndi = Object.assign({}, indi);\n delete newIndi.imageUrl;\n return newIndi;\n}\n\n/**\n * Removes images that are not HTTP links.\n * Does not modify the input object.\n */\nfunction filterImages(gedcom: JsonGedcomData): JsonGedcomData {\n const newIndis = gedcom.indis.map(filterImage);\n return Object.assign({}, gedcom, {indis: newIndis});\n}\n\n/**\n * Converts GEDCOM file into JSON data performing additional transformations:\n * - sort children by birth date\n * - remove images that are not HTTP links.\n */\nexport function convertGedcom(gedcom: string): JsonGedcomData {\n const json = gedcomToJson(gedcom);\n if (\n !json ||\n !json.indis ||\n !json.indis.length ||\n !json.fams ||\n !json.fams.length\n ) {\n throw new Error('Failed to read GEDCOM file');\n }\n return filterImages(sortChildren(json));\n}\n","import * as queryString from 'query-string';\nimport * as React from 'react';\nimport md5 from 'md5';\nimport {Chart} from './chart';\nimport {convertGedcom} from './gedcom_util';\nimport {IndiInfo, JsonGedcomData} from 'topola';\nimport {Loader, Message} from 'semantic-ui-react';\nimport {RouteComponentProps} from 'react-router-dom';\n\n/** Shows an error message. */\nexport function ErrorMessage(props: {message: string}) {\n return (\n \n Failed to load file\n

{props.message}

\n
\n );\n}\n\n/**\n * Returns a valid IndiInfo object, either with the given indi and generation\n * or with an individual taken from the data and generation 0.\n */\nfunction getSelection(\n data: JsonGedcomData,\n indi?: string,\n generation?: number,\n): IndiInfo {\n return {\n id: indi || data.indis[0].id,\n generation: generation || 0,\n };\n}\n\ninterface State {\n /** Loaded data. */\n data?: JsonGedcomData;\n /** Selected individual. */\n selection?: IndiInfo;\n /** Hash of the GEDCOM contents. */\n hash?: string;\n /** Error to display. */\n error?: string;\n /** True if currently loading. */\n loading: boolean;\n /** URL of the data that is loaded or is being loaded. */\n loadedUrl?: string;\n}\n\n/** The main area of the application dedicated for rendering the family chart. */\nexport class ChartView extends React.Component {\n state: State = {loading: false};\n\n /**\n * Called when the user clicks an individual box in the chart.\n * Updates the browser URL.\n */\n onSelection = (selection: IndiInfo) => {\n const location = this.props.location;\n const search = queryString.parse(location.search);\n search.indi = selection.id;\n search.gen = String(selection.generation);\n location.search = queryString.stringify(search);\n this.props.history.push(location);\n };\n\n /** Loads a GEDCOM file from the given URL. */\n loadFromUrl(\n url: string,\n options: {\n handleCors?: boolean;\n indi?: string;\n generation?: number;\n } = {},\n ) {\n const cachedData = sessionStorage.getItem(url);\n if (cachedData) {\n const data = JSON.parse(cachedData);\n this.setState(\n Object.assign({}, this.state, {\n data,\n selection: getSelection(data, options.indi, options.generation),\n loadedUrl: url,\n loading: false,\n error: undefined,\n hash: undefined,\n }),\n );\n return;\n }\n\n this.setState(\n Object.assign({}, this.state, {\n loading: true,\n loadedUrl: url,\n data: undefined,\n error: undefined,\n }),\n );\n\n const urlToFetch = options.handleCors\n ? 'https://cors-anywhere.herokuapp.com/' + url\n : url;\n\n window\n .fetch(urlToFetch)\n .then((response) => {\n if (response.status !== 200) {\n return Promise.reject(new Error(response.statusText));\n }\n return response.text();\n })\n .then((data) =>\n this.setGedcom({\n gedcom: data,\n url,\n indi: options.indi,\n generation: options.generation,\n }),\n )\n .catch((e: Error) =>\n this.setState(\n Object.assign({}, this.state, {error: e.message, loading: false}),\n ),\n );\n }\n\n /**\n * Converts GEDCOM contents and sets the data in the state.\n * In case of an error reading the file, sets an error.\n */\n setGedcom(input: {\n gedcom: string;\n url?: string;\n indi?: string;\n generation?: number;\n }) {\n const hash = md5(input.gedcom);\n try {\n const data = convertGedcom(input.gedcom);\n const serializedData = JSON.stringify(data);\n sessionStorage.setItem(input.url || hash, serializedData);\n this.setState(\n Object.assign({}, this.state, {\n data,\n selection: getSelection(data, input.indi, input.generation),\n hash,\n loading: false,\n loadedUrl: input.url,\n error: undefined,\n }),\n );\n } catch (e) {\n this.setState(\n Object.assign({}, this.state, {\n data: undefined,\n selection: undefined,\n hash,\n loading: false,\n error: 'Failed to read GEDCOM file',\n loadedUrl: input.url,\n }),\n );\n }\n }\n\n componentDidMount() {\n this.componentDidUpdate();\n }\n\n componentDidUpdate() {\n const gedcom = this.props.location.state && this.props.location.state.data;\n const search = queryString.parse(this.props.location.search);\n const getParam = (name: string) => {\n const value = search[name];\n return typeof value === 'string' ? value : undefined;\n };\n const url = getParam('url');\n const indi = getParam('indi');\n const parsedGen = Number(getParam('gen'));\n const generation = !isNaN(parsedGen) ? parsedGen : undefined;\n const hash = getParam('file');\n const handleCors = getParam('handleCors') !== 'false';\n\n if (hash && hash !== this.state.hash) {\n // New \"load from file\" data.\n if (gedcom) {\n this.setGedcom({gedcom, indi, generation});\n } else {\n // Data is not present. Try loading from cache.\n const cachedData = sessionStorage.getItem(hash);\n if (cachedData) {\n const data = JSON.parse(cachedData);\n this.setState(\n Object.assign({}, this.state, {\n data,\n hash,\n selection: getSelection(data, indi, generation),\n error: undefined,\n loading: false,\n loadedUrl: undefined,\n }),\n );\n } else {\n // No data available. Redirect to main page.\n this.props.history.replace({pathname: '/'});\n }\n }\n } else if (!this.state.loading && url && this.state.loadedUrl !== url) {\n // New URL to load data from.\n this.loadFromUrl(url, {\n indi,\n generation,\n handleCors: url.startsWith('http') && handleCors,\n });\n } else if (!url && !gedcom && hash !== this.state.hash) {\n this.props.history.replace({pathname: '/'});\n } else if (this.state.data && this.state.selection) {\n // Update selection if it has changed in the URL.\n const selection = getSelection(this.state.data, indi, generation);\n if (\n this.state.selection.id !== selection.id ||\n this.state.selection.generation !== selection.generation\n ) {\n this.setState(\n Object.assign({}, this.state, {\n selection,\n }),\n );\n }\n }\n }\n\n render() {\n if (this.state.data && this.state.selection) {\n return (\n \n );\n }\n if (this.state.error) {\n return ;\n }\n return ;\n }\n}\n","import * as queryString from 'query-string';\nimport * as React from 'react';\nimport {Card} from 'semantic-ui-react';\nimport {FormattedMessage} from 'react-intl';\nimport {Link} from 'react-router-dom';\n\n/** Link that loads a GEDCOM file from URL. */\nfunction GedcomLink(props: {url: string; text: string}) {\n return (\n \n {props.text}\n \n );\n}\n\n/** The intro page. */\nexport function Intro() {\n return (\n \n \n \n \n \n \n \n

\n \n

\n

\n \n

\n

\n \n

\n \n

\n \n \n \n :\n cors-anywhere\n ),\n }}\n />\n

\n
\n
\n );\n}\n","import * as queryString from 'query-string';\nimport * as React from 'react';\nimport md5 from 'md5';\nimport {FormattedMessage} from 'react-intl';\nimport {Link} from 'react-router-dom';\nimport {RouteComponentProps} from 'react-router-dom';\nimport {\n Header,\n Button,\n Icon,\n Menu,\n Modal,\n Input,\n Form,\n} from 'semantic-ui-react';\n\n/** Menus and dialogs state. */\ninterface State {\n loadUrlDialogOpen: boolean;\n url?: string;\n}\n\nexport class TopBar extends React.Component {\n state: State = {loadUrlDialogOpen: false};\n inputRef?: Input;\n\n /** Handles the \"Upload file\" button. */\n handleUpload(event: React.SyntheticEvent) {\n const files = (event.target as HTMLInputElement).files;\n if (!files || !files.length) {\n return;\n }\n const reader = new FileReader();\n reader.onload = (evt: ProgressEvent) => {\n const data = (evt.target as FileReader).result;\n const hash = md5(data as string);\n this.props.history.push({\n pathname: '/view',\n search: queryString.stringify({file: hash}),\n state: {data},\n });\n };\n reader.readAsText(files[0]);\n }\n\n /** Opens the \"Load from URL\" dialog. */\n handleLoadFromUrl() {\n this.setState(\n Object.assign({}, this.state, {loadUrlDialogOpen: true}),\n () => this.inputRef!.focus(),\n );\n }\n\n /** Cancels the \"Load from URL\" dialog. */\n handleClose() {\n this.setState(Object.assign({}, this.state, {loadUrlDialogOpen: false}));\n }\n\n /** Upload button clicked in the \"Load from URL\" dialog. */\n handleLoad() {\n this.setState(\n Object.assign({}, this.state, {\n loadUrlDialogOpen: false,\n }),\n );\n if (this.state.url) {\n this.props.history.push({\n pathname: '/view',\n search: queryString.stringify({url: this.state.url}),\n });\n }\n }\n\n /** Called when the URL input is typed into. */\n handleUrlChange(event: React.SyntheticEvent) {\n this.setState(\n Object.assign({}, this.state, {\n url: (event.target as HTMLInputElement).value,\n }),\n );\n }\n\n render() {\n const loadFromUrlModal = (\n this.handleClose()}\n centered={false}\n >\n
\n \n txt}\n />\n
\n \n
this.handleLoad()}>\n this.handleUrlChange(e)}\n ref={(ref) => (this.inputRef = ref!)}\n />\n

\n \n cors-anywhere.herokuapp.com\n \n ),\n }}\n />\n

\n \n
\n \n \n \n \n \n );\n\n return (\n \n \n \n Topola Genealogy\n \n \n this.handleLoadFromUrl()}>\n \n \n \n this.handleUpload(e)}\n />\n \n \n \n \n {loadFromUrlModal}\n \n );\n }\n}\n","import * as locale_en from 'react-intl/locale-data/en';\nimport * as locale_pl from 'react-intl/locale-data/pl';\nimport * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport messages_pl from './translations/pl.json';\nimport {addLocaleData} from 'react-intl';\nimport {detect} from 'detect-browser';\nimport {ChartView} from './chart_view';\nimport {HashRouter as Router, Route, Switch} from 'react-router-dom';\nimport {IntlProvider} from 'react-intl';\nimport {Intro} from './intro';\nimport {TopBar} from './top_bar';\nimport './index.css';\nimport 'semantic-ui-css/semantic.min.css';\n\naddLocaleData([...locale_en, ...locale_pl]);\n\nconst messages = {\n pl: messages_pl,\n};\nconst language = navigator.language && navigator.language.split(/[-_]/)[0];\n\nconst browser = detect();\n\nif (browser && browser.name === 'ie') {\n ReactDOM.render(\n

\n Topola Genealogy Viewer does not support Internet Explorer. Please try a\n different browser.\n

,\n document.querySelector('#root'),\n );\n} else {\n ReactDOM.render(\n \n \n
\n \n \n \n \n \n
\n
\n
,\n document.querySelector('#root'),\n );\n}\n"],"sourceRoot":""} \ No newline at end of file diff --git a/static/js/main.ea01489b.chunk.js b/static/js/main.ea01489b.chunk.js new file mode 100644 index 0000000..a80b283 --- /dev/null +++ b/static/js/main.ea01489b.chunk.js @@ -0,0 +1,2 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[0],{223:function(e){e.exports={"menu.load_from_url":"Otw\xf3rz URL","menu.load_from_file":"Otw\xf3rz plik","menu.print":"Drukuj","menu.github":"\u0179r\xf3d\u0142a na GitHub","intro.title":"Topola Genealogy","intro.description":"Topola Genealogy pozwala przegl\u0105da\u0107 drzewo genealogiczne w interaktywny spos\xf3b.","intro.instructions":"Kliknij OTW\xd3RZ URL lub OTW\xd3RZ PLIK, aby za\u0142adowa\u0107 plik GEDCOM. Wi\u0119kszo\u015b\u0107 program\xf3w genealogicznych posiada funkcj\u0119 eksportu do pliku GEDCOM.","intro.examples":"Poni\u017cej jest kilka przyk\u0142ad\xf3w znalezionych w Internecie:","intro.from":"\u017ar\xf3d\u0142o:","intro.privacy":"Prywatno\u015b\u0107","intro.privacy_note":'U\u017cywaj\u0105c funkcji "Otw\xf3rz plik", Twoje dane nie s\u0105 nigdzie wysy\u0142ane i pozostaj\u0105 na Twoim komputerze. U\u017cywaj\u0105c funkcji "Otw\xf3rz URL", dane z podanego adresu przesy\u0142ane s\u0105 przez us\u0142ug\u0119 {link} w celu umo\u017cliwienia za\u0142adowania danych z innej domeny (CORS).',"load_from_url.title":"Otw\xf3rz z adresu URL","load_from_url.comment":"Dane z podanego adresu URL zostan\u0105 za\u0142adowane poprzez us\u0142ug\u0119 {link} w celu unikni\u0119cia problem\xf3w z CORS.","load_from_url.cancel":"Anuluj","load_from_url.load":"Otw\xf3rz"}},224:function(e,t){},260:function(e,t,a){e.exports=a(461)},272:function(e,t){},278:function(e,t){},455:function(e,t,a){},461:function(e,t,a){"use strict";a.r(t);var n=a(143),r=a(221),o=a(222),i=a(0),l=a(58),s=a(223),c=a(25),d=a(226),u=a(35),m=a(36),h=a(40),p=a(37),f=a(39),g=a(41),v=a(100),y=a.n(v),E=a(26),w=a(66);function b(){var e=E.select("#chart").node().parentElement;e.scrollLeft=-E.event.transform.x,e.scrollTop=-E.event.transform.y}function O(){var e=E.select("#chart").node().parentElement,t=e.scrollLeft+e.clientWidth/2,a=e.scrollTop+e.clientHeight/2;E.select(e).call(E.zoom().translateTo,t,a)}var j=function(e){function t(){var e,a;Object(u.a)(this,t);for(var n=arguments.length,r=new Array(n),o=0;o0&&void 0!==arguments[0]?arguments[0]:{initialRender:!1};t.initialRender&&(E.select("#chart").node().innerHTML="",this.chart=Object(w.createChart)({json:this.props.data,chartType:w.HourglassChart,renderer:w.DetailedRenderer,svgSelector:"#chart",indiCallback:function(t){return e.props.onSelection(t)},animate:!0,updateSvgSize:!1,locale:this.context.intl.locale}));var a=this.chart.render({startIndi:this.props.selection.id,baseGeneration:this.props.selection.generation}),n=E.select("#chart"),r=n.node().parentElement;E.select(r).on("scroll",O).call(E.zoom().scaleExtent([1,1]).translateExtent([[0,0],a.size]).on("zoom",b));var o,i,l=r.clientWidth/2-a.origin[0],s=r.clientHeight/2-a.origin[1],c=E.max([0,(r.clientWidth-a.size[0])/2]),d=E.max([0,(r.clientHeight-a.size[1])/2]),u=n.transition().delay(200).duration(500);(t.initialRender?n:u).attr("transform","translate(".concat(c,", ").concat(d,")")).attr("width",a.size[0]).attr("height",a.size[1]),t.initialRender?(r.scrollLeft=-l,r.scrollTop=-s):u.tween("scrollLeft",(i=-l,function(){var e=E.interpolateNumber(r.scrollLeft,i);return function(t){r.scrollLeft=e(t)}})).tween("scrollTop",(o=-s,function(){var e=E.interpolateNumber(r.scrollTop,o);return function(t){r.scrollTop=e(t)}}))}},{key:"componentDidMount",value:function(){this.renderChart({initialRender:!0})}},{key:"componentDidUpdate",value:function(e){this.renderChart({initialRender:this.props.data!==e.data})}},{key:"render",value:function(){return i.createElement("div",{id:"svgContainer"},i.createElement("svg",{id:"chart"}))}},{key:"print",value:function(){var e=document.createElement("iframe");e.style.position="absolute",e.style.top="-1000px",e.style.left="-1000px",e.onload=function(){var t=document.getElementById("chart").cloneNode(!0);t.removeAttribute("transform");var a=t.outerHTML;e.contentDocument.open(),e.contentDocument.write(a),e.contentDocument.close(),setTimeout(function(){e.contentWindow.focus(),e.contentWindow.print(),e.parentNode.removeChild(e)},500)},document.body.appendChild(e)}}]),t}(i.PureComponent);function k(e,t){if(!e.children)return e;var a=e.children.sort(function(e){var t=new Map;return e.indis.forEach(function(e){t[e.id]=e}),function(e,a){var n,r,o=(n=e)<(r=a)?-1:n>r?1:0,i=t[e],l=t[a],s=i&&i.birth,c=l&&l.birth,d=s&&(s.date||s.dateRange&&s.dateRange.from),u=c&&(c.date||c.dateRange&&c.dateRange.from);return d&&d.year&&u&&u.year?d.year!==u.year?d.year-u.year:d.month&&u.month?d.month!==u.month?d.month-u.month:d.day&&u.day&&d.day!==u.day?d.month-u.month:o:o:o}}(t));return Object.assign({},e,{children:a})}function z(e){if(!e.imageUrl||e.imageUrl.startsWith("http"))return e;var t=Object.assign({},e);return delete t.imageUrl,t}function C(e){var t=Object(w.gedcomToJson)(e);if(!t||!t.indis||!t.indis.length||!t.fams||!t.fams.length)throw new Error("Failed to read GEDCOM file");return function(e){var t=e.indis.map(z);return Object.assign({},e,{indis:t})}(function(e){var t=e.fams.map(function(t){return k(t,e)});return Object.assign({},e,{fams:t})}(t))}j.contextTypes={intl:c.d};var U=a(477),R=a(470);function S(e){return i.createElement(U.a,{negative:!0,className:"error"},i.createElement(U.a.Header,null,"Failed to load file"),i.createElement("p",null,e.message))}function _(e,t,a){return{id:t||e.indis[0].id,generation:a||0}}var L=function(e){function t(){var e,a;Object(u.a)(this,t);for(var n=arguments.length,r=new Array(n),o=0;o1&&void 0!==arguments[1]?arguments[1]:{},n=sessionStorage.getItem(e);if(n){var r=JSON.parse(n);this.setState(Object.assign({},this.state,{data:r,selection:_(r,a.indi,a.generation),loadedUrl:e,loading:!1,error:void 0,hash:void 0}))}else{this.setState(Object.assign({},this.state,{loading:!0,loadedUrl:e,data:void 0,error:void 0}));var o=a.handleCors?"https://cors-anywhere.herokuapp.com/"+e:e;window.fetch(o).then(function(e){return 200!==e.status?Promise.reject(new Error(e.statusText)):e.text()}).then(function(n){return t.setGedcom({gedcom:n,url:e,indi:a.indi,generation:a.generation})}).catch(function(e){return t.setState(Object.assign({},t.state,{error:e.message,loading:!1}))})}}},{key:"setGedcom",value:function(e){var t=y()(e.gedcom);try{var a=C(e.gedcom),n=JSON.stringify(a);sessionStorage.setItem(e.url||t,n),this.setState(Object.assign({},this.state,{data:a,selection:_(a,e.indi,e.generation),hash:t,loading:!1,loadedUrl:e.url,error:void 0}))}catch(r){this.setState(Object.assign({},this.state,{data:void 0,selection:void 0,hash:t,loading:!1,error:"Failed to read GEDCOM file",loadedUrl:e.url}))}}},{key:"componentDidMount",value:function(){this.componentDidUpdate()}},{key:"componentDidUpdate",value:function(){var e=this.props.location.state&&this.props.location.state.data,t=g.parse(this.props.location.search),a=function(e){var a=t[e];return"string"===typeof a?a:void 0},n=a("url"),r=a("indi"),o=Number(a("gen")),i=isNaN(o)?void 0:o,l=a("file"),s="false"!==a("handleCors");if(l&&l!==this.state.hash)if(e)this.setGedcom({gedcom:e,indi:r,generation:i});else{var c=sessionStorage.getItem(l);if(c){var d=JSON.parse(c);this.setState(Object.assign({},this.state,{data:d,hash:l,selection:_(d,r,i),error:void 0,loading:!1,loadedUrl:void 0}))}else this.props.history.replace({pathname:"/"})}else if(!this.state.loading&&n&&this.state.loadedUrl!==n)this.loadFromUrl(n,{indi:r,generation:i,handleCors:n.startsWith("http")&&s});else if(n||e||l===this.state.hash){if(this.state.data&&this.state.selection){var u=_(this.state.data,r,i);this.state.selection.id===u.id&&this.state.selection.generation===u.generation||this.setState(Object.assign({},this.state,{selection:u}))}}else this.props.history.replace({pathname:"/"})}},{key:"render",value:function(){var e=this;return this.state.data&&this.state.selection?i.createElement(j,{data:this.state.data,onSelection:this.onSelection,selection:this.state.selection,ref:function(t){return e.chartRef=t}}):this.state.error?i.createElement(S,{message:this.state.error}):i.createElement(R.a,{active:!0,size:"large"})}},{key:"print",value:function(){this.chartRef&&this.chartRef.print()}}]),t}(i.Component),M=a(480),D=a(484),x=a(483),T=a(476),G=a(471);function I(e){return i.createElement(G.a,{to:{pathname:"/view",search:g.stringify({url:e.url})}},e.text)}var N=a(475),F=a(481),P=a(220),W=a(474),H=a(473),A=a(479),J=a(478),K=function(e){function t(){var e,a;Object(u.a)(this,t);for(var n=arguments.length,r=new Array(n),o=0;o void;\n}\n\n/** Component showing the genealogy chart and handling transition animations. */\nexport class Chart extends React.PureComponent {\n private chart?: ChartHandle;\n\n /**\n * Renders the chart or performs a transition animation to a new state.\n * If indiInfo is not given, it means that it is the initial render and no\n * animation is performed.\n */\n private renderChart(args: {initialRender: boolean} = {initialRender: false}) {\n if (args.initialRender) {\n (d3.select('#chart').node() as HTMLElement).innerHTML = '';\n this.chart = createChart({\n json: this.props.data,\n chartType: HourglassChart,\n renderer: DetailedRenderer,\n svgSelector: '#chart',\n indiCallback: (info) => this.props.onSelection(info),\n animate: true,\n updateSvgSize: false,\n locale: this.context.intl.locale,\n });\n }\n const chartInfo = this.chart!.render({\n startIndi: this.props.selection.id,\n baseGeneration: this.props.selection.generation,\n });\n const svg = d3.select('#chart');\n const parent = (svg.node() as HTMLElement).parentElement as Element;\n\n d3.select(parent)\n .on('scroll', scrolled)\n .call(\n d3\n .zoom()\n .scaleExtent([1, 1])\n .translateExtent([[0, 0], chartInfo.size])\n .on('zoom', zoomed),\n );\n\n const scrollTopTween = (scrollTop: number) => {\n return () => {\n const i = d3.interpolateNumber(parent.scrollTop, scrollTop);\n return (t: number) => {\n parent.scrollTop = i(t);\n };\n };\n };\n const scrollLeftTween = (scrollLeft: number) => {\n return () => {\n const i = d3.interpolateNumber(parent.scrollLeft, scrollLeft);\n return (t: number) => {\n parent.scrollLeft = i(t);\n };\n };\n };\n\n const dx = parent.clientWidth / 2 - chartInfo.origin[0];\n const dy = parent.clientHeight / 2 - chartInfo.origin[1];\n const offsetX = d3.max([0, (parent.clientWidth - chartInfo.size[0]) / 2]);\n const offsetY = d3.max([0, (parent.clientHeight - chartInfo.size[1]) / 2]);\n const svgTransition = svg\n .transition()\n .delay(200)\n .duration(500);\n const transition = args.initialRender ? svg : svgTransition;\n transition\n .attr('transform', `translate(${offsetX}, ${offsetY})`)\n .attr('width', chartInfo.size[0])\n .attr('height', chartInfo.size[1]);\n if (args.initialRender) {\n parent.scrollLeft = -dx;\n parent.scrollTop = -dy;\n } else {\n svgTransition\n .tween('scrollLeft', scrollLeftTween(-dx))\n .tween('scrollTop', scrollTopTween(-dy));\n }\n }\n\n componentDidMount() {\n this.renderChart({initialRender: true});\n }\n\n componentDidUpdate(prevProps: ChartProps) {\n this.renderChart({initialRender: this.props.data !== prevProps.data});\n }\n\n /** Make intl appear in this.context. */\n static contextTypes = {\n intl: intlShape,\n };\n\n render() {\n return (\n
\n \n
\n );\n }\n\n /** Shows the print dialog to print the currently displayed chart. */\n print() {\n const printWindow = document.createElement('iframe');\n printWindow.style.position = 'absolute';\n printWindow.style.top = '-1000px';\n printWindow.style.left = '-1000px';\n printWindow.onload = () => {\n const svg = document.getElementById('chart')!.cloneNode(true) as Element;\n svg.removeAttribute('transform');\n const contents = svg.outerHTML;\n printWindow.contentDocument!.open();\n printWindow.contentDocument!.write(contents);\n printWindow.contentDocument!.close();\n // Doesn't work on Firefox without the setTimeout.\n setTimeout(() => {\n printWindow.contentWindow!.focus();\n printWindow.contentWindow!.print();\n printWindow.parentNode!.removeChild(printWindow);\n }, 500);\n };\n document.body.appendChild(printWindow);\n }\n}\n","import {gedcomToJson, JsonFam, JsonGedcomData, JsonIndi} from 'topola';\n\nfunction strcmp(a: string, b: string) {\n if (a < b) {\n return -1;\n }\n if (a > b) {\n return 1;\n }\n return 0;\n}\n\n/** Birth date comparator for individuals. */\nfunction birthDatesComparator(gedcom: JsonGedcomData) {\n const idToIndiMap = new Map();\n gedcom.indis.forEach((indi) => {\n idToIndiMap[indi.id] = indi;\n });\n\n return (indiId1: string, indiId2: string) => {\n const idComparison = strcmp(indiId1, indiId2);\n const indi1: JsonIndi = idToIndiMap[indiId1];\n const indi2: JsonIndi = idToIndiMap[indiId2];\n const birth1 = indi1 && indi1.birth;\n const birth2 = indi2 && indi2.birth;\n const date1 =\n birth1 && (birth1.date || (birth1.dateRange && birth1.dateRange.from));\n const date2 =\n birth2 && (birth2.date || (birth2.dateRange && birth2.dateRange.from));\n if (!date1 || !date1.year || !date2 || !date2.year) {\n return idComparison;\n }\n if (date1.year !== date2.year) {\n return date1.year - date2.year;\n }\n if (!date1.month || !date2.month) {\n return idComparison;\n }\n if (date1.month !== date2.month) {\n return date1.month - date2.month;\n }\n if (date1.day && date2.day && date1.day !== date2.day) {\n return date1.month - date2.month;\n }\n return idComparison;\n };\n}\n\n/**\n * Sorts children by birth date in the given family.\n * Does not modify the input objects.\n */\nfunction sortFamilyChildren(fam: JsonFam, gedcom: JsonGedcomData): JsonFam {\n if (!fam.children) {\n return fam;\n }\n const newChildren = fam.children.sort(birthDatesComparator(gedcom));\n return Object.assign({}, fam, {children: newChildren});\n}\n\n/**\n * Sorts children by birth date.\n * Does not modify the input object.\n */\nfunction sortChildren(gedcom: JsonGedcomData): JsonGedcomData {\n const newFams = gedcom.fams.map((fam) => sortFamilyChildren(fam, gedcom));\n return Object.assign({}, gedcom, {fams: newFams});\n}\n\n/**\n * Removes images that are not HTTP links.\n * Does not modify the input object.\n */\nfunction filterImage(indi: JsonIndi): JsonIndi {\n if (!indi.imageUrl || indi.imageUrl.startsWith('http')) {\n return indi;\n }\n const newIndi = Object.assign({}, indi);\n delete newIndi.imageUrl;\n return newIndi;\n}\n\n/**\n * Removes images that are not HTTP links.\n * Does not modify the input object.\n */\nfunction filterImages(gedcom: JsonGedcomData): JsonGedcomData {\n const newIndis = gedcom.indis.map(filterImage);\n return Object.assign({}, gedcom, {indis: newIndis});\n}\n\n/**\n * Converts GEDCOM file into JSON data performing additional transformations:\n * - sort children by birth date\n * - remove images that are not HTTP links.\n */\nexport function convertGedcom(gedcom: string): JsonGedcomData {\n const json = gedcomToJson(gedcom);\n if (\n !json ||\n !json.indis ||\n !json.indis.length ||\n !json.fams ||\n !json.fams.length\n ) {\n throw new Error('Failed to read GEDCOM file');\n }\n return filterImages(sortChildren(json));\n}\n","import * as queryString from 'query-string';\nimport * as React from 'react';\nimport md5 from 'md5';\nimport {Chart} from './chart';\nimport {convertGedcom} from './gedcom_util';\nimport {IndiInfo, JsonGedcomData} from 'topola';\nimport {Loader, Message} from 'semantic-ui-react';\nimport {RouteComponentProps} from 'react-router-dom';\n\n/** Shows an error message. */\nexport function ErrorMessage(props: {message: string}) {\n return (\n \n Failed to load file\n

{props.message}

\n
\n );\n}\n\n/**\n * Returns a valid IndiInfo object, either with the given indi and generation\n * or with an individual taken from the data and generation 0.\n */\nfunction getSelection(\n data: JsonGedcomData,\n indi?: string,\n generation?: number,\n): IndiInfo {\n return {\n id: indi || data.indis[0].id,\n generation: generation || 0,\n };\n}\n\ninterface State {\n /** Loaded data. */\n data?: JsonGedcomData;\n /** Selected individual. */\n selection?: IndiInfo;\n /** Hash of the GEDCOM contents. */\n hash?: string;\n /** Error to display. */\n error?: string;\n /** True if currently loading. */\n loading: boolean;\n /** URL of the data that is loaded or is being loaded. */\n loadedUrl?: string;\n}\n\n/** The main area of the application dedicated for rendering the family chart. */\nexport class ChartView extends React.Component {\n state: State = {loading: false};\n chartRef: Chart | null = null;\n\n /**\n * Called when the user clicks an individual box in the chart.\n * Updates the browser URL.\n */\n onSelection = (selection: IndiInfo) => {\n const location = this.props.location;\n const search = queryString.parse(location.search);\n search.indi = selection.id;\n search.gen = String(selection.generation);\n location.search = queryString.stringify(search);\n this.props.history.push(location);\n };\n\n /** Loads a GEDCOM file from the given URL. */\n loadFromUrl(\n url: string,\n options: {\n handleCors?: boolean;\n indi?: string;\n generation?: number;\n } = {},\n ) {\n const cachedData = sessionStorage.getItem(url);\n if (cachedData) {\n const data = JSON.parse(cachedData);\n this.setState(\n Object.assign({}, this.state, {\n data,\n selection: getSelection(data, options.indi, options.generation),\n loadedUrl: url,\n loading: false,\n error: undefined,\n hash: undefined,\n }),\n );\n return;\n }\n\n this.setState(\n Object.assign({}, this.state, {\n loading: true,\n loadedUrl: url,\n data: undefined,\n error: undefined,\n }),\n );\n\n const urlToFetch = options.handleCors\n ? 'https://cors-anywhere.herokuapp.com/' + url\n : url;\n\n window\n .fetch(urlToFetch)\n .then((response) => {\n if (response.status !== 200) {\n return Promise.reject(new Error(response.statusText));\n }\n return response.text();\n })\n .then((data) =>\n this.setGedcom({\n gedcom: data,\n url,\n indi: options.indi,\n generation: options.generation,\n }),\n )\n .catch((e: Error) =>\n this.setState(\n Object.assign({}, this.state, {error: e.message, loading: false}),\n ),\n );\n }\n\n /**\n * Converts GEDCOM contents and sets the data in the state.\n * In case of an error reading the file, sets an error.\n */\n setGedcom(input: {\n gedcom: string;\n url?: string;\n indi?: string;\n generation?: number;\n }) {\n const hash = md5(input.gedcom);\n try {\n const data = convertGedcom(input.gedcom);\n const serializedData = JSON.stringify(data);\n sessionStorage.setItem(input.url || hash, serializedData);\n this.setState(\n Object.assign({}, this.state, {\n data,\n selection: getSelection(data, input.indi, input.generation),\n hash,\n loading: false,\n loadedUrl: input.url,\n error: undefined,\n }),\n );\n } catch (e) {\n this.setState(\n Object.assign({}, this.state, {\n data: undefined,\n selection: undefined,\n hash,\n loading: false,\n error: 'Failed to read GEDCOM file',\n loadedUrl: input.url,\n }),\n );\n }\n }\n\n componentDidMount() {\n this.componentDidUpdate();\n }\n\n componentDidUpdate() {\n const gedcom = this.props.location.state && this.props.location.state.data;\n const search = queryString.parse(this.props.location.search);\n const getParam = (name: string) => {\n const value = search[name];\n return typeof value === 'string' ? value : undefined;\n };\n const url = getParam('url');\n const indi = getParam('indi');\n const parsedGen = Number(getParam('gen'));\n const generation = !isNaN(parsedGen) ? parsedGen : undefined;\n const hash = getParam('file');\n const handleCors = getParam('handleCors') !== 'false';\n\n if (hash && hash !== this.state.hash) {\n // New \"load from file\" data.\n if (gedcom) {\n this.setGedcom({gedcom, indi, generation});\n } else {\n // Data is not present. Try loading from cache.\n const cachedData = sessionStorage.getItem(hash);\n if (cachedData) {\n const data = JSON.parse(cachedData);\n this.setState(\n Object.assign({}, this.state, {\n data,\n hash,\n selection: getSelection(data, indi, generation),\n error: undefined,\n loading: false,\n loadedUrl: undefined,\n }),\n );\n } else {\n // No data available. Redirect to main page.\n this.props.history.replace({pathname: '/'});\n }\n }\n } else if (!this.state.loading && url && this.state.loadedUrl !== url) {\n // New URL to load data from.\n this.loadFromUrl(url, {\n indi,\n generation,\n handleCors: url.startsWith('http') && handleCors,\n });\n } else if (!url && !gedcom && hash !== this.state.hash) {\n this.props.history.replace({pathname: '/'});\n } else if (this.state.data && this.state.selection) {\n // Update selection if it has changed in the URL.\n const selection = getSelection(this.state.data, indi, generation);\n if (\n this.state.selection.id !== selection.id ||\n this.state.selection.generation !== selection.generation\n ) {\n this.setState(\n Object.assign({}, this.state, {\n selection,\n }),\n );\n }\n }\n }\n\n render() {\n if (this.state.data && this.state.selection) {\n return (\n (this.chartRef = ref)}\n />\n );\n }\n if (this.state.error) {\n return ;\n }\n return ;\n }\n\n /** Shows the print dialog to print the currently displayed chart. */\n print() {\n if (this.chartRef) {\n this.chartRef.print();\n }\n }\n}\n","import * as queryString from 'query-string';\nimport * as React from 'react';\nimport {Card} from 'semantic-ui-react';\nimport {FormattedMessage} from 'react-intl';\nimport {Link} from 'react-router-dom';\n\n/** Link that loads a GEDCOM file from URL. */\nfunction GedcomLink(props: {url: string; text: string}) {\n return (\n \n {props.text}\n \n );\n}\n\n/** The intro page. */\nexport function Intro() {\n return (\n \n \n \n \n \n \n \n

\n \n

\n

\n \n

\n

\n \n

\n \n

\n \n \n \n :\n cors-anywhere\n ),\n }}\n />\n

\n
\n
\n );\n}\n","import * as queryString from 'query-string';\nimport * as React from 'react';\nimport md5 from 'md5';\nimport {FormattedMessage} from 'react-intl';\nimport {Link} from 'react-router-dom';\nimport {RouteComponentProps} from 'react-router-dom';\nimport {\n Header,\n Button,\n Icon,\n Menu,\n Modal,\n Input,\n Form,\n} from 'semantic-ui-react';\n\n/** Menus and dialogs state. */\ninterface State {\n loadUrlDialogOpen: boolean;\n url?: string;\n}\n\ninterface Props {\n onPrint: () => void;\n}\n\nexport class TopBar extends React.Component<\n RouteComponentProps & Props,\n State\n> {\n state: State = {loadUrlDialogOpen: false};\n inputRef?: Input;\n\n /** Handles the \"Upload file\" button. */\n handleUpload(event: React.SyntheticEvent) {\n const files = (event.target as HTMLInputElement).files;\n if (!files || !files.length) {\n return;\n }\n const reader = new FileReader();\n reader.onload = (evt: ProgressEvent) => {\n const data = (evt.target as FileReader).result;\n const hash = md5(data as string);\n this.props.history.push({\n pathname: '/view',\n search: queryString.stringify({file: hash}),\n state: {data},\n });\n };\n reader.readAsText(files[0]);\n }\n\n /** Opens the \"Load from URL\" dialog. */\n handleLoadFromUrl() {\n this.setState(\n Object.assign({}, this.state, {loadUrlDialogOpen: true}),\n () => this.inputRef!.focus(),\n );\n }\n\n /** Cancels the \"Load from URL\" dialog. */\n handleClose() {\n this.setState(Object.assign({}, this.state, {loadUrlDialogOpen: false}));\n }\n\n /** Upload button clicked in the \"Load from URL\" dialog. */\n handleLoad() {\n this.setState(\n Object.assign({}, this.state, {\n loadUrlDialogOpen: false,\n }),\n );\n if (this.state.url) {\n this.props.history.push({\n pathname: '/view',\n search: queryString.stringify({url: this.state.url}),\n });\n }\n }\n\n /** Called when the URL input is typed into. */\n handleUrlChange(event: React.SyntheticEvent) {\n this.setState(\n Object.assign({}, this.state, {\n url: (event.target as HTMLInputElement).value,\n }),\n );\n }\n\n render() {\n const loadFromUrlModal = (\n this.handleClose()}\n centered={false}\n >\n
\n \n txt}\n />\n
\n \n
this.handleLoad()}>\n this.handleUrlChange(e)}\n ref={(ref) => (this.inputRef = ref!)}\n />\n

\n \n cors-anywhere.herokuapp.com\n \n ),\n }}\n />\n

\n \n
\n \n \n \n \n \n );\n\n return (\n \n \n \n Topola Genealogy\n \n \n this.handleLoadFromUrl()}>\n \n \n \n this.handleUpload(e)}\n />\n \n this.props.onPrint()}>\n \n \n \n \n \n \n {loadFromUrlModal}\n \n );\n }\n}\n","import * as locale_en from 'react-intl/locale-data/en';\nimport * as locale_pl from 'react-intl/locale-data/pl';\nimport * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport messages_pl from './translations/pl.json';\nimport {addLocaleData} from 'react-intl';\nimport {detect} from 'detect-browser';\nimport {ChartView} from './chart_view';\nimport {\n HashRouter as Router,\n Route,\n RouteComponentProps,\n Switch,\n} from 'react-router-dom';\nimport {IntlProvider} from 'react-intl';\nimport {Intro} from './intro';\nimport {TopBar} from './top_bar';\nimport './index.css';\nimport 'semantic-ui-css/semantic.min.css';\n\naddLocaleData([...locale_en, ...locale_pl]);\n\nconst messages = {\n pl: messages_pl,\n};\nconst language = navigator.language && navigator.language.split(/[-_]/)[0];\n\nconst browser = detect();\n\nlet chartViewRef: ChartView | null = null;\n\nif (browser && browser.name === 'ie') {\n ReactDOM.render(\n

\n Topola Genealogy Viewer does not support Internet Explorer. Please try a\n different browser.\n

,\n document.querySelector('#root'),\n );\n} else {\n ReactDOM.render(\n \n \n
\n (\n chartViewRef && chartViewRef.print()}\n />\n )}\n />\n \n \n (\n (chartViewRef = ref)} />\n )}\n />\n \n
\n
\n
,\n document.querySelector('#root'),\n );\n}\n"],"sourceRoot":""} \ No newline at end of file