MapComplete/src/Logic/DetermineTheme.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

219 lines
8.5 KiB
TypeScript
Raw Normal View History

import ThemeConfig from "../Models/ThemeConfig/ThemeConfig"
2023-07-28 01:12:42 +02:00
import { QueryParameters } from "./Web/QueryParameters"
import { AllKnownLayouts } from "../Customizations/AllKnownLayouts"
import { FixedUiElement } from "../UI/Base/FixedUiElement"
import { Utils } from "../Utils"
import LZString from "lz-string"
2023-07-28 01:12:42 +02:00
import { FixLegacyTheme } from "../Models/ThemeConfig/Conversion/LegacyJsonConvert"
import { LayerConfigJson } from "../Models/ThemeConfig/Json/LayerConfigJson"
import known_layers from "../assets/generated/known_layers.json"
2023-07-28 01:12:42 +02:00
import { PrepareTheme } from "../Models/ThemeConfig/Conversion/PrepareTheme"
import licenses from "../assets/generated/license_info.json"
2022-02-08 00:56:47 +01:00
import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig"
2023-07-28 01:12:42 +02:00
import { FixImages } from "../Models/ThemeConfig/Conversion/FixImages"
import questions from "../assets/generated/layers/questions.json"
import { DoesImageExist, PrevalidateTheme } from "../Models/ThemeConfig/Conversion/Validation"
2023-07-28 01:12:42 +02:00
import { DesugaringContext } from "../Models/ThemeConfig/Conversion/Conversion"
import { TagRenderingConfigJson } from "../Models/ThemeConfig/Json/TagRenderingConfigJson"
import Hash from "./Web/Hash"
import { QuestionableTagRenderingConfigJson } from "../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
import { ThemeConfigJson } from "../Models/ThemeConfig/Json/ThemeConfigJson"
import { ValidateThemeAndLayers } from "../Models/ThemeConfig/Conversion/ValidateThemeAndLayers"
2022-02-08 00:56:47 +01:00
export default class DetermineTheme {
private static readonly _knownImages = new Set(Array.from(licenses).map((l) => l.path))
private static readonly loadCustomThemeParam = QueryParameters.GetQueryParameter(
"userlayout",
"false",
"If the parameter is an URL, it should point to a .json of a theme which will be loaded and used"
)
public static getCustomDefinition(): string {
const layoutFromBase64 = decodeURIComponent(DetermineTheme.loadCustomThemeParam.data)
if (layoutFromBase64.startsWith("http")) {
return layoutFromBase64
}
return undefined
}
2024-06-16 16:06:26 +02:00
private static async expandRemoteLayers(
layoutConfig: ThemeConfigJson
): Promise<ThemeConfigJson> {
2024-06-16 16:06:26 +02:00
if (!layoutConfig.layers) {
2024-04-23 21:31:58 +02:00
// This is probably a layer in 'layer-only-mode'
return layoutConfig
}
for (let i = 0; i < layoutConfig.layers.length; i++) {
const l = layoutConfig.layers[i]
if (typeof l !== "string") {
continue
}
try {
new URL(l)
console.log("Downloading remote layer " + l)
layoutConfig.layers[i] = <LayerConfigJson>await Utils.downloadJson(l)
} catch (_) {
continue
}
}
return layoutConfig
}
/**
* Gets the correct layout for this website
*/
public static async getTheme(): Promise<ThemeConfig | undefined> {
const layoutFromBase64 = decodeURIComponent(DetermineTheme.loadCustomThemeParam.data)
if (layoutFromBase64.startsWith("http")) {
return await DetermineTheme.LoadRemoteTheme(layoutFromBase64)
}
let layoutId: string = undefined
const path = window.location.pathname.split("/").slice(-1)[0]
2021-12-21 18:35:31 +01:00
if (path !== "theme.html" && path !== "") {
layoutId = path
if (path.endsWith(".html")) {
layoutId = path.substr(0, path.length - 5)
}
console.log("Using layout", layoutId)
}
layoutId = QueryParameters.GetQueryParameter(
"layout",
layoutId,
"The layout to load into MapComplete"
).data
2024-08-01 19:34:13 +02:00
const id = layoutId?.toLowerCase()
const layouts = AllKnownLayouts.allKnownLayouts
2024-08-14 13:53:56 +02:00
if (layouts.size() == 0) {
throw "Build failed or running, no layouts are known at all"
}
2024-08-01 19:34:13 +02:00
if (layouts.getConfig(id) === undefined) {
2024-08-14 13:53:56 +02:00
const alternatives = Utils.sortedByLevenshteinDistance(
id,
Array.from(layouts.keys()),
(i) => i
).slice(0, 3)
const msg = `No builtin map theme with name ${layoutId} exists. Perhaps you meant one of ${alternatives.join(
", "
)}`
2024-08-01 19:34:13 +02:00
throw msg
}
2024-08-01 19:34:13 +02:00
return layouts.get(id)
}
private static getSharedTagRenderings(): Map<string, QuestionableTagRenderingConfigJson> {
const dict = new Map<string, QuestionableTagRenderingConfigJson>()
2023-06-07 17:33:07 +02:00
for (const tagRendering of questions.tagRenderings) {
2024-10-19 14:44:55 +02:00
dict.set(tagRendering.id, <QuestionableTagRenderingConfigJson>tagRendering)
2023-06-07 17:33:07 +02:00
}
return dict
}
2024-08-01 19:34:13 +02:00
private static getSharedTagRenderingOrder(): string[] {
2024-06-20 04:21:29 +02:00
return questions.tagRenderings.map((tr) => tr.id)
}
private static prepCustomTheme(json: any, sourceUrl?: string, forceId?: string): ThemeConfig {
2022-02-08 00:56:47 +01:00
if (json.layers === undefined && json.tagRenderings !== undefined) {
// We got fed a layer instead of a theme
const layerConfig = <LayerConfigJson>json
let icon = Utils.NoNull(
2024-06-20 04:21:29 +02:00
layerConfig.pointRendering
.flatMap((pr) => pr.marker)
.map((iconSpec) => {
2024-06-24 13:11:35 +02:00
if (!iconSpec) {
return undefined
}
2024-06-20 04:21:29 +02:00
const icon = new TagRenderingConfig(<TagRenderingConfigJson>iconSpec.icon)
.render.txt
if (
iconSpec.color === undefined ||
icon.startsWith("http:") ||
icon.startsWith("https:")
) {
return icon
}
const color = new TagRenderingConfig(<TagRenderingConfigJson>iconSpec.color)
.render.txt
return icon + ":" + color
})
).join(";")
2024-06-24 13:11:35 +02:00
if (!icon) {
icon = "./assets/svg/bug.svg"
}
2022-02-08 00:56:47 +01:00
json = {
id: json.id,
description: json.description,
descriptionTail: {
2024-08-14 13:53:56 +02:00
en: "<div class='alert'>Layer only mode.</div> The loaded custom theme actually isn't a custom theme, but only contains a layer.",
2022-02-08 00:56:47 +01:00
},
icon,
title: json.name,
2024-08-14 13:53:56 +02:00
layers: [json],
2022-02-08 00:56:47 +01:00
}
}
2022-09-08 21:40:48 +02:00
2022-01-26 21:40:38 +01:00
const knownLayersDict = new Map<string, LayerConfigJson>()
for (const key in known_layers["layers"]) {
const layer = known_layers["layers"][key]
knownLayersDict.set(layer.id, <LayerConfigJson>layer)
2022-01-26 21:40:38 +01:00
}
2023-06-07 17:33:07 +02:00
const convertState: DesugaringContext = {
tagRenderings: DetermineTheme.getSharedTagRenderings(),
tagRenderingOrder: DetermineTheme.getSharedTagRenderingOrder(),
sharedLayers: knownLayersDict,
2024-08-14 13:53:56 +02:00
publicLayers: new Set<string>(),
2022-01-26 21:40:38 +01:00
}
json = new FixLegacyTheme().convertStrict(json)
const raw = json
json = new FixImages(DetermineTheme._knownImages).convertStrict(json)
json.enableNoteImports = json.enableNoteImports ?? false
json = new PrepareTheme(convertState).convertStrict(json)
2022-01-26 21:40:38 +01:00
console.log("The layoutconfig is ", json)
2022-09-08 21:40:48 +02:00
2022-06-21 16:47:54 +02:00
json.id = forceId ?? json.id
2022-09-08 21:40:48 +02:00
{
new PrevalidateTheme().convertStrict(json)
}
{
new ValidateThemeAndLayers(
new DoesImageExist(new Set<string>(), () => true),
"",
false
).convertStrict(json)
}
return new ThemeConfig(json, false, {
definitionRaw: JSON.stringify(raw, null, " "),
2024-08-14 13:53:56 +02:00
definedAtUrl: sourceUrl,
})
2022-01-26 21:40:38 +01:00
}
private static async LoadRemoteTheme(link: string): Promise<ThemeConfig | null> {
2021-11-07 16:34:51 +01:00
console.log("Downloading map theme from ", link)
new FixedUiElement(`Downloading the theme from the <a href="${link}">link</a>...`).AttachTo(
2023-06-07 17:33:07 +02:00
"maindiv"
2022-09-08 21:40:48 +02:00
)
2021-11-07 16:34:51 +01:00
let parsed = <ThemeConfigJson>await Utils.downloadJson(link)
let forcedId = parsed.id
const url = new URL(link)
if (!(url.hostname === "localhost" || url.hostname === "127.0.0.1")) {
forcedId = link
2021-11-07 16:34:51 +01:00
}
console.log("Loaded remote link:", link)
parsed = await this.expandRemoteLayers(parsed)
return DetermineTheme.prepCustomTheme(parsed, link, forcedId)
2021-11-07 16:34:51 +01:00
}
}