Merge branch 'develop' into feature/360-view

This commit is contained in:
Pieter Vander Vennet 2025-03-29 00:50:32 +01:00
commit fa9e61c2b5
230 changed files with 45510 additions and 33214 deletions

View file

@ -6,18 +6,26 @@
export let osmConnection: OsmConnection
export let clss: string | undefined = undefined
/**
* Show the button, even though we are logged in
*/
export let forceShow: boolean = false
export let msg: String = undefined
if (osmConnection === undefined) {
console.error("No osmConnection passed into loginButton")
}
let isLoggedIn = osmConnection.isLoggedIn
</script>
{#if !$isLoggedIn}
{#if !$isLoggedIn || forceShow}
<button class={clss} on:click={() => osmConnection.AttemptLogin()} style="margin-left: 0">
<ArrowLeftOnRectangle class="m-1 w-12" />
<slot>
<Tr t={Translations.t.general.loginWithOpenStreetMap} />
{#if msg}
{msg}
{:else}
<Tr t={Translations.t.general.loginWithOpenStreetMap} />
{/if}
</slot>
</button>
{/if}

View file

@ -24,11 +24,13 @@
let dialogClass =
"fixed top-0 start-0 end-0 h-modal inset-0 w-full p-4 flex border-none " + zIndex
export let backdropClass = "fixed inset-0 z-40 bg-gray-900 bg-opacity-50 dark:bg-opacity-80 frozen-glass"
export let backdropClass =
"fixed inset-0 z-40 bg-gray-900 bg-opacity-50 dark:bg-opacity-80 frozen-glass"
if (fullscreen) {
dialogClass += " h-full-child"
}
let bodyClass = bodyPadding + " h-full space-y-4 flex-1 overflow-y-auto overscroll-contain background-normal"
let bodyClass =
bodyPadding + " h-full space-y-4 flex-1 overflow-y-auto overscroll-contain background-normal"
let headerClass = "flex justify-between items-center p-2 px-4 md:px-5 rounded-t-lg"
if (!$$slots.header) {
@ -43,7 +45,7 @@
<Modal
open={_shown}
on:close={() => shown.set(false)}
outsideclose
outsideclose={dismissable}
size="xl"
{dismissable}
{defaultClass}

View file

@ -88,8 +88,7 @@
image={providedImage}
imgClass="max-h-64 w-auto sm:h-32 md:h-64"
attributionFormat="minimal"
>
</AttributedImage>
/>
<LoginToggle {state} silentFail={true}>
{#if linkable}
<label>

View file

@ -24,16 +24,20 @@
let enableLogin = state.featureSwitches.featureSwitchEnableLogin
export let shown = new UIEventSource(false)
onDestroy(MenuState.nearbyImagesFeature.addCallback(something => {
if (something !== feature) {
shown.set(false)
}
}))
onDestroy(shown.addCallbackAndRun(isShown => {
if (isShown) {
MenuState.nearbyImagesFeature.set(feature)
}
}))
onDestroy(
MenuState.nearbyImagesFeature.addCallback((something) => {
if (something !== feature) {
shown.set(false)
}
})
)
onDestroy(
shown.addCallbackAndRun((isShown) => {
if (isShown) {
MenuState.nearbyImagesFeature.set(feature)
}
})
)
</script>
{#if enableLogin.data}

View file

@ -56,13 +56,13 @@
let searchIsRunning = new UIEventSource(false)
let maplibremap: MapLibreAdaptor = new MapLibreAdaptor(map, {
zoom,
location: new UIEventSource<{ lon: number; lat: number }>({ lat: lat.data, lon: lon.data })
location: new UIEventSource<{ lon: number; lat: number }>({ lat: lat.data, lon: lon.data }),
})
maplibremap.location.stabilized(500).addCallbackAndRunD((l) => {
lat.set(l.lat)
lon.set(l.lon)
})
let searchSuggestions = searchvalue.bindD(search => {
let searchSuggestions = searchvalue.bindD((search) => {
searchIsRunning.set(true)
try {
return UIEventSource.FromPromise(geocoder.search(search))
@ -79,8 +79,8 @@
showSearchDrawer,
applyGeocodeResult(geocodeResult: GeocodeResult) {
maplibremap.location.set({ lon: geocodeResult.lon, lat: geocodeResult.lat })
}
}
},
},
}
let allLayers = HistoryUtils.personalTheme.layers
let layersNoFixme = allLayers.filter((l) => l.id !== "fixme")
@ -94,7 +94,7 @@
Utils.waitFor(200).then(() => {
selectedElement.set(f)
})
}
},
})
let osmConnection = new OsmConnection()
@ -126,7 +126,7 @@
inspectedData.push({
label: undefined,
visitedTime: new Date().toISOString(),
name: user
name: user,
})
}
inspectedContributors.ping()
@ -136,7 +136,7 @@
featuresStore.set([])
const overpass = new Overpass(
undefined,
user.split(";").map((user) => "nw(user_touched:\"" + user + "\");"),
user.split(";").map((user) => 'nw(user_touched:"' + user + '");'),
Constants.defaultOverpassUrls[0]
)
if (!maplibremap.bounds.data) {
@ -172,7 +172,12 @@
<h1 class="m-0 mx-2 flex-shrink-0">
<Tr t={t.title} />
</h1>
<ValidatedInput placeholder={t.previouslySpied.username} type="string" value={username} on:submit={() => load()} />
<ValidatedInput
placeholder={t.previouslySpied.username}
type="string"
value={username}
on:submit={() => load()}
/>
{#if loadingData}
<Loading />
{:else}
@ -242,12 +247,14 @@
</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 isFocused={searchIsFocussed} value={searchvalue}
on:focus={() => state.searchState.showSearchDrawer.set(true)} />
<Searchbar
isFocused={searchIsFocussed}
value={searchvalue}
on:focus={() => state.searchState.showSearchDrawer.set(true)}
/>
{#if $searchSuggestions?.length > 0 || $searchIsFocussed}
<GeocodeResults {state} />
{/if}

View file

@ -24,13 +24,13 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
"dragRotate",
"dragPan",
"keyboard",
"touchZoomRotate"
"touchZoomRotate",
]
private static maplibre_zoom_handlers = [
"scrollZoom",
"boxZoom",
"doubleClickZoom",
"touchZoomRotate"
"touchZoomRotate",
]
readonly location: UIEventSource<{ lon: number; lat: number }>
private readonly isFlying = new UIEventSource(false)
@ -44,14 +44,14 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
readonly lastClickLocation: Store<
| undefined
| {
lon: number
lat: number
mode: "left" | "right" | "middle"
/**
* The nearest feature from a MapComplete layer
*/
nearestFeature?: Feature
}
lon: number
lat: number
mode: "left" | "right" | "middle"
/**
* The nearest feature from a MapComplete layer
*/
nearestFeature?: Feature
}
>
readonly minzoom: UIEventSource<number>
readonly maxzoom: UIEventSource<number>
@ -141,7 +141,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
const features = map
.queryRenderedFeatures([
[point.x - buffer, point.y - buffer],
[point.x + buffer, point.y + buffer]
[point.x + buffer, point.y + buffer],
])
.filter((f) => f.source.startsWith("mapcomplete_"))
if (features.length === 1) {
@ -281,9 +281,9 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
return {
map: mlmap,
ui: new SvelteUIElement(MaplibreMap, {
map: mlmap
map: mlmap,
}),
mapproperties: new MapLibreAdaptor(mlmap)
mapproperties: new MapLibreAdaptor(mlmap),
}
}
@ -347,7 +347,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
) {
const event = {
date: new Date(),
key: key
key: key,
}
for (let i = 0; i < this._onKeyNavigation.length; i++) {
@ -536,7 +536,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
const bounds = map.getBounds()
const bbox = new BBox([
[bounds.getEast(), bounds.getNorth()],
[bounds.getWest(), bounds.getSouth()]
[bounds.getWest(), bounds.getSouth()],
])
if (this.bounds.data === undefined || !isSetup) {
this.bounds.setData(bbox)
@ -732,14 +732,14 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
type: "raster-dem",
url:
"https://api.maptiler.com/tiles/terrain-rgb/tiles.json?key=" +
Constants.maptilerApiKey
Constants.maptilerApiKey,
})
try {
while (!map?.isStyleLoaded()) {
await Utils.waitFor(250)
}
map.setTerrain({
source: id
source: id,
})
} catch (e) {
console.error(e)
@ -764,7 +764,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
if (this.scaleControl === undefined) {
this.scaleControl = new ScaleControl({
maxWidth: 100,
unit: "metric"
unit: "metric",
})
}
if (!map.hasControl(this.scaleControl)) {
@ -777,7 +777,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
window.requestAnimationFrame(() => {
this._maplibreMap.data?.flyTo({
zoom,
center: [lon, lat]
center: [lon, lat],
})
})
}

View file

@ -593,7 +593,10 @@ export default class ShowDataLayer {
try {
map.resize()
} catch (e) {
console.error("Could not resize the map in preparation of zoomToCurrentFeatures; the error is:", e)
console.error(
"Could not resize the map in preparation of zoomToCurrentFeatures; the error is:",
e
)
}
map.fitBounds(bbox.toLngLat(), {
padding: { top: 10, bottom: 10, left: 10, right: 10 },

View file

@ -11,7 +11,6 @@
import { UIEventSource } from "../../../Logic/UIEventSource"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import { TagUtils } from "../../../Logic/Tags/TagUtils"
import type { UploadableTag } from "../../../Logic/Tags/TagUtils"
import OsmChangeAction from "../../../Logic/Osm/Actions/OsmChangeAction"
import DeleteAction from "../../../Logic/Osm/Actions/DeleteAction"
import ChangeTagAction from "../../../Logic/Osm/Actions/ChangeTagAction"
@ -22,6 +21,7 @@
import Trash from "@babeard/svelte-heroicons/mini/Trash"
import Invalid from "../../../assets/svg/Invalid.svelte"
import { And } from "../../../Logic/Tags/And"
import type { UploadableTag } from "../../../Logic/Tags/TagTypes"
export let state: SpecialVisualizationState
export let deleteConfig: DeleteConfig
@ -46,9 +46,9 @@
const t = Translations.t.delete
let selectedTags: UploadableTag
let selectedTags: UploadableTag[]
let changedProperties = undefined
$: changedProperties = TagUtils.changeAsProperties(selectedTags?.asChange(tags?.data ?? {}) ?? [])
$: changedProperties = TagUtils.changeAsProperties(And.construct(selectedTags)?.asChange(tags?.data ?? {}) ?? [])
let isHardDelete = undefined
$: isHardDelete = changedProperties[DeleteConfig.deleteReasonKey] !== undefined
@ -58,7 +58,7 @@
}
currentState = "applying"
let actionToTake: OsmChangeAction
const changedProperties = TagUtils.changeAsProperties(selectedTags.asChange(tags?.data ?? {}))
const changedProperties = TagUtils.changeAsProperties(And.construct(selectedTags)?.asChange(tags?.data ?? {}))
const deleteReason = changedProperties[DeleteConfig.deleteReasonKey]
if (deleteReason) {
let softDeletionTags: UploadableTag
@ -81,7 +81,7 @@
)
} else {
// no _delete_reason is given, which implies that this is _not_ a deletion but merely a retagging via a nonDeleteMapping
actionToTake = new ChangeTagAction(featureId, selectedTags, tags.data, {
actionToTake = new ChangeTagAction(featureId, new And(selectedTags), tags.data, {
theme: state?.theme?.id ?? "unkown",
changeType: "special-delete",
})

View file

@ -69,7 +69,9 @@
/**
* The tags to apply to mark this answer as "unknown"
*/
let onMarkUnknown: Store<UploadableTag[] | undefined> = tags.mapD((tags) => config.markUnknown(layer, tags))
let onMarkUnknown: Store<UploadableTag[] | undefined> = tags.mapD((tags) =>
config.markUnknown(layer, tags)
)
let unknownModal = new UIEventSource(false)
let searchTerm: UIEventSource<string> = new UIEventSource("")
@ -120,7 +122,7 @@
seenFreeforms.push(newProps[confg.freeform.key])
}
return matches
})
}),
]
if (tgs !== undefined && confg.freeform) {
@ -229,7 +231,7 @@
freeform: $freeformInput,
selectedMapping,
checkedMappings,
currentTags: tags.data
currentTags: tags.data,
},
" --> ",
selectedTags
@ -290,7 +292,7 @@
dispatch("saved", { config, applied: selectedTagsJoined })
const change = new ChangeTagAction(tags.data.id, selectedTagsJoined, tags.data, {
theme: tags.data["_orig_theme"] ?? state.theme?.id,
changeType: "answer"
changeType: "answer",
})
freeformInput.set(undefined)
selectedMapping = undefined
@ -334,7 +336,7 @@
const tagsToSet: UploadableTag[] = onMarkUnknown.data
const change = new ChangeTagAction(tags.data.id, new And(tagsToSet), tags.data, {
theme: tags.data["_orig_theme"] ?? state.theme.id,
changeType: "answer"
changeType: "answer",
})
freeformInput.set(undefined)
selectedMapping = undefined
@ -581,7 +583,7 @@
>
<div class="subtle">
<Tr t={Translations.t.unknown.removedKeys} />
<TagHint tags={$onMarkUnknown}></TagHint>
<TagHint tags={$onMarkUnknown} />
</div>
</If>
<div class="flex w-full justify-end" slot="footer">
@ -613,13 +615,8 @@
<!-- TagRenderingQuestion-buttons -->
<slot name="cancel" />
<slot name="save-button" {selectedTags}>
<!-- Save-button / delete button -->
{#if config.freeform?.key &&
!checkedMappings?.some((m) => m) &&
!$freeformInput && !$freeformInputUnvalidated
&& $tags[config.freeform.key]
&& $isKnown}
{#if config.freeform?.key && !checkedMappings?.some((m) => m) && !$freeformInput && !$freeformInputUnvalidated && $tags[config.freeform.key] && $isKnown}
<button
class="primary flex"
on:click|stopPropagation|preventDefault={() => onSave()}
@ -652,7 +649,10 @@
</div>
{/if}
{#if $featureSwitchIsTesting || $featureSwitchIsDebugging}
<button class="small as-link" on:click={() => console.log("Configuration is ", config)}>
<button
class="small as-link"
on:click={() => console.log("Configuration is ", config)}
>
{config.id}
</button>
{/if}

View file

@ -16,9 +16,9 @@
export let entry: GeocodeResult
export let state: {
mapProperties: MapProperties,
theme?: ThemeConfig,
featureProperties?: FeaturePropertiesStore,
mapProperties: MapProperties
theme?: ThemeConfig
featureProperties?: FeaturePropertiesStore
searchState: Partial<SearchState>
}

View file

@ -16,7 +16,8 @@
searchTerm: UIEventSource<string>
suggestions: Store<GeocodeResult[]>
suggestionsSearchRunning: Store<boolean>
}, mapProperties: MapProperties
}
mapProperties: MapProperties
}
let searchTerm = state.searchState.searchTerm

View file

@ -57,7 +57,6 @@
{/if}
<GeocodeResults {state}>
<svelte:fragment slot="if-no-results">
{#if $recentlySeen?.length > 0}
<SidebarUnit>
<div class="flex justify-between">
@ -67,8 +66,8 @@
<DotMenu>
<button
on:click={() => {
state.userRelatedState.recentlyVisitedSearch.clear()
}}
state.userRelatedState.recentlyVisitedSearch.clear()
}}
>
<TrashIcon />
<Tr t={Translations.t.general.search.deleteSearchHistory} />
@ -85,7 +84,6 @@
</SidebarUnit>
{/if}
</svelte:fragment>
</GeocodeResults>
{#if $allowOtherThemes}

View file

@ -111,12 +111,30 @@ export class SettingsVisualisations {
},
{
funcName: "login_button",
args: [],
args: [
{
name: "force",
doc: "Always show this button, even if logged in",
},
{
name: "message",
doc: "Message to display on the button",
},
],
docs: "Show a login button",
needsUrls: [],
group: "settings",
constr(state: SpecialVisualizationState): SvelteUIElement {
return new SvelteUIElement(LoginButton, { osmConnection: state.osmConnection })
constr(state: SpecialVisualizationState, _, args): SvelteUIElement {
const force = args[0].toLowerCase()
let msg = args[1]
if (msg === "") {
msg = undefined
}
return new SvelteUIElement(LoginButton, {
osmConnection: state.osmConnection,
msg,
forceShow: force === "yes" || force === "true",
})
},
},

View file

@ -8,6 +8,7 @@
import { Utils } from "../../Utils"
import Loading from "../Base/Loading.svelte"
import Checkbox from "../Base/Checkbox.svelte"
import PlantNet from "../../Logic/Web/PlantNet"
let services: MCService[] = []
@ -62,7 +63,7 @@
return "offline"
}
}),
message: osmApi,
message: osmApi
})
}
@ -90,7 +91,7 @@
}
const files: string[] = s["success"]["allFiles"]
return "Contains " + (files.length ?? "no") + " files"
}),
})
})
}
{
@ -106,7 +107,7 @@
return "degraded"
}
}),
message: simpleMessage(testDownload(Constants.panoramax.url + "/api")),
message: simpleMessage(testDownload(Constants.panoramax.url + "/api"))
})
}
{
@ -122,7 +123,7 @@
return "degraded"
}
}),
message: simpleMessage(testDownload(Constants.GeoIpServer + "/ip")),
message: simpleMessage(testDownload(Constants.GeoIpServer + "/ip"))
})
}
@ -141,7 +142,7 @@
}
return "degraded"
}),
message: simpleMessage(status),
message: simpleMessage(status)
})
}
@ -160,7 +161,7 @@
}
return "online"
}),
message: simpleMessage(status),
message: simpleMessage(status)
})
}
@ -199,7 +200,7 @@
const json = JSON.stringify(s["success"], null, " ")
return "Database is " + Math.floor(timediffDays) + " days out of sync\n\n" + json
}),
})
})
}
@ -212,7 +213,7 @@
layer: "food",
z: 14,
x: 8848,
y: 5828,
y: 5828
})
)
services.push({
@ -223,7 +224,7 @@
}
return "online"
}),
message: new ImmutableStore("See SettingUpPSQL.md to fix"),
message: new ImmutableStore("See SettingUpPSQL.md to fix")
})
}
@ -242,7 +243,7 @@
}
return "degraded"
}),
message: status.map((s) => JSON.stringify(s)),
message: status.map((s) => JSON.stringify(s))
})
}
@ -261,7 +262,7 @@
return "online"
}
return "degraded"
}),
})
})
}
@ -280,7 +281,7 @@
}
return "degraded"
}),
message: simpleMessage(status),
message: simpleMessage(status)
})
}
@ -306,7 +307,7 @@
return "online"
}),
message: simpleMessage(status),
message: simpleMessage(status)
})
}
}
@ -319,7 +320,36 @@
return "online"
}
return "offline"
}),
})
})
services.push({
name: "Plantnet",
status: testDownload(PlantNet.baseUrl, true).mapD((r) => {
if (r["success"]) {
return "online"
}
// This code will break in the future. Time to blame past me!
const response = JSON.parse(r["error"].substring("other error: , ".length))
if (response.message === "\"images\" is required") {
// Actual expected behaviour
return "online"
}
console.log("R", response)
return "offline"
})
})
}
{
services.push({
name: "Version Control Server (Forgéjo)",
status: testDownload("https://source.mapcomplete.org", true).mapD(r => {
if (r["success"]) {
return "online"
}
return "offline"
})
})
}

View file

@ -9,7 +9,6 @@
import RawEditor from "./RawEditor.svelte"
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import DeleteButton from "./DeleteButton.svelte"
import { UIEventSource } from "../../Logic/UIEventSource"
import StudioHashSetter from "./StudioHashSetter"
export let state: EditThemeState
@ -57,7 +56,7 @@
let hasErrors = messages.map(
(m: ConversionMessage[]) => m.filter((m) => m.level === "error").length
)
let title = state.getStoreFor(["id"])
let title = state.getStoreFor<string>(["id"])
const wl = window.location
const baseUrl = wl.protocol + "//" + wl.host + "/theme.html?userlayout="

View file

@ -363,7 +363,7 @@
id="theme-search"
type="search"
placeholder="Filter themes by name"
bind:value={themeFilterTerm}
bind:value={$themeFilterTerm}
/>
</label>
</form>

View file

@ -49,6 +49,8 @@
import Loading from "./Base/Loading.svelte"
import { WithSearchState } from "../Models/ThemeViewState/WithSearchState"
import TitleHandler from "../Logic/Actors/TitleHandler"
import Popup from "./Base/Popup.svelte"
import TagRenderingAnswer from "./Popup/TagRendering/TagRenderingAnswer.svelte"
export let state: WithSearchState
new TitleHandler(state.selectedElement, state)
@ -76,6 +78,7 @@
let mapproperties: MapProperties = state.mapProperties
let searchOpened = state.searchState.showSearchDrawer
let metatags = state.userRelatedState.preferencesAsTags
Orientation.singleton.startMeasurements()
let slideDuration = 150 // ms
@ -500,5 +503,39 @@
{/if}
{/if}
{#each theme.popups as popup}
{#if popup.condition.matchesProperties($metatags)}
<Popup shown={new UIEventSource(true)} dismissable={popup.dismissible}>
<TagRenderingAnswer
slot="header"
config={popup.title}
{state}
tags={metatags}
layer={undefined}
selectedElement={{
type: "Feature",
properties: $metatags,
geometry: { type: "Point", coordinates: [0, 0] },
}}
/>
<div class="flex flex-col">
{#each popup.body as body}
<TagRenderingAnswer
config={body}
{state}
tags={metatags}
layer={undefined}
selectedElement={{
type: "Feature",
properties: $metatags,
geometry: { type: "Point", coordinates: [0, 0] },
}}
/>
{/each}
<span class="subtle">{popup.id}</span>
</div>
</Popup>
{/if}
{/each}
<MenuDrawer onlyLink={false} {state} />
</main>