2021-10-15 05:20:02 +02:00
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig" ;
import { QueryParameters } from "./Web/QueryParameters" ;
import { AllKnownLayouts } from "../Customizations/AllKnownLayouts" ;
import { FixedUiElement } from "../UI/Base/FixedUiElement" ;
import { Utils } from "../Utils" ;
import Combine from "../UI/Base/Combine" ;
import { SubtleButton } from "../UI/Base/SubtleButton" ;
import BaseUIElement from "../UI/BaseUIElement" ;
import { UIEventSource } from "./UIEventSource" ;
import { LocalStorageSource } from "./Web/LocalStorageSource" ;
import LZString from "lz-string" ;
2022-02-09 22:37:21 +01:00
import { FixLegacyTheme } from "../Models/ThemeConfig/Conversion/LegacyJsonConvert" ;
2021-12-21 18:35:31 +01:00
import { LayerConfigJson } from "../Models/ThemeConfig/Json/LayerConfigJson" ;
import SharedTagRenderings from "../Customizations/SharedTagRenderings" ;
2021-12-21 19:09:24 +01:00
import * as known_layers from "../assets/generated/known_layers.json"
2022-01-21 01:57:16 +01:00
import { PrepareTheme } from "../Models/ThemeConfig/Conversion/PrepareTheme" ;
2022-02-04 15:48:26 +01:00
import * as licenses from "../assets/generated/license_info.json"
2022-02-08 00:56:47 +01:00
import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig" ;
2022-02-09 22:37:21 +01:00
import { FixImages } from "../Models/ThemeConfig/Conversion/FixImages" ;
2022-02-14 02:26:03 +01:00
import Svg from "../Svg" ;
2022-02-08 00:56:47 +01:00
2021-10-15 05:20:02 +02:00
export default class DetermineLayout {
2022-02-04 15:48:26 +01:00
private static readonly _knownImages = new Set ( Array . from ( licenses ) . map ( l = > l . path ) )
2021-10-15 05:20:02 +02:00
/ * *
* Gets the correct layout for this website
* /
2021-12-21 19:56:04 +01:00
public static async GetLayout ( ) : Promise < LayoutConfig > {
2021-10-15 05:20:02 +02:00
const loadCustomThemeParam = QueryParameters . GetQueryParameter ( "userlayout" , "false" , "If not 'false', a custom (non-official) theme is loaded. This custom layout can be done in multiple ways: \n\n- The hash of the URL contains a base64-encoded .json-file containing the theme definition\n- The hash of the URL contains a lz-compressed .json-file, as generated by the custom theme generator\n- The parameter itself is an URL, in which case that URL will be downloaded. It should point to a .json of a theme" )
const layoutFromBase64 = decodeURIComponent ( loadCustomThemeParam . data ) ;
if ( layoutFromBase64 . startsWith ( "http" ) ) {
2021-12-21 19:56:04 +01:00
return await DetermineLayout . LoadRemoteTheme ( layoutFromBase64 )
2021-10-15 05:20:02 +02:00
}
if ( layoutFromBase64 !== "false" ) {
// We have to load something from the hash (or from disk)
2021-12-21 19:56:04 +01:00
return DetermineLayout . LoadLayoutFromHash ( loadCustomThemeParam )
2021-10-15 05:20:02 +02:00
}
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 !== "" ) {
2021-10-15 05:20:02 +02:00
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 ;
2022-06-13 03:13:42 +02:00
return AllKnownLayouts . allKnownLayouts . get ( layoutId ? . toLowerCase ( ) )
2021-10-15 05:20:02 +02:00
}
public static LoadLayoutFromHash (
userLayoutParam : UIEventSource < string >
2022-04-18 02:39:30 +02:00
) : LayoutConfig | null {
2021-10-15 05:20:02 +02:00
let hash = location . hash . substr ( 1 ) ;
2022-02-14 02:26:03 +01:00
let json : any ;
2021-10-15 05:20:02 +02:00
try {
// layoutFromBase64 contains the name of the theme. This is partly to do tracking with goat counter
const dedicatedHashFromLocalStorage = LocalStorageSource . Get (
2021-10-27 19:57:15 +02:00
"user-layout-" + userLayoutParam . data ? . replace ( " " , "_" )
2021-10-15 05:20:02 +02:00
) ;
if ( dedicatedHashFromLocalStorage . data ? . length < 10 ) {
dedicatedHashFromLocalStorage . setData ( undefined ) ;
}
const hashFromLocalStorage = LocalStorageSource . Get (
"last-loaded-user-layout"
) ;
if ( hash . length < 10 ) {
hash =
dedicatedHashFromLocalStorage . data ? ?
hashFromLocalStorage . data ;
} else {
console . log ( "Saving hash to local storage" ) ;
hashFromLocalStorage . setData ( hash ) ;
dedicatedHashFromLocalStorage . setData ( hash ) ;
}
try {
json = JSON . parse ( atob ( hash ) ) ;
} catch ( e ) {
// We try to decode with lz-string
try {
json = JSON . parse ( Utils . UnMinify ( LZString . decompressFromBase64 ( hash ) ) )
} catch ( e ) {
2021-10-27 19:57:15 +02:00
console . error ( e )
2021-10-15 05:20:02 +02:00
DetermineLayout . ShowErrorOnCustomTheme ( "Could not decode the hash" , new FixedUiElement ( "Not a valid (LZ-compressed) JSON" ) )
return null ;
}
}
2021-12-21 20:57:25 +01:00
const layoutToUse = DetermineLayout . prepCustomTheme ( json )
2021-10-15 05:20:02 +02:00
userLayoutParam . setData ( layoutToUse . id ) ;
2022-04-18 02:39:30 +02:00
return layoutToUse
2021-10-15 05:20:02 +02:00
} catch ( e ) {
2021-10-27 19:57:15 +02:00
console . error ( e )
2021-10-15 05:20:02 +02:00
if ( hash === undefined || hash . length < 10 ) {
2022-02-14 02:26:03 +01:00
DetermineLayout . ShowErrorOnCustomTheme ( "Could not load a theme from the hash" , new FixedUiElement ( "Hash does not contain data" ) , json )
2021-10-15 05:20:02 +02:00
}
2022-02-14 02:26:03 +01:00
this . ShowErrorOnCustomTheme ( "Could not parse the hash" , new FixedUiElement ( e ) , json )
2021-10-15 05:20:02 +02:00
return null ;
}
}
public static ShowErrorOnCustomTheme (
intro : string = "Error: could not parse the custom layout:" ,
2022-02-14 02:26:03 +01:00
error : BaseUIElement ,
json? : any ) {
2021-10-15 05:20:02 +02:00
new Combine ( [
intro ,
error . SetClass ( "alert" ) ,
2022-02-14 02:26:03 +01:00
new SubtleButton ( Svg . back_svg ( ) ,
2021-10-15 05:20:02 +02:00
"Go back to the theme overview" ,
2022-07-01 00:16:05 +02:00
{ url : window.location.protocol + "//" + window . location . host + "/index.html" , newTab : false } ) ,
2022-02-14 02:26:03 +01:00
json !== undefined ? new SubtleButton ( Svg . download_svg ( ) , "Download the JSON file" ) . onClick ( ( ) = > {
Utils . offerContentsAsDownloadableFile ( JSON . stringify ( json , null , " " ) , "theme_definition.json" )
} ) : undefined
2021-10-15 05:20:02 +02:00
] )
. SetClass ( "flex flex-col clickable" )
. AttachTo ( "centermessage" ) ;
}
2022-06-21 16:47:54 +02:00
private static prepCustomTheme ( json : any , sourceUrl? : string , forceId? : string ) : LayoutConfig {
2022-02-08 00:56:47 +01:00
if ( json . layers === undefined && json . tagRenderings !== undefined ) {
const iconTr = json . mapRendering . map ( mr = > mr . icon ) . find ( icon = > icon !== undefined )
const icon = new TagRenderingConfig ( iconTr ) . render . txt
json = {
id : json.id ,
description : json.description ,
descriptionTail : {
en : "<div class='alert'>Layer only mode.</div> The loaded custom theme actually isn't a custom theme, but only contains a layer."
} ,
icon ,
title : json.name ,
layers : [ json ] ,
}
}
2022-01-26 21:40:38 +01:00
const knownLayersDict = new Map < string , LayerConfigJson > ( )
2022-01-29 02:45:59 +01:00
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
}
const converState = {
tagRenderings : SharedTagRenderings.SharedTagRenderingJson ,
2022-06-13 03:13:42 +02:00
sharedLayers : knownLayersDict ,
publicLayers : new Set < string > ( )
2022-01-26 21:40:38 +01:00
}
2022-02-04 13:17:50 +01:00
json = new FixLegacyTheme ( ) . convertStrict ( json , "While loading a dynamic theme" )
2022-04-18 02:39:30 +02:00
const raw = json ;
2022-02-04 15:48:26 +01:00
json = new FixImages ( DetermineLayout . _knownImages ) . convertStrict ( json , "While fixing the images" )
2022-05-06 12:41:24 +02:00
json . enableNoteImports = json . enableNoteImports ? ? false ;
2022-02-04 13:17:50 +01:00
json = new PrepareTheme ( converState ) . convertStrict ( json , "While preparing a dynamic theme" )
2022-01-26 21:40:38 +01:00
console . log ( "The layoutconfig is " , json )
2022-04-18 02:39:30 +02:00
2022-06-21 16:47:54 +02:00
json . id = forceId ? ? json . id
2022-04-18 02:39:30 +02:00
return new LayoutConfig ( json , false , {
definitionRaw : JSON.stringify ( raw , null , " " ) ,
definedAtUrl : sourceUrl
} )
2022-01-26 21:40:38 +01:00
}
2021-11-07 16:34:51 +01:00
private static async LoadRemoteTheme ( link : string ) : Promise < LayoutConfig | null > {
console . log ( "Downloading map theme from " , link ) ;
new FixedUiElement ( ` Downloading the theme from the <a href=" ${ link } ">link</a>... ` )
. AttachTo ( "centermessage" ) ;
try {
2021-12-21 18:35:31 +01:00
let parsed = await Utils . downloadJson ( link )
2021-12-21 20:57:25 +01:00
try {
2022-06-21 16:47:54 +02:00
let forcedId = parsed . id
const url = new URL ( link )
if ( ! ( url . hostname === "localhost" || url . hostname === "127.0.0.1" ) ) {
forcedId = link ;
}
2022-01-27 20:37:22 +01:00
console . log ( "Loaded remote link:" , link )
2022-06-21 16:47:54 +02:00
return DetermineLayout . prepCustomTheme ( parsed , link , forcedId ) ;
2021-11-07 16:34:51 +01:00
} catch ( e ) {
console . error ( e )
DetermineLayout . ShowErrorOnCustomTheme (
` <a href=" ${ link } "> ${ link } </a> is invalid: ` ,
2022-02-14 02:26:03 +01:00
new FixedUiElement ( e ) ,
parsed
2021-11-07 16:34:51 +01:00
)
return null ;
}
} catch ( e ) {
console . error ( e )
DetermineLayout . ShowErrorOnCustomTheme (
` <a href=" ${ link } "> ${ link } </a> is invalid - probably not found or invalid JSON: ` ,
new FixedUiElement ( e )
)
return null ;
}
}
2021-10-15 05:20:02 +02:00
}