Merge master

This commit is contained in:
Pieter Vander Vennet 2023-10-16 13:39:38 +02:00
commit d4d10d1b18
169 changed files with 8582 additions and 1260 deletions

View file

@ -5,23 +5,11 @@ import { Utils } from "../../Utils"
import { Feature } from "geojson"
export default class PendingChangesUploader {
private lastChange: Date
constructor(changes: Changes, selectedFeature: UIEventSource<Feature>) {
const self = this
this.lastChange = new Date()
changes.pendingChanges.addCallback(() => {
self.lastChange = new Date()
changes.pendingChanges.stabilized(Constants.updateTimeoutSec * 1000).addCallback(() => changes.flushChanges("Flushing changes due to timeout"))
window.setTimeout(() => {
const diff = (new Date().getTime() - self.lastChange.getTime()) / 1000
if (Constants.updateTimeoutSec >= diff - 1) {
changes.flushChanges("Flushing changes due to timeout")
}
}, Constants.updateTimeoutSec * 1000)
})
selectedFeature.stabilized(10000).addCallback((feature) => {
selectedFeature.stabilized(1000).addCallback((feature) => {
if (feature === undefined) {
// The popup got closed - we flush
changes.flushChanges("Flushing changes due to popup closed")

View file

@ -97,7 +97,7 @@ export class ImageUploadManager {
console.log("Upload done, creating ")
const action = await this.uploadImageWithLicense(featureId, title, description, file)
if (!isNaN(Number(featureId))) {
// THis is a map note
// This is a map note
const url = action._url
await this._osmConnection.addCommentToNote(featureId, url)
NoteCommentElement.addCommentTo(url, <UIEventSource<any>>tagsStore, {
@ -151,9 +151,13 @@ export class ImageUploadManager {
}
private increaseCountFor(collection: Map<string, UIEventSource<number>>, key: string | "*") {
const counter = this.getCounterFor(collection, key)
counter.setData(counter.data + 1)
const global = this.getCounterFor(collection, "*")
global.setData(counter.data + 1)
{
const counter = this.getCounterFor(collection, key)
counter.setData(counter.data + 1)
}
{
const global = this.getCounterFor(collection, "*")
global.setData(global.data + 1)
}
}
}

View file

@ -26,6 +26,7 @@ export class Changes {
public readonly extraComment: UIEventSource<string> = new UIEventSource(undefined)
public readonly backend: string
public readonly isUploading = new UIEventSource(false)
public readonly errors = new UIEventSource<string[]>([], "upload-errors")
private readonly historicalUserLocations?: FeatureSource
private _nextId: number = -1 // Newly assigned ID's are negative
private readonly previouslyCreated: OsmObject[] = []
@ -128,8 +129,11 @@ export class Changes {
const csNumber = await this.flushChangesAsync()
this.isUploading.setData(false)
console.log("Changes flushed. Your changeset is " + csNumber)
this.errors.setData([])
} catch (e) {
this.isUploading.setData(false)
this.errors.data.push(e)
this.errors.ping()
console.error("Flushing changes failed due to", e)
}
}
@ -415,6 +419,8 @@ export class Changes {
id,
" dropping it from the changes (" + e + ")"
)
this.errors.data.push(e)
this.errors.ping()
return undefined
}
})
@ -572,9 +578,15 @@ export class Changes {
openChangeset.data
)
return await self.flushSelectChanges(pendingChanges, openChangeset)
const result = await self.flushSelectChanges(pendingChanges, openChangeset)
if(result){
this.errors.setData([])
}
return result
} catch (e) {
console.error("Could not upload some changes:", e)
this.errors.data.push(e)
this.errors.ping()
return false
}
})
@ -589,6 +601,8 @@ export class Changes {
"Could not handle changes - probably an old, pending changeset in localstorage with an invalid format; erasing those",
e
)
this.errors.data.push(e)
this.errors.ping()
self.pendingChanges.setData([])
} finally {
self.isUploading.setData(false)

View file

@ -6,7 +6,7 @@ import { AuthConfig } from "../Logic/Osm/AuthConfig"
export type PriviligedLayerType = (typeof Constants.priviliged_layers)[number]
export default class Constants {
public static vNumber = packagefile.version
public static vNumber : string = packagefile.version
/**
* API key for Maproulette
*
@ -63,7 +63,7 @@ export default class Constants {
* Used by 'PendingChangesUploader', which waits this amount of seconds to upload changes.
* (Note that pendingChanges might upload sooner if the popup is closed or similar)
*/
static updateTimeoutSec: number = 30
static updateTimeoutSec: number = 15
/**
* If the contributor has their GPS location enabled and makes a change,
* the points visited less then `nearbyVisitTime`-seconds ago will be inspected.

View file

@ -53,8 +53,6 @@ export class AvailableRasterLayers {
geometry: BBox.global.asGeometry(),
}
public static readonly vectorLayers = [AvailableRasterLayers.maptilerDefaultLayer]
public static layersAvailableAt(
location: Store<{ lon: number; lat: number }>
): Store<RasterLayerPolygon[]> {

View file

@ -29,16 +29,25 @@
* 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>
/**
* The center of the map at all times
* If undefined at the beginning, 'coordinate' will be used
*/
export let value: UIEventSource<{ lon: number; lat: number }>
if (value.data === undefined) {
value.setData(coordinate)
}
if(coordinate === undefined){
coordinate = value.data
}
export let snapToLayers: string[] | undefined
export let targetLayer: LayerConfig | undefined
export let maxSnapDistance: number = undefined
export let snappedTo: UIEventSource<string | undefined>
let preciseLocation: UIEventSource<{ lon: number; lat: number }> = new UIEventSource<{
lon: number
@ -66,12 +75,14 @@
rasterLayer: UIEventSource.feedFrom(state.mapProperties.rasterLayer),
}
const featuresForLayer = state.perLayer.get(targetLayer.id)
if (featuresForLayer) {
new ShowDataLayer(map, {
layer: targetLayer,
features: featuresForLayer,
})
if(targetLayer){
const featuresForLayer = state.perLayer.get(targetLayer.id)
if (featuresForLayer) {
new ShowDataLayer(map, {
layer: targetLayer,
features: featuresForLayer,
})
}
}
if (snapToLayers?.length > 0) {
@ -114,4 +125,8 @@
value={preciseLocation}
initialCoordinate={coordinate}
maxDistanceInMeters="50"
/>
>
<slot name="image" slot="image">
<img class="h-full max-h-24" src="./assets/svg/move-arrows.svg" />
</slot>
</LocationInput>

View file

@ -0,0 +1,32 @@
<script lang="ts">
import type { SpecialVisualizationState } from "../SpecialVisualization"
import { Store } from "../../Logic/UIEventSource"
import { Changes } from "../../Logic/Osm/Changes"
import Loading from "../Base/Loading.svelte"
import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"
export let state: SpecialVisualizationState
const changes: Changes = state.changes
const isUploading: Store<boolean> = changes.isUploading
const pendingChangesCount: Store<number> = changes.pendingChanges.map(ls => ls.length)
const errors = changes.errors
</script>
<div class="flex flex-col pointer-events-auto" on:click={() => changes.flushChanges("Pending changes indicator clicked")}>
{#if $isUploading}
<Loading>
<Tr cls="thx" t={Translations.t.general.uploadingChanges} />
</Loading>
{:else if $pendingChangesCount === 1}
<Tr cls="alert" t={Translations.t.general.uploadPendingSingle} />
{:else if $pendingChangesCount > 1}
<Tr cls="alert" t={Translations.t.general.uploadPending.Subs({count: $pendingChangesCount})} />
{/if}
{#each $errors as error}
<Tr cls="alert" t={Translations.t.general.uploadError.Subs({error})} />
{/each}
</div>

View file

@ -1,33 +1,38 @@
<script lang="ts">
/**
* Shows information about how much images are uploaded for the given feature
*/
/**
* Shows information about how much images are uploaded for the given feature
*
* Either pass in a store with tags or a featureId.
*/
import type { SpecialVisualizationState } from "../SpecialVisualization"
import { Store } from "../../Logic/UIEventSource"
import type { OsmTags } from "../../Models/OsmFeature"
import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"
import Loading from "../Base/Loading.svelte"
import type { SpecialVisualizationState } from "../SpecialVisualization"
import { Store } from "../../Logic/UIEventSource"
import type { OsmTags } from "../../Models/OsmFeature"
import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"
import Loading from "../Base/Loading.svelte"
export let state: SpecialVisualizationState
export let tags: Store<OsmTags>
const featureId = tags.data.id
const { uploadStarted, uploadFinished, retried, failed } =
state.imageUploadManager.getCountsFor(featureId)
const t = Translations.t.image
export let state: SpecialVisualizationState
export let tags: Store<OsmTags>
export let featureId = tags.data.id
export let showThankYou: boolean = true
const { uploadStarted, uploadFinished, retried, failed } =
state.imageUploadManager.getCountsFor(featureId)
const t = Translations.t.image
</script>
{#if $uploadStarted == 1}
{#if $uploadFinished == 1}
<Tr cls="thanks" t={t.upload.one.done} />
{:else if $failed == 1}
{#if $uploadStarted === 1}
{#if $uploadFinished === 1}
{#if showThankYou}
<Tr cls="thanks" t={t.upload.one.done} />
{/if}
{:else if $failed === 1}
<div class="alert flex flex-col">
<Tr cls="self-center" t={t.upload.one.failed} />
<Tr t={t.upload.failReasons} />
<Tr t={t.upload.failReasonsAdvanced} />
</div>
{:else if $retried == 1}
{:else if $retried === 1}
<Loading cls="alert">
<Tr t={t.upload.one.retrying} />
</Loading>
@ -37,9 +42,11 @@
</Loading>
{/if}
{:else if $uploadStarted > 1}
{#if $uploadFinished + $failed == $uploadStarted && $uploadFinished > 0}
<Tr cls="thanks" t={t.upload.multiple.done.Subs({ count: $uploadFinished })} />
{:else if $uploadFinished == 0}
{#if $uploadFinished + $failed === $uploadStarted && $uploadFinished > 0}
{#if showThankYou}
<Tr cls="thanks" t={t.upload.multiple.done.Subs({ count: $uploadFinished })} />
{/if}
{:else if $uploadFinished === 0}
<Loading cls="alert">
<Tr t={t.upload.multiple.uploading.Subs({ count: $uploadStarted })} />
</Loading>

View file

@ -89,7 +89,9 @@
<div
class="pointer-events-none absolute top-0 left-0 flex h-full w-full items-center p-8 opacity-50"
>
<img class="h-full max-h-24" src="./assets/svg/move-arrows.svg" />
<slot name="image">
<img class="h-full max-h-24" src="./assets/svg/move-arrows.svg" />
</slot>
</div>
<DragInvitation hideSignal={mla.location} />

View file

@ -422,6 +422,9 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
map.addSource(background.id, MapLibreAdaptor.prepareWmsSource(background))
}
if (!map.getLayer(background.id)) {
addLayerBeforeId ??= map
.getStyle()
.layers.find((l) => l.id.startsWith("mapcomplete_"))?.id
console.log(
"Adding background layer",
background.id,

View file

@ -513,7 +513,7 @@ export default class ShowDataLayer {
const l = new LineRenderingLayer(
map,
features,
this._options.layer.id + "_linerendering_" + i,
"mapcomplete_" + this._options.layer.id + "_linerendering_" + i,
lineRenderingConfig,
doShowLayer,
fetchStore,

View file

@ -162,16 +162,16 @@
<LoginButton osmConnection={state.osmConnection} slot="not-logged-in">
<Tr slot="message" t={Translations.t.general.add.pleaseLogin} />
</LoginButton>
{#if $isLoading}
<div class="alert">
<Loading>
<Tr t={Translations.t.general.add.stillLoading} />
</Loading>
</div>
{:else if $zoom < Constants.minZoomLevelToAddNewPoint}
{#if $zoom < Constants.minZoomLevelToAddNewPoint}
<div class="alert">
<Tr t={Translations.t.general.add.zoomInFurther} />
</div>
{:else if $isLoading}
<div class="alert">
<Loading>
<Tr t={Translations.t.general.add.stillLoading} />
</Loading>
</div>
{:else if selectedPreset === undefined}
<!-- First, select the correct preset -->
<PresetList

View file

@ -30,6 +30,7 @@
if (flayer.isDisplayed.data === false) {
// The layer is not displayed...
if (!state.featureSwitches.featureSwitchFilter.data) {
console.log("Not showing presets for layer", flayer.layerDef.id, "as not displayed and featureSwitchFilter.data is set",state.featureSwitches.featureSwitchFilter.data)
// ...and we cannot enable the layer control -> we skip, as these presets can never be shown anyway
continue
}

View file

@ -2,47 +2,50 @@
/**
* UIcomponent to create a new note at the given location
*/
import type { SpecialVisualizationState } from "../SpecialVisualization"
import { UIEventSource } from "../../Logic/UIEventSource"
import { LocalStorageSource } from "../../Logic/Web/LocalStorageSource"
import ValidatedInput from "../InputElement/ValidatedInput.svelte"
import SubtleButton from "../Base/SubtleButton.svelte"
import Tr from "../Base/Tr.svelte"
import Translations from "../i18n/Translations.js"
import type { Feature, Point } from "geojson"
import LoginToggle from "../Base/LoginToggle.svelte"
import FilteredLayer from "../../Models/FilteredLayer"
import type { SpecialVisualizationState } from "../SpecialVisualization";
import { UIEventSource } from "../../Logic/UIEventSource";
import { LocalStorageSource } from "../../Logic/Web/LocalStorageSource";
import ValidatedInput from "../InputElement/ValidatedInput.svelte";
import SubtleButton from "../Base/SubtleButton.svelte";
import Tr from "../Base/Tr.svelte";
import Translations from "../i18n/Translations.js";
import type { Feature, Point } from "geojson";
import LoginToggle from "../Base/LoginToggle.svelte";
import FilteredLayer from "../../Models/FilteredLayer";
import NewPointLocationInput from "../BigComponents/NewPointLocationInput.svelte";
import ToSvelte from "../Base/ToSvelte.svelte";
import Svg from "../../Svg";
export let coordinate: { lon: number; lat: number }
export let state: SpecialVisualizationState
export let coordinate: UIEventSource<{ lon: number; lat: number }>;
export let state: SpecialVisualizationState;
let comment: UIEventSource<string> = LocalStorageSource.Get("note-text")
let created = false
let comment: UIEventSource<string> = LocalStorageSource.Get("note-text");
let created = false;
let notelayer: FilteredLayer = state.layerState.filteredLayers.get("note")
let notelayer: FilteredLayer = state.layerState.filteredLayers.get("note");
let hasFilter = notelayer?.hasFilter
let isDisplayed = notelayer?.isDisplayed
let hasFilter = notelayer?.hasFilter;
let isDisplayed = notelayer?.isDisplayed;
function enableNoteLayer() {
state.guistate.closeAll()
isDisplayed.setData(true)
state.guistate.closeAll();
isDisplayed.setData(true);
}
async function uploadNote() {
let txt = comment.data
let txt = comment.data;
if (txt === undefined || txt === "") {
return
return;
}
const loc = coordinate
txt += "\n\n #MapComplete #" + state?.layout?.id
const id = await state?.osmConnection?.openNote(loc.lat, loc.lon, txt)
console.log("Created a note, got id", id)
const loc = coordinate.data;
txt += "\n\n #MapComplete #" + state?.layout?.id;
const id = await state?.osmConnection?.openNote(loc.lat, loc.lon, txt);
console.log("Created a note, got id", id);
const feature = <Feature<Point>>{
type: "Feature",
geometry: {
type: "Point",
coordinates: [loc.lon, loc.lat],
coordinates: [loc.lon, loc.lat]
},
properties: {
id: "" + id.id,
@ -53,22 +56,22 @@
text: txt,
html: txt,
user: state.osmConnection?.userDetails?.data?.name,
uid: state.osmConnection?.userDetails?.data?.uid,
},
]),
},
}
uid: state.osmConnection?.userDetails?.data?.uid
}
])
}
};
// Normally, the 'Changes' will generate the new element. The 'notes' are an exception to this
state.newFeatures.features.data.push(feature)
state.newFeatures.features.ping()
state.selectedElement?.setData(feature)
state.newFeatures.features.data.push(feature);
state.newFeatures.features.ping();
state.selectedElement?.setData(feature);
if (state.featureProperties.trackFeature) {
state.featureProperties.trackFeature(feature)
state.featureProperties.trackFeature(feature);
}
comment.setData("")
created = true
state.selectedElement.setData(feature)
state.selectedLayer.setData(state.layerState.filteredLayers.get("note"))
comment.setData("");
created = true;
state.selectedElement.setData(feature);
state.selectedLayer.setData(state.layerState.filteredLayers.get("note"));
}
</script>
@ -106,6 +109,15 @@
<ValidatedInput type="text" value={comment} />
</div>
<div class="w-full h-56">
<NewPointLocationInput value={coordinate} {state} >
<div class="h-20 w-full pb-10" slot="image">
<ToSvelte construct={Svg.note_svg().SetClass("h-10 w-full")}/>
</div>
</NewPointLocationInput>
</div>
<LoginToggle {state}>
<span slot="loading"><!--empty: don't show a loading message--></span>
<div slot="not-logged-in" class="alert">

View file

@ -74,7 +74,7 @@ import NearbyImagesSearch from "../Logic/Web/NearbyImagesSearch"
import AllReviews from "./Reviews/AllReviews.svelte"
import StarsBarIcon from "./Reviews/StarsBarIcon.svelte"
import ReviewForm from "./Reviews/ReviewForm.svelte"
import Questionbox from "./Popup/TagRendering/Questionbox.svelte";
import Questionbox from "./Popup/TagRendering/Questionbox.svelte"
class NearbyImageVis implements SpecialVisualization {
// Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
@ -181,7 +181,6 @@ class StealViz implements SpecialVisualization {
}
}
/**
* Thin wrapper around QuestionBox.svelte to include it into the special Visualisations
*/
@ -189,7 +188,7 @@ export class QuestionViz implements SpecialVisualization {
funcName = "questions"
needsUrls = []
docs =
"The special element which shows the questions which are unkown. Added by default if not yet there"
"The special element which shows the questions which are unkown. Added by default if not yet there"
args = [
{
name: "labels",
@ -202,20 +201,20 @@ export class QuestionViz implements SpecialVisualization {
]
constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
args: string[],
feature: Feature,
layer: LayerConfig
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
args: string[],
feature: Feature,
layer: LayerConfig
): BaseUIElement {
const labels = args[0]
?.split(";")
?.map((s) => s.trim())
?.filter((s) => s !== "")
?.split(";")
?.map((s) => s.trim())
?.filter((s) => s !== "")
const blacklist = args[1]
?.split(";")
?.map((s) => s.trim())
?.filter((s) => s !== "")
?.split(";")
?.map((s) => s.trim())
?.filter((s) => s !== "")
return new SvelteUIElement(Questionbox, {
layer,
tags,
@ -564,7 +563,10 @@ export default class SpecialVisualizations {
feature: Feature
): BaseUIElement {
const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
return new SvelteUIElement(CreateNewNote, { state, coordinate: { lon, lat } })
return new SvelteUIElement(CreateNewNote, {
state,
coordinate: new UIEventSource({ lon, lat }),
})
},
},
new CloseNoteButton(),

View file

@ -16,7 +16,6 @@
import Geosearch from "./BigComponents/Geosearch.svelte"
import Translations from "./i18n/Translations"
import { CogIcon, EyeIcon, MenuIcon, XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
import Tr from "./Base/Tr.svelte"
import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte"
import FloatOver from "./Base/FloatOver.svelte"
@ -52,6 +51,8 @@
import LanguagePicker from "./LanguagePicker"
import Locale from "./i18n/Locale"
import ShareScreen from "./BigComponents/ShareScreen.svelte"
import UploadingImageCounter from "./Image/UploadingImageCounter.svelte"
import PendingChangesIndicator from "./BigComponents/PendingChangesIndicator.svelte"
export let state: ThemeViewState
let layout = state.layout
@ -154,6 +155,8 @@
<ToSvelte
construct={() => new ExtraLinkButton(state, layout.extraLink).SetClass("pointer-events-auto")}
/>
<UploadingImageCounter {state} featureId="*" showThankYou={false}/>
<PendingChangesIndicator {state}/>
<If condition={state.featureSwitchIsTesting}>
<div class="alert w-fit">Testmode</div>
</If>

View file

@ -1,7 +1,7 @@
{
"contributors": [
{
"commits": 6085,
"commits": 6092,
"contributor": "Pieter Vander Vennet"
},
{

View file

@ -1,5 +1,17 @@
{
"layers": [
{
"name": "OpenStreetMap Carto",
"url": "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
"category": "osmbasedmap",
"id": "osm.carto",
"type": "raster",
"max_zoom": 19,
"attribution": {
"text": "OpenStreetMap",
"url": "https://osm.org/copyright"
}
},
{
"name": "Americana",
"url": "https://zelonewolf.github.io/openstreetmap-americana/style.json",

View file

@ -1,7 +1,7 @@
{
"contributors": [
{
"commits": 310,
"commits": 311,
"contributor": "kjon"
},
{
@ -348,6 +348,10 @@
"commits": 3,
"contributor": "SiegbjornSitumeang"
},
{
"commits": 2,
"contributor": "macpac"
},
{
"commits": 2,
"contributor": "Peter Brodersen"
@ -444,6 +448,10 @@
"commits": 2,
"contributor": "Leo Alcaraz"
},
{
"commits": 1,
"contributor": "Michal Čermák"
},
{
"commits": 1,
"contributor": "Kelson Vibber"