From 82263aea7c272a46cb9b4d13459c7ea6b62ce794 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Sun, 18 Nov 2018 19:38:19 -0500 Subject: [PATCH 1/4] Adds a Wikidata field --- data/presets.yaml | 3 + data/presets/fields.json | 1 + data/presets/fields/wikidata.json | 7 ++ data/presets/schema/field.json | 1 + data/taginfo.json | 2 +- dist/locales/en.json | 3 + modules/presets/index.js | 4 ++ modules/ui/fields/index.js | 3 + modules/ui/fields/wikidata.js | 106 ++++++++++++++++++++++++++++++ 9 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 data/presets/fields/wikidata.json create mode 100644 modules/ui/fields/wikidata.js diff --git a/data/presets.yaml b/data/presets.yaml index 0dd5a484f..92a364263 100644 --- a/data/presets.yaml +++ b/data/presets.yaml @@ -2025,6 +2025,9 @@ en: width: # width=* label: Width (Meters) + wikidata: + # wikidata=* + label: Wikidata wikipedia: # 'wikipedia=*, wikidata=*' label: Wikipedia diff --git a/data/presets/fields.json b/data/presets/fields.json index fafe8b83d..056f65bd3 100644 --- a/data/presets/fields.json +++ b/data/presets/fields.json @@ -342,6 +342,7 @@ "wheelchair": {"key": "wheelchair", "type": "radio", "options": ["yes", "limited", "no"], "icon": "maki-wheelchair", "universal": true, "label": "Wheelchair Access"}, "wholesale": {"key": "wholesale", "type": "typeCombo", "label": "Wholesale"}, "width": {"key": "width", "type": "number", "minValue": 0, "label": "Width (Meters)"}, + "wikidata": {"key": "wikidata", "type": "wikidata", "icon": "wikipedia", "universal": true, "label": "Wikidata"}, "wikipedia": {"key": "wikipedia", "keys": ["wikipedia", "wikidata"], "type": "wikipedia", "icon": "wikipedia", "universal": true, "label": "Wikipedia"}, "windings": {"key": "windings", "type": "number", "minValue": 1, "label": "Windings", "placeholder": "1, 2, 3..."}, "windings/configuration": {"key": "windings:configuration", "type": "combo", "label": "Windings Configuration", "strings": {"options": {"star": "Star / Wye", "delta": "Delta", "open-delta": "Open Delta", "zigzag": "Zig Zag", "open": "Open", "scott": "Scott", "leblanc": "Leblanc"}}} diff --git a/data/presets/fields/wikidata.json b/data/presets/fields/wikidata.json new file mode 100644 index 000000000..d1c5f478a --- /dev/null +++ b/data/presets/fields/wikidata.json @@ -0,0 +1,7 @@ +{ + "key": "wikidata", + "type": "wikidata", + "icon": "wikipedia", + "universal": true, + "label": "Wikidata" +} diff --git a/data/presets/schema/field.json b/data/presets/schema/field.json index 0683361b1..b77a28413 100644 --- a/data/presets/schema/field.json +++ b/data/presets/schema/field.json @@ -71,6 +71,7 @@ "text", "typeCombo", "url", + "wikidata", "wikipedia" ], "required": true diff --git a/data/taginfo.json b/data/taginfo.json index 96dc57fc7..6d335a975 100644 --- a/data/taginfo.json +++ b/data/taginfo.json @@ -7863,8 +7863,8 @@ {"key": "wheelchair", "description": "Wheelchair Access"}, {"key": "wholesale", "description": "Wholesale"}, {"key": "width", "description": "Width (Meters)"}, + {"key": "wikidata", "description": "Wikidata, Wikipedia"}, {"key": "wikipedia", "description": "Wikipedia"}, - {"key": "wikidata", "description": "Wikipedia"}, {"key": "windings", "description": "Windings"}, { "key": "windings:configuration", diff --git a/dist/locales/en.json b/dist/locales/en.json index 7e95240b7..74779740b 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -3155,6 +3155,9 @@ "width": { "label": "Width (Meters)" }, + "wikidata": { + "label": "Wikidata" + }, "wikipedia": { "label": "Wikipedia" }, diff --git a/modules/presets/index.js b/modules/presets/index.js index 70038eef7..9a7297803 100644 --- a/modules/presets/index.js +++ b/modules/presets/index.js @@ -139,6 +139,10 @@ export function presetIndex() { }); } + // move the wikidata field to directly follow the wikipedia field + _universal.splice(_universal.indexOf(_fields.wikidata), 1); + _universal.splice(_universal.indexOf(_fields.wikipedia)+1, 0, _fields.wikidata); + if (d.presets) { _forEach(d.presets, function(d, id) { all.collection.push(presetPreset(id, d, _fields)); diff --git a/modules/ui/fields/index.js b/modules/ui/fields/index.js index 27950023a..12f5a7350 100644 --- a/modules/ui/fields/index.js +++ b/modules/ui/fields/index.js @@ -10,6 +10,7 @@ export * from './maxspeed'; export * from './radio'; export * from './restrictions'; export * from './textarea'; +export * from './wikidata'; export * from './wikipedia'; import { @@ -47,6 +48,7 @@ import { uiFieldLocalized } from './localized'; import { uiFieldMaxspeed } from './maxspeed'; import { uiFieldRestrictions } from './restrictions'; import { uiFieldTextarea } from './textarea'; +import { uiFieldWikidata } from './wikidata'; import { uiFieldWikipedia } from './wikipedia'; export var uiFields = { @@ -73,5 +75,6 @@ export var uiFields = { textarea: uiFieldTextarea, typeCombo: uiFieldTypeCombo, url: uiFieldUrl, + wikidata: uiFieldWikidata, wikipedia: uiFieldWikipedia }; diff --git a/modules/ui/fields/wikidata.js b/modules/ui/fields/wikidata.js new file mode 100644 index 000000000..e0e2e75d9 --- /dev/null +++ b/modules/ui/fields/wikidata.js @@ -0,0 +1,106 @@ +import { dispatch as d3_dispatch } from 'd3-dispatch'; + +import { + select as d3_select, + event as d3_event +} from 'd3-selection'; + +import { svgIcon } from '../../svg/index'; +import { + utilGetSetValue, + utilNoAuto, + utilRebind +} from '../../util'; + + +export function uiFieldWikidata(field) { + var dispatch = d3_dispatch('change'), + link = d3_select(null), + title = d3_select(null), + wikiURL = '', + entity; + + + function wiki(selection) { + + title = selection.selectAll('input.wiki-title') + .data([0]); + + title = title.enter() + .append('input') + .attr('type', 'text') + .attr('class', 'wiki-title') + .attr('id', 'preset-input-' + field.safeid) + .call(utilNoAuto) + .merge(title); + + title + .on('blur', blur) + .on('change', change); + + + link = selection.selectAll('.wiki-link') + .data([0]); + + link = link.enter() + .append('button') + .attr('class', 'button-input-action wiki-link minor') + .attr('tabindex', -1) + .call(svgIcon('#iD-icon-out-link')) + .merge(link); + + link + .on('click', function() { + d3_event.preventDefault(); + if (wikiURL) window.open(wikiURL, '_blank'); + }); + } + + + function blur() { + change(); + } + + + function change() { + var syncTags = { + wikidata: utilGetSetValue(title) + }; + dispatch.call('change', this, syncTags); + } + + + wiki.tags = function(tags) { + var value = tags[field.key] || '', + matches = value.match(/^Q[0-9]*$/); + + utilGetSetValue(title, value); + + // value in correct format + if (matches) { + wikiURL = 'https://wikidata.org/wiki/' + value; + // unrecognized value format + } else { + if (value && value !== '') { + wikiURL = 'https://wikidata.org/wiki/Special:Search?search=' + value; + } else { + wikiURL = ''; + } + } + }; + + + wiki.entity = function(_) { + if (!arguments.length) return entity; + entity = _; + return wiki; + }; + + + wiki.focus = function() { + title.node().focus(); + }; + + + return utilRebind(wiki, dispatch, 'on'); +} From a28147aea2b2d66266fd2a4396ad1e6d5f84df40 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Sat, 1 Dec 2018 13:16:41 -0800 Subject: [PATCH 2/4] Updates Wikidata field for the latest field flexbox changes Adds an Identifier label to the Wikidata QID input --- data/core.yaml | 2 ++ dist/locales/en.json | 3 ++ modules/ui/fields/wikidata.js | 52 +++++++++++++++++++++++++++-------- 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/data/core.yaml b/data/core.yaml index 6d672ae10..154539f0d 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -1243,3 +1243,5 @@ en: west: "W" coordinate: "{coordinate}{direction}" coordinate_pair: "{latitude}, {longitude}" + wikidata: + identifier: "Identifier" diff --git a/dist/locales/en.json b/dist/locales/en.json index 00df217ee..bf0506afe 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -1437,6 +1437,9 @@ "coordinate": "{coordinate}{direction}", "coordinate_pair": "{latitude}, {longitude}" }, + "wikidata": { + "identifier": "Identifier" + }, "presets": { "categories": { "category-barrier": { diff --git a/modules/ui/fields/wikidata.js b/modules/ui/fields/wikidata.js index e0e2e75d9..497aebe6d 100644 --- a/modules/ui/fields/wikidata.js +++ b/modules/ui/fields/wikidata.js @@ -12,6 +12,8 @@ import { utilRebind } from '../../util'; +import { t } from '../../util/locale'; + export function uiFieldWikidata(field) { var dispatch = d3_dispatch('change'), @@ -23,14 +25,46 @@ export function uiFieldWikidata(field) { function wiki(selection) { - title = selection.selectAll('input.wiki-title') + var wrap = selection.selectAll('.form-field-input-wrap') .data([0]); - title = title.enter() - .append('input') + wrap = wrap.enter() + .append('div') + .attr('class', 'form-field-input-wrap form-field-input-' + field.type) + .merge(wrap); + + + var list = wrap.selectAll('ul') + .data([0]); + + list = list.enter() + .append('ul') + .attr('class', 'labeled-inputs') + .merge(list); + + + var items = list.selectAll('li') + .data(field.keys); + + // Enter + var enter = items.enter() + .append('li') + .attr('class', function(d) { return 'preset-access-' + d; }); + + enter + .append('span') + .attr('class', 'label preset-label-access') + .attr('for', function(d) { return 'preset-input-wikidata-' + d; }) + .text(t('wikidata.identifier')); + + var inputWrap = enter + .append('div') + .attr('class', 'preset-input-wikidata-wrap'); + + title = inputWrap.append('input') .attr('type', 'text') - .attr('class', 'wiki-title') - .attr('id', 'preset-input-' + field.safeid) + .attr('class', 'preset-input-wikidata') + .attr('id', function(d) { return 'preset-input-wikidata-' + d; }) .call(utilNoAuto) .merge(title); @@ -38,13 +72,9 @@ export function uiFieldWikidata(field) { .on('blur', blur) .on('change', change); - - link = selection.selectAll('.wiki-link') - .data([0]); - - link = link.enter() + link = enter .append('button') - .attr('class', 'button-input-action wiki-link minor') + .attr('class', 'form-field-button wiki-link') .attr('tabindex', -1) .call(svgIcon('#iD-icon-out-link')) .merge(link); From afcc474d3c12e842cc735477917c56aa923a003c Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Sat, 1 Dec 2018 17:39:16 -0800 Subject: [PATCH 3/4] Wikidata entity labels and descriptions are now fetched and displayed in the field, if present --- css/80_app.css | 7 +-- data/core.yaml | 4 ++ dist/locales/en.json | 8 +++- modules/services/wikidata.js | 27 ++++++++++++ modules/ui/fields/wikidata.js | 80 +++++++++++++++++++++++++++++++---- 5 files changed, 112 insertions(+), 14 deletions(-) diff --git a/css/80_app.css b/css/80_app.css index 11bfa43cb..ada187611 100644 --- a/css/80_app.css +++ b/css/80_app.css @@ -1303,6 +1303,7 @@ img.tag-reference-wiki-image { background-color: #f1f1f1; } .form-field-button .icon { + fill: #333; opacity: .5; } @@ -1336,12 +1337,12 @@ img.tag-reference-wiki-image { width: 100%; } .form-field ul.labeled-inputs li { - border-bottom: 1px solid #ccc; + border-top: 1px solid #ccc; display: flex; flex-flow: row nowrap; } -.form-field ul.labeled-inputs li:last-child { - border-bottom: 0; +.form-field ul.labeled-inputs li:first-child { + border-top: 0; } .form-field ul.labeled-inputs li > span, .form-field ul.labeled-inputs li > div { diff --git a/data/core.yaml b/data/core.yaml index 154539f0d..50e7a3031 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -5,6 +5,8 @@ en: remove: remove undo: undo zoom_to: zoom to + copy: copy + open_wikidata: open on wikidata.org modes: add_area: title: Area @@ -1245,3 +1247,5 @@ en: coordinate_pair: "{latitude}, {longitude}" wikidata: identifier: "Identifier" + label: "Label" + description: "Description" diff --git a/dist/locales/en.json b/dist/locales/en.json index bf0506afe..c7ffc067d 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -5,7 +5,9 @@ "information": "info", "remove": "remove", "undo": "undo", - "zoom_to": "zoom to" + "zoom_to": "zoom to", + "copy": "copy", + "open_wikidata": "open on wikidata.org" }, "modes": { "add_area": { @@ -1438,7 +1440,9 @@ "coordinate_pair": "{latitude}, {longitude}" }, "wikidata": { - "identifier": "Identifier" + "identifier": "Identifier", + "label": "Label", + "description": "Description" }, "presets": { "categories": { diff --git a/modules/services/wikidata.js b/modules/services/wikidata.js index 1e80e32a1..83301fc65 100644 --- a/modules/services/wikidata.js +++ b/modules/services/wikidata.js @@ -2,6 +2,7 @@ import { json as d3_json } from 'd3-request'; import { utilQsString } from '../util'; +import { currentLocale } from '../util/locale'; var endpoint = 'https://www.wikidata.org/w/api.php?'; @@ -34,6 +35,32 @@ export default { callback(title, data.entities || {}); } }); + }, + + entityByQID: function(qid, callback) { + if (!qid) { + callback('', {}); + return; + } + + var lang = currentLocale.replace(/-/g, '_'); + + d3_json(endpoint + utilQsString({ + action: 'wbgetentities', + format: 'json', + ids: qid, + props: /*sitelinks|*/'labels|descriptions', + //sitefilter: lang + 'wiki', + languages: lang, + languagefallback: 1, + origin: '*' + }), function(err, data) { + if (err || !data || data.error) { + callback('', {}); + } else { + callback(qid, data.entities[qid] || {}); + } + }); } }; diff --git a/modules/ui/fields/wikidata.js b/modules/ui/fields/wikidata.js index 497aebe6d..bfb1ac7dc 100644 --- a/modules/ui/fields/wikidata.js +++ b/modules/ui/fields/wikidata.js @@ -5,6 +5,8 @@ import { event as d3_event } from 'd3-selection'; +import { services } from '../../services/index'; + import { svgIcon } from '../../svg/index'; import { utilGetSetValue, @@ -16,6 +18,7 @@ import { t } from '../../util/locale'; export function uiFieldWikidata(field) { + var wikidata = services.wikidata; var dispatch = d3_dispatch('change'), link = d3_select(null), title = d3_select(null), @@ -42,29 +45,32 @@ export function uiFieldWikidata(field) { .attr('class', 'labeled-inputs') .merge(list); + var wikidataProperties = ['identifier', 'label', 'description']; var items = list.selectAll('li') - .data(field.keys); + .data(wikidataProperties); // Enter var enter = items.enter() .append('li') - .attr('class', function(d) { return 'preset-access-' + d; }); + .attr('class', function(d) { return 'preset-wikidata-' + d; }); enter .append('span') - .attr('class', 'label preset-label-access') + .attr('class', 'label') .attr('for', function(d) { return 'preset-input-wikidata-' + d; }) - .text(t('wikidata.identifier')); + .text(function(d) { return t('wikidata.'+d); }); var inputWrap = enter .append('div') - .attr('class', 'preset-input-wikidata-wrap'); + .attr('class', 'input-wrap'); - title = inputWrap.append('input') + inputWrap.append('input') .attr('type', 'text') .attr('class', 'preset-input-wikidata') - .attr('id', function(d) { return 'preset-input-wikidata-' + d; }) + .attr('id', function(d) { return 'preset-input-wikidata-' + d; }); + + title = wrap.select('.preset-wikidata-identifier input') .call(utilNoAuto) .merge(title); @@ -72,9 +78,14 @@ export function uiFieldWikidata(field) { .on('blur', blur) .on('change', change); - link = enter - .append('button') + var idItem = wrap.select('.preset-wikidata-identifier'); + + idItem.select('button') + .remove(); + + link = idItem.append('button') .attr('class', 'form-field-button wiki-link') + .attr('title', t('icons.open_wikidata')) .attr('tabindex', -1) .call(svgIcon('#iD-icon-out-link')) .merge(link); @@ -84,6 +95,29 @@ export function uiFieldWikidata(field) { d3_event.preventDefault(); if (wikiURL) window.open(wikiURL, '_blank'); }); + + var readOnlyItems = wrap.selectAll('li:not(.preset-wikidata-identifier)'); + + readOnlyItems.select('input') + .classed('disabled', 'true') + .attr('readonly', 'true'); + + readOnlyItems.select('button') + .remove(); + + readOnlyItems.append('button') + .attr('class', 'form-field-button wiki-link') + .attr('title', t('icons.copy')) + .attr('tabindex', -1) + .call(svgIcon('#iD-operation-copy')) + .on('click', function() { + d3_event.preventDefault(); + d3_select(this.parentNode) + .select('input') + .node() + .select(); + document.execCommand("copy"); + }); } @@ -109,8 +143,36 @@ export function uiFieldWikidata(field) { // value in correct format if (matches) { wikiURL = 'https://wikidata.org/wiki/' + value; + wikidata.entityByQID(value, function(qid, entity) { + var label = '', description = ''; + + if (entity.labels && Object.keys(entity.labels).length > 0) { + var lang = Object.keys(entity.labels)[0]; + label = entity.labels[lang].value; + } + if (entity.descriptions && Object.keys(entity.descriptions).length > 0) { + var lang = Object.keys(entity.descriptions)[0]; + description = entity.descriptions[lang].value; + } + + d3_select('.preset-wikidata-label') + .style('display', function(){ + return label.length > 0 ? 'flex' : 'none'; + }) + .select('input') + .attr('value', label); + + d3_select('.preset-wikidata-description') + .style('display', function(){ + return description.length > 0 ? 'flex' : 'none'; + }) + .select('input') + .attr('value', description); + }); // unrecognized value format } else { + d3_select('.preset-wikidata-label').style('display', 'none'); + d3_select('.preset-wikidata-description').style('display', 'none'); if (value && value !== '') { wikiURL = 'https://wikidata.org/wiki/Special:Search?search=' + value; } else { From 55c47021faf3b93cdd03ff2b27d2169987cd62ba Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Sat, 1 Dec 2018 18:14:51 -0800 Subject: [PATCH 4/4] Fixes Wikidata field lint errors --- modules/ui/fields/wikidata.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/modules/ui/fields/wikidata.js b/modules/ui/fields/wikidata.js index bfb1ac7dc..63fdc5d09 100644 --- a/modules/ui/fields/wikidata.js +++ b/modules/ui/fields/wikidata.js @@ -116,7 +116,7 @@ export function uiFieldWikidata(field) { .select('input') .node() .select(); - document.execCommand("copy"); + document.execCommand('copy'); }); } @@ -147,12 +147,10 @@ export function uiFieldWikidata(field) { var label = '', description = ''; if (entity.labels && Object.keys(entity.labels).length > 0) { - var lang = Object.keys(entity.labels)[0]; - label = entity.labels[lang].value; + label = entity.labels[Object.keys(entity.labels)[0]].value; } if (entity.descriptions && Object.keys(entity.descriptions).length > 0) { - var lang = Object.keys(entity.descriptions)[0]; - description = entity.descriptions[lang].value; + description = entity.descriptions[Object.keys(entity.descriptions)[0]].value; } d3_select('.preset-wikidata-label')