├── .eslintrc ├── .github ├── dependabot.yml └── workflows │ ├── run-end-to-end-tests.yml │ └── run-tests.yml ├── .gitignore ├── .npmignore ├── .tool-versions ├── COPYING ├── Dockerfile ├── LICENSE ├── LICENSE-3RD-PARTY ├── Makefile ├── README.md ├── favicon.ico ├── fly.publish.toml ├── fly.stage.toml ├── images ├── imls_logo_2c.svg ├── maptiles │ ├── 0 │ │ └── fwkw_-180.0x-89.98x180.0x90.0.jpg │ ├── 1 │ │ ├── 22ob_0.0x30.02x45.0x90.0.jpg │ │ ├── 268i_-180.0x0.02x-135.0x30.02.jpg │ │ ├── 4okn_-45.0x-89.98x0.0x-29.98.jpg │ │ ├── 4tfh_-135.0x-89.98x-90.0x-29.98.jpg │ │ ├── 4xh7_45.0x-89.98x90.0x-29.98.jpg │ │ ├── 5ciq_-180.0x-89.98x-135.0x-29.98.jpg │ │ ├── 5edg_135.0x30.02x180.0x90.0.jpg │ │ ├── 8pkb_-180.0x30.02x-135.0x90.0.jpg │ │ ├── 9ah4_-90.0x0.02x-45.0x30.02.jpg │ │ ├── 9cab_-90.0x-89.98x-45.0x-29.98.jpg │ │ ├── ba18_-135.0x0.02x-90.0x30.02.jpg │ │ ├── ehg6_135.0x0.02x180.0x30.02.jpg │ │ ├── g0e5_-90.0x30.02x-45.0x90.0.jpg │ │ ├── h6w3_45.0x30.02x90.0x90.0.jpg │ │ ├── i5tz_-135.0x-29.98x-90.0x0.02.jpg │ │ ├── io3k_90.0x-89.98x135.0x-29.98.jpg │ │ ├── ja8o_-90.0x-29.98x-45.0x0.02.jpg │ │ ├── l39s_45.0x-29.98x90.0x0.02.jpg │ │ ├── mvia_135.0x-89.98x180.0x-29.98.jpg │ │ ├── o7ho_45.0x0.02x90.0x30.02.jpg │ │ ├── ot5u_0.0x-29.98x45.0x0.02.jpg │ │ ├── qaj0_90.0x-29.98x135.0x0.02.jpg │ │ ├── r30n_-180.0x-29.98x-135.0x0.02.jpg │ │ ├── rn75_0.0x0.02x45.0x30.02.jpg │ │ ├── tipg_-45.0x0.02x0.0x30.02.jpg │ │ ├── vugo_-45.0x-29.98x0.0x0.02.jpg │ │ ├── vuzm_90.0x30.02x135.0x90.0.jpg │ │ ├── xm39_0.0x-89.98x45.0x-29.98.jpg │ │ ├── yutf_-45.0x30.02x0.0x90.0.jpg │ │ ├── zch2_-135.0x30.02x-90.0x90.0.jpg │ │ ├── zcx5_90.0x0.02x135.0x30.02.jpg │ │ └── zypk_135.0x-29.98x180.0x0.02.jpg │ ├── 2 │ │ ├── 0klu_-22.5x0.02x0.0x14.5.jpg │ │ ├── 128h_67.5x0.02x90.0x14.5.jpg │ │ ├── 28vy_-22.5x-48.57x0.0x-29.98.jpg │ │ ├── 29oz_22.5x30.02x45.0x48.6.jpg │ │ ├── 2ajp_67.5x-48.57x90.0x-29.98.jpg │ │ ├── 2lj0_112.5x0.02x135.0x14.5.jpg │ │ ├── 2qe0_45.0x0.02x67.5x14.5.jpg │ │ ├── 2sge_-45.0x0.02x-22.5x14.5.jpg │ │ ├── 2xji_45.0x-29.98x67.5x-14.47.jpg │ │ ├── 2yq1_-90.0x-29.98x-67.5x-14.47.jpg │ │ ├── 3kte_-157.5x-48.57x-135.0x-29.98.jpg │ │ ├── 3lt4_-180.0x-89.98x-157.5x-48.57.jpg │ │ ├── 3na7_0.0x-14.47x22.5x0.02.jpg │ │ ├── 44s5_67.5x14.5x90.0x30.02.jpg │ │ ├── 4c4v_-135.0x-29.98x-112.5x-14.47.jpg │ │ ├── 4fsr_90.0x0.02x112.5x14.5.jpg │ │ ├── 4rfi_-157.5x14.5x-135.0x30.02.jpg │ │ ├── 4vtu_-180.0x-48.57x-157.5x-29.98.jpg │ │ ├── 51rv_112.5x-14.47x135.0x0.02.jpg │ │ ├── 5791_-157.5x-89.98x-135.0x-48.57.jpg │ │ ├── 5ebh_-112.5x0.02x-90.0x14.5.jpg │ │ ├── 5skb_-67.5x14.5x-45.0x30.02.jpg │ │ ├── 5zo7_90.0x-48.57x112.5x-29.98.jpg │ │ ├── 63jf_157.5x48.6x180.0x90.0.jpg │ │ ├── 66d5_90.0x30.02x112.5x48.6.jpg │ │ ├── 6ghz_112.5x-29.98x135.0x-14.47.jpg │ │ ├── 740j_-90.0x14.5x-67.5x30.02.jpg │ │ ├── 7vqd_0.0x-89.98x22.5x-48.57.jpg │ │ ├── 828n_-112.5x-89.98x-90.0x-48.57.jpg │ │ ├── 855u_67.5x-29.98x90.0x-14.47.jpg │ │ ├── 85qm_-22.5x-14.47x0.0x0.02.jpg │ │ ├── 8hrz_112.5x-89.98x135.0x-48.57.jpg │ │ ├── 8hyo_-90.0x-14.47x-67.5x0.02.jpg │ │ ├── 8qid_22.5x14.5x45.0x30.02.jpg │ │ ├── 8uc7_90.0x14.5x112.5x30.02.jpg │ │ ├── 8vh3_-157.5x48.6x-135.0x90.0.jpg │ │ ├── 9e6c_-112.5x-14.47x-90.0x0.02.jpg │ │ ├── abtb_22.5x-14.47x45.0x0.02.jpg │ │ ├── axzp_0.0x48.6x22.5x90.0.jpg │ │ ├── bmne_90.0x-29.98x112.5x-14.47.jpg │ │ ├── bxvt_22.5x-29.98x45.0x-14.47.jpg │ │ ├── c60z_0.0x0.02x22.5x14.5.jpg │ │ ├── cmj5_22.5x-89.98x45.0x-48.57.jpg │ │ ├── cqu4_-45.0x14.5x-22.5x30.02.jpg │ │ ├── d6gk_-135.0x14.5x-112.5x30.02.jpg │ │ ├── dmmf_135.0x14.5x157.5x30.02.jpg │ │ ├── dpyx_135.0x30.02x157.5x48.6.jpg │ │ ├── du1y_45.0x-14.47x67.5x0.02.jpg │ │ ├── e724_-67.5x-89.98x-45.0x-48.57.jpg │ │ ├── ep6a_-22.5x-29.98x0.0x-14.47.jpg │ │ ├── eww2_-22.5x14.5x0.0x30.02.jpg │ │ ├── exbe_-90.0x48.6x-67.5x90.0.jpg │ │ ├── f5ri_157.5x-14.47x180.0x0.02.jpg │ │ ├── fayw_-112.5x14.5x-90.0x30.02.jpg │ │ ├── fjxh_0.0x30.02x22.5x48.6.jpg │ │ ├── flnz_-180.0x30.02x-157.5x48.6.jpg │ │ ├── fn34_-45.0x30.02x-22.5x48.6.jpg │ │ ├── frl7_22.5x0.02x45.0x14.5.jpg │ │ ├── garw_-157.5x30.02x-135.0x48.6.jpg │ │ ├── gphm_-180.0x14.5x-157.5x30.02.jpg │ │ ├── gsmw_112.5x48.6x135.0x90.0.jpg │ │ ├── gzct_135.0x-89.98x157.5x-48.57.jpg │ │ ├── h482_-90.0x30.02x-67.5x48.6.jpg │ │ ├── hl4b_22.5x48.6x45.0x90.0.jpg │ │ ├── i68c_-157.5x-14.47x-135.0x0.02.jpg │ │ ├── i7v3_-180.0x48.6x-157.5x90.0.jpg │ │ ├── ibx9_-22.5x-89.98x0.0x-48.57.jpg │ │ ├── id1h_157.5x14.5x180.0x30.02.jpg │ │ ├── imjd_-135.0x-14.47x-112.5x0.02.jpg │ │ ├── iwom_45.0x-89.98x67.5x-48.57.jpg │ │ ├── j0a2_-22.5x30.02x0.0x48.6.jpg │ │ ├── j2e0_67.5x-89.98x90.0x-48.57.jpg │ │ ├── jzte_-135.0x48.6x-112.5x90.0.jpg │ │ ├── k3wp_45.0x48.6x67.5x90.0.jpg │ │ ├── koj5_157.5x0.02x180.0x14.5.jpg │ │ ├── lh81_45.0x30.02x67.5x48.6.jpg │ │ ├── lldm_-180.0x-29.98x-157.5x-14.47.jpg │ │ ├── lmp1_-157.5x-29.98x-135.0x-14.47.jpg │ │ ├── lwz0_0.0x14.5x22.5x30.02.jpg │ │ ├── m51p_-90.0x-48.57x-67.5x-29.98.jpg │ │ ├── m5yg_157.5x-89.98x180.0x-48.57.jpg │ │ ├── meta_-112.5x30.02x-90.0x48.6.jpg │ │ ├── mf57_-45.0x-89.98x-22.5x-48.57.jpg │ │ ├── mmox_-180.0x0.02x-157.5x14.5.jpg │ │ ├── mpif_135.0x0.02x157.5x14.5.jpg │ │ ├── ni1n_45.0x-48.57x67.5x-29.98.jpg │ │ ├── njl3_135.0x-14.47x157.5x0.02.jpg │ │ ├── ojdg_135.0x48.6x157.5x90.0.jpg │ │ ├── omvv_-67.5x-29.98x-45.0x-14.47.jpg │ │ ├── p8v2_-112.5x-48.57x-90.0x-29.98.jpg │ │ ├── pbbt_-22.5x48.6x0.0x90.0.jpg │ │ ├── pfe6_135.0x-48.57x157.5x-29.98.jpg │ │ ├── pll0_-90.0x-89.98x-67.5x-48.57.jpg │ │ ├── qqvd_0.0x-29.98x22.5x-14.47.jpg │ │ ├── qtrt_67.5x-14.47x90.0x0.02.jpg │ │ ├── r0ty_-67.5x48.6x-45.0x90.0.jpg │ │ ├── r4hx_157.5x-29.98x180.0x-14.47.jpg │ │ ├── rmd9_-112.5x-29.98x-90.0x-14.47.jpg │ │ ├── sjfs_90.0x-14.47x112.5x0.02.jpg │ │ ├── smvd_112.5x-48.57x135.0x-29.98.jpg │ │ ├── soh6_45.0x14.5x67.5x30.02.jpg │ │ ├── tann_-67.5x30.02x-45.0x48.6.jpg │ │ ├── tmtq_0.0x-48.57x22.5x-29.98.jpg │ │ ├── ttq0_-90.0x0.02x-67.5x14.5.jpg │ │ ├── u279_-135.0x0.02x-112.5x14.5.jpg │ │ ├── ua6v_-135.0x-48.57x-112.5x-29.98.jpg │ │ ├── ubes_157.5x30.02x180.0x48.6.jpg │ │ ├── uhy6_-157.5x0.02x-135.0x14.5.jpg │ │ ├── uqi8_-45.0x-14.47x-22.5x0.02.jpg │ │ ├── vrgt_-135.0x-89.98x-112.5x-48.57.jpg │ │ ├── vvs1_-180.0x-14.47x-157.5x0.02.jpg │ │ ├── w1na_22.5x-48.57x45.0x-29.98.jpg │ │ ├── w4pt_-45.0x-29.98x-22.5x-14.47.jpg │ │ ├── wccd_90.0x-89.98x112.5x-48.57.jpg │ │ ├── wdi8_135.0x-29.98x157.5x-14.47.jpg │ │ ├── weh4_112.5x30.02x135.0x48.6.jpg │ │ ├── wgqx_-135.0x30.02x-112.5x48.6.jpg │ │ ├── wo2z_67.5x48.6x90.0x90.0.jpg │ │ ├── xg31_112.5x14.5x135.0x30.02.jpg │ │ ├── xxah_-45.0x-48.57x-22.5x-29.98.jpg │ │ ├── yj6o_-45.0x48.6x-22.5x90.0.jpg │ │ ├── ym1c_67.5x30.02x90.0x48.6.jpg │ │ ├── yott_-67.5x-14.47x-45.0x0.02.jpg │ │ ├── ytj7_-112.5x48.6x-90.0x90.0.jpg │ │ ├── yx2d_90.0x48.6x112.5x90.0.jpg │ │ ├── z8rk_-67.5x-48.57x-45.0x-29.98.jpg │ │ ├── zbaf_157.5x-48.57x180.0x-29.98.jpg │ │ └── zzla_-67.5x0.02x-45.0x14.5.jpg │ └── manifest.json ├── neh-logo.svg └── periodo-logo.svg ├── index.html ├── modules ├── org-async-actions │ ├── package.json │ └── src │ │ ├── index.js │ │ ├── middleware.js │ │ ├── symbols.js │ │ ├── tests │ │ └── middleware.js │ │ ├── types.js │ │ └── utils.js ├── periodo-app │ ├── README.md │ ├── package.json │ └── src │ │ ├── auth │ │ ├── actions.js │ │ ├── components │ │ │ ├── ORCID.js │ │ │ └── Settings.js │ │ └── reducer.js │ │ ├── backends │ │ ├── actions.js │ │ ├── components │ │ │ ├── AddBackend.js │ │ │ ├── AuthorityAddOrEdit.js │ │ │ ├── AuthorityView.js │ │ │ ├── BackendHome.js │ │ │ ├── BackendPatch.js │ │ │ ├── BackendSelect.js │ │ │ ├── BackendSelector.js │ │ │ ├── BackendSubmitPatch.js │ │ │ ├── BrowseAuthorities.js │ │ │ ├── DownloadBackend.js │ │ │ ├── EditBackend.js │ │ │ ├── History.js │ │ │ ├── PeriodAddOrEdit.js │ │ │ ├── PeriodView.js │ │ │ ├── PersistenceWarning.js │ │ │ ├── ReviewSubmittedPatches.js │ │ │ └── SyncBackend.js │ │ ├── dataset_proxy │ │ │ ├── index.js │ │ │ ├── index_items.js │ │ │ ├── sort.js │ │ │ └── worker │ │ │ │ ├── index.js │ │ │ │ └── sort.js │ │ ├── index.js │ │ ├── migrate_legacy_storage.js │ │ ├── parse_periodo_upload.js │ │ ├── reducer.js │ │ ├── test │ │ │ ├── actions.js │ │ │ ├── backup.json │ │ │ ├── proxy.js │ │ │ └── types.js │ │ └── types.js │ │ ├── db │ │ ├── index.js │ │ ├── version-01.js │ │ ├── version-02.js │ │ ├── version-03.js │ │ ├── version-04.js │ │ └── version-05.js │ │ ├── errors.js │ │ ├── forms │ │ ├── AuthorityForm │ │ │ ├── LDSourceForm.js │ │ │ ├── NonLDSourceForm.js │ │ │ └── index.js │ │ ├── BackendForm.js │ │ ├── PeriodForm │ │ │ ├── LabelForm.js │ │ │ ├── LocalizedLabelInput.js │ │ │ ├── RelatedPeriodList.js │ │ │ ├── RelatedPeriodsForm.js │ │ │ ├── SpatialCoverageForm │ │ │ │ └── index.js │ │ │ ├── TemporalCoverageForm.js │ │ │ ├── TerminusInput.js │ │ │ └── index.js │ │ ├── Validated.js │ │ ├── index.js │ │ └── validate.js │ │ ├── globals.js │ │ ├── graphs │ │ ├── actions.js │ │ └── reducer.js │ │ ├── index.js │ │ ├── layouts │ │ ├── LayoutBlock.js │ │ ├── LayoutRenderer.js │ │ ├── ListBlock │ │ │ ├── ColumnSelector.js │ │ │ ├── ListControls.js │ │ │ ├── ListHeader.js │ │ │ ├── ListRow.js │ │ │ └── index.js │ │ ├── authorities │ │ │ ├── AuthorityDetail.js │ │ │ ├── AuthorityList.js │ │ │ ├── AuthorityPeriodDetail.js │ │ │ ├── AuthoritySearch │ │ │ │ └── index.js │ │ │ ├── CoverageMap │ │ │ │ └── index.js │ │ │ ├── Facets │ │ │ │ ├── AspectTable.js │ │ │ │ ├── FacetCalculator.js │ │ │ │ ├── index.js │ │ │ │ └── worker.js │ │ │ ├── PeriodDetail.js │ │ │ ├── PeriodSearch │ │ │ │ └── index.js │ │ │ ├── PlaceFilter.js │ │ │ ├── Statistics │ │ │ │ └── index.js │ │ │ ├── TimeFilter.js │ │ │ ├── Timeline │ │ │ │ ├── Bars.js │ │ │ │ ├── FrequencyPath.js │ │ │ │ ├── Histogram.js │ │ │ │ ├── Plot.js │ │ │ │ └── index.js │ │ │ ├── WindowedPeriodList.js │ │ │ ├── blocks.js │ │ │ └── index.js │ │ ├── parser.js │ │ ├── patch_requests │ │ │ └── index.js │ │ ├── patches │ │ │ └── index.js │ │ ├── process_layout.js │ │ └── test │ │ │ └── parser.js │ │ ├── linked-data │ │ ├── AsyncRequestor.js │ │ ├── actions.js │ │ ├── context.json │ │ ├── ns.js │ │ ├── reducer.js │ │ ├── test │ │ │ ├── source.js │ │ │ └── source_ld_match.js │ │ └── utils │ │ │ ├── data.js │ │ │ ├── format_url.js │ │ │ ├── generate_skolem_id.js │ │ │ ├── index.js │ │ │ ├── is_periodo_server.js │ │ │ ├── make_source_repr.js │ │ │ ├── parse_jsonld.js │ │ │ ├── patch.js │ │ │ ├── skolem_ids.js │ │ │ └── source_ld_match.js │ │ ├── main │ │ ├── Application.js │ │ ├── Test.js │ │ ├── actions.js │ │ ├── components │ │ │ ├── About.js │ │ │ ├── Footer.js │ │ │ ├── Header.js │ │ │ ├── IndexedDBMessage.js │ │ │ └── NotFound.js │ │ ├── example-data.js │ │ ├── index.js │ │ ├── reducer.js │ │ └── resources.js │ │ ├── patches │ │ ├── Compare.js │ │ ├── OpenPatches.js │ │ ├── Review.js │ │ ├── SelectChanges.js │ │ ├── actions.js │ │ ├── patch.js │ │ ├── patch_collection.js │ │ ├── reducer.js │ │ ├── test │ │ │ ├── actions.js │ │ │ ├── fixtures │ │ │ │ ├── authority.json │ │ │ │ ├── dangling-related-period.json │ │ │ │ ├── multi-label-period.json │ │ │ │ ├── source-partof.json │ │ │ │ └── termini.json │ │ │ └── utils.js │ │ └── types.js │ │ ├── resources │ │ ├── index.js │ │ ├── resources.js │ │ └── wrappers.js │ │ ├── store.js │ │ └── store_mock.js ├── periodo-common │ ├── package.json │ └── src │ │ ├── RandomID.js │ │ ├── index.js │ │ └── types.js ├── periodo-date-parser │ ├── .eslintrc │ ├── .gitignore │ ├── COPYING │ ├── LICENSE │ ├── LICENSE-3RD-PARTY │ ├── README.md │ ├── grammar.pegjs │ ├── package.json │ └── test │ │ └── index.js ├── periodo-ui │ ├── package.json │ ├── src │ │ ├── components │ │ │ ├── Alerts.js │ │ │ ├── Authority.js │ │ │ ├── Autosuggest.js │ │ │ ├── BackendContext.js │ │ │ ├── Base.js │ │ │ ├── Breadcrumb.js │ │ │ ├── Buttons.js │ │ │ ├── ClientError.js │ │ │ ├── Dataset.js │ │ │ ├── Debug.js │ │ │ ├── Details.js │ │ │ ├── DropdownMenu.js │ │ │ ├── FeatureLabel.js │ │ │ ├── FormElements.js │ │ │ ├── Icons.js │ │ │ ├── InlineList.js │ │ │ ├── InputBlock.js │ │ │ ├── LabeledMap.js │ │ │ ├── Links.js │ │ │ ├── NavigationMenu.js │ │ │ ├── Notes.js │ │ │ ├── Pager.js │ │ │ ├── Patch.js │ │ │ ├── Period.js │ │ │ ├── PlaceSuggest.js │ │ │ ├── PlacesSelect.js │ │ │ ├── Source.js │ │ │ ├── Status.js │ │ │ ├── Table.js │ │ │ ├── Tabs.js │ │ │ ├── Tags.js │ │ │ ├── TimeSlider.js │ │ │ ├── Typography.js │ │ │ ├── WorldMap.js │ │ │ └── diffable │ │ │ │ ├── Diff.js │ │ │ │ ├── Field.js │ │ │ │ ├── Value.js │ │ │ │ └── types.js │ │ ├── extend.js │ │ ├── index.js │ │ ├── open_color.js │ │ ├── theme.js │ │ └── util.js │ └── test │ │ ├── diff.js │ │ ├── field.js │ │ └── types.js └── periodo-utils │ ├── assets │ └── dbpedia_countries.json │ ├── package.json │ ├── src │ ├── authority.js │ ├── authority_list.js │ ├── contributor.js │ ├── contributor_list.js │ ├── dataset.js │ ├── index.js │ ├── label.js │ ├── period.js │ ├── source.js │ ├── symbols.js │ ├── terminus.js │ ├── terminus_list.js │ └── util.js │ └── test │ ├── fixtures │ ├── authority.json │ ├── multi-label-period.json │ ├── source-partof.json │ └── termini.json │ └── items.js ├── nginx.conf ├── package-lock.json ├── package.json └── ssl-server.py /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "parserOptions": { 4 | "ecmaVersion": 2018 5 | }, 6 | "env": { 7 | "node": true, 8 | "browser": true, 9 | "es6": true 10 | }, 11 | "rules": { 12 | "array-bracket-newline": ["error", "consistent"], 13 | "array-bracket-spacing": ["error", "always", { 14 | "objectsInArrays": false, 15 | "arraysInArrays": false, 16 | }], 17 | "comma-dangle": [2, { 18 | "arrays": "always-multiline", 19 | "objects": "always-multiline" 20 | }], 21 | "comma-style": ["error", "last", { 22 | "exceptions": { 23 | "VariableDeclaration": true 24 | } 25 | }], 26 | "curly": [2, "multi-line"], 27 | "indent": ["error", 2, { 28 | "MemberExpression": "off", 29 | "ignoreComments": true, 30 | "ignoredNodes": ["VariableDeclaration"] 31 | }], 32 | "new-cap": 0, 33 | "no-mixed-requires": 0, 34 | "no-trailing-spaces": 2, 35 | "no-underscore-dangle": 0, 36 | "no-use-before-define": [2, "nofunc"], 37 | "no-var": 2, 38 | "object-curly-newline": ["error", { 39 | "ObjectExpression": { 40 | "multiline": true, 41 | "minProperties": 3, 42 | "consistent": true, 43 | } 44 | }], 45 | "object-curly-spacing": ["error", "always", { 46 | "objectsInObjects": false, 47 | "arraysInObjects": false, 48 | }], 49 | "object-property-newline": 2, 50 | "object-shorthand": 2, 51 | "prefer-arrow-callback": [2, { 52 | "allowNamedFunctions": true 53 | }], 54 | "prefer-const": 2, 55 | "prefer-spread": 2, 56 | "prefer-object-spread": 2, 57 | "quotes": 0 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | - package-ecosystem: "github-actions" 13 | directory: "/" 14 | schedule: 15 | interval: "daily" 16 | -------------------------------------------------------------------------------- /.github/workflows/run-end-to-end-tests.yml: -------------------------------------------------------------------------------- 1 | name: end-to-end tests 2 | on: [push, workflow_dispatch] 3 | jobs: 4 | dispatch-and-wait: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - run: echo "Dispatching end-to-end tests against $GITHUB_REF_NAME" 8 | - name: "Dispatch workflow" 9 | id: dispatch 10 | uses: mathze/workflow-dispatch-action@v1.3.0 11 | with: 12 | owner: periodo 13 | repo: periodo-tests 14 | ref: master 15 | token: ${{ secrets.WORKFLOW_DISPATCH_TOKEN }} 16 | use-marker-step: true 17 | workflow-name: run-tests-on-branch.yml 18 | payload: | 19 | { "client_branch": "${{ github.ref_name }}" } 20 | - run: echo 'Workflow log at https://github.com/periodo/periodo-tests/actions/runs/${{ steps.dispatch.outputs.run-id }}' 21 | - name: "Wait for workflow completion" 22 | id: wait 23 | uses: mathze/workflow-dispatch-action@v1.3.0 24 | with: 25 | owner: periodo 26 | repo: periodo-tests 27 | ref: master 28 | token: ${{ secrets.WORKFLOW_DISPATCH_TOKEN }} 29 | run-id: ${{ steps.dispatch.outputs.run-id }} 30 | wait-interval: 30s 31 | - name: "Check workflow conclusion" 32 | run: | 33 | if [[ "success" != "${{ steps.wait.outputs.run-conclusion }}" ]]; then 34 | echo "End-to-end tests failed (conclusion was <${{ steps.wait.outputs.run-conclusion }}>)" 35 | exit 1 36 | else 37 | echo "End-to-end tests succeeded" 38 | fi 39 | -------------------------------------------------------------------------------- /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | name: client tests 2 | on: [push] 3 | jobs: 4 | run-tests: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v4 8 | - uses: actions/setup-node@v4 9 | with: 10 | node-version: 16 11 | - run: >- 12 | make test 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | node_modules/ 3 | modules/**/node_modules/ 4 | periodo-client.js 5 | dist/* 6 | .DS_Store 7 | mkcert 8 | *.pem 9 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | !images/* 3 | !index.html 4 | !*.js 5 | !*.js.map 6 | !LICENSE-3RD-PARTY 7 | !COPYING 8 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | nodejs 16.15.0 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=linux/amd64 nginx 2 | 3 | ARG CLIENT_VERSION 4 | 5 | COPY nginx.conf /etc/nginx/conf.d/default.conf 6 | 7 | RUN mkdir /srv/www 8 | 9 | ADD dist/periodo-client-${CLIENT_VERSION} /srv/www/ 10 | 11 | RUN chown -R nginx /srv/www 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | PeriodO client 2 | 3 | Written 2014-2020 by Patrick Golden and Ryan 4 | Shaw . 5 | 6 | To the extent possible under law, the author(s) have dedicated all copyright 7 | and related and neighboring rights to this software to the public domain 8 | worldwide. This software is distributed without any warranty. 9 | 10 | You should have received a copy of the CC0 Public Domain Dedication along with 11 | this software. If not, see . 12 | -------------------------------------------------------------------------------- /LICENSE-3RD-PARTY: -------------------------------------------------------------------------------- 1 | Trash icon 2 | Created by Edward Boatman, from The Noun Project 3 | Creative Commons – Attribution (CC BY 3.0) 4 | https://thenounproject.com/term/trash/304/ 5 | 6 | Download icon 7 | Created by Edward Boatman, from The Noun Project 8 | Creative Commons – Attribution (CC BY 3.0) 9 | https://thenounproject.com/term/download/433/ 10 | 11 | Error icon 12 | Created by Anas Ramadan, from The Noun Project 13 | Creative Commons - Attribution (CC BY 3.0) 14 | https://thenounproject.com/term/error/2189/ 15 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/favicon.ico -------------------------------------------------------------------------------- /fly.publish.toml: -------------------------------------------------------------------------------- 1 | app = "periodo-client" 2 | primary_region = "iad" 3 | 4 | [build] 5 | 6 | [http_service] 7 | internal_port = 8080 8 | force_https = true 9 | auto_stop_machines = true 10 | auto_start_machines = true 11 | min_machines_running = 1 12 | -------------------------------------------------------------------------------- /fly.stage.toml: -------------------------------------------------------------------------------- 1 | app = "periodo-client-dev" 2 | primary_region = "iad" 3 | 4 | [build] 5 | 6 | [http_service] 7 | internal_port = 8080 8 | force_https = true 9 | auto_stop_machines = true 10 | auto_start_machines = true 11 | min_machines_running = 0 12 | -------------------------------------------------------------------------------- /images/maptiles/0/fwkw_-180.0x-89.98x180.0x90.0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/0/fwkw_-180.0x-89.98x180.0x90.0.jpg -------------------------------------------------------------------------------- /images/maptiles/1/22ob_0.0x30.02x45.0x90.0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/1/22ob_0.0x30.02x45.0x90.0.jpg -------------------------------------------------------------------------------- /images/maptiles/1/268i_-180.0x0.02x-135.0x30.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/1/268i_-180.0x0.02x-135.0x30.02.jpg -------------------------------------------------------------------------------- /images/maptiles/1/4okn_-45.0x-89.98x0.0x-29.98.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/1/4okn_-45.0x-89.98x0.0x-29.98.jpg -------------------------------------------------------------------------------- /images/maptiles/1/4tfh_-135.0x-89.98x-90.0x-29.98.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/1/4tfh_-135.0x-89.98x-90.0x-29.98.jpg -------------------------------------------------------------------------------- /images/maptiles/1/4xh7_45.0x-89.98x90.0x-29.98.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/1/4xh7_45.0x-89.98x90.0x-29.98.jpg -------------------------------------------------------------------------------- /images/maptiles/1/5ciq_-180.0x-89.98x-135.0x-29.98.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/1/5ciq_-180.0x-89.98x-135.0x-29.98.jpg -------------------------------------------------------------------------------- /images/maptiles/1/5edg_135.0x30.02x180.0x90.0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/1/5edg_135.0x30.02x180.0x90.0.jpg -------------------------------------------------------------------------------- /images/maptiles/1/8pkb_-180.0x30.02x-135.0x90.0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/1/8pkb_-180.0x30.02x-135.0x90.0.jpg -------------------------------------------------------------------------------- /images/maptiles/1/9ah4_-90.0x0.02x-45.0x30.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/1/9ah4_-90.0x0.02x-45.0x30.02.jpg -------------------------------------------------------------------------------- /images/maptiles/1/9cab_-90.0x-89.98x-45.0x-29.98.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/1/9cab_-90.0x-89.98x-45.0x-29.98.jpg -------------------------------------------------------------------------------- /images/maptiles/1/ba18_-135.0x0.02x-90.0x30.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/1/ba18_-135.0x0.02x-90.0x30.02.jpg -------------------------------------------------------------------------------- /images/maptiles/1/ehg6_135.0x0.02x180.0x30.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/1/ehg6_135.0x0.02x180.0x30.02.jpg -------------------------------------------------------------------------------- /images/maptiles/1/g0e5_-90.0x30.02x-45.0x90.0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/1/g0e5_-90.0x30.02x-45.0x90.0.jpg -------------------------------------------------------------------------------- /images/maptiles/1/h6w3_45.0x30.02x90.0x90.0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/1/h6w3_45.0x30.02x90.0x90.0.jpg -------------------------------------------------------------------------------- /images/maptiles/1/i5tz_-135.0x-29.98x-90.0x0.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/1/i5tz_-135.0x-29.98x-90.0x0.02.jpg -------------------------------------------------------------------------------- /images/maptiles/1/io3k_90.0x-89.98x135.0x-29.98.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/1/io3k_90.0x-89.98x135.0x-29.98.jpg -------------------------------------------------------------------------------- /images/maptiles/1/ja8o_-90.0x-29.98x-45.0x0.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/1/ja8o_-90.0x-29.98x-45.0x0.02.jpg -------------------------------------------------------------------------------- /images/maptiles/1/l39s_45.0x-29.98x90.0x0.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/1/l39s_45.0x-29.98x90.0x0.02.jpg -------------------------------------------------------------------------------- /images/maptiles/1/mvia_135.0x-89.98x180.0x-29.98.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/1/mvia_135.0x-89.98x180.0x-29.98.jpg -------------------------------------------------------------------------------- /images/maptiles/1/o7ho_45.0x0.02x90.0x30.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/1/o7ho_45.0x0.02x90.0x30.02.jpg -------------------------------------------------------------------------------- /images/maptiles/1/ot5u_0.0x-29.98x45.0x0.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/1/ot5u_0.0x-29.98x45.0x0.02.jpg -------------------------------------------------------------------------------- /images/maptiles/1/qaj0_90.0x-29.98x135.0x0.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/1/qaj0_90.0x-29.98x135.0x0.02.jpg -------------------------------------------------------------------------------- /images/maptiles/1/r30n_-180.0x-29.98x-135.0x0.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/1/r30n_-180.0x-29.98x-135.0x0.02.jpg -------------------------------------------------------------------------------- /images/maptiles/1/rn75_0.0x0.02x45.0x30.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/1/rn75_0.0x0.02x45.0x30.02.jpg -------------------------------------------------------------------------------- /images/maptiles/1/tipg_-45.0x0.02x0.0x30.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/1/tipg_-45.0x0.02x0.0x30.02.jpg -------------------------------------------------------------------------------- /images/maptiles/1/vugo_-45.0x-29.98x0.0x0.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/1/vugo_-45.0x-29.98x0.0x0.02.jpg -------------------------------------------------------------------------------- /images/maptiles/1/vuzm_90.0x30.02x135.0x90.0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/1/vuzm_90.0x30.02x135.0x90.0.jpg -------------------------------------------------------------------------------- /images/maptiles/1/xm39_0.0x-89.98x45.0x-29.98.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/1/xm39_0.0x-89.98x45.0x-29.98.jpg -------------------------------------------------------------------------------- /images/maptiles/1/yutf_-45.0x30.02x0.0x90.0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/1/yutf_-45.0x30.02x0.0x90.0.jpg -------------------------------------------------------------------------------- /images/maptiles/1/zch2_-135.0x30.02x-90.0x90.0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/1/zch2_-135.0x30.02x-90.0x90.0.jpg -------------------------------------------------------------------------------- /images/maptiles/1/zcx5_90.0x0.02x135.0x30.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/1/zcx5_90.0x0.02x135.0x30.02.jpg -------------------------------------------------------------------------------- /images/maptiles/1/zypk_135.0x-29.98x180.0x0.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/1/zypk_135.0x-29.98x180.0x0.02.jpg -------------------------------------------------------------------------------- /images/maptiles/2/0klu_-22.5x0.02x0.0x14.5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/0klu_-22.5x0.02x0.0x14.5.jpg -------------------------------------------------------------------------------- /images/maptiles/2/128h_67.5x0.02x90.0x14.5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/128h_67.5x0.02x90.0x14.5.jpg -------------------------------------------------------------------------------- /images/maptiles/2/28vy_-22.5x-48.57x0.0x-29.98.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/28vy_-22.5x-48.57x0.0x-29.98.jpg -------------------------------------------------------------------------------- /images/maptiles/2/29oz_22.5x30.02x45.0x48.6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/29oz_22.5x30.02x45.0x48.6.jpg -------------------------------------------------------------------------------- /images/maptiles/2/2ajp_67.5x-48.57x90.0x-29.98.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/2ajp_67.5x-48.57x90.0x-29.98.jpg -------------------------------------------------------------------------------- /images/maptiles/2/2lj0_112.5x0.02x135.0x14.5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/2lj0_112.5x0.02x135.0x14.5.jpg -------------------------------------------------------------------------------- /images/maptiles/2/2qe0_45.0x0.02x67.5x14.5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/2qe0_45.0x0.02x67.5x14.5.jpg -------------------------------------------------------------------------------- /images/maptiles/2/2sge_-45.0x0.02x-22.5x14.5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/2sge_-45.0x0.02x-22.5x14.5.jpg -------------------------------------------------------------------------------- /images/maptiles/2/2xji_45.0x-29.98x67.5x-14.47.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/2xji_45.0x-29.98x67.5x-14.47.jpg -------------------------------------------------------------------------------- /images/maptiles/2/2yq1_-90.0x-29.98x-67.5x-14.47.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/2yq1_-90.0x-29.98x-67.5x-14.47.jpg -------------------------------------------------------------------------------- /images/maptiles/2/3kte_-157.5x-48.57x-135.0x-29.98.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/3kte_-157.5x-48.57x-135.0x-29.98.jpg -------------------------------------------------------------------------------- /images/maptiles/2/3lt4_-180.0x-89.98x-157.5x-48.57.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/3lt4_-180.0x-89.98x-157.5x-48.57.jpg -------------------------------------------------------------------------------- /images/maptiles/2/3na7_0.0x-14.47x22.5x0.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/3na7_0.0x-14.47x22.5x0.02.jpg -------------------------------------------------------------------------------- /images/maptiles/2/44s5_67.5x14.5x90.0x30.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/44s5_67.5x14.5x90.0x30.02.jpg -------------------------------------------------------------------------------- /images/maptiles/2/4c4v_-135.0x-29.98x-112.5x-14.47.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/4c4v_-135.0x-29.98x-112.5x-14.47.jpg -------------------------------------------------------------------------------- /images/maptiles/2/4fsr_90.0x0.02x112.5x14.5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/4fsr_90.0x0.02x112.5x14.5.jpg -------------------------------------------------------------------------------- /images/maptiles/2/4rfi_-157.5x14.5x-135.0x30.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/4rfi_-157.5x14.5x-135.0x30.02.jpg -------------------------------------------------------------------------------- /images/maptiles/2/4vtu_-180.0x-48.57x-157.5x-29.98.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/4vtu_-180.0x-48.57x-157.5x-29.98.jpg -------------------------------------------------------------------------------- /images/maptiles/2/51rv_112.5x-14.47x135.0x0.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/51rv_112.5x-14.47x135.0x0.02.jpg -------------------------------------------------------------------------------- /images/maptiles/2/5791_-157.5x-89.98x-135.0x-48.57.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/5791_-157.5x-89.98x-135.0x-48.57.jpg -------------------------------------------------------------------------------- /images/maptiles/2/5ebh_-112.5x0.02x-90.0x14.5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/5ebh_-112.5x0.02x-90.0x14.5.jpg -------------------------------------------------------------------------------- /images/maptiles/2/5skb_-67.5x14.5x-45.0x30.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/5skb_-67.5x14.5x-45.0x30.02.jpg -------------------------------------------------------------------------------- /images/maptiles/2/5zo7_90.0x-48.57x112.5x-29.98.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/5zo7_90.0x-48.57x112.5x-29.98.jpg -------------------------------------------------------------------------------- /images/maptiles/2/63jf_157.5x48.6x180.0x90.0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/63jf_157.5x48.6x180.0x90.0.jpg -------------------------------------------------------------------------------- /images/maptiles/2/66d5_90.0x30.02x112.5x48.6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/66d5_90.0x30.02x112.5x48.6.jpg -------------------------------------------------------------------------------- /images/maptiles/2/6ghz_112.5x-29.98x135.0x-14.47.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/6ghz_112.5x-29.98x135.0x-14.47.jpg -------------------------------------------------------------------------------- /images/maptiles/2/740j_-90.0x14.5x-67.5x30.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/740j_-90.0x14.5x-67.5x30.02.jpg -------------------------------------------------------------------------------- /images/maptiles/2/7vqd_0.0x-89.98x22.5x-48.57.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/7vqd_0.0x-89.98x22.5x-48.57.jpg -------------------------------------------------------------------------------- /images/maptiles/2/828n_-112.5x-89.98x-90.0x-48.57.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/828n_-112.5x-89.98x-90.0x-48.57.jpg -------------------------------------------------------------------------------- /images/maptiles/2/855u_67.5x-29.98x90.0x-14.47.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/855u_67.5x-29.98x90.0x-14.47.jpg -------------------------------------------------------------------------------- /images/maptiles/2/85qm_-22.5x-14.47x0.0x0.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/85qm_-22.5x-14.47x0.0x0.02.jpg -------------------------------------------------------------------------------- /images/maptiles/2/8hrz_112.5x-89.98x135.0x-48.57.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/8hrz_112.5x-89.98x135.0x-48.57.jpg -------------------------------------------------------------------------------- /images/maptiles/2/8hyo_-90.0x-14.47x-67.5x0.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/8hyo_-90.0x-14.47x-67.5x0.02.jpg -------------------------------------------------------------------------------- /images/maptiles/2/8qid_22.5x14.5x45.0x30.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/8qid_22.5x14.5x45.0x30.02.jpg -------------------------------------------------------------------------------- /images/maptiles/2/8uc7_90.0x14.5x112.5x30.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/8uc7_90.0x14.5x112.5x30.02.jpg -------------------------------------------------------------------------------- /images/maptiles/2/8vh3_-157.5x48.6x-135.0x90.0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/8vh3_-157.5x48.6x-135.0x90.0.jpg -------------------------------------------------------------------------------- /images/maptiles/2/9e6c_-112.5x-14.47x-90.0x0.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/9e6c_-112.5x-14.47x-90.0x0.02.jpg -------------------------------------------------------------------------------- /images/maptiles/2/abtb_22.5x-14.47x45.0x0.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/abtb_22.5x-14.47x45.0x0.02.jpg -------------------------------------------------------------------------------- /images/maptiles/2/axzp_0.0x48.6x22.5x90.0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/axzp_0.0x48.6x22.5x90.0.jpg -------------------------------------------------------------------------------- /images/maptiles/2/bmne_90.0x-29.98x112.5x-14.47.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/bmne_90.0x-29.98x112.5x-14.47.jpg -------------------------------------------------------------------------------- /images/maptiles/2/bxvt_22.5x-29.98x45.0x-14.47.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/bxvt_22.5x-29.98x45.0x-14.47.jpg -------------------------------------------------------------------------------- /images/maptiles/2/c60z_0.0x0.02x22.5x14.5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/c60z_0.0x0.02x22.5x14.5.jpg -------------------------------------------------------------------------------- /images/maptiles/2/cmj5_22.5x-89.98x45.0x-48.57.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/cmj5_22.5x-89.98x45.0x-48.57.jpg -------------------------------------------------------------------------------- /images/maptiles/2/cqu4_-45.0x14.5x-22.5x30.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/cqu4_-45.0x14.5x-22.5x30.02.jpg -------------------------------------------------------------------------------- /images/maptiles/2/d6gk_-135.0x14.5x-112.5x30.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/d6gk_-135.0x14.5x-112.5x30.02.jpg -------------------------------------------------------------------------------- /images/maptiles/2/dmmf_135.0x14.5x157.5x30.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/dmmf_135.0x14.5x157.5x30.02.jpg -------------------------------------------------------------------------------- /images/maptiles/2/dpyx_135.0x30.02x157.5x48.6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/dpyx_135.0x30.02x157.5x48.6.jpg -------------------------------------------------------------------------------- /images/maptiles/2/du1y_45.0x-14.47x67.5x0.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/du1y_45.0x-14.47x67.5x0.02.jpg -------------------------------------------------------------------------------- /images/maptiles/2/e724_-67.5x-89.98x-45.0x-48.57.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/e724_-67.5x-89.98x-45.0x-48.57.jpg -------------------------------------------------------------------------------- /images/maptiles/2/ep6a_-22.5x-29.98x0.0x-14.47.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/ep6a_-22.5x-29.98x0.0x-14.47.jpg -------------------------------------------------------------------------------- /images/maptiles/2/eww2_-22.5x14.5x0.0x30.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/eww2_-22.5x14.5x0.0x30.02.jpg -------------------------------------------------------------------------------- /images/maptiles/2/exbe_-90.0x48.6x-67.5x90.0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/exbe_-90.0x48.6x-67.5x90.0.jpg -------------------------------------------------------------------------------- /images/maptiles/2/f5ri_157.5x-14.47x180.0x0.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/f5ri_157.5x-14.47x180.0x0.02.jpg -------------------------------------------------------------------------------- /images/maptiles/2/fayw_-112.5x14.5x-90.0x30.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/fayw_-112.5x14.5x-90.0x30.02.jpg -------------------------------------------------------------------------------- /images/maptiles/2/fjxh_0.0x30.02x22.5x48.6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/fjxh_0.0x30.02x22.5x48.6.jpg -------------------------------------------------------------------------------- /images/maptiles/2/flnz_-180.0x30.02x-157.5x48.6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/flnz_-180.0x30.02x-157.5x48.6.jpg -------------------------------------------------------------------------------- /images/maptiles/2/fn34_-45.0x30.02x-22.5x48.6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/fn34_-45.0x30.02x-22.5x48.6.jpg -------------------------------------------------------------------------------- /images/maptiles/2/frl7_22.5x0.02x45.0x14.5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/frl7_22.5x0.02x45.0x14.5.jpg -------------------------------------------------------------------------------- /images/maptiles/2/garw_-157.5x30.02x-135.0x48.6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/garw_-157.5x30.02x-135.0x48.6.jpg -------------------------------------------------------------------------------- /images/maptiles/2/gphm_-180.0x14.5x-157.5x30.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/gphm_-180.0x14.5x-157.5x30.02.jpg -------------------------------------------------------------------------------- /images/maptiles/2/gsmw_112.5x48.6x135.0x90.0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/gsmw_112.5x48.6x135.0x90.0.jpg -------------------------------------------------------------------------------- /images/maptiles/2/gzct_135.0x-89.98x157.5x-48.57.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/gzct_135.0x-89.98x157.5x-48.57.jpg -------------------------------------------------------------------------------- /images/maptiles/2/h482_-90.0x30.02x-67.5x48.6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/h482_-90.0x30.02x-67.5x48.6.jpg -------------------------------------------------------------------------------- /images/maptiles/2/hl4b_22.5x48.6x45.0x90.0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/hl4b_22.5x48.6x45.0x90.0.jpg -------------------------------------------------------------------------------- /images/maptiles/2/i68c_-157.5x-14.47x-135.0x0.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/i68c_-157.5x-14.47x-135.0x0.02.jpg -------------------------------------------------------------------------------- /images/maptiles/2/i7v3_-180.0x48.6x-157.5x90.0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/i7v3_-180.0x48.6x-157.5x90.0.jpg -------------------------------------------------------------------------------- /images/maptiles/2/ibx9_-22.5x-89.98x0.0x-48.57.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/ibx9_-22.5x-89.98x0.0x-48.57.jpg -------------------------------------------------------------------------------- /images/maptiles/2/id1h_157.5x14.5x180.0x30.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/id1h_157.5x14.5x180.0x30.02.jpg -------------------------------------------------------------------------------- /images/maptiles/2/imjd_-135.0x-14.47x-112.5x0.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/imjd_-135.0x-14.47x-112.5x0.02.jpg -------------------------------------------------------------------------------- /images/maptiles/2/iwom_45.0x-89.98x67.5x-48.57.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/iwom_45.0x-89.98x67.5x-48.57.jpg -------------------------------------------------------------------------------- /images/maptiles/2/j0a2_-22.5x30.02x0.0x48.6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/j0a2_-22.5x30.02x0.0x48.6.jpg -------------------------------------------------------------------------------- /images/maptiles/2/j2e0_67.5x-89.98x90.0x-48.57.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/j2e0_67.5x-89.98x90.0x-48.57.jpg -------------------------------------------------------------------------------- /images/maptiles/2/jzte_-135.0x48.6x-112.5x90.0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/jzte_-135.0x48.6x-112.5x90.0.jpg -------------------------------------------------------------------------------- /images/maptiles/2/k3wp_45.0x48.6x67.5x90.0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/k3wp_45.0x48.6x67.5x90.0.jpg -------------------------------------------------------------------------------- /images/maptiles/2/koj5_157.5x0.02x180.0x14.5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/koj5_157.5x0.02x180.0x14.5.jpg -------------------------------------------------------------------------------- /images/maptiles/2/lh81_45.0x30.02x67.5x48.6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/lh81_45.0x30.02x67.5x48.6.jpg -------------------------------------------------------------------------------- /images/maptiles/2/lldm_-180.0x-29.98x-157.5x-14.47.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/lldm_-180.0x-29.98x-157.5x-14.47.jpg -------------------------------------------------------------------------------- /images/maptiles/2/lmp1_-157.5x-29.98x-135.0x-14.47.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/lmp1_-157.5x-29.98x-135.0x-14.47.jpg -------------------------------------------------------------------------------- /images/maptiles/2/lwz0_0.0x14.5x22.5x30.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/lwz0_0.0x14.5x22.5x30.02.jpg -------------------------------------------------------------------------------- /images/maptiles/2/m51p_-90.0x-48.57x-67.5x-29.98.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/m51p_-90.0x-48.57x-67.5x-29.98.jpg -------------------------------------------------------------------------------- /images/maptiles/2/m5yg_157.5x-89.98x180.0x-48.57.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/m5yg_157.5x-89.98x180.0x-48.57.jpg -------------------------------------------------------------------------------- /images/maptiles/2/meta_-112.5x30.02x-90.0x48.6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/meta_-112.5x30.02x-90.0x48.6.jpg -------------------------------------------------------------------------------- /images/maptiles/2/mf57_-45.0x-89.98x-22.5x-48.57.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/mf57_-45.0x-89.98x-22.5x-48.57.jpg -------------------------------------------------------------------------------- /images/maptiles/2/mmox_-180.0x0.02x-157.5x14.5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/mmox_-180.0x0.02x-157.5x14.5.jpg -------------------------------------------------------------------------------- /images/maptiles/2/mpif_135.0x0.02x157.5x14.5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/mpif_135.0x0.02x157.5x14.5.jpg -------------------------------------------------------------------------------- /images/maptiles/2/ni1n_45.0x-48.57x67.5x-29.98.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/ni1n_45.0x-48.57x67.5x-29.98.jpg -------------------------------------------------------------------------------- /images/maptiles/2/njl3_135.0x-14.47x157.5x0.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/njl3_135.0x-14.47x157.5x0.02.jpg -------------------------------------------------------------------------------- /images/maptiles/2/ojdg_135.0x48.6x157.5x90.0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/ojdg_135.0x48.6x157.5x90.0.jpg -------------------------------------------------------------------------------- /images/maptiles/2/omvv_-67.5x-29.98x-45.0x-14.47.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/omvv_-67.5x-29.98x-45.0x-14.47.jpg -------------------------------------------------------------------------------- /images/maptiles/2/p8v2_-112.5x-48.57x-90.0x-29.98.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/p8v2_-112.5x-48.57x-90.0x-29.98.jpg -------------------------------------------------------------------------------- /images/maptiles/2/pbbt_-22.5x48.6x0.0x90.0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/pbbt_-22.5x48.6x0.0x90.0.jpg -------------------------------------------------------------------------------- /images/maptiles/2/pfe6_135.0x-48.57x157.5x-29.98.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/pfe6_135.0x-48.57x157.5x-29.98.jpg -------------------------------------------------------------------------------- /images/maptiles/2/pll0_-90.0x-89.98x-67.5x-48.57.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/pll0_-90.0x-89.98x-67.5x-48.57.jpg -------------------------------------------------------------------------------- /images/maptiles/2/qqvd_0.0x-29.98x22.5x-14.47.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/qqvd_0.0x-29.98x22.5x-14.47.jpg -------------------------------------------------------------------------------- /images/maptiles/2/qtrt_67.5x-14.47x90.0x0.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/qtrt_67.5x-14.47x90.0x0.02.jpg -------------------------------------------------------------------------------- /images/maptiles/2/r0ty_-67.5x48.6x-45.0x90.0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/r0ty_-67.5x48.6x-45.0x90.0.jpg -------------------------------------------------------------------------------- /images/maptiles/2/r4hx_157.5x-29.98x180.0x-14.47.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/r4hx_157.5x-29.98x180.0x-14.47.jpg -------------------------------------------------------------------------------- /images/maptiles/2/rmd9_-112.5x-29.98x-90.0x-14.47.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/rmd9_-112.5x-29.98x-90.0x-14.47.jpg -------------------------------------------------------------------------------- /images/maptiles/2/sjfs_90.0x-14.47x112.5x0.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/sjfs_90.0x-14.47x112.5x0.02.jpg -------------------------------------------------------------------------------- /images/maptiles/2/smvd_112.5x-48.57x135.0x-29.98.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/smvd_112.5x-48.57x135.0x-29.98.jpg -------------------------------------------------------------------------------- /images/maptiles/2/soh6_45.0x14.5x67.5x30.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/soh6_45.0x14.5x67.5x30.02.jpg -------------------------------------------------------------------------------- /images/maptiles/2/tann_-67.5x30.02x-45.0x48.6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/tann_-67.5x30.02x-45.0x48.6.jpg -------------------------------------------------------------------------------- /images/maptiles/2/tmtq_0.0x-48.57x22.5x-29.98.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/tmtq_0.0x-48.57x22.5x-29.98.jpg -------------------------------------------------------------------------------- /images/maptiles/2/ttq0_-90.0x0.02x-67.5x14.5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/ttq0_-90.0x0.02x-67.5x14.5.jpg -------------------------------------------------------------------------------- /images/maptiles/2/u279_-135.0x0.02x-112.5x14.5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/u279_-135.0x0.02x-112.5x14.5.jpg -------------------------------------------------------------------------------- /images/maptiles/2/ua6v_-135.0x-48.57x-112.5x-29.98.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/ua6v_-135.0x-48.57x-112.5x-29.98.jpg -------------------------------------------------------------------------------- /images/maptiles/2/ubes_157.5x30.02x180.0x48.6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/ubes_157.5x30.02x180.0x48.6.jpg -------------------------------------------------------------------------------- /images/maptiles/2/uhy6_-157.5x0.02x-135.0x14.5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/uhy6_-157.5x0.02x-135.0x14.5.jpg -------------------------------------------------------------------------------- /images/maptiles/2/uqi8_-45.0x-14.47x-22.5x0.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/uqi8_-45.0x-14.47x-22.5x0.02.jpg -------------------------------------------------------------------------------- /images/maptiles/2/vrgt_-135.0x-89.98x-112.5x-48.57.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/vrgt_-135.0x-89.98x-112.5x-48.57.jpg -------------------------------------------------------------------------------- /images/maptiles/2/vvs1_-180.0x-14.47x-157.5x0.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/vvs1_-180.0x-14.47x-157.5x0.02.jpg -------------------------------------------------------------------------------- /images/maptiles/2/w1na_22.5x-48.57x45.0x-29.98.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/w1na_22.5x-48.57x45.0x-29.98.jpg -------------------------------------------------------------------------------- /images/maptiles/2/w4pt_-45.0x-29.98x-22.5x-14.47.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/w4pt_-45.0x-29.98x-22.5x-14.47.jpg -------------------------------------------------------------------------------- /images/maptiles/2/wccd_90.0x-89.98x112.5x-48.57.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/wccd_90.0x-89.98x112.5x-48.57.jpg -------------------------------------------------------------------------------- /images/maptiles/2/wdi8_135.0x-29.98x157.5x-14.47.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/wdi8_135.0x-29.98x157.5x-14.47.jpg -------------------------------------------------------------------------------- /images/maptiles/2/weh4_112.5x30.02x135.0x48.6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/weh4_112.5x30.02x135.0x48.6.jpg -------------------------------------------------------------------------------- /images/maptiles/2/wgqx_-135.0x30.02x-112.5x48.6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/wgqx_-135.0x30.02x-112.5x48.6.jpg -------------------------------------------------------------------------------- /images/maptiles/2/wo2z_67.5x48.6x90.0x90.0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/wo2z_67.5x48.6x90.0x90.0.jpg -------------------------------------------------------------------------------- /images/maptiles/2/xg31_112.5x14.5x135.0x30.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/xg31_112.5x14.5x135.0x30.02.jpg -------------------------------------------------------------------------------- /images/maptiles/2/xxah_-45.0x-48.57x-22.5x-29.98.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/xxah_-45.0x-48.57x-22.5x-29.98.jpg -------------------------------------------------------------------------------- /images/maptiles/2/yj6o_-45.0x48.6x-22.5x90.0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/yj6o_-45.0x48.6x-22.5x90.0.jpg -------------------------------------------------------------------------------- /images/maptiles/2/ym1c_67.5x30.02x90.0x48.6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/ym1c_67.5x30.02x90.0x48.6.jpg -------------------------------------------------------------------------------- /images/maptiles/2/yott_-67.5x-14.47x-45.0x0.02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/yott_-67.5x-14.47x-45.0x0.02.jpg -------------------------------------------------------------------------------- /images/maptiles/2/ytj7_-112.5x48.6x-90.0x90.0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/ytj7_-112.5x48.6x-90.0x90.0.jpg -------------------------------------------------------------------------------- /images/maptiles/2/yx2d_90.0x48.6x112.5x90.0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/yx2d_90.0x48.6x112.5x90.0.jpg -------------------------------------------------------------------------------- /images/maptiles/2/z8rk_-67.5x-48.57x-45.0x-29.98.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/z8rk_-67.5x-48.57x-45.0x-29.98.jpg -------------------------------------------------------------------------------- /images/maptiles/2/zbaf_157.5x-48.57x180.0x-29.98.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/zbaf_157.5x-48.57x180.0x-29.98.jpg -------------------------------------------------------------------------------- /images/maptiles/2/zzla_-67.5x0.02x-45.0x14.5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/periodo/periodo-client/a38b59cc8ddb45cbbaf0ae734c8985241d9c76f2/images/maptiles/2/zzla_-67.5x0.02x-45.0x14.5.jpg -------------------------------------------------------------------------------- /modules/org-async-actions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "org-async-actions", 3 | "version": "1.0.0", 4 | "description": "Redux middleware with runtime typechecks for asynchronous requests and responses", 5 | "main": "src/index.js", 6 | "keywords": [], 7 | "author": "Patrick Golden ", 8 | "license": "CC0", 9 | "browserify": { 10 | "transform": [ 11 | "envify" 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /modules/org-async-actions/src/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const types = require('./types') 4 | , utils = require('./utils') 5 | 6 | module.exports = { 7 | typedAsyncActionMiddleware: require('./middleware'), 8 | makeTypedAction: types.makeTypedAction, 9 | ReadyState: types.ReadyState, 10 | getResponse: utils.getResponse, 11 | getError: utils.getError, 12 | handleCompletedAction: utils.handleCompletedAction, 13 | } 14 | -------------------------------------------------------------------------------- /modules/org-async-actions/src/middleware.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const R = require('ramda') 4 | , { isTypedRequest } = require('./utils') 5 | , { ReadyState } = require('./types') 6 | 7 | 8 | const enforceTypedAsyncActions = extraArgs => ( 9 | { dispatch, getState }) => next => action => { 10 | 11 | if (!action.readyState && !isTypedRequest(action)) { 12 | throw new Error('Actions should be called by creating a union type record.') 13 | } 14 | 15 | if (isTypedRequest(action)) { 16 | const req = action 17 | 18 | const update = readyState => dispatch({ 19 | type: req, 20 | readyState, 21 | }) 22 | 23 | update(ReadyState.Pending) 24 | 25 | return Promise.resolve(req.case({ 26 | [req._name]: (...args) => { 27 | let ret = req.exec(...args) 28 | 29 | if (typeof ret === 'function') { 30 | ret = ret(dispatch, getState, extraArgs) 31 | } 32 | 33 | return ret; 34 | }, 35 | _: R.T, 36 | })) 37 | .then(respData => update(ReadyState.Success(action.responseOf(respData)))) 38 | .catch(err => update(ReadyState.Failure(err))) 39 | } 40 | 41 | return next(action) 42 | } 43 | 44 | module.exports = enforceTypedAsyncActions; 45 | -------------------------------------------------------------------------------- /modules/org-async-actions/src/symbols.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | $$ActionType: Symbol('ActionType'), 5 | $$ReadyState: Symbol('ReadyState'), 6 | $$TypedRequest: Symbol('TypedRequest'), 7 | } 8 | -------------------------------------------------------------------------------- /modules/org-async-actions/src/tests/middleware.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const test = require('blue-tape') 4 | , configureMockStore = require('redux-mock-store').default 5 | , { typedAsyncActionMiddleware, makeTypedAction } = require('../') 6 | , { ReadyState } = require('../types') 7 | 8 | test('middleware', async t => { 9 | const Actions = makeTypedAction('ns', { 10 | GetSomething: { 11 | request: {}, 12 | response: { 13 | something: Number, 14 | }, 15 | exec: () => ({ 16 | something: 1, 17 | }), 18 | }, 19 | }) 20 | 21 | const store = configureMockStore([ 22 | typedAsyncActionMiddleware(), 23 | ])() 24 | 25 | await store.dispatch(Actions.GetSomething) 26 | 27 | const action = Actions.GetSomething 28 | 29 | t.deepEqual(store.getActions(), [ 30 | { 31 | type: action, 32 | readyState: ReadyState.Pending, 33 | }, 34 | { 35 | type: Actions.GetSomething, 36 | readyState: ReadyState.Success(action.responseOf({ 37 | something: 1, 38 | })), 39 | }, 40 | ]) 41 | 42 | t.ok(1) 43 | }) 44 | -------------------------------------------------------------------------------- /modules/org-async-actions/src/utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { $$TypedRequest } = require('./symbols') 4 | 5 | function isTypedRequest(obj) { 6 | return (typeof obj === 'object') && $$TypedRequest in obj 7 | } 8 | 9 | function isUnionTypeRecord(obj) { 10 | return ( 11 | Array.isArray(obj._keys) && 12 | typeof obj._name === 'string' && 13 | typeof obj.case === 'function' 14 | ) 15 | } 16 | 17 | function getResponse(action) { 18 | return action.readyState.case({ 19 | Success: resp => resp, 20 | _: () => { 21 | throw new Error('Ready state of action was not \'Success\'.') 22 | }, 23 | }) 24 | } 25 | 26 | function getError(action) { 27 | return action.readyState.case({ 28 | Failure: err => err, 29 | _: () => { 30 | throw new Error('Ready state of action was not \'Failure\'.') 31 | }, 32 | }) 33 | } 34 | 35 | function handleCompletedAction(action, onSuccess, onError) { 36 | return action.readyState.case({ 37 | Success: onSuccess, 38 | Failure: onError, 39 | Pending: () => { 40 | throw new Error( 41 | 'Ready state of action was pending. This can only be ' + 42 | 'called on a completed action.') 43 | }, 44 | }) 45 | } 46 | 47 | module.exports = { 48 | isTypedRequest, 49 | isUnionTypeRecord, 50 | getResponse, 51 | getError, 52 | handleCompletedAction, 53 | } 54 | -------------------------------------------------------------------------------- /modules/periodo-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "periodo-app", 3 | "version": "3.0.0-pre.2", 4 | "private": true, 5 | "description": "React application for browsing and editing PeriodO data", 6 | "main": "src/index.js", 7 | "license": "CC0", 8 | "browserify": { 9 | "transform": [ 10 | "envify" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /modules/periodo-app/src/auth/actions.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const R = require('ramda') 4 | , { makeTypedAction } = require('org-async-actions') 5 | 6 | const AuthAction = makeTypedAction({ 7 | GetAllSettings: { 8 | exec: getApplicationSettings, 9 | request: {}, 10 | response: { 11 | settings: Object, 12 | }, 13 | }, 14 | 15 | UpdateSettings: { 16 | exec: updateApplicationSettings, 17 | request: { 18 | fn: Function, 19 | }, 20 | response: { 21 | settings: Object, 22 | }, 23 | }, 24 | }) 25 | 26 | function getApplicationSettings() { 27 | return async (dispatch, getState, { db }) => { 28 | const settings = await db.settings.toArray() 29 | 30 | return { settings: R.dissoc('id', settings[0]) } 31 | } 32 | } 33 | 34 | function updateApplicationSettings(fn) { 35 | return async (dispatch, getState, { db }) => { 36 | const [ settings ] = await db.settings.toArray() 37 | , { id } = settings 38 | , newSettings = fn(R.dissoc('id', settings)) 39 | 40 | await db.settings.put(R.mergeRight(newSettings, { id })) 41 | 42 | return { settings: newSettings } 43 | } 44 | } 45 | 46 | module.exports = AuthAction 47 | -------------------------------------------------------------------------------- /modules/periodo-app/src/auth/components/Settings.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , BackendAction = require('../../backends/actions') 5 | , LinkedDataAction = require('../../linked-data/actions') 6 | , PersistenceNotice = require('../../backends/components/PersistenceWarning') 7 | 8 | const { 9 | Box, 10 | Button, 11 | Section, 12 | SectionHeading, 13 | } = require('periodo-ui') 14 | 15 | module.exports = function Settings(props) { 16 | const { dispatch } = props 17 | 18 | return ( 19 | h(Box, [ 20 | h(SectionHeading, 'In-browser data'), 21 | h(Section, [ 22 | h(Button, { 23 | mr: 2, 24 | onClick: async () => { 25 | await dispatch(LinkedDataAction.ClearLinkedDataCache); 26 | window.location.reload() 27 | }, 28 | }, 'Clear linked data cache'), 29 | 30 | h(Button, { 31 | variant: 'danger', 32 | onClick: async () => { 33 | if (confirm( 34 | 'Continue deleting all data sources?' 35 | + ' In-browser data is only stored on your own computer' 36 | + ' and can not be recovered.')) { 37 | await dispatch(BackendAction.DeleteAllBackends); 38 | window.location.reload() 39 | } 40 | }, 41 | }, 'Clear all data'), 42 | ]), 43 | 44 | h(SectionHeading, 'Browser storage'), 45 | h(Section, [ 46 | h(PersistenceNotice), 47 | ]), 48 | ]) 49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /modules/periodo-app/src/auth/reducer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const R = require('ramda') 4 | , AuthAction = require('./actions') 5 | 6 | const initialState = () => ({ 7 | settings: {}, 8 | }) 9 | 10 | module.exports = function auth(state=initialState(), action) { 11 | if(!Object.prototype.isPrototypeOf.call(AuthAction.prototype, action.type)) { 12 | return state 13 | } 14 | 15 | return action.readyState.case({ 16 | Success: resp => action.type.case({ 17 | GetAllSettings() { 18 | const { settings } = resp 19 | 20 | return R.mergeRight(state, { settings }) 21 | }, 22 | 23 | UpdateSettings() { 24 | const { settings } = resp 25 | 26 | return R.mergeRight(state, { settings }) 27 | }, 28 | 29 | }), 30 | _: () => state, 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /modules/periodo-app/src/backends/components/BackendPatch.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , Compare = require('../../patches/Compare') 5 | , { PatchDirection } = require('../../patches/types') 6 | , { Flex, Box, Link, Section } = require('periodo-ui') 7 | , { Route } = require('org-shell') 8 | 9 | module.exports = function BackendPatch(props) { 10 | const { backend, patch } = props 11 | , { prevDataset, dataset, position: { prev, next }} = patch 12 | 13 | return ( 14 | h(Box, [ 15 | h(Flex, { 16 | justifyContent: 'space-between', 17 | mb: 1, 18 | }, [ 19 | h(Box, prev && [ 20 | h(Link, { 21 | route: Route('backend-patch', { 22 | backendID: backend.storage.asIdentifier(), 23 | patchID: prev.url, 24 | }), 25 | }, '≪ Previous change'), 26 | ]), 27 | 28 | h(Box, next && [ 29 | h(Link, { 30 | route: Route('backend-patch', { 31 | backendID: backend.storage.asIdentifier(), 32 | patchID: next.url, 33 | }), 34 | }, 'Next change ≫'), 35 | ]), 36 | ]), 37 | 38 | h(Section, [ 39 | h(Compare, { 40 | direction: PatchDirection.Pull, 41 | localDataset: prevDataset, 42 | remoteDataset: dataset, 43 | }), 44 | ]), 45 | ]) 46 | ) 47 | } 48 | -------------------------------------------------------------------------------- /modules/periodo-app/src/backends/components/BackendSelector.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , { Box, Heading, Text, DropdownMenu, DropdownMenuItem } = require('periodo-ui') 5 | 6 | module.exports = function BackendSelector({ 7 | value, 8 | label, 9 | backends, 10 | onChange, 11 | }) { 12 | 13 | return ( 14 | h(DropdownMenu, { 15 | label: value 16 | ? value.metadata.label 17 | : label, 18 | onSelection: val => { 19 | onChange(val) 20 | }, 21 | }, backends.map(backend => 22 | h(DropdownMenuItem, { 23 | key: backend.asIdentifier(), 24 | value: backend, 25 | }, h(Box, [ 26 | h(Heading, { level: 4 }, backend.metadata.label), 27 | h(Text, backend.storage.url), 28 | ])) 29 | )) 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /modules/periodo-app/src/backends/components/BrowseAuthorities.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , { useState } = require('react') 5 | , { Route } = require('org-shell') 6 | , { Box, Link, HelpText } = require('periodo-ui') 7 | , AuthorityLayoutRenderer = require('../../layouts/authorities') 8 | 9 | const layout = ` 10 | [Search] 11 | type = authority-search 12 | section = untitled 13 | 14 | [AuthorityList] 15 | type = authority-list 16 | section = untitled 17 | ` 18 | 19 | module.exports = function BrowseAuthorities({ 20 | dataset, 21 | backend, 22 | opts, 23 | updateOpts, 24 | }) { 25 | const [ blockOpts, setBlockOpts ] = useState({}) 26 | 27 | return ( 28 | h(Box, [ 29 | dataset.authorities.length === 0 30 | ? h(HelpText, [ 31 | 'No authorities in this data source.', 32 | h(Link, { 33 | mx: 1, 34 | route: new Route('backend-add-authority', { 35 | backendID: backend.asIdentifier(), 36 | }), 37 | }, 'Add an authority'), 38 | 'or', 39 | h(Link, { 40 | mx: 1, 41 | route: new Route('backend-sync', { 42 | backendID: backend.asIdentifier(), 43 | }), 44 | }, 'import changes'), 45 | 'from another data source.', 46 | ]) 47 | : h(AuthorityLayoutRenderer, { 48 | data: dataset.authorities, 49 | layout, 50 | dataset, 51 | backend, 52 | blockOpts, 53 | shellOpts: opts, 54 | updateShellOpts: updateOpts, 55 | onBlockOptsChange: setBlockOpts, 56 | }), 57 | ]) 58 | ) 59 | } 60 | -------------------------------------------------------------------------------- /modules/periodo-app/src/backends/components/DownloadBackend.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , { Box } = require('periodo-ui') 5 | 6 | module.exports = () => 7 | h(Box, [ 8 | 'Make me', 9 | ]) 10 | -------------------------------------------------------------------------------- /modules/periodo-app/src/backends/components/History.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , React = require('react') 5 | , { Box, HelpText } = require('periodo-ui') 6 | , PatchLayoutRenderer = require('../../layouts/patches') 7 | 8 | const layout = ` 9 | [] 10 | type = patch-list 11 | ` 12 | 13 | class PatchHistory extends React.Component { 14 | constructor() { 15 | super(); 16 | 17 | this.state = { blockOpts: {}} 18 | } 19 | 20 | render() { 21 | const { patches, backend, authority, period } = this.props 22 | 23 | return ( 24 | h(Box, [ 25 | patches.length === 0 26 | ? h(HelpText, `No history of changes to this ${ 27 | authority 28 | ? period 29 | ? 'period' 30 | : 'authority' 31 | : 'data source' 32 | }.`) 33 | : ( 34 | h(PatchLayoutRenderer, { 35 | backend, 36 | patches, 37 | layout, 38 | blockOpts: this.state.blockOpts, 39 | onBlockOptsChange: blockOpts => this.setState({ blockOpts }), 40 | }) 41 | ), 42 | ]) 43 | ) 44 | } 45 | } 46 | 47 | module.exports = PatchHistory 48 | -------------------------------------------------------------------------------- /modules/periodo-app/src/backends/components/ReviewSubmittedPatches.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | 5 | // FIXME 6 | function ReviewSubmittedPatches() { 7 | return h('box', 'cool') 8 | } 9 | 10 | module.exports = ReviewSubmittedPatches; 11 | -------------------------------------------------------------------------------- /modules/periodo-app/src/backends/dataset_proxy/index_items.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | const { $$Authority, $$RelatedPeriods } = require('periodo-utils/src/symbols') 4 | 5 | function emptyRelations() { 6 | return { 7 | derivedFrom: {}, 8 | broader: {}, 9 | narrower: {}, 10 | } 11 | } 12 | 13 | const relations = Object.keys(emptyRelations()) 14 | 15 | const inverses = { 16 | broader: 'narrower', 17 | narrower: 'broader', 18 | // derivedFrom: 'derives', # TODO Add this? 19 | } 20 | 21 | module.exports = function indexItems(rawDataset) { 22 | const authoritiesByID = {} 23 | , periodsByID = {} 24 | 25 | // Index periods and authorities by ID 26 | Object.values(rawDataset.authorities || {}).forEach(authority => { 27 | authoritiesByID[authority.id] = authority 28 | 29 | Object.values(authority.periods || {}).forEach(period => { 30 | periodsByID[period.id] = period 31 | 32 | // Add symbols for circular references not part of the raw dataset 33 | period[$$Authority] = authority 34 | period[$$RelatedPeriods] = emptyRelations() 35 | }) 36 | }) 37 | 38 | const authorities = Object.values(authoritiesByID) 39 | , periods = Object.values(periodsByID) 40 | 41 | // Add references to related periods 42 | periods.forEach(period => { 43 | relations.forEach(relation => { 44 | const inverseRelation = inverses[relation] 45 | 46 | ;[].concat(period[relation] || []).forEach(relatedPeriodID => { 47 | const relatedPeriod = periodsByID[relatedPeriodID] 48 | 49 | // It's possible that this related period might not exist, if this is 50 | // a strangely formed dataset. 51 | if (!relatedPeriod) return 52 | 53 | period[$$RelatedPeriods][relation][relatedPeriodID] = relatedPeriod 54 | 55 | if (inverseRelation) { 56 | relatedPeriod[$$RelatedPeriods][inverseRelation][period.id] = period 57 | } 58 | }) 59 | }) 60 | }) 61 | 62 | return { 63 | authoritiesByID, 64 | periodsByID, 65 | authorities, 66 | periods, 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /modules/periodo-app/src/backends/dataset_proxy/sort.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Get the exact periods from the dataset, since the object identities will have 4 | // changed after coming from the web worker 5 | function restore(sortedPosByID, dataset) { 6 | return new Map([ ...sortedPosByID ].map(([ id, pos ]) => [ dataset.periodByID(id), pos ])) 7 | } 8 | 9 | // Add sorts for label, earliest start, and latest stop 10 | async function getSort(dataset, promiseWorker, field) { 11 | const { forward, reverse } = await promiseWorker.postMessage({ 12 | type: 'getSort', 13 | field, 14 | }) 15 | 16 | return { 17 | forward: restore(forward, dataset), 18 | reverse: restore(reverse, dataset), 19 | } 20 | } 21 | 22 | module.exports = { 23 | getSort, 24 | } 25 | -------------------------------------------------------------------------------- /modules/periodo-app/src/backends/dataset_proxy/worker/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const registerPromiseWorker = require('promise-worker/register') 4 | , indexItems = require('../index_items') 5 | , sort = require('./sort') 6 | 7 | 8 | module.exports = function sortWorker() { 9 | let dataset = null 10 | 11 | registerPromiseWorker(message => { 12 | switch (message.type) { 13 | case 'initialize': 14 | dataset = indexItems(message.rawDataset) 15 | break; 16 | 17 | case 'getSort': 18 | return sort(dataset.periods, message.field) 19 | 20 | default: 21 | break; 22 | } 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /modules/periodo-app/src/backends/dataset_proxy/worker/sort.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { terminus } = require('periodo-utils') 4 | , natsort = require('natsort').default 5 | , sorter = natsort({ insensitive: true }) 6 | , { transliterate } = require('transliteration') 7 | 8 | function sort(getter, vals) { 9 | const ret = new Map() 10 | 11 | const sorted = [ ...vals ].sort((a, b) => { 12 | const _a = getter(a) 13 | const _b = getter(b) 14 | 15 | if (_a == null) return 1 16 | if (_b == null) return -1 17 | 18 | return sorter(_a, _b) 19 | }) 20 | 21 | sorted.forEach((period, i) => { 22 | ret.set(period, i) 23 | }) 24 | 25 | return ret 26 | } 27 | 28 | function reverse(getter, map) { 29 | let reversed = [ ...map.keys() ].reverse() 30 | 31 | const toEnd = [] 32 | , ret = new Map() 33 | 34 | while (reversed[0] && (getter(reversed[0]) == null)) { 35 | toEnd.push(reversed.shift()) 36 | } 37 | 38 | reversed = reversed.concat(toEnd) 39 | 40 | reversed.forEach((period, i) => { 41 | ret.set(period, i) 42 | }) 43 | 44 | return ret 45 | } 46 | 47 | const getters = { 48 | label: p => transliterate(p.label), 49 | start: p => terminus.earliestYear(p.start), 50 | stop: p => terminus.latestYear(p.stop), 51 | } 52 | 53 | function sortPosByID(sortedMap) { 54 | return new Map([ ...sortedMap ].map(([ k, v ]) => [ k.id, v ])) 55 | } 56 | 57 | module.exports = function (periods, field) { 58 | const fn = getters[field] 59 | 60 | const forwardSort = sort(fn, periods) 61 | , reverseSort = reverse(fn, forwardSort) 62 | 63 | return { 64 | forward: sortPosByID(forwardSort), 65 | reverse: sortPosByID(reverseSort), 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /modules/periodo-app/src/backends/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | exports.reducer = require('./reducer') 4 | exports.resources = require('./resources') 5 | -------------------------------------------------------------------------------- /modules/periodo-app/src/backends/parse_periodo_upload.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Read a File object representing a PeriodO dataset. Return a promise that 4 | // resolves to the dataset as an object if successful, else reject with the 5 | // reason for failure. A valid PeriodO dataset must have a `authorities` 6 | // key at the very least. 7 | // 8 | // TODO: make a better check for validity of dataset. Maybe run it through a 9 | // few of the helper functions? 10 | 11 | module.exports = function (file) { 12 | const reader = new FileReader() 13 | 14 | return new Promise((resolve, reject) => { 15 | reader.onload = upload => { 16 | let data 17 | 18 | try { 19 | data = JSON.parse(upload.target.result); 20 | 21 | if (typeof data !== 'object') { 22 | throw new Error(`${file.name} is not a JSON document.`); 23 | } else if (!data.authorities) { 24 | throw new Error(`${file.name} does not seem to be a valid PeriodO dataset.`); 25 | } 26 | } catch (err) { 27 | reject(err); 28 | } 29 | 30 | resolve(data); 31 | } 32 | 33 | reader.readAsText(file); 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /modules/periodo-app/src/backends/test/backup.json: -------------------------------------------------------------------------------- 1 | { 2 | "backend": { 3 | "label": "asdfasdf", 4 | "description": "asdfasdf", 5 | "created": 1589478971142, 6 | "modified": 1589479119640, 7 | "accessed": 1590518256728 8 | }, 9 | "patches": [ 10 | { 11 | "forward": [ 12 | { 13 | "op": "add", 14 | "path": "/authorities/http:~1~1localhost:5002~1.well-known~1genid~10d58bf0987cc6232c8dbba2ce24979b1", 15 | "value": { 16 | "id": "http://localhost:5002/.well-known/genid/0d58bf0987cc6232c8dbba2ce24979b1", 17 | "type": "Authority", 18 | "periods": {}, 19 | "source": { 20 | "citation": "afsdl;jf;kalsdjf" 21 | } 22 | } 23 | } 24 | ], 25 | "backward": [ 26 | { 27 | "op": "remove", 28 | "path": "/authorities/http:~1~1localhost:5002~1.well-known~1genid~10d58bf0987cc6232c8dbba2ce24979b1" 29 | } 30 | ], 31 | "message": "Added authority http://localhost:5002/.well-known/genid/0d58bf0987cc6232c8dbba2ce24979b1\nAdded authority http://localhost:5002/.well-known/genid/0d58bf0987cc6232c8dbba2ce24979b1.", 32 | "forwardHashes": [ 33 | "28ea21d3f9e21cb255abbf844b7e876a" 34 | ], 35 | "backwardHashes": [ 36 | "1e688ee2341cf048091a7810f94513be" 37 | ], 38 | "created": 1589479119640, 39 | "affectedAuthorities": [ 40 | "http://localhost:5002/.well-known/genid/0d58bf0987cc6232c8dbba2ce24979b1" 41 | ], 42 | "affectedPeriods": [] 43 | } 44 | ], 45 | "dataset": { 46 | "authorities": { 47 | "http://localhost:5002/.well-known/genid/0d58bf0987cc6232c8dbba2ce24979b1": { 48 | "id": "http://localhost:5002/.well-known/genid/0d58bf0987cc6232c8dbba2ce24979b1", 49 | "type": "Authority", 50 | "periods": {}, 51 | "source": { 52 | "citation": "afsdl;jf;kalsdjf" 53 | } 54 | } 55 | }, 56 | "type": "rdf:Bag" 57 | }, 58 | "dexieVersion": 5 59 | } 60 | -------------------------------------------------------------------------------- /modules/periodo-app/src/backends/test/proxy.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | const test = require('blue-tape') 4 | , DatasetProxy = require('../dataset_proxy') 5 | 6 | const danglingRelatedPeriodsDataset = { 7 | authorities: { 8 | p0123: { 9 | id: 'p0123', 10 | periods: { 11 | p0123a: { 12 | id: 'p0123a', 13 | broader: 'p0123d', 14 | derivedFrom: [ 15 | 'p0123b', 16 | 'p0123c', 17 | ], 18 | }, 19 | p0123b: { 20 | id: 'p0123b', 21 | broader: 'p0123a', 22 | }, 23 | }, 24 | }, 25 | }, 26 | } 27 | 28 | test('Dataset proxy validation', async t => { 29 | const dataset = new DatasetProxy(danglingRelatedPeriodsDataset) 30 | 31 | t.deepEqual( 32 | dataset.validated, 33 | { 34 | type: 'rdf:Bag', 35 | authorities: { 36 | p0123: { 37 | id: 'p0123', 38 | periods: { 39 | p0123a: { 40 | id: 'p0123a', 41 | derivedFrom: [ 42 | 'p0123b', 43 | ], 44 | }, 45 | p0123b: { 46 | id: 'p0123b', 47 | broader: 'p0123a', 48 | }, 49 | }, 50 | }, 51 | }, 52 | }, 'should filter out related periods that do not exist') 53 | }) 54 | -------------------------------------------------------------------------------- /modules/periodo-app/src/backends/test/types.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const test = require('blue-tape') 4 | , { BackendBackup } = require('../types') 5 | , backupData = require('./backup.json') 6 | 7 | test('Creating a backup from a file', async t => { 8 | let backup 9 | 10 | t.doesNotThrow(() => { 11 | backup = BackendBackup.fromObject(backupData) 12 | }, 'should construct a backend') 13 | }) 14 | -------------------------------------------------------------------------------- /modules/periodo-app/src/db/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Dexie = require('dexie') 4 | , globals = require('../globals') 5 | 6 | const DB_NAME = '_PERIODO' 7 | 8 | module.exports = function periodoDB(dexieOpts) { 9 | const db = new Dexie(DB_NAME, { autoOpen: false, ...dexieOpts }) 10 | 11 | require('./version-01')(db) 12 | require('./version-02')(db) 13 | require('./version-03')(db) 14 | require('./version-04')(db) 15 | require('./version-05')(db) 16 | 17 | db.on('populate', () => { 18 | if (globals.periodoServerURL) { 19 | const backend = { 20 | url: globals.periodoServerURL, 21 | 22 | // FIXME: These are lies! But there's no way to know the latter two 23 | // until a request to the server has actually been made, and the type 24 | // definition says that they must be dates. It's not a big deal, but 25 | // this is a note for later. 26 | created: new Date(), 27 | modified: new Date(), 28 | accessed: new Date(), 29 | } 30 | 31 | if (globals.periodoServerURL.includes('://data.perio.do')) { 32 | backend.label = 'Canonical' 33 | backend.description = 'The canonical PeriodO dataset' 34 | } else { 35 | backend.label = 'Current host' 36 | backend.description = 'The PeriodO dataset hosted alongside this client' 37 | } 38 | 39 | db.remoteBackends.add(backend) 40 | } 41 | 42 | db.settings.add({}) 43 | }) 44 | 45 | return db; 46 | } 47 | -------------------------------------------------------------------------------- /modules/periodo-app/src/db/version-01.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = function dbVersion1(db) { 4 | db.version(1).stores({ 5 | localBackends: ` 6 | ++id, 7 | created, 8 | modified, 9 | accessed 10 | `, 11 | 12 | remoteBackends: 'url', 13 | 14 | // Patches derived from changes in IDB backends 15 | localBackendPatches: ` 16 | ++id, 17 | backendID, 18 | created, 19 | *changeType, 20 | *affectedCollections, 21 | *affectedPeriods, 22 | *forwardHashes, 23 | *backwardHashes 24 | `, 25 | 26 | // Patches submitted from a local dataset 27 | submittedPatches: 'url, backendName, resolved', 28 | 29 | // Cached triples from URLs 30 | linkedDataCache: 'url, *triples.subject, *triples.object', 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /modules/periodo-app/src/db/version-02.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = function dbVersion2(db) { 4 | db.version(2).stores({ 5 | localBackends: ` 6 | ++id, 7 | created, 8 | modified, 9 | accessed 10 | `, 11 | 12 | remoteBackends: 'url', 13 | 14 | settings: `++id`, 15 | 16 | // Patches derived from changes in IDB backends 17 | localBackendPatches: ` 18 | ++id, 19 | backendID, 20 | created, 21 | *changeType, 22 | *affectedCollections, 23 | *affectedPeriods, 24 | *forwardHashes, 25 | *backwardHashes 26 | `, 27 | 28 | // Patches submitted from a local dataset 29 | submittedPatches: 'url, backendName, resolved', 30 | 31 | // Cached triples from URLs 32 | linkedDataCache: 'url, *triples.subject, *triples.object', 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /modules/periodo-app/src/db/version-03.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = function dbVersion2(db) { 4 | db.version(3).stores({ 5 | localBackends: ` 6 | ++id, 7 | created, 8 | modified, 9 | accessed 10 | `, 11 | 12 | remoteBackends: 'url', 13 | 14 | settings: `++id`, 15 | 16 | // Patches derived from changes in IDB backends 17 | localBackendPatches: ` 18 | ++id, 19 | backendID, 20 | created, 21 | *changeType, 22 | *affectedCollections, 23 | *affectedPeriods, 24 | *forwardHashes, 25 | *backwardHashes 26 | `, 27 | 28 | // Patches submitted from a local dataset 29 | patchSubmissions: 'patchURL, backendID, resolved', 30 | 31 | // Cached triples from URLs 32 | linkedDataCache: 'url, *triples.subject, *triples.object', 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /modules/periodo-app/src/db/version-04.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = function dbVersion2(db) { 4 | db.version(4).stores({ 5 | localBackends: ` 6 | ++id, 7 | created, 8 | modified, 9 | accessed 10 | `, 11 | 12 | remoteBackends: 'url', 13 | 14 | settings: `++id`, 15 | 16 | // Patches derived from changes in IDB backends 17 | localBackendPatches: ` 18 | ++id, 19 | backendID, 20 | created, 21 | *changeType, 22 | *affectedAuthorities, 23 | *affectedPeriods, 24 | *forwardHashes, 25 | *backwardHashes 26 | `, 27 | 28 | // Patches submitted from a local dataset 29 | patchSubmissions: 'patchURL, backendID, resolved', 30 | 31 | // Cached triples from URLs 32 | linkedDataCache: 'url, *triples.subject, *triples.object', 33 | }).upgrade(tx => { 34 | return tx.localBackendPatches.toCollection().modify(patch => { 35 | patch.affectedAuthorities = patch.affectedCollections; 36 | delete patch.affectedCollections; 37 | }) 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /modules/periodo-app/src/db/version-05.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = function dbVersion2(db) { 4 | db.version(5).stores({ 5 | localBackends: ` 6 | ++id, 7 | created, 8 | modified, 9 | accessed 10 | `, 11 | 12 | remoteBackends: 'url', 13 | 14 | fileBackends: '++id', 15 | 16 | settings: `++id`, 17 | 18 | // Patches derived from changes in IDB backends 19 | localBackendPatches: ` 20 | ++id, 21 | backendID, 22 | created, 23 | *changeType, 24 | *affectedAuthorities, 25 | *affectedPeriods, 26 | *forwardHashes, 27 | *backwardHashes 28 | `, 29 | 30 | // Patches submitted from a local dataset 31 | patchSubmissions: 'patchURL, backendID, resolved', 32 | 33 | // Cached triples from URLs 34 | linkedDataCache: 'url, *triples.subject, *triples.object', 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /modules/periodo-app/src/errors.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function NotFoundError(message) { 4 | this.name = 'NotFoundError'; 5 | this.message = message || 'Object not found'; 6 | this.stack = Error().stack; 7 | } 8 | NotFoundError.prototype = Object.create(Error.prototype); 9 | NotFoundError.prototype.constructor = NotFoundError; 10 | 11 | 12 | function NotImplementedError(message) { 13 | this.name = 'NotFoundError'; 14 | this.message = message || 'Method not implemented.'; 15 | this.stack = Error().stack; 16 | } 17 | NotImplementedError.prototype = Object.create(Error.prototype); 18 | NotImplementedError.prototype.constructor = NotImplementedError; 19 | 20 | module.exports = { 21 | NotFoundError, 22 | NotImplementedError, 23 | } 24 | -------------------------------------------------------------------------------- /modules/periodo-app/src/forms/PeriodForm/RelatedPeriodsForm.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , R = require('ramda') 5 | , { Box } = require('periodo-ui') 6 | , { period } = require('periodo-utils') 7 | , RelatedPeriodList = require('./RelatedPeriodList') 8 | 9 | const $$RelatedPeriods = Symbol.for('RelatedPeriods') 10 | 11 | const RelatedPeriodsForm = ({ 12 | value, 13 | onValueChange, 14 | backendID, 15 | dataset, 16 | authority, 17 | }) => { 18 | const relatedPeriods = value[$$RelatedPeriods] 19 | 20 | const update = prop => periods => { 21 | value[prop] = periods.map(({ id }) => id) 22 | value[$$RelatedPeriods][prop] = R.indexBy(R.prop('id'), periods) 23 | onValueChange(value) 24 | } 25 | 26 | return h(Box, [ 27 | h(RelatedPeriodList, { 28 | name: 'broader', 29 | label: 'Part of', 30 | helpText: 'Broader period containing this one', 31 | periods: Object.values(relatedPeriods.broader), 32 | suggestionFilter: 33 | ({ id }) => value.id !== id && ! value.narrower.includes(id), 34 | limit: 1, 35 | authorities: [ authority ], 36 | backendID, 37 | onValueChange: update('broader'), 38 | }), 39 | 40 | h(RelatedPeriodList, { 41 | mt: 3, 42 | name: 'narrower', 43 | label: 'Has parts', 44 | helpText: 'Narrower periods contained by this one', 45 | periods: R.sort(period.byStartYear, Object.values(relatedPeriods.narrower)), 46 | suggestionFilter: ({ id }) => value.id !== id && value.broader !== id, 47 | authorities: [ authority ], 48 | backendID, 49 | onValueChange: update('narrower'), 50 | }), 51 | 52 | h(RelatedPeriodList, { 53 | mt: 3, 54 | name: 'derived-from', 55 | label: 'Derived from', 56 | helpText: 'Other periods from which this one was derived', 57 | periods: Object.values(relatedPeriods.derivedFrom), 58 | suggestionFilter: ({ id }) => value.id !== id, 59 | authorities: Object.values(dataset.authorities), 60 | backendID, 61 | onValueChange: update('derivedFrom'), 62 | }), 63 | ]) 64 | } 65 | 66 | module.exports = RelatedPeriodsForm 67 | -------------------------------------------------------------------------------- /modules/periodo-app/src/forms/PeriodForm/SpatialCoverageForm/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , { Box, HelpText, InputBlock, Label } = require('periodo-ui') 5 | , { PlacesSelect, ExternalLink } = require('periodo-ui') 6 | 7 | const SpatialCoverageForm = ({ 8 | onValueChange, 9 | description='', 10 | coverage=[], 11 | suggestions=[], 12 | gazetteers, 13 | }) => { 14 | 15 | return h(Box, [ 16 | h(InputBlock, { 17 | name: 'description', 18 | label: 'Description', 19 | helpText: 20 | 'Description of the spatial coverage as given in the original source', 21 | value: description, 22 | onChange: e => onValueChange({ 23 | spatialCoverageDescription: e.target.value, 24 | }), 25 | }), 26 | 27 | h(Box, { 28 | mt: 3, 29 | }, [ 30 | h(Label, { 31 | htmlFor: 'coverage-area', 32 | }, 'Coverage area'), 33 | 34 | h(HelpText, {}, [ 35 | 'Set of places that approximate the area of spatial coverage. ', 36 | "If you cannot find a suitable place, ", 37 | h(ExternalLink, { href: "https://perio.do/contact/" }, "contact us "), 38 | "and we can add it." 39 | ]), 40 | 41 | h(PlacesSelect, { 42 | onChange: places => onValueChange({ spatialCoverage: places }), 43 | coverage, 44 | suggestions, 45 | gazetteers, 46 | }), 47 | ]), 48 | ]) 49 | } 50 | 51 | module.exports = SpatialCoverageForm 52 | -------------------------------------------------------------------------------- /modules/periodo-app/src/forms/PeriodForm/TemporalCoverageForm.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , React = require('react') 5 | , TerminusInput = require('./TerminusInput') 6 | , { Label, Flex, Box } = require('periodo-ui') 7 | , { Checkbox } = require('periodo-ui') 8 | , { wasAutoparsed } = require('periodo-utils/src/terminus') 9 | 10 | const emptyTerminus = { 11 | label: '', 12 | in: { year: '' }, 13 | } 14 | 15 | module.exports = class TemporalCoverageForm extends React.Component { 16 | constructor(props) { 17 | super(); 18 | 19 | this.state = { 20 | autoparse: ( 21 | (!props.start && !props.stop) || 22 | (wasAutoparsed(props.start) && wasAutoparsed(props.stop)) 23 | ), 24 | } 25 | } 26 | 27 | render() { 28 | const { autoparse } = this.state 29 | , { start, stop, onValueChange } = this.props 30 | 31 | return h('div', [ 32 | h(Box, { my: 1 }, [ 33 | h(Label, [ 34 | h(Checkbox, { 35 | mr: 1, 36 | checked: autoparse, 37 | onChange: () => 38 | this.setState(prev => ({ autoparse: !prev.autoparse })), 39 | }), 40 | 'Parse dates automatically', 41 | ]), 42 | ]), 43 | 44 | h(Flex, { 45 | alignItems: 'center', 46 | mt: 2, 47 | }, [ 48 | h(TerminusInput, { 49 | autoparse, 50 | label: 'Start', 51 | terminus: start || emptyTerminus, 52 | onValueChange: start => onValueChange({ start }), 53 | }), 54 | ]), 55 | 56 | h(Flex, { 57 | alignItems: 'center', 58 | mt: 3, 59 | }, [ 60 | h(TerminusInput, { 61 | autoparse, 62 | label: 'Stop', 63 | terminus: stop || emptyTerminus, 64 | onValueChange: stop => onValueChange({ stop }), 65 | }), 66 | ]), 67 | ]) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /modules/periodo-app/src/forms/Validated.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , React = require('react') 5 | 6 | module.exports = function makeValidated(validationFn, Component) { 7 | return class Validated extends React.Component { 8 | constructor() { 9 | super(); 10 | 11 | this.state = { 12 | errors: {}, 13 | } 14 | } 15 | 16 | validate(data, onSuccess) { 17 | this.clearErrors.call(this, () => { 18 | validationFn(data).case({ 19 | Ok: onSuccess, 20 | Err: errors => this.setState({ errors }), 21 | }) 22 | }) 23 | } 24 | 25 | clearErrors(cb) { 26 | this.setState({ errors: {}}, cb) 27 | } 28 | 29 | render() { 30 | return ( 31 | h(Component, { 32 | ...this.props, 33 | errors: this.state.errors, 34 | validate: this.validate.bind(this), 35 | clearErrors: this.clearErrors.bind(this), 36 | }) 37 | ) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /modules/periodo-app/src/forms/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | BackendForm: require('./BackendForm'), 5 | PeriodForm: require('./PeriodForm'), 6 | AuthorityForm: require('./AuthorityForm'), 7 | } 8 | -------------------------------------------------------------------------------- /modules/periodo-app/src/globals.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | let periodoServerURL 4 | 5 | if (typeof window !== 'undefined') { 6 | periodoServerURL = ( 7 | global.PERIODO_SERVER_URL || 8 | new URL('/', window.location).href 9 | ) 10 | } 11 | 12 | module.exports = { 13 | periodoServerURL, 14 | corsProxyURL: global.PERIODO_PROXY_URL, 15 | corsProxyEnabled: global.CORS_PROXY_ENABLED, 16 | } 17 | -------------------------------------------------------------------------------- /modules/periodo-app/src/graphs/actions.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const R = require('ramda') 4 | , { makeTypedAction, getResponse } = require('org-async-actions') 5 | 6 | const GraphsAction = module.exports = makeTypedAction({ 7 | FetchGraphs: { 8 | exec: fetchGraphs, 9 | request: { 10 | path: String, 11 | }, 12 | response: { 13 | json: Object, 14 | }, 15 | }, 16 | 17 | FetchGazetteers: { 18 | exec: fetchGazetteers, 19 | request: {}, 20 | response: { 21 | gazetteers: Object, 22 | }, 23 | }, 24 | }) 25 | 26 | const graphURL = path => { 27 | const origin = (global.location 28 | && global.location.hostname.startsWith('client.')) 29 | ? global.location.origin.replace('client.', 'data.') 30 | : 'https://data.staging.perio.do' // assume we are testing if the origin 31 | // does not begin with 'client.' 32 | return `${origin}/graphs/${path}` 33 | } 34 | 35 | const indexFeatures = gazetteers => R.fromPairs(R.chain( 36 | gi => R.map( 37 | fi => { 38 | const path = [ gi, 'features', fi ] 39 | return [ R.path(path, gazetteers).id, path ] 40 | }, 41 | R.range(0, gazetteers[gi].features.length) 42 | ), 43 | R.range(0, gazetteers.length) 44 | )) 45 | 46 | function fetchGraphs(path) { 47 | return async () => { 48 | const resp = await fetch(graphURL(path), { 49 | mode: 'cors', 50 | headers: { Accept: 'application/json' }, 51 | }) 52 | 53 | if (!resp.ok) { 54 | const err = new Error( 55 | `Could not fetch ${resp.url} (${resp.status}):\n\n${resp.statusText}`) 56 | err.resp = resp 57 | throw err 58 | } 59 | 60 | return { json: await resp.json() } 61 | } 62 | } 63 | 64 | function fetchGazetteers() { 65 | return async dispatch => { 66 | const req = await dispatch(GraphsAction.FetchGraphs('places')) 67 | , { json } = getResponse(req) 68 | , gazetteers = Object.values(json.graphs) 69 | , featuresIndex = indexFeatures(gazetteers) 70 | 71 | gazetteers.find = id => { 72 | const index = featuresIndex[id] 73 | 74 | if (!index) return null 75 | 76 | return R.path(index, gazetteers) 77 | } 78 | return { gazetteers } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /modules/periodo-app/src/graphs/reducer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const R = require('ramda') 4 | , GraphsAction = require('./actions') 5 | 6 | const initialState = () => { 7 | const gazetteers = [] 8 | gazetteers.index = {} 9 | return { gazetteers } 10 | } 11 | 12 | module.exports = function linkedData(state=initialState(), action) { 13 | if(!Object.prototype.isPrototypeOf.call(GraphsAction.prototype, action.type)) { 14 | return state 15 | } 16 | 17 | return action.readyState.case({ 18 | Success: resp => action.type.case({ 19 | FetchGraphs() { 20 | return state 21 | }, 22 | 23 | FetchGazetteers() { 24 | return R.assoc('gazetteers', resp.gazetteers, state) 25 | }, 26 | }), 27 | _: () => state, 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /modules/periodo-app/src/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const PropTypes = require('prop-types') 4 | 5 | Object.defineProperty(require('react'), 'PropTypes', { 6 | get: () => PropTypes, 7 | }) 8 | 9 | const h = require('react-hyperscript') 10 | , ReactDOM = require('react-dom') 11 | , Application = require('./main/Application') 12 | 13 | if (process.browser) { 14 | ReactDOM.render(h(Application), document.getElementById('main')) 15 | } 16 | -------------------------------------------------------------------------------- /modules/periodo-app/src/layouts/LayoutBlock.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | const h = require('react-hyperscript') 4 | , R = require('ramda') 5 | , React = require('react') 6 | , { Box } = require('periodo-ui') 7 | 8 | class LayoutBlock extends React.Component { 9 | shouldComponentUpdate(nextProps) { 10 | const monitored = [ 11 | 'extraProps', 'processedOpts', 'passedOpts', 'defaultOpts', 12 | ] 13 | 14 | if (this.props.data !== nextProps.data) { 15 | return true 16 | } 17 | 18 | for (const key of monitored) { 19 | if (!R.equals(this.props[key], nextProps[key])) { 20 | return true 21 | } 22 | } 23 | 24 | return false; 25 | } 26 | 27 | render() { 28 | const { 29 | id, 30 | defaultOpts, 31 | passedOpts, 32 | processedOpts, 33 | extraProps, 34 | setBlockState, 35 | data, 36 | block: { Component }, 37 | onOptsChange, 38 | invalidate, 39 | hidden, 40 | } = this.props 41 | 42 | const opts = { 43 | ...defaultOpts, 44 | ...passedOpts, 45 | } 46 | 47 | const updateOpts = (fn, invalidate) => { 48 | const updated = typeof fn === 'object' 49 | ? ({ 50 | ...opts, 51 | ...fn, 52 | }) 53 | : fn(opts) 54 | 55 | const newOpts = {} 56 | 57 | for (const k in updated) { 58 | const addToNewOpts = ( 59 | updated[k] != undefined && 60 | defaultOpts[k] !== updated[k] && 61 | !!(defaultOpts[k] || updated[k]) 62 | ) 63 | 64 | if (addToNewOpts) { 65 | newOpts[k] = updated[k] 66 | } 67 | } 68 | 69 | onOptsChange(newOpts, invalidate) 70 | } 71 | 72 | return ( 73 | h(Box, { 74 | id, 75 | mb: 3, 76 | minWidth: 0, 77 | minHeight: 0, 78 | }, [ 79 | h(Component, { 80 | opts, 81 | updateOpts, 82 | data, 83 | setBlockState, 84 | invalidate, 85 | hidden, 86 | ...processedOpts, 87 | ...extraProps, 88 | }), 89 | ]) 90 | ) 91 | } 92 | } 93 | 94 | module.exports = LayoutBlock 95 | -------------------------------------------------------------------------------- /modules/periodo-app/src/layouts/ListBlock/ColumnSelector.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , R = require('ramda') 5 | , { Box, DropdownMenu, DropdownMenuItem } = require('periodo-ui') 6 | 7 | function ColumnSelector({ columns, shownColumns, updateOpts, ...props }) { 8 | 9 | const toggleColumn = key => () => { 10 | updateOpts(opts => 11 | R.over( 12 | R.lensProp('shownColumns'), 13 | (shownColumns.includes(key) 14 | ? R.without 15 | : R.flip(R.union) 16 | )([ key ]), 17 | opts 18 | ) 19 | ) 20 | } 21 | 22 | return h(Box, props, [ 23 | h(DropdownMenu, { 24 | closeOnSelection: false, 25 | openLeft: true, 26 | label: 'Columns', 27 | }, Object.keys(columns).map(key => 28 | h(DropdownMenuItem, { 29 | key, 30 | textAlign: 'left', 31 | onClick: toggleColumn(key), 32 | }, [ 33 | h('input', { 34 | type: 'checkbox', 35 | checked: shownColumns.includes(key), 36 | onChange: toggleColumn(key), 37 | }), 38 | 39 | columns[key].label, 40 | ]) 41 | )), 42 | ]) 43 | } 44 | 45 | module.exports = ColumnSelector 46 | -------------------------------------------------------------------------------- /modules/periodo-app/src/layouts/ListBlock/ListControls.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , R = require('ramda') 5 | , { Flex, PagerControls, PagerCounter } = require('periodo-ui') 6 | , ColumnSelector = require('./ColumnSelector') 7 | 8 | function ListControls({ 9 | start, 10 | limit, 11 | total, 12 | shown, 13 | columns, 14 | shownColumns, 15 | toPrevPage, 16 | toNextPage, 17 | toFirstPage, 18 | toLastPage, 19 | updateOpts, 20 | }) { 21 | return h(Flex, { 22 | alignItems: 'center', 23 | justifyContent: 'space-between', 24 | mb: 3, 25 | }, [ 26 | 27 | h(PagerCounter, { 28 | start, 29 | total, 30 | shown, 31 | }), 32 | 33 | h(PagerControls, { 34 | start, 35 | limit, 36 | total, 37 | shown, 38 | toFirstPage, 39 | toPrevPage, 40 | toNextPage, 41 | toLastPage, 42 | onLimitChange: limit => updateOpts(R.set(R.lensProp('limit'), limit)), 43 | }), 44 | 45 | h(ColumnSelector, { 46 | textAlign: 'right', 47 | columns, 48 | shownColumns, 49 | updateOpts, 50 | }), 51 | ]) 52 | } 53 | 54 | module.exports = ListControls 55 | -------------------------------------------------------------------------------- /modules/periodo-app/src/layouts/ListBlock/ListHeader.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , { Box } = require('periodo-ui') 5 | 6 | function ListHeader({ 7 | columns, 8 | shownColumns, 9 | sortBy, 10 | sortDirection, 11 | firstColumnWidth, 12 | updateOpts, 13 | toFirstPage, 14 | }) { 15 | return ( 16 | h(Box, { 17 | as: 'thead', 18 | mb: 1, 19 | }, [ 20 | h('tr', { 21 | style: { 22 | textAlign: 'left', 23 | }, 24 | }, [ 25 | h('th', { 26 | key: 'first', 27 | style: { 28 | width: firstColumnWidth, 29 | }, 30 | }), 31 | ].concat(shownColumns.map(n => 32 | h(Box, { 33 | as: 'th', 34 | key: n, 35 | p: 2, 36 | style: { 37 | width: columns[n].width || 'unset', 38 | cursor: 'pointer', 39 | }, 40 | onClick: () => { 41 | updateOpts((opts={}) => ({ 42 | ...opts, 43 | sortBy: n, 44 | sortDirection: opts.sortBy === n 45 | ? ( 46 | !opts.sortDirection || 47 | opts.sortDirection === 'asc' 48 | ) 49 | ? 'desc' : 'asc' 50 | : 'asc', 51 | })) 52 | toFirstPage() 53 | }, 54 | }, [ 55 | columns[n].label, 56 | n === sortBy && ( 57 | sortDirection === 'desc' ? '▲' : '▼' 58 | ), 59 | ]) 60 | ))), 61 | ]) 62 | ) 63 | } 64 | 65 | module.exports = ListHeader 66 | -------------------------------------------------------------------------------- /modules/periodo-app/src/layouts/ListBlock/ListRow.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , R = require('ramda') 5 | , { Box, Text, Link } = require('periodo-ui') 6 | 7 | function ListRow({ 8 | item, 9 | index, 10 | start, 11 | columns, 12 | shownColumns, 13 | backend, 14 | itemViewRoute, 15 | itemEditRoute, 16 | }) { 17 | return ( 18 | h('tr', { 19 | key: item.id, 20 | }, [ 21 | h('td', { 22 | key: '_numbering', 23 | style: { 24 | verticalAlign: 'middle', 25 | whiteSpace: 'nowrap', 26 | }, 27 | }, [ 28 | h(Text, { 29 | display: 'inline-block', 30 | width: '4ch', 31 | textAlign: 'right', 32 | }, index + 1 + start), 33 | 34 | h(Link, { 35 | ml: 2, 36 | fontWeight: 100, 37 | route: itemViewRoute(item, { backend }), 38 | }, 'view'), 39 | 40 | backend.isEditable() && itemEditRoute 41 | ? ( 42 | h(Link, { 43 | ml: 2, 44 | fontWeight: 100, 45 | route: itemEditRoute(item, { backend }), 46 | }, 'edit') 47 | ) 48 | : null, 49 | ]), 50 | ].concat(R.values(R.pick(shownColumns, columns)).map( 51 | col => h(Box, { 52 | as: 'td', 53 | key: col.label, 54 | p: 2, 55 | verticalAlign: 'middle', 56 | }, (col.render || R.identity)(col.getValue(item))) 57 | ))) 58 | ) 59 | } 60 | 61 | module.exports = ListRow 62 | -------------------------------------------------------------------------------- /modules/periodo-app/src/layouts/authorities/AuthorityDetail.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , { Authority, Box, Heading, Link } = require('periodo-ui') 5 | , { Route } = require('org-shell') 6 | , { period: { authorityOf }} = require('periodo-utils') 7 | 8 | function AuthorityDetail({ 9 | period, 10 | backend, 11 | ...props 12 | }) { 13 | const authority = period && authorityOf(period) 14 | , editable = backend.asIdentifier().startsWith('local-') 15 | 16 | return h(Box, { 17 | style: { 18 | minHeight: 600, 19 | wordBreak: 'break-word', 20 | }, 21 | ...props, 22 | }, authority == null ? null : [ 23 | h(Heading, { 24 | level: 4, 25 | fontSize: 2, 26 | style: { 27 | display: 'flex', 28 | alignItems: 'center', 29 | borderBottom: '1px solid #ddd', 30 | paddingBottom: '4px', 31 | }, 32 | }, [ 33 | 'Authority', 34 | 35 | h(Link, { 36 | ml: 2, 37 | fontSize: 1, 38 | fontWeight: 100, 39 | route: new Route('authority-view', { 40 | backendID: backend.asIdentifier(), 41 | authorityID: authority.id, 42 | }), 43 | }, [ 44 | 'view', 45 | ]), 46 | 47 | editable ? h(Link, { 48 | ml: 2, 49 | fontSize: 1, 50 | fontWeight: 100, 51 | route: new Route('authority-edit', { 52 | backendID: backend.asIdentifier(), 53 | authorityID: authority.id, 54 | }), 55 | }, [ 56 | 'edit', 57 | ]) : null, 58 | 59 | ]), 60 | 61 | h(Authority, { value: authority }), 62 | ]) 63 | } 64 | 65 | module.exports = { 66 | label: 'Authority detail', 67 | description: 'Details of a hovered or focused period', 68 | Component: AuthorityDetail, 69 | } 70 | -------------------------------------------------------------------------------- /modules/periodo-app/src/layouts/authorities/AuthorityList.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , contributorList = require('periodo-utils/src/contributor_list') 5 | , source = require('periodo-utils/src/source') 6 | , ListBlock = require('../ListBlock') 7 | , { InlineText, Text } = require('periodo-ui') 8 | , { Route } = require('org-shell') 9 | 10 | const columns = { 11 | authors: { 12 | width: '10em', 13 | label: 'Authors', 14 | getValue(authority) { 15 | const creators = source.creators(authority.source) 16 | , contributors = source.contributors(authority.source) 17 | 18 | const list = creators.length ? creators : contributors 19 | 20 | return list.length 21 | ? contributorList.asString(list) 22 | : h(InlineText, { 23 | color: 'gray', 24 | }, '(not given)') 25 | }, 26 | }, 27 | 28 | yearPublished: { 29 | width: '6em', 30 | label: 'Year published', 31 | getValue(authority) { 32 | return source.yearPublished(authority.source) 33 | }, 34 | }, 35 | 36 | numPeriods: { 37 | width: '6em', 38 | label: 'Periods', 39 | getValue(authority) { 40 | return Object.keys(authority.periods).length 41 | }, 42 | render(value) { 43 | return h(Text, { 44 | width: '4ch', 45 | textAlign: 'right', 46 | }, value) 47 | }, 48 | }, 49 | 50 | title: { 51 | width: '25em', 52 | label: 'Title', 53 | getValue(authority) { 54 | 55 | return source.title(authority.source) 56 | }, 57 | }, 58 | } 59 | 60 | module.exports = ListBlock({ 61 | label: 'Authority List', 62 | description: 'Selectable list of period authorities.', 63 | emptyMessage: 'No authorities with matching sources', 64 | itemViewRoute(item, { backend }) { 65 | return Route('authority-view', { 66 | backendID: backend.asIdentifier(), 67 | authorityID: item.id, 68 | }) 69 | }, 70 | itemEditRoute(item, { backend }) { 71 | return Route('authority-edit', { 72 | backendID: backend.asIdentifier(), 73 | authorityID: item.id, 74 | }) 75 | }, 76 | defaultOpts: { 77 | limit: 10, 78 | start: 0, 79 | selected: [], 80 | sortBy: 'title', 81 | shownColumns: [ 'title', 'authors', 'yearPublished', 'numPeriods' ], 82 | }, 83 | columns, 84 | }) 85 | -------------------------------------------------------------------------------- /modules/periodo-app/src/layouts/authorities/AuthorityPeriodDetail.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , { Flex, Box } = require('periodo-ui') 5 | , AuthorityDetail = require('./AuthorityDetail').Component 6 | , PeriodDetail = require('./PeriodDetail').Component 7 | 8 | function AuthorityPeriodDetail({ 9 | isHovering, 10 | hoveredPeriod, 11 | selectedPeriod, 12 | backend, 13 | data, 14 | }) { 15 | let showPeriod = null 16 | 17 | if (isHovering && hoveredPeriod) { 18 | showPeriod = hoveredPeriod 19 | } else if (selectedPeriod && data.includes(selectedPeriod)) { 20 | showPeriod = selectedPeriod 21 | } else if (hoveredPeriod) { 22 | showPeriod = hoveredPeriod 23 | } 24 | 25 | if (!showPeriod) { 26 | return h(Box, { mt: -3 }) 27 | } 28 | 29 | return ( 30 | h(Flex, [ 31 | h(AuthorityDetail, { 32 | flex: 0.5, 33 | mr: 3, 34 | period: showPeriod, 35 | backend, 36 | }), 37 | h(PeriodDetail, { 38 | flex: 0.5, 39 | period: showPeriod, 40 | backend, 41 | }), 42 | ]) 43 | ) 44 | } 45 | 46 | module.exports = { 47 | label: 'Authority and period details', 48 | description: 'Details of a hovered or focused period', 49 | Component: AuthorityPeriodDetail, 50 | } 51 | -------------------------------------------------------------------------------- /modules/periodo-app/src/layouts/authorities/AuthoritySearch/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , React = require('react') 5 | , { InputBlock } = require('periodo-ui') 6 | , { textMatcher, authority: { displayTitle }} = require('periodo-utils') 7 | 8 | class Search extends React.Component { 9 | 10 | render() { 11 | const { data, opts, updateOpts } = this.props 12 | , { text } = opts 13 | 14 | if (data.length <= 10) { 15 | return null // no reason to search over < 10 authorities 16 | } 17 | 18 | return h(InputBlock, { 19 | name: 'filter', 20 | label: 'Filter authorities by source', 21 | helpText: 'Show authorities with matching sources', 22 | placeholder: 'e.g. library', 23 | value: text || '', 24 | onChange: e => { 25 | updateOpts({ 26 | ...opts, 27 | text: e.target.value, 28 | }, true) 29 | }, 30 | }) 31 | } 32 | } 33 | 34 | module.exports = { 35 | label: 'Authority search', 36 | description: 'Search for authorties by source', 37 | makeFilter(opts) { 38 | const text = opts && opts.text 39 | if (!text) return null 40 | 41 | const test = textMatcher(text) 42 | 43 | return authority => test(displayTitle(authority)) 44 | }, 45 | Component: Search, 46 | } 47 | -------------------------------------------------------------------------------- /modules/periodo-app/src/layouts/authorities/CoverageMap/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , React = require('react') 5 | , { HelpText, InlineText, LabeledMap } = require('periodo-ui') 6 | 7 | const featuresOf = (period, gazetteers) => { 8 | if (!period) return [] 9 | return (period.spatialCoverage || []) 10 | .map(({ id }) => gazetteers.find(id)) 11 | .filter(feature => feature && feature.id) 12 | } 13 | 14 | const allFeatures = (periods, gazetteers) => Object.values( 15 | periods.reduce( 16 | (featuresById, period) => { 17 | featuresOf(period, gazetteers) 18 | .forEach(feature => featuresById[feature.id] = feature) 19 | return featuresById 20 | }, 21 | {} 22 | ) 23 | ) 24 | 25 | class CoverageMap extends React.Component { 26 | shouldComponentUpdate(nextProps) { 27 | return ( 28 | nextProps.data !== this.props.data || 29 | nextProps.selectedPeriod !== this.props.selectedPeriod || 30 | nextProps.gazetteers !== this.props.gazetteers 31 | ) 32 | } 33 | 34 | render() { 35 | const { 36 | data: periods, 37 | selectedPeriod, 38 | gazetteers, 39 | } = this.props 40 | 41 | const showSelectedPeriod = !!selectedPeriod && periods.includes(selectedPeriod) 42 | 43 | return ( 44 | h('div', [ 45 | h(HelpText, { key: 'help' }, [ 46 | 'Places covered by the listed periods', 47 | ...(showSelectedPeriod 48 | ? [ 49 | ', with the selected period in ', 50 | h(InlineText, { color: '#ff0000' }, 'red'), 51 | ] 52 | : [] 53 | ), 54 | '. Use mouse or touch to pan', 55 | ]), 56 | 57 | h(LabeledMap, { 58 | key: 'map', 59 | focusedFeatures: showSelectedPeriod 60 | ? featuresOf(selectedPeriod, gazetteers) 61 | : [], 62 | features: allFeatures(periods, gazetteers), 63 | }), 64 | ]) 65 | ) 66 | } 67 | } 68 | 69 | module.exports = { 70 | label: 'Spatial coverage map', 71 | description: 'WebGL map showing spatial coverage of selected periods', 72 | Component: CoverageMap, 73 | } 74 | -------------------------------------------------------------------------------- /modules/periodo-app/src/layouts/authorities/PeriodDetail.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , { Period, Heading, Box, Link } = require('periodo-ui') 5 | , { Route } = require('org-shell') 6 | , { period: { authorityOf }} = require('periodo-utils') 7 | 8 | function PeriodDetail({ 9 | period, 10 | gazetteers, 11 | backend, 12 | ...props 13 | }) { 14 | const authority = period && authorityOf(period) 15 | , editable = backend.asIdentifier().startsWith('local-') 16 | 17 | return h(Box, { 18 | style: { 19 | minHeight: 600, 20 | wordBreak: 'break-word', 21 | }, 22 | ...props, 23 | }, period == null ? null : [ 24 | h(Heading, { 25 | level: 4, 26 | fontSize: 2, 27 | style: { 28 | display: 'flex', 29 | alignItems: 'center', 30 | borderBottom: '1px solid #ddd', 31 | paddingBottom: '4px', 32 | }, 33 | }, [ 34 | 'Period', 35 | 36 | h(Link, { 37 | ml: 2, 38 | fontSize: 1, 39 | fontWeight: 100, 40 | style: { 41 | display: 'flex', 42 | }, 43 | route: new Route('period-view', { 44 | backendID: backend.asIdentifier(), 45 | authorityID: authority.id, 46 | periodID: period.id, 47 | }), 48 | }, [ 49 | 'view', 50 | ]), 51 | 52 | editable ? h(Link, { 53 | ml: 2, 54 | fontSize: 1, 55 | fontWeight: 100, 56 | route: new Route('period-edit', { 57 | backendID: backend.asIdentifier(), 58 | authorityID: authority.id, 59 | periodID: period.id, 60 | }), 61 | }, [ 62 | 'edit', 63 | ]) : null, 64 | 65 | ]), 66 | 67 | h(Period, { 68 | value: period, 69 | gazetteers, 70 | }), 71 | ]) 72 | } 73 | 74 | module.exports = { 75 | label: 'Period detail', 76 | description: 'Details of a hovered or focused period', 77 | Component: PeriodDetail, 78 | } 79 | -------------------------------------------------------------------------------- /modules/periodo-app/src/layouts/authorities/PlaceFilter.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , { Box, Label, PlacesSelect, HelpText } = require('periodo-ui') 5 | , { RandomID } = require('periodo-common') 6 | 7 | const indexById = items => items.reduce( 8 | (index, item) => ({ 9 | ...index, 10 | [item.id]: item, 11 | }), 12 | {} 13 | ) 14 | 15 | const accepts = (filter, item) => { 16 | if (!filter) return true 17 | return (item && 18 | item.id && 19 | Object.prototype.hasOwnProperty.call(filter, item.id)) 20 | } 21 | 22 | const PlaceFilter = ({ 23 | opts, 24 | updateOpts, 25 | gazetteers, 26 | randomID, 27 | }) => { 28 | 29 | const { filter } = opts 30 | const inputID = randomID('place-filter') 31 | 32 | return ( 33 | h(Box, [ 34 | h(Label, { htmlFor: inputID }, 'By place'), 35 | 36 | h(HelpText, 'Show periods linked to any of the selected places'), 37 | 38 | h(PlacesSelect, { 39 | onChange: places => updateOpts({ 40 | ...opts, 41 | filter: places.length > 0 ? indexById(places) : null, 42 | }, true), 43 | coverage: filter ? Object.values(filter) : [], 44 | gazetteers, 45 | closable: true, 46 | inputProps: { id: inputID }, 47 | }), 48 | ]) 49 | ) 50 | } 51 | 52 | module.exports = { 53 | label: 'Place filter', 54 | description: 'Set an optional filter for specific places', 55 | makeFilter(opts) { 56 | 57 | const filter = opts && opts.filter 58 | 59 | if (!filter) return null 60 | 61 | return period => { 62 | if (!period.spatialCoverage) return false 63 | for (const place of period.spatialCoverage) { 64 | if (accepts(filter, place)) return true 65 | } 66 | return false 67 | } 68 | }, 69 | Component: RandomID(PlaceFilter), 70 | } 71 | -------------------------------------------------------------------------------- /modules/periodo-app/src/layouts/authorities/Statistics/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , { Box, Text } = require('periodo-ui') 5 | , { period: { authorityOf }} = require('periodo-utils') 6 | 7 | function countAuthorities(data) { 8 | const s = new Set() 9 | 10 | data.forEach(period => { 11 | s.add(authorityOf(period)) 12 | }) 13 | 14 | return s.size 15 | } 16 | 17 | module.exports = { 18 | label: 'Statistics', 19 | description: 'Simple stastics about the dataset.', 20 | Component: props => 21 | h(Box, [ 22 | h(Box, [ 23 | h(Text, `There are ${props.data.length} periods in ${countAuthorities(props.data)} authorities`), 24 | ]), 25 | ]), 26 | } 27 | -------------------------------------------------------------------------------- /modules/periodo-app/src/layouts/authorities/TimeFilter.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , React = require('react') 5 | , { Box, Label, HelpText, TimeSlider } = require('periodo-ui') 6 | , { terminus: { earliestYear }} = require('periodo-utils') 7 | 8 | class TimeFilter extends React.Component { 9 | 10 | constructor (props) { 11 | super(props) 12 | 13 | const defaultYearRange = TimeSlider.getDefaultYearRange() 14 | , { defaultYearRangeStart=null } = props 15 | 16 | this.defaultYearRange = ( 17 | defaultYearRangeStart === null 18 | ? defaultYearRange 19 | : [ defaultYearRangeStart, defaultYearRange[1] ] 20 | ) 21 | } 22 | 23 | componentDidMount () { 24 | this.props.setBlockState({ defaultYearRange: this.defaultYearRange }) 25 | } 26 | 27 | render () { 28 | 29 | const { opts, updateOpts } = this.props 30 | , yearRange = opts.yearRange || this.defaultYearRange 31 | 32 | return ( 33 | h(Box, [ 34 | h(Label, 'By time'), 35 | 36 | h(HelpText, 'Show periods that start within the selected range'), 37 | 38 | h(TimeSlider, { 39 | yearRange, 40 | onChange: yearRange => { 41 | updateOpts({ 42 | ...opts, 43 | yearRange, 44 | }, true) 45 | }, 46 | }), 47 | ]) 48 | ) 49 | } 50 | } 51 | 52 | module.exports = { 53 | label: 'Time filter', 54 | description: 'Filter periods by specifying a temporal range', 55 | makeFilter(opts, state) { 56 | 57 | const yearRange = opts.yearRange || state.defaultYearRange || null 58 | 59 | if (! yearRange) return null 60 | 61 | return period => { 62 | if (period.start == undefined) return false 63 | const earliestStart = earliestYear(period.start) 64 | return ( 65 | earliestStart != null && 66 | earliestStart >= yearRange[0] && 67 | earliestStart <= yearRange[1] 68 | ) 69 | } 70 | }, 71 | Component: TimeFilter, 72 | } 73 | -------------------------------------------------------------------------------- /modules/periodo-app/src/layouts/authorities/Timeline/Bars.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = class BarVisualization { 4 | update({ 5 | xScale, 6 | yScale, 7 | group, 8 | periodsWithEndpoints, 9 | }) { 10 | 11 | const width = d => { 12 | let w = xScale(d.latest) - xScale(d.earliest) 13 | 14 | if (w < 1) w = 1; 15 | 16 | return w; 17 | } 18 | 19 | const height = () => { 20 | const h = yScale(periodsWithEndpoints.length - .85) 21 | 22 | return h >= 1 ? h : 1; 23 | } 24 | 25 | const x = d => xScale(d.earliest) 26 | , y = (d, i) => yScale(i + 1) 27 | 28 | const fill = d => d.selected ? '#ff000080' : '#bbb' 29 | 30 | group.selectAll('rect').remove() 31 | 32 | group 33 | .selectAll('rect') 34 | .data(periodsWithEndpoints, d => d.period.id) 35 | .enter() 36 | .append('rect') 37 | .attr('x', x) 38 | .attr('y', y) 39 | .attr('width', width) 40 | .attr('fill', fill) 41 | .attr('height', height) 42 | .style('opacity', 1) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /modules/periodo-app/src/layouts/authorities/Timeline/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , React = require('react') 5 | , AutoSizer = require('react-virtualized-auto-sizer').default 6 | , { HelpText, InlineText } = require('periodo-ui') 7 | , Plot = require('./Plot') 8 | 9 | class Timeline extends React.Component { 10 | shouldComponentUpdate(nextProps) { 11 | return ( 12 | nextProps.data !== this.props.data || 13 | nextProps.selectedPeriod !== this.props.selectedPeriod || 14 | nextProps.opts.visualization !== this.props.opts.visualization 15 | ) 16 | } 17 | 18 | render() { 19 | const { 20 | updateOpts, 21 | dataset, 22 | data, 23 | selectedPeriod, 24 | opts: { visualization, height }, 25 | } = this.props 26 | 27 | const showSelectedPeriod = !!selectedPeriod && data.includes(selectedPeriod) 28 | 29 | return ( 30 | h('div', [ 31 | h(HelpText, { key: 'help' }, [ 32 | 'Temporal extents covered by the listed periods', 33 | ...(showSelectedPeriod 34 | ? [ 35 | ', with the selected period in ', 36 | h(InlineText, { color: '#ff0000' }, 'red'), 37 | ] 38 | : [] 39 | ), 40 | ]), 41 | 42 | h(AutoSizer, { 43 | key: 'timeline', 44 | style: { height: 234 }, 45 | }, [ 46 | ({ width }) => h(Plot, { 47 | width, 48 | height, 49 | updateOpts, 50 | visualization, 51 | dataset, 52 | data, 53 | selectedPeriod: showSelectedPeriod ? selectedPeriod : null, 54 | }), 55 | ]), 56 | ]) 57 | ) 58 | } 59 | } 60 | 61 | module.exports = { 62 | label: 'Temporal coverage visualization', 63 | description: 'd3 timeline showing temporal coverage of selected periods', 64 | Component: Timeline, 65 | } 66 | -------------------------------------------------------------------------------- /modules/periodo-app/src/layouts/authorities/blocks.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'period-search': require('./PeriodSearch'), 3 | 'authority-search': require('./AuthoritySearch'), 4 | 'dataset-statistics': require('./Statistics'), 5 | 'windowed-period-list': require('./WindowedPeriodList'), 6 | 'authority-list': require('./AuthorityList'), 7 | 'timespan-visualization': require('./Timeline'), 8 | 'spatial-visualization': require('./CoverageMap'), 9 | 'place-filter': require('./PlaceFilter'), 10 | 'time-filter': require('./TimeFilter'), 11 | 'facets': require('./Facets'), 12 | 'period-detail': require('./PeriodDetail'), 13 | 'authority-detail': require('./AuthorityDetail'), 14 | 'authority-period-detail': require('./AuthorityPeriodDetail'), 15 | } 16 | -------------------------------------------------------------------------------- /modules/periodo-app/src/layouts/authorities/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , R = require('ramda') 5 | , { useState } = require('react') 6 | , LayoutRenderer = require('../LayoutRenderer') 7 | , { Navigable } = require('org-shell') 8 | , blocks = require('./blocks') 9 | 10 | module.exports = Navigable(({ 11 | data, 12 | fixedPeriod, 13 | 14 | shellOpts, 15 | updateShellOpts, 16 | 17 | dataset, 18 | backend, 19 | navigateTo, 20 | defaultYearRangeStart, 21 | gazetteers, 22 | ...props 23 | }) => { 24 | let selectedPeriod = null 25 | 26 | if (fixedPeriod) { 27 | selectedPeriod = fixedPeriod 28 | } else if (shellOpts.selectedPeriod) { 29 | selectedPeriod = dataset.periodByID(shellOpts.selectedPeriod) 30 | } 31 | 32 | const [ hoveredPeriod, setHoveredPeriod ] = useState(null) 33 | , [ isHovering, setIsHovering ] = useState(false) 34 | 35 | const setSelectedPeriod = period => { 36 | updateShellOpts( 37 | period 38 | ? R.assoc('selectedPeriod', period.id) 39 | : R.dissoc('selectedPeriod'), 40 | true) 41 | } 42 | 43 | return ( 44 | h(LayoutRenderer, { 45 | ...props, 46 | blocks, 47 | data, 48 | extraProps: { 49 | backend, 50 | dataset, 51 | totalCount: data ? data.length : 0, 52 | defaultYearRangeStart, 53 | gazetteers, 54 | navigateTo, 55 | 56 | isFixed: !!fixedPeriod, 57 | 58 | isHovering, 59 | setIsHovering, 60 | 61 | hoveredPeriod, 62 | setHoveredPeriod, 63 | 64 | selectedPeriod, 65 | setSelectedPeriod, 66 | }, 67 | }) 68 | ) 69 | }) 70 | -------------------------------------------------------------------------------- /modules/periodo-app/src/layouts/process_layout.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , R = require('ramda') 5 | , parseLayout = require('./parser') 6 | 7 | // Take an object full of definitions of blocks, and the specification of a 8 | // layout, and return a normalized representation. 9 | module.exports = function processLayout(blockDefs, layoutString) { 10 | const parsedLayout = parseLayout(layoutString) 11 | 12 | return R.pipe( 13 | R.over( 14 | R.lensProp('blocks'), 15 | R.map(({ 16 | id, 17 | type, 18 | section, 19 | opts, 20 | }) => { 21 | const { 22 | Component=() => h('div', { 23 | style: { 24 | backgroundColor: 'red', 25 | }, 26 | }, `No such block type: ${type}`), 27 | makeFilter=null, 28 | processOpts=R.defaultTo({}, R.identity), 29 | defaultOpts={}, 30 | keepMounted=false, 31 | } = (blockDefs[type] || {}) 32 | 33 | return { 34 | id, 35 | type, 36 | section, 37 | block: { 38 | Component, 39 | makeFilter, 40 | processOpts, 41 | keepMounted, 42 | }, 43 | defaultOpts: { 44 | ...defaultOpts, 45 | ...opts, 46 | }, 47 | } 48 | }) 49 | ) 50 | )(parsedLayout) 51 | } 52 | -------------------------------------------------------------------------------- /modules/periodo-app/src/layouts/test/parser.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const test = require('blue-tape') 4 | , parse = require('../parser') 5 | 6 | // TODO: Whitelist properties for particular layouts? Or not... 7 | 8 | test('Parsing specification', async t => { 9 | const spec = ` 10 | prop1 = foo 11 | prop2 = bar 12 | 13 | [Block1] 14 | type = List 15 | grid-column = 1/2 16 | 17 | [Block2] 18 | type = Graph 19 | grid-column = 2/3 20 | block-prop = baz 21 | ` 22 | 23 | t.deepEqual(parse(spec), { 24 | opts: { 25 | prop1: 'foo', 26 | prop2: 'bar', 27 | }, 28 | blocks: [ 29 | { 30 | id: 'Block1', 31 | type: 'List', 32 | gridColumn: '1/2', 33 | }, 34 | 35 | { 36 | id: 'Block2', 37 | type: 'Graph', 38 | gridColumn: '2/3', 39 | opts: { 40 | blockProp: 'baz', 41 | }, 42 | }, 43 | ], 44 | }, 'should parse a spec string') 45 | 46 | t.deepEqual(parse(` 47 | [] 48 | type = test 49 | `), { 50 | blocks: [ 51 | { 52 | id: '0', 53 | type: 'test', 54 | }, 55 | ], 56 | }, 'should allow blank block names') 57 | 58 | }) 59 | -------------------------------------------------------------------------------- /modules/periodo-app/src/linked-data/AsyncRequestor.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , React = require('react') 5 | , { ReadyState } = require('org-async-actions') 6 | 7 | const emptyState = () => ({ 8 | readyState: null, 9 | onNextCompletion: [], 10 | }) 11 | 12 | module.exports = function makeAsyncRequestor(Component) { 13 | return class AsyncRequestor extends React.Component { 14 | constructor() { 15 | super(); 16 | 17 | this.curReq = 0; 18 | this.state = emptyState() 19 | } 20 | 21 | onNextCompletion(fn) { 22 | this.setState(prev => ({ 23 | onNextCompletion: [ ...prev.onNextCompletion, fn ], 24 | })) 25 | } 26 | 27 | doRequest(fetchFn, ...opts) { 28 | const { curReq } = this 29 | , { readyState } = this.state 30 | 31 | if (readyState) { 32 | throw new Error( 33 | 'Cancel any pending requests before sending another.' 34 | ); 35 | } 36 | 37 | this.setState({ 38 | readyState: ReadyState.Pending, 39 | }) 40 | 41 | fetchFn(...opts).then( 42 | resp => { 43 | if (curReq !== this.curReq) return; 44 | 45 | this.state.onNextCompletion.forEach(fn => { 46 | fn(null, resp); 47 | }) 48 | 49 | this.setState({ 50 | readyState: ReadyState.Success(resp), 51 | onNextCompletion: [], 52 | }) 53 | }, 54 | 55 | err => { 56 | if (curReq !== this.curReq) return; 57 | 58 | this.state.onNextCompletion.forEach(fn => { 59 | fn(err) 60 | }) 61 | 62 | this.setState({ 63 | readyState: ReadyState.Failure(err), 64 | onNextCompletion: [], 65 | }) 66 | }) 67 | } 68 | 69 | render () { 70 | return h(Component, { 71 | ...this.props, 72 | ...this.state, 73 | clearRequest: (cb) => { 74 | this.curReq += 1; 75 | this.setState({ readyState: null }, cb) 76 | }, 77 | 78 | doRequest: this.doRequest.bind(this), 79 | onNextCompletion: this.onNextCompletion.bind(this), 80 | }); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /modules/periodo-app/src/linked-data/ns.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const ns = require('lov-ns') 4 | , { nsExpander } = require('org-n3-utils') 5 | 6 | 7 | module.exports = nsExpander({ 8 | activity: 'https://www.w3.org/ns/activitystreams#', 9 | skos: ns.skos, 10 | dc: ns.dcterms, 11 | foaf: ns.foaf, 12 | time: ns.time, 13 | xsd: ns.xsd, 14 | owl: ns.owl, 15 | bibo: ns.bibo, 16 | rdfs: ns.rdfs, 17 | prov: ns.prov, 18 | periodo: 'http://n2t.net/ark:/99152/p0v#', 19 | }) 20 | -------------------------------------------------------------------------------- /modules/periodo-app/src/linked-data/reducer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const R = require('ramda') 4 | , LinkedDataAction = require('./actions') 5 | 6 | const initialState = () => ({ 7 | infoByORCID: {}, 8 | sources: {}, 9 | }) 10 | 11 | module.exports = function linkedData(state=initialState(), action) { 12 | if(!Object.prototype.isPrototypeOf.call(LinkedDataAction.prototype, action.type)) { 13 | return state 14 | } 15 | 16 | return action.readyState.case({ 17 | Success: resp => action.type.case({ 18 | FetchLinkedData() { 19 | return state 20 | }, 21 | 22 | FetchSource() { 23 | return state 24 | }, 25 | 26 | FetchORCIDs() { 27 | const { infoByORCID } = resp 28 | 29 | return R.over( 30 | R.lensProp('infoByORCID'), 31 | R.flip(R.mergeRight)(infoByORCID), 32 | state 33 | ) 34 | }, 35 | 36 | ClearLinkedDataCache() { 37 | return {} 38 | }, 39 | }), 40 | _: () => state, 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /modules/periodo-app/src/linked-data/test/source.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const test = require('blue-tape') 4 | , N3 = require('n3') 5 | , ns = require('../ns') 6 | , makeSourceRepr = require('../utils/make_source_repr') 7 | 8 | test('Generating a textual representation of a graph with a document', async t => { 9 | const store = new N3.Store() 10 | 11 | const expand = ns.withPrefixes({ 12 | '': 'http://example.com/', 13 | }) 14 | 15 | store.addQuad( 16 | expand(':source'), 17 | expand('dc:title'), 18 | '"Computation and Human Experience"') 19 | 20 | store.addQuad( 21 | expand(':source'), 22 | expand(':irrelevant'), 23 | expand(':statement')) 24 | 25 | t.deepEqual(makeSourceRepr(store, expand(':source').id), { 26 | id: 'http://example.com/source', 27 | title: 'Computation and Human Experience', 28 | }) 29 | 30 | store.addQuad( 31 | expand(':source'), 32 | expand('dc:creator'), 33 | expand(':auth1')) 34 | 35 | store.addQuad( 36 | expand(':auth1'), 37 | expand('foaf:name'), 38 | '"Philip Agre"') 39 | 40 | t.deepEqual(makeSourceRepr(store, expand(':source').id), { 41 | id: expand(':source').id, 42 | title: 'Computation and Human Experience', 43 | creators: [ 44 | { 45 | id: expand(':auth1').id, 46 | name: 'Philip Agre', 47 | }, 48 | ], 49 | }) 50 | }) 51 | -------------------------------------------------------------------------------- /modules/periodo-app/src/linked-data/test/source_ld_match.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const test = require('blue-tape') 4 | , { match, asURL } = require('../utils/source_ld_match') 5 | 6 | test('Parsing WorldCat URLs', async t => { 7 | 8 | t.equal(asURL(match("http://worldcat.org/oclc/228741004")), "http://experiment.worldcat.org/oclc/228741004.ttl") 9 | t.equal(asURL(match("http://www.worldcat.org/oclc/228741004")), "http://experiment.worldcat.org/oclc/228741004.ttl") 10 | t.equal(asURL(match("https://worldcat.org/en/title/228741004")), "http://experiment.worldcat.org/oclc/228741004.ttl") 11 | t.equal(asURL(match("https://worldcat.org/ja/title/228741004")), "http://experiment.worldcat.org/oclc/228741004.ttl") 12 | t.equal(asURL(match("https://worldcat.org/oclc/228741004")), "http://experiment.worldcat.org/oclc/228741004.ttl") 13 | t.equal(asURL(match("https://worldcat.org/title/228741004")), "http://experiment.worldcat.org/oclc/228741004.ttl") 14 | t.equal(asURL(match("https://www.worldcat.org/oclc/228741004")), "http://experiment.worldcat.org/oclc/228741004.ttl") 15 | }) 16 | 17 | test('Parsing DOIs', async t => { 18 | 19 | t.equal(asURL(match("http://doi.org/10.1038/s41559-017-0138")), "https://doi.org/10.1038/s41559-017-0138") 20 | t.equal(asURL(match("http://dx.doi.org/10.1038/s41559-017-0138")), "https://doi.org/10.1038/s41559-017-0138") 21 | t.equal(asURL(match("https://doi.org/10.1038/s41559-017-0138")), "https://doi.org/10.1038/s41559-017-0138") 22 | t.equal(asURL(match("https://dx.doi.org/10.1038/s41559-017-0138")), "https://doi.org/10.1038/s41559-017-0138") 23 | }) 24 | -------------------------------------------------------------------------------- /modules/periodo-app/src/linked-data/utils/data.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | function asJSONLD(data) { 4 | return { 5 | '@context': require('../context'), 6 | ...data, 7 | } 8 | } 9 | 10 | module.exports = { 11 | asJSONLD, 12 | } 13 | -------------------------------------------------------------------------------- /modules/periodo-app/src/linked-data/utils/format_url.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { corsProxyURL, corsProxyEnabled } = require('../../globals') 4 | 5 | const CORS_PROXY_SITES = /(?:(?:\Wworldcat.org)|(?:\Worcid\.org))\// 6 | 7 | module.exports = function formatURL(url) { 8 | const useProxy = ( 9 | corsProxyEnabled && 10 | url.indexOf(corsProxyURL) === -1 && 11 | CORS_PROXY_SITES.test(url) 12 | ) 13 | 14 | return useProxy 15 | ? corsProxyURL + url 16 | : url 17 | } 18 | -------------------------------------------------------------------------------- /modules/periodo-app/src/linked-data/utils/generate_skolem_id.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const url = require('url') 4 | 5 | module.exports = function (protocol=location.protocol, host=location.host, length=32) { 6 | let hexstr = ''; 7 | 8 | for (let i = 0; i < length; i++) { 9 | hexstr += Math.floor(Math.random() * 16).toString(16); 10 | } 11 | 12 | return url.format({ 13 | protocol, 14 | host, 15 | pathname: '/.well-known/genid/' + hexstr, 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /modules/periodo-app/src/linked-data/utils/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | patch: require('./patch'), 5 | } 6 | -------------------------------------------------------------------------------- /modules/periodo-app/src/linked-data/utils/is_periodo_server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { isDataset } = require('periodo-utils').dataset 4 | 5 | module.exports = async function isPeriodoServer(rootURL) { 6 | const headers = new Headers({ 'Content-Type': 'application/ld+json' }) 7 | 8 | let rootObj 9 | , datasetObj 10 | 11 | const rootResp = await fetch(rootURL, { headers }) 12 | 13 | if (!rootResp.ok) return false 14 | 15 | try { 16 | rootObj = await rootResp.json() 17 | } catch (e) { 18 | return false 19 | } 20 | 21 | const datasetURL = rootObj.dataset.url 22 | 23 | if (!datasetURL) return false 24 | 25 | const datasetResp = await fetch(datasetURL) 26 | 27 | if (!datasetResp.ok) return false; 28 | 29 | try { 30 | datasetObj = await datasetResp.json() 31 | } catch (e) { 32 | return false 33 | } 34 | 35 | return isDataset(datasetObj) 36 | } 37 | -------------------------------------------------------------------------------- /modules/periodo-app/src/linked-data/utils/make_source_repr.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const ns = require('../ns') 4 | 5 | const expand = ns.withPrefixes({ 6 | schema: 'http://schema.org/', 7 | }) 8 | 9 | 10 | const sourceFields = { 11 | title: [ 12 | expand('dc:title'), 13 | expand('schema:name'), 14 | ], 15 | yearPublished: [ 16 | expand('dc:date'), 17 | expand('schema:datePublished'), 18 | ], 19 | creators: [ 20 | expand('dc:creator'), 21 | expand('schema:creator'), 22 | expand('schema:author'), 23 | ], 24 | contributors: [ 25 | expand('dc:contributor'), 26 | expand('schema:contributor'), 27 | expand('schema:editor'), 28 | ], 29 | } 30 | 31 | const contributorFields = { 32 | name: [ 33 | expand('foaf:name'), 34 | expand('schema:name'), 35 | ], 36 | } 37 | 38 | 39 | /* 40 | * Parsers for worldcat and citeseer citations. Returns the following 41 | * attributes, if they exist: 42 | * title, yearPublished, creators, contributors, and partOf 43 | */ 44 | 45 | // Given a store and a subject URI, iterate through the candidate predicate 46 | // URIs, and return the triples of the first predicate which matches the pattern 47 | // s-p-? in the store. 48 | function matchFromPredicateList(store, subject, predicates) { 49 | for (const predicate of predicates) { 50 | const match = store.getQuads(subject, predicate) 51 | 52 | if (match.length) return match 53 | } 54 | 55 | return null; 56 | } 57 | 58 | module.exports = function makeSourceRepr(store, sourceNode) { 59 | const source = { id: sourceNode } 60 | 61 | Object.entries(sourceFields).forEach(([ field, preds ]) => { 62 | const quads = matchFromPredicateList(store, sourceNode, preds) 63 | 64 | if (!quads) return; 65 | 66 | if (field !== 'creators' && field !== 'contributors') { 67 | source[field] = quads[0].object.value 68 | return 69 | } 70 | 71 | const agents = quads.map(quad => { 72 | const agent = { id: quad.object.id } 73 | 74 | const nameQuads = matchFromPredicateList( 75 | store, 76 | quad.object, 77 | contributorFields.name 78 | ) 79 | 80 | if (nameQuads) { 81 | agent.name = nameQuads[0].object.value 82 | } 83 | 84 | return agent; 85 | }) 86 | 87 | source[field] = agents 88 | }) 89 | 90 | return source; 91 | } 92 | -------------------------------------------------------------------------------- /modules/periodo-app/src/linked-data/utils/parse_jsonld.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | const N3 = require('n3') 4 | , jsonld = require('jsonld') 5 | 6 | module.exports = async function (doc) { 7 | if (typeof doc === 'string') doc = JSON.parse(doc) 8 | 9 | const store = new N3.Store() 10 | , quads = await jsonld.promises.toRDF(doc) 11 | 12 | const replacements = {} 13 | 14 | quads.forEach(quad => { 15 | Object.values(quad).forEach(term => { 16 | if (term.termType === 'BlankNode') { 17 | term.value = term.value in replacements 18 | ? replacements[term.value] 19 | : (replacements[term.value] = N3.DataFactory.blankNode().id.slice(2)) 20 | } 21 | }) 22 | }) 23 | 24 | store.addQuads(quads) 25 | 26 | return { store } 27 | } 28 | -------------------------------------------------------------------------------- /modules/periodo-app/src/linked-data/utils/skolem_ids.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const R = require('ramda') 4 | 5 | function isSkolemID(id) { 6 | return ( 7 | typeof id === 'string' && 8 | id.indexOf('.well-known/genid/') !== -1 9 | ) 10 | } 11 | 12 | // For object or array `data`, replace all keys/vals in `map` with their 13 | // corresponding values. 14 | function replaceIDs(data, map) { 15 | const mapper = ([ key, val ]) => [ 16 | map[key] || key, 17 | typeof val === 'string' 18 | ? map[val] || val 19 | : replaceIDs(val, map), 20 | ] 21 | 22 | return Array.isArray(data) 23 | ? data.map(val => mapper([ null, val ])[1]) 24 | : R.fromPairs(R.map(mapper, Object.entries(data))) 25 | } 26 | 27 | module.exports = { 28 | isSkolemID, 29 | replaceIDs, 30 | } 31 | -------------------------------------------------------------------------------- /modules/periodo-app/src/linked-data/utils/source_ld_match.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const R = require('ramda') 4 | , doi = require('identifiers-doi') 5 | , Type = require('union-type') 6 | 7 | const worldcatUrlRegex = /worldcat.org\/(?:[a-z]+\/)?(?:oclc|title)\/(\d+).*/i 8 | 9 | const Identifier = Type({ 10 | Worldcat: { 11 | id: String, 12 | }, 13 | 14 | Crossref: { 15 | doi: String, 16 | }, 17 | }) 18 | 19 | // String -> Identifier | null 20 | function match(text='') { 21 | const worldcatMatch = text.match(worldcatUrlRegex) 22 | 23 | if (worldcatMatch) { 24 | return Identifier.Worldcat(worldcatMatch[1]) 25 | } 26 | 27 | const dois = doi.extract(text) 28 | 29 | if (dois.length) { 30 | return Identifier.Crossref(dois[0]) 31 | } 32 | 33 | return null 34 | } 35 | 36 | 37 | function isLinkedData(source) { 38 | return !!match(R.path([ 'id' ], source)) || !!match(R.path([ 'partOf', 'id' ], source) || ''); 39 | } 40 | 41 | 42 | // Identifier -> String 43 | function asURL(identifier) { 44 | return identifier.case({ 45 | //Worldcat: id => `https://www.worldcat.org/oclc/${id}`, 46 | Worldcat: id => `http://experiment.worldcat.org/oclc/${id}.ttl`, 47 | Crossref: doi => `https://doi.org/${doi}`, 48 | }) 49 | } 50 | 51 | function getGraphSubject(url) { 52 | const identifier = match(url) 53 | 54 | if (!identifier) return null 55 | 56 | return identifier.case({ 57 | Worldcat: id => `http://www.worldcat.org/oclc/${id}`, 58 | Crossref: doi => `http://dx.doi.org/${doi}`, 59 | }) 60 | } 61 | 62 | module.exports = { 63 | match, 64 | asURL, 65 | getGraphSubject, 66 | isLinkedData, 67 | } 68 | -------------------------------------------------------------------------------- /modules/periodo-app/src/main/actions.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { makeTypedAction } = require('org-async-actions') 4 | 5 | module.exports = makeTypedAction({ 6 | InitIndexedDB: { 7 | exec: initIndexedDB, 8 | request: {}, 9 | response: {}, 10 | }, 11 | 12 | RequestPersistence: { 13 | exec: requestPersistence, 14 | request: {}, 15 | response: { 16 | isPersisted: Boolean, 17 | }, 18 | }, 19 | 20 | CheckPersistence: { 21 | exec: checkPersistence, 22 | request: {}, 23 | response: { 24 | isPersisted: Boolean, 25 | }, 26 | }, 27 | }) 28 | 29 | const persistenceAPISupported = ( 30 | typeof navigator !== 'undefined' && 31 | 'storage' in navigator && 32 | 'persisted' in navigator.storage 33 | ) 34 | 35 | function initIndexedDB() { 36 | return async (dispatch, getState, { db }) => { 37 | await db.open() 38 | 39 | return {} 40 | } 41 | } 42 | 43 | function requestPersistence() { 44 | return async () => { 45 | let isPersisted = false 46 | 47 | if (persistenceAPISupported) { 48 | isPersisted = await navigator.storage.persisted() 49 | 50 | if (!isPersisted) { 51 | isPersisted = await navigator.storage.persist() 52 | } 53 | } 54 | 55 | return { isPersisted } 56 | } 57 | } 58 | 59 | function checkPersistence() { 60 | return async () => { 61 | let isPersisted = false 62 | 63 | if (persistenceAPISupported) { 64 | isPersisted = await navigator.storage.persisted() 65 | } 66 | 67 | return { isPersisted } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /modules/periodo-app/src/main/components/Header.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , { Flex, Box, Heading } = require('periodo-ui') 5 | , Spinner = require('respin') 6 | 7 | module.exports = ({ showSpinner, ...props }) => 8 | h(Box, { 9 | as: 'header', 10 | sx: { 11 | bg: 'colorsets.bookends.bg', 12 | py: 1, 13 | borderBottomStyle: 'solid', 14 | borderBottomWidth: '1px', 15 | borderBottomColor: 'colorsets.bookends.border', 16 | }, 17 | ...props, 18 | }, [ 19 | h(Flex, { 20 | height: '100%', 21 | alignItems: 'center', 22 | justifyContent: 'space-between', 23 | p: 1, 24 | }, [ 25 | h(Box, [ 26 | h(Heading, { 27 | level: 1, 28 | mx: 3, 29 | fontSize: 3, 30 | }, [ 31 | 32 | h('a', { 33 | href: '', 34 | }, h('img', { 35 | src: 'images/periodo-logo.svg', 36 | alt: 'PeriodO logo', 37 | height: 32, 38 | })), 39 | ]), 40 | ]), 41 | 42 | h(Box, { width: 22 }, [ 43 | showSpinner && h(Spinner, { size: 22 }), 44 | ]), 45 | 46 | h(Box, ' '), 47 | ]), 48 | ]) 49 | -------------------------------------------------------------------------------- /modules/periodo-app/src/main/components/IndexedDBMessage.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , { SectionHeading, Section, Box } = require('periodo-ui') 5 | 6 | module.exports = function IndexedDBMessage() { 7 | return [ 8 | h(SectionHeading, { 9 | key: 'heading', 10 | }, 'Browser incompatible'), 11 | h(Section, { 12 | key: 'message', 13 | }, [ 14 | h(Box, { 15 | as: 'p', 16 | mb: 3, 17 | }, [ 18 | 'Your browser does not support the ', 19 | h('a', { 20 | href: 'https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API', 21 | }, 'IndexedDB'), 22 | ' standard, which PeriodO requires to operate. The most recent versions of all major Web browsers (Firefox, Safari, Chrome, Opera) all support IndexedDB. Please try another browser and reopen PeriodO.', 23 | ]), 24 | 25 | h(Box, { 26 | as: 'p', 27 | mb: 3, 28 | }, [ 29 | '(Note: if you have opened PeriodO in a "private" or "incognito" tab, IndexedDB may not be available. If that is the case, reopen PeriodO in a normal tab).', 30 | ]), 31 | ]), 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /modules/periodo-app/src/main/components/NotFound.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | 5 | const NotFound = () => 6 | h('h1', 'Page not found') 7 | 8 | module.exports = NotFound; 9 | -------------------------------------------------------------------------------- /modules/periodo-app/src/main/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | resources: require('./resources'), 5 | } 6 | -------------------------------------------------------------------------------- /modules/periodo-app/src/main/reducer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const R = require('ramda') 4 | , MainAction = require('./actions') 5 | 6 | function initialState() { 7 | return { 8 | browser: { 9 | indexedDBSupported: null, 10 | isPersisted: null, 11 | }, 12 | } 13 | } 14 | 15 | module.exports = function main(state=initialState(), action) { 16 | if(!Object.prototype.isPrototypeOf.call(MainAction.prototype, action.type)) { 17 | return state 18 | } 19 | 20 | return action.readyState.case({ 21 | Success: resp => action.type.case({ 22 | InitIndexedDB() { 23 | return R.assocPath([ 'browser', 'indexedDBSupported' ], true, state) 24 | }, 25 | 26 | RequestPersistence() { 27 | const { isPersisted } = resp 28 | 29 | return R.assocPath([ 'browser', 'isPersisted' ], isPersisted, state) 30 | }, 31 | 32 | CheckPersistence() { 33 | const { isPersisted } = resp 34 | 35 | return R.assocPath([ 'browser', 'isPersisted' ], isPersisted, state) 36 | }, 37 | }), 38 | 39 | Failure: err => action.type.case({ 40 | InitIndexedDB() { 41 | return R.assocPath([ 'browser', 'indexedDBSupported' ], false, state) 42 | }, 43 | 44 | _: () => state, 45 | }), 46 | 47 | _: () => state, 48 | }) 49 | } 50 | -------------------------------------------------------------------------------- /modules/periodo-app/src/patches/OpenPatches.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , { useState } = require('react') 5 | , { Box, Label } = require('periodo-ui') 6 | , PatchRequestLayout = require('../layouts/patch_requests') 7 | 8 | const layout = ` 9 | [] 10 | type = request-list 11 | ` 12 | 13 | function ReviewPatches(props) { 14 | const { patchRequests, backend } = props 15 | , [ blockOpts, setBlockOpts ] = useState({}) 16 | , [ onlyOpen, setOnlyOpen ] = useState(true) 17 | 18 | let shownPatchRequests = patchRequests 19 | 20 | if (onlyOpen) { 21 | shownPatchRequests = shownPatchRequests.filter(p => p.open) 22 | } 23 | 24 | return ( 25 | h(Box, [ 26 | h(Box, { mb: 1 }, [ 27 | h(Label, { 28 | style: { 29 | display: 'flex', 30 | alignItems: 'center', 31 | }, 32 | }, [ 33 | h('input', { 34 | type: 'checkbox', 35 | checked: onlyOpen, 36 | onChange: () => { 37 | setOnlyOpen(!onlyOpen) 38 | }, 39 | }), 40 | 'Open submissions only', 41 | ]), 42 | ]), 43 | 44 | h(PatchRequestLayout, { 45 | layout, 46 | patchRequests: shownPatchRequests, 47 | backend, 48 | blockOpts, 49 | onBlockOptsChange: setBlockOpts, 50 | }), 51 | ]) 52 | ) 53 | } 54 | 55 | module.exports = ReviewPatches; 56 | -------------------------------------------------------------------------------- /modules/periodo-app/src/patches/patch_collection.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const R = require('ramda') 4 | , { PatchType } = require('./types') 5 | 6 | function groupByChangeType(patches) { 7 | let ret = {} 8 | 9 | patches.forEach(patch => { 10 | const type = PatchType.fromPatch(patch) 11 | 12 | const authorityID = a => [ type._name, a ] 13 | , periodID = (a, b) => [ type._name, a, b ] 14 | , topLevel = () => [ type._name ] 15 | 16 | const path = type.case({ 17 | AddPeriod: authorityID, 18 | RemovePeriod: authorityID, 19 | ChangePeriod: periodID, 20 | ChangeAuthority: authorityID, 21 | _: topLevel, 22 | }) 23 | 24 | ret = R.over( 25 | R.lensPath(path), 26 | (arr=[]) => [ ...arr, patch ], 27 | ret 28 | ) 29 | }) 30 | 31 | return ret 32 | } 33 | 34 | function getOrcids(patches) { 35 | return R.pipe( 36 | R.chain(patch => [].concat( 37 | patch.created_by, 38 | patch.comments.map(comment => comment.author) 39 | )), 40 | R.uniq 41 | )(patches) 42 | } 43 | 44 | 45 | module.exports = { 46 | groupByChangeType, 47 | getOrcids, 48 | } 49 | -------------------------------------------------------------------------------- /modules/periodo-app/src/patches/test/fixtures/dangling-related-period.json: -------------------------------------------------------------------------------- 1 | { 2 | "before": { 3 | "type": "rdf:Bag", 4 | "authorities": { 5 | "p0123": { 6 | "id": "p0123", 7 | "periods": { 8 | } 9 | } 10 | } 11 | }, 12 | 13 | "after": { 14 | "type": "rdf:Bag", 15 | "authorities": { 16 | "p0123": { 17 | "id": "p0123", 18 | "periods": { 19 | "p0123a": { 20 | "id": "p0123a", 21 | "narrower": [ 22 | "p0123b", 23 | "p0123c" 24 | ] 25 | }, 26 | "p0123b": { 27 | "id": "p0123b" 28 | } 29 | } 30 | } 31 | } 32 | }, 33 | 34 | "expected": [ 35 | { 36 | "op": "add", 37 | "path": "/authorities/p0123/periods/p0123a", 38 | "value": { 39 | "id": "p0123a", 40 | "narrower": [ 41 | "p0123b" 42 | ] 43 | } 44 | }, 45 | { 46 | "op": "add", 47 | "path": "/authorities/p0123/periods/p0123b", 48 | "value": { 49 | "id": "p0123b" 50 | } 51 | } 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /modules/periodo-app/src/patches/test/fixtures/multi-label-period.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Progressive Era", 3 | "language": "eng-latn", 4 | "localizedLabels": { 5 | "eng-latn": [ 6 | "Progressive Era", 7 | "The Progressive Era" 8 | ], 9 | "fra-latn": [ 10 | "Ère progressiste" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /modules/periodo-app/src/patches/test/fixtures/source-partof.json: -------------------------------------------------------------------------------- 1 | { 2 | "locator": "pages 256-257", 3 | "partOf": { 4 | "creators": [ 5 | { 6 | "name": "Rijksdienst voor het Cultureel Erfgoed" 7 | } 8 | ], 9 | "dateAccessed": "2014-10-08", 10 | "title": "Het Archeologisch Basisregister (ABR)", 11 | "url": "http://www.cultureelerfgoed.nl/sites/default/files/downloads/dossiers/abr_website2.pdf", 12 | "yearPublished": 1992 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /modules/periodo-app/src/patches/test/fixtures/termini.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "label": "1200", "in": { "year": "1200" }}, 3 | { "label": "0", "in": { "year": "0000" }}, 4 | { "label": "6th century", "in": { "earliestYear": "0501", "latestYear": "0600" }}, 5 | { "label": "one hundred bee cee", "in": { "year": "-0099" }}, 6 | { "label": "present" }, 7 | { "label": "unknown" } 8 | ] 9 | -------------------------------------------------------------------------------- /modules/periodo-app/src/store.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { createStore, applyMiddleware, combineReducers } = require('redux') 4 | , { typedAsyncActionMiddleware } = require('org-async-actions') 5 | , periodoDB = require('./db') 6 | 7 | module.exports = function () { 8 | const db = periodoDB() 9 | 10 | const store = createStore( 11 | combineReducers({ 12 | main: require('./main/reducer'), 13 | backends: require('./backends/reducer'), 14 | auth: require('./auth/reducer'), 15 | linkedData: require('./linked-data/reducer'), 16 | patches: require('./patches/reducer'), 17 | graphs: require('./graphs/reducer'), 18 | }), 19 | applyMiddleware(typedAsyncActionMiddleware({ db })) 20 | ) 21 | 22 | return { 23 | store, 24 | db, 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /modules/periodo-app/src/store_mock.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const FDBFactory = require('fake-indexeddb/lib/FDBFactory') 4 | , configureMockStore = require('redux-mock-store').default 5 | , { typedAsyncActionMiddleware } = require('org-async-actions') 6 | 7 | class MockLocalStorage { 8 | constructor() { 9 | this.data = new Map(); 10 | } 11 | 12 | getItem(name, value) { 13 | return this.data.set(name, value); 14 | } 15 | 16 | setItem(name) { 17 | return this.data.get(name); 18 | } 19 | } 20 | 21 | module.exports = function () { 22 | const mockStorage = { 23 | indexedDB: new FDBFactory(), 24 | IDBKeyRange: require('fake-indexeddb/lib/FDBKeyRange'), 25 | localStorage: new MockLocalStorage(), 26 | } 27 | 28 | return configureMockStore([ 29 | typedAsyncActionMiddleware({ db: require('./db')(mockStorage) }), 30 | ])() 31 | } 32 | -------------------------------------------------------------------------------- /modules/periodo-common/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "periodo-common", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "", 6 | "main": "src/index.js", 7 | "keywords": [], 8 | "author": "", 9 | "license": "ISC" 10 | } 11 | -------------------------------------------------------------------------------- /modules/periodo-common/src/RandomID.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , React = require('react') 5 | 6 | function randomstr() { 7 | return (Math.random() + '').slice(2, 12) 8 | } 9 | 10 | function makeRandomID(Component) { 11 | return class RandomID extends React.Component { 12 | constructor() { 13 | super(); 14 | 15 | const str = randomstr() 16 | this.randomID = identifier => `${identifier}-${str}` 17 | } 18 | 19 | render() { 20 | return h(Component, { 21 | ...this.props, 22 | randomID: this.randomID, 23 | }) 24 | } 25 | } 26 | } 27 | 28 | // TODO: make `useRandomID` hook 29 | 30 | module.exports = { 31 | RandomID: makeRandomID, 32 | } 33 | -------------------------------------------------------------------------------- /modules/periodo-common/src/index.js: -------------------------------------------------------------------------------- 1 | module.exports = {} 2 | 3 | Object.assign(module.exports, 4 | require('./RandomID'), 5 | require('./types') 6 | ) 7 | -------------------------------------------------------------------------------- /modules/periodo-common/src/types.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | const Type = require('union-type') 4 | 5 | exports.Result = Type({ 6 | Ok: [ () => true ], 7 | Err: [ () => true ], 8 | }) 9 | 10 | exports.stripUnionTypeFields = function stripUnionTypeFields(obj, shallow=true) { 11 | if (shallow) { 12 | delete obj._keys; 13 | delete obj._name; 14 | 15 | return obj 16 | } 17 | 18 | return JSON.parse(JSON.stringify(obj), (key, val) => { 19 | if (val && typeof val === 'object') { 20 | delete val._keys; 21 | delete val._name; 22 | } 23 | 24 | return val 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /modules/periodo-date-parser/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "parserOptions": { 4 | "ecmaVersion": 2017 5 | }, 6 | "env": { 7 | "node": true, 8 | "browser": true 9 | }, 10 | "rules": { 11 | "comma-dangle": 0, 12 | "curly": [2, "multi-line"], 13 | "new-cap": 0, 14 | "no-mixed-requires": 0, 15 | "no-underscore-dangle": 0, 16 | "no-use-before-define": [2, "nofunc"], 17 | "no-var": 2, 18 | "object-shorthand": 2, 19 | "prefer-arrow-callback": 2, 20 | "prefer-const": 2, 21 | "prefer-spread": 2, 22 | "quotes": 0, 23 | "semi": 0 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /modules/periodo-date-parser/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | parser.js 3 | -------------------------------------------------------------------------------- /modules/periodo-date-parser/LICENSE: -------------------------------------------------------------------------------- 1 | PeriodO client 2 | 3 | Written in 2014 and 2015 by Patrick Golden and Ryan 4 | Shaw . 5 | 6 | To the extent possible under law, the author(s) have dedicated all copyright 7 | and related and neighboring rights to this software to the public domain 8 | worldwide. This software is distributed without any warranty. 9 | 10 | You should have received a copy of the CC0 Public Domain Dedication along with 11 | this software. If not, see . 12 | -------------------------------------------------------------------------------- /modules/periodo-date-parser/LICENSE-3RD-PARTY: -------------------------------------------------------------------------------- 1 | Trash icon 2 | Created by Edward Boatman, from The Noun Project 3 | Creative Commons – Attribution (CC BY 3.0) 4 | https://thenounproject.com/term/trash/304/ 5 | 6 | Download icon 7 | Created by Edward Boatman, from The Noun Project 8 | Creative Commons – Attribution (CC BY 3.0) 9 | https://thenounproject.com/term/download/433/ 10 | 11 | Error icon 12 | Created by Anas Ramadan, from The Noun Project 13 | Creative Commons - Attribution (CC BY 3.0) 14 | https://thenounproject.com/term/error/2189/ 15 | -------------------------------------------------------------------------------- /modules/periodo-date-parser/README.md: -------------------------------------------------------------------------------- 1 | # PeriodO date parser 2 | Natural language date parsing for historical time periods. 3 | 4 | # Supported year formats 5 | 6 | * [Before Present](https://en.wikipedia.org/wiki/Before_Present) 7 | 8 | Example: *2000BP* 9 | 10 | * [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) 11 | 12 | Example: *5000* 13 | 14 | * [Gregorian](https://en.wikipedia.org/wiki/Gregorian_calendar) 15 | 16 | Example: *1917AD*, *1200BCE* 17 | 18 | * Approximate dates 19 | 20 | Example: *1200B.C.*, *ca. 1923* 21 | 22 | * Date ranges 23 | 24 | Example: *21st century*, *early 14th century*, *1200/1400* 25 | 26 | See the [tests](test/index.js#L15-L151) for more specific examples. 27 | 28 | # Example 29 | 30 | ```javascript 31 | const parser = require('periodo-date-parser') 32 | 33 | // throws parser.SyntaxError if unsuccessful 34 | console.log(parser.parse('200 AD')); 35 | // { _type: 'gregorian', in: { year: '0200' }, label: '200 AD' } 36 | 37 | console.log(parser.parse('middle of the 21st century')); 38 | // { _type: 'gregorian', 39 | // in: { earliestYear: '2034', latestYear: '2067' }, 40 | // label: 'middle of the 21st century' } 41 | ``` 42 | -------------------------------------------------------------------------------- /modules/periodo-date-parser/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "periodo-date-parser", 3 | "version": "1.0.1", 4 | "description": "", 5 | "main": "parser.js", 6 | "scripts": { 7 | "prepublish": "npm run compile", 8 | "compile": "pegjs --allowed-start-rules start,gregorianyear,bpyear,iso8601year -o parser.js grammar.pegjs", 9 | "test": "npm run compile && tape tests.js" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/periodo/periodo-date-parser.git" 14 | }, 15 | "keywords": [], 16 | "author": "", 17 | "license": "CC0-1.0", 18 | "bugs": { 19 | "url": "https://github.com/periodo/periodo-date-parser/issues" 20 | }, 21 | "homepage": "https://github.com/periodo/periodo-date-parser#readme", 22 | "devDependencies": { 23 | "pegjs": "^0.10.0", 24 | "tape": "^5.0.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /modules/periodo-ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "periodo-ui", 3 | "version": "1.1.0", 4 | "private": true, 5 | "description": "", 6 | "main": "src/index.js", 7 | "scripts": { 8 | "test": "tape test/*.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": {}, 14 | "browserify": { 15 | "transform": [ 16 | "glslify" 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /modules/periodo-ui/src/components/Alerts.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , { Box } = require('./Base') 5 | 6 | exports.Alert = ({ variant='default', ...props }) => 7 | h(Box, { 8 | variant, 9 | tx: 'alerts', 10 | sx: { 11 | p: 2, 12 | borderWidth: 1, 13 | borderStyle: 'solid', 14 | fontSize: 1, 15 | fontWeight: 'bold', 16 | }, 17 | ...props, 18 | }) 19 | -------------------------------------------------------------------------------- /modules/periodo-ui/src/components/Authority.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , { DiffableItem, extract } = require('./diffable/Field') 5 | , { LinkValue, PermalinkValue } = require('./diffable/Value') 6 | , { LinkifiedTextValue, DownloadValue } = require('./diffable/Value') 7 | , { Period } = require('./Period') 8 | , { Source } = require('./Source') 9 | 10 | const authorityFields = [ 11 | { 12 | label: 'Permalink', 13 | getValues: extract('id'), 14 | component: PermalinkValue, 15 | required: true, 16 | immutable: true, 17 | }, 18 | 19 | { 20 | label: 'Type', 21 | getValues: extract('type'), 22 | required: true, 23 | immutable: true, 24 | hidden: true, 25 | }, 26 | 27 | { 28 | label: 'Source', 29 | getValues: extract('source'), 30 | component: Source, 31 | nested: true, 32 | required: true, 33 | }, 34 | 35 | { 36 | label: 'Editorial notes', 37 | getValues: extract('editorialNote', { withKey: 'text' }), 38 | component: LinkifiedTextValue, 39 | }, 40 | 41 | { 42 | label: 'Same as', 43 | getValues: extract('sameAs'), 44 | component: LinkValue, 45 | }, 46 | 47 | { 48 | label: 'Download', 49 | getValues: extract('id'), 50 | component: DownloadValue, 51 | required: true, 52 | immutable: true, 53 | includeCSV: true, 54 | }, 55 | ] 56 | 57 | const authorityFieldsWithPeriods = [ 58 | ...authorityFields, 59 | 60 | { 61 | label: 'Periods', 62 | getValues: extract('periods', { indexed: true }), 63 | component: props => ( 64 | h(Period, { 65 | ...props, 66 | m: 1, 67 | borderTop: 'thin solid', 68 | borderColor: 'Gainsboro', 69 | }) 70 | ), 71 | hideUnchanged: true, 72 | }, 73 | ] 74 | 75 | function Authority(props) { 76 | return ( 77 | h(DiffableItem, { 78 | ...props, 79 | fieldList: authorityFields, 80 | }) 81 | ) 82 | } 83 | 84 | function AuthorityWithPeriods(props) { 85 | return ( 86 | h(DiffableItem, { 87 | ...props, 88 | fieldList: authorityFieldsWithPeriods, 89 | }) 90 | ) 91 | } 92 | 93 | module.exports = { 94 | Authority, 95 | AuthorityWithPeriods, 96 | } 97 | -------------------------------------------------------------------------------- /modules/periodo-ui/src/components/BackendContext.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { createContext } = require('react') 4 | 5 | exports.BackendContext = createContext() 6 | -------------------------------------------------------------------------------- /modules/periodo-ui/src/components/Base.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , { Flex, Box, Text } = require('rebass') 5 | , ss = require('styled-system') 6 | , styled = require('@emotion/styled').default 7 | 8 | 9 | const Pre = props => 10 | h(Box, { 11 | as: 'pre', 12 | sx: { 13 | whiteSpace: 'pre-wrap', 14 | fontFamily: 'monospace', 15 | }, 16 | ...props, 17 | }) 18 | 19 | const Grid = styled(Box)( 20 | { display: 'grid' }, 21 | ss.grid 22 | ) 23 | 24 | const tagsForLevel = { 25 | 1: 'h1', 26 | 2: 'h2', 27 | 3: 'h3', 28 | 4: 'h4', 29 | 5: 'h5', 30 | 6: 'h6', 31 | } 32 | 33 | const sizeForLevel = { 34 | 1: '2.2rem', 35 | 2: '1.7rem', 36 | 3: '1.5rem', 37 | 4: '1.1rem', 38 | 5: '0.9rem', 39 | 6: '0.8rem', 40 | } 41 | 42 | const Heading = props => { 43 | const { level=1 } = props 44 | , tag = tagsForLevel[level] 45 | 46 | return ( 47 | h(Text, { 48 | as: tag, 49 | sx: { 50 | fontWeight: 'bold', 51 | fontSize: sizeForLevel[level], 52 | }, 53 | ...props, 54 | }) 55 | ) 56 | } 57 | 58 | const ResourceTitle = props => 59 | h(Heading, { 60 | level: 2, 61 | mb: 3, 62 | ...props, 63 | }) 64 | 65 | const SectionHeading = props => 66 | h(Heading, { 67 | level: 3, 68 | pt: 2, 69 | pb: 1, 70 | color: 'colorsets.primary.fg', 71 | ...props, 72 | }) 73 | 74 | const Section = props => 75 | h(Box, { 76 | className: 'section', 77 | sx: { 78 | p: 3, 79 | mb: 4, 80 | bg: 'colorsets.primary.bg', 81 | color: 'colorsets.primary.fg', 82 | borderStyle: 'solid', 83 | borderWidth: '1px', 84 | borderColor: 'colorsets.primary.border', 85 | }, 86 | ...props, 87 | }) 88 | 89 | const InlineText = props => 90 | h(Text, { 91 | as: 'span', 92 | ...props, 93 | }) 94 | 95 | const MonospaceText = props => 96 | h(InlineText, { 97 | fontFamily: "monospace", 98 | fontSize: "1.1em", 99 | ...props, 100 | }) 101 | 102 | module.exports = { 103 | Box, 104 | Flex, 105 | Grid, 106 | Heading, 107 | Pre, 108 | ResourceTitle, 109 | Section, 110 | SectionHeading, 111 | InlineText, 112 | MonospaceText, 113 | Text, 114 | } 115 | -------------------------------------------------------------------------------- /modules/periodo-ui/src/components/Breadcrumb.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , { Flex, Box } = require('./Base') 5 | , { Link } = require('./Links') 6 | 7 | exports.Breadcrumb = ({ crumbs=[], ...props }) => 8 | h(Flex, { 9 | as: 'ol', 10 | variant: 'menu', 11 | sx: { 12 | py: 2, 13 | px: 3, 14 | mb: 3, 15 | listStyleType: 'none', 16 | mt: theme => (-theme.space[3] - 1) + 'px', 17 | }, 18 | ...props, 19 | }, crumbs.map(({ label, route, truncate }, i) => 20 | h(Box, { 21 | as: 'li', 22 | key: i, 23 | sx: { 24 | display: 'inline-block', 25 | overflow: 'hidden', 26 | textOverflow: 'ellipsis', 27 | whiteSpace: 'nowrap', 28 | textDecoration: 'none', 29 | ...( 30 | truncate ? {} : { flex: '0 0 auto' } 31 | ), 32 | ':not(:first-of-type)': { 33 | '::before': { 34 | content: '"\\203a"', 35 | color: '#999', 36 | margin: '0 8px', 37 | }, 38 | }, 39 | }, 40 | }, [ 41 | !route ? label : ( 42 | h(Link, { 43 | route, 44 | }, label) 45 | ), 46 | ]) 47 | )) 48 | -------------------------------------------------------------------------------- /modules/periodo-ui/src/components/Buttons.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , { Box } = require('./Base') 5 | , { SettingsIcon } = require('./Icons') 6 | 7 | exports.Button = ({ sx, variant='default', ...props }) => 8 | h(Box, { 9 | as: 'button', 10 | tx: 'buttons', 11 | variant, 12 | sx: { 13 | px: 3, 14 | py: 2, 15 | borderWidth: 1, 16 | borderStyle: 'solid', 17 | borderRadius: 4, 18 | fontSize: 1, 19 | fontWeight: 'bold', 20 | cursor: 'pointer', 21 | ':disabled': { 22 | cursor: 'not-allowed', 23 | opacity: .4, 24 | }, 25 | ...sx, 26 | }, 27 | ...props, 28 | }) 29 | 30 | exports.AriaButton = ({ sx, ...props }) => 31 | h(Box, { 32 | as: 'span', 33 | role: 'button', 34 | tabIndex: 0, 35 | sx: { 36 | cursor: 'pointer', 37 | ...sx, 38 | }, 39 | onKeyPress: e => { 40 | if (e.key === ' ') { 41 | e.preventDefault(); 42 | e.stopPropagation(); 43 | } 44 | }, 45 | onKeyUp: e => { 46 | if (e.key === 'Enter' || e.key === ' ') { 47 | props.onSelect(); 48 | } 49 | }, 50 | onClick: props.onSelect, 51 | ...props, 52 | }, props.children) 53 | 54 | exports.LinkButton = props => 55 | h(exports.AriaButton, { 56 | color: 'link', 57 | sx: { 58 | display: 'inline-block', 59 | px: 2, 60 | py: 1, 61 | ':hover': { 62 | textDecoration: 'underline', 63 | }, 64 | }, 65 | ...props, 66 | }) 67 | 68 | exports.SettingsButton = props => 69 | h(exports.Button, { 70 | sx: { 71 | py: 1, 72 | px: 2, 73 | display: 'flex', 74 | alignItems: 'center', 75 | justifyContent: 'center', 76 | }, 77 | ...props, 78 | }, [ 79 | h(SettingsIcon), 80 | ]) 81 | -------------------------------------------------------------------------------- /modules/periodo-ui/src/components/ClientError.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , { Box, Heading, Pre } = require('./Base') 5 | 6 | exports.ClientError = ({ error, ...props }) => 7 | h(Box, { 8 | ...props, 9 | }, [ 10 | h(Heading, { 11 | level: '2', 12 | color: 'red.4', 13 | css: { 'letterSpacing': '4px' }, 14 | }, 'Client error'), 15 | h(Box, { 16 | my: 2, 17 | style: { 18 | fontWeight: 'bold', 19 | fontSize: '16px', 20 | }, 21 | }, [ 22 | error.err.toString(), 23 | ]), 24 | h(Heading, { 25 | level: '4', 26 | mt: 2, 27 | }, 'Error stack'), 28 | h(Pre, { 29 | ml: 2, 30 | }, [ 31 | error.err.stack, 32 | ]), 33 | h(Heading, { 34 | level: '4', 35 | mt: 2, 36 | }, 'Component stack'), 37 | h(Pre, error.info.componentStack.trim()), 38 | ]) 39 | -------------------------------------------------------------------------------- /modules/periodo-ui/src/components/Dataset.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , { DiffableItem, extract } = require('./diffable/Field') 5 | , { JSONLDContextValue } = require('./diffable/Value') 6 | , { Authority } = require('./Authority') 7 | 8 | const datasetFields = [ 9 | { 10 | label: 'Permalink', 11 | getValues: extract('id'), 12 | required: true, 13 | immutable: true, 14 | }, 15 | 16 | { 17 | label: 'Type', 18 | getValues: extract('type'), 19 | required: true, 20 | immutable: true, 21 | hidden: true, 22 | }, 23 | 24 | { 25 | label: 'Context', 26 | getValues: extract('@context', { withKey: 'context' }), 27 | component: JSONLDContextValue, 28 | required: true, 29 | immutable: true, 30 | hidden: true, 31 | }, 32 | 33 | { 34 | label: 'Authorities', 35 | getValues: extract('authorities', { indexed: true }), 36 | component: props => ( 37 | h(Authority, { 38 | ...props, 39 | m: 1, 40 | borderTop: 'thin solid', 41 | borderColor: 'Gainsboro', 42 | }) 43 | ), 44 | hideUnchanged: true, 45 | }, 46 | ] 47 | 48 | function Dataset(props) { 49 | return ( 50 | h(DiffableItem, { 51 | ...props, 52 | fieldList: datasetFields, 53 | }) 54 | ) 55 | } 56 | 57 | module.exports = { Dataset } 58 | -------------------------------------------------------------------------------- /modules/periodo-ui/src/components/Debug.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | 5 | exports.Debug = props => 6 | h('pre', {}, JSON.stringify(props, true, ' ')) 7 | -------------------------------------------------------------------------------- /modules/periodo-ui/src/components/Details.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , { useState, Children } = require('react') 5 | , { Box } = require('./Base') 6 | 7 | function Details({ 8 | summary, 9 | summaryProps={}, 10 | children, 11 | onToggle, 12 | ...props 13 | }) { 14 | const [ open, setOpen ] = useState(props.open || false) 15 | 16 | return ( 17 | h(Box, { 18 | as: 'details', 19 | open, 20 | }, [ 21 | h(Summary, { 22 | ...summaryProps, 23 | onClick: e => { 24 | e.preventDefault() 25 | setOpen(prevOpen => !prevOpen) 26 | if (onToggle) { onToggle() } 27 | }, 28 | }, [ summary ]), 29 | 30 | (typeof children === 'function') 31 | ? children(!open) // hidden: true 32 | : open ? Children.only(children) : null, 33 | ]) 34 | ) 35 | } 36 | 37 | function Summary({ ...props }) { 38 | return ( 39 | h(Box, { 40 | as: 'summary', 41 | sx: { 42 | width: '100%', 43 | color: 'colorsets.page.fg', 44 | cursor: 'pointer', 45 | pl: 1, 46 | pb: 2, 47 | ':hover, :hover *': { 48 | color: 'orange.9', 49 | }, 50 | '::marker, ::-webkit-details-marker': { 51 | color: 'colorsets.page.fg', 52 | }, 53 | }, 54 | ...props, 55 | }) 56 | ) 57 | } 58 | 59 | module.exports = { 60 | Details, 61 | Summary, 62 | } 63 | -------------------------------------------------------------------------------- /modules/periodo-ui/src/components/InlineList.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , { Box } = require('./Base') 5 | 6 | exports.InlineList = props => { 7 | const { children=[], ...rest } = props 8 | 9 | return ( 10 | h(Box, { 11 | ml: '1px', 12 | sx: { 13 | position: 'relative', 14 | overflow: 'hidden', 15 | }, 16 | ...rest, 17 | }, [ 18 | h(Box, { 19 | as: 'ul', 20 | sx: { 21 | ml: '-1px', 22 | listStyleType: 'none', 23 | }, 24 | }, [].concat(children).map((child, i) => 25 | h(Box, { 26 | as: 'li', 27 | key: i, 28 | sx: { 29 | borderLeft: '1px solid #ccc', 30 | display: 'inline-block', 31 | px: 1, 32 | }, 33 | }, child) 34 | )), 35 | ]) 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /modules/periodo-ui/src/components/InputBlock.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , R = require('ramda') 5 | , { RandomID } = require('periodo-common') 6 | , { Box } = require('./Base') 7 | , { HelpText } = require('./Typography') 8 | , { Label, Input, Textarea, Select } = require('./FormElements') 9 | , { Alert } = require('./Alerts') 10 | 11 | const inputProps = [ 12 | 'name', 13 | 'type', 14 | 'value', 15 | 'onChange', 16 | 'placeholder', 17 | 'disabled', 18 | 19 | 'rows', 20 | ] 21 | 22 | const FormControlBlock = FormComponent => RandomID(props => { 23 | const { 24 | randomID, 25 | name, 26 | isRequired=false, 27 | label, 28 | helpText, 29 | options, 30 | error, 31 | } = props 32 | 33 | const outerProps = R.omit([ 34 | ...inputProps, 35 | 'randomID', 36 | 'label', 37 | 'helpText', 38 | 'isRequired', 39 | ], props) 40 | 41 | const formProps = R.pick(inputProps, props) 42 | 43 | return ( 44 | h(Box, outerProps, [ 45 | h(Label, { 46 | htmlFor: randomID(name), 47 | isRequired, 48 | }, label), 49 | 50 | helpText && h(HelpText, helpText), 51 | 52 | h(FormComponent, { 53 | id: randomID(name), 54 | ...formProps, 55 | }, options), 56 | 57 | !error ? null : ( 58 | h(Alert, { 59 | variant: 'error', 60 | mt: 2, 61 | }, [ 62 | error, 63 | ]) 64 | ), 65 | ]) 66 | ) 67 | }) 68 | 69 | exports.InputBlock = FormControlBlock(Input) 70 | 71 | exports.TextareaBlock = FormControlBlock(Textarea) 72 | 73 | exports.SelectBlock = FormControlBlock(Select) 74 | -------------------------------------------------------------------------------- /modules/periodo-ui/src/components/LabeledMap.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , { Flex } = require('./Base') 5 | , { WorldMap } = require('./WorldMap') 6 | , { FeatureLabel } = require('./FeatureLabel') 7 | 8 | exports.LabeledMap = ({ 9 | focusedFeatures, 10 | features, 11 | ...props 12 | }) => 13 | h(Flex, { 14 | ...props, 15 | }, [ 16 | h(WorldMap, { 17 | focusedFeatures, 18 | features, 19 | flex: '1 1', 20 | }), 21 | h(FeatureLabel, { 22 | features: focusedFeatures, 23 | }), 24 | ]) 25 | -------------------------------------------------------------------------------- /modules/periodo-ui/src/components/Links.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , { Link: ORGLink } = require('org-shell') 5 | , { Text } = require('./Base') 6 | 7 | const Link = exports.Link = ORGLink(props => 8 | h(Text, { 9 | as: 'a', 10 | sx: { 11 | color: 'elements.link', 12 | textDecoration: 'none', 13 | cursor: 'pointer', 14 | ':hover': { 15 | textDecoration: 'underline', 16 | }, 17 | }, 18 | ...props, 19 | })) 20 | 21 | exports.ExternalLink = props => 22 | h(Link, { 23 | target: '_blank', 24 | ...props, 25 | }) 26 | -------------------------------------------------------------------------------- /modules/periodo-ui/src/components/NavigationMenu.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , { Flex, Box, Heading } = require('./Base') 5 | , { Link } = require('./Links') 6 | 7 | exports.NavigationMenu = ({ 8 | activeResource, 9 | routeGroups, 10 | ...props 11 | }) => 12 | h(Flex, { 13 | variant: 'menu', 14 | className: 'navigation-menu', 15 | ...props, 16 | }, routeGroups.map(({ label, routes }, i) => 17 | h(Box, { 18 | key: i, 19 | sx: { 20 | minWidth: 180, 21 | px: 2, 22 | py: 1, 23 | }, 24 | }, [ 25 | h(Heading, { 26 | key: 'heading' + '-i', 27 | level: 2, 28 | fontSize: 1, 29 | }, label), 30 | ].concat(routes.map(({ route, label }) => { 31 | const isActive = route.resourceName === activeResource.name 32 | return h(Link, { 33 | display: 'block', 34 | ['data-active']: isActive, 35 | key: route.resourceName, 36 | route, 37 | }, label) 38 | }))) 39 | )) 40 | -------------------------------------------------------------------------------- /modules/periodo-ui/src/components/Notes.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | const h = require('react-hyperscript') 4 | , { Box } = require('./Base') 5 | , { LinkifiedTextValue } = require('./diffable/Value') 6 | 7 | function EditorialNote({ text, ...props }) { 8 | return ( 9 | h(Box, { 10 | sx: { 11 | mt: 3, 12 | maxWidth: '60em', 13 | }, 14 | ...props, 15 | }, [ 16 | h(LinkifiedTextValue, { 17 | value: { text }, 18 | }), 19 | ]) 20 | ) 21 | } 22 | 23 | function Note({ cite, ...props }) { 24 | return ( 25 | h(EditorialNote, { 26 | as: 'blockquote', 27 | sx: { 28 | pl: 1, 29 | mt: 3, 30 | maxWidth: '60em', 31 | borderLeft: '4px solid', 32 | borderColor: 'gray.3', 33 | fontStyle: 'italic', 34 | quotes: "'“' '”' '‘' '’'", 35 | '::before': { 36 | color: '#ced4da', 37 | content: 'open-quote', 38 | fontSize: '2em', 39 | lineHeight: '0.1em', 40 | marginRight: '0.25em', 41 | verticalAlign: '-0.4em', 42 | }, 43 | }, 44 | cite, 45 | ...props, 46 | }) 47 | ) 48 | } 49 | 50 | module.exports = { 51 | Note, 52 | EditorialNote, 53 | } 54 | -------------------------------------------------------------------------------- /modules/periodo-ui/src/components/Patch.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , R = require('ramda') 5 | , { applyPatch } = require('fast-json-patch') 6 | , { Dataset } = require('./Dataset') 7 | 8 | const apply = (patch, data) => applyPatch( 9 | R.clone(data), R.clone(patch) 10 | ).newDocument 11 | 12 | exports.Patch = function (props) { 13 | const { patch, data, ...rest } = props 14 | 15 | return ( 16 | h(Dataset, { 17 | ...rest, 18 | value: data, 19 | compare: apply(patch, data), 20 | }) 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /modules/periodo-ui/src/components/Status.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , Type = require('union-type') 5 | , { Box } = require('./Base') 6 | 7 | const PatchStatus = Type({ 8 | Open: {}, 9 | Rejected: {}, 10 | Merged: {}, 11 | }) 12 | 13 | function Status({ open, merged }) { 14 | let type 15 | 16 | if (open) { 17 | type = PatchStatus.Open 18 | } else if (merged) { 19 | type = PatchStatus.Merged 20 | } else { 21 | type = PatchStatus.Rejected 22 | } 23 | 24 | const bg = type.case({ 25 | Open: () => '#fafad2', 26 | Rejected: () => '#ff9a9a', 27 | Merged: () => '#8fbd8f', 28 | }) 29 | 30 | const label = type.case({ 31 | Open: () => 'Open', 32 | Rejected: () => 'Rejected', 33 | Merged: () => 'Merged', 34 | }) 35 | 36 | return ( 37 | h(Box, { 38 | sx: { 39 | textAlign: 'center', 40 | p: 1, 41 | bg, 42 | fontWeight: 'bold', 43 | }, 44 | }, label) 45 | ) 46 | } 47 | 48 | exports.Status = Status 49 | -------------------------------------------------------------------------------- /modules/periodo-ui/src/components/Table.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , { Box } = require('rebass') 5 | 6 | exports.Table = ({ 7 | compact, 8 | secondary, 9 | ...props 10 | }) => { 11 | const colorset = secondary 12 | ? 'colorsets.tableSecondary' 13 | : 'colorsets.table' 14 | 15 | return ( 16 | h(Box, { 17 | as: 'table', 18 | sx: { 19 | width: '100%', 20 | borderSpacing: '4px 0', 21 | borderCollapse: 'collapse', 22 | bg: `${colorset}.bg`, 23 | color: `${colorset}.fg`, 24 | 25 | '& td, & th': { 26 | py: compact ? 1 : 2, 27 | px: compact ? 2 : 3, 28 | }, 29 | 30 | '& tr:hover': { 31 | bg: `${colorset}Focused.bg`, 32 | color: `${colorset}Focused.fg`, 33 | }, 34 | 35 | '& th': { 36 | bg: 'colorsets.secondary.bg', 37 | color: 'colorsets.secondary.fg', 38 | textAlign: 'left', 39 | fontWeight: 'bold', 40 | }, 41 | }, 42 | ...props, 43 | }) 44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /modules/periodo-ui/src/components/Tabs.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , { Flex, Box } = require('./Base') 5 | 6 | const TabItem = ({ 7 | i, 8 | label, 9 | isSelected, 10 | isLast, 11 | onClick, 12 | ...rest 13 | }) => 14 | h(Box, { 15 | sx: { 16 | p: 2, 17 | borderStyle: 'solid', 18 | borderWidth: 1, 19 | borderColor: 'gray.4', 20 | fontSize: 1, 21 | fontWeight: 'bold', 22 | textAlign: 'center', 23 | flexGrow: 1, 24 | marginLeft: i > 0 ? '-1px' : 0, 25 | marginRight: isLast ? '1px' : 0, 26 | ...(isSelected 27 | ? { 28 | borderBottomColor: 'transparent', 29 | } 30 | : { 31 | cursor: 'pointer', 32 | backgroundColor: '#eee', 33 | ':hover': { 34 | backgroundColor: '#ccc', 35 | }, 36 | }), 37 | }, 38 | onClick, 39 | ...rest, 40 | }, label) 41 | 42 | const TabSpacer = ({ isLast, ...rest }) => 43 | h(Box, { 44 | sx: { 45 | marginLeft: '-1px', 46 | width: isLast ? 0 : '16px', 47 | height: '34px', 48 | borderBottomStyle: 'solid', 49 | borderBottomWidth: 1, 50 | borderBottomColor: 'gray.4', 51 | }, 52 | ...rest, 53 | }) 54 | 55 | exports.Tabs = ({ 56 | tabs, 57 | value, 58 | onChange, 59 | ...rest 60 | }) => 61 | h(Box, { 62 | ...rest, 63 | }, [ 64 | h(Flex, { 65 | alignItems: 'center', 66 | }, tabs.map(({ id, label }, i) => [ 67 | h(TabItem, { 68 | i, 69 | key: id, 70 | label, 71 | isSelected: value === id, 72 | isLast: i === tabs.length - 1, 73 | onClick: () => onChange(id), 74 | }), 75 | h(TabSpacer, { 76 | isLast: i === tabs.length - 1, 77 | }), 78 | ]).flat()), 79 | 80 | h(Box, tabs.map(({ id, renderTab }) => 81 | value !== id ? null : ( 82 | h(Box, { 83 | key: id, 84 | sx: { 85 | border: 1, 86 | borderTop: 0, 87 | borderColor: 'gray.4', 88 | }, 89 | }, renderTab()) 90 | ) 91 | )), 92 | ]) 93 | -------------------------------------------------------------------------------- /modules/periodo-ui/src/components/Typography.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , { InlineText, Text } = require('./Base') 5 | 6 | exports.Italic = props => 7 | h(InlineText, { 8 | sx: { 9 | fontStyle: 'italic', 10 | }, 11 | ...props, 12 | }) 13 | 14 | exports.InfoText = props => 15 | h(InlineText, { 16 | sx: { 17 | fontStyle: 'italic', 18 | color: 'gray.8', 19 | }, 20 | ...props, 21 | }) 22 | 23 | exports.WarnText = props => 24 | h(InlineText, { 25 | sx: { 26 | fontStyle: 'italic', 27 | color: 'red.5', 28 | }, 29 | ...props, 30 | }) 31 | 32 | exports.HelpText = props => 33 | h(Text, { 34 | sx: { 35 | fontSize: 1, 36 | color: 'gray.7', 37 | mb: 1, 38 | }, 39 | ...props, 40 | }) 41 | -------------------------------------------------------------------------------- /modules/periodo-ui/src/components/diffable/types.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const R = require('ramda') 4 | , Type = require('union-type') 5 | 6 | function isIdentified(value) { 7 | return ( 8 | value != null && 9 | typeof value === 'object' && 10 | 'id' in value && 11 | value.id !== '@id' 12 | ) 13 | } 14 | 15 | // Is there any reason for this to exist? Wouldn't all non-identified values 16 | // be anonymous? 17 | function isAnonymous(value) { 18 | return ( 19 | value != null && ( 20 | typeof value === 'string' || 21 | typeof value === 'number' || 22 | (typeof value === 'object' && !isIdentified(value)) 23 | ) 24 | ) 25 | } 26 | 27 | function valueEquals(a, b) { 28 | if (isIdentified(a)) { 29 | return a.id === b.id 30 | } 31 | 32 | return R.equals(a, b) 33 | } 34 | 35 | function isValidValue(x) { 36 | return isIdentified(x) || isAnonymous(x) 37 | } 38 | 39 | const Change = Type({ 40 | Addition: { 41 | value: isValidValue, 42 | }, 43 | Deletion: { 44 | value: isValidValue, 45 | }, 46 | Preservation: { 47 | value: isValidValue, 48 | }, 49 | Mutation: { 50 | from: isIdentified, 51 | to: isIdentified, 52 | }, 53 | }) 54 | 55 | module.exports = { 56 | Change, 57 | isIdentified, 58 | isAnonymous, 59 | isValidValue, 60 | valueEquals, 61 | } 62 | -------------------------------------------------------------------------------- /modules/periodo-ui/src/extend.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | 5 | module.exports = (Component, overrides) => 6 | props => h(Component, { 7 | ...overrides, 8 | ...props, 9 | }) 10 | 11 | -------------------------------------------------------------------------------- /modules/periodo-ui/src/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function addUIModules(mods) { 4 | mods.forEach(mod => { 5 | for (const componentName in mod) { 6 | mod[componentName].displayName = 'UI:' + componentName; 7 | exports[componentName] = mod[componentName] 8 | } 9 | }) 10 | } 11 | 12 | exports.theme = require('./theme') 13 | 14 | addUIModules([ 15 | require('./components/Alerts'), 16 | require('./components/Authority'), 17 | require('./components/Autosuggest'), 18 | require('./components/BackendContext'), 19 | require('./components/Base'), 20 | require('./components/Breadcrumb'), 21 | require('./components/Buttons'), 22 | require('./components/ClientError'), 23 | require('./components/Dataset'), 24 | require('./components/Debug'), 25 | require('./components/Details'), 26 | require('./components/DropdownMenu'), 27 | require('./components/FormElements'), 28 | require('./components/Icons'), 29 | require('./components/InlineList'), 30 | require('./components/InputBlock'), 31 | require('./components/LabeledMap'), 32 | require('./components/Links'), 33 | require('./components/NavigationMenu'), 34 | require('./components/Notes'), 35 | require('./components/Pager'), 36 | require('./components/Patch'), 37 | require('./components/Period'), 38 | require('./components/PlaceSuggest'), 39 | require('./components/PlacesSelect'), 40 | require('./components/Source'), 41 | require('./components/Status'), 42 | require('./components/Table'), 43 | require('./components/Tabs'), 44 | require('./components/Tags'), 45 | require('./components/TimeSlider'), 46 | require('./components/Typography'), 47 | require('./components/WorldMap'), 48 | require('./components/diffable/Value'), 49 | require('./util'), 50 | ]) 51 | -------------------------------------------------------------------------------- /modules/periodo-ui/src/util.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const h = require('react-hyperscript') 4 | , linkifier = require('linkify-it')() 5 | , { ExternalLink } = require('./components/Links') 6 | 7 | const linkify = text => { 8 | const links = linkifier.match(text) 9 | 10 | if (! links) { return [ text ] } 11 | 12 | const nodes = [] 13 | 14 | let pos = 0 15 | 16 | links.forEach(match => { 17 | const minusOne = ',;.'.indexOf(match.url.slice(-1)) !== -1 18 | , href = minusOne ? match.url.slice(0, -1) : match.url 19 | , lastIndex = minusOne ? match.lastIndex - 1 : match.lastIndex 20 | 21 | nodes.push(text.slice(pos, match.index)) 22 | nodes.push(h(ExternalLink, { 23 | key: nodes.length, 24 | href, 25 | }, href)) 26 | nodes.push(text.slice(match.index + href.length, lastIndex)) 27 | 28 | pos = lastIndex 29 | }) 30 | 31 | nodes.push(text.slice(pos, text.length)) 32 | 33 | return nodes 34 | } 35 | 36 | module.exports = { 37 | linkify, 38 | } 39 | -------------------------------------------------------------------------------- /modules/periodo-ui/test/field.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const test = require('blue-tape') 4 | , { extract } = require('../src/components/diffable/Field') 5 | 6 | test('Extract function', async t => { 7 | t.plan(7) 8 | 9 | const o = { 10 | a: 1, 11 | b: [ 'foo', 'bar' ], 12 | c: { id: 1 }, 13 | d: null, 14 | f: true, 15 | } 16 | 17 | t.deepEqual( 18 | extract('a')(o), 19 | [ 1 ] 20 | ) 21 | 22 | t.deepEqual( 23 | extract('b')(o), 24 | [ 'foo', 'bar' ] 25 | ) 26 | 27 | t.deepEqual( 28 | extract('c')(o), 29 | [{ id: 1 }] 30 | ) 31 | 32 | t.deepEqual( 33 | extract([ 'c', 'id' ])(o), 34 | [ 1 ] 35 | ) 36 | 37 | t.deepEqual( 38 | extract('d')(o), 39 | [] 40 | ) 41 | 42 | t.deepEqual( 43 | extract('e')(o), 44 | [] 45 | ) 46 | 47 | t.throws(() => extract('f')(o), /^TypeError/) 48 | }) 49 | 50 | test('Extract with key', async t => { 51 | t.plan(5) 52 | 53 | const o = { 54 | a: 1, 55 | b: [ 'foo', 'bar' ], 56 | c: { id: 1 }, 57 | d: null, 58 | f: true, 59 | } 60 | 61 | t.deepEqual( 62 | extract('a', { withKey: 'x' })(o), 63 | [{ 64 | id: 0, 65 | x: 1, 66 | }] 67 | ) 68 | 69 | t.deepEqual( 70 | extract('b', { withKey: 'x' })(o), 71 | [ 72 | { 73 | id: 0, 74 | x: 'foo', 75 | }, 76 | { 77 | id: 1, 78 | x: 'bar', 79 | }, 80 | ] 81 | ) 82 | t.deepEqual( 83 | extract('c', { withKey: 'x' })(o), 84 | [ 85 | { id: 1 }, 86 | ] 87 | ) 88 | 89 | t.deepEqual( 90 | extract([ 'c', 'id' ], { withKey: 'x' })(o), 91 | [ 92 | { 93 | id: 0, 94 | x: 1, 95 | }, 96 | ] 97 | ) 98 | 99 | t.deepEqual( 100 | extract('d', { withKey: 'x' })(o), 101 | [] 102 | ) 103 | }) 104 | -------------------------------------------------------------------------------- /modules/periodo-utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "periodo-utils", 3 | "version": "5.0.0", 4 | "private": true, 5 | "description": "", 6 | "main": "src/index.js", 7 | "scripts": { 8 | "test": "tape test/*.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "tape": "^5.0.0" 15 | }, 16 | "dependencies": { 17 | "periodo-date-parser": "^1.0.1", 18 | "ramda": "^0.27.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /modules/periodo-utils/src/authority.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const R = require('ramda') 4 | , source = require('./source') 5 | , terminusList = require('./terminus_list') 6 | , { $$Authority } = require('./symbols') 7 | 8 | function periodsWithAuthority(authority) { 9 | return R.map( 10 | R.assoc($$Authority, authority), 11 | R.values(authority.periods) 12 | ) 13 | } 14 | 15 | function periods(authority) { 16 | return R.values(authority.periods) 17 | } 18 | 19 | function displayTitle(authority) { 20 | return source.displayTitle(authority.source) 21 | } 22 | 23 | function describe(authority) { 24 | const periods = R.values(authority.periods || {}) 25 | 26 | return { 27 | id: authority.id, 28 | source: displayTitle(authority), 29 | periods: periods.length, 30 | earliest: terminusList.minYear(R.map(R.path([ 'start' ]))(periods)), 31 | latest: terminusList.maxYear(R.map(R.path([ 'stop' ]))(periods)), 32 | editorialNote: authority.editorialNote || '', 33 | } 34 | } 35 | 36 | module.exports = { 37 | displayTitle, 38 | describe, 39 | periods, 40 | periodsWithAuthority, 41 | } 42 | -------------------------------------------------------------------------------- /modules/periodo-utils/src/authority_list.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const R = require('ramda') 4 | , { periodsWithAuthority } = require('./authority') 5 | 6 | // Iterable -> List 7 | // 8 | // Get all of the individual periods within the sequence of authorities. 9 | const periods = R.chain(periodsWithAuthority) 10 | 11 | const spatialCoverageCounts = R.pipe( 12 | R.map(p => p.spatialCoverage.map( 13 | R.pipe(R.prop('id'), encodeURIComponent))), 14 | R.countBy(R.identity), 15 | R.mapObjIndexed((count, countries) => ({ 16 | count, 17 | countries: countries.split(',').map(x => encodeURIComponent(x)), 18 | })), 19 | R.values() 20 | ) 21 | 22 | // Iterable -> Array 23 | const spatialCoverages = R.pipe( 24 | periods, 25 | R.groupBy(R.prop('spatialCoverageDescription')), 26 | R.pickBy(R.identity), 27 | R.map(spatialCoverageCounts), 28 | R.mapObjIndexed((uses, label) => ({ 29 | uses, 30 | label, 31 | })), 32 | R.values 33 | ) 34 | 35 | 36 | module.exports = { 37 | periods, 38 | spatialCoverages, 39 | spatialCoverageCounts, 40 | } 41 | -------------------------------------------------------------------------------- /modules/periodo-utils/src/contributor.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | asString, 5 | } 6 | 7 | // Contributor -> String 8 | function asString(contributor) { 9 | // FIXME: this sucks 10 | return contributor.name; 11 | } 12 | 13 | -------------------------------------------------------------------------------- /modules/periodo-utils/src/contributor_list.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const R = require('ramda') 4 | , contributor = require('./contributor') 5 | 6 | function asString(contributors) { 7 | return contributors.length < 3 8 | ? contributors.map(contributor.asString).join(' and ') 9 | : R.pipe(R.head, contributor.asString)(contributors) + ' et al.' 10 | } 11 | 12 | module.exports = { 13 | asString, 14 | } 15 | -------------------------------------------------------------------------------- /modules/periodo-utils/src/dataset.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function isDataset(obj, includeLegacy=false) { 4 | return ( 5 | typeof obj === 'object' && ( 6 | typeof obj.authorities === 'object' || 7 | (!!includeLegacy && typeof obj.periodCollections === 'object') 8 | ) 9 | ) 10 | } 11 | 12 | // The client should accept older versions of the dataset. This function 13 | // should implement changes that are necessary for the client to continue 14 | // to work. 15 | // 16 | // There is a directory of changes that have been applied to the dataset at: 17 | // 18 | // https://github.com/periodo/periodo-validation/blob/master/fix/ 19 | // 20 | // It may make sense in the future to just apply these. But those functions 21 | // would need to be changed to only work conditionally. 22 | function normalizeDataset(dataset) { 23 | const ret = JSON.parse(JSON.stringify(dataset)) 24 | 25 | let renamePeriodCollections = false 26 | , renamePeriods = false 27 | 28 | if (ret.periodCollections) { 29 | renamePeriodCollections = true; 30 | ret.authorities = ret.periodCollections; 31 | delete ret.periodCollections; 32 | } 33 | 34 | Object.values(ret.authorities).forEach(authority => { 35 | if (renamePeriodCollections) { 36 | authority.type = 'Authority'; 37 | } 38 | 39 | if (authority.definitions) { 40 | renamePeriods = true; 41 | authority.periods = authority.definitions; 42 | delete authority.definitions; 43 | } 44 | 45 | Object.values(authority.periods || {}).forEach(period => { 46 | if (renamePeriods) { 47 | period.type = 'Period'; 48 | } 49 | }) 50 | }) 51 | 52 | return ret; 53 | } 54 | 55 | module.exports = { 56 | isDataset, 57 | normalizeDataset, 58 | } 59 | -------------------------------------------------------------------------------- /modules/periodo-utils/src/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | authority: require('./authority'), 5 | authorityList: require('./authority_list'), 6 | contributor: require('./contributor'), 7 | contributorList: require('./contributor_list'), 8 | dataset: require('./dataset'), 9 | label: require('./label'), 10 | period: require('./period'), 11 | source: require('./source'), 12 | symbols: require('./symbols'), 13 | terminus: require('./terminus'), 14 | terminusList: require('./terminus_list'), 15 | ...require('./util'), 16 | } 17 | -------------------------------------------------------------------------------- /modules/periodo-utils/src/label.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const R = require('ramda') 4 | 5 | // Takes a set of Immutable Maps with keys language, script, and value, 6 | // and returns a Map grouped by language-script code 7 | const groupByCode = R.pipe( 8 | R.groupBy(code), 9 | R.map(R.map(R.prop('value'))) 10 | ) 11 | 12 | function code(label) { 13 | return `${label.language}-${label.script}` 14 | } 15 | 16 | module.exports = { 17 | code, 18 | groupByCode, 19 | } 20 | -------------------------------------------------------------------------------- /modules/periodo-utils/src/period.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const R = require('ramda') 4 | , { earliestYear } = require('./terminus') 5 | , { $$Authority } = require('./symbols') 6 | 7 | function originalLabel(period) { 8 | const { label, languageTag } = period 9 | 10 | if (!label || !languageTag) return null; 11 | 12 | return { 13 | label, 14 | languageTag, 15 | } 16 | } 17 | 18 | const allLabels = period => 19 | [].concat( 20 | originalLabel(period), 21 | R.pipe( 22 | R.propOr({}, 'localizedLabels'), 23 | R.mapObjIndexed((labels, languageTag) => 24 | labels.map(label => ({ 25 | label, 26 | languageTag, 27 | }))), 28 | R.values, 29 | R.unnest 30 | )(period) 31 | ) 32 | 33 | function alternateLabels(period) { 34 | return R.without([ originalLabel(period) ], allLabels(period)) 35 | } 36 | 37 | function authorityOf(period) { 38 | return R.prop($$Authority, period) 39 | } 40 | 41 | function periodWithAuthority(period) { 42 | return { 43 | period: R.path([ $$Authority, 'periods', period.id ], period), 44 | authority: authorityOf(period), 45 | } 46 | } 47 | 48 | const byStartYear = R.comparator( 49 | (a, b) => earliestYear(a.start) < earliestYear(b.start) 50 | ) 51 | 52 | module.exports = { 53 | originalLabel, 54 | allLabels, 55 | alternateLabels, 56 | authorityOf, 57 | periodWithAuthority, 58 | byStartYear, 59 | } 60 | -------------------------------------------------------------------------------- /modules/periodo-utils/src/source.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const R = require('ramda') 4 | , { oneOf } = require('./util') 5 | , contributorList = require('./contributor_list') 6 | 7 | const creators = R.pipe( 8 | oneOf( 9 | R.prop('creators'), 10 | R.path([ 'partOf', 'creators' ]) 11 | ), 12 | x => x || [] 13 | ) 14 | 15 | const contributors = R.pipe( 16 | oneOf( 17 | R.prop('contributors'), 18 | R.path([ 'partOf', 'contributors' ]) 19 | ), 20 | x => x || [] 21 | ) 22 | 23 | const title = oneOf( 24 | R.prop('title'), 25 | R.prop('citation'), 26 | R.path([ 'partOf', 'title' ]), 27 | R.path([ 'partOf', 'citation' ]) 28 | ) 29 | 30 | const yearPublished = R.pipe( 31 | oneOf( 32 | R.prop('yearPublished'), 33 | R.path([ 'partOf', 'yearPublished' ]) 34 | ), 35 | v => v || null 36 | ) 37 | 38 | function displayTitle(source) { 39 | const _creators = contributorList.asString(creators(source)) 40 | , year = yearPublished(source) 41 | , _title = title(source) 42 | 43 | let ret = '' 44 | 45 | if (_creators) { 46 | ret += _creators; 47 | if (ret.slice(-1) !== '.') { 48 | ret += '. '; 49 | } else { 50 | ret += ' '; 51 | } 52 | } 53 | 54 | ret += _title; 55 | 56 | if (ret.slice(-1) !== '.') { 57 | ret += '.' 58 | } 59 | 60 | if (year) { 61 | ret += ' ' + year + '.'; 62 | } 63 | 64 | return ret; 65 | } 66 | 67 | 68 | module.exports = { 69 | displayTitle, 70 | creators, 71 | contributors, 72 | title, 73 | yearPublished, 74 | } 75 | -------------------------------------------------------------------------------- /modules/periodo-utils/src/symbols.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | $$Authority: Symbol.for('Authority'), 5 | $$RelatedPeriods: Symbol.for('RelatedPeriods'), 6 | } 7 | -------------------------------------------------------------------------------- /modules/periodo-utils/src/terminus_list.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { earliestYear, latestYear } = require('./terminus') 4 | 5 | // Iterable -> Object({ label: String, iso: Int }) or Null 6 | function maxYear(termini) { 7 | const latest = termini.reduce((prev, terminus) => { 8 | const iso = latestYear(terminus) 9 | return iso !== null && iso > prev.iso 10 | ? 11 | { 12 | terminus, 13 | iso, 14 | } 15 | : prev 16 | }, { 17 | terminus: null, 18 | iso: -Infinity, 19 | }) 20 | 21 | if (latest.terminus === null) return null; 22 | 23 | return { 24 | label: latest.terminus.label, 25 | iso: latest.iso, 26 | } 27 | } 28 | 29 | // Iterable -> Object({ label: String, iso: Int }) or Null 30 | function minYear(termini) { 31 | const earliest = termini.reduce((prev, terminus) => { 32 | const iso = earliestYear(terminus) 33 | return iso !== null && iso < prev.iso 34 | ? 35 | { 36 | terminus, 37 | iso, 38 | } 39 | : prev 40 | }, { 41 | terminus: null, 42 | iso: Infinity, 43 | }) 44 | 45 | if (earliest.terminus === null) return null; 46 | 47 | return { 48 | label: earliest.terminus.label, 49 | iso: earliest.iso, 50 | } 51 | } 52 | 53 | module.exports = { 54 | maxYear, 55 | minYear, 56 | } 57 | -------------------------------------------------------------------------------- /modules/periodo-utils/test/fixtures/multi-label-period.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Progressive Era", 3 | "languageTag": "en", 4 | "localizedLabels": { 5 | "en": [ 6 | "The Progressive Era" 7 | ], 8 | "fr": [ 9 | "Ère progressiste" 10 | ] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /modules/periodo-utils/test/fixtures/source-partof.json: -------------------------------------------------------------------------------- 1 | { 2 | "locator": "pages 256-257", 3 | "partOf": { 4 | "creators": [ 5 | { 6 | "name": "Rijksdienst voor het Cultureel Erfgoed" 7 | } 8 | ], 9 | "dateAccessed": "2014-10-08", 10 | "title": "Het Archeologisch Basisregister (ABR)", 11 | "url": "http://www.cultureelerfgoed.nl/sites/default/files/downloads/dossiers/abr_website2.pdf", 12 | "yearPublished": 1992 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /modules/periodo-utils/test/fixtures/termini.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "label": "1200", "in": { "year": "1200" }}, 3 | { "label": "0", "in": { "year": "0000" }}, 4 | { "label": "6th century", "in": { "earliestYear": "0501", "latestYear": "0600" }}, 5 | { "label": "one hundred bee cee", "in": { "year": "-0099" }}, 6 | { "label": "present" }, 7 | { "label": "unknown" } 8 | ] 9 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 8080; 3 | listen [::]:8080; 4 | 5 | charset utf-8; 6 | 7 | gzip on; 8 | gzip_types application/javascript image/svg+xml; 9 | gzip_vary on; 10 | 11 | root /srv/www; 12 | 13 | location / { 14 | index index.html; 15 | } 16 | 17 | location /images/ { 18 | # cache images for a year 19 | add_header Cache-Control "max-age=31536000"; 20 | } 21 | 22 | location ~* ^/periodo-client.*.min\.js$ { 23 | # cache minified JS for a year 24 | add_header Cache-Control "max-age=31536000"; 25 | } 26 | 27 | location /index.html { 28 | # kill cache 29 | add_header Last-Modified $date_gmt; 30 | add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0'; 31 | if_modified_since off; 32 | expires off; 33 | etag off; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ssl-server.py: -------------------------------------------------------------------------------- 1 | from http.server import HTTPServer, SimpleHTTPRequestHandler 2 | import ssl 3 | 4 | addr = 'localhost' 5 | port = 5003 6 | cert = './localhost+2.pem' 7 | key = './localhost+2-key.pem' 8 | 9 | if __name__ == '__main__': 10 | context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) 11 | context.load_cert_chain(cert, key) 12 | 13 | httpsd = HTTPServer((addr, port), SimpleHTTPRequestHandler) 14 | httpsd.socket = context.wrap_socket(httpsd.socket, server_side=True) 15 | print(f'Serving HTTPS on {addr} port {port} (https://{addr}:{port}/) ...') 16 | print('Listening on ' + addr + ':' + str(port)) 17 | try: 18 | httpsd.serve_forever() 19 | except KeyboardInterrupt: 20 | pass 21 | --------------------------------------------------------------------------------