diff --git a/build.js b/build.js new file mode 100644 index 000000000..17af7a623 --- /dev/null +++ b/build.js @@ -0,0 +1,32 @@ +const buildData = require('./build_data'); +const buildSrc = require('./build_src'); +const buildCSS = require('./build_css'); + +let _currBuild = null; + +// if called directly, do the thing. +buildAll(); + + +function buildAll() { + if (_currBuild) return _currBuild; + + return _currBuild = + Promise.all([buildCSS(), buildBundle()]) + .then(() => { + _currBuild = null; + }) + .catch((err) => { + console.error(err); + _currBuild = null; + process.exit(1); + }); +}; + + +function buildBundle() { + return buildData(); + // .then(() => buildSrc()); +} + +module.exports = buildAll; diff --git a/build_css.js b/build_css.js index eae032731..03d797fe8 100644 --- a/build_css.js +++ b/build_css.js @@ -3,36 +3,50 @@ const colors = require('colors/safe'); const concat = require('concat-files'); const glob = require('glob'); +let _currBuild = null; -module.exports = function buildCSS() { - var isBuilding = false; - return function () { - if (isBuilding) return; +// if called directly, do the thing. +buildCSS(); - console.log('building css'); - console.time(colors.green('css built')); - isBuilding = true; - return concatFilesProm('css/**/*.css', 'dist/iD.css') - .then(function () { - console.timeEnd(colors.green('css built')); - isBuilding = false; - }) - .catch(function (err) { - console.error(err); - process.exit(1); - }); - }; +function buildCSS() { + if (_currBuild) return _currBuild; + + console.log('building css'); + console.time(colors.green('css built')); + + return _currBuild = + doGlob('css/**/*.css') + .then((files) => doConcat(files, 'dist/iD.css')) + .then(() => { + console.timeEnd(colors.green('css built')); + _currBuild = null; + }) + .catch((err) => { + console.error(err); + _currBuild = null; + process.exit(1); + }); }; -function concatFilesProm(globPath, output) { - return new Promise(function (res, rej) { - glob(globPath, function (er, files) { - if (er) return rej(er); - concat(files, output, function (err) { - if (err) return rej(err); - res(); - }); - }); + +function doGlob(pattern) { + return new Promise((resolve, reject) => { + glob(pattern, (err, files) => { + if (err) return reject(err); + resolve(files); }); -} + }); +}; + +function doConcat(files, output) { + return new Promise((resolve, reject) => { + concat(files, output, (err) => { + if (err) return reject(err); + resolve(); + }); + }); +}; + + +module.exports = buildCSS; \ No newline at end of file diff --git a/build_data.js b/build_data.js index 7bac37725..019285e4a 100644 --- a/build_data.js +++ b/build_data.js @@ -22,846 +22,835 @@ fontawesome.library.add(fas, far, fab); const request = require('request').defaults({ maxSockets: 1 }); -module.exports = function buildData() { - var building; - return function() { - // Note: even though this function is sync adding - // the `building` variable for consistency and future proofing - if (building) return; - building = true; - console.log('building data'); - console.time(colors.green('data built')); - // Create symlinks if necessary.. { 'target': 'source' } - const symlinks = { - 'land.html': 'dist/land.html', - img: 'dist/img' - }; +let _currBuild = null; - for (var target of Object.keys(symlinks)) { - if (!shell.test('-L', target)) { - console.log(`Creating symlink: ${target} -> ${symlinks[target]}`); - shell.ln('-sf', symlinks[target], target); - } - } +// if called directly, do the thing. +buildData(); - // Translation strings - var tstrings = { - categories: {}, - fields: {}, - presets: {} - }; - // Font Awesome icons used - var faIcons = { - 'fas-i-cursor': {}, - 'fas-lock': {}, - 'fas-long-arrow-alt-right': {}, - 'fas-th-list': {} - }; +function buildData() { + if (_currBuild) return _currBuild; - // The Noun Project icons used - var tnpIcons = {}; + console.log('building data'); + console.time(colors.green('data built')); - // all fields searchable under "add field" - var searchableFieldIDs = {}; + // Create symlinks if necessary.. { 'target': 'source' } + const symlinks = { + 'land.html': 'dist/land.html', + img: 'dist/img' + }; - // Start clean - shell.rm('-f', [ - 'data/presets/categories.json', - 'data/presets/fields.json', - 'data/presets/presets.json', - 'data/presets.yaml', - 'data/taginfo.json', - 'data/territory-languages.json', - 'dist/locales/en.json', - 'svg/fontawesome/*.svg', - ]); + for (let target of Object.keys(symlinks)) { + if (!shell.test('-L', target)) { + console.log(`Creating symlink: ${target} -> ${symlinks[target]}`); + shell.ln('-sf', symlinks[target], target); + } + } - var categories = generateCategories(tstrings, faIcons, tnpIcons); - var fields = generateFields(tstrings, faIcons, tnpIcons, searchableFieldIDs); - var presets = generatePresets(tstrings, faIcons, tnpIcons, searchableFieldIDs); - var defaults = read('data/presets/defaults.json'); - var translations = generateTranslations(fields, presets, tstrings, searchableFieldIDs); - var taginfo = generateTaginfo(presets, fields); - var territoryLanguages = generateTerritoryLanguages(); + // Translation strings + let tstrings = { + categories: {}, + fields: {}, + presets: {} + }; - // Additional consistency checks - validateCategoryPresets(categories, presets); - validatePresetFields(presets, fields); - validateDefaults(defaults, categories, presets); + // Font Awesome icons used + let faIcons = { + 'fas-i-cursor': {}, + 'fas-lock': {}, + 'fas-long-arrow-alt-right': {}, + 'fas-th-list': {} + }; - // Save individual data files - var tasks = [ - writeFileProm( - 'data/presets/categories.json', - prettyStringify({ categories: categories }) - ), - writeFileProm( - 'data/presets/fields.json', - prettyStringify({ fields: fields }, { maxLength: 9999 }) - ), - writeFileProm( - 'data/presets/presets.json', - prettyStringify({ presets: presets }, { maxLength: 9999 }) - ), - writeFileProm( - 'data/presets.yaml', - translationsToYAML(translations) - ), - writeFileProm( - 'data/taginfo.json', - prettyStringify(taginfo, { maxLength: 9999 }) - ), - writeFileProm( - 'data/territory-languages.json', - prettyStringify({ dataTerritoryLanguages: territoryLanguages }, { maxLength: 9999 }) - ), - writeEnJson(tstrings), - writeFaIcons(faIcons), - writeTnpIcons(tnpIcons) - ]; + // The Noun Project icons used + let tnpIcons = {}; - return Promise.all(tasks) - .then(function () { - console.timeEnd(colors.green('data built')); - building = false; - }) - .catch(function (err) { - console.error(err); - process.exit(1); - }); - }; + // all fields searchable under "add field" + let searchableFieldIDs = {}; + + // Start clean + shell.rm('-f', [ + 'data/presets/categories.json', + 'data/presets/fields.json', + 'data/presets/presets.json', + 'data/presets.yaml', + 'data/taginfo.json', + 'data/territory-languages.json', + 'dist/locales/en.json', + 'svg/fontawesome/*.svg', + ]); + + let categories = generateCategories(tstrings, faIcons, tnpIcons); + let fields = generateFields(tstrings, faIcons, tnpIcons, searchableFieldIDs); + let presets = generatePresets(tstrings, faIcons, tnpIcons, searchableFieldIDs); + let defaults = read('data/presets/defaults.json'); + let translations = generateTranslations(fields, presets, tstrings, searchableFieldIDs); + let taginfo = generateTaginfo(presets, fields); + let territoryLanguages = generateTerritoryLanguages(); + + // Additional consistency checks + validateCategoryPresets(categories, presets); + validatePresetFields(presets, fields); + validateDefaults(defaults, categories, presets); + + // Save individual data files + let tasks = [ + writeFileProm('data/presets/categories.json', prettyStringify({ categories: categories }) ), + writeFileProm('data/presets/fields.json', prettyStringify({ fields: fields }, { maxLength: 9999 }) ), + writeFileProm('data/presets/presets.json', prettyStringify({ presets: presets }, { maxLength: 9999 }) ), + writeFileProm('data/presets.yaml', translationsToYAML(translations) ), + writeFileProm('data/taginfo.json', prettyStringify(taginfo, { maxLength: 9999 }) ), + writeFileProm('data/territory-languages.json', prettyStringify({ dataTerritoryLanguages: territoryLanguages }, { maxLength: 9999 }) ), + writeEnJson(tstrings), + writeFaIcons(faIcons), + writeTnpIcons(tnpIcons) + ]; + + return _currBuild = + Promise.all(tasks) + .then(() => { + console.timeEnd(colors.green('data built')); + _currBuild = null; + }) + .catch((err) => { + console.error(err); + _currBuild = null; + process.exit(1); + }); }; function read(f) { - return JSON.parse(fs.readFileSync(f, 'utf8')); + return JSON.parse(fs.readFileSync(f, 'utf8')); } function validate(file, instance, schema) { - var validationErrors = jsonschema.validate(instance, schema).errors; - if (validationErrors.length) { - console.error(file + ': '); - validationErrors.forEach(function(error) { - if (error.property) { - console.error(error.property + ' ' + error.message); - } else { - console.error(error); - } - }); - process.exit(1); - } + let validationErrors = jsonschema.validate(instance, schema).errors; + + if (validationErrors.length) { + console.error(file + ': '); + validationErrors.forEach((error) => { + if (error.property) { + console.error(error.property + ' ' + error.message); + } else { + console.error(error); + } + }); + process.exit(1); + } } function generateCategories(tstrings, faIcons, tnpIcons) { - var categories = {}; - glob.sync(__dirname + '/data/presets/categories/*.json').forEach(function(file) { - var category = read(file); - var id = 'category-' + path.basename(file, '.json'); - tstrings.categories[id] = { name: category.name }; - categories[id] = category; + let categories = {}; - // fontawesome icon, remember for later - if (/^fa[srb]-/.test(category.icon)) { - faIcons[category.icon] = {}; - } - // noun project icon, remember for later - if (/^tnp-/.test(category.icon)) { - tnpIcons[category.icon] = {}; - } - }); - return categories; + glob.sync(__dirname + '/data/presets/categories/*.json').forEach(file => { + let category = read(file); + let id = 'category-' + path.basename(file, '.json'); + tstrings.categories[id] = { name: category.name }; + categories[id] = category; + + // fontawesome icon, remember for later + if (/^fa[srb]-/.test(category.icon)) { + faIcons[category.icon] = {}; + } + // noun project icon, remember for later + if (/^tnp-/.test(category.icon)) { + tnpIcons[category.icon] = {}; + } + }); + + return categories; } function generateFields(tstrings, faIcons, tnpIcons, searchableFieldIDs) { - var fields = {}; - glob.sync(__dirname + '/data/presets/fields/**/*.json').forEach(function(file) { - var field = read(file); - var id = stripLeadingUnderscores(file.match(/presets\/fields\/([^.]*)\.json/)[1]); + let fields = {}; - validate(file, field, fieldSchema); + glob.sync(__dirname + '/data/presets/fields/**/*.json').forEach(file => { + let field = read(file); + let id = stripLeadingUnderscores(file.match(/presets\/fields\/([^.]*)\.json/)[1]); - var t = tstrings.fields[id] = { - label: field.label, - terms: (field.terms || []).join(',') - }; + validate(file, field, fieldSchema); - if (field.universal) { - searchableFieldIDs[id] = true; - } + let t = tstrings.fields[id] = { + label: field.label, + terms: (field.terms || []).join(',') + }; - if (field.placeholder) { - t.placeholder = field.placeholder; - } + if (field.universal) { + searchableFieldIDs[id] = true; + } - if (field.strings) { - for (var i in field.strings) { - t[i] = field.strings[i]; - } - } + if (field.placeholder) { + t.placeholder = field.placeholder; + } - fields[id] = field; + if (field.strings) { + for (let i in field.strings) { + t[i] = field.strings[i]; + } + } - // fontawesome icon, remember for later - if (/^fa[srb]-/.test(field.icon)) { - faIcons[field.icon] = {}; - } - // noun project icon, remember for later - if (/^tnp-/.test(field.icon)) { - tnpIcons[field.icon] = {}; - } - }); - return fields; + fields[id] = field; + + // fontawesome icon, remember for later + if (/^fa[srb]-/.test(field.icon)) { + faIcons[field.icon] = {}; + } + // noun project icon, remember for later + if (/^tnp-/.test(field.icon)) { + tnpIcons[field.icon] = {}; + } + }); + + return fields; } function suggestionsToPresets(presets) { - const brands = nsi.brands.brands; - const wikidata = nsi.wikidata.wikidata; + const brands = nsi.brands.brands; + const wikidata = nsi.wikidata.wikidata; - Object.keys(brands).forEach(kvnd => { - const suggestion = brands[kvnd]; - const qid = suggestion.tags['brand:wikidata']; - if (!qid || !/^Q\d+$/.test(qid)) return; // wikidata tag missing or looks wrong.. + Object.keys(brands).forEach(kvnd => { + const suggestion = brands[kvnd]; + const qid = suggestion.tags['brand:wikidata']; + if (!qid || !/^Q\d+$/.test(qid)) return; // wikidata tag missing or looks wrong.. - const parts = kvnd.split('|', 2); - const kv = parts[0]; - const name = parts[1].replace('~', ' '); + const parts = kvnd.split('|', 2); + const kv = parts[0]; + const name = parts[1].replace('~', ' '); - let presetID, preset; + let presetID, preset; - // sometimes we can choose a more specific preset then key/value.. - if (suggestion.tags.cuisine) { - // cuisine can contain multiple values, so try them all in order - let cuisines = suggestion.tags.cuisine.split(';'); - for (let i = 0; i < cuisines.length; i++) { - presetID = kv + '/' + cuisines[i].trim(); - preset = presets[presetID]; - if (preset) break; // we matched one - } + // sometimes we can choose a more specific preset then key/value.. + if (suggestion.tags.cuisine) { + // cuisine can contain multiple values, so try them all in order + let cuisines = suggestion.tags.cuisine.split(';'); + for (let i = 0; i < cuisines.length; i++) { + presetID = kv + '/' + cuisines[i].trim(); + preset = presets[presetID]; + if (preset) break; // we matched one + } - } else if (suggestion.tags.vending) { - if (suggestion.tags.vending === 'parcel_pickup;parcel_mail_in') { - presetID = kv + '/parcel_pickup_dropoff'; - } else { - presetID = kv + '/' + suggestion.tags.vending; - } - preset = presets[presetID]; - } + } else if (suggestion.tags.vending) { + if (suggestion.tags.vending === 'parcel_pickup;parcel_mail_in') { + presetID = kv + '/parcel_pickup_dropoff'; + } else { + presetID = kv + '/' + suggestion.tags.vending; + } + preset = presets[presetID]; + } - // A few exceptions where the NSI tagging doesn't exactly match iD tagging.. - if (kv === 'healthcare/clinic') { - presetID = 'amenity/clinic'; - preset = presets[presetID]; - } else if (kv === 'leisure/tanning_salon') { - presetID = 'shop/beauty/tanning'; - preset = presets[presetID]; - } + // A few exceptions where the NSI tagging doesn't exactly match iD tagging.. + if (kv === 'healthcare/clinic') { + presetID = 'amenity/clinic'; + preset = presets[presetID]; + } else if (kv === 'leisure/tanning_salon') { + presetID = 'shop/beauty/tanning'; + preset = presets[presetID]; + } - // fallback to key/value - if (!preset) { - presetID = kv; - preset = presets[presetID]; - } + // fallback to key/value + if (!preset) { + presetID = kv; + preset = presets[presetID]; + } - // still no match? - if (!preset) { - console.log('Warning: No preset "' + presetID + '" for name-suggestion "' + name + '"'); - return; - } + // still no match? + if (!preset) { + console.log('Warning: No preset "' + presetID + '" for name-suggestion "' + name + '"'); + return; + } - let suggestionID = presetID + '/' + name.replace('/', ''); + let suggestionID = presetID + '/' + name.replace('/', ''); - let tags = { 'brand:wikidata': qid }; - for (let k in preset.tags) { - // prioritize suggestion tags over preset tags (for `vending`,`cuisine`, etc) - tags[k] = suggestion.tags[k] || preset.tags[k]; - } + let tags = { 'brand:wikidata': qid }; + for (let k in preset.tags) { + // prioritize suggestion tags over preset tags (for `vending`,`cuisine`, etc) + tags[k] = suggestion.tags[k] || preset.tags[k]; + } - // Prefer a wiki commons logo sometimes.. #6361 - const preferCommons = { - Q177054: true, // Burger King - Q524757: true, // KFC - Q779845: true, // CBA - Q1205312: true // In-N-Out - }; + // Prefer a wiki commons logo sometimes.. #6361 + const preferCommons = { + Q177054: true, // Burger King + Q524757: true, // KFC + Q779845: true, // CBA + Q1205312: true // In-N-Out + }; - let logoURL; - let logoURLs = wikidata[qid] && wikidata[qid].logos; - if (logoURLs) { - if (logoURLs.wikidata && preferCommons[qid]) { - logoURL = logoURLs.wikidata; - } else if (logoURLs.facebook) { - logoURL = logoURLs.facebook; - } else if (logoURLs.twitter) { - logoURL = logoURLs.twitter; - } else { - logoURL = logoURLs.wikidata; - } - } + let logoURL; + let logoURLs = wikidata[qid] && wikidata[qid].logos; + if (logoURLs) { + if (logoURLs.wikidata && preferCommons[qid]) { + logoURL = logoURLs.wikidata; + } else if (logoURLs.facebook) { + logoURL = logoURLs.facebook; + } else if (logoURLs.twitter) { + logoURL = logoURLs.twitter; + } else { + logoURL = logoURLs.wikidata; + } + } - presets[suggestionID] = { - name: name, - icon: preset.icon, - imageURL: logoURL, - geometry: preset.geometry, - tags: tags, - addTags: suggestion.tags, - reference: preset.reference, - countryCodes: suggestion.countryCodes, - terms: (suggestion.matchNames || []), - matchScore: 2, - suggestion: true - }; - }); + presets[suggestionID] = { + name: name, + icon: preset.icon, + imageURL: logoURL, + geometry: preset.geometry, + tags: tags, + addTags: suggestion.tags, + reference: preset.reference, + countryCodes: suggestion.countryCodes, + terms: (suggestion.matchNames || []), + matchScore: 2, + suggestion: true + }; + }); - return presets; + return presets; } function stripLeadingUnderscores(str) { - return str.split('/') - .map(function(s) { return s.replace(/^_/,''); }) - .join('/'); + return str.split('/') + .map(function(s) { return s.replace(/^_/,''); }) + .join('/'); } function generatePresets(tstrings, faIcons, tnpIcons, searchableFieldIDs) { - var presets = {}; + let presets = {}; - glob.sync(__dirname + '/data/presets/presets/**/*.json').forEach(function(file) { - var preset = read(file); - var id = stripLeadingUnderscores(file.match(/presets\/presets\/([^.]*)\.json/)[1]); + glob.sync(__dirname + '/data/presets/presets/**/*.json').forEach(file => { + let preset = read(file); + let id = stripLeadingUnderscores(file.match(/presets\/presets\/([^.]*)\.json/)[1]); - validate(file, preset, presetSchema); + validate(file, preset, presetSchema); - tstrings.presets[id] = { - name: preset.name, - terms: (preset.terms || []).join(',') - }; + tstrings.presets[id] = { + name: preset.name, + terms: (preset.terms || []).join(',') + }; - if (preset.moreFields) { - preset.moreFields.forEach(function(fieldID) { - searchableFieldIDs[fieldID] = true; - }); - } + if (preset.moreFields) { + preset.moreFields.forEach(fieldID => { searchableFieldIDs[fieldID] = true; }); + } - presets[id] = preset; + presets[id] = preset; - // fontawesome icon, remember for later - if (/^fa[srb]-/.test(preset.icon)) { - faIcons[preset.icon] = {}; - } - // noun project icon, remember for later - if (/^tnp-/.test(preset.icon)) { - tnpIcons[preset.icon] = {}; - } - }); + // fontawesome icon, remember for later + if (/^fa[srb]-/.test(preset.icon)) { + faIcons[preset.icon] = {}; + } + // noun project icon, remember for later + if (/^tnp-/.test(preset.icon)) { + tnpIcons[preset.icon] = {}; + } + }); - presets = Object.assign(presets, suggestionsToPresets(presets)); - return presets; + presets = Object.assign(presets, suggestionsToPresets(presets)); + return presets; } function generateTranslations(fields, presets, tstrings, searchableFieldIDs) { - var translations = JSON.parse(JSON.stringify(tstrings)); // deep clone + let translations = JSON.parse(JSON.stringify(tstrings)); // deep clone - Object.keys(translations.fields).forEach(function(id) { - var field = translations.fields[id]; - var f = fields[id]; - var options = field.options || {}; - var optkeys = Object.keys(options); + Object.keys(translations.fields).forEach(id => { + let field = translations.fields[id]; + let f = fields[id]; + let options = field.options || {}; + let optkeys = Object.keys(options); - if (f.keys) { - field['label#'] = f.keys.map(function(k) { return k + '=*'; }).join(', '); - optkeys.forEach(function(k) { - if (id === 'access') { - options[k]['title#'] = options[k]['description#'] = 'access=' + k; - } else { - options[k + '#'] = k + '=yes'; - } - }); - } else if (f.key) { - field['label#'] = f.key + '=*'; - optkeys.forEach(function(k) { - options[k + '#'] = f.key + '=' + k; - }); - } - - if (f.placeholder) { - field['placeholder#'] = id + ' field placeholder'; - } - - if (searchableFieldIDs[id]) { - if (f.terms && f.terms.length) { - field['terms#'] = 'terms: ' + f.terms.join(); - } - field.terms = '[translate with synonyms or related terms for \'' + field.label + '\', separated by commas]'; + if (f.keys) { + field['label#'] = f.keys.map(k => { return k + '=*'; }).join(', '); + optkeys.forEach(k => { + if (id === 'access') { + options[k]['title#'] = options[k]['description#'] = 'access=' + k; } else { - delete tstrings.fields[id].terms; - delete f.terms; - delete field.terms; + options[k + '#'] = k + '=yes'; } - }); + }); + } else if (f.key) { + field['label#'] = f.key + '=*'; + optkeys.forEach(k => { + options[k + '#'] = f.key + '=' + k; + }); + } - Object.keys(translations.presets).forEach(function(id) { - var preset = translations.presets[id]; - var p = presets[id]; - var tags = p.tags || {}; - var keys = Object.keys(tags); + if (f.placeholder) { + field['placeholder#'] = id + ' field placeholder'; + } - if (keys.length) { - preset['name#'] = keys.map(function(k) { return k + '=' + tags[k]; }).join(', '); - } - if (p.searchable !== false) { - if (p.terms && p.terms.length) { - preset['terms#'] = 'terms: ' + p.terms.join(); - } - preset.terms = ''; - } else { - delete tstrings.presets[id].terms; - delete p.terms; - delete preset.terms; - } - }); + if (searchableFieldIDs[id]) { + if (f.terms && f.terms.length) { + field['terms#'] = 'terms: ' + f.terms.join(); + } + field.terms = '[translate with synonyms or related terms for \'' + field.label + '\', separated by commas]'; + } else { + delete tstrings.fields[id].terms; + delete f.terms; + delete field.terms; + } + }); - return translations; + Object.keys(translations.presets).forEach(id => { + let preset = translations.presets[id]; + let p = presets[id]; + let tags = p.tags || {}; + let keys = Object.keys(tags); + + if (keys.length) { + preset['name#'] = keys.map(k => { return k + '=' + tags[k]; }).join(', '); + } + + if (p.searchable !== false) { + if (p.terms && p.terms.length) { + preset['terms#'] = 'terms: ' + p.terms.join(); + } + preset.terms = ''; + } else { + delete tstrings.presets[id].terms; + delete p.terms; + delete preset.terms; + } + }); + + return translations; } function generateTaginfo(presets, fields) { - var taginfo = { - 'data_format': 1, - 'data_url': 'https://raw.githubusercontent.com/openstreetmap/iD/master/data/taginfo.json', - 'project': { - 'name': 'iD Editor', - 'description': 'Online editor for OSM data.', - 'project_url': 'https://github.com/openstreetmap/iD', - 'doc_url': 'https://github.com/openstreetmap/iD/blob/master/data/presets/README.md', - 'icon_url': 'https://cdn.jsdelivr.net/gh/openstreetmap/iD@release/dist/img/logo.png', - 'keywords': ['editor'] - }, - 'tags': [] + let taginfo = { + 'data_format': 1, + 'data_url': 'https://raw.githubusercontent.com/openstreetmap/iD/master/data/taginfo.json', + 'project': { + 'name': 'iD Editor', + 'description': 'Online editor for OSM data.', + 'project_url': 'https://github.com/openstreetmap/iD', + 'doc_url': 'https://github.com/openstreetmap/iD/blob/master/data/presets/README.md', + 'icon_url': 'https://cdn.jsdelivr.net/gh/openstreetmap/iD@release/dist/img/logo.png', + 'keywords': ['editor'] + }, + 'tags': [] + }; + + Object.keys(presets).forEach(id => { + let preset = presets[id]; + if (preset.suggestion) return; + + let keys = Object.keys(preset.tags); + let last = keys[keys.length - 1]; + let tag = { key: last }; + + if (!last) return; + + if (preset.tags[last] !== '*') { + tag.value = preset.tags[last]; + } + if (preset.name) { + let legacy = (preset.searchable === false) ? ' (unsearchable)' : ''; + tag.description = [ '🄿 ' + preset.name + legacy ]; + } + if (preset.geometry) { + setObjectType(tag, preset); + } + + // add icon + if (/^maki-/.test(preset.icon)) { + tag.icon_url = 'https://cdn.jsdelivr.net/gh/mapbox/maki/icons/' + + preset.icon.replace(/^maki-/, '') + '-15.svg'; + } else if (/^temaki-/.test(preset.icon)) { + tag.icon_url = 'https://cdn.jsdelivr.net/gh/bhousel/temaki/icons/' + + preset.icon.replace(/^temaki-/, '') + '.svg'; + } else if (/^fa[srb]-/.test(preset.icon)) { + tag.icon_url = 'https://cdn.jsdelivr.net/gh/openstreetmap/iD@master/svg/fontawesome/' + + preset.icon + '.svg'; + } else if (/^iD-/.test(preset.icon)) { + tag.icon_url = 'https://cdn.jsdelivr.net/gh/openstreetmap/iD@master/svg/iD-sprite/presets/' + + preset.icon.replace(/^iD-/, '') + '.svg'; + } else if (/^tnp-/.test(preset.icon)) { + tag.icon_url = 'https://cdn.jsdelivr.net/gh/openstreetmap/iD@master/svg/the-noun-project/' + + preset.icon.replace(/^tnp-/, '') + '.svg'; + } + + coalesceTags(taginfo, tag); + }); + + Object.keys(fields).forEach(id => { + const field = fields[id]; + const keys = field.keys || [ field.key ] || []; + const isRadio = (field.type === 'radio' || field.type === 'structureRadio'); + + keys.forEach(key => { + if (field.strings && field.strings.options && !isRadio) { + let values = Object.keys(field.strings.options); + values.forEach(value => { + if (value === 'undefined' || value === '*' || value === '') return; + let tag = { key: key, value: value }; + if (field.label) { + tag.description = [ '🄵 ' + field.label ]; + } + coalesceTags(taginfo, tag); + }); + } else { + let tag = { key: key }; + if (field.label) { + tag.description = [ '🄵 ' + field.label ]; + } + coalesceTags(taginfo, tag); + } + }); + }); + + deprecated.forEach(elem => { + let old = elem.old; + let oldKeys = Object.keys(old); + if (oldKeys.length === 1) { + let oldKey = oldKeys[0]; + let tag = { key: oldKey }; + + let oldValue = old[oldKey]; + if (oldValue !== '*') tag.value = oldValue; + let replacementStrings = []; + for (let replaceKey in elem.replace) { + let replaceValue = elem.replace[replaceKey]; + if (replaceValue === '$1') replaceValue = '*'; + replacementStrings.push(replaceKey + '=' + replaceValue); + } + let description = '🄳'; + if (replacementStrings.length > 0) { + description += ' ➜ ' + replacementStrings.join(' + '); + } + tag.description = [description]; + coalesceTags(taginfo, tag); + } + }); + + taginfo.tags.forEach(elem => { + if (elem.description) { + elem.description = elem.description.join(', '); + } + }); + + + function coalesceTags(taginfo, tag) { + if (!tag.key) return; + + let currentTaginfoEntries = taginfo.tags.filter(t => { + return (t.key === tag.key && t.value === tag.value); + }); + + if (currentTaginfoEntries.length === 0) { + taginfo.tags.push(tag); + return; + } + + if (!tag.description) return; + + if (!currentTaginfoEntries[0].description) { + currentTaginfoEntries[0].description = tag.description; + return; + } + + let isNewDescription = currentTaginfoEntries[0].description + .indexOf(tag.description[0]) === -1; + + if (isNewDescription) { + currentTaginfoEntries[0].description.push(tag.description[0]); + } + } + + + function setObjectType(tag, input) { + tag.object_types = []; + const mapping = { + 'point' : 'node', + 'vertex' : 'node', + 'line' : 'way', + 'relation' : 'relation', + 'area' : 'area' }; - Object.keys(presets).forEach(function(id) { - var preset = presets[id]; - if (preset.suggestion) return; - - var keys = Object.keys(preset.tags); - var last = keys[keys.length - 1]; - var tag = { key: last }; - - if (!last) return; - - if (preset.tags[last] !== '*') { - tag.value = preset.tags[last]; - } - if (preset.name) { - var legacy = (preset.searchable === false) ? ' (unsearchable)' : ''; - tag.description = [ '🄿 ' + preset.name + legacy ]; - } - if (preset.geometry) { - setObjectType(tag, preset); - } - - // add icon - if (/^maki-/.test(preset.icon)) { - tag.icon_url = 'https://cdn.jsdelivr.net/gh/mapbox/maki/icons/' + - preset.icon.replace(/^maki-/, '') + '-15.svg'; - } else if (/^temaki-/.test(preset.icon)) { - tag.icon_url = 'https://cdn.jsdelivr.net/gh/bhousel/temaki/icons/' + - preset.icon.replace(/^temaki-/, '') + '.svg'; - } else if (/^fa[srb]-/.test(preset.icon)) { - tag.icon_url = 'https://cdn.jsdelivr.net/gh/openstreetmap/iD@master/svg/fontawesome/' + - preset.icon + '.svg'; - } else if (/^iD-/.test(preset.icon)) { - tag.icon_url = 'https://cdn.jsdelivr.net/gh/openstreetmap/iD@master/svg/iD-sprite/presets/' + - preset.icon.replace(/^iD-/, '') + '.svg'; - } else if (/^tnp-/.test(preset.icon)) { - tag.icon_url = 'https://cdn.jsdelivr.net/gh/openstreetmap/iD@master/svg/the-noun-project/' + - preset.icon.replace(/^tnp-/, '') + '.svg'; - } - - coalesceTags(taginfo, tag); + input.geometry.forEach(geom => { + if (tag.object_types.indexOf(mapping[geom]) === -1) { + tag.object_types.push(mapping[geom]); + } }); + } - Object.keys(fields).forEach(function(id) { - var field = fields[id]; - var keys = field.keys || [ field.key ] || []; - var isRadio = (field.type === 'radio' || field.type === 'structureRadio'); - - keys.forEach(function(key) { - if (field.strings && field.strings.options && !isRadio) { - var values = Object.keys(field.strings.options); - values.forEach(function(value) { - if (value === 'undefined' || value === '*' || value === '') return; - var tag = { key: key, value: value }; - if (field.label) { - tag.description = [ '🄵 ' + field.label ]; - } - coalesceTags(taginfo, tag); - }); - } else { - var tag = { key: key }; - if (field.label) { - tag.description = [ '🄵 ' + field.label ]; - } - coalesceTags(taginfo, tag); - } - }); - }); - - deprecated.forEach(function(elem) { - var old = elem.old; - var oldKeys = Object.keys(old); - if (oldKeys.length === 1) { - var oldKey = oldKeys[0]; - var tag = { key: oldKey }; - - var oldValue = old[oldKey]; - if (oldValue !== '*') tag.value = oldValue; - var replacementStrings = []; - for (var replaceKey in elem.replace) { - var replaceValue = elem.replace[replaceKey]; - if (replaceValue === '$1') replaceValue = '*'; - replacementStrings.push(replaceKey + '=' + replaceValue); - } - var description = '🄳'; - if (replacementStrings.length > 0) { - description += ' ➜ ' + replacementStrings.join(' + '); - } - tag.description = [description]; - coalesceTags(taginfo, tag); - } - }); - - taginfo.tags.forEach(function(elem) { - if (elem.description) { - elem.description = elem.description.join(', '); - } - }); - - - function coalesceTags(taginfo, tag) { - if (!tag.key) return; - - var currentTaginfoEntries = taginfo.tags.filter(function(t) { - return (t.key === tag.key && t.value === tag.value); - }); - - if (currentTaginfoEntries.length === 0) { - taginfo.tags.push(tag); - return; - } - - if (!tag.description) - return; - - if (!currentTaginfoEntries[0].description) { - currentTaginfoEntries[0].description = tag.description; - return; - } - - var isNewDescription = currentTaginfoEntries[0].description - .indexOf(tag.description[0]) === -1; - - if (isNewDescription) { - currentTaginfoEntries[0].description.push(tag.description[0]); - } - } - - - function setObjectType(tag, input) { - tag.object_types = []; - const mapping = { - 'point' : 'node', - 'vertex' : 'node', - 'line' : 'way', - 'relation' : 'relation', - 'area' : 'area' - }; - - input.geometry.forEach(function(geom) { - if (tag.object_types.indexOf(mapping[geom]) === -1) { - tag.object_types.push(mapping[geom]); - } - }); - } - - return taginfo; + return taginfo; } + function generateTerritoryLanguages() { - var allRawInfo = read('./node_modules/cldr-core/supplemental/territoryInfo.json').supplemental.territoryInfo; - var territoryLanguages = {}; - Object.keys(allRawInfo).forEach(function(territoryCode) { - var territoryLangInfo = allRawInfo[territoryCode].languagePopulation; - if (!territoryLangInfo) return; - var langCodes = Object.keys(territoryLangInfo); - territoryLanguages[territoryCode.toLowerCase()] = langCodes.sort(function(langCode1, langCode2) { - var popPercent1 = parseFloat(territoryLangInfo[langCode1]._populationPercent); - var popPercent2 = parseFloat(territoryLangInfo[langCode2]._populationPercent); - if (popPercent1 === popPercent2) { - return langCode1.localeCompare(langCode2, 'en', { sensitivity: 'base' }); - } - return popPercent2 - popPercent1; - }).map(function(langCode) { - return langCode.replace('_', '-'); - }); - }); - return territoryLanguages; + let allRawInfo = read('./node_modules/cldr-core/supplemental/territoryInfo.json').supplemental.territoryInfo; + let territoryLanguages = {}; + + Object.keys(allRawInfo).forEach(territoryCode => { + let territoryLangInfo = allRawInfo[territoryCode].languagePopulation; + if (!territoryLangInfo) return; + let langCodes = Object.keys(territoryLangInfo); + + territoryLanguages[territoryCode.toLowerCase()] = langCodes.sort(function(langCode1, langCode2) { + let popPercent1 = parseFloat(territoryLangInfo[langCode1]._populationPercent); + let popPercent2 = parseFloat(territoryLangInfo[langCode2]._populationPercent); + if (popPercent1 === popPercent2) { + return langCode1.localeCompare(langCode2, 'en', { sensitivity: 'base' }); + } + return popPercent2 - popPercent1; + }).map(langCode => { return langCode.replace('_', '-'); }); + }); + + return territoryLanguages; } + function validateCategoryPresets(categories, presets) { - Object.keys(categories).forEach(function(id) { - var category = categories[id]; - if (category.members) { - category.members.forEach(function(preset) { - if (presets[preset] === undefined) { - console.error('Unknown preset: ' + preset + ' in category ' + category.name); - process.exit(1); - } - }); - } + Object.keys(categories).forEach(id => { + const category = categories[id]; + if (!category.members) return; + category.members.forEach(preset => { + if (presets[preset] === undefined) { + console.error('Unknown preset: ' + preset + ' in category ' + category.name); + process.exit(1); + } }); + }); } function validatePresetFields(presets, fields) { - var betweenBracketsRegex = /([^{]*?)(?=\})/; - var maxFieldsBeforeError = 12; - var maxFieldsBeforeWarning = 8; - for (var presetID in presets) { - var preset = presets[presetID]; + const betweenBracketsRegex = /([^{]*?)(?=\})/; + const maxFieldsBeforeError = 12; + const maxFieldsBeforeWarning = 8; - if (preset.replacement) { - var replacementPreset = presets[preset.replacement]; - var p1geometry = preset.geometry.slice().sort.toString(); - var p2geometry = replacementPreset.geometry.slice().sort.toString(); - if (replacementPreset === undefined) { - console.error('Unknown preset "' + preset.replacement + '" referenced as replacement of preset ' + preset.name); - process.exit(1); - } else if (p1geometry !== p2geometry) { - console.error('The preset "' + presetID + '" has different geometry than its replacement preset, "' + preset.replacement + '". They must match for tag upgrades to work.'); - process.exit(1); - } - } + for (let presetID in presets) { + let preset = presets[presetID]; - // the keys for properties that contain arrays of field ids - var fieldKeys = ['fields', 'moreFields']; - for (var fieldsKeyIndex in fieldKeys) { - var fieldsKey = fieldKeys[fieldsKeyIndex]; - if (!preset[fieldsKey]) continue; // no fields are referenced, okay - - for (var fieldIndex in preset[fieldsKey]) { - var field = preset[fieldsKey][fieldIndex]; - if (fields[field] !== undefined) continue; // field found, okay - - var regexResult = betweenBracketsRegex.exec(field); - if (regexResult) { - var foreignPresetID = regexResult[0]; - if (presets[foreignPresetID] === undefined) { - console.error('Unknown preset "' + foreignPresetID + '" referenced in "' + fieldsKey + '" array of preset ' + preset.name); - process.exit(1); - } - } else { - console.error('Unknown preset field "' + field + '" in "' + fieldsKey + '" array of preset ' + preset.name); - process.exit(1); - } - } - } - - if (preset.fields) { - // since `moreFields` is available, check that `fields` doesn't get too cluttered - var fieldCount = preset.fields.length; - - if (fieldCount > maxFieldsBeforeWarning) { - // Fields with `prerequisiteTag` probably won't show up initially, - // so don't count them against the limits. - var fieldsWithoutPrerequisites = preset.fields.filter(function(fieldID) { - if (fields[fieldID] && fields[fieldID].prerequisiteTag) { - return false; - } - return true; - }); - fieldCount = fieldsWithoutPrerequisites.length; - } - if (fieldCount > maxFieldsBeforeError) { - console.error(fieldCount + ' values in "fields" of "' + preset.name + '" (' + presetID + '). Limit: ' + maxFieldsBeforeError + '. Please move lower-priority fields to "moreFields".'); - process.exit(1); - } - else if (fieldCount > maxFieldsBeforeWarning) { - console.log('Warning: ' + fieldCount + ' values in "fields" of "' + preset.name + '" (' + presetID + '). Recommended: ' + maxFieldsBeforeWarning + ' or fewer. Consider moving lower-priority fields to "moreFields".'); - } - } + if (preset.replacement) { + let replacementPreset = presets[preset.replacement]; + let p1geometry = preset.geometry.slice().sort.toString(); + let p2geometry = replacementPreset.geometry.slice().sort.toString(); + if (replacementPreset === undefined) { + console.error('Unknown preset "' + preset.replacement + '" referenced as replacement of preset ' + preset.name); + process.exit(1); + } else if (p1geometry !== p2geometry) { + console.error('The preset "' + presetID + '" has different geometry than its replacement preset, "' + preset.replacement + '". They must match for tag upgrades to work.'); + process.exit(1); + } } + + // the keys for properties that contain arrays of field ids + let fieldKeys = ['fields', 'moreFields']; + for (let fieldsKeyIndex in fieldKeys) { + let fieldsKey = fieldKeys[fieldsKeyIndex]; + if (!preset[fieldsKey]) continue; // no fields are referenced, okay + + for (let fieldIndex in preset[fieldsKey]) { + let field = preset[fieldsKey][fieldIndex]; + if (fields[field] !== undefined) continue; // field found, okay + + let regexResult = betweenBracketsRegex.exec(field); + if (regexResult) { + let foreignPresetID = regexResult[0]; + if (presets[foreignPresetID] === undefined) { + console.error('Unknown preset "' + foreignPresetID + '" referenced in "' + fieldsKey + '" array of preset ' + preset.name); + process.exit(1); + } + } else { + console.error('Unknown preset field "' + field + '" in "' + fieldsKey + '" array of preset ' + preset.name); + process.exit(1); + } + } + } + + if (preset.fields) { + // since `moreFields` is available, check that `fields` doesn't get too cluttered + let fieldCount = preset.fields.length; + + if (fieldCount > maxFieldsBeforeWarning) { + // Fields with `prerequisiteTag` probably won't show up initially, + // so don't count them against the limits. + let fieldsWithoutPrerequisites = preset.fields.filter(fieldID => { + if (fields[fieldID] && fields[fieldID].prerequisiteTag) return false; + return true; + }); + fieldCount = fieldsWithoutPrerequisites.length; + } + if (fieldCount > maxFieldsBeforeError) { + console.error(fieldCount + ' values in "fields" of "' + preset.name + '" (' + presetID + '). Limit: ' + maxFieldsBeforeError + '. Please move lower-priority fields to "moreFields".'); + process.exit(1); + } + else if (fieldCount > maxFieldsBeforeWarning) { + console.log('Warning: ' + fieldCount + ' values in "fields" of "' + preset.name + '" (' + presetID + '). Recommended: ' + maxFieldsBeforeWarning + ' or fewer. Consider moving lower-priority fields to "moreFields".'); + } + } + } } -function validateDefaults (defaults, categories, presets) { - Object.keys(defaults.defaults).forEach(function(name) { - var members = defaults.defaults[name]; - members.forEach(function (id) { - if (!presets[id] && !categories[id]) { - console.error('Unknown category or preset: ' + id + ' in default ' + name); - process.exit(1); - } - }); +function validateDefaults(defaults, categories, presets) { + Object.keys(defaults.defaults).forEach(name => { + let members = defaults.defaults[name]; + members.forEach(id => { + if (!presets[id] && !categories[id]) { + console.error('Unknown category or preset: ' + id + ' in default ' + name); + process.exit(1); + } }); + }); } function translationsToYAML(translations) { - // comment keys end with '#' and should sort immediately before their related key. - function commentFirst(a, b) { - return (a === b + '#') ? -1 - : (b === a + '#') ? 1 - : (a > b ? 1 : a < b ? -1 : 0); - } + // comment keys end with '#' and should sort immediately before their related key. + function commentFirst(a, b) { + return (a === b + '#') ? -1 + : (b === a + '#') ? 1 + : (a > b ? 1 : a < b ? -1 : 0); + } - return YAML.safeDump({ en: { presets: translations }}, { sortKeys: commentFirst, lineWidth: -1 }) - .replace(/\'.*#\':/g, '#'); + return YAML.safeDump({ en: { presets: translations }}, { sortKeys: commentFirst, lineWidth: -1 }) + .replace(/\'.*#\':/g, '#'); } function writeEnJson(tstrings) { - var readCoreYaml = readFileProm('data/core.yaml', 'utf8'); - var readImagery = readFileProm('node_modules/editor-layer-index/i18n/en.yaml', 'utf8'); - var readCommunity = readFileProm('node_modules/osm-community-index/i18n/en.yaml', 'utf8'); + const readCoreYaml = readFileProm('data/core.yaml', 'utf8'); + const readImagery = readFileProm('node_modules/editor-layer-index/i18n/en.yaml', 'utf8'); + const readCommunity = readFileProm('node_modules/osm-community-index/i18n/en.yaml', 'utf8'); - return Promise.all([readCoreYaml, readImagery, readCommunity]).then(function(data) { - var core = YAML.load(data[0]); - var imagery = YAML.load(data[1]); - var community = YAML.load(data[2]); + return Promise.all([readCoreYaml, readImagery, readCommunity]) + .then(data => { + let core = YAML.load(data[0]); + let imagery = YAML.load(data[1]); + let community = YAML.load(data[2]); - var enjson = core; - enjson.en.presets = tstrings; - enjson.en.imagery = imagery.en.imagery; - enjson.en.community = community.en; + let enjson = core; + enjson.en.presets = tstrings; + enjson.en.imagery = imagery.en.imagery; + enjson.en.community = community.en; - return writeFileProm('dist/locales/en.json', JSON.stringify(enjson, null, 4)); + return writeFileProm('dist/locales/en.json', JSON.stringify(enjson, null, 4)); }); } function writeFaIcons(faIcons) { - for (var key in faIcons) { - var prefix = key.substring(0, 3); // `fas`, `far`, `fab` - var name = key.substring(4); - var def = fontawesome.findIconDefinition({ prefix: prefix, iconName: name }); - try { - writeFileProm('svg/fontawesome/' + key + '.svg', fontawesome.icon(def).html); - } catch (error) { - console.error('Error: No FontAwesome icon for ' + key); - throw (error); - } + for (const key in faIcons) { + const prefix = key.substring(0, 3); // `fas`, `far`, `fab` + const name = key.substring(4); + const def = fontawesome.findIconDefinition({ prefix: prefix, iconName: name }); + try { + writeFileProm('svg/fontawesome/' + key + '.svg', fontawesome.icon(def).html); + } catch (error) { + console.error('Error: No FontAwesome icon for ' + key); + throw (error); } + } } function writeTnpIcons(tnpIcons) { - /* - * The Noun Project doesn't allow anonymous API access. New "tnp-" icons will - * not be downloaded without a "the_noun_project.auth" file with a json object: - * { - * "consumer_key": "xxxxxx", - * "consumer_secret": "xxxxxx" - * } - * */ - var nounAuth; - if (fs.existsSync('./the_noun_project.auth')) { - nounAuth = JSON.parse(fs.readFileSync('./the_noun_project.auth', 'utf8')); - } - var baseURL = 'http://api.thenounproject.com/icon/'; + /* + * The Noun Project doesn't allow anonymous API access. New "tnp-" icons will + * not be downloaded without a "the_noun_project.auth" file with a json object: + * { + * "consumer_key": "xxxxxx", + * "consumer_secret": "xxxxxx" + * } + */ + let nounAuth; + if (fs.existsSync('./the_noun_project.auth')) { + nounAuth = JSON.parse(fs.readFileSync('./the_noun_project.auth', 'utf8')); + } + const baseURL = 'http://api.thenounproject.com/icon/'; - var unusedSvgFiles = fs.readdirSync('svg/the-noun-project', 'utf8').reduce(function(obj, name) { - if (name.endsWith('.svg')) { - obj[name] = true; - } - return obj; + let unusedSvgFiles = fs.readdirSync('svg/the-noun-project', 'utf8') + .reduce(function(obj, name) { + if (name.endsWith('.svg')) { + obj[name] = true; + } + return obj; }, {}); - for (var key in tnpIcons) { - var id = key.substring(4); - var fileName = id + '.svg'; + for (const key in tnpIcons) { + const id = key.substring(4); + const fileName = id + '.svg'; - if (unusedSvgFiles[fileName]) { - delete unusedSvgFiles[fileName]; - } - - var localPath = 'svg/the-noun-project/' + fileName; - - // don't redownload existing icons - if (fs.existsSync(localPath)) continue; - - if (!nounAuth) { - console.error('No authentication file for The Noun Project. Cannot download icon: ' + key); - continue; - } - - var url = baseURL + id; - request.get(url, { oauth : nounAuth }, handleTheNounProjectResponse); + if (unusedSvgFiles[fileName]) { + delete unusedSvgFiles[fileName]; } - // remove icons that are not needed - for (var unusedFileName in unusedSvgFiles) { - shell.rm('-f', [ - 'svg/the-noun-project/' + unusedFileName - ]); + const localPath = 'svg/the-noun-project/' + fileName; + + // don't redownload existing icons + if (fs.existsSync(localPath)) continue; + + if (!nounAuth) { + console.error('No authentication file for The Noun Project. Cannot download icon: ' + key); + continue; } + + const url = baseURL + id; + request.get(url, { oauth : nounAuth }, handleTheNounProjectResponse); + } + + // remove icons that are not needed + for (const unusedFileName in unusedSvgFiles) { + shell.rm('-f', ['svg/the-noun-project/' + unusedFileName]); + } } + function handleTheNounProjectResponse(err, resp, body) { - if (err) { - console.error(err); - return; + if (err) { + console.error(err); + return; + } + let icon = JSON.parse(body).icon; + if (icon.license_description !== 'public-domain') { + console.error('The icon "' + icon.term + '" (tnp-' + icon.id + ') from The Noun Project cannot be used in iD because it is not in the public domain.'); + return; + } + let iconURL = icon.icon_url; + if (!iconURL) { + console.error('The Noun Project has not provided a URL to download the icon "' + icon.term + '" (tnp-' + icon.id + ').'); + return; + } + request.get(iconURL, function(err2, resp2, svg) { + if (err2) { + console.error(err2); + return; } - var icon = JSON.parse(body).icon; - if (icon.license_description !== 'public-domain') { - console.error('The icon "' + icon.term + '" (tnp-' + icon.id + ') from The Noun Project cannot be used in iD because it is not in the public domain.'); - return; + try { + writeFileProm('svg/the-noun-project/' + icon.id + '.svg', svg); + } catch (error) { + console.error(error); + throw (error); } - var iconURL = icon.icon_url; - if (!iconURL) { - console.error('The Noun Project has not provided a URL to download the icon "' + icon.term + '" (tnp-' + icon.id + ').'); - return; - } - request.get(iconURL, function(err2, resp2, svg) { - if (err2) { - console.error(err2); - return; - } - try { - writeFileProm('svg/the-noun-project/' + icon.id + '.svg', svg); - } catch (error) { - console.error(error); - throw (error); - } - }); + }); } function writeFileProm(path, content) { - return new Promise(function(res, rej) { - fs.writeFile(path, content, function(err) { - if (err) { - return rej(err); - } - res(); - }); + return new Promise((resolve, reject) => { + fs.writeFile(path, content, (err) => { + if (err) return reject(err); + resolve(); }); + }); } function readFileProm(path, options) { - return new Promise(function(res, rej) { - fs.readFile(path, options, function(err, data) { - if (err) { - return rej(err); - } - res(data); - }); + return new Promise((resolve, reject) => { + fs.readFile(path, options, (err, data) => { + if (err) return reject(err); + resolve(data); }); + }); } + +module.exports = buildData; \ No newline at end of file diff --git a/build_src.js b/build_src.js index 937a7e9e6..045eb43e3 100644 --- a/build_src.js +++ b/build_src.js @@ -8,65 +8,65 @@ const rollup = require('rollup'); const shell = require('shelljs'); //const visualizer = require('rollup-plugin-visualizer'); +let _isBuilding = false; + module.exports = function buildSrc() { - var isBuilding = false; + return function () { + if (_isBuilding) return; - return function () { - if (isBuilding) return; + // Start clean + shell.rm('-f', [ + //'docs/statistics.html', + 'dist/iD.js', + 'dist/iD.js.map' + ]); - // Start clean - shell.rm('-f', [ - //'docs/statistics.html', - 'dist/iD.js', - 'dist/iD.js.map' - ]); + console.log('building src'); + console.time(colors.green('src built')); - console.log('building src'); - console.time(colors.green('src built')); + _isBuilding = true; - isBuilding = true; - - return rollup - .rollup({ - input: './modules/id.js', - plugins: [ - includePaths( { - paths: ['node_modules/d3/node_modules'], // npm2 or windows - include: { - 'martinez-polygon-clipping': 'node_modules/martinez-polygon-clipping/dist/martinez.umd.js' - } - }), - nodeResolve({ - mainFields: ['module', 'main'], - browser: false, - dedupe: ['object-inspect'] - }), - commonjs(), - json({ indent: '' }), - // viz causes src build to take about 3x longer; skip - // visualizer({ - // filename: 'docs/statistics.html', - // sourcemap: true - // }) - ] - }) - .then(function (bundle) { - return bundle.write({ - format: 'iife', - file: 'dist/iD.js', - sourcemap: true, - strict: false - }); - }) - .then(function () { - isBuilding = false; - console.timeEnd(colors.green('src built')); - }) - .catch(function (err) { - isBuilding = false; - console.error(err); - process.exit(1); - }); - }; + return rollup + .rollup({ + input: './modules/id.js', + plugins: [ + includePaths({ + paths: ['node_modules/d3/node_modules'], // npm2 or windows + include: { + 'martinez-polygon-clipping': 'node_modules/martinez-polygon-clipping/dist/martinez.umd.js' + } + }), + nodeResolve({ + mainFields: ['module', 'main'], + browser: false, + dedupe: ['object-inspect'] + }), + commonjs(), + json({ indent: '' }), + // viz causes src build to take about 3x longer; skip + // visualizer({ + // filename: 'docs/statistics.html', + // sourcemap: true + // }) + ] + }) + .then((bundle) => { + return bundle.write({ + format: 'iife', + file: 'dist/iD.js', + sourcemap: true, + strict: false + }); + }) + .then(() => { + _isBuilding = false; + console.timeEnd(colors.green('src built')); + }) + .catch((err) => { + _isBuilding = false; + console.error(err); + process.exit(1); + }); + }; }; diff --git a/development_server.js b/development_server.js deleted file mode 100644 index 23f8ac459..000000000 --- a/development_server.js +++ /dev/null @@ -1,60 +0,0 @@ -/* eslint-disable no-console */ -const colors = require('colors/safe'); -const gaze = require('gaze'); -const StaticServer = require('static-server'); - -const isDevelopment = process.argv[2] === 'develop'; - -const buildData = require('./build_data')(isDevelopment); -const buildSrc = require('./build_src')(isDevelopment); -const buildCSS = require('./build_css')(isDevelopment); - - -buildData() - .then(function () { - return buildSrc(); - }); - -buildCSS(); - -if (isDevelopment) { - gaze(['css/**/*.css'], function(err, watcher) { - watcher.on('all', function() { - buildCSS(); - }); - }); - - gaze([ - 'data/**/*.{js,json}', - 'data/core.yaml', - // ignore the output files of `buildData` - '!data/presets/categories.json', - '!data/presets/fields.json', - '!data/presets/presets.json', - '!data/presets.yaml', - '!data/taginfo.json', - '!data/territory-languages.json', - '!dist/locales/en.json' - ], - function(err, watcher) { - watcher.on('all', function() { - buildData() - .then(function () { - // need to recompute js files when data changes - buildSrc(); - }); - }); - } - ); - - gaze(['modules/**/*.js'], function(err, watcher) { - watcher.on('all', function() { - buildSrc(); - }); - }); - - const server = new StaticServer({ rootPath: __dirname, port: 8080, followSymlink: true }); - server.start(function () { - console.log(colors.yellow('Listening on ' + server.port)); - }); -} diff --git a/package.json b/package.json index 8c1fe3a2f..b109b3ecb 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "license": "ISC", "scripts": { "all": "npm-run-all -s clean build dist", - "build": "node development_server.js", + "build": "node build.js", "clean": "shx rm -f dist/*.js dist/*.map dist/*.css dist/img/*.svg", "dist": "npm-run-all -p dist:**", "dist:mapillary": "shx mkdir -p dist/mapillary-js && shx cp -R node_modules/mapillary-js/dist/* dist/mapillary-js/", @@ -27,7 +27,7 @@ "dist:svg:temaki": "svg-sprite --symbol --symbol-dest . --shape-id-generator \"temaki-%s\" --symbol-sprite dist/img/temaki-sprite.svg node_modules/temaki/icons/*.svg", "imagery": "node data/update_imagery", "lint": "eslint *.js test/spec modules", - "start": "node development_server.js develop", + "start": "node server.js", "phantom": "phantomjs --web-security=no node_modules/mocha-phantomjs-core/mocha-phantomjs-core.js test/phantom.html spec", "test": "npm-run-all -s lint build test:**", "test:phantom": "phantomjs --web-security=no node_modules/mocha-phantomjs-core/mocha-phantomjs-core.js test/index.html spec", @@ -62,6 +62,7 @@ "@fortawesome/free-regular-svg-icons": "^5.11.2", "@fortawesome/free-solid-svg-icons": "^5.11.2", "@mapbox/maki": "^6.0.0", + "@rollup/plugin-buble": "^0.20.0", "chai": "^4.1.0", "cldr-core": "36.0.0", "cldr-localenames-full": "36.0.0", diff --git a/server.js b/server.js new file mode 100644 index 000000000..0b4738c82 --- /dev/null +++ b/server.js @@ -0,0 +1,51 @@ +/* eslint-disable no-console */ +const colors = require('colors/safe'); +const gaze = require('gaze'); +const StaticServer = require('static-server'); + +const buildAll = require('./build'); +const buildData = require('./build_data'); +const buildSrc = require('./build_src'); +const buildCSS = require('./build_css'); + +const CSSFILES = ['css/**/*.css']; +const SRCFILES = ['modules/**/*.js']; +const DATAFILES = [ + 'data/**/*.{js,json}', + 'data/core.yaml', + // ignore the output files of `buildData` + '!data/presets/categories.json', + '!data/presets/fields.json', + '!data/presets/presets.json', + '!data/presets.yaml', + '!data/taginfo.json', + '!data/territory-languages.json', + '!dist/locales/en.json' +]; + + +buildAll() + .then(() => startServer()); + + +function startServer() { + gaze(CSSFILES, (err, watcher) => { + watcher.on('all', () => buildCSS()); + }); + + gaze(DATAFILES, (err, watcher) => { + watcher.on('all', () => { + buildData() + .then(() => buildSrc()); + }); + }); + + gaze(SRCFILES, (err, watcher) => { + watcher.on('all', () => buildSrc()); + }); + + const server = new StaticServer({ rootPath: __dirname, port: 8080, followSymlink: true }); + server.start(() => { + console.log(colors.yellow('Listening on ' + server.port)); + }); +};