Merge master

This commit is contained in:
Pieter Vander Vennet 2024-04-02 19:07:11 +02:00
commit 890816d2dd
424 changed files with 40595 additions and 3354 deletions

View file

@ -66,7 +66,7 @@
/>
</If>
{filteredLayer.layerDef.name}
<Tr t={filteredLayer.layerDef.name}/>
{#if $zoomlevel < layer.minzoom}
<span class="alert">
@ -82,7 +82,7 @@
<!-- 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}
<Checkbox selected={getBooleanStateFor(filter)}>
{filter.options[0].question}
<Tr t={filter.options[0].question}/>
</Checkbox>
{/if}
@ -94,7 +94,7 @@
<Dropdown value={getStateFor(filter)}>
{#each filter.options as option, i}
<option value={i}>
{option.question}
<Tr t={option.question}/>
</option>
{/each}
</Dropdown>

View file

@ -6,6 +6,7 @@
import { UIEventSource } from "../../Logic/UIEventSource"
import { onDestroy } from "svelte"
import { Utils } from "../../Utils"
import Tr from "../Base/Tr.svelte"
export let filteredLayer: FilteredLayer
export let option: FilterConfigOption

View file

@ -2,7 +2,6 @@
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"
@ -15,7 +14,6 @@
import FeatureSourceMerger from "../../Logic/FeatureSource/Sources/FeatureSourceMerger"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { Utils } from "../../Utils"
import { createEventDispatcher } from "svelte"
import Move_arrows from "../../assets/svg/Move_arrows.svelte"
/**
@ -53,9 +51,6 @@
lat: number
}>(undefined)
const dispatch = createEventDispatcher<{ click: { lon: number; lat: number } }>()
const xyz = Tiles.embedded_tile(coordinate.lat, coordinate.lon, 16)
const map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined)
let initialMapProperties: Partial<MapProperties> & { location } = {
zoom: new UIEventSource<number>(19),
@ -73,6 +68,7 @@
minzoom: new UIEventSource<number>(18),
rasterLayer: UIEventSource.feedFrom(state.mapProperties.rasterLayer),
}
state?.showCurrentLocationOn(map)
if (targetLayer) {
const featuresForLayer = state.perLayer.get(targetLayer.id)
@ -120,7 +116,7 @@
<LocationInput
{map}
on:click={(data) => dispatch("click", data)}
on:click
mapProperties={initialMapProperties}
value={preciseLocation}
initialCoordinate={coordinate}

View file

@ -1,15 +1,20 @@
<script lang="ts">
/**
* A mapcontrol button which allows the user to select a different background.
* Even though the component is very small, it gets it's own class as it is often reused
* Even though the component is very small, it gets its own class as it is often reused
*/
import { Square3Stack3dIcon } from "@babeard/svelte-heroicons/solid"
import type { SpecialVisualizationState } from "../SpecialVisualization"
import Translations from "../i18n/Translations"
import MapControlButton from "../Base/MapControlButton.svelte"
import Tr from "../Base/Tr.svelte"
import StyleLoadingIndicator from "../Map/StyleLoadingIndicator.svelte"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import { Map as MlMap } from "maplibre-gl"
import ThemeViewState from "../../Models/ThemeViewState"
export let state: SpecialVisualizationState
export let state: ThemeViewState
export let map: Store<MlMap> = undefined
export let hideTooltip = false
</script>
@ -17,7 +22,10 @@
arialabel={Translations.t.general.labels.background}
on:click={() => state.guistate.backgroundLayerSelectionIsOpened.setData(true)}
>
<Square3Stack3dIcon class="h-6 w-6" />
<StyleLoadingIndicator map={map ?? state.map} rasterLayer={state.mapProperties.rasterLayer} >
<Square3Stack3dIcon class="h-6 w-6" />
</StyleLoadingIndicator>
{#if !hideTooltip}
<Tr cls="mx-2" t={Translations.t.general.backgroundSwitch} />
{/if}

View file

@ -13,20 +13,25 @@
export let layer: LayerConfig
export let selectedElement: Feature
let tags: UIEventSource<Record<string, string>> = state.featureProperties.getStore(
selectedElement.properties.id
selectedElement.properties.id,
)
$: {
tags = state.featureProperties.getStore(selectedElement.properties.id)
}
let isTesting = state.featureSwitchIsTesting
let isDebugging = state.featureSwitches.featureSwitchIsDebugging
let metatags: Store<Record<string, string>> = state.userRelatedState.preferencesAsTags
</script>
{#if $tags._deleted === "yes"}
<Tr t={Translations.t.delete.isDeleted} />
{:else}
<div class="low-interaction flex border-b-2 border-black px-3 drop-shadow-md">
<div class="h-fit w-full overflow-auto sm:p-2" style="max-height: 20vh;">
<div class="low-interaction flex border-b-2 border-black px-3 drop-shadow-md">
<div class="h-fit w-full overflow-auto sm:p-2" style="max-height: 20vh;">
{#if $tags._deleted === "yes"}
<h3 class="p-4">
<Tr t={Translations.t.delete.deletedTitle} />
</h3>
{:else}
<div class="flex h-full w-full flex-grow flex-col">
<!-- Title element and title icons-->
<h3 class="m-0">
@ -34,12 +39,11 @@
<TagRenderingAnswer config={layer.title} {selectedElement} {state} {tags} {layer} />
</a>
</h3>
<div
class="no-weblate title-icons links-as-button mr-2 flex flex-row flex-wrap items-center gap-x-0.5 pt-0.5 sm:pt-1"
>
{#each layer.titleIcons as titleIconConfig}
{#if (titleIconConfig.condition?.matchesProperties($tags) ?? true) && (titleIconConfig.metacondition?.matchesProperties( { ...$metatags, ...$tags } ) ?? true) && titleIconConfig.IsKnown($tags)}
{#if (titleIconConfig.condition?.matchesProperties($tags) ?? true) && (titleIconConfig.metacondition?.matchesProperties({ ...$metatags, ...$tags }) ?? true) && titleIconConfig.IsKnown($tags)}
<div class={titleIconConfig.renderIconClass ?? "flex h-8 w-8 items-center"}>
<TagRenderingAnswer
config={titleIconConfig}
@ -52,23 +56,27 @@
</div>
{/if}
{/each}
{#if $isTesting || $isDebugging}
<a class="subtle" href="https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Layers/{layer.id}.md"
target="_blank" rel="noreferrer noopener ">{layer.id}</a>
{/if}
</div>
</div>
</div>
<button
on:click={() => state.selectedElement.setData(undefined)}
use:ariaLabel={Translations.t.general.backToMap}
class="mt-2 h-fit shrink-0 rounded-full border-none p-0"
style="border: 0 !important; padding: 0 !important;"
>
<XCircleIcon aria-hidden={true} class="h-8 w-8" />
</button>
{/if}
</div>
{/if}
<button
class="mt-2 h-fit shrink-0 rounded-full border-none p-0"
on:click={() => state.selectedElement.setData(undefined)}
style="border: 0 !important; padding: 0 !important;"
use:ariaLabel={Translations.t.general.backToMap}
>
<XCircleIcon aria-hidden={true} class="h-8 w-8" />
</button>
</div>
<style>
:global(.title-icons a) {
display: block !important;
}
:global(.title-icons a) {
display: block !important;
}
</style>

View file

@ -8,17 +8,21 @@
import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
import UserRelatedState from "../../Logic/State/UserRelatedState"
import Delete_icon from "../../assets/svg/Delete_icon.svelte"
import BackButton from "../Base/BackButton.svelte"
export let state: SpecialVisualizationState
export let layer: LayerConfig
export let selectedElement: Feature
export let highlightedRendering: UIEventSource<string> = undefined
export let tags: UIEventSource<Record<string, string>> = state?.featureProperties?.getStore(
selectedElement.properties.id
)
let layer: LayerConfig = selectedElement.properties.id === "settings" ? UserRelatedState.usersettingsConfig : state.layout.getMatchingLayer(tags.data)
let stillMatches = tags.map(tags => !layer?.source?.osmTags || layer.source.osmTags?.matchesProperties(tags))
let _metatags: Record<string, string>
@ -27,7 +31,7 @@
onDestroy(
state.userRelatedState.preferencesAsTags.addCallbackAndRun((tags) => {
_metatags = tags
})
}),
)
}
@ -36,22 +40,26 @@
(config) =>
(config.condition?.matchesProperties(tgs) ?? true) &&
(config.metacondition?.matchesProperties({ ...tgs, ..._metatags }) ?? true) &&
config.IsKnown(tgs)
)
config.IsKnown(tgs),
),
)
</script>
{#if !$stillMatches}
<div class="alert" aria-live="assertive">
<Tr t={Translations.t.delete.isChanged}/>
<div class="alert" aria-live="assertive">
<Tr t={Translations.t.delete.isChanged} />
</div>
{:else if $tags._deleted === "yes"}
<div aria-live="assertive">
<Tr t={Translations.t.delete.isDeleted} />
<div class="flex w-full flex-col p-2">
<div aria-live="assertive" class="alert flex items-center justify-center self-stretch">
<Delete_icon class="w-8 h-8 m-2" />
<Tr t={Translations.t.delete.isDeleted} />
</div>
<BackButton clss="self-stretch mt-4" on:click={() => state.selectedElement.setData(undefined)}>
<Tr t={Translations.t.general.returnToTheMap} />
</BackButton>
</div>
<button class="w-full" on:click={() => state.selectedElement.setData(undefined)}>
<Tr t={Translations.t.general.returnToTheMap} />
</button>
{:else}
<div
class="selected-element-view flex h-full w-full flex-col gap-y-2 overflow-y-auto p-1 px-4"

View file

@ -11,10 +11,11 @@
import Tr from "../Base/Tr.svelte"
import Translations from "../i18n/Translations"
import { Utils } from "../../Utils"
import Svg from "../../Svg"
import ToSvelte from "../Base/ToSvelte.svelte"
import { DocumentDuplicateIcon } from "@rgossiaux/svelte-heroicons/outline"
import Share from "../../assets/svg/Share.svelte"
import ToSvelte from "../Base/ToSvelte.svelte"
import Img from "../Base/Img"
import Qr from "../../Utils/Qr"
export let state: ThemeViewState
const tr = Translations.t.general.sharescreen
@ -69,22 +70,32 @@
}
</script>
<div>
<Tr t={tr.intro} />
<div class="flex">
{#if typeof navigator?.share === "function"}
<button class="h-8 w-8 shrink-0 p-1" on:click={shareCurrentLink}>
<Share />
</button>
{/if}
{#if navigator.clipboard !== undefined}
<button class="no-image-background h-8 w-8 shrink-0 p-1" on:click={copyCurrentLink}>
<DocumentDuplicateIcon />
</button>
{/if}
<div class="literal-code" on:click={(e) => Utils.selectTextIn(e.target)}>
{linkToShare}
<div class="flex flex-col">
<div class="flex justify-between items-start">
<div class="flex flex-col">
<Tr t={tr.intro} />
<div class="flex">
{#if typeof navigator?.share === "function"}
<button class="h-8 w-8 shrink-0 p-1" on:click={shareCurrentLink}>
<Share />
</button>
{/if}
{#if navigator.clipboard !== undefined}
<button class="no-image-background h-8 w-8 shrink-0 p-1" on:click={copyCurrentLink}>
<DocumentDuplicateIcon />
</button>
{/if}
<div class="literal-code" on:click={(e) => Utils.selectTextIn(e.target)}>
{linkToShare}
</div>
</div>
</div>
<ToSvelte construct={() => new Img(new Qr(linkToShare).toImageElement(125)).SetStyle(
"width: 125px"
)} />
</div>
<div class="flex justify-center">
@ -95,29 +106,31 @@
<Tr t={tr.embedIntro} />
<div class="link-underline my-1 flex flex-col">
<label>
<input bind:checked={showWelcomeMessage} type="checkbox" />
<Tr t={tr.fsWelcomeMessage} />
</label>
<div class="flex flex-col interactive p-1">
<label>
<input bind:checked={enableLogin} type="checkbox" />
<Tr t={tr.fsUserbadge} />
</label>
</div>
<div class="literal-code m-1">
&lt;span class="literal-code iframe-code-block"&gt; <br />
&lt;iframe src="{linkToShare}"
<br />
allow="geolocation" width="100%" height="100%" style="min-width: 250px; min-height: 250px"
<br />
title="{state.layout.title?.txt ?? "MapComplete"} with MapComplete"&gt;
<br />
&lt;/iframe&gt;
<br />
&lt;/span&gt;
</div>
<div class="link-underline my-1 flex flex-col">
<label>
<input bind:checked={showWelcomeMessage} type="checkbox" id="share_show_welcome" />
<Tr t={tr.fsWelcomeMessage} />
</label>
<div class="literal-code m-1">
&lt;span class="literal-code iframe-code-block"&gt; <br />
&lt;iframe src="${url}"
<br />
allow="geolocation" width="100%" height="100%" style="min-width: 250px; min-height: 250px"
<br />
title="${state.layout.title?.txt ?? "MapComplete"} with MapComplete"&gt;
<br />
&lt;/iframe&gt;
<br />
&lt;/span&gt;
<label>
<input bind:checked={enableLogin} type="checkbox" id="share_enable_login"/>
<Tr t={tr.fsUserbadge} />
</label>
</div>
</div>
<Tr t={tr.documentation} cls="link-underline" />
<Tr cls="link-underline" t={tr.documentation} />
</div>

View file

@ -17,14 +17,17 @@ export default class StatisticsForLayerPanel extends VariableUiElement {
return new Loading("Loading data")
}
if (features.length === 0) {
return "No elements in view"
return new Combine([
"No elements in view for layer ",
layer.id
]).SetClass("block")
}
const els: BaseUIElement[] = []
const featuresForLayer = features
if (featuresForLayer.length === 0) {
return
}
els.push(new Title(layer.name.Clone(), 1).SetClass("mt-8"))
els.push(new Title(layer.name, 1).SetClass("mt-8"))
const layerStats = []
for (const tagRendering of layer?.tagRenderings ?? []) {

View file

@ -8,7 +8,9 @@ import { OsmFeature } from "../../Models/OsmFeature"
export interface TagRenderingChartOptions {
groupToOtherCutoff?: 3 | number
sort?: boolean
sort?: boolean,
hideUnkown?: boolean,
hideNotApplicable?: boolean
}
export class StackedRenderingChart extends ChartJs {
@ -19,12 +21,16 @@ export class StackedRenderingChart extends ChartJs {
period: "day" | "month"
groupToOtherCutoff?: 3 | number
// If given, take the sum of these fields to get the feature weight
sumFields?: string[]
sumFields?: string[],
hideUnknown?: boolean,
hideNotApplicable?: boolean
}
) {
const { labels, data } = TagRenderingChart.extractDataAndLabels(tr, features, {
sort: true,
groupToOtherCutoff: options?.groupToOtherCutoff,
hideNotApplicable: options?.hideNotApplicable,
hideUnkown: options?.hideUnknown
})
if (labels === undefined || data === undefined) {
console.error(
@ -36,7 +42,6 @@ export class StackedRenderingChart extends ChartJs {
)
throw "No labels or data given..."
}
// labels: ["cyclofix", "buurtnatuur", ...]; data : [ ["cyclofix-changeset", "cyclofix-changeset", ...], ["buurtnatuur-cs", "buurtnatuur-cs"], ... ]
for (let i = labels.length; i >= 0; i--) {
if (data[i]?.length != 0) {
@ -116,13 +121,13 @@ export class StackedRenderingChart extends ChartJs {
datasets.push({
data: countsPerDay,
backgroundColor,
label,
label
})
}
const perDayData = {
labels: trimmedDays,
datasets,
datasets
}
const config = <ChartConfiguration>{
@ -131,17 +136,17 @@ export class StackedRenderingChart extends ChartJs {
options: {
responsive: true,
legend: {
display: false,
display: false
},
scales: {
x: {
stacked: true,
stacked: true
},
y: {
stacked: true,
},
},
},
stacked: true
}
}
}
}
super(config)
}
@ -194,7 +199,7 @@ export default class TagRenderingChart extends Combine {
"rgba(255, 206, 86, 0.2)",
"rgba(75, 192, 192, 0.2)",
"rgba(153, 102, 255, 0.2)",
"rgba(255, 159, 64, 0.2)",
"rgba(255, 159, 64, 0.2)"
]
public static readonly borderColors = [
@ -203,7 +208,7 @@ export default class TagRenderingChart extends Combine {
"rgba(255, 206, 86, 1)",
"rgba(75, 192, 192, 1)",
"rgba(153, 102, 255, 1)",
"rgba(255, 159, 64, 1)",
"rgba(255, 159, 64, 1)"
]
/**
@ -239,12 +244,12 @@ export default class TagRenderingChart extends Combine {
const borderColor = [
TagRenderingChart.unkownBorderColor,
TagRenderingChart.otherBorderColor,
TagRenderingChart.notApplicableBorderColor,
TagRenderingChart.notApplicableBorderColor
]
const backgroundColor = [
TagRenderingChart.unkownColor,
TagRenderingChart.otherColor,
TagRenderingChart.notApplicableColor,
TagRenderingChart.notApplicableColor
]
while (borderColor.length < data.length) {
@ -276,17 +281,17 @@ export default class TagRenderingChart extends Combine {
backgroundColor,
borderColor,
borderWidth: 1,
label: undefined,
},
],
label: undefined
}
]
},
options: {
plugins: {
legend: {
display: !barchartMode,
},
},
},
display: !barchartMode
}
}
}
}
const chart = new ChartJs(config).SetClass(options?.chartclasses ?? "w-32 h-32")
@ -297,7 +302,7 @@ export default class TagRenderingChart extends Combine {
super([
options?.includeTitle ? tagRendering.question.Clone() ?? tagRendering.id : undefined,
chart,
chart
])
this.SetClass("block")
@ -386,20 +391,26 @@ export default class TagRenderingChart extends Combine {
}
}
const labels = [
"Unknown",
"Other",
"Not applicable",
const labels = []
const data: T[][] = []
if (!options.hideUnkown) {
data.push(unknownCount)
labels.push("Unknown")
}
data.push(otherGrouped)
labels.push("Other")
if (!options.hideNotApplicable) {
data.push(notApplicable)
labels.push(
"Not applicable"
)
}
data.push(...categoryCounts,
...otherData)
labels.push(
...(mappings?.map((m) => m.then.txt) ?? []),
...otherLabels,
]
const data: T[][] = [
unknownCount,
otherGrouped,
notApplicable,
...categoryCounts,
...otherData,
]
...otherLabels)
return { labels, data }
}

View file

@ -6,7 +6,6 @@
import Constants from "../../Models/Constants"
import type { LayoutInformation } from "../../Models/ThemeConfig/LayoutConfig"
import Tr from "../Base/Tr.svelte"
import SubtleLink from "../Base/SubtleLink.svelte"
import Translations from "../i18n/Translations"
import { LocalStorageSource } from "../../Logic/Web/LocalStorageSource"
@ -86,8 +85,10 @@
</script>
{#if theme.id !== personal.id || $unlockedPersonal}
<SubtleLink href={$href} options={{ extraClasses: "w-full" }}>
<img slot="image" src={theme.icon} class="m-1 mr-2 block h-11 w-11 sm:m-2 sm:mr-4" alt="" />
<a
class={"w-full button text-ellipsis"}
href={$href}
> <img src={theme.icon} class="m-1 mr-2 block h-11 w-11 sm:m-2 sm:mr-4" alt="" />
<span class="flex flex-col overflow-hidden text-ellipsis">
<Tr t={title} />
@ -96,6 +97,5 @@
<Tr t={Translations.t.general.morescreen.enterToOpen} />
</span>
{/if}
</span>
</SubtleLink>
</span></a>
{/if}

View file

@ -23,18 +23,20 @@
import { GeoOperations } from "../../Logic/GeoOperations"
import { BBox } from "../../Logic/BBox"
import type { Feature, LineString, Point } from "geojson"
import type { SpecialVisualizationState } from "../SpecialVisualization"
import SmallZoomButtons from "../Map/SmallZoomButtons.svelte"
const splitpoint_style = new LayerConfig(
<LayerConfigJson>split_point,
"(BUILTIN) SplitRoadWizard.ts",
true
) as const
true,
)
const splitroad_style = new LayerConfig(
<LayerConfigJson>split_road,
"(BUILTIN) SplitRoadWizard.ts",
true
) as const
true,
)
/**
* The way to focus on
@ -45,6 +47,7 @@
* A default is given
*/
export let layer: LayerConfig = splitroad_style
export let state: SpecialVisualizationState | undefined = undefined
/**
* Optional: use these properties to set e.g. background layer
*/
@ -58,6 +61,7 @@
adaptor.bounds.setData(BBox.get(wayGeojson).pad(2))
adaptor.maxbounds.setData(BBox.get(wayGeojson).pad(2))
state?.showCurrentLocationOn(map)
new ShowDataLayer(map, {
features: new StaticFeatureSource([wayGeojson]),
drawMarkers: false,
@ -101,6 +105,7 @@
})
</script>
<div class="h-full w-full">
<MaplibreMap {map} />
<div class="h-full w-full relative">
<MaplibreMap {map} mapProperties={adaptor} />
<SmallZoomButtons {adaptor} />
</div>