Merge develop
This commit is contained in:
commit
2001ef09bf
50 changed files with 1443 additions and 1341 deletions
|
@ -1,60 +1,68 @@
|
|||
import LayoutConfig from "../../../Models/ThemeConfig/LayoutConfig"
|
||||
import { WritableFeatureSource } from "../FeatureSource"
|
||||
import { ImmutableStore, Store, UIEventSource } from "../../UIEventSource"
|
||||
import { Feature, Point } from "geojson"
|
||||
import { TagUtils } from "../../Tags/TagUtils"
|
||||
import BaseUIElement from "../../../UI/BaseUIElement"
|
||||
import { Utils } from "../../../Utils"
|
||||
import LayoutConfig from "../../../Models/ThemeConfig/LayoutConfig";
|
||||
import { WritableFeatureSource } from "../FeatureSource";
|
||||
import { ImmutableStore, Store, UIEventSource } from "../../UIEventSource";
|
||||
import { Feature, Point } from "geojson";
|
||||
import { TagUtils } from "../../Tags/TagUtils";
|
||||
import BaseUIElement from "../../../UI/BaseUIElement";
|
||||
import { Utils } from "../../../Utils";
|
||||
import { OsmTags } from "../../../Models/OsmFeature";
|
||||
|
||||
/**
|
||||
* Highly specialized feature source.
|
||||
* Based on a lon/lat UIEVentSource, will generate the corresponding feature with the correct properties
|
||||
*/
|
||||
export class LastClickFeatureSource implements WritableFeatureSource {
|
||||
public readonly features: UIEventSource<Feature[]> = new UIEventSource<Feature[]>([])
|
||||
public readonly features: UIEventSource<Feature[]> = new UIEventSource<Feature[]>([]);
|
||||
public readonly hasNoteLayer: boolean;
|
||||
public readonly renderings: string[];
|
||||
public readonly hasPresets: boolean;
|
||||
private i: number = 0;
|
||||
|
||||
constructor(location: Store<{ lon: number; lat: number }>, layout: LayoutConfig) {
|
||||
const allPresets: BaseUIElement[] = []
|
||||
this.hasNoteLayer = layout.layers.some((l) => l.id === "note");
|
||||
this.hasPresets = layout.layers.some((l) => l.presets?.length > 0);
|
||||
const allPresets: BaseUIElement[] = [];
|
||||
for (const layer of layout.layers)
|
||||
for (let i = 0; i < (layer.presets ?? []).length; i++) {
|
||||
const preset = layer.presets[i]
|
||||
const tags = new ImmutableStore(TagUtils.KVtoProperties(preset.tags))
|
||||
const preset = layer.presets[i];
|
||||
const tags = new ImmutableStore(TagUtils.KVtoProperties(preset.tags));
|
||||
const { html } = layer.mapRendering[0].RenderIcon(tags, false, {
|
||||
noSize: true,
|
||||
includeBadges: false,
|
||||
})
|
||||
allPresets.push(html)
|
||||
includeBadges: false
|
||||
});
|
||||
allPresets.push(html);
|
||||
}
|
||||
|
||||
const renderings = Utils.Dedup(
|
||||
this.renderings = Utils.Dedup(
|
||||
allPresets.map((uiElem) =>
|
||||
Utils.runningFromConsole ? "" : uiElem.ConstructElement().innerHTML
|
||||
)
|
||||
)
|
||||
|
||||
let i = 0
|
||||
);
|
||||
|
||||
location.addCallbackAndRunD(({ lon, lat }) => {
|
||||
const properties = {
|
||||
lastclick: "yes",
|
||||
id: "last_click_" + i,
|
||||
has_note_layer: layout.layers.some((l) => l.id === "note") ? "yes" : "no",
|
||||
has_presets: layout.layers.some((l) => l.presets?.length > 0) ? "yes" : "no",
|
||||
renderings: renderings.join(""),
|
||||
number_of_presets: "" + renderings.length,
|
||||
first_preset: renderings[0],
|
||||
}
|
||||
i++
|
||||
this.features.setData([this.createFeature(lon, lat)]);
|
||||
});
|
||||
}
|
||||
|
||||
const point = <Feature<Point>>{
|
||||
type: "Feature",
|
||||
properties,
|
||||
geometry: {
|
||||
type: "Point",
|
||||
coordinates: [lon, lat],
|
||||
},
|
||||
public createFeature(lon: number, lat: number): Feature<Point, OsmTags> {
|
||||
const properties: OsmTags = {
|
||||
lastclick: "yes",
|
||||
id: "last_click_" + this.i,
|
||||
has_note_layer: this.hasNoteLayer ? "yes" : "no",
|
||||
has_presets: this.hasPresets ? "yes" : "no",
|
||||
renderings: this.renderings.join(""),
|
||||
number_of_presets: "" + this.renderings.length,
|
||||
first_preset: this.renderings[0]
|
||||
};
|
||||
this.i++;
|
||||
|
||||
return <Feature<Point, OsmTags>>{
|
||||
type: "Feature",
|
||||
properties,
|
||||
geometry: {
|
||||
type: "Point",
|
||||
coordinates: [lon, lat]
|
||||
}
|
||||
this.features.setData([point])
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -99,7 +99,7 @@ export default class FeatureSwitchState extends OsmConnectionFeatureSwitches {
|
|||
)
|
||||
this.featureSwitchCommunityIndex = FeatureSwitchUtils.initSwitch(
|
||||
"fs-community-index",
|
||||
true,
|
||||
this.featureSwitchEnableLogin.data,
|
||||
"Disables/enables the button to get in touch with the community"
|
||||
)
|
||||
this.featureSwitchExtraLinkEnabled = FeatureSwitchUtils.initSwitch(
|
||||
|
|
|
@ -1,42 +1,14 @@
|
|||
import { Utils } from "../../Utils"
|
||||
/** This code is autogenerated - do not edit. Edit ./assets/layers/usersettings/usersettings.json instead */
|
||||
export class ThemeMetaTagging {
|
||||
public static readonly themeName = "usersettings"
|
||||
public static readonly themeName = "usersettings"
|
||||
|
||||
public metaTaggging_for_usersettings(feat: { properties: Record<string, string> }) {
|
||||
Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_md", () =>
|
||||
feat.properties._description
|
||||
.match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)
|
||||
?.at(1)
|
||||
)
|
||||
Utils.AddLazyProperty(
|
||||
feat.properties,
|
||||
"_d",
|
||||
() => feat.properties._description?.replace(/</g, "<")?.replace(/>/g, ">") ?? ""
|
||||
)
|
||||
Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_a", () =>
|
||||
((feat) => {
|
||||
const e = document.createElement("div")
|
||||
e.innerHTML = feat.properties._d
|
||||
return Array.from(e.getElementsByTagName("a")).filter(
|
||||
(a) => a.href.match(/mastodon|en.osm.town/) !== null
|
||||
)[0]?.href
|
||||
})(feat)
|
||||
)
|
||||
Utils.AddLazyProperty(feat.properties, "_mastodon_link", () =>
|
||||
((feat) => {
|
||||
const e = document.createElement("div")
|
||||
e.innerHTML = feat.properties._d
|
||||
return Array.from(e.getElementsByTagName("a")).filter(
|
||||
(a) => a.getAttribute("rel")?.indexOf("me") >= 0
|
||||
)[0]?.href
|
||||
})(feat)
|
||||
)
|
||||
Utils.AddLazyProperty(
|
||||
feat.properties,
|
||||
"_mastodon_candidate",
|
||||
() => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a
|
||||
)
|
||||
feat.properties["__current_backgroun"] = "initial_value"
|
||||
}
|
||||
}
|
||||
public metaTaggging_for_usersettings(feat: {properties: Record<string, string>}) {
|
||||
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_md', () => feat.properties._description.match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)?.at(1) )
|
||||
Utils.AddLazyProperty(feat.properties, '_d', () => feat.properties._description?.replace(/</g,'<')?.replace(/>/g,'>') ?? '' )
|
||||
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_a', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.href.match(/mastodon|en.osm.town/) !== null)[0]?.href }) (feat) )
|
||||
Utils.AddLazyProperty(feat.properties, '_mastodon_link', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.getAttribute("rel")?.indexOf('me') >= 0)[0]?.href})(feat) )
|
||||
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate', () => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a )
|
||||
feat.properties['__current_backgroun'] = 'initial_value'
|
||||
}
|
||||
}
|
|
@ -175,7 +175,6 @@ export default class ThemeViewStateHashActor {
|
|||
}
|
||||
|
||||
private back() {
|
||||
console.log("Got a back event")
|
||||
const state = this._state
|
||||
// history.pushState(null, null, window.location.pathname);
|
||||
if (state.selectedElement.data) {
|
||||
|
|
|
@ -58,7 +58,7 @@ export default class Constants {
|
|||
|
||||
importHelperUnlock: 5000,
|
||||
}
|
||||
static readonly minZoomLevelToAddNewPoint = Constants.isRetina() ? 18 : 19
|
||||
static readonly minZoomLevelToAddNewPoint = Constants.isRetina() ? 17 : 18
|
||||
/**
|
||||
* Used by 'PendingChangesUploader', which waits this amount of seconds to upload changes.
|
||||
* (Note that pendingChanges might upload sooner if the popup is closed or similar)
|
||||
|
|
|
@ -53,61 +53,6 @@ export class AvailableRasterLayers {
|
|||
geometry: BBox.global.asGeometry(),
|
||||
}
|
||||
|
||||
public static readonly maptilerCarto: RasterLayerPolygon = {
|
||||
type: "Feature",
|
||||
properties: {
|
||||
name: "MapTiler Carto",
|
||||
url: "https://api.maptiler.com/maps/openstreetmap/style.json?key=GvoVAJgu46I5rZapJuAy",
|
||||
category: "osmbasedmap",
|
||||
id: "maptiler.carto",
|
||||
type: "vector",
|
||||
attribution: {
|
||||
text: "Maptiler",
|
||||
url: "https://www.maptiler.com/copyright/",
|
||||
},
|
||||
},
|
||||
geometry: BBox.global.asGeometry(),
|
||||
}
|
||||
|
||||
public static readonly maptilerBackdrop: RasterLayerPolygon = {
|
||||
type: "Feature",
|
||||
properties: {
|
||||
name: "MapTiler Backdrop",
|
||||
url: "https://api.maptiler.com/maps/backdrop/style.json?key=GvoVAJgu46I5rZapJuAy",
|
||||
category: "osmbasedmap",
|
||||
id: "maptiler.backdrop",
|
||||
type: "vector",
|
||||
attribution: {
|
||||
text: "Maptiler",
|
||||
url: "https://www.maptiler.com/copyright/",
|
||||
},
|
||||
},
|
||||
geometry: BBox.global.asGeometry(),
|
||||
}
|
||||
public static readonly americana: RasterLayerPolygon = {
|
||||
type: "Feature",
|
||||
properties: {
|
||||
name: "Americana",
|
||||
url: "https://zelonewolf.github.io/openstreetmap-americana/style.json",
|
||||
category: "osmbasedmap",
|
||||
id: "americana",
|
||||
type: "vector",
|
||||
attribution: {
|
||||
text: "Americana",
|
||||
url: "https://github.com/ZeLonewolf/openstreetmap-americana/",
|
||||
},
|
||||
},
|
||||
geometry: BBox.global.asGeometry(),
|
||||
}
|
||||
|
||||
public static readonly vectorLayers = [
|
||||
AvailableRasterLayers.maptilerDefaultLayer,
|
||||
AvailableRasterLayers.osmCarto,
|
||||
AvailableRasterLayers.maptilerCarto,
|
||||
AvailableRasterLayers.maptilerBackdrop,
|
||||
AvailableRasterLayers.americana,
|
||||
]
|
||||
|
||||
public static layersAvailableAt(
|
||||
location: Store<{ lon: number; lat: number }>
|
||||
): Store<RasterLayerPolygon[]> {
|
||||
|
@ -119,7 +64,7 @@ export class AvailableRasterLayers {
|
|||
)
|
||||
})
|
||||
)
|
||||
const available = Stores.ListStabilized(
|
||||
return Stores.ListStabilized(
|
||||
availableLayersBboxes.map((eliPolygons) => {
|
||||
const loc = location.data
|
||||
const lonlat: [number, number] = [loc.lon, loc.lat]
|
||||
|
@ -129,12 +74,11 @@ export class AvailableRasterLayers {
|
|||
}
|
||||
return GeoOperations.inside(lonlat, eliPolygon)
|
||||
})
|
||||
matching.push(AvailableRasterLayers.maptilerDefaultLayer)
|
||||
matching.push(...AvailableRasterLayers.globalLayers)
|
||||
matching.unshift(...AvailableRasterLayers.vectorLayers)
|
||||
return matching
|
||||
})
|
||||
)
|
||||
return available
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -94,6 +94,9 @@ export default class LayoutConfig implements LayoutInformation {
|
|||
}
|
||||
const context = this.id
|
||||
this.credits = json.credits
|
||||
if(!json.title){
|
||||
throw `The theme ${json.id} does not have a title defined.`
|
||||
}
|
||||
this.language = json.mustHaveLanguage ?? Object.keys(json.title)
|
||||
this.usedImages = Array.from(
|
||||
new ExtractImages(official, undefined)
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -12,7 +12,28 @@
|
|||
let id = Math.random() * 1000000000 + ""
|
||||
</script>
|
||||
|
||||
<form>
|
||||
<form on:change|preventDefault={() => {
|
||||
drawAttention = false
|
||||
dispatcher("submit", inputElement.files)
|
||||
}}
|
||||
on:dragend={() => {
|
||||
console.log("Drag end")
|
||||
drawAttention = false
|
||||
}}
|
||||
on:dragenter|preventDefault|stopPropagation={(e) => {
|
||||
console.log("Dragging enter")
|
||||
drawAttention = true
|
||||
e.dataTransfer.drop = "copy"
|
||||
}}
|
||||
on:dragstart={() => {
|
||||
console.log("DragStart")
|
||||
drawAttention = false
|
||||
}}
|
||||
on:drop|preventDefault|stopPropagation={(e) => {
|
||||
console.log("Got a 'drop'")
|
||||
drawAttention = false
|
||||
dispatcher("submit", e.dataTransfer.files)
|
||||
}}>
|
||||
<label class={twMerge(cls, drawAttention ? "glowing-shadow" : "")} for={"fileinput" + id}>
|
||||
<slot />
|
||||
</label>
|
||||
|
@ -23,26 +44,7 @@
|
|||
id={"fileinput" + id}
|
||||
{multiple}
|
||||
name="file-input"
|
||||
on:change|preventDefault={() => {
|
||||
drawAttention = false
|
||||
dispatcher("submit", inputElement.files)
|
||||
}}
|
||||
on:dragend={() => {
|
||||
drawAttention = false
|
||||
}}
|
||||
on:dragover|preventDefault|stopPropagation={(e) => {
|
||||
console.log("Dragging over!")
|
||||
drawAttention = true
|
||||
e.dataTransfer.drop = "copy"
|
||||
}}
|
||||
on:dragstart={() => {
|
||||
drawAttention = false
|
||||
}}
|
||||
on:drop|preventDefault|stopPropagation={(e) => {
|
||||
console.log("Got a 'drop'")
|
||||
drawAttention = false
|
||||
dispatcher("submit", e.dataTransfer.files)
|
||||
}}
|
||||
|
||||
type="file"
|
||||
/>
|
||||
</form>
|
||||
|
|
|
@ -11,8 +11,9 @@
|
|||
<div
|
||||
class="absolute top-0 right-0 h-screen w-screen p-4 md:p-6"
|
||||
style="background-color: #00000088"
|
||||
on:click={() => {dispatch("close")}}
|
||||
>
|
||||
<div class="content normal-background">
|
||||
<div class="content normal-background" on:click|stopPropagation={() => {}}>
|
||||
<div class="h-full rounded-xl">
|
||||
<slot />
|
||||
</div>
|
||||
|
|
|
@ -22,7 +22,14 @@ export default class Hotkeys {
|
|||
}[]
|
||||
>([])
|
||||
|
||||
private static textElementSelected(): boolean {
|
||||
private static textElementSelected(event: KeyboardEvent): boolean {
|
||||
if(event.ctrlKey || event.altKey){
|
||||
// This is an event with a modifier-key, lets not ignore it
|
||||
return false
|
||||
}
|
||||
if(event.key === "Escape"){
|
||||
return false // Another not-printable character that should not be ignored
|
||||
}
|
||||
return ["input", "textarea"].includes(document?.activeElement?.tagName?.toLowerCase())
|
||||
}
|
||||
public static RegisterHotkey(
|
||||
|
@ -68,7 +75,7 @@ export default class Hotkeys {
|
|||
})
|
||||
} else if (key["shift"] !== undefined) {
|
||||
document.addEventListener(type, function (event) {
|
||||
if (Hotkeys.textElementSelected()) {
|
||||
if (Hotkeys.textElementSelected(event)) {
|
||||
// A text element is selected, we don't do anything special
|
||||
return
|
||||
}
|
||||
|
@ -86,7 +93,7 @@ export default class Hotkeys {
|
|||
})
|
||||
} else if (key["nomod"] !== undefined) {
|
||||
document.addEventListener(type, function (event) {
|
||||
if (Hotkeys.textElementSelected()) {
|
||||
if (Hotkeys.textElementSelected(event)) {
|
||||
// A text element is selected, we don't do anything special
|
||||
return
|
||||
}
|
||||
|
|
|
@ -1,20 +1,32 @@
|
|||
<script lang="ts">
|
||||
/**
|
||||
* Thin wrapper around 'TabGroup' which binds the state
|
||||
*/
|
||||
/**
|
||||
* Thin wrapper around 'TabGroup' which binds the state
|
||||
*/
|
||||
|
||||
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@rgossiaux/svelte-headlessui"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { twJoin } from "tailwind-merge"
|
||||
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@rgossiaux/svelte-headlessui";
|
||||
import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource";
|
||||
import { twJoin } from "tailwind-merge";
|
||||
|
||||
export let tab: UIEventSource<number>
|
||||
let tabElements: HTMLElement[] = []
|
||||
$: tabElements[$tab]?.click()
|
||||
$: {
|
||||
if (tabElements[tab.data]) {
|
||||
window.setTimeout(() => tabElements[tab.data].click(), 50)
|
||||
/**
|
||||
* If a condition is given for a certain tab, it will only be shown if this condition is true.
|
||||
* E.g.
|
||||
* condition3 = new ImmutableStore(false) will always hide tab3 (the fourth tab)
|
||||
*/
|
||||
let tr = new ImmutableStore(true)
|
||||
export let condition0: Store<boolean> = tr
|
||||
export let condition1: Store<boolean> = tr
|
||||
export let condition2: Store<boolean> = tr
|
||||
export let condition3: Store<boolean> = tr
|
||||
export let condition4: Store<boolean> = tr
|
||||
|
||||
export let tab: UIEventSource<number>;
|
||||
let tabElements: HTMLElement[] = [];
|
||||
$: tabElements[$tab]?.click();
|
||||
$: {
|
||||
if (tabElements[tab.data]) {
|
||||
window.setTimeout(() => tabElements[tab.data].click(), 50);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="tabbedgroup flex h-full w-full">
|
||||
|
@ -29,41 +41,31 @@
|
|||
>
|
||||
<div class="interactive sticky top-0 flex items-center justify-between">
|
||||
<TabList class="flex flex-wrap">
|
||||
{#if $$slots.title1}
|
||||
<Tab class={({ selected }) => twJoin("tab", selected && "primary")}>
|
||||
<div bind:this={tabElements[0]} class="flex">
|
||||
<slot name="title0">Tab 0</slot>
|
||||
</div>
|
||||
</Tab>
|
||||
{/if}
|
||||
{#if $$slots.title1}
|
||||
<Tab class={({ selected }) => twJoin("tab", selected && "primary")}>
|
||||
<div bind:this={tabElements[1]} class="flex">
|
||||
<slot name="title1" />
|
||||
</div>
|
||||
</Tab>
|
||||
{/if}
|
||||
{#if $$slots.title2}
|
||||
<Tab class={({ selected }) => twJoin("tab", selected && "primary")}>
|
||||
<div bind:this={tabElements[2]} class="flex">
|
||||
<slot name="title2" />
|
||||
</div>
|
||||
</Tab>
|
||||
{/if}
|
||||
{#if $$slots.title3}
|
||||
<Tab class={({ selected }) => twJoin("tab", selected && "primary")}>
|
||||
<div bind:this={tabElements[3]} class="flex">
|
||||
<slot name="title3" />
|
||||
</div>
|
||||
</Tab>
|
||||
{/if}
|
||||
{#if $$slots.title4}
|
||||
<Tab class={({ selected }) => twJoin("tab", selected && "primary")}>
|
||||
<div bind:this={tabElements[4]} class="flex">
|
||||
<slot name="title4" />
|
||||
</div>
|
||||
</Tab>
|
||||
{/if}
|
||||
<Tab class={({ selected }) => twJoin("tab", selected && "primary", !$condition0 && "hidden")}>
|
||||
<div bind:this={tabElements[0]} class="flex">
|
||||
<slot name="title0">Tab 0</slot>
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab class={({ selected }) => twJoin("tab", selected && "primary", !$condition1 && "hidden")}>
|
||||
<div bind:this={tabElements[1]} class="flex">
|
||||
<slot name="title1" />
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab class={({ selected }) => twJoin("tab", selected && "primary", !$condition2 && "hidden")}>
|
||||
<div bind:this={tabElements[2]} class="flex">
|
||||
<slot name="title2" />
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab class={({ selected }) => twJoin("tab", selected && "primary", !$condition3 && "hidden")}>
|
||||
<div bind:this={tabElements[3]} class="flex">
|
||||
<slot name="title3" />
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab class={({ selected }) => twJoin("tab", selected && "primary", !$condition4 && "hidden")}>
|
||||
<div bind:this={tabElements[4]} class="flex">
|
||||
<slot name="title4" />
|
||||
</div>
|
||||
</Tab>
|
||||
</TabList>
|
||||
<slot name="post-tablist" />
|
||||
</div>
|
||||
|
@ -75,16 +77,24 @@
|
|||
</slot>
|
||||
</TabPanel>
|
||||
<TabPanel class="tabpanel">
|
||||
<slot name="content1" />
|
||||
<slot name="content1">
|
||||
<div />
|
||||
</slot>
|
||||
</TabPanel>
|
||||
<TabPanel class="tabpanel">
|
||||
<slot name="content2" />
|
||||
<slot name="content2">
|
||||
<div />
|
||||
</slot>
|
||||
</TabPanel>
|
||||
<TabPanel class="tabpanel">
|
||||
<slot name="content3" />
|
||||
<slot name="content3">
|
||||
<div />
|
||||
</slot>
|
||||
</TabPanel>
|
||||
<TabPanel class="tabpanel">
|
||||
<slot name="content4" />
|
||||
<slot name="content4">
|
||||
<div />
|
||||
</slot>
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</div>
|
||||
|
@ -92,44 +102,44 @@
|
|||
</div>
|
||||
|
||||
<style>
|
||||
.tabbedgroup {
|
||||
max-height: 100vh;
|
||||
height: 100%;
|
||||
}
|
||||
.tabbedgroup {
|
||||
max-height: 100vh;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
:global(.tabpanel) {
|
||||
height: 100%;
|
||||
}
|
||||
:global(.tabpanel) {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
:global(.tabpanels) {
|
||||
height: calc(100% - 2rem);
|
||||
}
|
||||
:global(.tabpanels) {
|
||||
height: calc(100% - 2rem);
|
||||
}
|
||||
|
||||
:global(.tab) {
|
||||
margin: 0.25rem;
|
||||
padding: 0.25rem;
|
||||
padding-left: 0.75rem;
|
||||
padding-right: 0.75rem;
|
||||
border-radius: 1rem;
|
||||
}
|
||||
:global(.tab) {
|
||||
margin: 0.25rem;
|
||||
padding: 0.25rem;
|
||||
padding-left: 0.75rem;
|
||||
padding-right: 0.75rem;
|
||||
border-radius: 1rem;
|
||||
}
|
||||
|
||||
:global(.tab .flex) {
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
:global(.tab .flex) {
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
:global(.tab span|div) {
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
display: flex;
|
||||
}
|
||||
:global(.tab span|div) {
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
:global(.tab-selected svg) {
|
||||
fill: var(--catch-detail-color-contrast);
|
||||
}
|
||||
:global(.tab-selected svg) {
|
||||
fill: var(--catch-detail-color-contrast);
|
||||
}
|
||||
|
||||
:global(.tab-unselected) {
|
||||
background-color: var(--background-color) !important;
|
||||
color: var(--foreground-color) !important;
|
||||
}
|
||||
:global(.tab-unselected) {
|
||||
background-color: var(--background-color) !important;
|
||||
color: var(--foreground-color) !important;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,47 +1,48 @@
|
|||
<script lang="ts">
|
||||
import Translations from "../i18n/Translations"
|
||||
import Svg from "../../Svg"
|
||||
import Tr from "../Base/Tr.svelte"
|
||||
import NextButton from "../Base/NextButton.svelte"
|
||||
import Geosearch from "./Geosearch.svelte"
|
||||
import ToSvelte from "../Base/ToSvelte.svelte"
|
||||
import ThemeViewState from "../../Models/ThemeViewState"
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { SearchIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
import { twJoin } from "tailwind-merge"
|
||||
import { Utils } from "../../Utils"
|
||||
import type { GeolocationPermissionState } from "../../Logic/State/GeoLocationState"
|
||||
import Translations from "../i18n/Translations";
|
||||
import Svg from "../../Svg";
|
||||
import Tr from "../Base/Tr.svelte";
|
||||
import NextButton from "../Base/NextButton.svelte";
|
||||
import Geosearch from "./Geosearch.svelte";
|
||||
import ToSvelte from "../Base/ToSvelte.svelte";
|
||||
import ThemeViewState from "../../Models/ThemeViewState";
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource";
|
||||
import { SearchIcon } from "@rgossiaux/svelte-heroicons/solid";
|
||||
import { twJoin } from "tailwind-merge";
|
||||
import { Utils } from "../../Utils";
|
||||
import type { GeolocationPermissionState } from "../../Logic/State/GeoLocationState";
|
||||
import If from "../Base/If.svelte";
|
||||
|
||||
/**
|
||||
* The theme introduction panel
|
||||
*/
|
||||
export let state: ThemeViewState
|
||||
let layout = state.layout
|
||||
let selectedElement = state.selectedElement
|
||||
let selectedLayer = state.selectedLayer
|
||||
/**
|
||||
* The theme introduction panel
|
||||
*/
|
||||
export let state: ThemeViewState;
|
||||
let layout = state.layout;
|
||||
let selectedElement = state.selectedElement;
|
||||
let selectedLayer = state.selectedLayer;
|
||||
|
||||
let triggerSearch: UIEventSource<any> = new UIEventSource<any>(undefined)
|
||||
let searchEnabled = false
|
||||
let triggerSearch: UIEventSource<any> = new UIEventSource<any>(undefined);
|
||||
let searchEnabled = false;
|
||||
|
||||
let geopermission: Store<GeolocationPermissionState> =
|
||||
state.geolocation.geolocationState.permission
|
||||
let currentGPSLocation = state.geolocation.geolocationState.currentGPSLocation
|
||||
let geopermission: Store<GeolocationPermissionState> =
|
||||
state.geolocation.geolocationState.permission;
|
||||
let currentGPSLocation = state.geolocation.geolocationState.currentGPSLocation;
|
||||
|
||||
geopermission.addCallback((perm) => console.log(">>>> Permission", perm))
|
||||
geopermission.addCallback((perm) => console.log(">>>> Permission", perm));
|
||||
|
||||
function jumpToCurrentLocation() {
|
||||
const glstate = state.geolocation.geolocationState
|
||||
if (glstate.currentGPSLocation.data !== undefined) {
|
||||
const c: GeolocationCoordinates = glstate.currentGPSLocation.data
|
||||
state.guistate.themeIsOpened.setData(false)
|
||||
const coor = { lon: c.longitude, lat: c.latitude }
|
||||
state.mapProperties.location.setData(coor)
|
||||
function jumpToCurrentLocation() {
|
||||
const glstate = state.geolocation.geolocationState;
|
||||
if (glstate.currentGPSLocation.data !== undefined) {
|
||||
const c: GeolocationCoordinates = glstate.currentGPSLocation.data;
|
||||
state.guistate.themeIsOpened.setData(false);
|
||||
const coor = { lon: c.longitude, lat: c.latitude };
|
||||
state.mapProperties.location.setData(coor);
|
||||
}
|
||||
if (glstate.permission.data !== "granted") {
|
||||
glstate.requestPermission();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (glstate.permission.data !== "granted") {
|
||||
glstate.requestPermission()
|
||||
return
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex h-full flex-col justify-between">
|
||||
|
@ -62,61 +63,67 @@
|
|||
</div>
|
||||
</NextButton>
|
||||
|
||||
<div class="flex w-full flex-wrap sm:flex-nowrap">
|
||||
{#if $currentGPSLocation !== undefined || $geopermission === "prompt"}
|
||||
<button class="flex w-full items-center gap-x-2" on:click={jumpToCurrentLocation}>
|
||||
<ToSvelte construct={Svg.crosshair_svg().SetClass("w-8 h-8")} />
|
||||
<Tr t={Translations.t.general.openTheMapAtGeolocation} />
|
||||
</button>
|
||||
<!-- No geolocation granted - we don't show the button -->
|
||||
{:else if $geopermission === "requested"}
|
||||
<button class="disabled flex w-full items-center gap-x-2" on:click={jumpToCurrentLocation}>
|
||||
<!-- Even though disabled, when clicking we request the location again in case the contributor dismissed the location popup -->
|
||||
<ToSvelte
|
||||
construct={Svg.crosshair_svg()
|
||||
.SetClass("w-8 h-8")
|
||||
.SetStyle("animation: 3s linear 0s infinite normal none running spin;")}
|
||||
/>
|
||||
<Tr t={Translations.t.general.waitingForGeopermission} />
|
||||
</button>
|
||||
{:else if $geopermission === "denied"}
|
||||
<button class="disabled flex w-full items-center gap-x-2">
|
||||
<ToSvelte construct={Svg.location_refused_svg().SetClass("w-8 h-8")} />
|
||||
<Tr t={Translations.t.general.geopermissionDenied} />
|
||||
</button>
|
||||
{:else}
|
||||
<button class="disabled flex w-full items-center gap-x-2">
|
||||
<ToSvelte
|
||||
construct={Svg.crosshair_svg()
|
||||
.SetClass("w-8 h-8")
|
||||
.SetStyle("animation: 3s linear 0s infinite normal none running spin;")}
|
||||
/>
|
||||
<Tr t={Translations.t.general.waitingForLocation} />
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<div class=".button low-interaction m-1 flex w-full items-center gap-x-2 rounded border p-2">
|
||||
<div class="w-full">
|
||||
<Geosearch
|
||||
bounds={state.mapProperties.bounds}
|
||||
on:searchCompleted={() => state.guistate.themeIsOpened.setData(false)}
|
||||
on:searchIsValid={(isValid) => {
|
||||
<div class="flex w-full flex-wrap sm:flex-nowrap">
|
||||
<If condition={state.featureSwitches.featureSwitchGeolocation}>
|
||||
{#if $currentGPSLocation !== undefined || $geopermission === "prompt"}
|
||||
<button class="flex w-full items-center gap-x-2" on:click={jumpToCurrentLocation}>
|
||||
<ToSvelte construct={Svg.crosshair_svg().SetClass("w-8 h-8")} />
|
||||
<Tr t={Translations.t.general.openTheMapAtGeolocation} />
|
||||
</button>
|
||||
<!-- No geolocation granted - we don't show the button -->
|
||||
{:else if $geopermission === "requested"}
|
||||
<button class="disabled flex w-full items-center gap-x-2" on:click={jumpToCurrentLocation}>
|
||||
<!-- Even though disabled, when clicking we request the location again in case the contributor dismissed the location popup -->
|
||||
<ToSvelte
|
||||
construct={Svg.crosshair_svg()
|
||||
.SetClass("w-8 h-8")
|
||||
.SetStyle("animation: 3s linear 0s infinite normal none running spin;")}
|
||||
/>
|
||||
<Tr t={Translations.t.general.waitingForGeopermission} />
|
||||
</button>
|
||||
{:else if $geopermission === "denied"}
|
||||
<button class="disabled flex w-full items-center gap-x-2">
|
||||
<ToSvelte construct={Svg.location_refused_svg().SetClass("w-8 h-8")} />
|
||||
<Tr t={Translations.t.general.geopermissionDenied} />
|
||||
</button>
|
||||
{:else}
|
||||
<button class="disabled flex w-full items-center gap-x-2">
|
||||
<ToSvelte
|
||||
construct={Svg.crosshair_svg()
|
||||
.SetClass("w-8 h-8")
|
||||
.SetStyle("animation: 3s linear 0s infinite normal none running spin;")}
|
||||
/>
|
||||
<Tr t={Translations.t.general.waitingForLocation} />
|
||||
</button>
|
||||
{/if}
|
||||
</If>
|
||||
|
||||
|
||||
<If condition={state.featureSwitches.featureSwitchSearch}>
|
||||
<div class=".button low-interaction m-1 flex w-full items-center gap-x-2 rounded border p-2">
|
||||
<div class="w-full">
|
||||
<Geosearch
|
||||
bounds={state.mapProperties.bounds}
|
||||
on:searchCompleted={() => state.guistate.themeIsOpened.setData(false)}
|
||||
on:searchIsValid={(isValid) => {
|
||||
searchEnabled = isValid
|
||||
}}
|
||||
perLayer={state.perLayer}
|
||||
{selectedElement}
|
||||
{selectedLayer}
|
||||
{triggerSearch}
|
||||
/>
|
||||
perLayer={state.perLayer}
|
||||
{selectedElement}
|
||||
{selectedLayer}
|
||||
{triggerSearch}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
class={twJoin("flex items-center justify-between gap-x-2", !searchEnabled && "disabled")}
|
||||
on:click={() => triggerSearch.ping()}
|
||||
>
|
||||
<Tr t={Translations.t.general.search.searchShort} />
|
||||
<SearchIcon class="h-6 w-6" />
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
class={twJoin("flex items-center justify-between gap-x-2", !searchEnabled && "disabled")}
|
||||
on:click={() => triggerSearch.ping()}
|
||||
>
|
||||
<Tr t={Translations.t.general.search.searchShort} />
|
||||
<SearchIcon class="h-6 w-6" />
|
||||
</button>
|
||||
</div>
|
||||
</If>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,98 +0,0 @@
|
|||
import { InputElement } from "./InputElement"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { Utils } from "../../Utils"
|
||||
import BaseUIElement from "../BaseUIElement"
|
||||
import InputElementMap from "./InputElementMap"
|
||||
import Translations from "../i18n/Translations"
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export class CheckBox extends InputElementMap<number[], boolean> {
|
||||
constructor(el: BaseUIElement | string, defaultValue?: boolean) {
|
||||
super(
|
||||
new CheckBoxes([Translations.W(el)]),
|
||||
(x0, x1) => x0 === x1,
|
||||
(t) => t.length > 0,
|
||||
(x) => (x ? [0] : [])
|
||||
)
|
||||
if (defaultValue !== undefined) {
|
||||
this.GetValue().setData(defaultValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of individual checkboxes
|
||||
* The value will contain the indexes of the selected checkboxes
|
||||
*/
|
||||
export default class CheckBoxes extends InputElement<number[]> {
|
||||
private static _nextId = 0
|
||||
private readonly value: UIEventSource<number[]>
|
||||
private readonly _elements: BaseUIElement[]
|
||||
|
||||
constructor(elements: BaseUIElement[], value = new UIEventSource<number[]>([])) {
|
||||
super()
|
||||
this.value = value
|
||||
this._elements = Utils.NoNull(elements)
|
||||
this.SetClass("flex flex-col")
|
||||
}
|
||||
|
||||
IsValid(ts: number[]): boolean {
|
||||
return ts !== undefined
|
||||
}
|
||||
|
||||
GetValue(): UIEventSource<number[]> {
|
||||
return this.value
|
||||
}
|
||||
|
||||
protected InnerConstructElement(): HTMLElement {
|
||||
const formTag = document.createElement("form")
|
||||
|
||||
const value = this.value
|
||||
const elements = this._elements
|
||||
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
let inputI = elements[i]
|
||||
const input = document.createElement("input")
|
||||
const id = CheckBoxes._nextId
|
||||
CheckBoxes._nextId++
|
||||
input.id = "checkbox" + id
|
||||
|
||||
input.type = "checkbox"
|
||||
input.classList.add("p-1", "cursor-pointer", "m-3", "pl-3", "mr-0")
|
||||
|
||||
const label = document.createElement("label")
|
||||
label.htmlFor = input.id
|
||||
label.appendChild(input)
|
||||
label.appendChild(inputI.ConstructElement())
|
||||
label.classList.add("block", "w-full", "p-2", "cursor-pointer")
|
||||
|
||||
formTag.appendChild(label)
|
||||
|
||||
value.addCallbackAndRunD((selectedValues) => {
|
||||
input.checked = selectedValues.indexOf(i) >= 0
|
||||
|
||||
if (input.checked) {
|
||||
label.classList.add("checked")
|
||||
} else {
|
||||
label.classList.remove("checked")
|
||||
}
|
||||
})
|
||||
|
||||
input.onchange = () => {
|
||||
// Index = index in the list of already checked items
|
||||
const index = value.data.indexOf(i)
|
||||
if (input.checked && index < 0) {
|
||||
value.data.push(i)
|
||||
value.ping()
|
||||
} else if (index >= 0) {
|
||||
value.data.splice(index, 1)
|
||||
value.ping()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return formTag
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
import { InputElement } from "./InputElement"
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export default class InputElementMap<T, X> extends InputElement<X> {
|
||||
private readonly _inputElement: InputElement<T>
|
||||
private isSame: (x0: X, x1: X) => boolean
|
||||
private readonly fromX: (x: X) => T
|
||||
private readonly toX: (t: T) => X
|
||||
private readonly _value: UIEventSource<X>
|
||||
|
||||
constructor(
|
||||
inputElement: InputElement<T>,
|
||||
isSame: (x0: X, x1: X) => boolean,
|
||||
toX: (t: T) => X,
|
||||
fromX: (x: X) => T,
|
||||
extraSources: Store<any>[] = []
|
||||
) {
|
||||
super()
|
||||
this.isSame = isSame
|
||||
this.fromX = fromX
|
||||
this.toX = toX
|
||||
this._inputElement = inputElement
|
||||
const self = this
|
||||
this._value = inputElement.GetValue().sync(
|
||||
(t) => {
|
||||
const newX = toX(t)
|
||||
const currentX = self.GetValue()?.data
|
||||
if (isSame(currentX, newX)) {
|
||||
return currentX
|
||||
}
|
||||
return newX
|
||||
},
|
||||
extraSources,
|
||||
(x) => {
|
||||
return fromX(x)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
GetValue(): UIEventSource<X> {
|
||||
return this._value
|
||||
}
|
||||
|
||||
IsValid(x: X): boolean {
|
||||
if (x === undefined) {
|
||||
return false
|
||||
}
|
||||
const t = this.fromX(x)
|
||||
if (t === undefined) {
|
||||
return false
|
||||
}
|
||||
return this._inputElement.IsValid(t)
|
||||
}
|
||||
|
||||
protected InnerConstructElement(): HTMLElement {
|
||||
return this._inputElement.ConstructElement()
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ import { QueryParameters } from "../Logic/Web/QueryParameters"
|
|||
|
||||
export default class LanguagePicker extends Toggle {
|
||||
constructor(languages: string[], assignTo: UIEventSource<string>) {
|
||||
console.log("Constructing a language pîcker for languages", languages)
|
||||
console.log("Constructing a language picker for languages", languages)
|
||||
if (
|
||||
languages === undefined ||
|
||||
languages.length <= 1 ||
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import type { Map as MLMap } from "maplibre-gl"
|
||||
import { Map as MlMap, SourceSpecification } from "maplibre-gl"
|
||||
import { AvailableRasterLayers, RasterLayerPolygon } from "../../Models/RasterLayers"
|
||||
import { Utils } from "../../Utils"
|
||||
import { BBox } from "../../Logic/BBox"
|
||||
import { ExportableMap, MapProperties } from "../../Models/MapProperties"
|
||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||
import MaplibreMap from "./MaplibreMap.svelte"
|
||||
import { RasterLayerProperties } from "../../Models/RasterLayerProperties"
|
||||
import * as htmltoimage from "html-to-image"
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource";
|
||||
import type { Map as MLMap } from "maplibre-gl";
|
||||
import { Map as MlMap, SourceSpecification } from "maplibre-gl";
|
||||
import { AvailableRasterLayers, RasterLayerPolygon } from "../../Models/RasterLayers";
|
||||
import { Utils } from "../../Utils";
|
||||
import { BBox } from "../../Logic/BBox";
|
||||
import { ExportableMap, MapProperties } from "../../Models/MapProperties";
|
||||
import SvelteUIElement from "../Base/SvelteUIElement";
|
||||
import MaplibreMap from "./MaplibreMap.svelte";
|
||||
import { RasterLayerProperties } from "../../Models/RasterLayerProperties";
|
||||
import * as htmltoimage from "html-to-image";
|
||||
|
||||
/**
|
||||
* The 'MapLibreAdaptor' bridges 'MapLibre' with the various properties of the `MapProperties`
|
||||
|
|
|
@ -16,6 +16,7 @@ import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"
|
|||
import PerLayerFeatureSourceSplitter from "../../Logic/FeatureSource/PerLayerFeatureSourceSplitter"
|
||||
import FilteredLayer from "../../Models/FilteredLayer"
|
||||
import SimpleFeatureSource from "../../Logic/FeatureSource/Sources/SimpleFeatureSource"
|
||||
import { CLIENT_RENEG_LIMIT } from "tls";
|
||||
|
||||
class PointRenderingLayer {
|
||||
private readonly _config: PointRenderingConfig
|
||||
|
@ -406,13 +407,10 @@ class LineRenderingLayer {
|
|||
} else {
|
||||
const tags = this._fetchStore(id)
|
||||
this._listenerInstalledOn.add(id)
|
||||
map.setFeatureState(
|
||||
{ source: this._layername, id },
|
||||
this.calculatePropsFor(feature.properties)
|
||||
)
|
||||
tags.addCallbackD((properties) => {
|
||||
if (!map.getLayer(this._layername)) {
|
||||
return
|
||||
tags.addCallbackAndRunD((properties) => {
|
||||
// Make sure to use 'getSource' here, the layer names are different!
|
||||
if(map.getSource(this._layername) === undefined){
|
||||
return true
|
||||
}
|
||||
map.setFeatureState(
|
||||
{ source: this._layername, id },
|
||||
|
|
|
@ -92,7 +92,7 @@
|
|||
<LoginToggle ignoreLoading={true} {state}>
|
||||
{#if currentState === "start"}
|
||||
<button
|
||||
class="flex"
|
||||
class="flex items-center"
|
||||
on:click={() => {
|
||||
currentState = "confirm"
|
||||
}}
|
||||
|
@ -112,7 +112,7 @@
|
|||
<button
|
||||
slot="save-button"
|
||||
on:click={onDelete}
|
||||
class={twJoin(selectedTags === undefined && "disabled", "primary flex bg-red-600")}
|
||||
class={twJoin(selectedTags === undefined && "disabled", "primary flex bg-red-600 items-center")}
|
||||
>
|
||||
<TrashIcon
|
||||
class={twJoin(
|
||||
|
@ -122,7 +122,7 @@
|
|||
/>
|
||||
<Tr t={t.delete} />
|
||||
</button>
|
||||
<button slot="cancel" on:click={() => (currentState = "start")}>
|
||||
<button slot="cancel" class="items-center" on:click={() => (currentState = "start")}>
|
||||
<Tr t={t.cancel} />
|
||||
</button>
|
||||
<XCircleIcon
|
||||
|
|
|
@ -1,114 +1,114 @@
|
|||
<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 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"
|
||||
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
|
||||
export let state: ThemeViewState;
|
||||
let layout = state.layout;
|
||||
|
||||
let maplibremap: UIEventSource<MlMap> = state.map
|
||||
let selectedElement: UIEventSource<Feature> = state.selectedElement
|
||||
let selectedLayer: UIEventSource<LayerConfig> = state.selectedLayer
|
||||
let maplibremap: UIEventSource<MlMap> = state.map;
|
||||
let selectedElement: UIEventSource<Feature> = state.selectedElement;
|
||||
let selectedLayer: UIEventSource<LayerConfig> = state.selectedLayer;
|
||||
|
||||
const selectedElementView = selectedElement.map(
|
||||
(selectedElement) => {
|
||||
// Svelte doesn't properly reload some of the legacy UI-elements
|
||||
// As such, we _reconstruct_ the selectedElementView every time a new feature is selected
|
||||
// This is a bit wasteful, but until everything is a svelte-component, this should do the trick
|
||||
const layer = selectedLayer.data
|
||||
if (selectedElement === undefined || layer === undefined) {
|
||||
return undefined
|
||||
}
|
||||
const selectedElementView = selectedElement.map(
|
||||
(selectedElement) => {
|
||||
// Svelte doesn't properly reload some of the legacy UI-elements
|
||||
// As such, we _reconstruct_ the selectedElementView every time a new feature is selected
|
||||
// This is a bit wasteful, but until everything is a svelte-component, this should do the trick
|
||||
const layer = selectedLayer.data;
|
||||
if (selectedElement === undefined || layer === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!(layer.tagRenderings?.length > 0) || layer.title === undefined) {
|
||||
return undefined
|
||||
}
|
||||
if (!(layer.tagRenderings?.length > 0) || layer.title === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const tags = state.featureProperties.getStore(selectedElement.properties.id)
|
||||
return new SvelteUIElement(SelectedElementView, { state, layer, selectedElement, tags })
|
||||
},
|
||||
[selectedLayer]
|
||||
)
|
||||
const tags = state.featureProperties.getStore(selectedElement.properties.id);
|
||||
return new SvelteUIElement(SelectedElementView, { state, layer, selectedElement, tags });
|
||||
},
|
||||
[selectedLayer]
|
||||
);
|
||||
|
||||
const selectedElementTitle = selectedElement.map(
|
||||
(selectedElement) => {
|
||||
// Svelte doesn't properly reload some of the legacy UI-elements
|
||||
// As such, we _reconstruct_ the selectedElementView every time a new feature is selected
|
||||
// This is a bit wasteful, but until everything is a svelte-component, this should do the trick
|
||||
const layer = selectedLayer.data
|
||||
if (selectedElement === undefined || layer === undefined) {
|
||||
return undefined
|
||||
}
|
||||
const selectedElementTitle = selectedElement.map(
|
||||
(selectedElement) => {
|
||||
// Svelte doesn't properly reload some of the legacy UI-elements
|
||||
// As such, we _reconstruct_ the selectedElementView every time a new feature is selected
|
||||
// This is a bit wasteful, but until everything is a svelte-component, this should do the trick
|
||||
const layer = selectedLayer.data;
|
||||
if (selectedElement === undefined || layer === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const tags = state.featureProperties.getStore(selectedElement.properties.id)
|
||||
return new SvelteUIElement(SelectedElementTitle, { state, layer, selectedElement, tags })
|
||||
},
|
||||
[selectedLayer]
|
||||
)
|
||||
const tags = state.featureProperties.getStore(selectedElement.properties.id);
|
||||
return new SvelteUIElement(SelectedElementTitle, { state, layer, selectedElement, tags });
|
||||
},
|
||||
[selectedLayer]
|
||||
);
|
||||
|
||||
let mapproperties: MapProperties = state.mapProperties
|
||||
let featureSwitches: FeatureSwitchState = state.featureSwitches
|
||||
let availableLayers = state.availableLayers
|
||||
let userdetails = state.osmConnection.userDetails
|
||||
let currentViewLayer = layout.layers.find((l) => l.id === "current_view")
|
||||
let rasterLayer: Store<RasterLayerPolygon> = state.mapProperties.rasterLayer
|
||||
let rasterLayerName =
|
||||
rasterLayer.data?.properties?.name ?? AvailableRasterLayers.maptilerDefaultLayer.properties.name
|
||||
onDestroy(
|
||||
rasterLayer.addCallbackAndRunD((l) => {
|
||||
rasterLayerName = l.properties.name
|
||||
})
|
||||
)
|
||||
let mapproperties: MapProperties = state.mapProperties;
|
||||
let featureSwitches: FeatureSwitchState = state.featureSwitches;
|
||||
let availableLayers = state.availableLayers;
|
||||
let userdetails = state.osmConnection.userDetails;
|
||||
let currentViewLayer = layout.layers.find((l) => l.id === "current_view");
|
||||
let rasterLayer: Store<RasterLayerPolygon> = state.mapProperties.rasterLayer;
|
||||
let rasterLayerName =
|
||||
rasterLayer.data?.properties?.name ?? AvailableRasterLayers.maptilerDefaultLayer.properties.name;
|
||||
onDestroy(
|
||||
rasterLayer.addCallbackAndRunD((l) => {
|
||||
rasterLayerName = l.properties.name;
|
||||
})
|
||||
);
|
||||
</script>
|
||||
|
||||
<div class="absolute top-0 left-0 h-screen w-screen overflow-hidden">
|
||||
|
@ -168,18 +168,39 @@
|
|||
<div class="pointer-events-none absolute bottom-0 left-0 mb-4 w-screen">
|
||||
<!-- bottom controls -->
|
||||
<div class="flex w-full items-end justify-between px-4">
|
||||
<div class="flex">
|
||||
<!-- bottom left elements -->
|
||||
<OpenBackgroundSelectorButton hideTooltip={true} {state} />
|
||||
<a
|
||||
class="bg-black-transparent pointer-events-auto h-fit max-h-12 cursor-pointer self-end overflow-hidden rounded-2xl pl-1 pr-2 text-white opacity-50 hover:opacity-100"
|
||||
on:click={() => {
|
||||
<div class="flex flex-col">
|
||||
<If condition={featureSwitches.featureSwitchEnableLogin}>
|
||||
{#if state.lastClickObject.hasPresets || state.lastClickObject.hasNoteLayer}
|
||||
<button class="w-fit pointer-events-auto" on:click={() => {state.openNewDialog()}}>
|
||||
{#if state.lastClickObject.hasPresets}
|
||||
<Tr t={Translations.t.general.add.title} />
|
||||
{:else}
|
||||
<Tr t={Translations.t.notes.addAComment} />
|
||||
{/if}
|
||||
</button>
|
||||
{/if}
|
||||
</If>
|
||||
|
||||
<div class="flex">
|
||||
<!-- bottom left elements -->
|
||||
<If condition={state.featureSwitches.featureSwitchFilter}>
|
||||
<MapControlButton on:click={() => state.guistate.openFilterView()}>
|
||||
<ToSvelte construct={Svg.filter_svg().SetClass("h-6 w-6")} />
|
||||
</MapControlButton>
|
||||
</If>
|
||||
<If condition={state.featureSwitches.featureSwitchBackgroundSelection}>
|
||||
<OpenBackgroundSelectorButton hideTooltip={true} {state} />
|
||||
</If>
|
||||
<a
|
||||
class="bg-black-transparent pointer-events-auto h-fit max-h-12 cursor-pointer self-end overflow-hidden rounded-2xl pl-1 pr-2 text-white opacity-50 hover:opacity-100"
|
||||
on:click={() => {
|
||||
state.guistate.themeViewTab.setData("copyright")
|
||||
state.guistate.themeIsOpened.setData(true)
|
||||
}}
|
||||
>
|
||||
© OpenStreetMap, <span class="w-24">{rasterLayerName}</span>
|
||||
</a>
|
||||
>
|
||||
© OpenStreetMap, <span class="w-24">{rasterLayerName}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-end">
|
||||
|
@ -255,9 +276,9 @@
|
|||
|
||||
<If condition={state.guistate.themeIsOpened}>
|
||||
<!-- Theme menu -->
|
||||
<FloatOver>
|
||||
<FloatOver on:close={() => state.guistate.themeIsOpened.setData(false)}>
|
||||
<span slot="close-button"><!-- Disable the close button --></span>
|
||||
<TabbedGroup tab={state.guistate.themeViewTabIndex}>
|
||||
<TabbedGroup condition1={state.featureSwitches.featureSwitchFilter} tab={state.guistate.themeViewTabIndex}>
|
||||
<div slot="post-tablist">
|
||||
<XCircleIcon
|
||||
class="mr-2 h-8 w-8"
|
||||
|
@ -275,10 +296,8 @@
|
|||
</div>
|
||||
|
||||
<div class="flex" slot="title1">
|
||||
<If condition={state.featureSwitches.featureSwitchFilter}>
|
||||
<ToSvelte construct={Svg.filter_svg().SetClass("w-4 h-4")} />
|
||||
<Tr t={Translations.t.general.menu.filter} />
|
||||
</If>
|
||||
<ToSvelte construct={Svg.filter_svg().SetClass("w-4 h-4")} />
|
||||
<Tr t={Translations.t.general.menu.filter} />
|
||||
</div>
|
||||
|
||||
<div class="m-2 flex flex-col" slot="content1">
|
||||
|
@ -298,6 +317,7 @@
|
|||
/>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="flex" slot="title2">
|
||||
<If condition={state.featureSwitches.featureSwitchEnableExport}>
|
||||
<ToSvelte construct={Svg.download_svg().SetClass("w-4 h-4")} />
|
||||
|
@ -314,7 +334,7 @@
|
|||
|
||||
<ToSvelte construct={() => new CopyrightPanel(state)} slot="content3" />
|
||||
|
||||
<div slot="title4" class="flex">
|
||||
<div class="flex" slot="title4">
|
||||
<ToSvelte construct={Svg.share_svg().SetClass("w-4 h-4")} />
|
||||
<Tr t={Translations.t.general.sharescreen.title} />
|
||||
</div>
|
||||
|
@ -327,7 +347,7 @@
|
|||
|
||||
<IfHidden condition={state.guistate.backgroundLayerSelectionIsOpened}>
|
||||
<!-- background layer selector -->
|
||||
<FloatOver on:close={() => state.guistate.backgroundLayerSelectionIsOpened.setData(false)}>
|
||||
<FloatOver on:close={() => {state.guistate.backgroundLayerSelectionIsOpened.setData(false)}}>
|
||||
<div class="h-full p-2">
|
||||
<RasterLayerOverview
|
||||
{availableLayers}
|
||||
|
@ -342,9 +362,10 @@
|
|||
|
||||
<If condition={state.guistate.menuIsOpened}>
|
||||
<!-- Menu page -->
|
||||
<FloatOver>
|
||||
<FloatOver on:close={() => state.guistate.menuIsOpened.setData(false) }>
|
||||
<span slot="close-button"><!-- Hide the default close button --></span>
|
||||
<TabbedGroup tab={state.guistate.menuViewTabIndex}>
|
||||
<TabbedGroup condition1={featureSwitches.featureSwitchEnableLogin} condition2={state.featureSwitches. featureSwitchCommunityIndex}
|
||||
tab={state.guistate.menuViewTabIndex}>
|
||||
<div slot="post-tablist">
|
||||
<XCircleIcon
|
||||
class="mr-2 h-8 w-8"
|
||||
|
@ -419,7 +440,6 @@
|
|||
<div class="m-2" slot="content2">
|
||||
<CommunityIndexView location={state.mapProperties.location} />
|
||||
</div>
|
||||
|
||||
<div class="flex" slot="title3">
|
||||
<EyeIcon class="w-6" />
|
||||
<Tr t={Translations.t.privacy.title} />
|
||||
|
@ -430,12 +450,15 @@
|
|||
|
||||
<Tr slot="title4" t={Translations.t.advanced.title} />
|
||||
<div class="m-2 flex flex-col" slot="content4">
|
||||
<OpenIdEditor mapProperties={state.mapProperties} />
|
||||
<ToSvelte
|
||||
construct={() =>
|
||||
<If condition={featureSwitches.featureSwitchEnableLogin}>
|
||||
<OpenIdEditor mapProperties={state.mapProperties} />
|
||||
<ToSvelte
|
||||
construct={() =>
|
||||
new OpenJosm(state.osmConnection, state.mapProperties.bounds).SetClass("w-full")}
|
||||
/>
|
||||
<MapillaryLink mapProperties={state.mapProperties} />
|
||||
/>
|
||||
<MapillaryLink mapProperties={state.mapProperties} />
|
||||
</If>
|
||||
|
||||
<ToSvelte construct={Hotkeys.generateDocumentationDynamic} />
|
||||
</div>
|
||||
</TabbedGroup>
|
||||
|
|
11
src/Utils.ts
11
src/Utils.ts
|
@ -1098,7 +1098,16 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
}
|
||||
return { content: data }
|
||||
} catch (e) {
|
||||
console.error("Could not parse ", data, "due to", e, "\n", e.stack)
|
||||
console.error(
|
||||
"Could not parse the response of",
|
||||
url,
|
||||
"which contains",
|
||||
data,
|
||||
"due to",
|
||||
e,
|
||||
"\n",
|
||||
e.stack
|
||||
)
|
||||
return { error: "malformed", url }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,97 +1,169 @@
|
|||
{
|
||||
"layers": [
|
||||
{
|
||||
"id": "Stamen.TonerLite",
|
||||
"name": "Toner Lite (by Stamen)",
|
||||
"url": "https://stamen-tiles-{switch:a,b,c,d}.a.ssl.fastly.net/toner-lite/{z}/{x}/{y}.png",
|
||||
"name": "Americana",
|
||||
"url": "https://zelonewolf.github.io/openstreetmap-americana/style.json",
|
||||
"category": "osmbasedmap",
|
||||
"id": "americana",
|
||||
"type": "vector",
|
||||
"attribution": {
|
||||
"html": "Map tiles by <a href=\"http://stamen.com\">Stamen Design</a>, <a href=\"http://creativecommons.org/licenses/by/3.0\">CC BY 3.0</a> — Map data {attribution.OpenStreetMap}"
|
||||
},
|
||||
"min_zoom": 0,
|
||||
"max_zoom": 20
|
||||
"text": "Americana",
|
||||
"url": "https://github.com/ZeLonewolf/openstreetmap-americana/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "Stamen.TonerBackground",
|
||||
"name": "Toner Background - no labels (by Stamen)",
|
||||
"name": "MapTiler Backdrop",
|
||||
"url": "https://api.maptiler.com/maps/backdrop/style.json?key=GvoVAJgu46I5rZapJuAy",
|
||||
"category": "osmbasedmap",
|
||||
"url": "https://stamen-tiles-{switch:a,b,c,d}.a.ssl.fastly.net/toner-background/{z}/{x}/{y}.png",
|
||||
"id": "maptiler.backdrop",
|
||||
"type": "vector",
|
||||
"attribution": {
|
||||
"html": "Map tiles by <a href=\"http://stamen.com\">Stamen Design</a>, <a href=\"http://creativecommons.org/licenses/by/3.0\">CC BY 3.0</a> — Map data {attribution.OpenStreetMap}"
|
||||
},
|
||||
"min_zoom": 0,
|
||||
"max_zoom": 20
|
||||
"text": "Maptiler",
|
||||
"url": "https://www.maptiler.com/copyright/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "Stamen.Watercolor",
|
||||
"name": "Watercolor (by Stamen)",
|
||||
"name": "MapTiler Carto",
|
||||
"url": "https://api.maptiler.com/maps/openstreetmap/style.json?key=GvoVAJgu46I5rZapJuAy",
|
||||
"category": "osmbasedmap",
|
||||
"url": "https://stamen-tiles-{switch:a,b,c,d}.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.png",
|
||||
"id": "maptiler.carto",
|
||||
"type": "vector",
|
||||
"attribution": {
|
||||
"html": "Map tiles by <a href=\"http://stamen.com\">Stamen Design</a>, <a href=\"http://creativecommons.org/licenses/by/3.0\">CC BY 3.0</a> — Map data {attribution.OpenStreetMap}"
|
||||
},
|
||||
"min_zoom": 0,
|
||||
"max_zoom": 20
|
||||
"text": "Maptiler",
|
||||
"url": "https://www.maptiler.com/copyright/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "CartoDB.Positron",
|
||||
"name": "Positron (by CartoDB)",
|
||||
"url": "https://{switch:a,b,c,d}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}@2x.png",
|
||||
"name": "Alidade Smooth",
|
||||
"url": "https://tiles-eu.stadiamaps.com/styles/alidade_smooth.json?key=14c5a900-7137-42f7-9cb9-fff0f4696f75",
|
||||
"category": "osmbasedmap",
|
||||
"id": "alidade.smooth",
|
||||
"type": "vector",
|
||||
"attribution": {
|
||||
"html": "<a href=\"https://carto.com/attributions\">CARTO</a>"
|
||||
},
|
||||
"max_zoom": 20,
|
||||
"category": "osmbasedmap"
|
||||
"text": "Alidade",
|
||||
"url": "https://stadiamaps.com/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "CartoDB.PositronNoLabels",
|
||||
"name": "Positron - no labels (by CartoDB)",
|
||||
"url": "https://{switch:a,b,c,d}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png",
|
||||
"name": "Alidade Smooth Dark",
|
||||
"url": "https://tiles-eu.stadiamaps.com/styles/alidade_smooth_dark.json?key=14c5a900-7137-42f7-9cb9-fff0f4696f75",
|
||||
"category": "osmbasedmap",
|
||||
"id": "alidade.smooth_dark",
|
||||
"type": "vector",
|
||||
"attribution": {
|
||||
"html": "<a href=\"https://carto.com/attributions\">CARTO</a>"
|
||||
},
|
||||
"max_zoom": 20
|
||||
"text": "Alidade/Stadiamaps",
|
||||
"url": "https://stadiamaps.com/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "CartoDB.Voyager",
|
||||
"name": "Voyager (by CartoDB)",
|
||||
"url": "https://{switch:a,b,c,d}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.png",
|
||||
"name": "Stamen Terrain",
|
||||
"url": "https://tiles-eu.stadiamaps.com/styles/stamen_terrain.json?key=14c5a900-7137-42f7-9cb9-fff0f4696f75",
|
||||
"category": "osmbasedmap",
|
||||
"id": "stamen.terrain",
|
||||
"type": "vector",
|
||||
"attribution": {
|
||||
"html": "<a href=\"https://carto.com/attributions\">CARTO</a>"
|
||||
},
|
||||
"max_zoom": 20
|
||||
"text": "Stamen/Stadiamaps",
|
||||
"url": "https://stadiamaps.com/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "CartoDB.VoyagerNoLabels",
|
||||
"name": "Voyager - no labels (by CartoDB)",
|
||||
"url": "https://{switch:a,b,c,d}.basemaps.cartocdn.com/rastertiles/voyager_nolabels/{z}/{x}/{y}.png",
|
||||
"name": "Stamen Toner",
|
||||
"url": "https://tiles-eu.stadiamaps.com/styles/stamen_toner.json?key=14c5a900-7137-42f7-9cb9-fff0f4696f75",
|
||||
"category": "osmbasedmap",
|
||||
"id": "stamen.toner",
|
||||
"type": "vector",
|
||||
"attribution": {
|
||||
"html": "<a href=\"https://carto.com/attributions\">CARTO</a>"
|
||||
},
|
||||
"max_zoom": 20
|
||||
"text": "Stamen/Stadiamaps",
|
||||
"url": "https://stadiamaps.com/"
|
||||
}
|
||||
}, {
|
||||
"name": "Stamen Watercolor",
|
||||
"url": "https://tiles.stadiamaps.com/tiles/stamen_watercolor/{z}/{x}/{y}.jpg?key=14c5a900-7137-42f7-9cb9-fff0f4696f75",
|
||||
"category": "osmbasedmap",
|
||||
"id": "stamen.watercolor",
|
||||
"attribution": {
|
||||
"text": "Stamen/Stadiamaps",
|
||||
"url": "https://stadiamaps.com/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "CartoDB.DarkMatter",
|
||||
"name": "Dark Matter (by CartoDB)",
|
||||
"url": "https://{switch:a,b,c,d}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png",
|
||||
"url": "https://tiles-eu.stadiamaps.com/styles/osm_bright.json",
|
||||
"name": "StadiaMaps OSM Bright",
|
||||
"category": "osmbasedmap",
|
||||
"id": "stadia.bright",
|
||||
"type": "vector",
|
||||
"attribution": {
|
||||
"html": "<a href=\"https://carto.com/attributions\">CARTO</a>"
|
||||
},
|
||||
"max_zoom": 20
|
||||
"text": "Stadiamaps",
|
||||
"url": "https://stadiamaps.com/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "CartoDB.DarkMatterNoLabels",
|
||||
"name": "Dark Matter - no labels (by CartoDB)",
|
||||
"url": "https://{switch:a,b,c,d}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png",
|
||||
"url": "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json?key=eyJhbGciOiJIUzI1NiJ9.eyJhIjoiYWNfdW4ybmhlbTciLCJqdGkiOiIwZGQxNjJmNyJ9.uATJpa6QcrtXhph3Bzvk2nX3QsxEw-Q8dj5khUG6hGk",
|
||||
"name": "Carto Positron",
|
||||
"category": "osmbasedmap",
|
||||
"id": "carto.positron",
|
||||
"type": "vector",
|
||||
"attribution": {
|
||||
"html": "<a href=\"https://carto.com/attributions\">CARTO</a>"
|
||||
},
|
||||
"max_zoom": 20
|
||||
"text": "<a href=\"https://carto.com/about-carto/\" target=\"_blank\" rel=\"noopener\">CARTO</a>",
|
||||
"url": "https://carto.com/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"url": "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json?key=eyJhbGciOiJIUzI1NiJ9.eyJhIjoiYWNfdW4ybmhlbTciLCJqdGkiOiIwZGQxNjJmNyJ9.uATJpa6QcrtXhph3Bzvk2nX3QsxEw-Q8dj5khUG6hGk",
|
||||
"name": "Carto Dark Matter",
|
||||
"category": "osmbasedmap",
|
||||
"id": "carto.dark_matter",
|
||||
"type": "vector",
|
||||
"attribution": {
|
||||
"text": "<a href=\"https://carto.com/about-carto/\" target=\"_blank\" rel=\"noopener\">CARTO</a>",
|
||||
"url": "https://carto.com/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"url": "https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json?key=eyJhbGciOiJIUzI1NiJ9.eyJhIjoiYWNfdW4ybmhlbTciLCJqdGkiOiIwZGQxNjJmNyJ9.uATJpa6QcrtXhph3Bzvk2nX3QsxEw-Q8dj5khUG6hGk",
|
||||
"name": "Carto Voyager",
|
||||
"category": "osmbasedmap",
|
||||
"id": "carto.voyager",
|
||||
"type": "vector",
|
||||
"attribution": {
|
||||
"text": "<a href=\"https://carto.com/about-carto/\" target=\"_blank\" rel=\"noopener\">CARTO</a>",
|
||||
"url": "https://carto.com/"
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
"url": "https://basemaps.cartocdn.com/gl/positron-nolabels-gl-style/style.json?key=eyJhbGciOiJIUzI1NiJ9.eyJhIjoiYWNfdW4ybmhlbTciLCJqdGkiOiIwZGQxNjJmNyJ9.uATJpa6QcrtXhph3Bzvk2nX3QsxEw-Q8dj5khUG6hGk",
|
||||
"name": "Carto Positron (no labels)",
|
||||
"category": "osmbasedmap",
|
||||
"id": "carto.positron_no_labels",
|
||||
"type": "vector",
|
||||
"attribution": {
|
||||
"text": "<a href=\"https://carto.com/about-carto/\" target=\"_blank\" rel=\"noopener\">CARTO</a>",
|
||||
"url": "https://carto.com/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"url": "https://basemaps.cartocdn.com/gl/dark-matter-nolabels-gl-style/style.json?key=eyJhbGciOiJIUzI1NiJ9.eyJhIjoiYWNfdW4ybmhlbTciLCJqdGkiOiIwZGQxNjJmNyJ9.uATJpa6QcrtXhph3Bzvk2nX3QsxEw-Q8dj5khUG6hGk",
|
||||
"name": "Carto Dark Matter (no labels)",
|
||||
"category": "osmbasedmap",
|
||||
"id": "carto.dark_matter_no_labels",
|
||||
"type": "vector",
|
||||
"attribution": {
|
||||
"text": "<a href=\"https://carto.com/about-carto/\" target=\"_blank\" rel=\"noopener\">CARTO</a>",
|
||||
"url": "https://carto.com/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"url": "https://basemaps.cartocdn.com/gl/voyager-nolabels-gl-style/style.json?key=eyJhbGciOiJIUzI1NiJ9.eyJhIjoiYWNfdW4ybmhlbTciLCJqdGkiOiIwZGQxNjJmNyJ9.uATJpa6QcrtXhph3Bzvk2nX3QsxEw-Q8dj5khUG6hGk",
|
||||
"name": "Carto Voyager (no labels)",
|
||||
"category": "osmbasedmap",
|
||||
"id": "carto.voyager_no_labels",
|
||||
"type": "vector",
|
||||
"attribution": {
|
||||
"text": "<a href=\"https://carto.com/about-carto/\" target=\"_blank\" rel=\"noopener\">CARTO</a>",
|
||||
"url": "https://carto.com/"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue