import {Conversion} from "./Conversion"; import LayerConfig from "../LayerConfig"; import {LayerConfigJson} from "../Json/LayerConfigJson"; import Translations from "../../../UI/i18n/Translations"; import PointRenderingConfigJson from "../Json/PointRenderingConfigJson"; import {Translation, TypedTranslation} from "../../../UI/i18n/Translation"; export default class CreateNoteImportLayer extends Conversion { /** * A closed note is included if it is less then 'n'-days closed * @private */ private readonly _includeClosedNotesDays: number; constructor(includeClosedNotesDays = 0) { super([ "Advanced conversion which deducts a layer showing all notes that are 'importable' (i.e. a note that contains a link to some MapComplete theme, with hash '#import').", "The import buttons and matches will be based on the presets of the given theme", ].join("\n\n"), [], "CreateNoteImportLayer") this._includeClosedNotesDays = includeClosedNotesDays; } convert(layerJson: LayerConfigJson, context: string): { result: LayerConfigJson } { const t = Translations.t.importLayer; /** * The note itself will contain `tags=k=v;k=v;k=v;... * This must be matched with a regex. * This is a simple JSON-object as how it'll be put into the layerConfigJson directly */ const isShownIfAny: any[] = [] const layer = new LayerConfig(layerJson, "while constructing a note-import layer") for (const preset of layer.presets) { const mustMatchAll = [] for (const tag of preset.tags) { const key = tag.key const value = tag.value const condition = "_tags~(^|.*;)" + key + "\=" + value + "($|;.*)" mustMatchAll.push(condition) } isShownIfAny.push({and: mustMatchAll}) } const pointRenderings = (layerJson.mapRendering ?? []).filter(r => r !== null && r["location"] !== undefined); const firstRender = (pointRenderings [0]) if (firstRender === undefined) { throw `Layer ${layerJson.id} does not have a pointRendering: ` + context } const title = layer.presets[0].title const importButton = {} { const translations = trs(t.importButton, {layerId: layer.id, title: layer.presets[0].title}) for (const key in translations) { if (key !== "_context") { importButton[key] = "{" + translations[key] + "}" } else { importButton[key] = translations[key] } } } function embed(prefix, translation: Translation, postfix) { const result = {} for (const language in translation.translations) { result[language] = prefix + translation.translations[language] + postfix } result["_context"] = translation.context return result } function tr(translation: Translation) { return {...translation.translations, "_context": translation.context} } function trs(translation: TypedTranslation, subs: T): object { return {...translation.Subs(subs).translations, "_context": translation.context} } const result: LayerConfigJson = { "id": "note_import_" + layer.id, // By disabling the name, the import-layers won't pollute the filter view "name": t.layerName.Subs({title: layer.title.render}).translations, "description": trs(t.description, {title: layer.title.render}), "source": { "osmTags": { "and": [ "id~*" ] }, "geoJson": "https://api.openstreetmap.org/api/0.6/notes.json?limit=10000&closed=" + this._includeClosedNotesDays + "&bbox={x_min},{y_min},{x_max},{y_max}", "geoJsonZoomLevel": 10, "maxCacheAge": 0 }, "minzoom": Math.min(12, layerJson.minzoom - 2), "title": { "render": trs(t.popupTitle, {title}) }, "calculatedTags": [ "_first_comment=feat.get('comments')[0].text.toLowerCase()", "_trigger_index=(() => {const lines = feat.properties['_first_comment'].split('\\n'); const matchesMapCompleteURL = lines.map(l => l.match(\".*https://mapcomplete.osm.be/\\([a-zA-Z_-]+\\)\\(.html\\)?.*#import\")); const matchedIndexes = matchesMapCompleteURL.map((doesMatch, i) => [doesMatch !== null, i]).filter(v => v[0]).map(v => v[1]); return matchedIndexes[0] })()", "_comments_count=feat.get('comments').length", "_intro=(() => {const lines = feat.get('comments')[0].text.split('\\n'); lines.splice(feat.get('_trigger_index')-1, lines.length); return lines.filter(l => l !== '').join('
');})()", "_tags=(() => {let lines = feat.get('comments')[0].text.split('\\n').map(l => l.trim()); lines.splice(0, feat.get('_trigger_index') + 1); lines = lines.filter(l => l != ''); return lines.join(';');})()" ], "isShown": { and: ["_trigger_index~*", {or: isShownIfAny} ] }, "titleIcons": [ { "render": "" } ], "tagRenderings": [ { "id": "Intro", render: "{_intro}" }, { "id": "conversation", "render": "{visualize_note_comments(comments,1)}", condition: "_comments_count>1" }, { "id": "import", "render": importButton, condition: "closed_at=" }, { "id": "close_note_", "render": embed( "{close_note(", t.notFound.Subs({title}), ", ./assets/svg/close.svg, id, This feature does not exist, 18)}"), condition: "closed_at=" }, { "id": "close_note_mapped", "render": embed("{close_note(", t.alreadyMapped.Subs({title}), ", ./assets/svg/duplicate.svg, id, Already mapped, 18)}"), condition: "closed_at=" }, { "id": "handled", "render": tr(t.importHandled), condition: "closed_at~*" }, { "id": "comment", "render": "{add_note_comment()}" }, { "id": "add_image", "render": "{add_image_to_note()}" }, { "id": "nearby_images", render: tr(t.nearbyImagesIntro) } ], "mapRendering": [ { "location": [ "point" ], "icon": { "render": "circle:white;help:black", mappings: [{ if: {or: ["closed_at~*", "_imported=yes"]}, then: "circle:white;checkmark:black" }] }, "iconSize": "40,40,center" } ] } return { result }; } }