forked from MapComplete/MapComplete
Refactoring: new background selector
This commit is contained in:
parent
5427a4cb05
commit
82093ffdf4
25 changed files with 658 additions and 269 deletions
|
@ -24,6 +24,8 @@ export class MenuState {
|
||||||
public readonly menuViewTabIndex: UIEventSource<number>
|
public readonly menuViewTabIndex: UIEventSource<number>
|
||||||
public readonly menuViewTab: UIEventSource<MenuViewTabStates>
|
public readonly menuViewTab: UIEventSource<MenuViewTabStates>
|
||||||
|
|
||||||
|
public readonly backgroundLayerSelectionIsOpened: UIEventSource<boolean> = new UIEventSource<boolean>(false)
|
||||||
|
|
||||||
public readonly highlightedLayerInFilters: UIEventSource<string> = new UIEventSource<string>(
|
public readonly highlightedLayerInFilters: UIEventSource<string> = new UIEventSource<string>(
|
||||||
undefined
|
undefined
|
||||||
)
|
)
|
||||||
|
@ -102,5 +104,7 @@ export class MenuState {
|
||||||
public closeAll() {
|
public closeAll() {
|
||||||
this.menuIsOpened.setData(false)
|
this.menuIsOpened.setData(false)
|
||||||
this.themeIsOpened.setData(false)
|
this.themeIsOpened.setData(false)
|
||||||
|
this.backgroundLayerSelectionIsOpened.setData(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,11 +106,11 @@ export default class CreateNoteImportLayer extends Conversion<LayerConfigJson, L
|
||||||
render: trs(t.popupTitle, { title }),
|
render: trs(t.popupTitle, { title }),
|
||||||
},
|
},
|
||||||
calculatedTags: [
|
calculatedTags: [
|
||||||
"_first_comment=feat.get('comments')[0].text.toLowerCase()",
|
"_first_comment=get(feat)('comments')[0].text.toLowerCase()",
|
||||||
"_trigger_index=(() => {const lines = feat.properties['_first_comment'].split('\\n'); const matchesMapCompleteURL = lines.map(l => l.match(\".*https://mapcomplete.osm.be/\\([a-zA-Z_-]+\\)\\(.html\\)?.*#import\")); const matchedIndexes = matchesMapCompleteURL.map((doesMatch, i) => [doesMatch !== null, i]).filter(v => v[0]).map(v => v[1]); return matchedIndexes[0] })()",
|
"_trigger_index=(() => {const lines = feat.properties['_first_comment'].split('\\n'); const matchesMapCompleteURL = lines.map(l => l.match(\".*https://mapcomplete.osm.be/\\([a-zA-Z_-]+\\)\\(.html\\)?.*#import\")); const matchedIndexes = matchesMapCompleteURL.map((doesMatch, i) => [doesMatch !== null, i]).filter(v => v[0]).map(v => v[1]); return matchedIndexes[0] })()",
|
||||||
"_comments_count=feat.get('comments').length",
|
"_comments_count=get(feat)('comments').length",
|
||||||
"_intro=(() => {const lines = feat.get('comments')[0].text.split('\\n'); lines.splice(feat.get('_trigger_index')-1, lines.length); return lines.filter(l => l !== '').join('<br/>');})()",
|
"_intro=(() => {const lines = get(feat)('comments')[0].text.split('\\n'); lines.splice(get(feat)('_trigger_index')-1, lines.length); return lines.filter(l => l !== '').join('<br/>');})()",
|
||||||
"_tags=(() => {let lines = feat.get('comments')[0].text.split('\\n').map(l => l.trim()); lines.splice(0, feat.get('_trigger_index') + 1); lines = lines.filter(l => l != ''); return lines.join(';');})()",
|
"_tags=(() => {let lines = get(feat)('comments')[0].text.split('\\n').map(l => l.trim()); lines.splice(0, get(feat)('_trigger_index') + 1); lines = lines.filter(l => l != ''); return lines.join(';');})()",
|
||||||
],
|
],
|
||||||
isShown: {
|
isShown: {
|
||||||
and: ["_trigger_index~*", { or: isShownIfAny }],
|
and: ["_trigger_index~*", { or: isShownIfAny }],
|
||||||
|
|
|
@ -303,7 +303,9 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
||||||
this.drawSpecialLayers()
|
this.drawSpecialLayers()
|
||||||
this.initHotkeys()
|
this.initHotkeys()
|
||||||
this.miscSetup()
|
this.miscSetup()
|
||||||
console.log("State setup completed", this)
|
if(!Utils.runningFromConsole){
|
||||||
|
console.log("State setup completed", this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
<div class="absolute top-0 right-0 w-screen h-screen p-4 md:p-6" style="background-color: #00000088">
|
<div class="absolute top-0 right-0 w-screen h-screen p-4 md:p-6" style="background-color: #00000088">
|
||||||
<div class="content normal-background">
|
<div class="content normal-background">
|
||||||
<div class="rounded-xl">
|
<div class="rounded-xl h-full">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
<slot name="close-button">
|
<slot name="close-button">
|
||||||
|
|
33
UI/Base/IfHidden.svelte
Normal file
33
UI/Base/IfHidden.svelte
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
|
import {onDestroy} from "svelte";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Functions as 'If', but uses 'display:hidden' instead.
|
||||||
|
*/
|
||||||
|
export let condition: UIEventSource<boolean>;
|
||||||
|
let _c = condition.data;
|
||||||
|
let hasBeenShownPositive = false
|
||||||
|
let hasBeenShownNegative = false
|
||||||
|
onDestroy(condition.addCallbackAndRun(c => {
|
||||||
|
/* Do _not_ abbreviate this as `.addCallback(c => _c = c)`. This is the same as writing `.addCallback(c => {return _c = c})`,
|
||||||
|
which will _unregister_ the callback if `c = true`! */
|
||||||
|
hasBeenShownPositive = hasBeenShownPositive || c
|
||||||
|
hasBeenShownNegative = hasBeenShownNegative || !c
|
||||||
|
_c = c;
|
||||||
|
return false
|
||||||
|
}))
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if hasBeenShownPositive}
|
||||||
|
<span class={_c ? "" : "hidden"}>
|
||||||
|
<slot/>
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if hasBeenShownNegative}
|
||||||
|
<span class={_c ? "hidden" : ""}>
|
||||||
|
<slot name="else"/>
|
||||||
|
</span>
|
||||||
|
{/if}
|
|
@ -16,11 +16,11 @@
|
||||||
|
|
||||||
{#if context}
|
{#if context}
|
||||||
{#if $linkOnMobile}
|
{#if $linkOnMobile}
|
||||||
<a href={LinkToWeblate.hrefToWeblate($language, context)} target="_blank" class="mx-1">
|
<a href={LinkToWeblate.hrefToWeblate($language, context)} target="_blank" class="mx-1 weblate-link">
|
||||||
<img src="./assets/svg/translate.svg" class="w-3 h-3 rounded-full font-gray" />
|
<img src="./assets/svg/translate.svg" class="w-3 h-3 rounded-full font-gray" />
|
||||||
</a>
|
</a>
|
||||||
{:else if $linkToWeblate}
|
{:else if $linkToWeblate}
|
||||||
<a href={LinkToWeblate.hrefToWeblate($language, context)} class="hidden-on-mobile mx-1" target="_blank">
|
<a href={LinkToWeblate.hrefToWeblate($language, context)} class="weblate-link hidden-on-mobile mx-1" target="_blank">
|
||||||
<img src="./assets/svg/translate.svg" class="w-3 h-3 rounded-full font-gray" />
|
<img src="./assets/svg/translate.svg" class="w-3 h-3 rounded-full font-gray" />
|
||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -3,13 +3,13 @@
|
||||||
import type {RasterLayerPolygon} from "../../Models/RasterLayers";
|
import type {RasterLayerPolygon} from "../../Models/RasterLayers";
|
||||||
import {AvailableRasterLayers} from "../../Models/RasterLayers";
|
import {AvailableRasterLayers} from "../../Models/RasterLayers";
|
||||||
import Tr from "../Base/Tr.svelte";
|
import Tr from "../Base/Tr.svelte";
|
||||||
import {onDestroy} from "svelte";
|
import {createEventDispatcher, onDestroy} from "svelte";
|
||||||
import Translations from "../i18n/Translations";
|
import Translations from "../i18n/Translations";
|
||||||
import Svg from "../../Svg";
|
import Svg from "../../Svg";
|
||||||
import {Map as MlMap} from "maplibre-gl"
|
import {Map as MlMap} from "maplibre-gl"
|
||||||
import MaplibreMap from "../Map/MaplibreMap.svelte";
|
|
||||||
import {MapLibreAdaptor} from "../Map/MapLibreAdaptor";
|
|
||||||
import type {MapProperties} from "../../Models/MapProperties";
|
import type {MapProperties} from "../../Models/MapProperties";
|
||||||
|
import OverlayMap from "../Map/OverlayMap.svelte";
|
||||||
|
import RasterLayerPicker from "../Map/RasterLayerPicker.svelte";
|
||||||
|
|
||||||
export let mapproperties: MapProperties
|
export let mapproperties: MapProperties
|
||||||
export let normalMap: UIEventSource<MlMap>
|
export let normalMap: UIEventSource<MlMap>
|
||||||
|
@ -28,63 +28,55 @@
|
||||||
*/
|
*/
|
||||||
export let availableRasterLayers: Store<RasterLayerPolygon[]>
|
export let availableRasterLayers: Store<RasterLayerPolygon[]>
|
||||||
|
|
||||||
let altmap: UIEventSource<MlMap> = new UIEventSource(undefined)
|
let raster0 = new UIEventSource<RasterLayerPolygon>(undefined)
|
||||||
let altproperties = new MapLibreAdaptor(altmap, {zoom: UIEventSource.feedFrom(mapproperties.zoom)})
|
|
||||||
altproperties.allowMoving.setData(false)
|
let raster1 = new UIEventSource<RasterLayerPolygon>(undefined)
|
||||||
altproperties.allowZooming.setData(false)
|
|
||||||
let altmap0: UIEventSource<MlMap> = new UIEventSource(undefined)
|
let currentLayer: RasterLayerPolygon
|
||||||
let altproperties0 = new MapLibreAdaptor(altmap0, {zoom: altproperties.zoom})
|
|
||||||
// altproperties0.allowMoving.setData(false)
|
|
||||||
// altproperties0.allowZooming.setData(false)
|
|
||||||
|
|
||||||
function updatedAltLayer() {
|
function updatedAltLayer() {
|
||||||
const available = availableRasterLayers.data
|
const available = availableRasterLayers.data
|
||||||
const current = rasterLayer.data
|
const current = rasterLayer.data
|
||||||
const defaultLayer = AvailableRasterLayers.maplibre
|
const defaultLayer = AvailableRasterLayers.maplibre
|
||||||
const firstOther = available.find(l => l !== current && l !== defaultLayer)
|
const firstOther = available.find(l => l !== defaultLayer)
|
||||||
|
const secondOther = available.find(l => l !== defaultLayer && l !== firstOther)
|
||||||
altproperties.rasterLayer.setData(firstOther)
|
raster0.setData(firstOther === current ? defaultLayer : firstOther)
|
||||||
const secondOther = available.find(l => l !== current && l !== firstOther && l !== defaultLayer)
|
raster1.setData(secondOther === current ? defaultLayer : secondOther)
|
||||||
altproperties0.rasterLayer.setData(secondOther)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updatedAltLayer()
|
||||||
|
onDestroy(mapproperties.rasterLayer.addCallbackAndRunD(updatedAltLayer))
|
||||||
onDestroy(availableRasterLayers.addCallbackAndRunD(updatedAltLayer))
|
onDestroy(availableRasterLayers.addCallbackAndRunD(updatedAltLayer))
|
||||||
onDestroy(rasterLayer.addCallbackAndRunD(updatedAltLayer))
|
|
||||||
|
|
||||||
function pixelCenterOf(map: UIEventSource<MlMap>): [number, number] {
|
function use(rasterLayer: UIEventSource<RasterLayerPolygon>): (() => void) {
|
||||||
const rect = map?.data?.getCanvas()?.getBoundingClientRect()
|
return () => {
|
||||||
if (!rect) {
|
currentLayer = undefined
|
||||||
return undefined
|
mapproperties.rasterLayer.setData(rasterLayer.data)
|
||||||
}
|
}
|
||||||
const x = (rect.left + rect.right) / 2
|
|
||||||
const y = (rect.top + rect.bottom) / 2
|
|
||||||
return [x, y]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mapproperties.location.addCallbackAndRunD(({lon, lat}) => {
|
|
||||||
if (!normalMap.data || !altmap.data) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const altMapCenter = pixelCenterOf(altmap)
|
|
||||||
const c = normalMap.data.unproject(altMapCenter)
|
|
||||||
altproperties.location.setData({lon: c.lng, lat: c.lat})
|
|
||||||
|
|
||||||
const altMapCenter0 = pixelCenterOf(altmap0)
|
const dispatch = createEventDispatcher<{ copyright_clicked }>()
|
||||||
const c0 = normalMap.data.unproject(altMapCenter0)
|
|
||||||
altproperties0.location.setData({lon: c0.lng, lat: c0.lat})
|
|
||||||
})
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<div class="flex">
|
<div class="flex items-end opacity-50 hover:opacity-100">
|
||||||
<div class="w-32 h-32 overflow-hidden border-interactive">
|
<div class="flex flex-col md:flex-row">
|
||||||
<MaplibreMap map={altmap}/>
|
<button class="w-16 h-12 md:w-16 md:h-16 overflow-hidden m-0 p-0"
|
||||||
|
on:click={use(raster0)}>
|
||||||
|
<OverlayMap placedOverMap={normalMap} placedOverMapProperties={mapproperties} rasterLayer={raster0}/>
|
||||||
|
</button>
|
||||||
|
<button class="w-16 h-12 md:w-16 md:h-16 overflow-hidden m-0 p-0 " on:click={use(raster1)}>
|
||||||
|
<OverlayMap placedOverMap={normalMap} placedOverMapProperties={mapproperties} rasterLayer={raster1}/>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-32 h-32 overflow-hidden border-interactive">
|
<div class="text-sm flex flex-col gap-y-1 h-fit ml-1">
|
||||||
<MaplibreMap map={altmap0}/>
|
|
||||||
</div>
|
<div class="low-interaction rounded p-1 w-64">
|
||||||
<div class="low-interaction flex flex-col">
|
<RasterLayerPicker availableLayers={availableRasterLayers} value={mapproperties.rasterLayer}></RasterLayerPicker>
|
||||||
<b>Current background:</b>
|
</div>
|
||||||
<Tr t={Translations.T(name)}/>
|
|
||||||
|
<button class="small" on:click={() => dispatch("copyright_clicked")}>
|
||||||
|
© OpenStreetMap
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -22,6 +22,7 @@ import ContributorCount from "../../Logic/ContributorCount"
|
||||||
import Img from "../Base/Img"
|
import Img from "../Base/Img"
|
||||||
import { TypedTranslation } from "../i18n/Translation"
|
import { TypedTranslation } from "../i18n/Translation"
|
||||||
import GeoIndexedStore from "../../Logic/FeatureSource/Actors/GeoIndexedStore"
|
import GeoIndexedStore from "../../Logic/FeatureSource/Actors/GeoIndexedStore"
|
||||||
|
import {RasterLayerPolygon} from "../../Models/RasterLayers";
|
||||||
|
|
||||||
export class OpenIdEditor extends VariableUiElement {
|
export class OpenIdEditor extends VariableUiElement {
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -113,7 +114,7 @@ export default class CopyrightPanel extends Combine {
|
||||||
|
|
||||||
constructor(state: {
|
constructor(state: {
|
||||||
layout: LayoutConfig
|
layout: LayoutConfig
|
||||||
mapProperties: { bounds: Store<BBox> }
|
mapProperties: { readonly bounds: Store<BBox>, readonly rasterLayer: Store<RasterLayerPolygon> }
|
||||||
osmConnection: OsmConnection
|
osmConnection: OsmConnection
|
||||||
dataIsLoading: Store<boolean>
|
dataIsLoading: Store<boolean>
|
||||||
perLayer: ReadonlyMap<string, GeoIndexedStore>
|
perLayer: ReadonlyMap<string, GeoIndexedStore>
|
||||||
|
@ -173,6 +174,29 @@ export default class CopyrightPanel extends Combine {
|
||||||
[
|
[
|
||||||
new Title(t.attributionTitle),
|
new Title(t.attributionTitle),
|
||||||
t.attributionContent,
|
t.attributionContent,
|
||||||
|
|
||||||
|
new VariableUiElement(state.mapProperties.rasterLayer.mapD(layer => {
|
||||||
|
const props = layer.properties
|
||||||
|
const attrUrl = props.attribution?.url
|
||||||
|
const attrText = props.attribution?.text
|
||||||
|
|
||||||
|
let bgAttr: BaseUIElement | string = undefined
|
||||||
|
if(attrText && attrUrl){
|
||||||
|
bgAttr = "<a href='"+attrUrl+"' target='_blank'>"+attrText+"</a>"
|
||||||
|
}else if(attrUrl){
|
||||||
|
bgAttr = attrUrl
|
||||||
|
}else{
|
||||||
|
bgAttr = attrText
|
||||||
|
}
|
||||||
|
if(bgAttr){
|
||||||
|
return Translations.t.general.attribution.attributionBackgroundLayerWithCopyright.Subs({
|
||||||
|
name: props.name,
|
||||||
|
copyright: bgAttr
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return Translations.t.general.attribution.attributionBackgroundLayer.Subs(props)
|
||||||
|
})),
|
||||||
|
|
||||||
maintainer,
|
maintainer,
|
||||||
dataContributors,
|
dataContributors,
|
||||||
CopyrightPanel.CodeContributors(contributors, t.codeContributionsBy),
|
CopyrightPanel.CodeContributors(contributors, t.codeContributionsBy),
|
||||||
|
|
|
@ -1,100 +1,116 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|
||||||
import { UIEventSource } from "../../Logic/UIEventSource";
|
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
import type { Feature } from "geojson";
|
import type {Feature} from "geojson";
|
||||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
|
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
|
||||||
import ToSvelte from "../Base/ToSvelte.svelte";
|
import ToSvelte from "../Base/ToSvelte.svelte";
|
||||||
import Svg from "../../Svg.js";
|
import Svg from "../../Svg.js";
|
||||||
import Translations from "../i18n/Translations";
|
import Translations from "../i18n/Translations";
|
||||||
import Loading from "../Base/Loading.svelte";
|
import Loading from "../Base/Loading.svelte";
|
||||||
import Hotkeys from "../Base/Hotkeys";
|
import Hotkeys from "../Base/Hotkeys";
|
||||||
import { Geocoding } from "../../Logic/Osm/Geocoding";
|
import {Geocoding} from "../../Logic/Osm/Geocoding";
|
||||||
import { BBox } from "../../Logic/BBox";
|
import {BBox} from "../../Logic/BBox";
|
||||||
import { GeoIndexedStoreForLayer } from "../../Logic/FeatureSource/Actors/GeoIndexedStore";
|
import {GeoIndexedStoreForLayer} from "../../Logic/FeatureSource/Actors/GeoIndexedStore";
|
||||||
import {createEventDispatcher} from "svelte";
|
import {createEventDispatcher, onDestroy} from "svelte";
|
||||||
|
|
||||||
export let perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer> | undefined = undefined;
|
export let perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer> | undefined = undefined;
|
||||||
export let bounds: UIEventSource<BBox>;
|
export let bounds: UIEventSource<BBox>;
|
||||||
export let selectedElement: UIEventSource<Feature> | undefined = undefined;
|
export let selectedElement: UIEventSource<Feature> | undefined = undefined;
|
||||||
export let selectedLayer: UIEventSource<LayerConfig> | undefined = undefined;
|
export let selectedLayer: UIEventSource<LayerConfig> | undefined = undefined;
|
||||||
|
|
||||||
let searchContents: string = undefined;
|
let searchContents: string = ""
|
||||||
|
export let triggerSearch: UIEventSource<any> = new UIEventSource<any>(undefined)
|
||||||
|
onDestroy(triggerSearch.addCallback(_ => {
|
||||||
|
console.log("TriggerRun pinged")
|
||||||
|
performSearch()
|
||||||
|
}))
|
||||||
|
|
||||||
let isRunning: boolean = false;
|
let isRunning: boolean = false;
|
||||||
|
|
||||||
let inputElement: HTMLInputElement;
|
let inputElement: HTMLInputElement;
|
||||||
|
|
||||||
let feedback: string = undefined;
|
let feedback: string = undefined;
|
||||||
|
|
||||||
Hotkeys.RegisterHotkey(
|
|
||||||
{ ctrl: "F" },
|
|
||||||
Translations.t.hotkeyDocumentation.selectSearch,
|
|
||||||
() => {
|
|
||||||
inputElement?.focus();
|
|
||||||
inputElement?.select();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{searchCompleted}>()
|
|
||||||
|
|
||||||
async function performSearch() {
|
|
||||||
try {
|
|
||||||
isRunning = true;
|
|
||||||
searchContents = searchContents?.trim() ?? "";
|
|
||||||
if (searchContents === "") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const result = await Geocoding.Search(searchContents, bounds.data);
|
|
||||||
if (result.length == 0) {
|
|
||||||
feedback = Translations.t.general.search.nothing.txt;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const poi = result[0];
|
|
||||||
const [lat0, lat1, lon0, lon1] = poi.boundingbox;
|
|
||||||
bounds.set(new BBox([[lon0, lat0], [lon1, lat1]]).pad(0.01));
|
|
||||||
if (perLayer !== undefined) {
|
|
||||||
const id = poi.osm_type + "/" + poi.osm_id;
|
|
||||||
const layers = Array.from(perLayer?.values() ?? []);
|
|
||||||
for (const layer of layers) {
|
|
||||||
const found = layer.features.data.find(f => f.properties.id === id);
|
|
||||||
selectedElement?.setData(found);
|
|
||||||
selectedLayer?.setData(layer.layer.layerDef);
|
|
||||||
|
|
||||||
|
Hotkeys.RegisterHotkey(
|
||||||
|
{ctrl: "F"},
|
||||||
|
Translations.t.hotkeyDocumentation.selectSearch,
|
||||||
|
() => {
|
||||||
|
inputElement?.focus();
|
||||||
|
inputElement?.select();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher<{ searchCompleted, searchIsValid: boolean }>()
|
||||||
|
$: {
|
||||||
|
if (!searchContents?.trim()) {
|
||||||
|
dispatch("searchIsValid", false)
|
||||||
|
}else{
|
||||||
|
dispatch("searchIsValid", true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function performSearch() {
|
||||||
|
try {
|
||||||
|
isRunning = true;
|
||||||
|
searchContents = searchContents?.trim() ?? "";
|
||||||
|
|
||||||
|
if (searchContents === "") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const result = await Geocoding.Search(searchContents, bounds.data);
|
||||||
|
if (result.length == 0) {
|
||||||
|
feedback = Translations.t.general.search.nothing.txt;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const poi = result[0];
|
||||||
|
const [lat0, lat1, lon0, lon1] = poi.boundingbox;
|
||||||
|
bounds.set(new BBox([[lon0, lat0], [lon1, lat1]]).pad(0.01));
|
||||||
|
if (perLayer !== undefined) {
|
||||||
|
const id = poi.osm_type + "/" + poi.osm_id;
|
||||||
|
const layers = Array.from(perLayer?.values() ?? []);
|
||||||
|
for (const layer of layers) {
|
||||||
|
const found = layer.features.data.find(f => f.properties.id === id);
|
||||||
|
selectedElement?.setData(found);
|
||||||
|
selectedLayer?.setData(layer.layer.layerDef);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
searchContents = ""
|
||||||
|
dispatch("searchIsValid", false)
|
||||||
|
dispatch("searchCompleted")
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
feedback = Translations.t.general.search.error.txt;
|
||||||
|
} finally {
|
||||||
|
isRunning = false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
dispatch("searchCompleted")
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
feedback = Translations.t.general.search.error.txt;
|
|
||||||
} finally {
|
|
||||||
isRunning = false;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex normal-background rounded-full pl-2 justify-between">
|
<div class="flex normal-background rounded-full pl-2 justify-between">
|
||||||
<form class="w-full">
|
<form class="w-full">
|
||||||
|
|
||||||
{#if isRunning}
|
{#if isRunning}
|
||||||
<Loading>{Translations.t.general.search.searching}</Loading>
|
<Loading>{Translations.t.general.search.searching}</Loading>
|
||||||
{:else if feedback !== undefined}
|
{:else if feedback !== undefined}
|
||||||
<div class="alert" on:click={() => feedback = undefined}>
|
<div class="alert" on:click={() => feedback = undefined}>
|
||||||
{feedback}
|
{feedback}
|
||||||
</div>
|
</div>
|
||||||
{:else }
|
{:else }
|
||||||
<input
|
<input
|
||||||
type="search"
|
type="search"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
bind:this={inputElement}
|
bind:this={inputElement}
|
||||||
on:keypress={keypr => keypr.key === "Enter" ? performSearch() : undefined}
|
on:keypress={keypr => keypr.key === "Enter" ? performSearch() : undefined}
|
||||||
|
|
||||||
bind:value={searchContents}
|
bind:value={searchContents}
|
||||||
placeholder={Translations.t.general.search.search}>
|
placeholder={Translations.t.general.search.search}>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
<div class="w-6 h-6 self-end" on:click={performSearch}>
|
<div class="w-6 h-6 self-end" on:click={performSearch}>
|
||||||
<ToSvelte construct={Svg.search_svg}></ToSvelte>
|
<ToSvelte construct={Svg.search_svg}></ToSvelte>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
export let onMainScreen: boolean = true
|
export let onMainScreen: boolean = true
|
||||||
|
|
||||||
const prefix = "mapcomplete-hidden-theme-"
|
const prefix = "mapcomplete-hidden-theme-"
|
||||||
const hiddenThemes: LayoutInformation[] = themeOverview["default"].filter(
|
const hiddenThemes: LayoutInformation[] = themeOverview.filter(
|
||||||
(layout) => layout.hideFromOverview
|
(layout) => layout.hideFromOverview
|
||||||
)
|
)
|
||||||
const userPreferences = state.osmConnection.preferencesHandler.preferences
|
const userPreferences = state.osmConnection.preferencesHandler.preferences
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
{layer}></TagRenderingAnswer>
|
{layer}></TagRenderingAnswer>
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div class="title-icons flex flex-row flex-wrap pt-0.5 sm:pt-1 items-center mr-2 gap-x-0.5 p-1 links-as-button">
|
<div class="no-weblate title-icons flex flex-row flex-wrap pt-0.5 sm:pt-1 items-center mr-2 gap-x-0.5 p-1 links-as-button">
|
||||||
{#each layer.titleIcons as titleIconConfig}
|
{#each layer.titleIcons as titleIconConfig}
|
||||||
{#if (titleIconConfig.condition?.matchesProperties(_tags) ?? true) && (titleIconConfig.metacondition?.matchesProperties({..._metatags, ..._tags}) ?? true) && titleIconConfig.IsKnown(_tags)}
|
{#if (titleIconConfig.condition?.matchesProperties(_tags) ?? true) && (titleIconConfig.metacondition?.matchesProperties({..._metatags, ..._tags}) ?? true) && titleIconConfig.IsKnown(_tags)}
|
||||||
<div class="w-8 h-8 flex items-center">
|
<div class="w-8 h-8 flex items-center">
|
||||||
|
|
87
UI/BigComponents/ThemeIntroPanel.svelte
Normal file
87
UI/BigComponents/ThemeIntroPanel.svelte
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
<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 IfNot from "../Base/IfNot.svelte";
|
||||||
|
import ToSvelte from "../Base/ToSvelte.svelte";
|
||||||
|
import ThemeViewState from "../../Models/ThemeViewState";
|
||||||
|
import If from "../Base/If.svelte";
|
||||||
|
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
|
import {SearchIcon} from "@rgossiaux/svelte-heroicons/solid";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Tr t={layout.description}></Tr>
|
||||||
|
<Tr t={Translations.t.general.welcomeExplanation.general}/>
|
||||||
|
{#if layout.layers.some((l) => l.presets?.length > 0)}
|
||||||
|
<If condition={state.featureSwitches.featureSwitchAddNew}>
|
||||||
|
<Tr t={Translations.t.general.welcomeExplanation.addNew}/>
|
||||||
|
</If>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<!--toTheMap,
|
||||||
|
loginStatus.SetClass("block mt-6 pt-2 md:border-t-2 border-dotted border-gray-400"),
|
||||||
|
-->
|
||||||
|
<Tr t={layout.descriptionTail}></Tr>
|
||||||
|
<NextButton clss="primary w-full" on:click={() => state.guistate.themeIsOpened.setData(false)}>
|
||||||
|
<div class="flex justify-center w-full text-2xl">
|
||||||
|
<Tr t={Translations.t.general.openTheMap}/>
|
||||||
|
</div>
|
||||||
|
</NextButton>
|
||||||
|
|
||||||
|
<div class="flex w-full flex-wrap sm:flex-nowrap">
|
||||||
|
<IfNot condition={state.geolocation.geolocationState.permission.map(p => p === "denied")}>
|
||||||
|
<button class="flex w-full gap-x-2 items-center" on:click={jumpToCurrentLocation}>
|
||||||
|
<ToSvelte construct={Svg.crosshair_svg().SetClass("w-8 h-8")}/>
|
||||||
|
<Tr t={Translations.t.general.openTheMapAtGeolocation}/>
|
||||||
|
</button>
|
||||||
|
</IfNot>
|
||||||
|
|
||||||
|
<div class="flex gap-x-2 items-center w-full border rounded .button p-2 m-1 low-interaction">
|
||||||
|
<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}>
|
||||||
|
</Geosearch>
|
||||||
|
</div>
|
||||||
|
<button class={"flex gap-x-2 justify-between items-center "+(searchEnabled ? "" : "disabled")}
|
||||||
|
on:click={() => triggerSearch.ping()}>
|
||||||
|
<Tr t={Translations.t.general.search.searchShort}/>
|
||||||
|
<SearchIcon class="w-6 h-6"></SearchIcon>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -275,7 +275,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
||||||
return new Promise<Blob>((resolve) => drawOn.toBlob((data) => resolve(data)))
|
return new Promise<Blob>((resolve) => drawOn.toBlob((data) => resolve(data)))
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateStores() {
|
private updateStores(): void {
|
||||||
const map = this._maplibreMap.data
|
const map = this._maplibreMap.data
|
||||||
if (!map) {
|
if (!map) {
|
||||||
return
|
return
|
||||||
|
@ -293,7 +293,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
||||||
this.bounds.setData(bbox)
|
this.bounds.setData(bbox)
|
||||||
}
|
}
|
||||||
|
|
||||||
private SetZoom(z: number) {
|
private SetZoom(z: number): void {
|
||||||
const map = this._maplibreMap.data
|
const map = this._maplibreMap.data
|
||||||
if (!map || z === undefined) {
|
if (!map || z === undefined) {
|
||||||
return
|
return
|
||||||
|
@ -303,7 +303,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private MoveMapToCurrentLoc(loc: { lat: number; lon: number }) {
|
private MoveMapToCurrentLoc(loc: { lat: number; lon: number }): void {
|
||||||
const map = this._maplibreMap.data
|
const map = this._maplibreMap.data
|
||||||
if (!map || loc === undefined) {
|
if (!map || loc === undefined) {
|
||||||
return
|
return
|
||||||
|
@ -325,7 +325,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private removeCurrentLayer(map: MLMap) {
|
private removeCurrentLayer(map: MLMap): void {
|
||||||
if (this._currentRasterLayer) {
|
if (this._currentRasterLayer) {
|
||||||
// hide the previous layer
|
// hide the previous layer
|
||||||
map.removeLayer(this._currentRasterLayer)
|
map.removeLayer(this._currentRasterLayer)
|
||||||
|
@ -333,7 +333,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async setBackground() {
|
private async setBackground(): Promise<void> {
|
||||||
const map = this._maplibreMap.data
|
const map = this._maplibreMap.data
|
||||||
if (!map) {
|
if (!map) {
|
||||||
return
|
return
|
||||||
|
@ -363,6 +363,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
||||||
|
|
||||||
map.addSource(background.id, MapLibreAdaptor.prepareWmsSource(background))
|
map.addSource(background.id, MapLibreAdaptor.prepareWmsSource(background))
|
||||||
|
|
||||||
|
map.resize()
|
||||||
map.addLayer(
|
map.addLayer(
|
||||||
{
|
{
|
||||||
id: background.id,
|
id: background.id,
|
||||||
|
|
69
UI/Map/OverlayMap.svelte
Normal file
69
UI/Map/OverlayMap.svelte
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
<script lang="ts">
|
||||||
|
/**
|
||||||
|
* The overlay map is a bit a weird map:
|
||||||
|
* it is a HTML-component which is intended to be placed _over_ another map.
|
||||||
|
* It will align itself in order to seamlessly show the same location; but possibly in a different style
|
||||||
|
*/
|
||||||
|
import MaplibreMap from "./MaplibreMap.svelte";
|
||||||
|
import {Store, UIEventSource} from "../../Logic/UIEventSource";
|
||||||
|
import {Map as MlMap} from "maplibre-gl";
|
||||||
|
import {MapLibreAdaptor} from "./MapLibreAdaptor";
|
||||||
|
import type {MapProperties} from "../../Models/MapProperties";
|
||||||
|
import {onDestroy} from "svelte";
|
||||||
|
import type {RasterLayerPolygon} from "../../Models/RasterLayers";
|
||||||
|
|
||||||
|
|
||||||
|
export let placedOverMapProperties: MapProperties
|
||||||
|
export let placedOverMap: UIEventSource<MlMap>
|
||||||
|
|
||||||
|
export let rasterLayer: UIEventSource<RasterLayerPolygon>
|
||||||
|
|
||||||
|
export let visible: Store<boolean> = undefined
|
||||||
|
let altmap: UIEventSource<MlMap> = new UIEventSource(undefined)
|
||||||
|
let altproperties = new MapLibreAdaptor(altmap, {
|
||||||
|
rasterLayer,
|
||||||
|
zoom: UIEventSource.feedFrom(placedOverMapProperties.zoom)
|
||||||
|
})
|
||||||
|
altproperties.allowMoving.setData(false)
|
||||||
|
altproperties.allowZooming.setData(false)
|
||||||
|
|
||||||
|
function pixelCenterOf(map: UIEventSource<MlMap>): [number, number] {
|
||||||
|
const rect = map?.data?.getCanvas()?.getBoundingClientRect()
|
||||||
|
if (!rect) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
const x = (rect.left + rect.right) / 2
|
||||||
|
const y = (rect.top + rect.bottom) / 2
|
||||||
|
return [x, y]
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateLocation() {
|
||||||
|
if (!placedOverMap.data || !altmap.data) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
altmap.data.resize()
|
||||||
|
const {lon, lat} = placedOverMapProperties.location.data
|
||||||
|
const altMapCenter = pixelCenterOf(altmap)
|
||||||
|
const c = placedOverMap.data.unproject(altMapCenter)
|
||||||
|
altproperties.location.setData({lon: c.lng, lat: c.lat})
|
||||||
|
}
|
||||||
|
|
||||||
|
onDestroy(placedOverMapProperties.location.addCallbackAndRunD(updateLocation))
|
||||||
|
updateLocation()
|
||||||
|
window.setTimeout(updateLocation, 150)
|
||||||
|
window.setTimeout(updateLocation, 500)
|
||||||
|
|
||||||
|
if (visible) {
|
||||||
|
onDestroy(visible?.addCallbackAndRunD(v => {
|
||||||
|
if (!v) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
updateLocation()
|
||||||
|
window.setTimeout(updateLocation, 150)
|
||||||
|
window.setTimeout(updateLocation, 500)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<MaplibreMap map={altmap}/>
|
73
UI/Map/RasterLayerOverview.svelte
Normal file
73
UI/Map/RasterLayerOverview.svelte
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
<script lang="ts">
|
||||||
|
/**
|
||||||
|
* The RasterLayerOverview shows the available 4 categories of maps with a RasterLayerPicker
|
||||||
|
*/
|
||||||
|
import {Store, UIEventSource} from "../../Logic/UIEventSource";
|
||||||
|
import type {RasterLayerPolygon} from "../../Models/RasterLayers";
|
||||||
|
import type {MapProperties} from "../../Models/MapProperties";
|
||||||
|
import {Map as MlMap} from "maplibre-gl";
|
||||||
|
import RasterLayerPicker from "./RasterLayerPicker.svelte";
|
||||||
|
import type {EliCategory} from "../../Models/RasterLayerProperties";
|
||||||
|
import UserRelatedState from "../../Logic/State/UserRelatedState";
|
||||||
|
import Translations from "../i18n/Translations";
|
||||||
|
import Tr from "../Base/Tr.svelte";
|
||||||
|
|
||||||
|
export let availableLayers: Store<RasterLayerPolygon[]>
|
||||||
|
export let mapproperties: MapProperties
|
||||||
|
export let userstate: UserRelatedState
|
||||||
|
export let map: Store<MlMap>
|
||||||
|
/**
|
||||||
|
* Used to toggle the background layers on/off
|
||||||
|
*/
|
||||||
|
export let visible: UIEventSource<boolean> = undefined
|
||||||
|
|
||||||
|
type CategoryType = "photo" | "map" | "other" | "osmbasedmap"
|
||||||
|
const categories: Record<CategoryType, EliCategory[]> = {
|
||||||
|
"photo": ["photo", "historicphoto"],
|
||||||
|
"map": ["map", "historicmap"],
|
||||||
|
"other": ["other", "elevation"],
|
||||||
|
"osmbasedmap": ["osmbasedmap"]
|
||||||
|
}
|
||||||
|
|
||||||
|
function availableForCategory(type: CategoryType): Store<RasterLayerPolygon[]> {
|
||||||
|
const keywords = categories[type]
|
||||||
|
return availableLayers.mapD(available => available.filter(layer =>
|
||||||
|
keywords.indexOf(<EliCategory>layer.properties.category) >= 0
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapLayers = availableForCategory("map")
|
||||||
|
const osmbasedmapLayers = availableForCategory("osmbasedmap")
|
||||||
|
const photoLayers = availableForCategory("photo")
|
||||||
|
const otherLayers = availableForCategory("other")
|
||||||
|
|
||||||
|
function onApply() {
|
||||||
|
visible.setData(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPref(type: CategoryType): UIEventSource<string> {
|
||||||
|
return userstate.osmConnection.GetPreference("preferred-layer-" + type)
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="h-full flex flex-col">
|
||||||
|
<slot name="title">
|
||||||
|
<h2>
|
||||||
|
<Tr t={Translations.t.general.backgroundMap}/>
|
||||||
|
</h2>
|
||||||
|
</slot>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-2 h-full w-full">
|
||||||
|
<RasterLayerPicker availableLayers={photoLayers} favourite={getPref("photo")} {map} {mapproperties}
|
||||||
|
on:appliedLayer={onApply} {visible}/>
|
||||||
|
<RasterLayerPicker availableLayers={mapLayers} favourite={getPref("map")} {map} {mapproperties}
|
||||||
|
on:appliedLayer={onApply} {visible}/>
|
||||||
|
<RasterLayerPicker availableLayers={osmbasedmapLayers} favourite={getPref("osmbasedmap")}
|
||||||
|
{map} {mapproperties} on:appliedLayer={onApply} {visible}/>
|
||||||
|
<RasterLayerPicker availableLayers={otherLayers} favourite={getPref("other")} {map} {mapproperties}
|
||||||
|
on:appliedLayer={onApply} {visible}/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
|
@ -1,18 +1,77 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Readable, Writable } from "svelte/store";
|
import type {RasterLayerPolygon} from "../../Models/RasterLayers";
|
||||||
import type { RasterLayerPolygon } from "../../Models/RasterLayers";
|
import OverlayMap from "./OverlayMap.svelte";
|
||||||
|
import type {MapProperties} from "../../Models/MapProperties";
|
||||||
|
import {Store, UIEventSource} from "../../Logic/UIEventSource";
|
||||||
|
import {Map as MlMap} from "maplibre-gl"
|
||||||
|
import {createEventDispatcher, onDestroy} from "svelte";
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* Chooses a background-layer out of available options
|
* Chooses a background-layer out of available options
|
||||||
*/
|
*/
|
||||||
export let availableLayers: Readable<RasterLayerPolygon[]>
|
export let availableLayers: Store<RasterLayerPolygon[]>
|
||||||
export let value: Writable<RasterLayerPolygon>
|
export let mapproperties: MapProperties
|
||||||
|
export let map: Store<MlMap>
|
||||||
|
|
||||||
|
export let visible: Store<boolean> = undefined
|
||||||
|
|
||||||
|
let dispatch = createEventDispatcher<{appliedLayer}>()
|
||||||
|
|
||||||
|
export let favourite : UIEventSource<string> = undefined
|
||||||
|
|
||||||
|
|
||||||
|
let rasterLayer = new UIEventSource<RasterLayerPolygon>(availableLayers.data?.[0])
|
||||||
|
let hasLayers = true
|
||||||
|
onDestroy(availableLayers.addCallbackAndRun(layers => {
|
||||||
|
if (layers === undefined || layers.length === 0) {
|
||||||
|
hasLayers = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hasLayers = true
|
||||||
|
rasterLayer.setData(layers[0])
|
||||||
|
}))
|
||||||
|
|
||||||
|
if(favourite){
|
||||||
|
onDestroy(favourite.addCallbackAndRunD(favourite => {
|
||||||
|
const fav = availableLayers.data?.find(l => l.properties.id === favourite)
|
||||||
|
if(!fav){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rasterLayer.setData(fav)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
onDestroy(rasterLayer.addCallbackAndRunD(selected => {
|
||||||
|
favourite?.setData(selected.properties.id)
|
||||||
|
}))
|
||||||
|
|
||||||
|
let rasterLayerOnMap = UIEventSource.feedFrom(rasterLayer)
|
||||||
|
|
||||||
|
if (visible) {
|
||||||
|
onDestroy(visible?.addCallbackAndRunD(visible => {
|
||||||
|
if (visible) {
|
||||||
|
rasterLayerOnMap.setData(rasterLayer.data ?? availableLayers.data[0])
|
||||||
|
} else {
|
||||||
|
rasterLayerOnMap.setData(undefined)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<select bind:value={$value}>
|
{#if hasLayers}
|
||||||
{#each $availableLayers as availableLayer }
|
<div class="h-full w-full flex flex-col">
|
||||||
<option value={availableLayer}>
|
<button on:click={() => {mapproperties.rasterLayer.setData(rasterLayer.data);
|
||||||
{availableLayer.properties.name}
|
dispatch("appliedLayer")
|
||||||
</option>
|
}} class="w-full h-full m-0 p-0">
|
||||||
{/each}
|
<OverlayMap rasterLayer={rasterLayerOnMap} placedOverMap={map} placedOverMapProperties={mapproperties}
|
||||||
</select>
|
{visible}/>
|
||||||
|
</button>
|
||||||
|
<select bind:value={$rasterLayer} class="w-full">
|
||||||
|
{#each $availableLayers as availableLayer }
|
||||||
|
<option value={availableLayer}>
|
||||||
|
{availableLayer.properties.name}
|
||||||
|
</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
|
@ -11,12 +11,13 @@
|
||||||
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 RasterLayerPicker from "./Map/RasterLayerPicker.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 {Square3Stack3dIcon} from "@babeard/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";
|
||||||
|
@ -39,9 +40,12 @@
|
||||||
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 {ShareScreen} from "./BigComponents/ShareScreen";
|
||||||
import NextButton from "./Base/NextButton.svelte";
|
import ThemeIntroPanel from "./BigComponents/ThemeIntroPanel.svelte";
|
||||||
import IfNot from "./Base/IfNot.svelte";
|
import type {Readable} from "svelte/store";
|
||||||
import BackgroundSwitcher from "./BigComponents/BackgroundSwitcher.svelte";
|
import type {RasterLayerPolygon} from "../Models/RasterLayers";
|
||||||
|
import RasterLayerPicker from "./Map/RasterLayerPicker.svelte";
|
||||||
|
import RasterLayerOverview from "./Map/RasterLayerOverview.svelte";
|
||||||
|
import IfHidden from "./Base/IfHidden.svelte";
|
||||||
|
|
||||||
export let state: ThemeViewState;
|
export let state: ThemeViewState;
|
||||||
let layout = state.layout;
|
let layout = state.layout;
|
||||||
|
@ -86,21 +90,7 @@
|
||||||
let availableLayers = state.availableLayers;
|
let availableLayers = state.availableLayers;
|
||||||
let userdetails = state.osmConnection.userDetails;
|
let userdetails = state.osmConnection.userDetails;
|
||||||
let currentViewLayer = layout.layers.find(l => l.id === "current_view")
|
let currentViewLayer = layout.layers.find(l => l.id === "current_view")
|
||||||
|
let rasterLayer: Readable<RasterLayerPolygon> = state.mapProperties.rasterLayer
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
@ -138,35 +128,48 @@
|
||||||
<ToSvelte construct={() => new ExtraLinkButton(state, layout.extraLink)}></ToSvelte>
|
<ToSvelte construct={() => new ExtraLinkButton(state, layout.extraLink)}></ToSvelte>
|
||||||
<If condition={state.featureSwitchIsTesting}>
|
<If condition={state.featureSwitchIsTesting}>
|
||||||
<div class="alert w-fit">
|
<div class="alert w-fit">
|
||||||
Testmode
|
Testmode
|
||||||
</div>
|
</div>
|
||||||
</If>
|
</If>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="absolute bottom-0 left-0 mb-4 ml-4">
|
<div class="absolute bottom-0 left-0 mb-4 w-screen">
|
||||||
<BackgroundSwitcher availableRasterLayers={state.availableLayers} mapproperties={state.mapProperties} normalMap={state.map}/>
|
<div class="w-full flex justify-between px-4 items-end">
|
||||||
</div>
|
<div>
|
||||||
|
<!-- bottom left elements -->
|
||||||
<div class="absolute bottom-0 right-0 mb-4 mr-4 flex flex-col items-end">
|
<MapControlButton on:click={() => state.guistate.backgroundLayerSelectionIsOpened.setData(true)}>
|
||||||
<If condition={state.floors.map(f => f.length > 1)}>
|
<Square3Stack3dIcon class="w-6 h-6"/>
|
||||||
<div class="mr-0.5">
|
</MapControlButton>
|
||||||
<LevelSelector floors={state.floors} layerState={state.layerState} zoom={state.mapProperties.zoom}/>
|
<a class="opacity-50 hover:opacity-100 text-white cursor-pointer bg-black-transparent px-1 rounded-2xl"
|
||||||
|
on:click={() =>{ state.guistate.themeViewTab.setData("copyright"); state.guistate.themeIsOpened.setData(true)}}>
|
||||||
|
© OpenStreetMap | <span class="w-24">{$rasterLayer.properties.name}</span>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</If>
|
|
||||||
<MapControlButton on:click={() => mapproperties.zoom.update(z => z+1)}>
|
<div class="flex flex-col items-end">
|
||||||
<ToSvelte construct={Svg.plus_svg().SetClass("w-8 h-8")}/>
|
<!-- bottom right elements -->
|
||||||
</MapControlButton>
|
<If condition={state.floors.map(f => f.length > 1)}>
|
||||||
<MapControlButton on:click={() => mapproperties.zoom.update(z => z-1)}>
|
<div class="mr-0.5">
|
||||||
<ToSvelte construct={Svg.min_svg().SetClass("w-8 h-8")}/>
|
<LevelSelector floors={state.floors} layerState={state.layerState} zoom={state.mapProperties.zoom}/>
|
||||||
</MapControlButton>
|
</div>
|
||||||
<If condition={featureSwitches.featureSwitchGeolocation}>
|
</If>
|
||||||
<MapControlButton>
|
<MapControlButton on:click={() => mapproperties.zoom.update(z => z+1)}>
|
||||||
<ToSvelte
|
<ToSvelte construct={Svg.plus_svg().SetClass("w-8 h-8")}/>
|
||||||
construct={new GeolocationControl(state.geolocation, mapproperties).SetClass("block w-8 h-8")}></ToSvelte>
|
</MapControlButton>
|
||||||
</MapControlButton>
|
<MapControlButton on:click={() => mapproperties.zoom.update(z => z-1)}>
|
||||||
</If>
|
<ToSvelte construct={Svg.min_svg().SetClass("w-8 h-8")}/>
|
||||||
|
</MapControlButton>
|
||||||
|
<If condition={featureSwitches.featureSwitchGeolocation}>
|
||||||
|
<MapControlButton>
|
||||||
|
<ToSvelte
|
||||||
|
construct={new GeolocationControl(state.geolocation, mapproperties).SetClass("block w-8 h-8")}></ToSvelte>
|
||||||
|
</MapControlButton>
|
||||||
|
</If>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<If condition={selectedElementView.map(v => v !== undefined && selectedLayer.data !== undefined && !selectedLayer.data.popupInFloatover,[ selectedLayer] )}>
|
<If condition={selectedElementView.map(v => v !== undefined && selectedLayer.data !== undefined && !selectedLayer.data.popupInFloatover,[ selectedLayer] )}>
|
||||||
|
@ -204,43 +207,7 @@
|
||||||
|
|
||||||
<div class="m-4" slot="content0">
|
<div class="m-4" slot="content0">
|
||||||
|
|
||||||
<Tr t={layout.description}></Tr>
|
<ThemeIntroPanel {state}/>
|
||||||
<Tr t={Translations.t.general.welcomeExplanation.general}/>
|
|
||||||
{#if layout.layers.some((l) => l.presets?.length > 0)}
|
|
||||||
<If condition={state.featureSwitches.featureSwitchAddNew}>
|
|
||||||
<Tr t={Translations.t.general.welcomeExplanation.addNew}/>
|
|
||||||
</If>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<!--toTheMap,
|
|
||||||
loginStatus.SetClass("block mt-6 pt-2 md:border-t-2 border-dotted border-gray-400"),
|
|
||||||
-->
|
|
||||||
<Tr t={layout.descriptionTail}></Tr>
|
|
||||||
<NextButton clss="primary w-full" on:click={() => state.guistate.themeIsOpened.setData(false)}>
|
|
||||||
<div class="flex justify-center w-full text-2xl">
|
|
||||||
<Tr t={Translations.t.general.openTheMap}/>
|
|
||||||
</div>
|
|
||||||
</NextButton>
|
|
||||||
|
|
||||||
<div class="flex w-full">
|
|
||||||
<IfNot condition={state.geolocation.geolocationState.permission.map(p => p === "denied")}>
|
|
||||||
<button class="flex w-full gap-x-2 items-center" on:click={jumpToCurrentLocation}>
|
|
||||||
<ToSvelte construct={Svg.crosshair_svg().SetClass("w-8 h-8")}/>
|
|
||||||
<span>
|
|
||||||
Jump to your location
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</IfNot>
|
|
||||||
|
|
||||||
<div class="flex flex-col w-full border rounded low-interactive">
|
|
||||||
Search for a location:
|
|
||||||
<Geosearch bounds={state.mapProperties.bounds}
|
|
||||||
on:searchCompleted={() => state.guistate.themeIsOpened.setData(false)}
|
|
||||||
perLayer={state.perLayer}
|
|
||||||
{selectedElement}
|
|
||||||
{selectedLayer}/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -265,9 +232,6 @@
|
||||||
zoomlevel={state.mapProperties.zoom}
|
zoomlevel={state.mapProperties.zoom}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
<If condition={state.featureSwitches.featureSwitchBackgroundSelection}>
|
|
||||||
<RasterLayerPicker {availableLayers} value={mapproperties.rasterLayer}></RasterLayerPicker>
|
|
||||||
</If>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex" slot="title2">
|
<div class="flex" slot="title2">
|
||||||
<If condition={state.featureSwitches.featureSwitchEnableExport}>
|
<If condition={state.featureSwitches.featureSwitchEnableExport}>
|
||||||
|
@ -288,12 +252,22 @@
|
||||||
<div slot="title4">
|
<div slot="title4">
|
||||||
<Tr t={Translations.t.general.sharescreen.title}/>
|
<Tr t={Translations.t.general.sharescreen.title}/>
|
||||||
</div>
|
</div>
|
||||||
<ToSvelte construct={() => new ShareScreen(state)} slot="content4"/>
|
<div class="m-2" slot="content4">
|
||||||
|
<ToSvelte construct={() => new ShareScreen(state)}/>
|
||||||
|
</div>
|
||||||
|
|
||||||
</TabbedGroup>
|
</TabbedGroup>
|
||||||
</FloatOver>
|
</FloatOver>
|
||||||
</If>
|
</If>
|
||||||
|
|
||||||
|
<IfHidden condition={state.guistate.backgroundLayerSelectionIsOpened}>
|
||||||
|
<!-- background layer selector -->
|
||||||
|
<FloatOver on:close={() => state.guistate.backgroundLayerSelectionIsOpened.setData(false)}>
|
||||||
|
<div class="p-2 h-full">
|
||||||
|
<RasterLayerOverview userstate={state.userRelatedState} mapproperties={state.mapProperties} map={state.map} {availableLayers} visible={state.guistate.backgroundLayerSelectionIsOpened}/>
|
||||||
|
</div>
|
||||||
|
</FloatOver>
|
||||||
|
</IfHidden>
|
||||||
|
|
||||||
<If condition={state.guistate.menuIsOpened}>
|
<If condition={state.guistate.menuIsOpened}>
|
||||||
<!-- Menu page -->
|
<!-- Menu page -->
|
||||||
|
|
|
@ -233,7 +233,7 @@
|
||||||
{
|
{
|
||||||
"if": "amenity=cafe",
|
"if": "amenity=cafe",
|
||||||
"then": {
|
"then": {
|
||||||
"en": "A <b>cafe</b> to drink tea, coffee or an alcoholic beverage in a quiet environment",
|
"en": "A <b>cafe</b> to drink tea, coffee or an alcoholical bevarage in a quiet environment",
|
||||||
"nl": "Dit is een <b>cafe</b> - een plaats waar men rustig kan zitten om een thee, koffie of alcoholische drank te nuttigen.",
|
"nl": "Dit is een <b>cafe</b> - een plaats waar men rustig kan zitten om een thee, koffie of alcoholische drank te nuttigen.",
|
||||||
"de": "Ein <b>Café</b>, um in ruhiger Umgebung Tee, Kaffee oder ein alkoholisches Getränk zu trinken",
|
"de": "Ein <b>Café</b>, um in ruhiger Umgebung Tee, Kaffee oder ein alkoholisches Getränk zu trinken",
|
||||||
"da": "En <b>café</b> til at drikke te, kaffe eller en alkoholisk drik i rolige omgivelser",
|
"da": "En <b>café</b> til at drikke te, kaffe eller en alkoholisk drik i rolige omgivelser",
|
||||||
|
@ -245,7 +245,7 @@
|
||||||
{
|
{
|
||||||
"if": "amenity=restaurant",
|
"if": "amenity=restaurant",
|
||||||
"then": {
|
"then": {
|
||||||
"en": "A <b>restaurant</b> where one can get a proper meal",
|
"en": "A <b>restuarant</b> where one can get a proper meal",
|
||||||
"nl": "Dit is een <b>restaurant</b> waar men een maaltijd geserveerd krijgt",
|
"nl": "Dit is een <b>restaurant</b> waar men een maaltijd geserveerd krijgt",
|
||||||
"de": "Ein <b>Restaurant</b>, in dem man ordentlich essen kann",
|
"de": "Ein <b>Restaurant</b>, in dem man ordentlich essen kann",
|
||||||
"da": "En <b>restaurant</b>, hvor man kan få et ordentligt måltid",
|
"da": "En <b>restaurant</b>, hvor man kan få et ordentligt måltid",
|
||||||
|
|
|
@ -4699,12 +4699,12 @@
|
||||||
"socket:typee=1"
|
"socket:typee=1"
|
||||||
],
|
],
|
||||||
"title": {
|
"title": {
|
||||||
"en": "a charging station for electrical bikes with a normal european wall plug <img src='./assets/layers/charging_station/TypeE.svg' class=\"w-6 h-6 mx-1 bg-white rounded-full \" style='display: inline-block'/>",
|
"en": "a charging station for electrical bikes with a normal european wall plug <img src='./assets/layers/charging_station/typee.svg' style='width: 2rem; height: 2rem; float: left; background: white; border-radius: 1rem; margin-right: 0.5rem'/> (meant to charge electrical bikes)",
|
||||||
"nl": "een oplaadpunt voor elektrische fietsen met een gewoon Europees stopcontact <img src='./assets/layers/charging_station/TypeE.svg' class=\"w-6 h-6 mx-1 bg-white rounded-full\" style='display: inline-block'/>",
|
"nl": "een oplaadpunt voor elektrische fietsen met een gewoon Europees stopcontact <img src='./assets/layers/charging_station/typee.svg' style='width: 2rem; height: 2rem; float: left; background: white; border-radius: 1rem; margin-right: 0.5rem'/> (speciaal bedoeld voor fietsen)",
|
||||||
"ca": "una estació de càrrega per a bicicletes elèctriques amb un endoll de paret europeu normal <img src='./assets/layers/charging_station/TypeE.svg' class=\"w-6 h-6 mx-1 bg-white rounded-full\" style='display: inline-block'/>",
|
"ca": "una estació de càrrega per a bicicletes elèctriques amb un endoll de paret europeu normal<img src='./assets/layers/charging_station/typee.svg' style='width: 2rem; height: 2rem; float: left; background: white; border-radius: 1rem; margin-right: 0.5rem'/> (destinat a carregar bicicletes elèctriques)",
|
||||||
"da": "en ladestation til elektriske cykler med et normalt europæisk vægstik <img src='./assets/layers/charging_station/TypeE.svg' class=\"w-6 h-6 mx-1 bg-white rounded-full\" style='display: inline-block'/>",
|
"da": "en ladestation til elektriske cykler med et normalt europæisk vægstik <img src='./assets/layers/charging_station/typee.svg' style='width: 2rem; height: 2rem; float: left; background: white; border-radius: 1rem; margin-right: 0.5rem'/> (beregnet til opladning af elektriske cykler)",
|
||||||
"de": "eine Ladestation für Elektrofahrräder mit einer normalen europäischen Steckdose <img src='./assets/layers/charging_station/TypeE.svg' class=\"w-6 h-6 mx-1 bg-white rounded-full\" style='display: inline-block'/>",
|
"de": "eine Ladestation für Elektrofahrräder mit einer normalen europäischen Steckdose <img src='./assets/layers/charging_station/typee.svg' style='width: 2rem; height: 2rem; float: left; background: white; border-radius: 1rem; margin-right: 0.5rem'/> (zum Laden von Elektrofahrrädern)",
|
||||||
"es": "una estación de carga para bicicletas eléctricas con un enchufe de pared europeo normal <img src='./assets/layers/charging_station/TypeE.svg' class=\"w-6 h-6 mx-1 bg-white rounded-full\" style='display: inline-block'/>"
|
"es": "una estación de carga para bicicletas eléctricas con un enchufe de pared europeo normal <img src='./assets/layers/charging_station/typee.svg' style='width: 2rem; height: 2rem; float: left; background: white; border-radius: 1rem; margin-right: 0.5rem'/> (pensado para cargar bicicletas eléctricas)"
|
||||||
},
|
},
|
||||||
"preciseInput": {
|
"preciseInput": {
|
||||||
"preferredBackground": "map"
|
"preferredBackground": "map"
|
||||||
|
|
|
@ -434,6 +434,10 @@ select:hover {
|
||||||
color: var(--background-color);
|
color: var(--background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bg-black-transparent {
|
||||||
|
background-color: #00000088;
|
||||||
|
}
|
||||||
|
|
||||||
.block-ruby {
|
.block-ruby {
|
||||||
display: block ruby;
|
display: block ruby;
|
||||||
}
|
}
|
||||||
|
@ -455,6 +459,9 @@ select:hover {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.no-weblate weblate {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
.link-underline a {
|
.link-underline a {
|
||||||
text-decoration: underline 1px var(--foreground-color);
|
text-decoration: underline 1px var(--foreground-color);
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,6 +132,8 @@
|
||||||
"isApplied": "The changes are applied"
|
"isApplied": "The changes are applied"
|
||||||
},
|
},
|
||||||
"attribution": {
|
"attribution": {
|
||||||
|
"attributionBackgroundLayer": "The current background layer is {name}",
|
||||||
|
"attributionBackgroundLayerWithCopyright": "The current background layer is {name}: {copyright}",
|
||||||
"attributionContent": "<p>All data is provided by <a href='https://osm.org' target='_blank'>OpenStreetMap</a>, freely reusable under <a href='https://osm.org/copyright' target='_blank'>the Open DataBase License</a>.</p>",
|
"attributionContent": "<p>All data is provided by <a href='https://osm.org' target='_blank'>OpenStreetMap</a>, freely reusable under <a href='https://osm.org/copyright' target='_blank'>the Open DataBase License</a>.</p>",
|
||||||
"attributionTitle": "Attribution notice",
|
"attributionTitle": "Attribution notice",
|
||||||
"codeContributionsBy": "MapComplete has been built by {contributors} and <a href='https://github.com/pietervdvn/MapComplete/graphs/contributors' target='_blank'>{hiddenCount} more contributors</a>",
|
"codeContributionsBy": "MapComplete has been built by {contributors} and <a href='https://github.com/pietervdvn/MapComplete/graphs/contributors' target='_blank'>{hiddenCount} more contributors</a>",
|
||||||
|
@ -158,7 +160,7 @@
|
||||||
"back": "Back",
|
"back": "Back",
|
||||||
"backToIndex": "Go back to the overview with all thematic maps",
|
"backToIndex": "Go back to the overview with all thematic maps",
|
||||||
"backToMapcomplete": "Back to the theme overview",
|
"backToMapcomplete": "Back to the theme overview",
|
||||||
"backgroundMap": "Background map",
|
"backgroundMap": "Select a background layer",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"confirm": "Confirm",
|
"confirm": "Confirm",
|
||||||
"customThemeIntro": "<h3>Custom themes</h3>These are previously visited user-generated themes.",
|
"customThemeIntro": "<h3>Custom themes</h3>These are previously visited user-generated themes.",
|
||||||
|
@ -236,6 +238,7 @@
|
||||||
"number": "number",
|
"number": "number",
|
||||||
"openStreetMapIntro": "<h3>An Open Map</h3><p>One that everyone can use and edit freely. A single place to store all geo-info. Different, small, incompatible and outdated maps are not needed anywhere.</p><p><b><a href='https://OpenStreetMap.org' target='_blank'>OpenStreetMap</a></b> is not the enemy map. The map data can be used freely (with <a href='https://osm.org/copyright' target='_blank'>attribution and publication of changes to that data</a>). Everyone can add new data and fix errors. This website uses OpenStreetMap. All the data is from there, and your answers and corrections are used all over.</p><p>Many people and apps already use OpenStreetMap: <a href='https://organicmaps.app/' target='_blank'>Organic Maps</a>, <a href='https://osmAnd.net' target='_blank'>OsmAnd</a>, but also the maps at Facebook, Instagram, Apple-maps and Bing-maps are (partly) powered by OpenStreetMap.</p>",
|
"openStreetMapIntro": "<h3>An Open Map</h3><p>One that everyone can use and edit freely. A single place to store all geo-info. Different, small, incompatible and outdated maps are not needed anywhere.</p><p><b><a href='https://OpenStreetMap.org' target='_blank'>OpenStreetMap</a></b> is not the enemy map. The map data can be used freely (with <a href='https://osm.org/copyright' target='_blank'>attribution and publication of changes to that data</a>). Everyone can add new data and fix errors. This website uses OpenStreetMap. All the data is from there, and your answers and corrections are used all over.</p><p>Many people and apps already use OpenStreetMap: <a href='https://organicmaps.app/' target='_blank'>Organic Maps</a>, <a href='https://osmAnd.net' target='_blank'>OsmAnd</a>, but also the maps at Facebook, Instagram, Apple-maps and Bing-maps are (partly) powered by OpenStreetMap.</p>",
|
||||||
"openTheMap": "Open the map",
|
"openTheMap": "Open the map",
|
||||||
|
"openTheMapAtGeolocation": "Zoom to your location",
|
||||||
"opening_hours": {
|
"opening_hours": {
|
||||||
"closed_permanently": "Closed for an unkown duration",
|
"closed_permanently": "Closed for an unkown duration",
|
||||||
"closed_until": "Closed until {date}",
|
"closed_until": "Closed until {date}",
|
||||||
|
@ -289,6 +292,7 @@
|
||||||
"error": "Something went wrong…",
|
"error": "Something went wrong…",
|
||||||
"nothing": "Nothing found…",
|
"nothing": "Nothing found…",
|
||||||
"search": "Search a location",
|
"search": "Search a location",
|
||||||
|
"searchShort": "Search…",
|
||||||
"searching": "Searching…"
|
"searching": "Searching…"
|
||||||
},
|
},
|
||||||
"sharescreen": {
|
"sharescreen": {
|
||||||
|
|
|
@ -149,7 +149,7 @@
|
||||||
"back": "Vorige",
|
"back": "Vorige",
|
||||||
"backToIndex": "Keer terug naar het overzicht met alle thematische kaarten",
|
"backToIndex": "Keer terug naar het overzicht met alle thematische kaarten",
|
||||||
"backToMapcomplete": "Terug naar het themaoverzicht",
|
"backToMapcomplete": "Terug naar het themaoverzicht",
|
||||||
"backgroundMap": "Achtergrondkaart",
|
"backgroundMap": "Selecteer een achtergrondlaag",
|
||||||
"cancel": "Annuleren",
|
"cancel": "Annuleren",
|
||||||
"confirm": "Bevestigen",
|
"confirm": "Bevestigen",
|
||||||
"customThemeIntro": "<h3>Onofficiële thema's</h3>De onderstaande thema's heb je eerder bezocht en zijn gemaakt door andere OpenStreetMappers.",
|
"customThemeIntro": "<h3>Onofficiële thema's</h3>De onderstaande thema's heb je eerder bezocht en zijn gemaakt door andere OpenStreetMappers.",
|
||||||
|
@ -221,6 +221,7 @@
|
||||||
"number": "getal",
|
"number": "getal",
|
||||||
"openStreetMapIntro": "<h3>Een open kaart</h3><p>Zou het niet fantastisch zijn als er een open kaart zou zijn die door iedereen aangepast én gebruikt kan worden? Een kaart waar iedereen zijn interesses aan zou kunnen toevoegen? Dan zouden er geen duizend-en-één verschillende kleine kaartjes, websites, ... meer nodig zijn</p><p><b><a href=\"https://OpenStreetMap.org\" target=\"_blank\">OpenStreetMap</a></b> is deze open kaart. Je mag de kaartdata gratis gebruiken (mits <a href=\"https://osm.org/copyright\" target=\"_blank\">bronvermelding en herpublicatie van aanpassingen</a>). Daarenboven mag je de kaart ook gratis aanpassen als je een account maakt. Ook deze website is gebaseerd op OpenStreetMap. Als je hier een vraag beantwoord, gaat het antwoord daar ook naartoe</p><p>Tenslotte zijn er reeds vele gebruikers van OpenStreetMap. Denk maar <a href=\"https://organicmaps.app//\" target=\"_blank\">Organic Maps</a>, <a href=\"https://osmAnd.net\" target=\"_blank\">OsmAnd</a>, verschillende gespecialiseerde routeplanners, de achtergrondkaarten op Facebook, Instagram,...<br/>;Zelfs Apple Maps en Bing-Maps gebruiken OpenStreetMap in hun kaarten!</p><p></p><p>Kortom, als je hier een punt toevoegd of een vraag beantwoord, zal dat na een tijdje ook in al dié applicaties te zien zijn.</p>",
|
"openStreetMapIntro": "<h3>Een open kaart</h3><p>Zou het niet fantastisch zijn als er een open kaart zou zijn die door iedereen aangepast én gebruikt kan worden? Een kaart waar iedereen zijn interesses aan zou kunnen toevoegen? Dan zouden er geen duizend-en-één verschillende kleine kaartjes, websites, ... meer nodig zijn</p><p><b><a href=\"https://OpenStreetMap.org\" target=\"_blank\">OpenStreetMap</a></b> is deze open kaart. Je mag de kaartdata gratis gebruiken (mits <a href=\"https://osm.org/copyright\" target=\"_blank\">bronvermelding en herpublicatie van aanpassingen</a>). Daarenboven mag je de kaart ook gratis aanpassen als je een account maakt. Ook deze website is gebaseerd op OpenStreetMap. Als je hier een vraag beantwoord, gaat het antwoord daar ook naartoe</p><p>Tenslotte zijn er reeds vele gebruikers van OpenStreetMap. Denk maar <a href=\"https://organicmaps.app//\" target=\"_blank\">Organic Maps</a>, <a href=\"https://osmAnd.net\" target=\"_blank\">OsmAnd</a>, verschillende gespecialiseerde routeplanners, de achtergrondkaarten op Facebook, Instagram,...<br/>;Zelfs Apple Maps en Bing-Maps gebruiken OpenStreetMap in hun kaarten!</p><p></p><p>Kortom, als je hier een punt toevoegd of een vraag beantwoord, zal dat na een tijdje ook in al dié applicaties te zien zijn.</p>",
|
||||||
"openTheMap": "Raadpleeg de kaart",
|
"openTheMap": "Raadpleeg de kaart",
|
||||||
|
"openTheMapAtGeolocation": "Ga naar jouw locatie",
|
||||||
"opening_hours": {
|
"opening_hours": {
|
||||||
"closed_permanently": "Gesloten voor onbepaalde tijd",
|
"closed_permanently": "Gesloten voor onbepaalde tijd",
|
||||||
"closed_until": "Gesloten - open op {date}",
|
"closed_until": "Gesloten - open op {date}",
|
||||||
|
@ -262,6 +263,7 @@
|
||||||
"error": "Niet gelukt…",
|
"error": "Niet gelukt…",
|
||||||
"nothing": "Niets gevonden…",
|
"nothing": "Niets gevonden…",
|
||||||
"search": "Zoek naar een locatie",
|
"search": "Zoek naar een locatie",
|
||||||
|
"searchShort": "Zoek…",
|
||||||
"searching": "Aan het zoeken…"
|
"searching": "Aan het zoeken…"
|
||||||
},
|
},
|
||||||
"sharescreen": {
|
"sharescreen": {
|
||||||
|
|
21
package-lock.json
generated
21
package-lock.json
generated
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "mapcomplete",
|
"name": "mapcomplete",
|
||||||
"version": "0.30.5",
|
"version": "0.30.6",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "mapcomplete",
|
"name": "mapcomplete",
|
||||||
"version": "0.30.5",
|
"version": "0.30.6",
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@onsvisual/svelte-maps": "^1.1.6",
|
"@onsvisual/svelte-maps": "^1.1.6",
|
||||||
|
@ -56,6 +56,7 @@
|
||||||
"xml2js": "^0.5.0"
|
"xml2js": "^0.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@babeard/svelte-heroicons": "^2.0.0-rc.0",
|
||||||
"@babel/polyfill": "^7.10.4",
|
"@babel/polyfill": "^7.10.4",
|
||||||
"@babel/preset-env": "7.13.8",
|
"@babel/preset-env": "7.13.8",
|
||||||
"@parcel/service-worker": "^2.6.0",
|
"@parcel/service-worker": "^2.6.0",
|
||||||
|
@ -106,6 +107,15 @@
|
||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babeard/svelte-heroicons": {
|
||||||
|
"version": "2.0.0-rc.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babeard/svelte-heroicons/-/svelte-heroicons-2.0.0-rc.0.tgz",
|
||||||
|
"integrity": "sha512-zjoRKBHA2QLjj3bi9mJMtyUyb/Y+PGlxiMdVXO3Bv8If3RJBuxtvVAYD2/tUakhxocB272PxhGNz4sixmB0rpA==",
|
||||||
|
"dev": true,
|
||||||
|
"peerDependencies": {
|
||||||
|
"svelte": "^3.44.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
"version": "7.18.6",
|
"version": "7.18.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
|
||||||
|
@ -12105,6 +12115,13 @@
|
||||||
"@jridgewell/trace-mapping": "^0.3.9"
|
"@jridgewell/trace-mapping": "^0.3.9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@babeard/svelte-heroicons": {
|
||||||
|
"version": "2.0.0-rc.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babeard/svelte-heroicons/-/svelte-heroicons-2.0.0-rc.0.tgz",
|
||||||
|
"integrity": "sha512-zjoRKBHA2QLjj3bi9mJMtyUyb/Y+PGlxiMdVXO3Bv8If3RJBuxtvVAYD2/tUakhxocB272PxhGNz4sixmB0rpA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"@babel/code-frame": {
|
"@babel/code-frame": {
|
||||||
"version": "7.18.6",
|
"version": "7.18.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
|
||||||
|
|
|
@ -108,6 +108,7 @@
|
||||||
"xml2js": "^0.5.0"
|
"xml2js": "^0.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@babeard/svelte-heroicons": "^2.0.0-rc.0",
|
||||||
"@babel/polyfill": "^7.10.4",
|
"@babel/polyfill": "^7.10.4",
|
||||||
"@babel/preset-env": "7.13.8",
|
"@babel/preset-env": "7.13.8",
|
||||||
"@parcel/service-worker": "^2.6.0",
|
"@parcel/service-worker": "^2.6.0",
|
||||||
|
|
|
@ -715,14 +715,14 @@ video {
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.right-0 {
|
|
||||||
right: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right-1\/3 {
|
.right-1\/3 {
|
||||||
right: 33.333333%;
|
right: 33.333333%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.right-0 {
|
||||||
|
right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
.right-10 {
|
.right-10 {
|
||||||
right: 2.5rem;
|
right: 2.5rem;
|
||||||
}
|
}
|
||||||
|
@ -1105,6 +1105,10 @@ video {
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.w-24 {
|
||||||
|
width: 6rem;
|
||||||
|
}
|
||||||
|
|
||||||
.w-4 {
|
.w-4 {
|
||||||
width: 1rem;
|
width: 1rem;
|
||||||
}
|
}
|
||||||
|
@ -1142,10 +1146,6 @@ video {
|
||||||
width: 24rem;
|
width: 24rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.w-24 {
|
|
||||||
width: 6rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.w-10 {
|
.w-10 {
|
||||||
width: 2.5rem;
|
width: 2.5rem;
|
||||||
}
|
}
|
||||||
|
@ -1245,6 +1245,14 @@ video {
|
||||||
resize: both;
|
resize: both;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.grid-cols-2 {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-cols-1 {
|
||||||
|
grid-template-columns: repeat(1, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
.flex-row {
|
.flex-row {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
@ -1297,6 +1305,10 @@ video {
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gap-2 {
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.gap-y-1 {
|
.gap-y-1 {
|
||||||
row-gap: 0.25rem;
|
row-gap: 0.25rem;
|
||||||
}
|
}
|
||||||
|
@ -1388,6 +1400,10 @@ video {
|
||||||
border-radius: 0.75rem;
|
border-radius: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rounded-2xl {
|
||||||
|
border-radius: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.rounded-lg {
|
.rounded-lg {
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
}
|
}
|
||||||
|
@ -1554,6 +1570,11 @@ video {
|
||||||
padding-right: 0.25rem;
|
padding-right: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.px-4 {
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.py-4 {
|
.py-4 {
|
||||||
padding-top: 1rem;
|
padding-top: 1rem;
|
||||||
padding-bottom: 1rem;
|
padding-bottom: 1rem;
|
||||||
|
@ -1569,11 +1590,6 @@ video {
|
||||||
padding-right: 0.5rem;
|
padding-right: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.px-4 {
|
|
||||||
padding-left: 1rem;
|
|
||||||
padding-right: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pb-12 {
|
.pb-12 {
|
||||||
padding-bottom: 3rem;
|
padding-bottom: 3rem;
|
||||||
}
|
}
|
||||||
|
@ -1722,6 +1738,11 @@ video {
|
||||||
letter-spacing: -0.025em;
|
letter-spacing: -0.025em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-white {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(255 255 255 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.text-gray-900 {
|
.text-gray-900 {
|
||||||
--tw-text-opacity: 1;
|
--tw-text-opacity: 1;
|
||||||
color: rgb(17 24 39 / var(--tw-text-opacity));
|
color: rgb(17 24 39 / var(--tw-text-opacity));
|
||||||
|
@ -1742,11 +1763,6 @@ video {
|
||||||
color: rgb(22 163 74 / var(--tw-text-opacity));
|
color: rgb(22 163 74 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-white {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.underline {
|
.underline {
|
||||||
text-decoration-line: underline;
|
text-decoration-line: underline;
|
||||||
}
|
}
|
||||||
|
@ -2270,6 +2286,10 @@ select:hover {
|
||||||
color: var(--background-color);
|
color: var(--background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bg-black-transparent {
|
||||||
|
background-color: #00000088;
|
||||||
|
}
|
||||||
|
|
||||||
.block-ruby {
|
.block-ruby {
|
||||||
display: block ruby;
|
display: block ruby;
|
||||||
}
|
}
|
||||||
|
@ -2289,6 +2309,10 @@ select:hover {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.no-weblate weblate {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.link-underline a {
|
.link-underline a {
|
||||||
-webkit-text-decoration: underline 1px var(--foreground-color);
|
-webkit-text-decoration: underline 1px var(--foreground-color);
|
||||||
text-decoration: underline 1px var(--foreground-color);
|
text-decoration: underline 1px var(--foreground-color);
|
||||||
|
|
Loading…
Reference in a new issue