forked from MapComplete/MapComplete
Chore: reformat all files with prettier
This commit is contained in:
parent
5757ae5dea
commit
d008dcb54d
214 changed files with 8926 additions and 8196 deletions
|
|
@ -1,80 +1,88 @@
|
|||
<script lang="ts">
|
||||
import {Store, UIEventSource} from "../../Logic/UIEventSource";
|
||||
import type {RasterLayerPolygon} from "../../Models/RasterLayers";
|
||||
import {AvailableRasterLayers} from "../../Models/RasterLayers";
|
||||
import {createEventDispatcher, onDestroy} from "svelte";
|
||||
import Svg from "../../Svg";
|
||||
import {Map as MlMap} from "maplibre-gl"
|
||||
import type {MapProperties} from "../../Models/MapProperties";
|
||||
import OverlayMap from "../Map/OverlayMap.svelte";
|
||||
import RasterLayerPicker from "../Map/RasterLayerPicker.svelte";
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import type { RasterLayerPolygon } from "../../Models/RasterLayers"
|
||||
import { AvailableRasterLayers } from "../../Models/RasterLayers"
|
||||
import { createEventDispatcher, onDestroy } from "svelte"
|
||||
import Svg from "../../Svg"
|
||||
import { Map as MlMap } from "maplibre-gl"
|
||||
import type { MapProperties } from "../../Models/MapProperties"
|
||||
import OverlayMap from "../Map/OverlayMap.svelte"
|
||||
import RasterLayerPicker from "../Map/RasterLayerPicker.svelte"
|
||||
|
||||
export let mapproperties: MapProperties
|
||||
export let normalMap: UIEventSource<MlMap>
|
||||
/**
|
||||
* The current background (raster) layer of the polygon.
|
||||
* This is undefined if a vector layer is used
|
||||
*/
|
||||
let rasterLayer: UIEventSource<RasterLayerPolygon | undefined> = mapproperties.rasterLayer
|
||||
let name = rasterLayer.data?.properties?.name
|
||||
let icon = Svg.satellite_svg()
|
||||
onDestroy(rasterLayer.addCallback(polygon => {
|
||||
name = polygon.properties?.name
|
||||
}))
|
||||
/**
|
||||
* The layers that this component can offer as a choice.
|
||||
*/
|
||||
export let availableRasterLayers: Store<RasterLayerPolygon[]>
|
||||
export let mapproperties: MapProperties
|
||||
export let normalMap: UIEventSource<MlMap>
|
||||
/**
|
||||
* The current background (raster) layer of the polygon.
|
||||
* This is undefined if a vector layer is used
|
||||
*/
|
||||
let rasterLayer: UIEventSource<RasterLayerPolygon | undefined> = mapproperties.rasterLayer
|
||||
let name = rasterLayer.data?.properties?.name
|
||||
let icon = Svg.satellite_svg()
|
||||
onDestroy(
|
||||
rasterLayer.addCallback((polygon) => {
|
||||
name = polygon.properties?.name
|
||||
})
|
||||
)
|
||||
/**
|
||||
* The layers that this component can offer as a choice.
|
||||
*/
|
||||
export let availableRasterLayers: Store<RasterLayerPolygon[]>
|
||||
|
||||
let raster0 = new UIEventSource<RasterLayerPolygon>(undefined)
|
||||
let raster0 = new UIEventSource<RasterLayerPolygon>(undefined)
|
||||
|
||||
let raster1 = new UIEventSource<RasterLayerPolygon>(undefined)
|
||||
let raster1 = new UIEventSource<RasterLayerPolygon>(undefined)
|
||||
|
||||
let currentLayer: RasterLayerPolygon
|
||||
let currentLayer: RasterLayerPolygon
|
||||
|
||||
function updatedAltLayer() {
|
||||
const available = availableRasterLayers.data
|
||||
const current = rasterLayer.data
|
||||
const defaultLayer = AvailableRasterLayers.maplibre
|
||||
const firstOther = available.find(l => l !== defaultLayer)
|
||||
const secondOther = available.find(l => l !== defaultLayer && l !== firstOther)
|
||||
raster0.setData(firstOther === current ? defaultLayer : firstOther)
|
||||
raster1.setData(secondOther === current ? defaultLayer : secondOther)
|
||||
function updatedAltLayer() {
|
||||
const available = availableRasterLayers.data
|
||||
const current = rasterLayer.data
|
||||
const defaultLayer = AvailableRasterLayers.maplibre
|
||||
const firstOther = available.find((l) => l !== defaultLayer)
|
||||
const secondOther = available.find((l) => l !== defaultLayer && l !== firstOther)
|
||||
raster0.setData(firstOther === current ? defaultLayer : firstOther)
|
||||
raster1.setData(secondOther === current ? defaultLayer : secondOther)
|
||||
}
|
||||
|
||||
updatedAltLayer()
|
||||
onDestroy(mapproperties.rasterLayer.addCallbackAndRunD(updatedAltLayer))
|
||||
onDestroy(availableRasterLayers.addCallbackAndRunD(updatedAltLayer))
|
||||
|
||||
function use(rasterLayer: UIEventSource<RasterLayerPolygon>): () => void {
|
||||
return () => {
|
||||
currentLayer = undefined
|
||||
mapproperties.rasterLayer.setData(rasterLayer.data)
|
||||
}
|
||||
}
|
||||
|
||||
updatedAltLayer()
|
||||
onDestroy(mapproperties.rasterLayer.addCallbackAndRunD(updatedAltLayer))
|
||||
onDestroy(availableRasterLayers.addCallbackAndRunD(updatedAltLayer))
|
||||
|
||||
function use(rasterLayer: UIEventSource<RasterLayerPolygon>): (() => void) {
|
||||
return () => {
|
||||
currentLayer = undefined
|
||||
mapproperties.rasterLayer.setData(rasterLayer.data)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const dispatch = createEventDispatcher<{ copyright_clicked }>()
|
||||
|
||||
const dispatch = createEventDispatcher<{ copyright_clicked }>()
|
||||
</script>
|
||||
|
||||
<div class="flex items-end opacity-50 hover:opacity-100">
|
||||
<div class="flex flex-col md:flex-row">
|
||||
<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 class="text-sm flex flex-col gap-y-1 h-fit ml-1">
|
||||
|
||||
<div class="low-interaction rounded p-1 w-64">
|
||||
<RasterLayerPicker availableLayers={availableRasterLayers} value={mapproperties.rasterLayer}></RasterLayerPicker>
|
||||
</div>
|
||||
|
||||
<button class="small" on:click={() => dispatch("copyright_clicked")}>
|
||||
© OpenStreetMap
|
||||
</button>
|
||||
<div class="flex flex-col md:flex-row">
|
||||
<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 class="text-sm flex flex-col gap-y-1 h-fit ml-1">
|
||||
<div class="low-interaction rounded p-1 w-64">
|
||||
<RasterLayerPicker
|
||||
availableLayers={availableRasterLayers}
|
||||
value={mapproperties.rasterLayer}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button class="small" on:click={() => dispatch("copyright_clicked")}>© OpenStreetMap</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,25 +1,25 @@
|
|||
import Combine from "../Base/Combine"
|
||||
import Translations from "../i18n/Translations"
|
||||
import {Store} from "../../Logic/UIEventSource"
|
||||
import {FixedUiElement} from "../Base/FixedUiElement"
|
||||
import { Store } from "../../Logic/UIEventSource"
|
||||
import { FixedUiElement } from "../Base/FixedUiElement"
|
||||
import licenses from "../../assets/generated/license_info.json"
|
||||
import SmallLicense from "../../Models/smallLicense"
|
||||
import {Utils} from "../../Utils"
|
||||
import { Utils } from "../../Utils"
|
||||
import Link from "../Base/Link"
|
||||
import {VariableUiElement} from "../Base/VariableUIElement"
|
||||
import { VariableUiElement } from "../Base/VariableUIElement"
|
||||
import contributors from "../../assets/contributors.json"
|
||||
import translators from "../../assets/translators.json"
|
||||
import BaseUIElement from "../BaseUIElement"
|
||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
|
||||
import Title from "../Base/Title"
|
||||
import {BBox} from "../../Logic/BBox"
|
||||
import {OsmConnection} from "../../Logic/Osm/OsmConnection"
|
||||
import { BBox } from "../../Logic/BBox"
|
||||
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
|
||||
import Constants from "../../Models/Constants"
|
||||
import ContributorCount from "../../Logic/ContributorCount"
|
||||
import Img from "../Base/Img"
|
||||
import {TypedTranslation} from "../i18n/Translation"
|
||||
import { TypedTranslation } from "../i18n/Translation"
|
||||
import GeoIndexedStore from "../../Logic/FeatureSource/Actors/GeoIndexedStore"
|
||||
import {RasterLayerPolygon} from "../../Models/RasterLayers";
|
||||
import { RasterLayerPolygon } from "../../Models/RasterLayers"
|
||||
|
||||
/**
|
||||
* The attribution panel in the theme menu.
|
||||
|
|
@ -29,7 +29,10 @@ export default class CopyrightPanel extends Combine {
|
|||
|
||||
constructor(state: {
|
||||
layout: LayoutConfig
|
||||
mapProperties: { readonly bounds: Store<BBox>, readonly rasterLayer: Store<RasterLayerPolygon> }
|
||||
mapProperties: {
|
||||
readonly bounds: Store<BBox>
|
||||
readonly rasterLayer: Store<RasterLayerPolygon>
|
||||
}
|
||||
osmConnection: OsmConnection
|
||||
dataIsLoading: Store<boolean>
|
||||
perLayer: ReadonlyMap<string, GeoIndexedStore>
|
||||
|
|
@ -90,27 +93,34 @@ export default class CopyrightPanel extends Combine {
|
|||
new Title(t.attributionTitle),
|
||||
t.attributionContent,
|
||||
|
||||
new VariableUiElement(state.mapProperties.rasterLayer.mapD(layer => {
|
||||
const props = layer.properties
|
||||
const attrUrl = props.attribution?.url
|
||||
const attrText = props.attribution?.text
|
||||
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)
|
||||
})),
|
||||
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,
|
||||
dataContributors,
|
||||
|
|
|
|||
|
|
@ -1,21 +1,21 @@
|
|||
import {UIElement} from "../UIElement"
|
||||
import { UIElement } from "../UIElement"
|
||||
import BaseUIElement from "../BaseUIElement"
|
||||
import {Store} from "../../Logic/UIEventSource"
|
||||
import { Store } from "../../Logic/UIEventSource"
|
||||
import ExtraLinkConfig from "../../Models/ThemeConfig/ExtraLinkConfig"
|
||||
import Img from "../Base/Img"
|
||||
import {SubtleButton} from "../Base/SubtleButton"
|
||||
import { SubtleButton } from "../Base/SubtleButton"
|
||||
import Toggle from "../Input/Toggle"
|
||||
import Locale from "../i18n/Locale"
|
||||
import {Utils} from "../../Utils"
|
||||
import { Utils } from "../../Utils"
|
||||
import Svg from "../../Svg"
|
||||
import Translations from "../i18n/Translations"
|
||||
import {Translation} from "../i18n/Translation"
|
||||
import { Translation } from "../i18n/Translation"
|
||||
|
||||
interface ExtraLinkButtonState {
|
||||
layout: { id: string; title: Translation }
|
||||
featureSwitches: { featureSwitchWelcomeMessage: Store<boolean> },
|
||||
featureSwitches: { featureSwitchWelcomeMessage: Store<boolean> }
|
||||
mapProperties: {
|
||||
location: Store<{ lon: number, lat: number }>;
|
||||
location: Store<{ lon: number; lat: number }>
|
||||
zoom: Store<number>
|
||||
}
|
||||
}
|
||||
|
|
@ -23,10 +23,7 @@ export default class ExtraLinkButton extends UIElement {
|
|||
private readonly _config: ExtraLinkConfig
|
||||
private readonly state: ExtraLinkButtonState
|
||||
|
||||
constructor(
|
||||
state: ExtraLinkButtonState,
|
||||
config: ExtraLinkConfig
|
||||
) {
|
||||
constructor(state: ExtraLinkButtonState, config: ExtraLinkConfig) {
|
||||
super()
|
||||
this.state = state
|
||||
this._config = config
|
||||
|
|
@ -45,21 +42,24 @@ export default class ExtraLinkButton extends UIElement {
|
|||
}
|
||||
|
||||
if (c.requirements?.has("no-iframe") && isIframe) {
|
||||
return undefined
|
||||
return undefined
|
||||
}
|
||||
|
||||
let link: BaseUIElement
|
||||
const theme = this.state.layout?.id ?? ""
|
||||
const basepath = window.location.host
|
||||
const href = this.state.mapProperties.location.map((loc) => {
|
||||
const subs = {
|
||||
...loc,
|
||||
theme: theme,
|
||||
basepath,
|
||||
language: Locale.language.data,
|
||||
}
|
||||
return Utils.SubstituteKeys(c.href, subs)
|
||||
}, [this.state.mapProperties.zoom])
|
||||
const href = this.state.mapProperties.location.map(
|
||||
(loc) => {
|
||||
const subs = {
|
||||
...loc,
|
||||
theme: theme,
|
||||
basepath,
|
||||
language: Locale.language.data,
|
||||
}
|
||||
return Utils.SubstituteKeys(c.href, subs)
|
||||
},
|
||||
[this.state.mapProperties.zoom]
|
||||
)
|
||||
|
||||
let img: BaseUIElement = Svg.pop_out_svg()
|
||||
if (c.icon !== undefined) {
|
||||
|
|
@ -81,11 +81,19 @@ export default class ExtraLinkButton extends UIElement {
|
|||
})
|
||||
|
||||
if (c.requirements?.has("no-welcome-message")) {
|
||||
link = new Toggle(undefined, link, this.state.featureSwitches.featureSwitchWelcomeMessage)
|
||||
link = new Toggle(
|
||||
undefined,
|
||||
link,
|
||||
this.state.featureSwitches.featureSwitchWelcomeMessage
|
||||
)
|
||||
}
|
||||
|
||||
if (c.requirements?.has("welcome-message")) {
|
||||
link = new Toggle(link, undefined, this.state.featureSwitches.featureSwitchWelcomeMessage)
|
||||
link = new Toggle(
|
||||
link,
|
||||
undefined,
|
||||
this.state.featureSwitches.featureSwitchWelcomeMessage
|
||||
)
|
||||
}
|
||||
|
||||
return link
|
||||
|
|
|
|||
|
|
@ -1,107 +1,110 @@
|
|||
<script lang="ts">/**
|
||||
* The FilterView shows the various options to enable/disable a single layer or to only show a subset of the data.
|
||||
*/
|
||||
import type FilteredLayer from "../../Models/FilteredLayer";
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
|
||||
import ToSvelte from "../Base/ToSvelte.svelte";
|
||||
import Checkbox from "../Base/Checkbox.svelte";
|
||||
import FilterConfig from "../../Models/ThemeConfig/FilterConfig";
|
||||
import type {Writable} from "svelte/store";
|
||||
import If from "../Base/If.svelte";
|
||||
import Dropdown from "../Base/Dropdown.svelte";
|
||||
import {onDestroy} from "svelte";
|
||||
import {ImmutableStore, Store} from "../../Logic/UIEventSource";
|
||||
import FilterviewWithFields from "./FilterviewWithFields.svelte";
|
||||
import Tr from "../Base/Tr.svelte";
|
||||
import Translations from "../i18n/Translations";
|
||||
<script lang="ts">
|
||||
/**
|
||||
* The FilterView shows the various options to enable/disable a single layer or to only show a subset of the data.
|
||||
*/
|
||||
import type FilteredLayer from "../../Models/FilteredLayer"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import ToSvelte from "../Base/ToSvelte.svelte"
|
||||
import Checkbox from "../Base/Checkbox.svelte"
|
||||
import FilterConfig from "../../Models/ThemeConfig/FilterConfig"
|
||||
import type { Writable } from "svelte/store"
|
||||
import If from "../Base/If.svelte"
|
||||
import Dropdown from "../Base/Dropdown.svelte"
|
||||
import { onDestroy } from "svelte"
|
||||
import { ImmutableStore, Store } from "../../Logic/UIEventSource"
|
||||
import FilterviewWithFields from "./FilterviewWithFields.svelte"
|
||||
import Tr from "../Base/Tr.svelte"
|
||||
import Translations from "../i18n/Translations"
|
||||
|
||||
export let filteredLayer: FilteredLayer;
|
||||
export let highlightedLayer: Store<string | undefined> = new ImmutableStore(undefined);
|
||||
export let zoomlevel: Store<number> = new ImmutableStore(22);
|
||||
let layer: LayerConfig = filteredLayer.layerDef;
|
||||
let isDisplayed: Store<boolean> = filteredLayer.isDisplayed;
|
||||
export let filteredLayer: FilteredLayer
|
||||
export let highlightedLayer: Store<string | undefined> = new ImmutableStore(undefined)
|
||||
export let zoomlevel: Store<number> = new ImmutableStore(22)
|
||||
let layer: LayerConfig = filteredLayer.layerDef
|
||||
let isDisplayed: Store<boolean> = filteredLayer.isDisplayed
|
||||
|
||||
/**
|
||||
* Gets a UIEventSource as boolean for the given option, to be used with a checkbox
|
||||
*/
|
||||
function getBooleanStateFor(option: FilterConfig): Writable<boolean> {
|
||||
const state = filteredLayer.appliedFilters.get(option.id);
|
||||
return state.sync(f => f === 0, [], (b) => b ? 0 : undefined);
|
||||
}
|
||||
/**
|
||||
* Gets a UIEventSource as boolean for the given option, to be used with a checkbox
|
||||
*/
|
||||
function getBooleanStateFor(option: FilterConfig): Writable<boolean> {
|
||||
const state = filteredLayer.appliedFilters.get(option.id)
|
||||
return state.sync(
|
||||
(f) => f === 0,
|
||||
[],
|
||||
(b) => (b ? 0 : undefined)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a UIEventSource as number for the given option, to be used with a dropdown or radiobutton
|
||||
*/
|
||||
function getStateFor(option: FilterConfig): Writable<number> {
|
||||
return filteredLayer.appliedFilters.get(option.id);
|
||||
}
|
||||
/**
|
||||
* Gets a UIEventSource as number for the given option, to be used with a dropdown or radiobutton
|
||||
*/
|
||||
function getStateFor(option: FilterConfig): Writable<number> {
|
||||
return filteredLayer.appliedFilters.get(option.id)
|
||||
}
|
||||
|
||||
let mainElem: HTMLElement;
|
||||
$: onDestroy(
|
||||
highlightedLayer.addCallbackAndRun(highlightedLayer => {
|
||||
if (highlightedLayer === filteredLayer.layerDef.id) {
|
||||
mainElem?.classList?.add("glowing-shadow");
|
||||
} else {
|
||||
mainElem?.classList?.remove("glowing-shadow");
|
||||
}
|
||||
let mainElem: HTMLElement
|
||||
$: onDestroy(
|
||||
highlightedLayer.addCallbackAndRun((highlightedLayer) => {
|
||||
if (highlightedLayer === filteredLayer.layerDef.id) {
|
||||
mainElem?.classList?.add("glowing-shadow")
|
||||
} else {
|
||||
mainElem?.classList?.remove("glowing-shadow")
|
||||
}
|
||||
})
|
||||
);
|
||||
)
|
||||
</script>
|
||||
|
||||
{#if filteredLayer.layerDef.name}
|
||||
<div bind:this={mainElem} class="mb-1.5">
|
||||
<label class="flex gap-1 no-image-background">
|
||||
<Checkbox selected={isDisplayed}/>
|
||||
<If condition={filteredLayer.isDisplayed}>
|
||||
<ToSvelte
|
||||
construct={() => layer.defaultIcon()?.SetClass("block h-6 w-6 no-image-background")}></ToSvelte>
|
||||
<ToSvelte slot="else"
|
||||
construct={() => layer.defaultIcon()?.SetClass("block h-6 w-6 no-image-background opacity-50")}></ToSvelte>
|
||||
</If>
|
||||
<div bind:this={mainElem} class="mb-1.5">
|
||||
<label class="flex gap-1 no-image-background">
|
||||
<Checkbox selected={isDisplayed} />
|
||||
<If condition={filteredLayer.isDisplayed}>
|
||||
<ToSvelte
|
||||
construct={() => layer.defaultIcon()?.SetClass("block h-6 w-6 no-image-background")}
|
||||
/>
|
||||
<ToSvelte
|
||||
slot="else"
|
||||
construct={() =>
|
||||
layer.defaultIcon()?.SetClass("block h-6 w-6 no-image-background opacity-50")}
|
||||
/>
|
||||
</If>
|
||||
|
||||
{filteredLayer.layerDef.name}
|
||||
{filteredLayer.layerDef.name}
|
||||
|
||||
{#if $zoomlevel < layer.minzoom}
|
||||
{#if $zoomlevel < layer.minzoom}
|
||||
<span class="alert">
|
||||
<Tr t={Translations.t.general.layerSelection.zoomInToSeeThisLayer}/>
|
||||
<Tr t={Translations.t.general.layerSelection.zoomInToSeeThisLayer} />
|
||||
</span>
|
||||
{/if}
|
||||
</label>
|
||||
|
||||
{#if $isDisplayed && filteredLayer.layerDef.filters?.length > 0}
|
||||
<div id="subfilters" class="flex flex-col gap-y-1 ml-4">
|
||||
{#each filteredLayer.layerDef.filters as filter}
|
||||
<div>
|
||||
<!-- There are three (and a half) modes of filters: a single checkbox, a radio button/dropdown or with searchable fields -->
|
||||
{#if filter.options.length === 1 && filter.options[0].fields.length === 0}
|
||||
<label>
|
||||
<Checkbox selected={getBooleanStateFor(filter)} />
|
||||
{filter.options[0].question}
|
||||
</label>
|
||||
{/if}
|
||||
|
||||
</label>
|
||||
{#if filter.options.length === 1 && filter.options[0].fields.length > 0}
|
||||
<FilterviewWithFields id={filter.id} {filteredLayer} option={filter.options[0]} />
|
||||
{/if}
|
||||
|
||||
{#if $isDisplayed && filteredLayer.layerDef.filters?.length > 0}
|
||||
<div id="subfilters" class="flex flex-col gap-y-1 ml-4">
|
||||
{#each filteredLayer.layerDef.filters as filter}
|
||||
<div>
|
||||
|
||||
<!-- There are three (and a half) modes of filters: a single checkbox, a radio button/dropdown or with searchable fields -->
|
||||
{#if filter.options.length === 1 && filter.options[0].fields.length === 0}
|
||||
<label>
|
||||
<Checkbox selected={getBooleanStateFor(filter)}/>
|
||||
{filter.options[0].question}
|
||||
</label>
|
||||
{/if}
|
||||
|
||||
{#if filter.options.length === 1 && filter.options[0].fields.length > 0}
|
||||
<FilterviewWithFields id={filter.id} filteredLayer={filteredLayer}
|
||||
option={filter.options[0]}></FilterviewWithFields>
|
||||
|
||||
{/if}
|
||||
|
||||
{#if filter.options.length > 1}
|
||||
<Dropdown value={getStateFor(filter)}>
|
||||
{#each filter.options as option, i}
|
||||
<option value={i}>
|
||||
{ option.question}
|
||||
</option>
|
||||
{/each}
|
||||
</Dropdown>
|
||||
{/if}
|
||||
|
||||
|
||||
</div>
|
||||
{#if filter.options.length > 1}
|
||||
<Dropdown value={getStateFor(filter)}>
|
||||
{#each filter.options as option, i}
|
||||
<option value={i}>
|
||||
{option.question}
|
||||
</option>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Dropdown>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,60 +1,61 @@
|
|||
<script lang="ts">
|
||||
import FilteredLayer from "../../Models/FilteredLayer";
|
||||
import type {FilterConfigOption} from "../../Models/ThemeConfig/FilterConfig";
|
||||
import Locale from "../i18n/Locale";
|
||||
import ValidatedInput from "../InputElement/ValidatedInput.svelte";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import {onDestroy} from "svelte";
|
||||
import {Utils} from "../../Utils";
|
||||
import FilteredLayer from "../../Models/FilteredLayer"
|
||||
import type { FilterConfigOption } from "../../Models/ThemeConfig/FilterConfig"
|
||||
import Locale from "../i18n/Locale"
|
||||
import ValidatedInput from "../InputElement/ValidatedInput.svelte"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { onDestroy } from "svelte"
|
||||
import { Utils } from "../../Utils"
|
||||
|
||||
export let filteredLayer: FilteredLayer;
|
||||
export let option: FilterConfigOption;
|
||||
export let id: string;
|
||||
let parts: ({ message: string } | { subs: string })[];
|
||||
let language = Locale.language;
|
||||
$: {
|
||||
const template = option.question.textFor($language)
|
||||
parts = Utils.splitIntoSubstitutionParts(template)
|
||||
}
|
||||
let fieldValues: Record<string, UIEventSource<string>> = {};
|
||||
let fieldTypes: Record<string, string> = {};
|
||||
let appliedFilter = <UIEventSource<string>>filteredLayer.appliedFilters.get(id);
|
||||
let initialState: Record<string, string> = JSON.parse(appliedFilter?.data ?? "{}");
|
||||
export let filteredLayer: FilteredLayer
|
||||
export let option: FilterConfigOption
|
||||
export let id: string
|
||||
let parts: ({ message: string } | { subs: string })[]
|
||||
let language = Locale.language
|
||||
$: {
|
||||
const template = option.question.textFor($language)
|
||||
parts = Utils.splitIntoSubstitutionParts(template)
|
||||
}
|
||||
let fieldValues: Record<string, UIEventSource<string>> = {}
|
||||
let fieldTypes: Record<string, string> = {}
|
||||
let appliedFilter = <UIEventSource<string>>filteredLayer.appliedFilters.get(id)
|
||||
let initialState: Record<string, string> = JSON.parse(appliedFilter?.data ?? "{}")
|
||||
|
||||
function setFields() {
|
||||
const properties: Record<string, string> = {};
|
||||
for (const key in fieldValues) {
|
||||
const v = fieldValues[key].data;
|
||||
if (v === undefined) {
|
||||
properties[key] = undefined;
|
||||
} else {
|
||||
properties[key] = v;
|
||||
}
|
||||
}
|
||||
appliedFilter?.setData(FilteredLayer.fieldsToString(properties));
|
||||
function setFields() {
|
||||
const properties: Record<string, string> = {}
|
||||
for (const key in fieldValues) {
|
||||
const v = fieldValues[key].data
|
||||
if (v === undefined) {
|
||||
properties[key] = undefined
|
||||
} else {
|
||||
properties[key] = v
|
||||
}
|
||||
}
|
||||
appliedFilter?.setData(FilteredLayer.fieldsToString(properties))
|
||||
}
|
||||
|
||||
for (const field of option.fields) {
|
||||
// A bit of cheating: the 'parts' will have '}' suffixed for fields
|
||||
const src = new UIEventSource<string>(initialState[field.name] ?? "");
|
||||
fieldTypes[field.name] = field.type;
|
||||
fieldValues[field.name] = src;
|
||||
onDestroy(src.stabilized(200).addCallback(() => {
|
||||
setFields();
|
||||
}));
|
||||
}
|
||||
|
||||
for (const field of option.fields) {
|
||||
// A bit of cheating: the 'parts' will have '}' suffixed for fields
|
||||
const src = new UIEventSource<string>(initialState[field.name] ?? "")
|
||||
fieldTypes[field.name] = field.type
|
||||
fieldValues[field.name] = src
|
||||
onDestroy(
|
||||
src.stabilized(200).addCallback(() => {
|
||||
setFields()
|
||||
})
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
{#each parts as part, i}
|
||||
{#if part.subs}
|
||||
<!-- This is a field! -->
|
||||
<span class="mx-1">
|
||||
<ValidatedInput value={fieldValues[part.subs]} type={fieldTypes[part.subs]}/>
|
||||
</span>
|
||||
{:else}
|
||||
{part.message}
|
||||
{/if}
|
||||
{/each}
|
||||
{#each parts as part, i}
|
||||
{#if part.subs}
|
||||
<!-- This is a field! -->
|
||||
<span class="mx-1">
|
||||
<ValidatedInput value={fieldValues[part.subs]} type={fieldTypes[part.subs]} />
|
||||
</span>
|
||||
{:else}
|
||||
{part.message}
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,119 +1,116 @@
|
|||
<script lang="ts">
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import type { Feature } from "geojson"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import ToSvelte from "../Base/ToSvelte.svelte"
|
||||
import Svg from "../../Svg.js"
|
||||
import Translations from "../i18n/Translations"
|
||||
import Loading from "../Base/Loading.svelte"
|
||||
import Hotkeys from "../Base/Hotkeys"
|
||||
import { Geocoding } from "../../Logic/Osm/Geocoding"
|
||||
import { BBox } from "../../Logic/BBox"
|
||||
import { GeoIndexedStoreForLayer } from "../../Logic/FeatureSource/Actors/GeoIndexedStore"
|
||||
import { createEventDispatcher, onDestroy } from "svelte"
|
||||
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import type {Feature} from "geojson";
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
|
||||
import ToSvelte from "../Base/ToSvelte.svelte";
|
||||
import Svg from "../../Svg.js";
|
||||
import Translations from "../i18n/Translations";
|
||||
import Loading from "../Base/Loading.svelte";
|
||||
import Hotkeys from "../Base/Hotkeys";
|
||||
import {Geocoding} from "../../Logic/Osm/Geocoding";
|
||||
import {BBox} from "../../Logic/BBox";
|
||||
import {GeoIndexedStoreForLayer} from "../../Logic/FeatureSource/Actors/GeoIndexedStore";
|
||||
import {createEventDispatcher, onDestroy} from "svelte";
|
||||
export let perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer> | undefined = undefined
|
||||
export let bounds: UIEventSource<BBox>
|
||||
export let selectedElement: UIEventSource<Feature> | undefined = undefined
|
||||
export let selectedLayer: UIEventSource<LayerConfig> | undefined = undefined
|
||||
|
||||
export let perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer> | undefined = undefined;
|
||||
export let bounds: UIEventSource<BBox>;
|
||||
export let selectedElement: UIEventSource<Feature> | undefined = undefined;
|
||||
export let selectedLayer: UIEventSource<LayerConfig> | undefined = undefined;
|
||||
|
||||
export let clearAfterView: boolean = true
|
||||
export let clearAfterView: boolean = true
|
||||
|
||||
let searchContents: string = ""
|
||||
export let triggerSearch: UIEventSource<any> = new UIEventSource<any>(undefined)
|
||||
onDestroy(triggerSearch.addCallback(_ => {
|
||||
performSearch()
|
||||
}))
|
||||
let searchContents: string = ""
|
||||
export let triggerSearch: UIEventSource<any> = new UIEventSource<any>(undefined)
|
||||
onDestroy(
|
||||
triggerSearch.addCallback((_) => {
|
||||
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, searchIsValid: boolean }>()
|
||||
$: {
|
||||
if (!searchContents?.trim()) {
|
||||
dispatch("searchIsValid", false)
|
||||
}else{
|
||||
dispatch("searchIsValid", true)
|
||||
}
|
||||
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() ?? ""
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
}
|
||||
if(clearAfterView){
|
||||
searchContents = ""
|
||||
}
|
||||
dispatch("searchIsValid", false)
|
||||
dispatch("searchCompleted")
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
feedback = Translations.t.general.search.error.txt;
|
||||
} finally {
|
||||
isRunning = false;
|
||||
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)
|
||||
}
|
||||
}
|
||||
if (clearAfterView) {
|
||||
searchContents = ""
|
||||
}
|
||||
dispatch("searchIsValid", false)
|
||||
dispatch("searchCompleted")
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
feedback = Translations.t.general.search.error.txt
|
||||
} finally {
|
||||
isRunning = false
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex normal-background rounded-full pl-2 justify-between">
|
||||
<form class="w-full">
|
||||
|
||||
{#if isRunning}
|
||||
<Loading>{Translations.t.general.search.searching}</Loading>
|
||||
{:else if feedback !== undefined}
|
||||
<div class="alert" on:click={() => feedback = undefined}>
|
||||
{feedback}
|
||||
</div>
|
||||
{:else }
|
||||
<input
|
||||
type="search"
|
||||
class="w-full"
|
||||
bind:this={inputElement}
|
||||
on:keypress={keypr => keypr.key === "Enter" ? performSearch() : undefined}
|
||||
|
||||
bind:value={searchContents}
|
||||
placeholder={Translations.t.general.search.search}>
|
||||
{/if}
|
||||
|
||||
</form>
|
||||
<div class="w-6 h-6 self-end" on:click={performSearch}>
|
||||
<ToSvelte construct={Svg.search_svg}></ToSvelte>
|
||||
</div>
|
||||
<form class="w-full">
|
||||
{#if isRunning}
|
||||
<Loading>{Translations.t.general.search.searching}</Loading>
|
||||
{:else if feedback !== undefined}
|
||||
<div class="alert" on:click={() => (feedback = undefined)}>
|
||||
{feedback}
|
||||
</div>
|
||||
{:else}
|
||||
<input
|
||||
type="search"
|
||||
class="w-full"
|
||||
bind:this={inputElement}
|
||||
on:keypress={(keypr) => (keypr.key === "Enter" ? performSearch() : undefined)}
|
||||
bind:value={searchContents}
|
||||
placeholder={Translations.t.general.search.search}
|
||||
/>
|
||||
{/if}
|
||||
</form>
|
||||
<div class="w-6 h-6 self-end" on:click={performSearch}>
|
||||
<ToSvelte construct={Svg.search_svg} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,53 +1,50 @@
|
|||
<script lang="ts">
|
||||
import {OsmConnection} from "../../Logic/Osm/OsmConnection"
|
||||
import {UIEventSource} from "../../Logic/UIEventSource"
|
||||
import * as themeOverview from "../../assets/generated/theme_overview.json"
|
||||
import {Utils} from "../../Utils"
|
||||
import ThemesList from "./ThemesList.svelte"
|
||||
import Translations from "../i18n/Translations"
|
||||
import {LayoutInformation} from "../../Models/ThemeConfig/LayoutConfig"
|
||||
import LoginToggle from "../Base/LoginToggle.svelte";
|
||||
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import * as themeOverview from "../../assets/generated/theme_overview.json"
|
||||
import { Utils } from "../../Utils"
|
||||
import ThemesList from "./ThemesList.svelte"
|
||||
import Translations from "../i18n/Translations"
|
||||
import { LayoutInformation } from "../../Models/ThemeConfig/LayoutConfig"
|
||||
import LoginToggle from "../Base/LoginToggle.svelte"
|
||||
|
||||
export let search: UIEventSource<string>
|
||||
export let state: { osmConnection: OsmConnection }
|
||||
export let onMainScreen: boolean = true
|
||||
export let search: UIEventSource<string>
|
||||
export let state: { osmConnection: OsmConnection }
|
||||
export let onMainScreen: boolean = true
|
||||
|
||||
const prefix = "mapcomplete-hidden-theme-"
|
||||
const hiddenThemes: LayoutInformation[] = (themeOverview["default"] ?? themeOverview)?.filter(
|
||||
(layout) => layout.hideFromOverview
|
||||
) ?? []
|
||||
const userPreferences = state.osmConnection.preferencesHandler.preferences
|
||||
const t = Translations.t.general.morescreen
|
||||
const prefix = "mapcomplete-hidden-theme-"
|
||||
const hiddenThemes: LayoutInformation[] =
|
||||
(themeOverview["default"] ?? themeOverview)?.filter((layout) => layout.hideFromOverview) ?? []
|
||||
const userPreferences = state.osmConnection.preferencesHandler.preferences
|
||||
const t = Translations.t.general.morescreen
|
||||
|
||||
let knownThemesId: string[]
|
||||
$: knownThemesId = Utils.NoNull(
|
||||
Object.keys($userPreferences)
|
||||
.filter((key) => key.startsWith(prefix))
|
||||
.map((key) => key.substring(prefix.length, key.length - "-enabled".length))
|
||||
)
|
||||
$: console.log("Known theme ids:", knownThemesId)
|
||||
$: knownThemes = hiddenThemes.filter((theme) => knownThemesId.includes(theme.id))
|
||||
let knownThemesId: string[]
|
||||
$: knownThemesId = Utils.NoNull(
|
||||
Object.keys($userPreferences)
|
||||
.filter((key) => key.startsWith(prefix))
|
||||
.map((key) => key.substring(prefix.length, key.length - "-enabled".length))
|
||||
)
|
||||
$: console.log("Known theme ids:", knownThemesId)
|
||||
$: knownThemes = hiddenThemes.filter((theme) => knownThemesId.includes(theme.id))
|
||||
</script>
|
||||
|
||||
<LoginToggle {state}>
|
||||
|
||||
<ThemesList
|
||||
hideThemes={false}
|
||||
isCustom={false}
|
||||
{onMainScreen}
|
||||
{search}
|
||||
{state}
|
||||
themes={knownThemes}
|
||||
>
|
||||
<svelte:fragment slot="title">
|
||||
<h3>{t.previouslyHiddenTitle.toString()}</h3>
|
||||
<p>
|
||||
{t.hiddenExplanation.Subs({
|
||||
hidden_discovered: knownThemes.length.toString(),
|
||||
total_hidden: hiddenThemes.length.toString(),
|
||||
})}
|
||||
</p>
|
||||
</svelte:fragment>
|
||||
</ThemesList>
|
||||
|
||||
<ThemesList
|
||||
hideThemes={false}
|
||||
isCustom={false}
|
||||
{onMainScreen}
|
||||
{search}
|
||||
{state}
|
||||
themes={knownThemes}
|
||||
>
|
||||
<svelte:fragment slot="title">
|
||||
<h3>{t.previouslyHiddenTitle.toString()}</h3>
|
||||
<p>
|
||||
{t.hiddenExplanation.Subs({
|
||||
hidden_discovered: knownThemes.length.toString(),
|
||||
total_hidden: hiddenThemes.length.toString(),
|
||||
})}
|
||||
</p>
|
||||
</svelte:fragment>
|
||||
</ThemesList>
|
||||
</LoginToggle>
|
||||
|
|
|
|||
|
|
@ -2,28 +2,31 @@
|
|||
/**
|
||||
* Shows a 'floorSelector' and maps the selected floor onto a global filter
|
||||
*/
|
||||
import LayerState from "../../Logic/State/LayerState";
|
||||
import FloorSelector from "../InputElement/Helpers/FloorSelector.svelte";
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource";
|
||||
import LayerState from "../../Logic/State/LayerState"
|
||||
import FloorSelector from "../InputElement/Helpers/FloorSelector.svelte"
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
|
||||
export let layerState: LayerState;
|
||||
export let floors: Store<string[]>;
|
||||
export let zoom: Store<number>;
|
||||
export let layerState: LayerState
|
||||
export let floors: Store<string[]>
|
||||
export let zoom: Store<number>
|
||||
const maxZoom = 16
|
||||
|
||||
let selectedFloor: UIEventSource<string> = new UIEventSource<string>(undefined);
|
||||
|
||||
selectedFloor.stabilized(5).map(floor => {
|
||||
if(floors.data === undefined || floors.data.length <= 1 || zoom.data < maxZoom){
|
||||
// Only a single floor is visible -> disable the 'level' global filter
|
||||
// OR we might have zoomed out to much ant want to show all
|
||||
layerState.setLevelFilter(undefined)
|
||||
}else{
|
||||
layerState.setLevelFilter(floor)
|
||||
}
|
||||
}, [floors, zoom])
|
||||
|
||||
let selectedFloor: UIEventSource<string> = new UIEventSource<string>(undefined)
|
||||
|
||||
selectedFloor.stabilized(5).map(
|
||||
(floor) => {
|
||||
if (floors.data === undefined || floors.data.length <= 1 || zoom.data < maxZoom) {
|
||||
// Only a single floor is visible -> disable the 'level' global filter
|
||||
// OR we might have zoomed out to much ant want to show all
|
||||
layerState.setLevelFilter(undefined)
|
||||
} else {
|
||||
layerState.setLevelFilter(floor)
|
||||
}
|
||||
},
|
||||
[floors, zoom]
|
||||
)
|
||||
</script>
|
||||
|
||||
{#if $zoom >= maxZoom}
|
||||
<FloorSelector {floors} value={selectedFloor} />
|
||||
{/if}
|
||||
<FloorSelector {floors} value={selectedFloor} />
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -1,31 +1,29 @@
|
|||
<script lang="ts">
|
||||
import Translations from "../i18n/Translations"
|
||||
import Svg from "../../Svg"
|
||||
import { Store } from "../../Logic/UIEventSource"
|
||||
import Tr from "../Base/Tr.svelte"
|
||||
import ToSvelte from "../Base/ToSvelte.svelte"
|
||||
|
||||
import Translations from "../i18n/Translations"
|
||||
import Svg from "../../Svg"
|
||||
import {Store} from "../../Logic/UIEventSource";
|
||||
import Tr from "../Base/Tr.svelte";
|
||||
import ToSvelte from "../Base/ToSvelte.svelte";
|
||||
|
||||
/*
|
||||
/*
|
||||
A subtleButton which opens mapillary in a new tab at the current location
|
||||
*/
|
||||
|
||||
export let mapProperties: {
|
||||
readonly zoom: Store<number>,
|
||||
readonly location: Store<{ lon: number, lat: number }>
|
||||
}
|
||||
let location = mapProperties.location
|
||||
let zoom = mapProperties.zoom
|
||||
let mapillaryLink = `https://www.mapillary.com/app/?focus=map&lat=${
|
||||
$location?.lat ?? 0
|
||||
}&lng=${$location?.lon ?? 0}&z=${Math.max(($zoom ?? 2) - 1, 1)}`
|
||||
|
||||
export let mapProperties: {
|
||||
readonly zoom: Store<number>
|
||||
readonly location: Store<{ lon: number; lat: number }>
|
||||
}
|
||||
let location = mapProperties.location
|
||||
let zoom = mapProperties.zoom
|
||||
let mapillaryLink = `https://www.mapillary.com/app/?focus=map&lat=${$location?.lat ?? 0}&lng=${
|
||||
$location?.lon ?? 0
|
||||
}&z=${Math.max(($zoom ?? 2) - 1, 1)}`
|
||||
</script>
|
||||
|
||||
<a class="flex button items-center" href={mapillaryLink} target="_blank">
|
||||
<ToSvelte construct={() =>Svg.mapillary_black_svg().SetClass("w-12 h-12 m-2 mr-4 shrink-0")}/>
|
||||
<div class="flex flex-col">
|
||||
<Tr t={Translations.t.general.attribution.openMapillary}/>
|
||||
<Tr cls="subtle" t={ Translations.t.general.attribution.mapillaryHelp}/>
|
||||
</div>
|
||||
<ToSvelte construct={() => Svg.mapillary_black_svg().SetClass("w-12 h-12 m-2 mr-4 shrink-0")} />
|
||||
<div class="flex flex-col">
|
||||
<Tr t={Translations.t.general.attribution.openMapillary} />
|
||||
<Tr cls="subtle" t={Translations.t.general.attribution.mapillaryHelp} />
|
||||
</div>
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import Svg from "../../Svg"
|
||||
import Combine from "../Base/Combine"
|
||||
import Translations from "../i18n/Translations"
|
||||
import LayoutConfig, {LayoutInformation} from "../../Models/ThemeConfig/LayoutConfig"
|
||||
import {ImmutableStore, Store} from "../../Logic/UIEventSource"
|
||||
import LayoutConfig, { LayoutInformation } from "../../Models/ThemeConfig/LayoutConfig"
|
||||
import { ImmutableStore, Store } from "../../Logic/UIEventSource"
|
||||
import UserRelatedState from "../../Logic/State/UserRelatedState"
|
||||
import {Utils} from "../../Utils"
|
||||
import { Utils } from "../../Utils"
|
||||
import themeOverview from "../../assets/generated/theme_overview.json"
|
||||
import {TextField} from "../Input/TextField"
|
||||
import { TextField } from "../Input/TextField"
|
||||
import Locale from "../i18n/Locale"
|
||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||
import ThemesList from "./ThemesList.svelte"
|
||||
|
|
@ -29,7 +29,7 @@ export default class MoreScreen extends Combine {
|
|||
})
|
||||
search.enterPressed.addCallbackD((searchTerm) => {
|
||||
searchTerm = searchTerm.toLowerCase()
|
||||
if(!searchTerm){
|
||||
if (!searchTerm) {
|
||||
return
|
||||
}
|
||||
if (searchTerm === "personal") {
|
||||
|
|
|
|||
|
|
@ -1,108 +1,113 @@
|
|||
<script lang="ts">
|
||||
import type {SpecialVisualizationState} from "../SpecialVisualization";
|
||||
import LocationInput from "../InputElement/Helpers/LocationInput.svelte";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import {Tiles} from "../../Models/TileRange";
|
||||
import {Map as MlMap} from "maplibre-gl";
|
||||
import {BBox} from "../../Logic/BBox";
|
||||
import type {MapProperties} from "../../Models/MapProperties";
|
||||
import ShowDataLayer from "../Map/ShowDataLayer";
|
||||
import type {FeatureSource, FeatureSourceForLayer} from "../../Logic/FeatureSource/FeatureSource";
|
||||
import SnappingFeatureSource from "../../Logic/FeatureSource/Sources/SnappingFeatureSource";
|
||||
import FeatureSourceMerger from "../../Logic/FeatureSource/Sources/FeatureSourceMerger";
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
|
||||
import {Utils} from "../../Utils";
|
||||
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||
import LocationInput from "../InputElement/Helpers/LocationInput.svelte"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { Tiles } from "../../Models/TileRange"
|
||||
import { Map as MlMap } from "maplibre-gl"
|
||||
import { BBox } from "../../Logic/BBox"
|
||||
import type { MapProperties } from "../../Models/MapProperties"
|
||||
import ShowDataLayer from "../Map/ShowDataLayer"
|
||||
import type {
|
||||
FeatureSource,
|
||||
FeatureSourceForLayer,
|
||||
} from "../../Logic/FeatureSource/FeatureSource"
|
||||
import SnappingFeatureSource from "../../Logic/FeatureSource/Sources/SnappingFeatureSource"
|
||||
import FeatureSourceMerger from "../../Logic/FeatureSource/Sources/FeatureSourceMerger"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import { Utils } from "../../Utils"
|
||||
|
||||
/**
|
||||
* An advanced location input, which has support to:
|
||||
* - Show more layers
|
||||
* - Snap to layers
|
||||
*
|
||||
* This one is mostly used to insert new points, including when importing
|
||||
*/
|
||||
export let state: SpecialVisualizationState;
|
||||
/**
|
||||
* The start coordinate
|
||||
*/
|
||||
export let coordinate: { lon: number, lat: number };
|
||||
export let snapToLayers: string[] | undefined;
|
||||
export let targetLayer: LayerConfig;
|
||||
export let maxSnapDistance: number = undefined;
|
||||
/**
|
||||
* An advanced location input, which has support to:
|
||||
* - Show more layers
|
||||
* - Snap to layers
|
||||
*
|
||||
* This one is mostly used to insert new points, including when importing
|
||||
*/
|
||||
export let state: SpecialVisualizationState
|
||||
/**
|
||||
* The start coordinate
|
||||
*/
|
||||
export let coordinate: { lon: number; lat: number }
|
||||
export let snapToLayers: string[] | undefined
|
||||
export let targetLayer: LayerConfig
|
||||
export let maxSnapDistance: number = undefined
|
||||
|
||||
export let snappedTo: UIEventSource<string | undefined>;
|
||||
|
||||
export let value: UIEventSource<{ lon: number, lat: number }>;
|
||||
if (value.data === undefined) {
|
||||
value.setData(coordinate);
|
||||
export let snappedTo: UIEventSource<string | undefined>
|
||||
|
||||
export let value: UIEventSource<{ lon: number; lat: number }>
|
||||
if (value.data === undefined) {
|
||||
value.setData(coordinate)
|
||||
}
|
||||
|
||||
let preciseLocation: UIEventSource<{ lon: number; lat: number }> = new UIEventSource<{
|
||||
lon: number
|
||||
lat: number
|
||||
}>(undefined)
|
||||
|
||||
const xyz = Tiles.embedded_tile(coordinate.lat, coordinate.lon, 16)
|
||||
const map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined)
|
||||
let initialMapProperties: Partial<MapProperties> = {
|
||||
zoom: new UIEventSource<number>(19),
|
||||
maxbounds: new UIEventSource(undefined),
|
||||
/*If no snapping needed: the value is simply the map location;
|
||||
* If snapping is needed: the value will be set later on by the snapping feature source
|
||||
* */
|
||||
location:
|
||||
snapToLayers?.length > 0
|
||||
? new UIEventSource<{ lon: number; lat: number }>(coordinate)
|
||||
: value,
|
||||
bounds: new UIEventSource<BBox>(undefined),
|
||||
allowMoving: new UIEventSource<boolean>(true),
|
||||
allowZooming: new UIEventSource<boolean>(true),
|
||||
minzoom: new UIEventSource<number>(18),
|
||||
rasterLayer: UIEventSource.feedFrom(state.mapProperties.rasterLayer),
|
||||
}
|
||||
|
||||
const featuresForLayer = state.perLayer.get(targetLayer.id)
|
||||
if (featuresForLayer) {
|
||||
new ShowDataLayer(map, {
|
||||
layer: targetLayer,
|
||||
features: featuresForLayer,
|
||||
})
|
||||
}
|
||||
|
||||
if (snapToLayers?.length > 0) {
|
||||
const snapSources: FeatureSource[] = []
|
||||
for (const layerId of snapToLayers ?? []) {
|
||||
const layer: FeatureSourceForLayer = state.perLayer.get(layerId)
|
||||
snapSources.push(layer)
|
||||
if (layer.features === undefined) {
|
||||
continue
|
||||
}
|
||||
new ShowDataLayer(map, {
|
||||
layer: layer.layer.layerDef,
|
||||
zoomToFeatures: false,
|
||||
features: layer,
|
||||
})
|
||||
}
|
||||
const snappedLocation = new SnappingFeatureSource(
|
||||
new FeatureSourceMerger(...Utils.NoNull(snapSources)),
|
||||
// We snap to the (constantly updating) map location
|
||||
initialMapProperties.location,
|
||||
{
|
||||
maxDistance: maxSnapDistance ?? 15,
|
||||
allowUnsnapped: true,
|
||||
snappedTo,
|
||||
snapLocation: value,
|
||||
}
|
||||
)
|
||||
|
||||
let preciseLocation: UIEventSource<{ lon: number, lat: number }> = new UIEventSource<{
|
||||
lon: number;
|
||||
lat: number
|
||||
}>(undefined);
|
||||
|
||||
const xyz = Tiles.embedded_tile(coordinate.lat, coordinate.lon, 16);
|
||||
const map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined);
|
||||
let initialMapProperties: Partial<MapProperties> = {
|
||||
zoom: new UIEventSource<number>(19),
|
||||
maxbounds: new UIEventSource(undefined),
|
||||
/*If no snapping needed: the value is simply the map location;
|
||||
* If snapping is needed: the value will be set later on by the snapping feature source
|
||||
* */
|
||||
location: snapToLayers?.length > 0 ? new UIEventSource<{ lon: number; lat: number }>(coordinate) : value,
|
||||
bounds: new UIEventSource<BBox>(undefined),
|
||||
allowMoving: new UIEventSource<boolean>(true),
|
||||
allowZooming: new UIEventSource<boolean>(true),
|
||||
minzoom: new UIEventSource<number>(18),
|
||||
rasterLayer: UIEventSource.feedFrom(state.mapProperties.rasterLayer)
|
||||
};
|
||||
|
||||
|
||||
const featuresForLayer = state.perLayer.get(targetLayer.id)
|
||||
if(featuresForLayer){
|
||||
new ShowDataLayer(map, {
|
||||
layer: targetLayer,
|
||||
features: featuresForLayer
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
if (snapToLayers?.length > 0) {
|
||||
|
||||
const snapSources: FeatureSource[] = [];
|
||||
for (const layerId of (snapToLayers ?? [])) {
|
||||
const layer: FeatureSourceForLayer = state.perLayer.get(layerId);
|
||||
snapSources.push(layer);
|
||||
if (layer.features === undefined) {
|
||||
continue;
|
||||
}
|
||||
new ShowDataLayer(map, {
|
||||
layer: layer.layer.layerDef,
|
||||
zoomToFeatures: false,
|
||||
features: layer
|
||||
});
|
||||
}
|
||||
const snappedLocation = new SnappingFeatureSource(
|
||||
new FeatureSourceMerger(...Utils.NoNull(snapSources)),
|
||||
// We snap to the (constantly updating) map location
|
||||
initialMapProperties.location,
|
||||
{
|
||||
maxDistance: maxSnapDistance ?? 15,
|
||||
allowUnsnapped: true,
|
||||
snappedTo,
|
||||
snapLocation: value
|
||||
}
|
||||
);
|
||||
|
||||
new ShowDataLayer(map, {
|
||||
layer: targetLayer,
|
||||
features: snappedLocation
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
new ShowDataLayer(map, {
|
||||
layer: targetLayer,
|
||||
features: snappedLocation,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<LocationInput {map} mapProperties={initialMapProperties}
|
||||
value={preciseLocation} initialCoordinate={coordinate} maxDistanceInMeters=50 />
|
||||
<LocationInput
|
||||
{map}
|
||||
mapProperties={initialMapProperties}
|
||||
value={preciseLocation}
|
||||
initialCoordinate={coordinate}
|
||||
maxDistanceInMeters="50"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,35 +1,34 @@
|
|||
<script context="module" lang="ts">
|
||||
export interface Theme {
|
||||
id: string
|
||||
icon: string
|
||||
title: any
|
||||
shortDescription: any
|
||||
definition?: any
|
||||
mustHaveLanguage?: boolean
|
||||
hideFromOverview: boolean
|
||||
keywords?: any[]
|
||||
}
|
||||
export interface Theme {
|
||||
id: string
|
||||
icon: string
|
||||
title: any
|
||||
shortDescription: any
|
||||
definition?: any
|
||||
mustHaveLanguage?: boolean
|
||||
hideFromOverview: boolean
|
||||
keywords?: any[]
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import {UIEventSource} from "../../Logic/UIEventSource"
|
||||
import Svg from "../../Svg"
|
||||
import ToSvelte from "../Base/ToSvelte.svelte"
|
||||
import Translations from "../i18n/Translations"
|
||||
import Tr from "../Base/Tr.svelte";
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import Svg from "../../Svg"
|
||||
import ToSvelte from "../Base/ToSvelte.svelte"
|
||||
import Translations from "../i18n/Translations"
|
||||
import Tr from "../Base/Tr.svelte"
|
||||
|
||||
export let search: UIEventSource<string>
|
||||
export let search: UIEventSource<string>
|
||||
|
||||
const t = Translations.t.general.morescreen
|
||||
const t = Translations.t.general.morescreen
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
<h5>{t.noMatchingThemes.toString()}</h5>
|
||||
<div class="flex justify-center">
|
||||
|
||||
<button on:click={() => search.setData("")}>
|
||||
<ToSvelte construct={Svg.search_disable_svg().SetClass("w-6 mr-2")}/>
|
||||
<Tr slot="message" t={t.noSearch}/>
|
||||
</button>
|
||||
</div>
|
||||
<h5>{t.noMatchingThemes.toString()}</h5>
|
||||
<div class="flex justify-center">
|
||||
<button on:click={() => search.setData("")}>
|
||||
<ToSvelte construct={Svg.search_disable_svg().SetClass("w-6 mr-2")} />
|
||||
<Tr slot="message" t={t.noSearch} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
<script lang="ts">
|
||||
import { Square3Stack3dIcon } from "@babeard/svelte-heroicons/solid"
|
||||
import MapControlButton from "../Base/MapControlButton.svelte"
|
||||
import ThemeViewState from "../../Models/ThemeViewState"
|
||||
|
||||
import {Square3Stack3dIcon} from "@babeard/svelte-heroicons/solid";
|
||||
import MapControlButton from "../Base/MapControlButton.svelte";
|
||||
import ThemeViewState from "../../Models/ThemeViewState";
|
||||
|
||||
export let state: ThemeViewState
|
||||
export let state: ThemeViewState
|
||||
</script>
|
||||
|
||||
<MapControlButton on:click={() => state.guistate.backgroundLayerSelectionIsOpened.setData(true)}>
|
||||
<Square3Stack3dIcon class="w-6 h-6"/>
|
||||
<Square3Stack3dIcon class="w-6 h-6" />
|
||||
</MapControlButton>
|
||||
|
|
|
|||
|
|
@ -1,33 +1,32 @@
|
|||
<script lang="ts">
|
||||
import {Store} from "../../Logic/UIEventSource";
|
||||
import {PencilIcon} from "@babeard/svelte-heroicons/solid";
|
||||
import Translations from "../i18n/Translations";
|
||||
import Tr from "../Base/Tr.svelte";
|
||||
import { Store } from "../../Logic/UIEventSource"
|
||||
import { PencilIcon } from "@babeard/svelte-heroicons/solid"
|
||||
import Translations from "../i18n/Translations"
|
||||
import Tr from "../Base/Tr.svelte"
|
||||
|
||||
export let mapProperties: { location: Store<{ lon: number; lat: number }>; zoom: Store<number> }
|
||||
let location = mapProperties.location
|
||||
let zoom = mapProperties.zoom
|
||||
export let objectId: undefined | string = undefined
|
||||
export let mapProperties: { location: Store<{ lon: number; lat: number }>; zoom: Store<number> }
|
||||
let location = mapProperties.location
|
||||
let zoom = mapProperties.zoom
|
||||
export let objectId: undefined | string = undefined
|
||||
|
||||
let elementSelect = ""
|
||||
if (objectId !== undefined) {
|
||||
const parts = objectId?.split("/")
|
||||
const tp = parts[0]
|
||||
if (
|
||||
parts.length === 2 &&
|
||||
!isNaN(Number(parts[1])) &&
|
||||
(tp === "node" || tp === "way" || tp === "relation")
|
||||
) {
|
||||
elementSelect = "&" + tp + "=" + parts[1]
|
||||
}
|
||||
let elementSelect = ""
|
||||
if (objectId !== undefined) {
|
||||
const parts = objectId?.split("/")
|
||||
const tp = parts[0]
|
||||
if (
|
||||
parts.length === 2 &&
|
||||
!isNaN(Number(parts[1])) &&
|
||||
(tp === "node" || tp === "way" || tp === "relation")
|
||||
) {
|
||||
elementSelect = "&" + tp + "=" + parts[1]
|
||||
}
|
||||
const idLink = `https://www.openstreetmap.org/edit?editor=id${elementSelect}#map=${
|
||||
$zoom ?? 0
|
||||
}/${$location?.lat ?? 0}/${$location?.lon ?? 0}`
|
||||
}
|
||||
const idLink = `https://www.openstreetmap.org/edit?editor=id${elementSelect}#map=${$zoom ?? 0}/${
|
||||
$location?.lat ?? 0
|
||||
}/${$location?.lon ?? 0}`
|
||||
</script>
|
||||
|
||||
|
||||
<a class="flex button items-center" target="_blank" href={idLink}>
|
||||
<PencilIcon class="w-12 h-12 p-2 pr-4"/>
|
||||
<Tr t={ Translations.t.general.attribution.editId}/>
|
||||
<PencilIcon class="w-12 h-12 p-2 pr-4" />
|
||||
<Tr t={Translations.t.general.attribution.editId} />
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import Combine from "../Base/Combine";
|
||||
import {OsmConnection} from "../../Logic/Osm/OsmConnection";
|
||||
import {Store, UIEventSource} from "../../Logic/UIEventSource";
|
||||
import {BBox} from "../../Logic/BBox";
|
||||
import Translations from "../i18n/Translations";
|
||||
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||
import Toggle from "../Input/Toggle";
|
||||
import {SubtleButton} from "../Base/SubtleButton";
|
||||
import Svg from "../../Svg";
|
||||
import {Utils} from "../../Utils";
|
||||
import Constants from "../../Models/Constants";
|
||||
import Combine from "../Base/Combine"
|
||||
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { BBox } from "../../Logic/BBox"
|
||||
import Translations from "../i18n/Translations"
|
||||
import { VariableUiElement } from "../Base/VariableUIElement"
|
||||
import Toggle from "../Input/Toggle"
|
||||
import { SubtleButton } from "../Base/SubtleButton"
|
||||
import Svg from "../../Svg"
|
||||
import { Utils } from "../../Utils"
|
||||
import Constants from "../../Models/Constants"
|
||||
|
||||
export class OpenJosm extends Combine {
|
||||
constructor(osmConnection: OsmConnection, bounds: Store<BBox>, iconStyle?: string) {
|
||||
|
|
@ -32,20 +32,22 @@ export class OpenJosm extends Combine {
|
|||
)
|
||||
|
||||
const toggle = new Toggle(
|
||||
new SubtleButton(Svg.josm_logo_svg().SetStyle(iconStyle), t.editJosm).onClick(() => {
|
||||
const bbox = bounds.data
|
||||
if (bbox === undefined) {
|
||||
return
|
||||
}
|
||||
const top = bbox.getNorth()
|
||||
const bottom = bbox.getSouth()
|
||||
const right = bbox.getEast()
|
||||
const left = bbox.getWest()
|
||||
const josmLink = `http://127.0.0.1:8111/load_and_zoom?left=${left}&right=${right}&top=${top}&bottom=${bottom}`
|
||||
Utils.download(josmLink)
|
||||
.then((answer) => josmState.setData(answer.replace(/\n/g, "").trim()))
|
||||
.catch((_) => josmState.setData("ERROR"))
|
||||
}).SetClass("w-full"),
|
||||
new SubtleButton(Svg.josm_logo_svg().SetStyle(iconStyle), t.editJosm)
|
||||
.onClick(() => {
|
||||
const bbox = bounds.data
|
||||
if (bbox === undefined) {
|
||||
return
|
||||
}
|
||||
const top = bbox.getNorth()
|
||||
const bottom = bbox.getSouth()
|
||||
const right = bbox.getEast()
|
||||
const left = bbox.getWest()
|
||||
const josmLink = `http://127.0.0.1:8111/load_and_zoom?left=${left}&right=${right}&top=${top}&bottom=${bottom}`
|
||||
Utils.download(josmLink)
|
||||
.then((answer) => josmState.setData(answer.replace(/\n/g, "").trim()))
|
||||
.catch((_) => josmState.setData("ERROR"))
|
||||
})
|
||||
.SetClass("w-full"),
|
||||
undefined,
|
||||
osmConnection.userDetails.map(
|
||||
(ud) => ud.loggedIn && ud.csCount >= Constants.userJourney.historyLinkVisible
|
||||
|
|
|
|||
|
|
@ -1,42 +1,45 @@
|
|||
<script lang="ts">/**
|
||||
* The OverlayToggle shows a single toggle to enable or disable an overlay
|
||||
*/
|
||||
import Checkbox from "../Base/Checkbox.svelte";
|
||||
import { onDestroy } from "svelte";
|
||||
import { UIEventSource } from "../../Logic/UIEventSource";
|
||||
import Tr from "../Base/Tr.svelte";
|
||||
import Translations from "../i18n/Translations";
|
||||
import { Translation } from "../i18n/Translation";
|
||||
import type { RasterLayerProperties } from "../../Models/RasterLayerProperties";
|
||||
<script lang="ts">
|
||||
/**
|
||||
* The OverlayToggle shows a single toggle to enable or disable an overlay
|
||||
*/
|
||||
import Checkbox from "../Base/Checkbox.svelte"
|
||||
import { onDestroy } from "svelte"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import Tr from "../Base/Tr.svelte"
|
||||
import Translations from "../i18n/Translations"
|
||||
import { Translation } from "../i18n/Translation"
|
||||
import type { RasterLayerProperties } from "../../Models/RasterLayerProperties"
|
||||
|
||||
export let layerproperties : RasterLayerProperties
|
||||
export let state: {isDisplayed: UIEventSource<boolean>};
|
||||
export let zoomlevel: UIEventSource<number>;
|
||||
export let highlightedLayer: UIEventSource<string> | undefined;
|
||||
export let layerproperties: RasterLayerProperties
|
||||
export let state: { isDisplayed: UIEventSource<boolean> }
|
||||
export let zoomlevel: UIEventSource<number>
|
||||
export let highlightedLayer: UIEventSource<string> | undefined
|
||||
|
||||
let isDisplayed: boolean = state.isDisplayed.data;
|
||||
onDestroy(state.isDisplayed.addCallbackAndRunD(d => {
|
||||
isDisplayed = d;
|
||||
return false;
|
||||
}));
|
||||
let isDisplayed: boolean = state.isDisplayed.data
|
||||
onDestroy(
|
||||
state.isDisplayed.addCallbackAndRunD((d) => {
|
||||
isDisplayed = d
|
||||
return false
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
let mainElem: HTMLElement;
|
||||
$: onDestroy(
|
||||
highlightedLayer.addCallbackAndRun(highlightedLayer => {
|
||||
if (highlightedLayer === layerproperties.id) {
|
||||
mainElem?.classList?.add("glowing-shadow");
|
||||
} else {
|
||||
mainElem?.classList?.remove("glowing-shadow");
|
||||
}
|
||||
})
|
||||
);
|
||||
let mainElem: HTMLElement
|
||||
$: onDestroy(
|
||||
highlightedLayer.addCallbackAndRun((highlightedLayer) => {
|
||||
if (highlightedLayer === layerproperties.id) {
|
||||
mainElem?.classList?.add("glowing-shadow")
|
||||
} else {
|
||||
mainElem?.classList?.remove("glowing-shadow")
|
||||
}
|
||||
})
|
||||
)
|
||||
</script>
|
||||
|
||||
{#if layerproperties.name}
|
||||
<div bind:this={mainElem}>
|
||||
<label class="flex gap-1">
|
||||
<Checkbox selected={state.isDisplayed} />
|
||||
<Tr t={new Translation(layerproperties.name)}/>
|
||||
<Tr t={new Translation(layerproperties.name)} />
|
||||
{#if $zoomlevel < layerproperties.min_zoom}
|
||||
<span class="alert">
|
||||
<Tr t={Translations.t.general.layerSelection.zoomInToSeeThisLayer} />
|
||||
|
|
|
|||
|
|
@ -1,62 +1,74 @@
|
|||
<script lang="ts">
|
||||
import type {Feature} from "geojson";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
|
||||
import type {SpecialVisualizationState} from "../SpecialVisualization";
|
||||
import TagRenderingAnswer from "../Popup/TagRendering/TagRenderingAnswer.svelte";
|
||||
import {onDestroy} from "svelte";
|
||||
import Translations from "../i18n/Translations";
|
||||
import Tr from "../Base/Tr.svelte";
|
||||
import {XCircleIcon} from "@rgossiaux/svelte-heroicons/solid";
|
||||
import type { Feature } from "geojson"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||
import TagRenderingAnswer from "../Popup/TagRendering/TagRenderingAnswer.svelte"
|
||||
import { onDestroy } from "svelte"
|
||||
import Translations from "../i18n/Translations"
|
||||
import Tr from "../Base/Tr.svelte"
|
||||
import { XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
|
||||
export let state: SpecialVisualizationState;
|
||||
export let layer: LayerConfig;
|
||||
export let selectedElement: Feature;
|
||||
export let tags: UIEventSource<Record<string, string>>;
|
||||
export let state: SpecialVisualizationState
|
||||
export let layer: LayerConfig
|
||||
export let selectedElement: Feature
|
||||
export let tags: UIEventSource<Record<string, string>>
|
||||
|
||||
let _tags: Record<string, string>
|
||||
onDestroy(
|
||||
tags.addCallbackAndRun((tags) => {
|
||||
_tags = tags
|
||||
})
|
||||
)
|
||||
|
||||
let _tags: Record<string, string>;
|
||||
onDestroy(tags.addCallbackAndRun(tags => {
|
||||
_tags = tags;
|
||||
}));
|
||||
|
||||
let _metatags: Record<string, string>;
|
||||
onDestroy(state.userRelatedState.preferencesAsTags.addCallbackAndRun(tags => {
|
||||
_metatags = tags;
|
||||
}));
|
||||
let _metatags: Record<string, string>
|
||||
onDestroy(
|
||||
state.userRelatedState.preferencesAsTags.addCallbackAndRun((tags) => {
|
||||
_metatags = tags
|
||||
})
|
||||
)
|
||||
</script>
|
||||
|
||||
|
||||
{#if _tags._deleted === "yes"}
|
||||
<Tr t={ Translations.t.delete.isDeleted}/>
|
||||
<Tr t={Translations.t.delete.isDeleted} />
|
||||
{:else}
|
||||
<div class="flex border-b-2 border-black drop-shadow-md justify-between items-center low-interaction px-3">
|
||||
<div class="flex flex-col">
|
||||
<div
|
||||
class="flex border-b-2 border-black drop-shadow-md justify-between items-center low-interaction px-3"
|
||||
>
|
||||
<div class="flex flex-col">
|
||||
<!-- Title element-->
|
||||
<h3>
|
||||
<TagRenderingAnswer config={layer.title} {selectedElement} {state} {tags} {layer} />
|
||||
</h3>
|
||||
|
||||
<!-- Title element-->
|
||||
<h3>
|
||||
<TagRenderingAnswer config={layer.title} {selectedElement} {state} {tags}
|
||||
{layer}></TagRenderingAnswer>
|
||||
</h3>
|
||||
|
||||
<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}
|
||||
{#if (titleIconConfig.condition?.matchesProperties(_tags) ?? true) && (titleIconConfig.metacondition?.matchesProperties({..._metatags, ..._tags}) ?? true) && titleIconConfig.IsKnown(_tags)}
|
||||
<div class="w-8 h-8 flex items-center">
|
||||
<TagRenderingAnswer config={titleIconConfig} {tags} {selectedElement} {state}
|
||||
{layer} extraClasses="h-full justify-center"></TagRenderingAnswer>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
<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}
|
||||
{#if (titleIconConfig.condition?.matchesProperties(_tags) ?? true) && (titleIconConfig.metacondition?.matchesProperties( { ..._metatags, ..._tags } ) ?? true) && titleIconConfig.IsKnown(_tags)}
|
||||
<div class="w-8 h-8 flex items-center">
|
||||
<TagRenderingAnswer
|
||||
config={titleIconConfig}
|
||||
{tags}
|
||||
{selectedElement}
|
||||
{state}
|
||||
{layer}
|
||||
extraClasses="h-full justify-center"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<XCircleIcon class="w-8 h-8 cursor-pointer" on:click={() => state.selectedElement.setData(undefined)}/>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<XCircleIcon
|
||||
class="w-8 h-8 cursor-pointer"
|
||||
on:click={() => state.selectedElement.setData(undefined)}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
:global(.title-icons a) {
|
||||
display: block !important;
|
||||
}
|
||||
:global(.title-icons a) {
|
||||
display: block !important;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,46 +1,54 @@
|
|||
<script lang="ts">
|
||||
import type {Feature} from "geojson";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
|
||||
import type {SpecialVisualizationState} from "../SpecialVisualization";
|
||||
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte";
|
||||
import {onDestroy} from "svelte";
|
||||
import Translations from "../i18n/Translations";
|
||||
import Tr from "../Base/Tr.svelte";
|
||||
import type { Feature } from "geojson"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte"
|
||||
import { onDestroy } from "svelte"
|
||||
import Translations from "../i18n/Translations"
|
||||
import Tr from "../Base/Tr.svelte"
|
||||
|
||||
export let state: SpecialVisualizationState;
|
||||
export let layer: LayerConfig;
|
||||
export let selectedElement: Feature;
|
||||
export let tags: UIEventSource<Record<string, string>>;
|
||||
export let highlightedRendering: UIEventSource<string> = undefined;
|
||||
export let state: SpecialVisualizationState
|
||||
export let layer: LayerConfig
|
||||
export let selectedElement: Feature
|
||||
export let tags: UIEventSource<Record<string, string>>
|
||||
export let highlightedRendering: UIEventSource<string> = undefined
|
||||
|
||||
let _tags: Record<string, string>
|
||||
onDestroy(
|
||||
tags.addCallbackAndRun((tags) => {
|
||||
_tags = tags
|
||||
})
|
||||
)
|
||||
|
||||
let _tags: Record<string, string>;
|
||||
onDestroy(tags.addCallbackAndRun(tags => {
|
||||
_tags = tags;
|
||||
}));
|
||||
|
||||
let _metatags: Record<string, string>;
|
||||
onDestroy(state.userRelatedState.preferencesAsTags.addCallbackAndRun(tags => {
|
||||
_metatags = tags;
|
||||
}));
|
||||
let _metatags: Record<string, string>
|
||||
onDestroy(
|
||||
state.userRelatedState.preferencesAsTags.addCallbackAndRun((tags) => {
|
||||
_metatags = tags
|
||||
})
|
||||
)
|
||||
</script>
|
||||
|
||||
|
||||
{#if _tags._deleted === "yes"}
|
||||
<Tr t={ Translations.t.delete.isDeleted}/>
|
||||
<button class="w-full" on:click={() => state.selectedElement.setData(undefined)}>
|
||||
<Tr t={ Translations.t.general.returnToTheMap}/>
|
||||
</button>
|
||||
<Tr t={Translations.t.delete.isDeleted} />
|
||||
<button class="w-full" on:click={() => state.selectedElement.setData(undefined)}>
|
||||
<Tr t={Translations.t.general.returnToTheMap} />
|
||||
</button>
|
||||
{:else}
|
||||
<div class="flex flex-col overflow-y-auto p-1 px-2 gap-y-2">
|
||||
{#each layer.tagRenderings as config (config.id)}
|
||||
{#if (config.condition === undefined || config.condition.matchesProperties(_tags)) && (config.metacondition === undefined || config.metacondition.matchesProperties({..._tags, ..._metatags}))}
|
||||
{#if config.IsKnown(_tags)}
|
||||
<TagRenderingEditable {tags} {config} {state} {selectedElement} {layer}
|
||||
{highlightedRendering}></TagRenderingEditable>
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
<div class="flex flex-col overflow-y-auto p-1 px-2 gap-y-2">
|
||||
{#each layer.tagRenderings as config (config.id)}
|
||||
{#if (config.condition === undefined || config.condition.matchesProperties(_tags)) && (config.metacondition === undefined || config.metacondition.matchesProperties( { ..._tags, ..._metatags } ))}
|
||||
{#if config.IsKnown(_tags)}
|
||||
<TagRenderingEditable
|
||||
{tags}
|
||||
{config}
|
||||
{state}
|
||||
{selectedElement}
|
||||
{layer}
|
||||
{highlightedRendering}
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
import {VariableUiElement} from "../Base/VariableUIElement"
|
||||
import {Translation} from "../i18n/Translation"
|
||||
import { VariableUiElement } from "../Base/VariableUIElement"
|
||||
import { Translation } from "../i18n/Translation"
|
||||
import Svg from "../../Svg"
|
||||
import Combine from "../Base/Combine"
|
||||
import {Store, UIEventSource} from "../../Logic/UIEventSource"
|
||||
import {Utils} from "../../Utils"
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { Utils } from "../../Utils"
|
||||
import Translations from "../i18n/Translations"
|
||||
import BaseUIElement from "../BaseUIElement"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import {InputElement} from "../Input/InputElement"
|
||||
import {CheckBox} from "../Input/Checkboxes"
|
||||
import {SubtleButton} from "../Base/SubtleButton"
|
||||
import { InputElement } from "../Input/InputElement"
|
||||
import { CheckBox } from "../Input/Checkboxes"
|
||||
import { SubtleButton } from "../Base/SubtleButton"
|
||||
import LZString from "lz-string"
|
||||
import {SpecialVisualizationState} from "../SpecialVisualization"
|
||||
import { SpecialVisualizationState } from "../SpecialVisualization"
|
||||
|
||||
export class ShareScreen extends Combine{
|
||||
export class ShareScreen extends Combine {
|
||||
constructor(state: SpecialVisualizationState) {
|
||||
const layout = state?.layout
|
||||
const tr = Translations.t.general.sharescreen
|
||||
|
|
@ -60,8 +60,9 @@ export class ShareScreen extends Combine{
|
|||
return "layer-" + flayer.layerDef.id + "=" + flayer.isDisplayed.data
|
||||
}
|
||||
|
||||
const currentLayer: Store<{ id: string; name: string | Record<string, string> } | undefined> =
|
||||
state.mapProperties.rasterLayer.map((l) => l?.properties)
|
||||
const currentLayer: Store<
|
||||
{ id: string; name: string | Record<string, string> } | undefined
|
||||
> = state.mapProperties.rasterLayer.map((l) => l?.properties)
|
||||
const currentBackground = new VariableUiElement(
|
||||
currentLayer.map((layer) => {
|
||||
return tr.fsIncludeCurrentBackgroundMap.Subs({ name: layer?.name ?? "" })
|
||||
|
|
@ -90,13 +91,15 @@ export class ShareScreen extends Combine{
|
|||
(includeLayerSelection) => {
|
||||
if (includeLayerSelection) {
|
||||
return Utils.NoNull(
|
||||
Array.from( state.layerState.filteredLayers.values()).map(fLayerToParam)
|
||||
Array.from(state.layerState.filteredLayers.values()).map(fLayerToParam)
|
||||
).join("&")
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
},
|
||||
Array.from(state.layerState.filteredLayers.values()).map((flayer) => flayer.isDisplayed)
|
||||
Array.from(state.layerState.filteredLayers.values()).map(
|
||||
(flayer) => flayer.isDisplayed
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,87 +1,88 @@
|
|||
<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";
|
||||
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
|
||||
/**
|
||||
* 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
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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}/>
|
||||
<Tr t={layout.description} />
|
||||
<Tr t={Translations.t.general.welcomeExplanation.general} />
|
||||
{#if layout.layers.some((l) => l.presets?.length > 0)}
|
||||
<If condition={state.featureSwitches.featureSwitchAddNew}>
|
||||
<Tr t={Translations.t.general.welcomeExplanation.addNew}/>
|
||||
</If>
|
||||
<If 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>
|
||||
<Tr t={layout.descriptionTail} />
|
||||
<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>
|
||||
<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>
|
||||
<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}
|
||||
/>
|
||||
</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" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
<script lang="ts">
|
||||
import NoThemeResultButton from "./NoThemeResultButton.svelte"
|
||||
|
||||
import {OsmConnection} from "../../Logic/Osm/OsmConnection"
|
||||
import {UIEventSource} from "../../Logic/UIEventSource"
|
||||
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import ThemeButton from "./ThemeButton.svelte"
|
||||
import {LayoutInformation} from "../../Models/ThemeConfig/LayoutConfig"
|
||||
import { LayoutInformation } from "../../Models/ThemeConfig/LayoutConfig"
|
||||
import MoreScreen from "./MoreScreen"
|
||||
|
||||
export let search: UIEventSource<string>
|
||||
|
|
@ -30,7 +30,6 @@
|
|||
</div>
|
||||
{:else}
|
||||
<div>
|
||||
|
||||
{#each filteredThemes as theme (theme.id)}
|
||||
{#if theme !== undefined && !(hideThemes && theme?.hideFromOverview)}
|
||||
<ThemeButton {theme} {isCustom} userDetails={state.osmConnection.userDetails} {state} />
|
||||
|
|
|
|||
|
|
@ -1,37 +1,37 @@
|
|||
<script lang="ts">
|
||||
import {OsmConnection} from "../../Logic/Osm/OsmConnection"
|
||||
import {Store, Stores, UIEventSource} from "../../Logic/UIEventSource"
|
||||
import {Utils} from "../../Utils"
|
||||
import ThemesList from "./ThemesList.svelte"
|
||||
import Translations from "../i18n/Translations"
|
||||
import UserRelatedState from "../../Logic/State/UserRelatedState"
|
||||
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
|
||||
import { Store, Stores, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { Utils } from "../../Utils"
|
||||
import ThemesList from "./ThemesList.svelte"
|
||||
import Translations from "../i18n/Translations"
|
||||
import UserRelatedState from "../../Logic/State/UserRelatedState"
|
||||
|
||||
export let search: UIEventSource<string>
|
||||
export let state: UserRelatedState & {
|
||||
osmConnection: OsmConnection
|
||||
}
|
||||
export let onMainScreen: boolean = true
|
||||
export let search: UIEventSource<string>
|
||||
export let state: UserRelatedState & {
|
||||
osmConnection: OsmConnection
|
||||
}
|
||||
export let onMainScreen: boolean = true
|
||||
|
||||
const t = Translations.t.general
|
||||
const currentIds: Store<string[]> = state.installedUserThemes
|
||||
const stableIds = Stores.ListStabilized<string>(currentIds)
|
||||
let customThemes
|
||||
$: customThemes = Utils.NoNull($stableIds.map((id) => state.GetUnofficialTheme(id)))
|
||||
$: console.log("Custom themes are", customThemes)
|
||||
const t = Translations.t.general
|
||||
const currentIds: Store<string[]> = state.installedUserThemes
|
||||
const stableIds = Stores.ListStabilized<string>(currentIds)
|
||||
let customThemes
|
||||
$: customThemes = Utils.NoNull($stableIds.map((id) => state.GetUnofficialTheme(id)))
|
||||
$: console.log("Custom themes are", customThemes)
|
||||
</script>
|
||||
|
||||
{#if customThemes.length > 0}
|
||||
<ThemesList
|
||||
{search}
|
||||
{state}
|
||||
{onMainScreen}
|
||||
themes={customThemes}
|
||||
isCustom={true}
|
||||
hideThemes={false}
|
||||
>
|
||||
<svelte:fragment slot="title">
|
||||
<!-- TODO: Change string to exclude html -->
|
||||
{@html t.customThemeIntro.toString()}
|
||||
</svelte:fragment>
|
||||
</ThemesList>
|
||||
<ThemesList
|
||||
{search}
|
||||
{state}
|
||||
{onMainScreen}
|
||||
themes={customThemes}
|
||||
isCustom={true}
|
||||
hideThemes={false}
|
||||
>
|
||||
<svelte:fragment slot="title">
|
||||
<!-- TODO: Change string to exclude html -->
|
||||
{@html t.customThemeIntro.toString()}
|
||||
</svelte:fragment>
|
||||
</ThemesList>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -1,50 +1,53 @@
|
|||
<script lang="ts">
|
||||
import UserDetails, { OsmConnection } from "../../Logic/Osm/OsmConnection";
|
||||
import { UIEventSource } from "../../Logic/UIEventSource";
|
||||
import { PencilAltIcon, UserCircleIcon } from "@rgossiaux/svelte-heroicons/solid";
|
||||
import { onDestroy } from "svelte";
|
||||
import Showdown from "showdown";
|
||||
import FromHtml from "../Base/FromHtml.svelte";
|
||||
import Tr from "../Base/Tr.svelte";
|
||||
import Translations from "../i18n/Translations.js";
|
||||
import UserDetails, { OsmConnection } from "../../Logic/Osm/OsmConnection"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { PencilAltIcon, UserCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
import { onDestroy } from "svelte"
|
||||
import Showdown from "showdown"
|
||||
import FromHtml from "../Base/FromHtml.svelte"
|
||||
import Tr from "../Base/Tr.svelte"
|
||||
import Translations from "../i18n/Translations.js"
|
||||
|
||||
/**
|
||||
* This panel shows information about the logged-in user, showing account name, profile pick, description and an edit-button
|
||||
*/
|
||||
export let osmConnection: OsmConnection;
|
||||
let userdetails: UIEventSource<UserDetails> = osmConnection.userDetails;
|
||||
let description: string;
|
||||
onDestroy(userdetails.addCallbackAndRunD(userdetails => {
|
||||
description = new Showdown.Converter()
|
||||
.makeHtml(userdetails.description)
|
||||
?.replace(/>/g, ">")
|
||||
?.replace(/</g, "<");
|
||||
|
||||
}));
|
||||
export let osmConnection: OsmConnection
|
||||
let userdetails: UIEventSource<UserDetails> = osmConnection.userDetails
|
||||
let description: string
|
||||
onDestroy(
|
||||
userdetails.addCallbackAndRunD((userdetails) => {
|
||||
description = new Showdown.Converter()
|
||||
.makeHtml(userdetails.description)
|
||||
?.replace(/>/g, ">")
|
||||
?.replace(/</g, "<")
|
||||
})
|
||||
)
|
||||
</script>
|
||||
|
||||
<div class="flex border border-gray-600 border-dashed m-1 p-1 rounded-md link-underline">
|
||||
{#if $userdetails.img}
|
||||
<img src={$userdetails.img} class="rounded-full w-12 h-12 m-4">
|
||||
<img src={$userdetails.img} class="rounded-full w-12 h-12 m-4" />
|
||||
{:else}
|
||||
<UserCircleIcon class="w-12 h-12" />
|
||||
{/if}
|
||||
<div class="flex flex-col">
|
||||
<h3>{$userdetails.name}</h3>
|
||||
{#if description}
|
||||
<FromHtml src={description}/>
|
||||
<a href={osmConnection.Backend() + "/profile/edit"} target="_blank" class="link-no-underline flex items-center self-end">
|
||||
<FromHtml src={description} />
|
||||
<a
|
||||
href={osmConnection.Backend() + "/profile/edit"}
|
||||
target="_blank"
|
||||
class="link-no-underline flex items-center self-end"
|
||||
>
|
||||
<PencilAltIcon slot="image" class="p-2 w-8 h-8" />
|
||||
<Tr slot="message" t={Translations.t.userinfo.editDescription} />
|
||||
</a>
|
||||
|
||||
{:else}
|
||||
<Tr t={Translations.t. userinfo.noDescription} />
|
||||
<Tr t={Translations.t.userinfo.noDescription} />
|
||||
<a href={osmConnection.Backend() + "/profile/edit"} target="_blank" class="flex items-center">
|
||||
<PencilAltIcon slot="image" class="p-2 w-8 h-8" />
|
||||
<Tr slot="message" t={Translations.t.userinfo.noDescriptionCallToAction} />
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -8,33 +8,33 @@
|
|||
*
|
||||
* This component is _not_ responsible for the rest of the flow, e.g. the confirm button
|
||||
*/
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
|
||||
import type { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson";
|
||||
import split_point from "../../assets/layers/split_point/split_point.json";
|
||||
import split_road from "../../assets/layers/split_road/split_road.json";
|
||||
import { UIEventSource } from "../../Logic/UIEventSource";
|
||||
import { Map as MlMap } from "maplibre-gl";
|
||||
import type { MapProperties } from "../../Models/MapProperties";
|
||||
import { MapLibreAdaptor } from "../Map/MapLibreAdaptor";
|
||||
import MaplibreMap from "../Map/MaplibreMap.svelte";
|
||||
import { OsmWay } from "../../Logic/Osm/OsmObject";
|
||||
import ShowDataLayer from "../Map/ShowDataLayer";
|
||||
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource";
|
||||
import { GeoOperations } from "../../Logic/GeoOperations";
|
||||
import { BBox } from "../../Logic/BBox";
|
||||
import type { Feature, LineString, Point } from "geojson";
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import type { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"
|
||||
import split_point from "../../assets/layers/split_point/split_point.json"
|
||||
import split_road from "../../assets/layers/split_road/split_road.json"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { Map as MlMap } from "maplibre-gl"
|
||||
import type { MapProperties } from "../../Models/MapProperties"
|
||||
import { MapLibreAdaptor } from "../Map/MapLibreAdaptor"
|
||||
import MaplibreMap from "../Map/MaplibreMap.svelte"
|
||||
import { OsmWay } from "../../Logic/Osm/OsmObject"
|
||||
import ShowDataLayer from "../Map/ShowDataLayer"
|
||||
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"
|
||||
import { GeoOperations } from "../../Logic/GeoOperations"
|
||||
import { BBox } from "../../Logic/BBox"
|
||||
import type { Feature, LineString, Point } from "geojson"
|
||||
|
||||
const splitpoint_style = new LayerConfig(
|
||||
<LayerConfigJson>split_point,
|
||||
"(BUILTIN) SplitRoadWizard.ts",
|
||||
true
|
||||
) as const;
|
||||
) as const
|
||||
|
||||
const splitroad_style = new LayerConfig(
|
||||
<LayerConfigJson>split_road,
|
||||
"(BUILTIN) SplitRoadWizard.ts",
|
||||
true
|
||||
) as const;
|
||||
) as const
|
||||
|
||||
/**
|
||||
* The way to focus on
|
||||
|
|
@ -45,60 +45,62 @@
|
|||
* A default is given
|
||||
*/
|
||||
export let layer: LayerConfig = splitroad_style
|
||||
/**
|
||||
* Optional: use these properties to set e.g. background layer
|
||||
*/
|
||||
export let mapProperties: undefined | Partial<MapProperties> = undefined;
|
||||
|
||||
let map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined);
|
||||
let adaptor = new MapLibreAdaptor(map, mapProperties);
|
||||
|
||||
const wayGeojson: Feature<LineString> = GeoOperations.forceLineString( osmWay.asGeoJson())
|
||||
/**
|
||||
* Optional: use these properties to set e.g. background layer
|
||||
*/
|
||||
export let mapProperties: undefined | Partial<MapProperties> = undefined
|
||||
|
||||
let map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined)
|
||||
let adaptor = new MapLibreAdaptor(map, mapProperties)
|
||||
|
||||
const wayGeojson: Feature<LineString> = GeoOperations.forceLineString(osmWay.asGeoJson())
|
||||
adaptor.location.setData(GeoOperations.centerpointCoordinatesObj(wayGeojson))
|
||||
adaptor.bounds.setData(BBox.get(wayGeojson).pad(2))
|
||||
adaptor.maxbounds.setData(BBox.get(wayGeojson).pad(2))
|
||||
|
||||
|
||||
new ShowDataLayer(map, {
|
||||
features: new StaticFeatureSource([wayGeojson]),
|
||||
drawMarkers: false,
|
||||
layer: layer
|
||||
layer: layer,
|
||||
})
|
||||
|
||||
export let splitPoints: UIEventSource< Feature<
|
||||
Point,
|
||||
{
|
||||
id: number
|
||||
index: number
|
||||
dist: number
|
||||
location: number
|
||||
}
|
||||
>[]> = new UIEventSource([])
|
||||
|
||||
export let splitPoints: UIEventSource<
|
||||
Feature<
|
||||
Point,
|
||||
{
|
||||
id: number
|
||||
index: number
|
||||
dist: number
|
||||
location: number
|
||||
}
|
||||
>[]
|
||||
> = new UIEventSource([])
|
||||
const splitPointsFS = new StaticFeatureSource(splitPoints)
|
||||
|
||||
|
||||
new ShowDataLayer(map, {
|
||||
layer: splitpoint_style,
|
||||
features: splitPointsFS,
|
||||
onClick: (clickedFeature: Feature) => {
|
||||
console.log("Clicked feature is", clickedFeature, splitPoints.data)
|
||||
const i = splitPoints.data.findIndex(f => f === clickedFeature)
|
||||
if(i < 0){
|
||||
const i = splitPoints.data.findIndex((f) => f === clickedFeature)
|
||||
if (i < 0) {
|
||||
return
|
||||
}
|
||||
splitPoints.data.splice(i, 1)
|
||||
splitPoints.ping()
|
||||
}
|
||||
},
|
||||
})
|
||||
let id = 0
|
||||
adaptor.lastClickLocation.addCallbackD(({lon, lat}) => {
|
||||
adaptor.lastClickLocation.addCallbackD(({ lon, lat }) => {
|
||||
const projected = GeoOperations.nearestPoint(wayGeojson, [lon, lat])
|
||||
|
||||
|
||||
projected.properties["id"] = id
|
||||
id++
|
||||
splitPoints.data.push(<any> projected)
|
||||
splitPoints.data.push(<any>projected)
|
||||
splitPoints.ping()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<div class="w-full h-full">
|
||||
<MaplibreMap {map}></MaplibreMap>
|
||||
<MaplibreMap {map} />
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue