diff --git a/lib/featureDiff/DiffColumn.js b/lib/featureDiff/DiffColumn.js index ddc0dc2..9ee19a3 100644 --- a/lib/featureDiff/DiffColumn.js +++ b/lib/featureDiff/DiffColumn.js @@ -2,15 +2,17 @@ import React from 'react'; import PropTypes from 'prop-types'; import { config } from '../config'; -export const DiffColumn = function({ diff, prop, type, propClass }) { - return ( - - - - ); -}; +export class DiffColumn extends React.Component { + render() { + return ( + + + + ); + } +} -const DiffColumnContent = function({ diff, prop, type }) { +const DiffColumnContent = function({ diff, prop, type, tag2link }) { let renderOutput; const value = diff[prop][type], propIsWikidata = typeof prop == 'string' && /wikidata$/.test(prop); @@ -54,6 +56,21 @@ const DiffColumnContent = function({ diff, prop, type }) { } }); renderOutput = {renderArray}; + } else if (tag2link[prop] && typeof value === 'string') { + // This is a foreign key which is defined in the tag2link DB. + // So, we render a clickable link. + renderOutput = ( + + + {value} + + + ); } else { // Standard tag, no processing needed renderOutput = {value}; @@ -67,3 +84,7 @@ DiffColumn.propTypes = { type: PropTypes.string.isRequired, propClass: PropTypes.string }; + +DiffColumn.contextTypes = { + tag2link: PropTypes.objectOf(PropTypes.string) +}; diff --git a/lib/featureDiff/DiffTable.js b/lib/featureDiff/DiffTable.js index 453789a..f7ebb92 100644 --- a/lib/featureDiff/DiffTable.js +++ b/lib/featureDiff/DiffTable.js @@ -1,6 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { DiffRows } from './DiffRows'; +import { Tag2LinkContext } from '../tag2link'; //Renders the markup for a table @@ -30,13 +31,15 @@ export const DiffTable = function({ diff, ignoreList, header }) { )} - + + + ); }; diff --git a/lib/tag2link.js b/lib/tag2link.js new file mode 100644 index 0000000..aed1186 --- /dev/null +++ b/lib/tag2link.js @@ -0,0 +1,81 @@ +// @ts-check +import React from 'react'; +import PropTypes from 'prop-types'; + +const URL = 'https://cdn.jsdelivr.net/gh/JOSM/tag2link@master/index.json'; + +const RANKS = ['deprecated', 'normal', 'preferred']; + +/** @type {Promise | undefined} */ +let promise; + +/** + * @param {Tag2LinkItem[]} input + * @returns {State} + */ +function convertSourceData(input) { + /** @type {Record} */ + const output = {}; + + const allKeys = new Set(input.map(item => item.key)); + + for (const key of allKeys) { + // find the item with the best rank + const bestDefinition = input + .filter(item => item.key === key) + .sort((a, b) => RANKS.indexOf(b.rank) - RANKS.indexOf(a.rank))[0]; + + output[key.replace('Key:', '')] = bestDefinition.url; + } + + return { tag2link: output }; +} + +/** + * @typedef {{ + * key: `Key:${string}`; + * url: string; + * source: string; + * rank: "normal" | "preferred"; + * }} Tag2LinkItem + * + * @typedef {{ + * tag2link: Record; + * }} State + * + * @typedef {React.PropsWithChildren} Props + */ + +/** @type {React.Component} */ +export class Tag2LinkContext extends React.Component { + /** @param {Props} props */ + constructor(props) { + super(props); + this.state = { tag2link: {} }; + } + + componentDidMount() { + if (!promise) { + promise = fetch(URL) + .then(r => r.json()) + .then(convertSourceData); + } + promise.then(state => this.setState(state)); + } + + getChildContext() { + return this.state; + } + + render() { + return this.props.children; + } +} + +Tag2LinkContext.propTypes = { + children: PropTypes.node +}; + +Tag2LinkContext.childContextTypes = { + tag2link: PropTypes.objectOf(PropTypes.string) +};