mirror of
https://github.com/FoggedLens/iD.git
synced 2026-05-18 14:45:12 +02:00
b47952b369
It's easier to insert the links there, and then the HTML strings are availble for anyone who wants them
411 lines
12 KiB
JavaScript
411 lines
12 KiB
JavaScript
import { dispatch as d3_dispatch } from 'd3-dispatch';
|
|
import { select as d3_select } from 'd3-selection';
|
|
|
|
import LocationConflation from '@ideditor/location-conflation';
|
|
import whichPolygon from 'which-polygon';
|
|
import { resolveStrings } from 'osm-community-index';
|
|
|
|
import { fileFetcher } from '../core/file_fetcher';
|
|
import { t, localizer } from '../core/localizer';
|
|
import { svgIcon } from '../svg/icon';
|
|
import { uiDisclosure } from '../ui/disclosure';
|
|
import { utilRebind } from '../util/rebind';
|
|
|
|
|
|
let _oci = null;
|
|
|
|
export function uiSuccess(context) {
|
|
const MAXEVENTS = 2;
|
|
const dispatch = d3_dispatch('cancel');
|
|
let _changeset;
|
|
let _location;
|
|
ensureOSMCommunityIndex(); // start fetching the data
|
|
|
|
|
|
function ensureOSMCommunityIndex() {
|
|
const data = fileFetcher;
|
|
return Promise.all([
|
|
data.get('oci_resources'),
|
|
data.get('oci_features'),
|
|
data.get('oci_defaults')
|
|
])
|
|
.then(vals => {
|
|
if (_oci) return _oci;
|
|
|
|
const ociResources = vals[0].resources;
|
|
const loco = new LocationConflation(vals[1]);
|
|
let ociFeatures = {};
|
|
|
|
Object.values(ociResources).forEach(resource => {
|
|
let feature;
|
|
try {
|
|
feature = loco.resolveLocationSet(resource.locationSet).feature;
|
|
let ociFeature = ociFeatures[feature.id];
|
|
if (!ociFeature) {
|
|
ociFeature = JSON.parse(JSON.stringify(feature)); // deep clone
|
|
ociFeature.properties.resourceIDs = new Set();
|
|
ociFeatures[feature.id] = ociFeature;
|
|
}
|
|
ociFeature.properties.resourceIDs.add(resource.id);
|
|
} catch (err) {
|
|
/* ignore communities with an unresolvable locationSet */
|
|
console.warn(`warning: skipping community resource ${resource.id}: ${err.message}`); // eslint-disable-line no-console
|
|
}
|
|
});
|
|
|
|
return _oci = {
|
|
defaults: vals[2].defaults,
|
|
features: ociFeatures,
|
|
resources: ociResources,
|
|
query: whichPolygon({ type: 'FeatureCollection', features: Object.values(ociFeatures) })
|
|
};
|
|
});
|
|
}
|
|
|
|
|
|
// string-to-date parsing in JavaScript is weird
|
|
function parseEventDate(when) {
|
|
if (!when) return;
|
|
|
|
let raw = when.trim();
|
|
if (!raw) return;
|
|
|
|
if (!/Z$/.test(raw)) { // if no trailing 'Z', add one
|
|
raw += 'Z'; // this forces date to be parsed as a UTC date
|
|
}
|
|
|
|
const parsed = new Date(raw);
|
|
return new Date(parsed.toUTCString().substr(0, 25)); // convert to local timezone
|
|
}
|
|
|
|
|
|
function success(selection) {
|
|
let header = selection
|
|
.append('div')
|
|
.attr('class', 'header fillL');
|
|
|
|
header
|
|
.append('h3')
|
|
.html(t.html('success.just_edited'));
|
|
|
|
header
|
|
.append('button')
|
|
.attr('class', 'close')
|
|
.on('click', () => dispatch.call('cancel'))
|
|
.call(svgIcon('#iD-icon-close'));
|
|
|
|
let body = selection
|
|
.append('div')
|
|
.attr('class', 'body save-success fillL');
|
|
|
|
let summary = body
|
|
.append('div')
|
|
.attr('class', 'save-summary');
|
|
|
|
summary
|
|
.append('h3')
|
|
.html(t.html('success.thank_you' + (_location ? '_location' : ''), { where: _location }));
|
|
|
|
summary
|
|
.append('p')
|
|
.html(t.html('success.help_html'))
|
|
.append('a')
|
|
.attr('class', 'link-out')
|
|
.attr('target', '_blank')
|
|
.attr('href', t('success.help_link_url'))
|
|
.call(svgIcon('#iD-icon-out-link', 'inline'))
|
|
.append('span')
|
|
.html(t.html('success.help_link_text'));
|
|
|
|
let osm = context.connection();
|
|
if (!osm) return;
|
|
|
|
let changesetURL = osm.changesetURL(_changeset.id);
|
|
|
|
let table = summary
|
|
.append('table')
|
|
.attr('class', 'summary-table');
|
|
|
|
let row = table
|
|
.append('tr')
|
|
.attr('class', 'summary-row');
|
|
|
|
row
|
|
.append('td')
|
|
.attr('class', 'cell-icon summary-icon')
|
|
.append('a')
|
|
.attr('target', '_blank')
|
|
.attr('href', changesetURL)
|
|
.append('svg')
|
|
.attr('class', 'logo-small')
|
|
.append('use')
|
|
.attr('xlink:href', '#iD-logo-osm');
|
|
|
|
let summaryDetail = row
|
|
.append('td')
|
|
.attr('class', 'cell-detail summary-detail');
|
|
|
|
summaryDetail
|
|
.append('a')
|
|
.attr('class', 'cell-detail summary-view-on-osm')
|
|
.attr('target', '_blank')
|
|
.attr('href', changesetURL)
|
|
.html(t.html('success.view_on_osm'));
|
|
|
|
summaryDetail
|
|
.append('div')
|
|
.html(t.html('success.changeset_id', {
|
|
changeset_id: `<a href="${changesetURL}" target="_blank">${_changeset.id}</a>`
|
|
}));
|
|
|
|
|
|
// Get OSM community index features intersecting the map..
|
|
ensureOSMCommunityIndex()
|
|
.then(oci => {
|
|
let communities = [];
|
|
const properties = oci.query(context.map().center(), true) || [];
|
|
|
|
// Gather the communities from the result
|
|
properties.forEach(props => {
|
|
const resourceIDs = Array.from(props.resourceIDs);
|
|
resourceIDs.forEach(resourceID => {
|
|
let resource = oci.resources[resourceID];
|
|
|
|
// Resolve strings
|
|
const localizer = (stringID) => t.html(`community.${stringID}`);
|
|
resource.resolved = resolveStrings(resource, oci.defaults, localizer);
|
|
|
|
communities.push({
|
|
area: props.area || Infinity,
|
|
order: resource.order || 0,
|
|
resource: resource
|
|
});
|
|
});
|
|
});
|
|
|
|
// sort communities by feature area ascending, community order descending
|
|
communities.sort((a, b) => a.area - b.area || b.order - a.order);
|
|
|
|
body
|
|
.call(showCommunityLinks, communities.map(c => c.resource));
|
|
});
|
|
}
|
|
|
|
|
|
function showCommunityLinks(selection, resources) {
|
|
let communityLinks = selection
|
|
.append('div')
|
|
.attr('class', 'save-communityLinks');
|
|
|
|
communityLinks
|
|
.append('h3')
|
|
.html(t.html('success.like_osm'));
|
|
|
|
let table = communityLinks
|
|
.append('table')
|
|
.attr('class', 'community-table');
|
|
|
|
let row = table.selectAll('.community-row')
|
|
.data(resources);
|
|
|
|
let rowEnter = row.enter()
|
|
.append('tr')
|
|
.attr('class', 'community-row');
|
|
|
|
rowEnter
|
|
.append('td')
|
|
.attr('class', 'cell-icon community-icon')
|
|
.append('a')
|
|
.attr('target', '_blank')
|
|
.attr('href', d => d.resolved.url)
|
|
.append('svg')
|
|
.attr('class', 'logo-small')
|
|
.append('use')
|
|
.attr('xlink:href', d => `#community-${d.type}`);
|
|
|
|
let communityDetail = rowEnter
|
|
.append('td')
|
|
.attr('class', 'cell-detail community-detail');
|
|
|
|
communityDetail
|
|
.each(showCommunityDetails);
|
|
|
|
communityLinks
|
|
.append('div')
|
|
.attr('class', 'community-missing')
|
|
.html(t.html('success.missing'))
|
|
.append('a')
|
|
.attr('class', 'link-out')
|
|
.attr('target', '_blank')
|
|
.call(svgIcon('#iD-icon-out-link', 'inline'))
|
|
.attr('href', 'https://github.com/osmlab/osm-community-index/issues')
|
|
.append('span')
|
|
.html(t.html('success.tell_us'));
|
|
}
|
|
|
|
|
|
function showCommunityDetails(d) {
|
|
let selection = d3_select(this);
|
|
let communityID = d.id;
|
|
|
|
selection
|
|
.append('div')
|
|
.attr('class', 'community-name')
|
|
.html(d.resolved.nameHTML);
|
|
|
|
selection
|
|
.append('div')
|
|
.attr('class', 'community-description')
|
|
.html(d.resolved.descriptionHTML);
|
|
|
|
// Create an expanding section if any of these are present..
|
|
if (d.resolved.extendedDescriptionHTML || (d.languageCodes && d.languageCodes.length)) {
|
|
selection
|
|
.append('div')
|
|
.call(uiDisclosure(context, `community-more-${d.id}`, false)
|
|
.expanded(false)
|
|
.updatePreference(false)
|
|
.label(t.html('success.more'))
|
|
.content(showMore)
|
|
);
|
|
}
|
|
|
|
let nextEvents = (d.events || [])
|
|
.map(event => {
|
|
event.date = parseEventDate(event.when);
|
|
return event;
|
|
})
|
|
.filter(event => { // date is valid and future (or today)
|
|
const t = event.date.getTime();
|
|
const now = (new Date()).setHours(0,0,0,0);
|
|
return !isNaN(t) && t >= now;
|
|
})
|
|
.sort((a, b) => { // sort by date ascending
|
|
return a.date < b.date ? -1 : a.date > b.date ? 1 : 0;
|
|
})
|
|
.slice(0, MAXEVENTS); // limit number of events shown
|
|
|
|
if (nextEvents.length) {
|
|
selection
|
|
.append('div')
|
|
.call(uiDisclosure(context, `community-events-${d.id}`, false)
|
|
.expanded(false)
|
|
.updatePreference(false)
|
|
.label(t.html('success.events'))
|
|
.content(showNextEvents)
|
|
)
|
|
.select('.hide-toggle')
|
|
.append('span')
|
|
.attr('class', 'badge-text')
|
|
.html(nextEvents.length);
|
|
}
|
|
|
|
|
|
function showMore(selection) {
|
|
let more = selection.selectAll('.community-more')
|
|
.data([0]);
|
|
|
|
let moreEnter = more.enter()
|
|
.append('div')
|
|
.attr('class', 'community-more');
|
|
|
|
if (d.resolved.extendedDescriptionHTML) {
|
|
moreEnter
|
|
.append('div')
|
|
.attr('class', 'community-extended-description')
|
|
.html(d.resolved.extendedDescriptionHTML);
|
|
}
|
|
|
|
if (d.languageCodes && d.languageCodes.length) {
|
|
const languageList = d.languageCodes
|
|
.map(code => localizer.languageName(code))
|
|
.join(', ');
|
|
|
|
moreEnter
|
|
.append('div')
|
|
.attr('class', 'community-languages')
|
|
.html(t.html('success.languages', { languages: languageList }));
|
|
}
|
|
}
|
|
|
|
|
|
function showNextEvents(selection) {
|
|
let events = selection
|
|
.append('div')
|
|
.attr('class', 'community-events');
|
|
|
|
let item = events.selectAll('.community-event')
|
|
.data(nextEvents);
|
|
|
|
let itemEnter = item.enter()
|
|
.append('div')
|
|
.attr('class', 'community-event');
|
|
|
|
itemEnter
|
|
.append('div')
|
|
.attr('class', 'community-event-name')
|
|
.append('a')
|
|
.attr('target', '_blank')
|
|
.attr('href', d => d.url)
|
|
.html(d => {
|
|
let name = d.name;
|
|
if (d.i18n && d.id) {
|
|
name = t(`community.${communityID}.events.${d.id}.name`, { default: name });
|
|
}
|
|
return name;
|
|
});
|
|
|
|
itemEnter
|
|
.append('div')
|
|
.attr('class', 'community-event-when')
|
|
.html(d => {
|
|
let options = { weekday: 'short', day: 'numeric', month: 'short', year: 'numeric' };
|
|
if (d.date.getHours() || d.date.getMinutes()) { // include time if it has one
|
|
options.hour = 'numeric';
|
|
options.minute = 'numeric';
|
|
}
|
|
return d.date.toLocaleString(localizer.localeCode(), options);
|
|
});
|
|
|
|
itemEnter
|
|
.append('div')
|
|
.attr('class', 'community-event-where')
|
|
.html(d => {
|
|
let where = d.where;
|
|
if (d.i18n && d.id) {
|
|
where = t(`community.${communityID}.events.${d.id}.where`, { default: where });
|
|
}
|
|
return where;
|
|
});
|
|
|
|
itemEnter
|
|
.append('div')
|
|
.attr('class', 'community-event-description')
|
|
.html(d => {
|
|
let description = d.description;
|
|
if (d.i18n && d.id) {
|
|
description = t(`community.${communityID}.events.${d.id}.description`, { default: description });
|
|
}
|
|
return description;
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
success.changeset = function(val) {
|
|
if (!arguments.length) return _changeset;
|
|
_changeset = val;
|
|
return success;
|
|
};
|
|
|
|
|
|
success.location = function(val) {
|
|
if (!arguments.length) return _location;
|
|
_location = val;
|
|
return success;
|
|
};
|
|
|
|
|
|
return utilRebind(success, dispatch, 'on');
|
|
}
|