forked from MapComplete/MapComplete
		
	Fix: update and simplification of sharescreen, rename some feature switches, remove some no longer relevant feature switches
This commit is contained in:
		
							parent
							
								
									b30b029aff
								
							
						
					
					
						commit
						96d036781f
					
				
					 11 changed files with 252 additions and 343 deletions
				
			
		|  | @ -51,10 +51,9 @@ export default class FeatureSwitchState extends OsmConnectionFeatureSwitches { | |||
|      */ | ||||
|     public readonly layoutToUse: LayoutConfig | ||||
| 
 | ||||
|     public readonly featureSwitchUserbadge: UIEventSource<boolean> | ||||
|     public readonly featureSwitchEnableLogin: UIEventSource<boolean> | ||||
|     public readonly featureSwitchSearch: UIEventSource<boolean> | ||||
|     public readonly featureSwitchBackgroundSelection: UIEventSource<boolean> | ||||
|     public readonly featureSwitchAddNew: UIEventSource<boolean> | ||||
|     public readonly featureSwitchWelcomeMessage: UIEventSource<boolean> | ||||
|     public readonly featureSwitchCommunityIndex: UIEventSource<boolean> | ||||
|     public readonly featureSwitchExtraLinkEnabled: UIEventSource<boolean> | ||||
|  | @ -78,10 +77,10 @@ export default class FeatureSwitchState extends OsmConnectionFeatureSwitches { | |||
| 
 | ||||
|         // Helper function to initialize feature switches
 | ||||
| 
 | ||||
|         this.featureSwitchUserbadge = FeatureSwitchUtils.initSwitch( | ||||
|             "fs-userbadge", | ||||
|         this.featureSwitchEnableLogin = FeatureSwitchUtils.initSwitch( | ||||
|             "fs-enable-login", | ||||
|             layoutToUse?.enableUserBadge ?? true, | ||||
|             "Disables/Enables the user information pill (userbadge) at the top left. Disabling this disables logging in and thus disables editing all together, effectively putting MapComplete into read-only mode." | ||||
|             "Disables/Enables logging in and thus disables editing all together. This effectively puts MapComplete into read-only mode." | ||||
|         ) | ||||
|         this.featureSwitchSearch = FeatureSwitchUtils.initSwitch( | ||||
|             "fs-search", | ||||
|  | @ -99,11 +98,7 @@ export default class FeatureSwitchState extends OsmConnectionFeatureSwitches { | |||
|             layoutToUse?.enableLayers ?? true, | ||||
|             "Disables/Enables the filter view" | ||||
|         ) | ||||
|         this.featureSwitchAddNew = FeatureSwitchUtils.initSwitch( | ||||
|             "fs-add-new", | ||||
|             layoutToUse?.enableAddNewPoints ?? true, | ||||
|             "Disables/Enables the 'add new feature'-popup. (A theme without presets might not have it in the first place)" | ||||
|         ) | ||||
| 
 | ||||
|         this.featureSwitchWelcomeMessage = FeatureSwitchUtils.initSwitch( | ||||
|             "fs-welcome-message", | ||||
|             true, | ||||
|  | @ -201,12 +196,6 @@ export default class FeatureSwitchState extends OsmConnectionFeatureSwitches { | |||
|             ) | ||||
|         ) | ||||
| 
 | ||||
|         this.featureSwitchUserbadge.addCallbackAndRun((userbadge) => { | ||||
|             if (!userbadge) { | ||||
|                 this.featureSwitchAddNew.setData(false) | ||||
|             } | ||||
|         }) | ||||
| 
 | ||||
|         this.backgroundLayerId = QueryParameters.GetQueryParameter( | ||||
|             "background", | ||||
|             layoutToUse?.defaultBackgroundId ?? "osm", | ||||
|  |  | |||
|  | @ -4,14 +4,13 @@ | |||
| import { UIEventSource } from "../UIEventSource" | ||||
| import Hash from "./Hash" | ||||
| import { Utils } from "../../Utils" | ||||
| import doc = Mocha.reporters.doc | ||||
| 
 | ||||
| export class QueryParameters { | ||||
|     static defaults: Record<string, string> = {} | ||||
|     static documentation: Map<string, string> = new Map<string, string>() | ||||
|     private static order: string[] = ["layout", "test", "z", "lat", "lon"] | ||||
|     protected static readonly _wasInitialized: Set<string> = new Set() | ||||
|     protected static readonly knownSources: Record<string, UIEventSource<string>> = {} | ||||
|     private static order: string[] = ["layout", "test", "z", "lat", "lon"] | ||||
|     private static initialized = false | ||||
| 
 | ||||
|     public static GetQueryParameter( | ||||
|  | @ -74,6 +73,7 @@ export class QueryParameters { | |||
|         this.init() | ||||
|         return QueryParameters._wasInitialized.has(key) | ||||
|     } | ||||
| 
 | ||||
|     public static initializedParameters(): ReadonlyArray<string> { | ||||
|         return Array.from(QueryParameters._wasInitialized.keys()) | ||||
|     } | ||||
|  | @ -108,14 +108,12 @@ export class QueryParameters { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set the query parameters of the page location | ||||
|      * @constructor | ||||
|      * @private | ||||
|      */ | ||||
|     private static Serialize() { | ||||
|         const parts = [] | ||||
|     public static GetParts(exclude?: Set<string>) { | ||||
|         const parts: string[] = [] | ||||
|         for (const key of QueryParameters.order) { | ||||
|             if (exclude?.has(key)) { | ||||
|                 continue | ||||
|             } | ||||
|             if (QueryParameters.knownSources[key]?.data === undefined) { | ||||
|                 continue | ||||
|             } | ||||
|  | @ -134,6 +132,16 @@ export class QueryParameters { | |||
|                     encodeURIComponent(QueryParameters.knownSources[key].data) | ||||
|             ) | ||||
|         } | ||||
|         return parts | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set the query parameters of the page location | ||||
|      * @constructor | ||||
|      * @private | ||||
|      */ | ||||
|     private static Serialize() { | ||||
|         const parts = QueryParameters.GetParts() | ||||
|         if (!Utils.runningFromConsole) { | ||||
|             // Don't pollute the history every time a parameter changes
 | ||||
|             try { | ||||
|  | @ -151,4 +159,8 @@ export class QueryParameters { | |||
|         QueryParameters._wasInitialized.clear() | ||||
|         QueryParameters.order = [] | ||||
|     } | ||||
| 
 | ||||
|     static GetDefaultFor(key: string): string { | ||||
|         return QueryParameters.defaults[key] | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -1,9 +1,25 @@ | |||
| import ThemeViewState from "../../Models/ThemeViewState" | ||||
| import Hash from "./Hash" | ||||
| import { MenuState } from "../../Models/MenuState" | ||||
| 
 | ||||
| export default class ThemeViewStateHashActor { | ||||
|     private readonly _state: ThemeViewState | ||||
| 
 | ||||
|     public static readonly documentation = [ | ||||
|         "The URL-hash can contain multiple values:", | ||||
|         "", | ||||
|         "- The id of the currently selected object, e.g. `node/1234`", | ||||
|         "- The currently opened menu view", | ||||
|         "- The base64-encoded JSON-file specifying a custom theme (only when loading)", | ||||
|         "", | ||||
|         "### Possible hashes to open a menu", | ||||
|         "", | ||||
|         "The possible hashes are:", | ||||
|         "", | ||||
|         MenuState._menuviewTabs.map((tab) => "`menu:" + tab + "`").join(","), | ||||
|         MenuState._themeviewTabs.map((tab) => "`theme-menu:" + tab + "`").join(","), | ||||
|     ] | ||||
| 
 | ||||
|     /** | ||||
|      * Converts the hash to the appropriate themeview state and, vice versa, sets the hash. | ||||
|      * | ||||
|  |  | |||
|  | @ -50,11 +50,15 @@ export class MenuState { | |||
|     ) | ||||
|     public highlightedUserSetting: UIEventSource<string> = new UIEventSource<string>(undefined) | ||||
| 
 | ||||
|     constructor(themeid: string = "") { | ||||
|     constructor(shouldOpenWelcomeMessage: boolean, themeid: string = "") { | ||||
|         // Note: this class is _not_ responsible to update the Hash, @see ThemeViewStateHashActor for this
 | ||||
|         if (themeid) { | ||||
|             themeid += "-" | ||||
|         } | ||||
|         this.themeIsOpened = LocalStorageSource.GetParsed(themeid + "thememenuisopened", true) | ||||
|         this.themeIsOpened = LocalStorageSource.GetParsed( | ||||
|             themeid + "thememenuisopened", | ||||
|             shouldOpenWelcomeMessage | ||||
|         ) | ||||
|         this.themeViewTabIndex = LocalStorageSource.GetParsed(themeid + "themeviewtabindex", 0) | ||||
|         this.themeViewTab = this.themeViewTabIndex.sync( | ||||
|             (i) => MenuState._themeviewTabs[i], | ||||
|  |  | |||
|  | @ -114,15 +114,18 @@ export default class ThemeViewState implements SpecialVisualizationState { | |||
| 
 | ||||
|     constructor(layout: LayoutConfig) { | ||||
|         this.layout = layout | ||||
|         this.guistate = new MenuState(layout.id) | ||||
|         this.featureSwitches = new FeatureSwitchState(layout) | ||||
|         this.guistate = new MenuState( | ||||
|             this.featureSwitches.featureSwitchWelcomeMessage.data, | ||||
|             layout.id | ||||
|         ) | ||||
|         this.map = new UIEventSource<MlMap>(undefined) | ||||
|         const initial = new InitialMapPositioning(layout) | ||||
|         this.mapProperties = new MapLibreAdaptor(this.map, initial) | ||||
|         const geolocationState = new GeoLocationState() | ||||
| 
 | ||||
|         this.featureSwitches = new FeatureSwitchState(layout) | ||||
|         this.featureSwitchIsTesting = this.featureSwitches.featureSwitchIsTesting | ||||
|         this.featureSwitchUserbadge = this.featureSwitches.featureSwitchUserbadge | ||||
|         this.featureSwitchUserbadge = this.featureSwitches.featureSwitchEnableLogin | ||||
| 
 | ||||
|         this.osmConnection = new OsmConnection({ | ||||
|             dryRun: this.featureSwitches.featureSwitchIsTesting, | ||||
|  | @ -469,7 +472,7 @@ export default class ThemeViewState implements SpecialVisualizationState { | |||
| 
 | ||||
|         new ShowDataLayer(this.map, { | ||||
|             features: new FilteringFeatureSource(last_click_layer, last_click), | ||||
|             doShowLayer: new ImmutableStore(true), | ||||
|             doShowLayer: this.featureSwitches.featureSwitchEnableLogin, | ||||
|             layer: last_click_layer.layerDef, | ||||
|             selectedElement: this.selectedElement, | ||||
|             selectedLayer: this.selectedLayer, | ||||
|  |  | |||
							
								
								
									
										121
									
								
								src/UI/BigComponents/ShareScreen.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								src/UI/BigComponents/ShareScreen.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,121 @@ | |||
| <script lang="ts">/** | ||||
|  * A screen showing: | ||||
|  * - A link to share the current view | ||||
|  * - Some query parameters that can be enabled/disabled | ||||
|  * - The code to embed MC as IFrame | ||||
|  */ | ||||
| 
 | ||||
| import ThemeViewState from "../../Models/ThemeViewState"; | ||||
| import { QueryParameters } from "../../Logic/Web/QueryParameters"; | ||||
| import Tr from "../Base/Tr.svelte"; | ||||
| import Translations from "../i18n/Translations"; | ||||
| import { Utils } from "../../Utils"; | ||||
| import Svg from "../../Svg"; | ||||
| import ToSvelte from "../Base/ToSvelte.svelte"; | ||||
| import { DocumentDuplicateIcon } from "@rgossiaux/svelte-heroicons/outline"; | ||||
| 
 | ||||
| export let state: ThemeViewState; | ||||
| const tr = Translations.t.general.sharescreen; | ||||
| 
 | ||||
| let url = window.location; | ||||
| let linkToShare: string = undefined; | ||||
| /** | ||||
|  * In some cases (local deploys, custom themes), we need to set the URL to `/theme.html?layout=xyz` instead of `/xyz?...` | ||||
|  */ | ||||
| let needsThemeRedirect = url.port !== "" || url.hostname.match(/^[0-9]/) || !state.layout.official; | ||||
| let layoutId = state.layout.id; | ||||
| let baseLink = url.protocol + "//" + url.host + "/" + (needsThemeRedirect ? "theme.html?layout=" + layoutId + "&" : layoutId + "?"); | ||||
| 
 | ||||
| let showWelcomeMessage = true; | ||||
| let enableLogin = true; | ||||
| $: { | ||||
|   const layout = state.layout; | ||||
|   let excluded = Utils.NoNull([ | ||||
|     showWelcomeMessage ? undefined : "fs-welcome-message", | ||||
|     enableLogin ? undefined : "fs-enable-login" | ||||
|   ]); | ||||
|   linkToShare = baseLink + QueryParameters.GetParts(new Set(excluded)) | ||||
|     .concat(excluded.map(k => k + "=" + false)) | ||||
|     .join("&"); | ||||
|   if (layout.definitionRaw !== undefined) { | ||||
|     linkToShare += "&userlayout=" + (layout.definedAtUrl ?? layout.id); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| async function shareCurrentLink() { | ||||
|   await navigator.share({ | ||||
|     title: Translations.W(state.layout.title)?.ConstructElement().textContent ?? "MapComplete", | ||||
|     text: Translations.W(state.layout.description)?.ConstructElement().textContent ?? "", | ||||
|     url: linkToShare | ||||
|   }); | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| let isCopied = false; | ||||
| 
 | ||||
| async function copyCurrentLink() { | ||||
|   await navigator.clipboard.writeText(linkToShare); | ||||
|   isCopied = true; | ||||
|   await Utils.waitFor(5000); | ||||
|   isCopied = false; | ||||
| } | ||||
| 
 | ||||
| </script> | ||||
| 
 | ||||
| 
 | ||||
| <div> | ||||
| 
 | ||||
|   <Tr t={tr.intro} /> | ||||
|   <div class="flex"> | ||||
|     {#if typeof navigator?.share === "function"} | ||||
|       <button class="w-8 h-8 p-1 shrink-0" on:click={shareCurrentLink}> | ||||
|         <ToSvelte construct={Svg.share_svg()} /> | ||||
|       </button> | ||||
|     {/if} | ||||
|     {#if navigator.clipboard !== undefined} | ||||
|       <button class="w-8 h-8 p-1  shrink-0 no-image-background" on:click={copyCurrentLink}> | ||||
|         <DocumentDuplicateIcon /> | ||||
|       </button> | ||||
|     {/if} | ||||
|     <div class="literal-code" on:click={e => Utils.selectTextIn(e.target)}> | ||||
|       {linkToShare} | ||||
|     </div> | ||||
|   </div> | ||||
| 
 | ||||
|   <div class="flex justify-center"> | ||||
| 
 | ||||
|     {#if isCopied} | ||||
|       <Tr t={tr.copiedToClipboard} cls="thanks m-2" /> | ||||
|     {/if} | ||||
|   </div> | ||||
| 
 | ||||
| 
 | ||||
|   <Tr t={ tr.embedIntro} /> | ||||
| 
 | ||||
| 
 | ||||
|   <div class="flex flex-col my-1 link-underline"> | ||||
| 
 | ||||
|     <label> | ||||
|       <input bind:checked={showWelcomeMessage} type="checkbox" /> | ||||
|       <Tr t={tr.fsWelcomeMessage} /> | ||||
|     </label> | ||||
| 
 | ||||
| 
 | ||||
|     <label> | ||||
|       <input bind:checked={enableLogin} type="checkbox" /> | ||||
|       <Tr t={tr.fsUserbadge} /> | ||||
|     </label> | ||||
|   </div> | ||||
| 
 | ||||
|   <div class="literal-code m-1"> | ||||
|     <span class="literal-code iframe-code-block"> <br /> | ||||
|     <iframe src="${url}" <br /> | ||||
|     allow="geolocation" width="100%" height="100%" style="min-width: 250px; min-height: 250px" <br /> | ||||
|     title="${state.layout.title?.txt ?? "MapComplete"  } with MapComplete"> <br /> | ||||
|     </iframe> <br /> | ||||
|     </span> | ||||
|   </div> | ||||
|   <Tr t={tr.documentation} cls="link-underline"/> | ||||
| </div> | ||||
|  | @ -1,256 +0,0 @@ | |||
| import { VariableUiElement } from "../Base/VariableUIElement" | ||||
| import { Translation } from "../i18n/Translation" | ||||
| import Svg from "../../Svg" | ||||
| import Combine from "../Base/Combine" | ||||
| import { Store, UIEventSource } from "../../Logic/UIEventSource" | ||||
| import { Utils } from "../../Utils" | ||||
| import Translations from "../i18n/Translations" | ||||
| import BaseUIElement from "../BaseUIElement" | ||||
| import LayerConfig from "../../Models/ThemeConfig/LayerConfig" | ||||
| import { InputElement } from "../Input/InputElement" | ||||
| import { CheckBox } from "../Input/Checkboxes" | ||||
| import { SubtleButton } from "../Base/SubtleButton" | ||||
| import LZString from "lz-string" | ||||
| import { SpecialVisualizationState } from "../SpecialVisualization" | ||||
| 
 | ||||
| export class ShareScreen extends Combine { | ||||
|     constructor(state: SpecialVisualizationState) { | ||||
|         const layout = state?.layout | ||||
|         const tr = Translations.t.general.sharescreen | ||||
| 
 | ||||
|         const optionCheckboxes: InputElement<boolean>[] = [] | ||||
|         const optionParts: Store<string>[] = [] | ||||
| 
 | ||||
|         const includeLocation = new CheckBox(tr.fsIncludeCurrentLocation, true) | ||||
|         optionCheckboxes.push(includeLocation) | ||||
| 
 | ||||
|         const currentLocation = state.mapProperties.location | ||||
|         const zoom = state.mapProperties.zoom | ||||
| 
 | ||||
|         optionParts.push( | ||||
|             includeLocation.GetValue().map( | ||||
|                 (includeL) => { | ||||
|                     if (currentLocation === undefined) { | ||||
|                         return null | ||||
|                     } | ||||
|                     if (includeL) { | ||||
|                         return [ | ||||
|                             ["z", zoom.data], | ||||
|                             ["lat", currentLocation.data?.lat], | ||||
|                             ["lon", currentLocation.data?.lon], | ||||
|                         ] | ||||
|                             .filter((p) => p[1] !== undefined) | ||||
|                             .map((p) => p[0] + "=" + p[1]) | ||||
|                             .join("&") | ||||
|                     } else { | ||||
|                         return null | ||||
|                     } | ||||
|                 }, | ||||
|                 [currentLocation, zoom] | ||||
|             ) | ||||
|         ) | ||||
| 
 | ||||
|         function fLayerToParam(flayer: { | ||||
|             isDisplayed: UIEventSource<boolean> | ||||
|             layerDef: LayerConfig | ||||
|         }) { | ||||
|             if (flayer.isDisplayed.data) { | ||||
|                 return null // Being displayed is the default
 | ||||
|             } | ||||
|             return "layer-" + flayer.layerDef.id + "=" + flayer.isDisplayed.data | ||||
|         } | ||||
| 
 | ||||
|         const currentLayer: Store< | ||||
|             { id: string; name: string | Record<string, string> } | undefined | ||||
|         > = state.mapProperties.rasterLayer.map((l) => l?.properties) | ||||
|         const currentBackground = new VariableUiElement( | ||||
|             currentLayer.map((layer) => { | ||||
|                 return tr.fsIncludeCurrentBackgroundMap.Subs({ name: layer?.name ?? "" }) | ||||
|             }) | ||||
|         ) | ||||
|         const includeCurrentBackground = new CheckBox(currentBackground, true) | ||||
|         optionCheckboxes.push(includeCurrentBackground) | ||||
|         optionParts.push( | ||||
|             includeCurrentBackground.GetValue().map( | ||||
|                 (includeBG) => { | ||||
|                     if (includeBG) { | ||||
|                         return "background=" + currentLayer.data?.id | ||||
|                     } else { | ||||
|                         return null | ||||
|                     } | ||||
|                 }, | ||||
|                 [currentLayer] | ||||
|             ) | ||||
|         ) | ||||
| 
 | ||||
|         const includeLayerChoices = new CheckBox(tr.fsIncludeCurrentLayers, true) | ||||
|         optionCheckboxes.push(includeLayerChoices) | ||||
| 
 | ||||
|         optionParts.push( | ||||
|             includeLayerChoices.GetValue().map( | ||||
|                 (includeLayerSelection) => { | ||||
|                     if (includeLayerSelection) { | ||||
|                         return Utils.NoNull( | ||||
|                             Array.from(state.layerState.filteredLayers.values()).map(fLayerToParam) | ||||
|                         ).join("&") | ||||
|                     } else { | ||||
|                         return null | ||||
|                     } | ||||
|                 }, | ||||
|                 Array.from(state.layerState.filteredLayers.values()).map( | ||||
|                     (flayer) => flayer.isDisplayed | ||||
|                 ) | ||||
|             ) | ||||
|         ) | ||||
| 
 | ||||
|         const switches = [ | ||||
|             { urlName: "fs-userbadge", human: tr.fsUserbadge }, | ||||
|             { urlName: "fs-search", human: tr.fsSearch }, | ||||
|             { urlName: "fs-welcome-message", human: tr.fsWelcomeMessage }, | ||||
|             { urlName: "fs-layers", human: tr.fsLayers }, | ||||
|             { urlName: "fs-add-new", human: tr.fsAddNew }, | ||||
|             { urlName: "fs-geolocation", human: tr.fsGeolocation }, | ||||
|         ] | ||||
| 
 | ||||
|         for (const swtch of switches) { | ||||
|             const checkbox = new CheckBox(Translations.W(swtch.human)) | ||||
|             optionCheckboxes.push(checkbox) | ||||
|             optionParts.push( | ||||
|                 checkbox.GetValue().map((isEn) => { | ||||
|                     if (isEn) { | ||||
|                         return null | ||||
|                     } else { | ||||
|                         return `${swtch.urlName}=false` | ||||
|                     } | ||||
|                 }) | ||||
|             ) | ||||
|         } | ||||
| 
 | ||||
|         if (layout.definitionRaw !== undefined) { | ||||
|             optionParts.push(new UIEventSource("userlayout=" + (layout.definedAtUrl ?? layout.id))) | ||||
|         } | ||||
| 
 | ||||
|         const options = new Combine(optionCheckboxes).SetClass("flex flex-col") | ||||
|         const url = (currentLocation ?? new UIEventSource(undefined)).map(() => { | ||||
|             const host = window.location.host | ||||
|             let path = window.location.pathname | ||||
|             path = path.substr(0, path.lastIndexOf("/")) | ||||
|             let id = layout.id.toLowerCase() | ||||
|             if (layout.definitionRaw !== undefined) { | ||||
|                 id = "theme.html" | ||||
|             } | ||||
|             let literalText = `https://${host}${path}/${id}` | ||||
| 
 | ||||
|             let hash = "" | ||||
|             if (layout.definedAtUrl === undefined && layout.definitionRaw !== undefined) { | ||||
|                 hash = "#" + LZString.compressToBase64(Utils.MinifyJSON(layout.definitionRaw)) | ||||
|             } | ||||
|             const parts = Utils.NoEmpty( | ||||
|                 Utils.NoNull(optionParts.map((eventSource) => eventSource.data)) | ||||
|             ) | ||||
|             if (parts.length === 0) { | ||||
|                 return literalText + hash | ||||
|             } | ||||
|             return literalText + "?" + parts.join("&") + hash | ||||
|         }, optionParts) | ||||
| 
 | ||||
|         const iframeCode = new VariableUiElement( | ||||
|             url.map((url) => { | ||||
|                 return `<span class='literal-code iframe-code-block'>
 | ||||
|                          <iframe src="${url}" allow="geolocation" width="100%" height="100%" style="min-width: 250px; min-height: 250px" title="${ | ||||
|                     layout.title?.txt ?? "MapComplete" | ||||
|                 } with MapComplete"></iframe> | ||||
|                     </span>` | ||||
|             }) | ||||
|         ) | ||||
| 
 | ||||
|         const linkStatus = new UIEventSource<string | Translation>("") | ||||
|         const link = new VariableUiElement( | ||||
|             url.map( | ||||
|                 (url) => | ||||
|                     `<input type="text" value=" ${url}" id="code-link--copyable" style="width:90%">` | ||||
|             ) | ||||
|         ).onClick(async () => { | ||||
|             const shareData = { | ||||
|                 title: Translations.W(layout.title)?.ConstructElement().textContent ?? "", | ||||
|                 text: Translations.W(layout.description)?.ConstructElement().textContent ?? "", | ||||
|                 url: url.data, | ||||
|             } | ||||
| 
 | ||||
|             function rejected() { | ||||
|                 const copyText = document.getElementById("code-link--copyable") | ||||
| 
 | ||||
|                 // @ts-ignore
 | ||||
|                 copyText.select() | ||||
|                 // @ts-ignore
 | ||||
|                 copyText.setSelectionRange(0, 99999) /*For mobile devices*/ | ||||
| 
 | ||||
|                 document.execCommand("copy") | ||||
|                 const copied = tr.copiedToClipboard.Clone() | ||||
|                 copied.SetClass("thanks") | ||||
|                 linkStatus.setData(copied) | ||||
|             } | ||||
| 
 | ||||
|             try { | ||||
|                 navigator | ||||
|                     .share(shareData) | ||||
|                     .then(() => { | ||||
|                         const thx = tr.thanksForSharing.Clone() | ||||
|                         thx.SetClass("thanks") | ||||
|                         linkStatus.setData(thx) | ||||
|                     }, rejected) | ||||
|                     .catch(rejected) | ||||
|             } catch (err) { | ||||
|                 rejected() | ||||
|             } | ||||
|         }) | ||||
| 
 | ||||
|         let downloadThemeConfig: BaseUIElement = undefined | ||||
|         if (layout.definitionRaw !== undefined) { | ||||
|             const downloadThemeConfigAsJson = new SubtleButton( | ||||
|                 Svg.download_svg(), | ||||
|                 new Combine([tr.downloadCustomTheme, tr.downloadCustomThemeHelp.SetClass("subtle")]) | ||||
|                     .onClick(() => { | ||||
|                         Utils.offerContentsAsDownloadableFile( | ||||
|                             layout.definitionRaw, | ||||
|                             layout.id + ".mapcomplete-theme-definition.json", | ||||
|                             { | ||||
|                                 mimetype: "application/json", | ||||
|                             } | ||||
|                         ) | ||||
|                     }) | ||||
|                     .SetClass("flex flex-col") | ||||
|             ) | ||||
|             let editThemeConfig: BaseUIElement = undefined | ||||
|             if (layout.definedAtUrl === undefined) { | ||||
|                 const patchedDefinition = JSON.parse(layout.definitionRaw) | ||||
|                 patchedDefinition["language"] = Object.keys(patchedDefinition.title) | ||||
|                 editThemeConfig = new SubtleButton( | ||||
|                     Svg.pencil_svg(), | ||||
|                     "Edit this theme on the custom theme generator", | ||||
|                     { | ||||
|                         url: `https://pietervdvn.github.io/mc/legacy/070/customGenerator.html#${btoa( | ||||
|                             JSON.stringify(patchedDefinition) | ||||
|                         )}`,
 | ||||
|                     } | ||||
|                 ) | ||||
|             } | ||||
|             downloadThemeConfig = new Combine([ | ||||
|                 downloadThemeConfigAsJson, | ||||
|                 editThemeConfig, | ||||
|             ]).SetClass("flex flex-col") | ||||
|         } | ||||
| 
 | ||||
|         super([ | ||||
|             tr.intro, | ||||
|             link, | ||||
|             new VariableUiElement(linkStatus), | ||||
|             downloadThemeConfig, | ||||
|             tr.addToHomeScreen, | ||||
|             tr.embedIntro, | ||||
|             options, | ||||
|             iframeCode, | ||||
|         ]) | ||||
|         this.SetClass("flex flex-col link-underline") | ||||
|     } | ||||
| } | ||||
|  | @ -45,9 +45,7 @@ | |||
|     <Tr t={layout.description} /> | ||||
|     <Tr t={Translations.t.general.welcomeExplanation.general} /> | ||||
|     {#if layout.layers.some((l) => l.presets?.length > 0)} | ||||
|       <If condition={state.featureSwitches.featureSwitchAddNew}> | ||||
|         <Tr t={Translations.t.general.welcomeExplanation.addNew} /> | ||||
|       </If> | ||||
|     {/if} | ||||
| 
 | ||||
|     <Tr t={layout.descriptionTail} /> | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ import Translations from "./i18n/Translations" | |||
| import { QueryParameters } from "../Logic/Web/QueryParameters" | ||||
| import FeatureSwitchState from "../Logic/State/FeatureSwitchState" | ||||
| import LayoutConfig from "../Models/ThemeConfig/LayoutConfig" | ||||
| import ThemeViewStateHashActor from "../Logic/Web/ThemeViewStateHashActor" | ||||
| 
 | ||||
| export default class QueryParameterDocumentation { | ||||
|     private static QueryParamDocsIntro = [ | ||||
|  | @ -60,6 +61,7 @@ export default class QueryParameterDocumentation { | |||
|     public static GenerateQueryParameterDocs(): BaseUIElement { | ||||
|         const docs: (string | BaseUIElement)[] = [ | ||||
|             ...QueryParameterDocumentation.QueryParamDocsIntro, | ||||
|             ...ThemeViewStateHashActor.documentation, | ||||
|         ] | ||||
|         this.UrlParamDocs().forEach((value, key) => { | ||||
|             const c = new Combine([ | ||||
|  |  | |||
|  | @ -1,57 +1,57 @@ | |||
| <script lang="ts"> | ||||
|   import { Store, UIEventSource } from "../Logic/UIEventSource" | ||||
|   import { Map as MlMap } from "maplibre-gl" | ||||
|   import MaplibreMap from "./Map/MaplibreMap.svelte" | ||||
|   import FeatureSwitchState from "../Logic/State/FeatureSwitchState" | ||||
|   import MapControlButton from "./Base/MapControlButton.svelte" | ||||
|   import ToSvelte from "./Base/ToSvelte.svelte" | ||||
|   import If from "./Base/If.svelte" | ||||
|   import { GeolocationControl } from "./BigComponents/GeolocationControl" | ||||
|   import type { Feature } from "geojson" | ||||
|   import SelectedElementView from "./BigComponents/SelectedElementView.svelte" | ||||
|   import LayerConfig from "../Models/ThemeConfig/LayerConfig" | ||||
|   import Filterview from "./BigComponents/Filterview.svelte" | ||||
|   import ThemeViewState from "../Models/ThemeViewState" | ||||
|   import type { MapProperties } from "../Models/MapProperties" | ||||
|   import Geosearch from "./BigComponents/Geosearch.svelte" | ||||
|   import Translations from "./i18n/Translations" | ||||
|   import { CogIcon, EyeIcon, MenuIcon, XCircleIcon } from "@rgossiaux/svelte-heroicons/solid" | ||||
|   import { Store, UIEventSource } from "../Logic/UIEventSource"; | ||||
|   import { Map as MlMap } from "maplibre-gl"; | ||||
|   import MaplibreMap from "./Map/MaplibreMap.svelte"; | ||||
|   import FeatureSwitchState from "../Logic/State/FeatureSwitchState"; | ||||
|   import MapControlButton from "./Base/MapControlButton.svelte"; | ||||
|   import ToSvelte from "./Base/ToSvelte.svelte"; | ||||
|   import If from "./Base/If.svelte"; | ||||
|   import { GeolocationControl } from "./BigComponents/GeolocationControl"; | ||||
|   import type { Feature } from "geojson"; | ||||
|   import SelectedElementView from "./BigComponents/SelectedElementView.svelte"; | ||||
|   import LayerConfig from "../Models/ThemeConfig/LayerConfig"; | ||||
|   import Filterview from "./BigComponents/Filterview.svelte"; | ||||
|   import ThemeViewState from "../Models/ThemeViewState"; | ||||
|   import type { MapProperties } from "../Models/MapProperties"; | ||||
|   import Geosearch from "./BigComponents/Geosearch.svelte"; | ||||
|   import Translations from "./i18n/Translations"; | ||||
|   import { CogIcon, EyeIcon, MenuIcon, XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"; | ||||
| 
 | ||||
|   import Tr from "./Base/Tr.svelte" | ||||
|   import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte" | ||||
|   import FloatOver from "./Base/FloatOver.svelte" | ||||
|   import PrivacyPolicy from "./BigComponents/PrivacyPolicy" | ||||
|   import Constants from "../Models/Constants" | ||||
|   import TabbedGroup from "./Base/TabbedGroup.svelte" | ||||
|   import UserRelatedState from "../Logic/State/UserRelatedState" | ||||
|   import LoginToggle from "./Base/LoginToggle.svelte" | ||||
|   import LoginButton from "./Base/LoginButton.svelte" | ||||
|   import CopyrightPanel from "./BigComponents/CopyrightPanel" | ||||
|   import DownloadPanel from "./DownloadFlow/DownloadPanel.svelte" | ||||
|   import ModalRight from "./Base/ModalRight.svelte" | ||||
|   import { Utils } from "../Utils" | ||||
|   import Hotkeys from "./Base/Hotkeys" | ||||
|   import { VariableUiElement } from "./Base/VariableUIElement" | ||||
|   import SvelteUIElement from "./Base/SvelteUIElement" | ||||
|   import OverlayToggle from "./BigComponents/OverlayToggle.svelte" | ||||
|   import LevelSelector from "./BigComponents/LevelSelector.svelte" | ||||
|   import ExtraLinkButton from "./BigComponents/ExtraLinkButton" | ||||
|   import SelectedElementTitle from "./BigComponents/SelectedElementTitle.svelte" | ||||
|   import Svg from "../Svg" | ||||
|   import { ShareScreen } from "./BigComponents/ShareScreen" | ||||
|   import ThemeIntroPanel from "./BigComponents/ThemeIntroPanel.svelte" | ||||
|   import type { RasterLayerPolygon } from "../Models/RasterLayers" | ||||
|   import { AvailableRasterLayers } from "../Models/RasterLayers" | ||||
|   import RasterLayerOverview from "./Map/RasterLayerOverview.svelte" | ||||
|   import IfHidden from "./Base/IfHidden.svelte" | ||||
|   import { onDestroy } from "svelte" | ||||
|   import { OpenJosm } from "./BigComponents/OpenJosm" | ||||
|   import MapillaryLink from "./BigComponents/MapillaryLink.svelte" | ||||
|   import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte" | ||||
|   import OpenBackgroundSelectorButton from "./BigComponents/OpenBackgroundSelectorButton.svelte" | ||||
|   import StateIndicator from "./BigComponents/StateIndicator.svelte" | ||||
|   import LanguagePicker from "./LanguagePicker" | ||||
|   import Locale from "./i18n/Locale" | ||||
|   import Tr from "./Base/Tr.svelte"; | ||||
|   import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte"; | ||||
|   import FloatOver from "./Base/FloatOver.svelte"; | ||||
|   import PrivacyPolicy from "./BigComponents/PrivacyPolicy"; | ||||
|   import Constants from "../Models/Constants"; | ||||
|   import TabbedGroup from "./Base/TabbedGroup.svelte"; | ||||
|   import UserRelatedState from "../Logic/State/UserRelatedState"; | ||||
|   import LoginToggle from "./Base/LoginToggle.svelte"; | ||||
|   import LoginButton from "./Base/LoginButton.svelte"; | ||||
|   import CopyrightPanel from "./BigComponents/CopyrightPanel"; | ||||
|   import DownloadPanel from "./DownloadFlow/DownloadPanel.svelte"; | ||||
|   import ModalRight from "./Base/ModalRight.svelte"; | ||||
|   import { Utils } from "../Utils"; | ||||
|   import Hotkeys from "./Base/Hotkeys"; | ||||
|   import { VariableUiElement } from "./Base/VariableUIElement"; | ||||
|   import SvelteUIElement from "./Base/SvelteUIElement"; | ||||
|   import OverlayToggle from "./BigComponents/OverlayToggle.svelte"; | ||||
|   import LevelSelector from "./BigComponents/LevelSelector.svelte"; | ||||
|   import ExtraLinkButton from "./BigComponents/ExtraLinkButton"; | ||||
|   import SelectedElementTitle from "./BigComponents/SelectedElementTitle.svelte"; | ||||
|   import Svg from "../Svg"; | ||||
|   import ThemeIntroPanel from "./BigComponents/ThemeIntroPanel.svelte"; | ||||
|   import type { RasterLayerPolygon } from "../Models/RasterLayers"; | ||||
|   import { AvailableRasterLayers } from "../Models/RasterLayers"; | ||||
|   import RasterLayerOverview from "./Map/RasterLayerOverview.svelte"; | ||||
|   import IfHidden from "./Base/IfHidden.svelte"; | ||||
|   import { onDestroy } from "svelte"; | ||||
|   import { OpenJosm } from "./BigComponents/OpenJosm"; | ||||
|   import MapillaryLink from "./BigComponents/MapillaryLink.svelte"; | ||||
|   import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte"; | ||||
|   import OpenBackgroundSelectorButton from "./BigComponents/OpenBackgroundSelectorButton.svelte"; | ||||
|   import StateIndicator from "./BigComponents/StateIndicator.svelte"; | ||||
|   import LanguagePicker from "./LanguagePicker"; | ||||
|   import Locale from "./i18n/Locale"; | ||||
|   import ShareScreen from "./BigComponents/ShareScreen.svelte"; | ||||
| 
 | ||||
|   export let state: ThemeViewState | ||||
|   let layout = state.layout | ||||
|  | @ -314,11 +314,12 @@ | |||
| 
 | ||||
|       <ToSvelte construct={() => new CopyrightPanel(state)} slot="content3" /> | ||||
| 
 | ||||
|       <div slot="title4"> | ||||
|       <div slot="title4" class="flex"> | ||||
|         <ToSvelte construct={Svg.share_svg().SetClass("w-4 h-4")} /> | ||||
|         <Tr t={Translations.t.general.sharescreen.title} /> | ||||
|       </div> | ||||
|       <div class="m-2" slot="content4"> | ||||
|         <ToSvelte construct={() => new ShareScreen(state)} /> | ||||
|         <ShareScreen {state}/> | ||||
|       </div> | ||||
|     </TabbedGroup> | ||||
|   </FloatOver> | ||||
|  |  | |||
							
								
								
									
										19
									
								
								src/Utils.ts
									
										
									
									
									
								
							
							
						
						
									
										19
									
								
								src/Utils.ts
									
										
									
									
									
								
							|  | @ -221,6 +221,9 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be | |||
|      * Utils.Round7(12.123456789) // => 12.1234568
 | ||||
|      */ | ||||
|     public static Round7(i: number): number { | ||||
|         if (i == undefined) { | ||||
|             return undefined | ||||
|         } | ||||
|         return Math.round(i * 10000000) / 10000000 | ||||
|     } | ||||
| 
 | ||||
|  | @ -1211,6 +1214,22 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be | |||
|         return new Date(str) | ||||
|     } | ||||
| 
 | ||||
|     public static selectTextIn(node) { | ||||
|         if (document.body["createTextRange"]) { | ||||
|             const range = document.body["createTextRange"]() | ||||
|             range.moveToElementText(node) | ||||
|             range.select() | ||||
|         } else if (window.getSelection) { | ||||
|             const selection = window.getSelection() | ||||
|             const range = document.createRange() | ||||
|             range.selectNodeContents(node) | ||||
|             selection.removeAllRanges() | ||||
|             selection.addRange(range) | ||||
|         } else { | ||||
|             console.warn("Could not select text in node: Unsupported browser.") | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static sortedByLevenshteinDistance<T>( | ||||
|         reference: string, | ||||
|         ts: T[], | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue