| 
									
										
										
										
											2023-10-10 01:52:02 +02:00
										 |  |  | import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFile, writeFileSync } from "fs" | 
					
						
							|  |  |  | import Locale from "../src/UI/i18n/Locale" | 
					
						
							|  |  |  | import Translations from "../src/UI/i18n/Translations" | 
					
						
							|  |  |  | import { Translation } from "../src/UI/i18n/Translation" | 
					
						
							|  |  |  | import all_known_layouts from "../src/assets/generated/known_themes.json" | 
					
						
							|  |  |  | import { LayoutConfigJson } from "../src/Models/ThemeConfig/Json/LayoutConfigJson" | 
					
						
							|  |  |  | import LayoutConfig from "../src/Models/ThemeConfig/LayoutConfig" | 
					
						
							|  |  |  | import xml2js from "xml2js" | 
					
						
							|  |  |  | import ScriptUtils from "./ScriptUtils" | 
					
						
							|  |  |  | import { Utils } from "../src/Utils" | 
					
						
							|  |  |  | import SpecialVisualizations from "../src/UI/SpecialVisualizations" | 
					
						
							|  |  |  | import Constants from "../src/Models/Constants" | 
					
						
							|  |  |  | import { AvailableRasterLayers, RasterLayerPolygon } from "../src/Models/RasterLayers" | 
					
						
							|  |  |  | import { ImmutableStore } from "../src/Logic/UIEventSource" | 
					
						
							| 
									
										
										
										
											2024-08-14 12:08:48 +02:00
										 |  |  | import * as eli from "../public/assets/data/editor-layer-index.json" | 
					
						
							| 
									
										
										
										
											2023-10-10 01:52:02 +02:00
										 |  |  | import * as eli_global from "../src/assets/global-raster-layers.json" | 
					
						
							| 
									
										
										
										
											2023-11-19 16:31:58 +01:00
										 |  |  | import ValidationUtils from "../src/Models/ThemeConfig/Conversion/ValidationUtils" | 
					
						
							| 
									
										
										
										
											2024-01-14 22:24:35 +01:00
										 |  |  | import { LayerConfigJson } from "../src/Models/ThemeConfig/Json/LayerConfigJson" | 
					
						
							|  |  |  | import { QuestionableTagRenderingConfigJson } from "../src/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson" | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  | import Script from "./Script" | 
					
						
							|  |  |  | import crypto from "crypto" | 
					
						
							| 
									
										
										
										
											2023-10-08 17:38:28 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-17 14:17:33 +01:00
										 |  |  | const sharp = require("sharp") | 
					
						
							| 
									
										
										
										
											2021-01-06 02:21:50 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  | class GenerateLayouts extends Script { | 
					
						
							|  |  |  |     private readonly template = readFileSync("theme.html", "utf8") | 
					
						
							|  |  |  |     private readonly codeTemplate = readFileSync("src/index_theme.ts.template", "utf8") | 
					
						
							|  |  |  |     private readonly removeOtherLanguages = readFileSync("src/UI/RemoveOtherLanguages.ts", "utf8") | 
					
						
							|  |  |  |         .split("\n") | 
					
						
							|  |  |  |         .slice(1) | 
					
						
							|  |  |  |         .map((s) => s.trim()) | 
					
						
							|  |  |  |         .filter((s) => s !== "") | 
					
						
							|  |  |  |         .join("\n") | 
					
						
							|  |  |  |     private readonly removeOtherLanguagesHash = | 
					
						
							|  |  |  |         "sha256-" + crypto.createHash("sha256").update(this.removeOtherLanguages).digest("base64") | 
					
						
							|  |  |  |     private previousSrc: Set<string> = new Set<string>() | 
					
						
							|  |  |  |     private eliUrlsCached: string[] | 
					
						
							|  |  |  |     private date = new Date().toISOString() | 
					
						
							| 
									
										
										
										
											2024-03-11 14:29:15 +01:00
										 |  |  |     private branchName: string = undefined | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     constructor() { | 
					
						
							|  |  |  |         super("Generates an '<theme>.html' and 'index_<theme>.ts' for every theme") | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     enc(str: string): string { | 
					
						
							|  |  |  |         return encodeURIComponent(str.toLowerCase()) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 14:29:15 +01:00
										 |  |  |     getBranchName(): Promise<string> { | 
					
						
							|  |  |  |         if (this.branchName) { | 
					
						
							|  |  |  |             return Promise.resolve(this.branchName) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         const { exec } = require("child_process") | 
					
						
							|  |  |  |         return new Promise<string>((resolve, reject) => { | 
					
						
							|  |  |  |             exec("git rev-parse --abbrev-ref HEAD", (err, stdout, stderr) => { | 
					
						
							|  |  |  |                 if (err) { | 
					
						
							|  |  |  |                     reject(err) | 
					
						
							|  |  |  |                     return | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2020-07-26 02:01:34 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 14:29:15 +01:00
										 |  |  |                 if (typeof stdout === "string") { | 
					
						
							|  |  |  |                     this.branchName = stdout.trim() | 
					
						
							|  |  |  |                     resolve(stdout.trim()) | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 reject("Did not get output") | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |         }) | 
					
						
							| 
									
										
										
										
											2020-07-26 02:01:34 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-03-08 04:09:03 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |     async createIcon(iconPath: string, size: number, alreadyWritten: string[]) { | 
					
						
							|  |  |  |         let name = iconPath.split(".").slice(0, -1).join(".") // drop svg suffix
 | 
					
						
							|  |  |  |         if (name.startsWith("./")) { | 
					
						
							|  |  |  |             name = name.substring(2) | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-03-08 04:09:03 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         const newname = `assets/generated/images/${name.replace(/\//g, "_")}${size}.png` | 
					
						
							|  |  |  |         const targetpath = `public/${newname}` | 
					
						
							|  |  |  |         if (alreadyWritten.indexOf(newname) >= 0) { | 
					
						
							|  |  |  |             return newname | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         alreadyWritten.push(newname) | 
					
						
							|  |  |  |         if (existsSync(targetpath)) { | 
					
						
							|  |  |  |             return newname | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-07-26 02:01:34 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         if (!existsSync(iconPath)) { | 
					
						
							|  |  |  |             throw "No file at " + iconPath | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-09-25 23:37:59 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         try { | 
					
						
							|  |  |  |             // We already read to file, in order to crash here if the file is not found
 | 
					
						
							| 
									
										
										
										
											2024-07-19 17:12:31 +02:00
										 |  |  |             const img = await sharp(iconPath) | 
					
						
							|  |  |  |             const resized = await img.resize(size) | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |             await resized.toFile(targetpath) | 
					
						
							|  |  |  |             console.log("Created png version at ", newname) | 
					
						
							|  |  |  |         } catch (e) { | 
					
						
							|  |  |  |             console.error("Could not read icon", iconPath, " to create a PNG due to", e) | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-07-26 02:01:34 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         return newname | 
					
						
							| 
									
										
										
										
											2020-09-20 20:28:35 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-09-25 23:37:59 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |     async createSocialImage(layout: LayoutConfig, template: "" | "Wide"): Promise<string> { | 
					
						
							|  |  |  |         if (!layout.icon.endsWith(".svg")) { | 
					
						
							|  |  |  |             console.warn( | 
					
						
							|  |  |  |                 "Not creating a social image for " + | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |                     layout.id + | 
					
						
							|  |  |  |                     " as it is _not_ a .svg: " + | 
					
						
							|  |  |  |                     layout.icon | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |             ) | 
					
						
							|  |  |  |             return undefined | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         const path = `./public/assets/generated/images/social_image_${layout.id}_${template}.svg` | 
					
						
							|  |  |  |         if (existsSync(path)) { | 
					
						
							|  |  |  |             return path | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         const svg = await ScriptUtils.ReadSvg(layout.icon) | 
					
						
							|  |  |  |         let width: string = svg.$.width | 
					
						
							|  |  |  |         if (width === undefined) { | 
					
						
							|  |  |  |             throw "The logo at " + layout.icon + " does not have a defined width" | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (width?.endsWith("px")) { | 
					
						
							|  |  |  |             width = width.substring(0, width.length - 2) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (width?.endsWith("%")) { | 
					
						
							|  |  |  |             throw "The logo at " + layout.icon + " has a relative width; this is not supported" | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         delete svg["defs"] | 
					
						
							|  |  |  |         delete svg["$"] | 
					
						
							|  |  |  |         let templateSvg = await ScriptUtils.ReadSvg( | 
					
						
							|  |  |  |             "./public/assets/SocialImageTemplate" + template + ".svg" | 
					
						
							| 
									
										
										
										
											2022-03-08 04:09:03 +01:00
										 |  |  |         ) | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         templateSvg = Utils.WalkJson( | 
					
						
							|  |  |  |             templateSvg, | 
					
						
							|  |  |  |             (leaf) => { | 
					
						
							|  |  |  |                 const { cx, cy, r } = leaf["circle"][0].$ | 
					
						
							|  |  |  |                 return { | 
					
						
							|  |  |  |                     $: { | 
					
						
							|  |  |  |                         id: "icon", | 
					
						
							|  |  |  |                         transform: `translate(${cx - r},${cy - r}) scale(${ | 
					
						
							|  |  |  |                             (r * 2) / Number(width) | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |                         }) `,
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |                     }, | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |                     g: [svg], | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |                 } | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |             (mightBeTokenToReplace) => { | 
					
						
							|  |  |  |                 if (mightBeTokenToReplace?.circle === undefined) { | 
					
						
							|  |  |  |                     return false | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 return mightBeTokenToReplace.circle[0]?.$?.style?.indexOf("fill:#ff00ff") >= 0 | 
					
						
							| 
									
										
										
										
											2022-03-08 04:09:03 +01:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         ) | 
					
						
							| 
									
										
										
										
											2022-03-08 04:09:03 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         const builder = new xml2js.Builder() | 
					
						
							|  |  |  |         const xml = builder.buildObject({ svg: templateSvg }) | 
					
						
							|  |  |  |         writeFileSync(path, xml) | 
					
						
							|  |  |  |         console.log("Created social image at ", path) | 
					
						
							| 
									
										
										
										
											2022-03-10 16:26:25 +01:00
										 |  |  |         return path | 
					
						
							| 
									
										
										
										
											2022-03-08 04:09:03 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-02-06 01:57:33 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |     async createManifest( | 
					
						
							|  |  |  |         layout: LayoutConfig, | 
					
						
							|  |  |  |         alreadyWritten: string[] | 
					
						
							|  |  |  |     ): Promise<{ | 
					
						
							|  |  |  |         manifest: any | 
					
						
							|  |  |  |         whiteIcons: string[] | 
					
						
							|  |  |  |     }> { | 
					
						
							|  |  |  |         Translation.forcedLanguage = "en" | 
					
						
							|  |  |  |         const icons = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const whiteIcons: string[] = [] | 
					
						
							|  |  |  |         let icon = layout.icon | 
					
						
							|  |  |  |         if (icon.endsWith(".svg") || icon.startsWith("<svg") || icon.startsWith("<?xml")) { | 
					
						
							|  |  |  |             // This is an svg. Lets create the needed pngs and do some checkes!
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const whiteBackgroundPath = | 
					
						
							|  |  |  |                 "./public/assets/generated/images/theme_" + layout.id + "_white_background.svg" | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 const svg = await ScriptUtils.ReadSvg(icon) | 
					
						
							|  |  |  |                 const width: string = svg.$.width | 
					
						
							|  |  |  |                 const height: string = svg.$.height | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 const builder = new xml2js.Builder() | 
					
						
							|  |  |  |                 const withRect = { rect: { $: { width, height, style: "fill:#ffffff;" } }, ...svg } | 
					
						
							|  |  |  |                 const xml = builder.buildObject({ svg: withRect }) | 
					
						
							|  |  |  |                 writeFileSync(whiteBackgroundPath, xml) | 
					
						
							| 
									
										
										
										
											2022-03-08 04:09:03 +01:00
										 |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |             let path = layout.icon | 
					
						
							|  |  |  |             if (layout.icon.startsWith("<")) { | 
					
						
							|  |  |  |                 // THis is already the svg
 | 
					
						
							|  |  |  |                 path = "./public/assets/generated/images/" + layout.id + "_logo.svg" | 
					
						
							|  |  |  |                 writeFileSync(path, layout.icon) | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-01-17 21:04:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |             const sizes = [72, 96, 120, 128, 144, 152, 180, 192, 384, 512] | 
					
						
							|  |  |  |             for (const size of sizes) { | 
					
						
							|  |  |  |                 const name = await this.createIcon(path, size, alreadyWritten) | 
					
						
							|  |  |  |                 const whiteIcon = await this.createIcon(whiteBackgroundPath, size, alreadyWritten) | 
					
						
							|  |  |  |                 whiteIcons.push(whiteIcon) | 
					
						
							|  |  |  |                 icons.push({ | 
					
						
							|  |  |  |                     src: name, | 
					
						
							|  |  |  |                     sizes: size + "x" + size, | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |                     type: "image/png", | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |                 }) | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2020-07-26 02:01:34 +02:00
										 |  |  |             icons.push({ | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |                 src: path, | 
					
						
							|  |  |  |                 sizes: "513x513", | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |                 type: "image/svg", | 
					
						
							| 
									
										
										
										
											2020-07-26 02:01:34 +02:00
										 |  |  |             }) | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         } else if (icon.endsWith(".png")) { | 
					
						
							|  |  |  |             icons.push({ | 
					
						
							|  |  |  |                 src: icon, | 
					
						
							|  |  |  |                 sizes: "513x513", | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |                 type: "image/png", | 
					
						
							| 
									
										
										
										
											2020-07-26 02:01:34 +02:00
										 |  |  |             }) | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         } else { | 
					
						
							|  |  |  |             console.log(icon) | 
					
						
							|  |  |  |             throw "Icon is not an svg for " + layout.id | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         const ogTitle = Translations.T(layout.title).txt | 
					
						
							|  |  |  |         const ogDescr = Translations.T(layout.description ?? "").txt | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const manifest = { | 
					
						
							|  |  |  |             name: ogTitle, | 
					
						
							|  |  |  |             short_name: ogTitle, | 
					
						
							|  |  |  |             start_url: `${layout.id.toLowerCase()}.html`, | 
					
						
							|  |  |  |             lang: "en", | 
					
						
							|  |  |  |             display: "standalone", | 
					
						
							|  |  |  |             background_color: "#fff", | 
					
						
							|  |  |  |             description: ogDescr, | 
					
						
							|  |  |  |             orientation: "portrait-primary, landscape-primary", | 
					
						
							|  |  |  |             icons: icons, | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |             categories: ["map", "navigation"], | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         } | 
					
						
							|  |  |  |         return { | 
					
						
							|  |  |  |             manifest, | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |             whiteIcons, | 
					
						
							| 
									
										
										
										
											2020-07-26 02:01:34 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-03-08 04:09:03 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-07-26 02:01:34 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |     asLangSpan(t: Translation, tag = "span"): string { | 
					
						
							|  |  |  |         const values: string[] = [] | 
					
						
							| 
									
										
										
										
											2024-06-18 03:33:11 +02:00
										 |  |  |         let defaultSet = false | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         for (const lang in t.translations) { | 
					
						
							|  |  |  |             if (lang === "_context") { | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2024-06-18 03:33:11 +02:00
										 |  |  |             let display = ' style="display: none"' | 
					
						
							| 
									
										
										
										
											2024-06-20 04:21:29 +02:00
										 |  |  |             if (!defaultSet) { | 
					
						
							| 
									
										
										
										
											2024-06-18 03:33:11 +02:00
										 |  |  |                 display = "" | 
					
						
							|  |  |  |                 defaultSet = true | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             values.push(`<${tag} lang="${lang}"${display}>${t.translations[lang]}</${tag}>`) | 
					
						
							| 
									
										
										
										
											2023-04-13 23:40:28 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         return values.join("\n") | 
					
						
							| 
									
										
										
										
											2023-04-13 23:40:28 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-09-29 11:13:30 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |     async eliUrls(): Promise<string[]> { | 
					
						
							|  |  |  |         if (this.eliUrlsCached) { | 
					
						
							|  |  |  |             return this.eliUrlsCached | 
					
						
							| 
									
										
										
										
											2023-10-02 00:15:49 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         const urls: string[] = [] | 
					
						
							|  |  |  |         const regex = /{switch:([^}]+)}/ | 
					
						
							|  |  |  |         const rasterLayers = [ | 
					
						
							| 
									
										
										
										
											2024-03-25 04:17:13 +01:00
										 |  |  |             AvailableRasterLayers.defaultBackgroundLayer, | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |             ...eli.features, | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |             ...eli_global.layers.map((properties) => ({ properties })), | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         ] | 
					
						
							|  |  |  |         for (const feature of rasterLayers) { | 
					
						
							|  |  |  |             const f = <RasterLayerPolygon>feature | 
					
						
							|  |  |  |             const url = f.properties.url | 
					
						
							|  |  |  |             const match = url.match(regex) | 
					
						
							|  |  |  |             if (match) { | 
					
						
							|  |  |  |                 const domains = match[1].split(",") | 
					
						
							|  |  |  |                 const subpart = match[0] | 
					
						
							|  |  |  |                 urls.push(...domains.map((d) => url.replace(subpart, d))) | 
					
						
							|  |  |  |             } else { | 
					
						
							| 
									
										
										
										
											2023-10-11 19:04:31 +02:00
										 |  |  |                 urls.push(url) | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2023-10-11 19:04:31 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |             if (f.properties.type === "vector") { | 
					
						
							|  |  |  |                 // We also need to whitelist eventual sources
 | 
					
						
							| 
									
										
										
										
											2024-03-21 15:50:27 +01:00
										 |  |  |                 let url = f.properties.url | 
					
						
							| 
									
										
										
										
											2024-03-21 16:32:18 +01:00
										 |  |  |                 if (url.startsWith("pmtiles://")) { | 
					
						
							| 
									
										
										
										
											2024-03-21 15:50:27 +01:00
										 |  |  |                     url = url.substring("pmtiles://".length) | 
					
						
							| 
									
										
										
										
											2023-10-12 00:43:42 +02:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2024-03-21 15:50:27 +01:00
										 |  |  |                 const styleSpec = await Utils.downloadJsonCached(url, 1000 * 120, { | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |                     Origin: "https://mapcomplete.org", | 
					
						
							| 
									
										
										
										
											2024-03-21 15:50:27 +01:00
										 |  |  |                 }) | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |                 urls.push(...(f.properties["connect-src"] ?? [])) | 
					
						
							| 
									
										
										
										
											2024-03-21 15:50:27 +01:00
										 |  |  |                 for (const key of Object.keys(styleSpec?.sources ?? {})) { | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |                     const url = styleSpec.sources[key].url | 
					
						
							|  |  |  |                     if (!url) { | 
					
						
							|  |  |  |                         continue | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     let urlClipped = url | 
					
						
							|  |  |  |                     if (url.indexOf("?") > 0) { | 
					
						
							|  |  |  |                         urlClipped = url?.substring(0, url.indexOf("?")) | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     console.log("Source url ", key, url) | 
					
						
							|  |  |  |                     urls.push(url) | 
					
						
							|  |  |  |                     if (urlClipped.endsWith(".json")) { | 
					
						
							| 
									
										
										
										
											2024-03-21 15:50:27 +01:00
										 |  |  |                         const tileInfo = await Utils.downloadJsonCached(url, 1000 * 120, { | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |                             Origin: "https://mapcomplete.org", | 
					
						
							| 
									
										
										
										
											2024-03-21 15:50:27 +01:00
										 |  |  |                         }) | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |                         urls.push(tileInfo["tiles"] ?? []) | 
					
						
							|  |  |  |                     } | 
					
						
							| 
									
										
										
										
											2023-10-12 00:43:42 +02:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |                 urls.push(...(styleSpec["tiles"] ?? [])) | 
					
						
							|  |  |  |                 urls.push(styleSpec["sprite"]) | 
					
						
							|  |  |  |                 urls.push(styleSpec["glyphs"]) | 
					
						
							| 
									
										
										
										
											2023-10-11 19:04:31 +02:00
										 |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         this.eliUrlsCached = urls | 
					
						
							|  |  |  |         return Utils.NoNull(urls).sort() | 
					
						
							| 
									
										
										
										
											2023-10-02 00:15:49 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |     async generateCsp( | 
					
						
							|  |  |  |         layout: LayoutConfig, | 
					
						
							|  |  |  |         layoutJson: LayoutConfigJson, | 
					
						
							|  |  |  |         options: { | 
					
						
							|  |  |  |             scriptSrcs: string[] | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     ): Promise<string> { | 
					
						
							|  |  |  |         const apiUrls: string[] = [ | 
					
						
							| 
									
										
										
										
											2024-06-27 03:44:52 +02:00
										 |  |  |             ...Constants.allServers, | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |             Constants.countryCoderEndpoint, | 
					
						
							|  |  |  |             Constants.nominatimEndpoint, | 
					
						
							|  |  |  |             "https://www.openstreetmap.org", | 
					
						
							|  |  |  |             "https://api.openstreetmap.org", | 
					
						
							|  |  |  |             "https://pietervdvn.goatcounter.com", | 
					
						
							|  |  |  |         ].concat(...(await this.eliUrls())) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         SpecialVisualizations.specialVisualizations.forEach((sv) => { | 
					
						
							|  |  |  |             if (typeof sv.needsUrls === "function") { | 
					
						
							|  |  |  |                 // Handled below
 | 
					
						
							|  |  |  |                 return | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             apiUrls.push(...(sv.needsUrls ?? [])) | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const usedSpecialVisualisations = [].concat( | 
					
						
							|  |  |  |             ...layoutJson.layers.map((l) => | 
					
						
							|  |  |  |                 ValidationUtils.getAllSpecialVisualisations( | 
					
						
							|  |  |  |                     <QuestionableTagRenderingConfigJson[]>(<LayerConfigJson>l).tagRenderings ?? [] | 
					
						
							|  |  |  |                 ) | 
					
						
							| 
									
										
										
										
											2024-02-20 02:01:08 +01:00
										 |  |  |             ) | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         for (const usedSpecialVisualisation of usedSpecialVisualisations) { | 
					
						
							|  |  |  |             if (typeof usedSpecialVisualisation === "string") { | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             const neededUrls = usedSpecialVisualisation.func.needsUrls ?? [] | 
					
						
							|  |  |  |             if (typeof neededUrls === "function") { | 
					
						
							|  |  |  |                 let needed: string | string[] = neededUrls(usedSpecialVisualisation.args) | 
					
						
							|  |  |  |                 if (typeof needed === "string") { | 
					
						
							|  |  |  |                     needed = [needed] | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 apiUrls.push(...needed) | 
					
						
							| 
									
										
										
										
											2024-01-14 22:24:35 +01:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2023-11-19 16:31:58 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         const geojsonSources: string[] = layout.layers.map((l) => l.source?.geojsonSource) | 
					
						
							|  |  |  |         const hosts = new Set<string>() | 
					
						
							| 
									
										
										
										
											2024-04-02 19:07:11 +02:00
										 |  |  |         hosts.add("https://schema.org") | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         const eliLayers: RasterLayerPolygon[] = AvailableRasterLayers.layersAvailableAt( | 
					
						
							|  |  |  |             new ImmutableStore({ lon: 0, lat: 0 }) | 
					
						
							| 
									
										
										
										
											2024-08-11 17:27:15 +02:00
										 |  |  |         ).store.data | 
					
						
							| 
									
										
										
										
											2024-03-21 15:50:27 +01:00
										 |  |  |         { | 
					
						
							|  |  |  |             const vectorLayers = eliLayers.filter((l) => l.properties.type === "vector") | 
					
						
							|  |  |  |             const vectorSources = vectorLayers.map((l) => l.properties.url) | 
					
						
							|  |  |  |             vectorSources.push(...vectorLayers.map((l) => l.properties.style)) | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |             apiUrls.push( | 
					
						
							|  |  |  |                 ...vectorSources.map((url) => { | 
					
						
							|  |  |  |                     if (url?.startsWith("pmtiles://")) { | 
					
						
							|  |  |  |                         return url.substring("pmtiles://".length) | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     return url | 
					
						
							|  |  |  |                 }) | 
					
						
							|  |  |  |             ) | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         for (let connectSource of apiUrls.concat(geojsonSources)) { | 
					
						
							|  |  |  |             if (!connectSource) { | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             try { | 
					
						
							|  |  |  |                 if (!connectSource.startsWith("http")) { | 
					
						
							|  |  |  |                     connectSource = "https://" + connectSource | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 const url = new URL(connectSource) | 
					
						
							|  |  |  |                 hosts.add("https://" + url.host) | 
					
						
							|  |  |  |             } catch (e) { | 
					
						
							|  |  |  |                 hosts.add(connectSource) | 
					
						
							| 
									
										
										
										
											2024-01-14 22:24:35 +01:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2023-09-27 22:21:35 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-09-22 11:20:22 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-15 20:11:14 +02:00
										 |  |  |         hosts.add("http://www.schema.org") // Schema.org is _not_ encrypted and thus needs an exception
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         if (hosts.has("*")) { | 
					
						
							|  |  |  |             throw "* is not allowed as connect-src" | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-11-19 16:31:58 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         const connectSrc = Array.from(hosts).sort() | 
					
						
							| 
									
										
										
										
											2023-09-22 11:20:22 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         const newSrcs = connectSrc.filter((newItem) => !this.previousSrc.has(newItem)) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         console.log( | 
					
						
							|  |  |  |             "Got", | 
					
						
							|  |  |  |             hosts.size, | 
					
						
							|  |  |  |             "connect-src items for theme", | 
					
						
							|  |  |  |             layout.id, | 
					
						
							|  |  |  |             newSrcs.length > 0 ? "(extra sources: " + newSrcs.join(" ") + ")" : "" | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         this.previousSrc = hosts | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const csp: Record<string, string> = { | 
					
						
							|  |  |  |             "default-src": "'self'", | 
					
						
							|  |  |  |             "child-src": "'self' blob: ", | 
					
						
							|  |  |  |             "img-src": "* data:", // maplibre depends on 'data:' to load
 | 
					
						
							|  |  |  |             "connect-src": "'self' " + connectSrc.join(" "), | 
					
						
							|  |  |  |             "report-to": "https://report.mapcomplete.org/csp", | 
					
						
							|  |  |  |             "worker-src": "'self' blob:", // Vite somehow loads the worker via a 'blob'
 | 
					
						
							|  |  |  |             "style-src": "'self' 'unsafe-inline'", // unsafe-inline is needed to change the default background pin colours
 | 
					
						
							|  |  |  |             "script-src": [ | 
					
						
							|  |  |  |                 "'self'", | 
					
						
							|  |  |  |                 "https://gc.zgo.at/count.js", | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |                 ...(options?.scriptSrcs?.map((s) => "'" + s + "'") ?? []), | 
					
						
							|  |  |  |             ].join(" "), | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         } | 
					
						
							|  |  |  |         const content = Object.keys(csp) | 
					
						
							|  |  |  |             .map((k) => k + " " + csp[k]) | 
					
						
							|  |  |  |             .join(" ; ") | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         return [ | 
					
						
							|  |  |  |             `<meta http-equiv ="Report-To" content='{"group":"csp-endpoint", "max_age": 86400,"endpoints": [\{"url": "https://report.mapcomplete.org/csp"}], "include_subdomains": true}'>`, | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |             `<meta http-equiv="Content-Security-Policy" content="${content}">`, | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         ].join("\n") | 
					
						
							| 
									
										
										
										
											2022-03-08 04:09:03 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-07-26 02:01:34 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |     async createLandingPage( | 
					
						
							|  |  |  |         layout: LayoutConfig, | 
					
						
							|  |  |  |         layoutJson: LayoutConfigJson, | 
					
						
							|  |  |  |         whiteIcons, | 
					
						
							|  |  |  |         alreadyWritten | 
					
						
							|  |  |  |     ) { | 
					
						
							|  |  |  |         Locale.language.setData(layout.language[0]) | 
					
						
							|  |  |  |         const targetLanguage = layout.language[0] | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |         const ogTitle = Translations.T(layout.title).textFor(targetLanguage).replace(/"/g, '\\"') | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         const ogDescr = Translations.T( | 
					
						
							|  |  |  |             layout.shortDescription ?? "Easily add and edit geodata with OpenStreetMap" | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |             .textFor(targetLanguage) | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |             .replace(/"/g, '\\"') | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         let ogImage = layout.socialImage | 
					
						
							|  |  |  |         let twitterImage = ogImage | 
					
						
							|  |  |  |         if (ogImage === LayoutConfig.defaultSocialImage && layout.official) { | 
					
						
							| 
									
										
										
										
											2024-07-21 10:52:51 +02:00
										 |  |  |             try { | 
					
						
							| 
									
										
										
										
											2024-07-19 17:12:31 +02:00
										 |  |  |                 ogImage = (await this.createSocialImage(layout, "")) ?? layout.socialImage | 
					
						
							|  |  |  |                 twitterImage = (await this.createSocialImage(layout, "Wide")) ?? layout.socialImage | 
					
						
							| 
									
										
										
										
											2024-07-21 10:52:51 +02:00
										 |  |  |             } catch (e) { | 
					
						
							| 
									
										
										
										
											2024-07-19 17:12:31 +02:00
										 |  |  |                 console.error("Could not generate image:", e) | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         } | 
					
						
							|  |  |  |         if (twitterImage.endsWith(".svg")) { | 
					
						
							| 
									
										
										
										
											2024-07-21 10:52:51 +02:00
										 |  |  |             try { | 
					
						
							|  |  |  |                 // svgs are badly supported as social image, we use a generated svg instead
 | 
					
						
							|  |  |  |                 twitterImage = await this.createIcon(twitterImage, 512, alreadyWritten) | 
					
						
							|  |  |  |             } catch (e) { | 
					
						
							| 
									
										
										
										
											2024-07-19 17:12:31 +02:00
										 |  |  |                 console.error("Could not generate image:", e) | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2020-11-14 02:54:33 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-10-23 02:15:58 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         if (ogImage.endsWith(".svg")) { | 
					
						
							| 
									
										
										
										
											2024-07-21 10:52:51 +02:00
										 |  |  |             try { | 
					
						
							| 
									
										
										
										
											2024-07-19 17:12:31 +02:00
										 |  |  |                 ogImage = await this.createIcon(ogImage, 512, alreadyWritten) | 
					
						
							| 
									
										
										
										
											2024-07-21 10:52:51 +02:00
										 |  |  |             } catch (e) { | 
					
						
							| 
									
										
										
										
											2024-07-19 17:12:31 +02:00
										 |  |  |                 console.error("Could not generate image:", e) | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         let customCss = "" | 
					
						
							|  |  |  |         if (layout.customCss !== undefined && layout.customCss !== "") { | 
					
						
							|  |  |  |             try { | 
					
						
							|  |  |  |                 const cssContent = readFileSync(layout.customCss) | 
					
						
							|  |  |  |                 customCss = "<style>" + cssContent + "</style>" | 
					
						
							|  |  |  |             } catch (e) { | 
					
						
							|  |  |  |                 customCss = `<link rel="stylesheet" href="${layout.customCss}"/>` | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2020-11-14 02:54:33 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-10-23 02:15:58 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         const og = `
 | 
					
						
							| 
									
										
										
										
											2022-01-18 20:18:12 +01:00
										 |  |  |     <meta property="og:image" content="${ogImage ?? "assets/SocialImage.png"}"> | 
					
						
							| 
									
										
										
										
											2020-07-26 02:01:34 +02:00
										 |  |  |     <meta property="og:title" content="${ogTitle}"> | 
					
						
							| 
									
										
										
										
											2022-01-18 20:18:12 +01:00
										 |  |  |     <meta property="og:description" content="${ogDescr}"> | 
					
						
							|  |  |  |     <meta name="twitter:card" content="summary_large_image"> | 
					
						
							| 
									
										
										
										
											2023-08-23 18:33:30 +02:00
										 |  |  |     <meta name="twitter:site" content="@mapcomplete.org"> | 
					
						
							| 
									
										
										
										
											2022-01-18 20:18:12 +01:00
										 |  |  |     <meta name="twitter:creator" content="@pietervdvn"> | 
					
						
							|  |  |  |     <meta name="twitter:title" content="${ogTitle}"> | 
					
						
							|  |  |  |     <meta name="twitter:description" content="${ogDescr}"> | 
					
						
							| 
									
										
										
										
											2022-03-08 04:09:03 +01:00
										 |  |  |     <meta name="twitter:image" content="${twitterImage}">`
 | 
					
						
							| 
									
										
										
										
											2020-07-26 02:01:34 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         let icon = layout.icon | 
					
						
							|  |  |  |         if (icon.startsWith("<?xml") || icon.startsWith("<svg")) { | 
					
						
							|  |  |  |             // This already is an svg
 | 
					
						
							|  |  |  |             icon = `./public/assets/generated/images/${layout.id}_icon.svg` | 
					
						
							|  |  |  |             writeFileSync(icon, layout.icon) | 
					
						
							| 
									
										
										
										
											2022-02-06 12:51:23 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-09-09 00:05:51 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         const apple_icons = [] | 
					
						
							|  |  |  |         for (const icon of whiteIcons) { | 
					
						
							|  |  |  |             if (!existsSync(icon)) { | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             const size = icon.replace(/[^0-9]/g, "") | 
					
						
							|  |  |  |             apple_icons.push(`<link rel="apple-touch-icon" sizes="${size}x${size}" href="${icon}">`) | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-05-25 10:46:32 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-19 17:12:31 +02:00
										 |  |  |         const themeSpecific = [ | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |             `<title>${ogTitle}</title>`, | 
					
						
							|  |  |  |             `<link rel="manifest" href="${this.enc(layout.id)}.webmanifest">`, | 
					
						
							|  |  |  |             og, | 
					
						
							|  |  |  |             customCss, | 
					
						
							|  |  |  |             `<link rel="icon" href="${icon}" sizes="any" type="image/svg+xml">`, | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |             ...apple_icons, | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         ].join("\n") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 14:29:15 +01:00
										 |  |  |         let branchname = await this.getBranchName() | 
					
						
							|  |  |  |         if (branchname === "master" || branchname === "main") { | 
					
						
							|  |  |  |             branchname = "" | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             branchname = "<div class='text-xs'>" + branchname + "</div>" | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-02-12 18:32:10 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         const loadingText = Translations.t.general.loadingTheme.Subs({ theme: layout.title }) | 
					
						
							|  |  |  |         // const templateLines: string[] = this.template.split("\n").slice(1) // Slice to remove the 'export {}'-line
 | 
					
						
							| 
									
										
										
										
											2020-07-26 02:01:34 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         return this.template | 
					
						
							|  |  |  |             .replace("Loading MapComplete, hang on...", this.asLangSpan(loadingText, "h1")) | 
					
						
							|  |  |  |             .replace( | 
					
						
							|  |  |  |                 "Made with OpenStreetMap", | 
					
						
							|  |  |  |                 Translations.t.general.poweredByOsm.textFor(targetLanguage) | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |             .replace(/<!-- THEME-SPECIFIC -->.*<!-- THEME-SPECIFIC-END-->/s, themeSpecific) | 
					
						
							|  |  |  |             .replace( | 
					
						
							|  |  |  |                 /<!-- CSP -->/, | 
					
						
							|  |  |  |                 await this.generateCsp(layout, layoutJson, { | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |                     scriptSrcs: [this.removeOtherLanguagesHash], | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |                 }) | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |             .replace( | 
					
						
							|  |  |  |                 /<!-- DESCRIPTION START -->.*<!-- DESCRIPTION END -->/s, | 
					
						
							|  |  |  |                 this.asLangSpan(layout.shortDescription) | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |             .replace( | 
					
						
							|  |  |  |                 /<!-- IMAGE-START -->.*<!-- IMAGE-END -->/s, | 
					
						
							| 
									
										
										
										
											2024-03-21 22:39:36 +01:00
										 |  |  |                 "<img class='p-0 h-32 w-32 self-start' src='" + icon + "' />" | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |             ) | 
					
						
							|  |  |  |             .replace( | 
					
						
							|  |  |  |                 /.*\/src\/index\.ts.*/, | 
					
						
							|  |  |  |                 `<script type="module" src="./index_${layout.id}.ts"></script>` | 
					
						
							|  |  |  |             ) | 
					
						
							| 
									
										
										
										
											2023-09-22 11:20:22 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |             .replace( | 
					
						
							|  |  |  |                 /\n.*RemoveOtherLanguages.*\n/i, | 
					
						
							|  |  |  |                 "\n<script>" + this.removeOtherLanguages + "</script>\n" | 
					
						
							|  |  |  |             ) | 
					
						
							| 
									
										
										
										
											2024-03-11 14:29:15 +01:00
										 |  |  |             .replace( | 
					
						
							|  |  |  |                 "Version", | 
					
						
							|  |  |  |                 `${Constants.vNumber} <div class='text-xs'>${this.date}</div>${branchname}` | 
					
						
							|  |  |  |             ) | 
					
						
							| 
									
										
										
										
											2023-12-06 12:12:53 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-07-16 03:26:43 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |     async createIndexFor(theme: LayoutConfig) { | 
					
						
							|  |  |  |         const filename = "index_" + theme.id + ".ts" | 
					
						
							| 
									
										
										
										
											2023-12-06 12:12:53 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         const imports = [ | 
					
						
							|  |  |  |             `import layout from "./src/assets/generated/themes/${theme.id}.json"`, | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |             `import { ThemeMetaTagging } from "./src/assets/generated/metatagging/${theme.id}"`, | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         ] | 
					
						
							|  |  |  |         for (const layerName of Constants.added_by_default) { | 
					
						
							|  |  |  |             imports.push( | 
					
						
							|  |  |  |                 `import ${layerName} from "./src/assets/generated/layers/${layerName}.json"` | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         writeFileSync(filename, imports.join("\n") + "\n") | 
					
						
							| 
									
										
										
										
											2023-12-06 12:12:53 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         const addLayers = [] | 
					
						
							| 
									
										
										
										
											2023-12-06 12:12:53 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         for (const layerName of Constants.added_by_default) { | 
					
						
							|  |  |  |             addLayers.push(`    layout.layers.push(<any> ${layerName})`) | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-12-21 18:35:31 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         let codeTemplate = this.codeTemplate.replace( | 
					
						
							|  |  |  |             "    // LAYOUT.ADD_LAYERS", | 
					
						
							|  |  |  |             addLayers.join("\n") | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         appendFileSync(filename, codeTemplate) | 
					
						
							| 
									
										
										
										
											2022-02-06 01:57:33 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-11-17 16:29:51 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |     createDir(path) { | 
					
						
							|  |  |  |         if (!existsSync(path)) { | 
					
						
							|  |  |  |             mkdirSync(path) | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-07-26 02:01:34 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     async main(): Promise<void> { | 
					
						
							|  |  |  |         const alreadyWritten = [] | 
					
						
							|  |  |  |         this.createDir("./public/assets/") | 
					
						
							|  |  |  |         this.createDir("./public/assets/generated") | 
					
						
							|  |  |  |         this.createDir("./public/assets/generated/images") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const blacklist = [ | 
					
						
							|  |  |  |             "", | 
					
						
							|  |  |  |             "test", | 
					
						
							|  |  |  |             ".", | 
					
						
							|  |  |  |             "..", | 
					
						
							|  |  |  |             "manifest", | 
					
						
							|  |  |  |             "index", | 
					
						
							|  |  |  |             "land", | 
					
						
							|  |  |  |             "preferences", | 
					
						
							|  |  |  |             "account", | 
					
						
							|  |  |  |             "openstreetmap", | 
					
						
							|  |  |  |             "custom", | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |             "theme", | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         ] | 
					
						
							|  |  |  |         // @ts-ignore
 | 
					
						
							|  |  |  |         const all: LayoutConfigJson[] = all_known_layouts.themes | 
					
						
							|  |  |  |         const args = process.argv | 
					
						
							|  |  |  |         const theme = args[2] | 
					
						
							|  |  |  |         if (theme !== undefined) { | 
					
						
							|  |  |  |             console.warn("Only generating layout " + theme) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         for (const i in all) { | 
					
						
							|  |  |  |             const layoutConfigJson: LayoutConfigJson = all[i] | 
					
						
							|  |  |  |             if (theme !== undefined && layoutConfigJson.id !== theme) { | 
					
						
							|  |  |  |                 continue | 
					
						
							| 
									
										
										
										
											2022-02-06 01:57:33 +01:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |             const layout = new LayoutConfig(layoutConfigJson, true) | 
					
						
							|  |  |  |             const layoutName = layout.id | 
					
						
							|  |  |  |             if (blacklist.indexOf(layoutName.toLowerCase()) >= 0) { | 
					
						
							| 
									
										
										
										
											2024-07-14 04:17:22 +02:00
										 |  |  |                 console.log(`Skipping a layout with name ${layoutName}, it is on the blacklist`) | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |                 continue | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             const err = (err) => { | 
					
						
							|  |  |  |                 if (err !== null) { | 
					
						
							|  |  |  |                     console.log("Could not write manifest for ", layoutName, " because ", err) | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             const { manifest, whiteIcons } = await this.createManifest(layout, alreadyWritten) | 
					
						
							|  |  |  |             const manif = JSON.stringify(manifest, undefined, 2) | 
					
						
							|  |  |  |             const manifestLocation = encodeURIComponent(layout.id.toLowerCase()) + ".webmanifest" | 
					
						
							|  |  |  |             writeFile("public/" + manifestLocation, manif, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // Create a landing page for the given theme
 | 
					
						
							|  |  |  |             const landing = await this.createLandingPage( | 
					
						
							|  |  |  |                 layout, | 
					
						
							|  |  |  |                 layoutConfigJson, | 
					
						
							|  |  |  |                 whiteIcons, | 
					
						
							|  |  |  |                 alreadyWritten | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             writeFile(this.enc(layout.id) + ".html", landing, err) | 
					
						
							|  |  |  |             await this.createIndexFor(layout) | 
					
						
							| 
									
										
										
										
											2022-02-06 01:57:33 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-03-08 04:09:03 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         const { manifest } = await this.createManifest( | 
					
						
							|  |  |  |             new LayoutConfig({ | 
					
						
							|  |  |  |                 icon: "./assets/svg/mapcomplete_logo.svg", | 
					
						
							|  |  |  |                 id: "index", | 
					
						
							|  |  |  |                 layers: [], | 
					
						
							|  |  |  |                 socialImage: "assets/SocialImage.png", | 
					
						
							|  |  |  |                 startLat: 0, | 
					
						
							|  |  |  |                 startLon: 0, | 
					
						
							|  |  |  |                 startZoom: 0, | 
					
						
							|  |  |  |                 title: { en: "MapComplete" }, | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |                 description: { en: "A thematic map viewer and editor based on OpenStreetMap" }, | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |             }), | 
					
						
							| 
									
										
										
										
											2023-11-19 16:31:58 +01:00
										 |  |  |             alreadyWritten | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2023-07-16 03:26:43 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  |         const manif = JSON.stringify(manifest, undefined, 2) | 
					
						
							|  |  |  |         writeFileSync("public/index.webmanifest", manif) | 
					
						
							| 
									
										
										
										
											2022-02-06 01:57:33 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-07-26 02:01:34 +02:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2020-10-25 17:26:15 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-11 01:17:33 +01:00
										 |  |  | new GenerateLayouts().run() |