forked from MapComplete/MapComplete
		
	
		
			
				
	
	
		
			166 lines
		
	
	
		
			No EOL
		
	
	
		
			6.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			166 lines
		
	
	
		
			No EOL
		
	
	
		
			6.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| 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";
 | |
| import * as personal from "../assets/themes/personal/personal.json";
 | |
| 
 | |
| export default class DetermineLayout {
 | |
| 
 | |
|     /**
 | |
|      * Gets the correct layout for this website
 | |
|      */
 | |
|     public static async GetLayout(): Promise<[LayoutConfig, string]> {
 | |
| 
 | |
|         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")) {
 | |
|             // The userLayout is actually an url
 | |
|             const layout = await DetermineLayout.LoadRemoteTheme(layoutFromBase64)
 | |
|             return [layout, undefined]
 | |
|         }
 | |
| 
 | |
|         if (layoutFromBase64 !== "false") {
 | |
|             // We have to load something from the hash (or from disk)
 | |
|             let loaded = DetermineLayout.LoadLayoutFromHash(loadCustomThemeParam);
 | |
|             if (loaded === null) {
 | |
|                 return [null, undefined]
 | |
|             }
 | |
|             return loaded
 | |
|         }
 | |
| 
 | |
|         let layoutId: string = undefined
 | |
|         if (location.href.indexOf("buurtnatuur.be") >= 0) {
 | |
|             layoutId = "buurtnatuur"
 | |
|         }
 | |
| 
 | |
| 
 | |
|         const path = window.location.pathname.split("/").slice(-1)[0];
 | |
|         if (path !== "index.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;
 | |
|         const layoutToUse: LayoutConfig = AllKnownLayouts.allKnownLayouts.get(layoutId?.toLowerCase());
 | |
| 
 | |
|         if (layoutToUse?.id === personal.id) {
 | |
|             layoutToUse.layers = AllKnownLayouts.AllPublicLayers()
 | |
|             for (const layer of layoutToUse.layers) {
 | |
|                 layer.minzoomVisible = Math.max(layer.minzoomVisible, layer.minzoom)
 | |
|                 layer.minzoom = Math.max(16, layer.minzoom)
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         return [layoutToUse, undefined]
 | |
|     }
 | |
| 
 | |
|     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 {
 | |
| 
 | |
|             const parsed = await Utils.downloadJson(link)
 | |
|             console.log("Got ", parsed)
 | |
|             try {
 | |
|                 parsed.id = link;
 | |
|                 return new LayoutConfig(parsed, false).patchImages(link, JSON.stringify(parsed));
 | |
|             } catch (e) {
 | |
|                 console.error(e)
 | |
|                 DetermineLayout.ShowErrorOnCustomTheme(
 | |
|                     `<a href="${link}">${link}</a> is invalid:`,
 | |
|                     new FixedUiElement(e)
 | |
|                 )
 | |
|                 return null;
 | |
|             }
 | |
| 
 | |
|         } catch (e) {
 | |
|             console.erorr(e)
 | |
|             DetermineLayout.ShowErrorOnCustomTheme(
 | |
|                 `<a href="${link}">${link}</a> is invalid - probably not found or invalid JSON:`,
 | |
|                 new FixedUiElement(e)
 | |
|             )
 | |
|             return null;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public static LoadLayoutFromHash(
 | |
|         userLayoutParam: UIEventSource<string>
 | |
|     ): [LayoutConfig, string] | null {
 | |
|         let hash = location.hash.substr(1);
 | |
|         try {
 | |
|             // layoutFromBase64 contains the name of the theme. This is partly to do tracking with goat counter
 | |
|             const dedicatedHashFromLocalStorage = LocalStorageSource.Get(
 | |
|                 "user-layout-" + userLayoutParam.data?.replace(" ", "_")
 | |
|             );
 | |
|             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);
 | |
|             }
 | |
| 
 | |
|             let json: any;
 | |
|             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) {
 | |
|                     console.error(e)
 | |
|                     DetermineLayout.ShowErrorOnCustomTheme("Could not decode the hash", new FixedUiElement("Not a valid (LZ-compressed) JSON"))
 | |
|                     return null;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             const layoutToUse = new LayoutConfig(json, false);
 | |
|             userLayoutParam.setData(layoutToUse.id);
 | |
|             return [layoutToUse, btoa(Utils.MinifyJSON(JSON.stringify(json)))];
 | |
|         } catch (e) {
 | |
|             console.error(e)
 | |
|             if (hash === undefined || hash.length < 10) {
 | |
|                 DetermineLayout.ShowErrorOnCustomTheme("Could not load a theme from the hash", new FixedUiElement("Hash does not contain data"))
 | |
|             }
 | |
|             return null;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public static ShowErrorOnCustomTheme(
 | |
|         intro: string = "Error: could not parse the custom layout:",
 | |
|         error: BaseUIElement) {
 | |
|         new Combine([
 | |
|             intro,
 | |
|             error.SetClass("alert"),
 | |
|             new SubtleButton("./assets/svg/mapcomplete_logo.svg",
 | |
|                 "Go back to the theme overview",
 | |
|                 {url: window.location.protocol + "//" + window.location.hostname + "/index.html", newTab: false})
 | |
| 
 | |
|         ])
 | |
|             .SetClass("flex flex-col clickable")
 | |
|             .AttachTo("centermessage");
 | |
|     }
 | |
| 
 | |
| } |