Merge develop

This commit is contained in:
Pieter Vander Vennet 2025-08-31 15:21:28 +02:00
commit 0969558b97
74 changed files with 1785 additions and 637 deletions

View file

@ -43,11 +43,11 @@
}
}}
>
<div class="interactive sticky top-0 flex items-center justify-between">
<TabList class="flex flex-wrap">
<div class="tablist sticky top-0 flex items-center justify-center">
<TabList class="flex items-center justify-center">
{#if $$slots.title0}
<Tab
class={({ selected }) => twJoin("tab", selected && "primary", !$condition0 && "hidden")}
class={({ selected }) => twJoin("tab", selected ? "tab-selected": "tab-unselected", !$condition0 && "hidden")}
>
<div bind:this={tabElements[0]} class="flex">
<slot name="title0">Tab 0</slot>
@ -56,7 +56,7 @@
{/if}
{#if $$slots.title1}
<Tab
class={({ selected }) => twJoin("tab", selected && "primary", !$condition1 && "hidden")}
class={({ selected }) => twJoin("tab", selected ? "tab-selected": "tab-unselected", !$condition1 && "hidden")}
>
<div bind:this={tabElements[1]} class="flex">
<slot name="title1" />
@ -65,7 +65,7 @@
{/if}
{#if $$slots.title2}
<Tab
class={({ selected }) => twJoin("tab", selected && "primary", !$condition2 && "hidden")}
class={({ selected }) => twJoin("tab", selected ? "tab-selected": "tab-unselected", !$condition2 && "hidden")}
>
<div bind:this={tabElements[2]} class="flex">
<slot name="title2" />
@ -74,7 +74,7 @@
{/if}
{#if $$slots.title3}
<Tab
class={({ selected }) => twJoin("tab", selected && "primary", !$condition3 && "hidden")}
class={({ selected }) => twJoin("tab", selected ? "tab-selected": "tab-unselected", !$condition3 && "hidden")}
>
<div bind:this={tabElements[3]} class="flex">
<slot name="title3" />
@ -83,7 +83,7 @@
{/if}
{#if $$slots.title4}
<Tab
class={({ selected }) => twJoin("tab", selected && "primary", !$condition4 && "hidden")}
class={({ selected }) => twJoin("tab", selected ? "tab-selected": "tab-unselected", !$condition4 && "hidden")}
>
<div bind:this={tabElements[4]} class="flex">
<slot name="title4" />
@ -92,7 +92,7 @@
{/if}
{#if $$slots.title5}
<Tab
class={({ selected }) => twJoin("tab", selected && "primary", !$condition5 && "hidden")}
class={({ selected }) => twJoin("tab", selected ? "tab-selected": "tab-unselected", !$condition5 && "hidden")}
>
<div bind:this={tabElements[5]} class="flex">
<slot name="title5" />
@ -101,7 +101,7 @@
{/if}
{#if $$slots.title6}
<Tab
class={({ selected }) => twJoin("tab", selected && "primary", !$condition6 && "hidden")}
class={({ selected }) => twJoin("tab", selected ? "tab-selected": "tab-unselected", !$condition6 && "hidden")}
>
<div bind:this={tabElements[6]} class="flex">
<slot name="title6" />
@ -167,19 +167,6 @@
height: calc(100% - 2rem);
}
:global(.tab) {
margin: 0.25rem;
padding: 0.25rem;
padding-left: 0.75rem;
padding-right: 0.75rem;
border-radius: 1rem;
}
:global(.tab .flex) {
align-items: center;
gap: 0.25rem;
}
:global(.tab span|div) {
align-items: center;
gap: 0.25rem;
@ -190,8 +177,4 @@
fill: var(--interactive-contrast);
}
:global(.tab-unselected) {
background-color: var(--background-color) !important;
color: var(--foreground-color) !important;
}
</style>

View file

@ -55,15 +55,13 @@
import ImageUploadQueue from "../../Logic/ImageProviders/ImageUploadQueue"
import QueuedImagesView from "../Image/QueuedImagesView.svelte"
import InsetSpacer from "../Base/InsetSpacer.svelte"
import UserCircle from "@rgossiaux/svelte-heroicons/solid/UserCircle"
import OfflineManagement from "./OfflineManagement.svelte"
import { GlobeEuropeAfrica } from "@babeard/svelte-heroicons/solid/GlobeEuropeAfrica"
import { onDestroy } from "svelte"
import Avatar from "../Base/Avatar.svelte"
import { SpecialVisualizationSvelte } from "../SpecialVisualization"
import ThemeViewState from "../../Models/ThemeViewState"
import { Changes } from "../../Logic/Osm/Changes"
import PendingChangesView from "./PendingChangesView.svelte"
import { DevicePhoneMobileIcon } from "@babeard/svelte-heroicons/solid"
export let state: {
favourites: FavouritesFeatureSource
@ -236,6 +234,12 @@
<Tr t={Translations.t.general.attribution.emailCreators} />
</a>
{#if !$isAndroid}
<a href="https://app.mapcomplete.org">
<DevicePhoneMobileIcon class="h-6 w-6"/>
<Tr t={Translations.t.general.menu.downloadApp}/>
</a>
{/if}
<a class="flex" href="https://en.osm.town/@MapComplete" target="_blank">
<Mastodon class="h-6 w-6" />
<Tr t={Translations.t.general.attribution.followOnMastodon} />

View file

@ -12,9 +12,9 @@
import Translations from "../i18n/Translations"
import type { TagRenderingConfigJson } from "../../Models/ThemeConfig/Json/TagRenderingConfigJson"
import { Or } from "../../Logic/Tags/Or"
import { Utils } from "../../Utils"
import ChartJs from "../Base/ChartJs.svelte"
import { ChartJsUtils } from "../Base/ChartJsUtils"
import { Lists } from "../../Utils/Lists"
export let onlyShowUsername: string[]
export let features: Feature[]

View file

@ -33,6 +33,9 @@
import GeocodeResults from "./Search/GeocodeResults.svelte"
import MagnifyingGlassCircle from "@babeard/svelte-heroicons/mini/MagnifyingGlassCircle"
import type { GeocodeResult } from "../Logic/Search/GeocodingProvider"
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@rgossiaux/svelte-headlessui"
import WikipediaTitle from "./Wikipedia/WikipediaTitle.svelte"
import WikipediaArticle from "./Wikipedia/WikipediaArticle.svelte"
console.log("Loading inspector GUI")
let username = QueryParameters.GetQueryParameter("user", undefined, "Inspect this user")
@ -49,7 +52,7 @@
new CoordinateSearch(),
new OpenLocationCodeSearch(),
new PhotonSearch(true, 2),
new PhotonSearch()
new PhotonSearch(),
)
let showSearchDrawer = new UIEventSource(true)
let searchIsFocussed = new UIEventSource(false)
@ -138,7 +141,7 @@
const overpass = new Overpass(
Constants.defaultOverpassUrls[0],
undefined,
user.split(";").map((user) => 'nw(user_touched:"' + user + '");')
user.split(";").map((user) => "nw(user_touched:\"" + user + "\");"),
)
if (!maplibremap.bounds.data) {
return
@ -161,8 +164,6 @@
return true
})
let mode: "map" | "table" | "aggregate" | "images" = "map"
let showPreviouslyVisited = new UIEventSource(true)
const t = Translations.t.inspector
@ -181,8 +182,8 @@
<div class="flex h-screen w-full flex-col">
<div class="low-interaction flex flex-wrap items-center gap-x-2 p-2">
<MagnifyingGlassCircle class="h-12 w-12" />
<h1 class="m-0 mx-2 flex-shrink-0">
<MagnifyingGlassCircle class="h-6 w-6" />
<Tr t={t.title} />
</h1>
<ValidatedInput
@ -207,89 +208,98 @@
</a>
</div>
<div class="flex">
<button class:primary={mode === "map"} on:click={() => (mode = "map")}>
<Tr t={t.mapView} />
</button>
<button class:primary={mode === "table"} on:click={() => (mode = "table")}>
<Tr t={t.tableView} />
</button>
<button class:primary={mode === "aggregate"} on:click={() => (mode = "aggregate")}>
<Tr t={t.aggregateView} />
</button>
<button class:primary={mode === "images"} on:click={() => (mode = "images")}>
<Tr t={t.images} />
</button>
</div>
<TabGroup class="flex-grow flex flex-col">
<TabList class="tablist">
<Tab class={({ selected }) => ("tab "+ (selected ? "tab-selected" : "tab-unselected"))}>
{#if mode === "map"}
{#if $selectedElement !== undefined}
<!-- right modal with the selected element view -->
<Drawer
placement="right"
transitionType="fly"
activateClickOutside={false}
backdrop={false}
id="drawer-right"
width="w-full md:w-6/12 lg:w-5/12 xl:w-4/12"
rightOffset="inset-y-0 right-0"
transitionParams={{
<Tr t={t.mapView} />
</Tab>
<Tab class={({ selected }) => ("tab "+ (selected ? "tab-selected" : "tab-unselected"))}>
<Tr t={t.tableView} />
</Tab>
<Tab class={({ selected }) => ("tab "+ (selected ? "tab-selected" : "tab-unselected"))}>
<Tr t={t.aggregateView} />
</Tab>
<Tab class={({ selected }) => ("tab "+ (selected ? "tab-selected" : "tab-unselected"))}>
<Tr t={t.images} />
</Tab>
</TabList>
<TabPanels class="flex-grow flex flex-col">
<TabPanel class="flex-grow">
{#if $selectedElement !== undefined}
<!-- right modal with the selected element view -->
<Drawer
placement="right"
transitionType="fly"
activateClickOutside={false}
backdrop={false}
id="drawer-right"
width="w-full md:w-6/12 lg:w-5/12 xl:w-4/12"
rightOffset="inset-y-0 right-0"
transitionParams={{
x: 640,
duration: 0,
easing: linear,
}}
divClass="overflow-y-auto z-50 bg-white"
hidden={$selectedElement === undefined}
on:close={() => {
divClass="overflow-y-auto z-50 bg-white"
hidden={$selectedElement === undefined}
on:close={() => {
selectedElement.setData(undefined)
}}
>
<TitledPanel>
<div slot="title" class="flex justify-between">
<a
target="_blank"
rel="noopener"
href={"https://osm.org/" + $selectedElement.properties.id}
>
{$selectedElement.properties.id}
</a>
<XCircleIcon class="h-6 w-6" on:click={() => selectedElement.set(undefined)} />
</div>
>
<TitledPanel>
<div slot="title" class="flex justify-between">
<a
target="_blank"
rel="noopener"
href={"https://osm.org/" + $selectedElement.properties.id}
>
{$selectedElement.properties.id}
</a>
<XCircleIcon class="h-6 w-6" on:click={() => selectedElement.set(undefined)} />
</div>
<History onlyShowChangesBy={$username.split(";")} id={$selectedElement.properties.id} />
</TitledPanel>
</Drawer>
{/if}
<div class="relative m-1 flex-grow overflow-hidden rounded-xl">
<MaplibreMap {map} mapProperties={maplibremap} autorecovery={true} />
<div class="absolute right-0 top-0 w-1/4 p-4">
<Searchbar
on:search={() => search()}
isFocused={searchIsFocussed}
value={searchvalue}
on:focus={() => state.searchState.showSearchDrawer.set(true)}
/>
{#if $searchSuggestions?.length > 0 || $searchIsFocussed}
<GeocodeResults {state} on:select={(event) => search(event.detail)} />
<History onlyShowChangesBy={$username.split(";")} id={$selectedElement.properties.id} />
</TitledPanel>
</Drawer>
{/if}
</div>
</div>
{:else if mode === "table"}
<div class="m-2 h-full overflow-y-auto">
{#each $featuresStore as f}
<History onlyShowChangesBy={$username?.split(";")} id={f.properties.id} />
{/each}
</div>
{:else if mode === "aggregate"}
<div class="m-2 h-full overflow-y-auto">
<AggregateView features={$featuresStore} onlyShowUsername={$username?.split(";")} />
</div>
{:else if mode === "images"}
<div class="m-2 h-full overflow-y-auto">
<AggregateImages features={$featuresStore} onlyShowUsername={$username?.split(";")} />
</div>
{/if}
<div class="relative m-1 h-full flex-grow overflow-hidden rounded-xl">
<MaplibreMap {map} mapProperties={maplibremap} autorecovery={true} />
<div class="absolute right-0 top-0 w-1/4 p-4">
<Searchbar
on:search={() => search()}
isFocused={searchIsFocussed}
value={searchvalue}
on:focus={() => state.searchState.showSearchDrawer.set(true)}
/>
{#if $searchSuggestions?.length > 0 || $searchIsFocussed}
<GeocodeResults {state} on:select={(event) => search(event.detail)} />
{/if}
</div>
</div>
</TabPanel>
<TabPanel class="overflow-auto grow-0">
{#each $featuresStore as f}
<History onlyShowChangesBy={$username?.split(";")} id={f.properties.id} />
{/each}
</TabPanel>
<TabPanel >
<AggregateView features={$featuresStore} onlyShowUsername={$username?.split(";")} />
</TabPanel>
<TabPanel>
<AggregateImages features={$featuresStore} onlyShowUsername={$username?.split(";")} />
</TabPanel>
</TabPanels>
</TabGroup>
</div>
<Page shown={showPreviouslyVisited}>

View file

@ -30,6 +30,8 @@ import Tr from "../Base/Tr.svelte"
import Combine from "../Base/Combine"
import Marker from "../Map/Marker.svelte"
import { twJoin } from "tailwind-merge"
import { Tag } from "../../Logic/Tags/Tag"
import { Lists } from "../../Utils/Lists"
class DirectionIndicatorVis extends SpecialVisualizationSvelte {
funcName = "direction_indicator"
@ -243,17 +245,24 @@ class PresetTypeSelect extends SpecialVisualizationSvelte {
console.warn("Trying to use the _original_ layer")
layer = state.theme.layers.find((l) => l.id === layer._basedOn) ?? layer
}
const allKeys = Lists.dedup(layer.presets.flatMap(preset => preset.tags.flatMap(tag => tag.usedKeys())))
const question: QuestionableTagRenderingConfigJson = {
id: layer.id + "-type",
question: t.question.translations,
mappings: layer.presets.map((pr) => ({
if: new And(pr.tags).asJson(),
icon: "auto",
then: (pr.description ? t.typeDescription : t.typeTitle).Subs({
title: pr.title,
description: pr.description,
}).translations,
})),
mappings: layer.presets.map((pr) => {
const presetKeys = new Set(pr.tags.flatMap(t => t.key))
const keysToRemove = allKeys.filter(k => !presetKeys.has(k)).map(k => new Tag(k, ""))
return ({
if: new And([...pr.tags, ...keysToRemove]).asJson(),
icon: "auto",
then: (pr.description ? t.typeDescription : t.typeTitle).Subs({
title: pr.title,
description: pr.description,
}).translations,
})
}),
}
if (question.mappings.length === 0) {
console.error("No mappings for preset_type_select, something went wrong")

View file

@ -12,6 +12,7 @@
import { Utils } from "../../../Utils"
import { twMerge } from "tailwind-merge"
import EditButton from "./EditButton.svelte"
import { Strings } from "../../../Utils/Strings"
export let config: TagRenderingConfig
export let tags: UIEventSource<Record<string, string>>
@ -83,7 +84,7 @@
onDestroy(highlightedRendering?.addCallbackAndRun(() => setHighlighting()))
onDestroy(_htmlElement.addCallbackAndRun(() => setHighlighting()))
}
let answerId = "answer-" + Utils.randomString(5)
let answerId = "answer-" + Strings.randomString(5)
let debug = state?.featureSwitches?.featureSwitchIsDebugging ?? new ImmutableStore(false)
let apiState: Store<string> = state?.osmConnection?.apiIsOnline ?? new ImmutableStore("online")

View file

@ -44,8 +44,9 @@
}
function getAutoIcon(mapping: { readonly if?: TagsFilter }): Readonly<Record<string, string>> {
const ifTags = TagUtils.removeEmptyParts(mapping.if)
for (const preset of layer.presets) {
if (!new And(preset.tags).shadows(mapping.if)) {
if (!new And(preset.tags).shadows(ifTags)) {
continue
}

View file

@ -1,12 +1,7 @@
import { ConfigMeta } from "./configMeta"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"
import {
Conversion,
ConversionMessage,
DesugaringContext,
Pipe,
} from "../../Models/ThemeConfig/Conversion/Conversion"
import { Conversion, ConversionMessage, DesugaringContext, Pipe } from "../../Models/ThemeConfig/Conversion/Conversion"
import { PrepareLayer } from "../../Models/ThemeConfig/Conversion/PrepareLayer"
import { PrevalidateTheme, ValidateLayer } from "../../Models/ThemeConfig/Conversion/Validation"
import { AllSharedLayers } from "../../Customizations/AllSharedLayers"
@ -26,6 +21,7 @@ import { TagRenderingConfigJson } from "../../Models/ThemeConfig/Json/TagRenderi
import { ValidateTheme } from "../../Models/ThemeConfig/Conversion/ValidateTheme"
import * as questions from "../../../public/assets/generated/layers/questions.json"
import { Lists } from "../../Utils/Lists"
import { Strings } from "../../Utils/Strings"
export interface HighlightedTagRendering {
path: ReadonlyArray<string | number>
@ -431,7 +427,7 @@ export default class EditLayerState extends EditJsonState<LayerConfigJson> {
}
if (!tr["id"] && !tr["override"]) {
const qtr = <QuestionableTagRenderingConfigJson>tr
let id = "" + i + "_" + Utils.randomString(5)
let id = "" + i + "_" + Strings.randomString(5)
if (qtr?.freeform?.key) {
id = qtr?.freeform?.key
} else if (qtr.mappings?.[0]?.if) {

View file

@ -31,9 +31,9 @@
<WikipediaArticle wikipediaDetails={_wikipediaStores[0]} />
{:else}
<TabGroup>
<TabList>
<TabList class="tablist">
{#each _wikipediaStores as store (store.tag)}
<Tab class={({ selected }) => (selected ? "tab-selected" : "tab-unselected")}>
<Tab class={({ selected }) => ("tab "+ (selected ? "tab-selected" : "tab-unselected"))}>
<WikipediaTitle wikipediaDetails={store} />
</Tab>
{/each}
@ -51,14 +51,5 @@
<style>
/* Actually used, don't remove*/
.tab-selected {
background-color: rgb(59 130 246);
color: rgb(255 255 255);
}
/* Actually used, don't remove*/
.tab-unselected {
background-color: rgb(255 255 255);
color: rgb(0 0 0);
}
</style>