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 layoutToUse: LayoutConfig
|
||||||
|
|
||||||
public readonly featureSwitchUserbadge: UIEventSource<boolean>
|
public readonly featureSwitchEnableLogin: UIEventSource<boolean>
|
||||||
public readonly featureSwitchSearch: UIEventSource<boolean>
|
public readonly featureSwitchSearch: UIEventSource<boolean>
|
||||||
public readonly featureSwitchBackgroundSelection: UIEventSource<boolean>
|
public readonly featureSwitchBackgroundSelection: UIEventSource<boolean>
|
||||||
public readonly featureSwitchAddNew: UIEventSource<boolean>
|
|
||||||
public readonly featureSwitchWelcomeMessage: UIEventSource<boolean>
|
public readonly featureSwitchWelcomeMessage: UIEventSource<boolean>
|
||||||
public readonly featureSwitchCommunityIndex: UIEventSource<boolean>
|
public readonly featureSwitchCommunityIndex: UIEventSource<boolean>
|
||||||
public readonly featureSwitchExtraLinkEnabled: UIEventSource<boolean>
|
public readonly featureSwitchExtraLinkEnabled: UIEventSource<boolean>
|
||||||
|
@ -78,10 +77,10 @@ export default class FeatureSwitchState extends OsmConnectionFeatureSwitches {
|
||||||
|
|
||||||
// Helper function to initialize feature switches
|
// Helper function to initialize feature switches
|
||||||
|
|
||||||
this.featureSwitchUserbadge = FeatureSwitchUtils.initSwitch(
|
this.featureSwitchEnableLogin = FeatureSwitchUtils.initSwitch(
|
||||||
"fs-userbadge",
|
"fs-enable-login",
|
||||||
layoutToUse?.enableUserBadge ?? true,
|
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(
|
this.featureSwitchSearch = FeatureSwitchUtils.initSwitch(
|
||||||
"fs-search",
|
"fs-search",
|
||||||
|
@ -99,11 +98,7 @@ export default class FeatureSwitchState extends OsmConnectionFeatureSwitches {
|
||||||
layoutToUse?.enableLayers ?? true,
|
layoutToUse?.enableLayers ?? true,
|
||||||
"Disables/Enables the filter view"
|
"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(
|
this.featureSwitchWelcomeMessage = FeatureSwitchUtils.initSwitch(
|
||||||
"fs-welcome-message",
|
"fs-welcome-message",
|
||||||
true,
|
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(
|
this.backgroundLayerId = QueryParameters.GetQueryParameter(
|
||||||
"background",
|
"background",
|
||||||
layoutToUse?.defaultBackgroundId ?? "osm",
|
layoutToUse?.defaultBackgroundId ?? "osm",
|
||||||
|
|
|
@ -4,14 +4,13 @@
|
||||||
import { UIEventSource } from "../UIEventSource"
|
import { UIEventSource } from "../UIEventSource"
|
||||||
import Hash from "./Hash"
|
import Hash from "./Hash"
|
||||||
import { Utils } from "../../Utils"
|
import { Utils } from "../../Utils"
|
||||||
import doc = Mocha.reporters.doc
|
|
||||||
|
|
||||||
export class QueryParameters {
|
export class QueryParameters {
|
||||||
static defaults: Record<string, string> = {}
|
static defaults: Record<string, string> = {}
|
||||||
static documentation: Map<string, string> = new Map<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 _wasInitialized: Set<string> = new Set()
|
||||||
protected static readonly knownSources: Record<string, UIEventSource<string>> = {}
|
protected static readonly knownSources: Record<string, UIEventSource<string>> = {}
|
||||||
|
private static order: string[] = ["layout", "test", "z", "lat", "lon"]
|
||||||
private static initialized = false
|
private static initialized = false
|
||||||
|
|
||||||
public static GetQueryParameter(
|
public static GetQueryParameter(
|
||||||
|
@ -74,6 +73,7 @@ export class QueryParameters {
|
||||||
this.init()
|
this.init()
|
||||||
return QueryParameters._wasInitialized.has(key)
|
return QueryParameters._wasInitialized.has(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static initializedParameters(): ReadonlyArray<string> {
|
public static initializedParameters(): ReadonlyArray<string> {
|
||||||
return Array.from(QueryParameters._wasInitialized.keys())
|
return Array.from(QueryParameters._wasInitialized.keys())
|
||||||
}
|
}
|
||||||
|
@ -108,14 +108,12 @@ export class QueryParameters {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static GetParts(exclude?: Set<string>) {
|
||||||
* Set the query parameters of the page location
|
const parts: string[] = []
|
||||||
* @constructor
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private static Serialize() {
|
|
||||||
const parts = []
|
|
||||||
for (const key of QueryParameters.order) {
|
for (const key of QueryParameters.order) {
|
||||||
|
if (exclude?.has(key)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if (QueryParameters.knownSources[key]?.data === undefined) {
|
if (QueryParameters.knownSources[key]?.data === undefined) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -134,6 +132,16 @@ export class QueryParameters {
|
||||||
encodeURIComponent(QueryParameters.knownSources[key].data)
|
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) {
|
if (!Utils.runningFromConsole) {
|
||||||
// Don't pollute the history every time a parameter changes
|
// Don't pollute the history every time a parameter changes
|
||||||
try {
|
try {
|
||||||
|
@ -151,4 +159,8 @@ export class QueryParameters {
|
||||||
QueryParameters._wasInitialized.clear()
|
QueryParameters._wasInitialized.clear()
|
||||||
QueryParameters.order = []
|
QueryParameters.order = []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static GetDefaultFor(key: string): string {
|
||||||
|
return QueryParameters.defaults[key]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,25 @@
|
||||||
import ThemeViewState from "../../Models/ThemeViewState"
|
import ThemeViewState from "../../Models/ThemeViewState"
|
||||||
import Hash from "./Hash"
|
import Hash from "./Hash"
|
||||||
|
import { MenuState } from "../../Models/MenuState"
|
||||||
|
|
||||||
export default class ThemeViewStateHashActor {
|
export default class ThemeViewStateHashActor {
|
||||||
private readonly _state: ThemeViewState
|
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.
|
* 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)
|
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) {
|
if (themeid) {
|
||||||
themeid += "-"
|
themeid += "-"
|
||||||
}
|
}
|
||||||
this.themeIsOpened = LocalStorageSource.GetParsed(themeid + "thememenuisopened", true)
|
this.themeIsOpened = LocalStorageSource.GetParsed(
|
||||||
|
themeid + "thememenuisopened",
|
||||||
|
shouldOpenWelcomeMessage
|
||||||
|
)
|
||||||
this.themeViewTabIndex = LocalStorageSource.GetParsed(themeid + "themeviewtabindex", 0)
|
this.themeViewTabIndex = LocalStorageSource.GetParsed(themeid + "themeviewtabindex", 0)
|
||||||
this.themeViewTab = this.themeViewTabIndex.sync(
|
this.themeViewTab = this.themeViewTabIndex.sync(
|
||||||
(i) => MenuState._themeviewTabs[i],
|
(i) => MenuState._themeviewTabs[i],
|
||||||
|
|
|
@ -114,15 +114,18 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
||||||
|
|
||||||
constructor(layout: LayoutConfig) {
|
constructor(layout: LayoutConfig) {
|
||||||
this.layout = layout
|
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)
|
this.map = new UIEventSource<MlMap>(undefined)
|
||||||
const initial = new InitialMapPositioning(layout)
|
const initial = new InitialMapPositioning(layout)
|
||||||
this.mapProperties = new MapLibreAdaptor(this.map, initial)
|
this.mapProperties = new MapLibreAdaptor(this.map, initial)
|
||||||
const geolocationState = new GeoLocationState()
|
const geolocationState = new GeoLocationState()
|
||||||
|
|
||||||
this.featureSwitches = new FeatureSwitchState(layout)
|
|
||||||
this.featureSwitchIsTesting = this.featureSwitches.featureSwitchIsTesting
|
this.featureSwitchIsTesting = this.featureSwitches.featureSwitchIsTesting
|
||||||
this.featureSwitchUserbadge = this.featureSwitches.featureSwitchUserbadge
|
this.featureSwitchUserbadge = this.featureSwitches.featureSwitchEnableLogin
|
||||||
|
|
||||||
this.osmConnection = new OsmConnection({
|
this.osmConnection = new OsmConnection({
|
||||||
dryRun: this.featureSwitches.featureSwitchIsTesting,
|
dryRun: this.featureSwitches.featureSwitchIsTesting,
|
||||||
|
@ -469,7 +472,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
||||||
|
|
||||||
new ShowDataLayer(this.map, {
|
new ShowDataLayer(this.map, {
|
||||||
features: new FilteringFeatureSource(last_click_layer, last_click),
|
features: new FilteringFeatureSource(last_click_layer, last_click),
|
||||||
doShowLayer: new ImmutableStore(true),
|
doShowLayer: this.featureSwitches.featureSwitchEnableLogin,
|
||||||
layer: last_click_layer.layerDef,
|
layer: last_click_layer.layerDef,
|
||||||
selectedElement: this.selectedElement,
|
selectedElement: this.selectedElement,
|
||||||
selectedLayer: this.selectedLayer,
|
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={layout.description} />
|
||||||
<Tr t={Translations.t.general.welcomeExplanation.general} />
|
<Tr t={Translations.t.general.welcomeExplanation.general} />
|
||||||
{#if layout.layers.some((l) => l.presets?.length > 0)}
|
{#if layout.layers.some((l) => l.presets?.length > 0)}
|
||||||
<If condition={state.featureSwitches.featureSwitchAddNew}>
|
|
||||||
<Tr t={Translations.t.general.welcomeExplanation.addNew} />
|
<Tr t={Translations.t.general.welcomeExplanation.addNew} />
|
||||||
</If>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<Tr t={layout.descriptionTail} />
|
<Tr t={layout.descriptionTail} />
|
||||||
|
|
|
@ -6,6 +6,7 @@ import Translations from "./i18n/Translations"
|
||||||
import { QueryParameters } from "../Logic/Web/QueryParameters"
|
import { QueryParameters } from "../Logic/Web/QueryParameters"
|
||||||
import FeatureSwitchState from "../Logic/State/FeatureSwitchState"
|
import FeatureSwitchState from "../Logic/State/FeatureSwitchState"
|
||||||
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"
|
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"
|
||||||
|
import ThemeViewStateHashActor from "../Logic/Web/ThemeViewStateHashActor"
|
||||||
|
|
||||||
export default class QueryParameterDocumentation {
|
export default class QueryParameterDocumentation {
|
||||||
private static QueryParamDocsIntro = [
|
private static QueryParamDocsIntro = [
|
||||||
|
@ -60,6 +61,7 @@ export default class QueryParameterDocumentation {
|
||||||
public static GenerateQueryParameterDocs(): BaseUIElement {
|
public static GenerateQueryParameterDocs(): BaseUIElement {
|
||||||
const docs: (string | BaseUIElement)[] = [
|
const docs: (string | BaseUIElement)[] = [
|
||||||
...QueryParameterDocumentation.QueryParamDocsIntro,
|
...QueryParameterDocumentation.QueryParamDocsIntro,
|
||||||
|
...ThemeViewStateHashActor.documentation,
|
||||||
]
|
]
|
||||||
this.UrlParamDocs().forEach((value, key) => {
|
this.UrlParamDocs().forEach((value, key) => {
|
||||||
const c = new Combine([
|
const c = new Combine([
|
||||||
|
|
|
@ -1,57 +1,57 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Store, UIEventSource } from "../Logic/UIEventSource"
|
import { Store, UIEventSource } from "../Logic/UIEventSource";
|
||||||
import { Map as MlMap } from "maplibre-gl"
|
import { Map as MlMap } from "maplibre-gl";
|
||||||
import MaplibreMap from "./Map/MaplibreMap.svelte"
|
import MaplibreMap from "./Map/MaplibreMap.svelte";
|
||||||
import FeatureSwitchState from "../Logic/State/FeatureSwitchState"
|
import FeatureSwitchState from "../Logic/State/FeatureSwitchState";
|
||||||
import MapControlButton from "./Base/MapControlButton.svelte"
|
import MapControlButton from "./Base/MapControlButton.svelte";
|
||||||
import ToSvelte from "./Base/ToSvelte.svelte"
|
import ToSvelte from "./Base/ToSvelte.svelte";
|
||||||
import If from "./Base/If.svelte"
|
import If from "./Base/If.svelte";
|
||||||
import { GeolocationControl } from "./BigComponents/GeolocationControl"
|
import { GeolocationControl } from "./BigComponents/GeolocationControl";
|
||||||
import type { Feature } from "geojson"
|
import type { Feature } from "geojson";
|
||||||
import SelectedElementView from "./BigComponents/SelectedElementView.svelte"
|
import SelectedElementView from "./BigComponents/SelectedElementView.svelte";
|
||||||
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
|
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
|
||||||
import Filterview from "./BigComponents/Filterview.svelte"
|
import Filterview from "./BigComponents/Filterview.svelte";
|
||||||
import ThemeViewState from "../Models/ThemeViewState"
|
import ThemeViewState from "../Models/ThemeViewState";
|
||||||
import type { MapProperties } from "../Models/MapProperties"
|
import type { MapProperties } from "../Models/MapProperties";
|
||||||
import Geosearch from "./BigComponents/Geosearch.svelte"
|
import Geosearch from "./BigComponents/Geosearch.svelte";
|
||||||
import Translations from "./i18n/Translations"
|
import Translations from "./i18n/Translations";
|
||||||
import { CogIcon, EyeIcon, MenuIcon, XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
|
import { CogIcon, EyeIcon, MenuIcon, XCircleIcon } from "@rgossiaux/svelte-heroicons/solid";
|
||||||
|
|
||||||
import Tr from "./Base/Tr.svelte"
|
import Tr from "./Base/Tr.svelte";
|
||||||
import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte"
|
import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte";
|
||||||
import FloatOver from "./Base/FloatOver.svelte"
|
import FloatOver from "./Base/FloatOver.svelte";
|
||||||
import PrivacyPolicy from "./BigComponents/PrivacyPolicy"
|
import PrivacyPolicy from "./BigComponents/PrivacyPolicy";
|
||||||
import Constants from "../Models/Constants"
|
import Constants from "../Models/Constants";
|
||||||
import TabbedGroup from "./Base/TabbedGroup.svelte"
|
import TabbedGroup from "./Base/TabbedGroup.svelte";
|
||||||
import UserRelatedState from "../Logic/State/UserRelatedState"
|
import UserRelatedState from "../Logic/State/UserRelatedState";
|
||||||
import LoginToggle from "./Base/LoginToggle.svelte"
|
import LoginToggle from "./Base/LoginToggle.svelte";
|
||||||
import LoginButton from "./Base/LoginButton.svelte"
|
import LoginButton from "./Base/LoginButton.svelte";
|
||||||
import CopyrightPanel from "./BigComponents/CopyrightPanel"
|
import CopyrightPanel from "./BigComponents/CopyrightPanel";
|
||||||
import DownloadPanel from "./DownloadFlow/DownloadPanel.svelte"
|
import DownloadPanel from "./DownloadFlow/DownloadPanel.svelte";
|
||||||
import ModalRight from "./Base/ModalRight.svelte"
|
import ModalRight from "./Base/ModalRight.svelte";
|
||||||
import { Utils } from "../Utils"
|
import { Utils } from "../Utils";
|
||||||
import Hotkeys from "./Base/Hotkeys"
|
import Hotkeys from "./Base/Hotkeys";
|
||||||
import { VariableUiElement } from "./Base/VariableUIElement"
|
import { VariableUiElement } from "./Base/VariableUIElement";
|
||||||
import SvelteUIElement from "./Base/SvelteUIElement"
|
import SvelteUIElement from "./Base/SvelteUIElement";
|
||||||
import OverlayToggle from "./BigComponents/OverlayToggle.svelte"
|
import OverlayToggle from "./BigComponents/OverlayToggle.svelte";
|
||||||
import LevelSelector from "./BigComponents/LevelSelector.svelte"
|
import LevelSelector from "./BigComponents/LevelSelector.svelte";
|
||||||
import ExtraLinkButton from "./BigComponents/ExtraLinkButton"
|
import ExtraLinkButton from "./BigComponents/ExtraLinkButton";
|
||||||
import SelectedElementTitle from "./BigComponents/SelectedElementTitle.svelte"
|
import SelectedElementTitle from "./BigComponents/SelectedElementTitle.svelte";
|
||||||
import Svg from "../Svg"
|
import Svg from "../Svg";
|
||||||
import { ShareScreen } from "./BigComponents/ShareScreen"
|
import ThemeIntroPanel from "./BigComponents/ThemeIntroPanel.svelte";
|
||||||
import ThemeIntroPanel from "./BigComponents/ThemeIntroPanel.svelte"
|
import type { RasterLayerPolygon } from "../Models/RasterLayers";
|
||||||
import type { RasterLayerPolygon } from "../Models/RasterLayers"
|
import { AvailableRasterLayers } from "../Models/RasterLayers";
|
||||||
import { AvailableRasterLayers } from "../Models/RasterLayers"
|
import RasterLayerOverview from "./Map/RasterLayerOverview.svelte";
|
||||||
import RasterLayerOverview from "./Map/RasterLayerOverview.svelte"
|
import IfHidden from "./Base/IfHidden.svelte";
|
||||||
import IfHidden from "./Base/IfHidden.svelte"
|
import { onDestroy } from "svelte";
|
||||||
import { onDestroy } from "svelte"
|
import { OpenJosm } from "./BigComponents/OpenJosm";
|
||||||
import { OpenJosm } from "./BigComponents/OpenJosm"
|
import MapillaryLink from "./BigComponents/MapillaryLink.svelte";
|
||||||
import MapillaryLink from "./BigComponents/MapillaryLink.svelte"
|
import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte";
|
||||||
import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte"
|
import OpenBackgroundSelectorButton from "./BigComponents/OpenBackgroundSelectorButton.svelte";
|
||||||
import OpenBackgroundSelectorButton from "./BigComponents/OpenBackgroundSelectorButton.svelte"
|
import StateIndicator from "./BigComponents/StateIndicator.svelte";
|
||||||
import StateIndicator from "./BigComponents/StateIndicator.svelte"
|
import LanguagePicker from "./LanguagePicker";
|
||||||
import LanguagePicker from "./LanguagePicker"
|
import Locale from "./i18n/Locale";
|
||||||
import Locale from "./i18n/Locale"
|
import ShareScreen from "./BigComponents/ShareScreen.svelte";
|
||||||
|
|
||||||
export let state: ThemeViewState
|
export let state: ThemeViewState
|
||||||
let layout = state.layout
|
let layout = state.layout
|
||||||
|
@ -314,11 +314,12 @@
|
||||||
|
|
||||||
<ToSvelte construct={() => new CopyrightPanel(state)} slot="content3" />
|
<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} />
|
<Tr t={Translations.t.general.sharescreen.title} />
|
||||||
</div>
|
</div>
|
||||||
<div class="m-2" slot="content4">
|
<div class="m-2" slot="content4">
|
||||||
<ToSvelte construct={() => new ShareScreen(state)} />
|
<ShareScreen {state}/>
|
||||||
</div>
|
</div>
|
||||||
</TabbedGroup>
|
</TabbedGroup>
|
||||||
</FloatOver>
|
</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
|
* Utils.Round7(12.123456789) // => 12.1234568
|
||||||
*/
|
*/
|
||||||
public static Round7(i: number): number {
|
public static Round7(i: number): number {
|
||||||
|
if (i == undefined) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
return Math.round(i * 10000000) / 10000000
|
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)
|
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>(
|
public static sortedByLevenshteinDistance<T>(
|
||||||
reference: string,
|
reference: string,
|
||||||
ts: T[],
|
ts: T[],
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue