Merge master

This commit is contained in:
Pieter Vander Vennet 2023-10-19 12:24:34 +02:00
commit e060bd3288
67 changed files with 1417 additions and 1010 deletions

View file

@ -5,9 +5,10 @@ import { Utils } from "../../Utils"
import { Feature } from "geojson"
export default class PendingChangesUploader {
constructor(changes: Changes, selectedFeature: UIEventSource<Feature>) {
changes.pendingChanges.stabilized(Constants.updateTimeoutSec * 1000).addCallback(() => changes.flushChanges("Flushing changes due to timeout"))
changes.pendingChanges
.stabilized(Constants.updateTimeoutSec * 1000)
.addCallback(() => changes.flushChanges("Flushing changes due to timeout"))
selectedFeature.stabilized(1000).addCallback((feature) => {
if (feature === undefined) {

View file

@ -1,47 +1,47 @@
import LayoutConfig from "../../../Models/ThemeConfig/LayoutConfig";
import { WritableFeatureSource } from "../FeatureSource";
import { ImmutableStore, Store, UIEventSource } from "../../UIEventSource";
import { Feature, Point } from "geojson";
import { TagUtils } from "../../Tags/TagUtils";
import BaseUIElement from "../../../UI/BaseUIElement";
import { Utils } from "../../../Utils";
import { OsmTags } from "../../../Models/OsmFeature";
import LayoutConfig from "../../../Models/ThemeConfig/LayoutConfig"
import { WritableFeatureSource } from "../FeatureSource"
import { ImmutableStore, Store, UIEventSource } from "../../UIEventSource"
import { Feature, Point } from "geojson"
import { TagUtils } from "../../Tags/TagUtils"
import BaseUIElement from "../../../UI/BaseUIElement"
import { Utils } from "../../../Utils"
import { OsmTags } from "../../../Models/OsmFeature"
/**
* Highly specialized feature source.
* Based on a lon/lat UIEVentSource, will generate the corresponding feature with the correct properties
*/
export class LastClickFeatureSource implements WritableFeatureSource {
public readonly features: UIEventSource<Feature[]> = new UIEventSource<Feature[]>([]);
public readonly hasNoteLayer: boolean;
public readonly renderings: string[];
public readonly hasPresets: boolean;
private i: number = 0;
public readonly features: UIEventSource<Feature[]> = new UIEventSource<Feature[]>([])
public readonly hasNoteLayer: boolean
public readonly renderings: string[]
public readonly hasPresets: boolean
private i: number = 0
constructor(location: Store<{ lon: number; lat: number }>, layout: LayoutConfig) {
this.hasNoteLayer = layout.layers.some((l) => l.id === "note");
this.hasPresets = layout.layers.some((l) => l.presets?.length > 0);
const allPresets: BaseUIElement[] = [];
this.hasNoteLayer = layout.layers.some((l) => l.id === "note")
this.hasPresets = layout.layers.some((l) => l.presets?.length > 0)
const allPresets: BaseUIElement[] = []
for (const layer of layout.layers)
for (let i = 0; i < (layer.presets ?? []).length; i++) {
const preset = layer.presets[i];
const tags = new ImmutableStore(TagUtils.KVtoProperties(preset.tags));
const preset = layer.presets[i]
const tags = new ImmutableStore(TagUtils.KVtoProperties(preset.tags))
const { html } = layer.mapRendering[0].RenderIcon(tags, false, {
noSize: true,
includeBadges: false
});
allPresets.push(html);
includeBadges: false,
})
allPresets.push(html)
}
this.renderings = Utils.Dedup(
allPresets.map((uiElem) =>
Utils.runningFromConsole ? "" : uiElem.ConstructElement().innerHTML
)
);
)
location.addCallbackAndRunD(({ lon, lat }) => {
this.features.setData([this.createFeature(lon, lat)]);
});
this.features.setData([this.createFeature(lon, lat)])
})
}
public createFeature(lon: number, lat: number): Feature<Point, OsmTags> {
@ -52,17 +52,17 @@ export class LastClickFeatureSource implements WritableFeatureSource {
has_presets: this.hasPresets ? "yes" : "no",
renderings: this.renderings.join(""),
number_of_presets: "" + this.renderings.length,
first_preset: this.renderings[0]
};
this.i++;
first_preset: this.renderings[0],
}
this.i++
return <Feature<Point, OsmTags>>{
type: "Feature",
properties,
geometry: {
type: "Point",
coordinates: [lon, lat]
}
};
coordinates: [lon, lat],
},
}
}
}

View file

@ -314,7 +314,7 @@ export class GeoOperations {
return <any>way
}
public static toCSV(features: any[]): string {
public static toCSV(features: Feature[] | FeatureCollection): string {
const headerValuesSeen = new Set<string>()
const headerValuesOrdered: string[] = []
@ -330,7 +330,14 @@ export class GeoOperations {
const lines: string[] = []
for (const feature of features) {
let _features
if(Array.isArray(features)){
_features = features
}else{
_features = features.features
}
for (const feature of _features) {
const properties = feature.properties
for (const key in properties) {
if (!properties.hasOwnProperty(key)) {
@ -340,7 +347,7 @@ export class GeoOperations {
}
}
headerValuesOrdered.sort()
for (const feature of features) {
for (const feature of _features) {
const properties = feature.properties
let line = ""
for (const key of headerValuesOrdered) {

View file

@ -579,7 +579,7 @@ export class Changes {
)
const result = await self.flushSelectChanges(pendingChanges, openChangeset)
if(result){
if (result) {
this.errors.setData([])
}
return result

View file

@ -1,14 +1,42 @@
import { Utils } from "../../Utils"
/** This code is autogenerated - do not edit. Edit ./assets/layers/usersettings/usersettings.json instead */
export class ThemeMetaTagging {
public static readonly themeName = "usersettings"
public static readonly themeName = "usersettings"
public metaTaggging_for_usersettings(feat: {properties: Record<string, string>}) {
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_md', () => feat.properties._description.match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)?.at(1) )
Utils.AddLazyProperty(feat.properties, '_d', () => feat.properties._description?.replace(/&lt;/g,'<')?.replace(/&gt;/g,'>') ?? '' )
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_a', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.href.match(/mastodon|en.osm.town/) !== null)[0]?.href }) (feat) )
Utils.AddLazyProperty(feat.properties, '_mastodon_link', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.getAttribute("rel")?.indexOf('me') >= 0)[0]?.href})(feat) )
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate', () => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a )
feat.properties['__current_backgroun'] = 'initial_value'
}
}
public metaTaggging_for_usersettings(feat: { properties: Record<string, string> }) {
Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_md", () =>
feat.properties._description
.match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)
?.at(1)
)
Utils.AddLazyProperty(
feat.properties,
"_d",
() => feat.properties._description?.replace(/&lt;/g, "<")?.replace(/&gt;/g, ">") ?? ""
)
Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_a", () =>
((feat) => {
const e = document.createElement("div")
e.innerHTML = feat.properties._d
return Array.from(e.getElementsByTagName("a")).filter(
(a) => a.href.match(/mastodon|en.osm.town/) !== null
)[0]?.href
})(feat)
)
Utils.AddLazyProperty(feat.properties, "_mastodon_link", () =>
((feat) => {
const e = document.createElement("div")
e.innerHTML = feat.properties._d
return Array.from(e.getElementsByTagName("a")).filter(
(a) => a.getAttribute("rel")?.indexOf("me") >= 0
)[0]?.href
})(feat)
)
Utils.AddLazyProperty(
feat.properties,
"_mastodon_candidate",
() => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a
)
feat.properties["__current_backgroun"] = "initial_value"
}
}

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 : string = packagefile.version
public static vNumber: string = packagefile.version
/**
* API key for Maproulette
*

View file

@ -94,7 +94,7 @@ export default class LayoutConfig implements LayoutInformation {
}
const context = this.id
this.credits = json.credits
if(!json.title){
if (!json.title) {
throw `The theme ${json.id} does not have a title defined.`
}
this.language = json.mustHaveLanguage ?? Object.keys(json.title)

File diff suppressed because it is too large Load diff

View file

@ -12,28 +12,30 @@
let id = Math.random() * 1000000000 + ""
</script>
<form on:change|preventDefault={() => {
drawAttention = false
dispatcher("submit", inputElement.files)
}}
on:dragend={() => {
console.log("Drag end")
drawAttention = false
}}
on:dragenter|preventDefault|stopPropagation={(e) => {
console.log("Dragging enter")
drawAttention = true
e.dataTransfer.drop = "copy"
}}
on:dragstart={() => {
console.log("DragStart")
drawAttention = false
}}
on:drop|preventDefault|stopPropagation={(e) => {
console.log("Got a 'drop'")
drawAttention = false
dispatcher("submit", e.dataTransfer.files)
}}>
<form
on:change|preventDefault={() => {
drawAttention = false
dispatcher("submit", inputElement.files)
}}
on:dragend={() => {
console.log("Drag end")
drawAttention = false
}}
on:dragenter|preventDefault|stopPropagation={(e) => {
console.log("Dragging enter")
drawAttention = true
e.dataTransfer.drop = "copy"
}}
on:dragstart={() => {
console.log("DragStart")
drawAttention = false
}}
on:drop|preventDefault|stopPropagation={(e) => {
console.log("Got a 'drop'")
drawAttention = false
dispatcher("submit", e.dataTransfer.files)
}}
>
<label class={twMerge(cls, drawAttention ? "glowing-shadow" : "")} for={"fileinput" + id}>
<slot />
</label>
@ -44,7 +46,6 @@
id={"fileinput" + id}
{multiple}
name="file-input"
type="file"
/>
</form>

View file

@ -11,7 +11,9 @@
<div
class="absolute top-0 right-0 h-screen w-screen p-4 md:p-6"
style="background-color: #00000088"
on:click={() => {dispatch("close")}}
on:click={() => {
dispatch("close")
}}
>
<div class="content normal-background" on:click|stopPropagation={() => {}}>
<div class="h-full rounded-xl">

View file

@ -23,11 +23,11 @@ export default class Hotkeys {
>([])
private static textElementSelected(event: KeyboardEvent): boolean {
if(event.ctrlKey || event.altKey){
if (event.ctrlKey || event.altKey) {
// This is an event with a modifier-key, lets not ignore it
return false
}
if(event.key === "Escape"){
if (event.key === "Escape") {
return false // Another not-printable character that should not be ignored
}
return ["input", "textarea"].includes(document?.activeElement?.tagName?.toLowerCase())

View file

@ -1,32 +1,32 @@
<script lang="ts">
/**
* Thin wrapper around 'TabGroup' which binds the state
*/
/**
* Thin wrapper around 'TabGroup' which binds the state
*/
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@rgossiaux/svelte-headlessui";
import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource";
import { twJoin } from "tailwind-merge";
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@rgossiaux/svelte-headlessui"
import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource"
import { twJoin } from "tailwind-merge"
/**
* If a condition is given for a certain tab, it will only be shown if this condition is true.
* E.g.
* condition3 = new ImmutableStore(false) will always hide tab3 (the fourth tab)
*/
let tr = new ImmutableStore(true)
export let condition0: Store<boolean> = tr
export let condition1: Store<boolean> = tr
export let condition2: Store<boolean> = tr
export let condition3: Store<boolean> = tr
export let condition4: Store<boolean> = tr
export let tab: UIEventSource<number>;
let tabElements: HTMLElement[] = [];
$: tabElements[$tab]?.click();
$: {
if (tabElements[tab.data]) {
window.setTimeout(() => tabElements[tab.data].click(), 50);
}
/**
* If a condition is given for a certain tab, it will only be shown if this condition is true.
* E.g.
* condition3 = new ImmutableStore(false) will always hide tab3 (the fourth tab)
*/
let tr = new ImmutableStore(true)
export let condition0: Store<boolean> = tr
export let condition1: Store<boolean> = tr
export let condition2: Store<boolean> = tr
export let condition3: Store<boolean> = tr
export let condition4: Store<boolean> = tr
export let tab: UIEventSource<number>
let tabElements: HTMLElement[] = []
$: tabElements[$tab]?.click()
$: {
if (tabElements[tab.data]) {
window.setTimeout(() => tabElements[tab.data].click(), 50)
}
}
</script>
<div class="tabbedgroup flex h-full w-full">
@ -41,27 +41,37 @@
>
<div class="interactive sticky top-0 flex items-center justify-between">
<TabList class="flex flex-wrap">
<Tab class={({ selected }) => twJoin("tab", selected && "primary", !$condition0 && "hidden")}>
<Tab
class={({ selected }) => twJoin("tab", selected && "primary", !$condition0 && "hidden")}
>
<div bind:this={tabElements[0]} class="flex">
<slot name="title0">Tab 0</slot>
</div>
</Tab>
<Tab class={({ selected }) => twJoin("tab", selected && "primary", !$condition1 && "hidden")}>
<Tab
class={({ selected }) => twJoin("tab", selected && "primary", !$condition1 && "hidden")}
>
<div bind:this={tabElements[1]} class="flex">
<slot name="title1" />
</div>
</Tab>
<Tab class={({ selected }) => twJoin("tab", selected && "primary", !$condition2 && "hidden")}>
<Tab
class={({ selected }) => twJoin("tab", selected && "primary", !$condition2 && "hidden")}
>
<div bind:this={tabElements[2]} class="flex">
<slot name="title2" />
</div>
</Tab>
<Tab class={({ selected }) => twJoin("tab", selected && "primary", !$condition3 && "hidden")}>
<Tab
class={({ selected }) => twJoin("tab", selected && "primary", !$condition3 && "hidden")}
>
<div bind:this={tabElements[3]} class="flex">
<slot name="title3" />
</div>
</Tab>
<Tab class={({ selected }) => twJoin("tab", selected && "primary", !$condition4 && "hidden")}>
<Tab
class={({ selected }) => twJoin("tab", selected && "primary", !$condition4 && "hidden")}
>
<div bind:this={tabElements[4]} class="flex">
<slot name="title4" />
</div>
@ -102,44 +112,44 @@
</div>
<style>
.tabbedgroup {
max-height: 100vh;
height: 100%;
}
.tabbedgroup {
max-height: 100vh;
height: 100%;
}
:global(.tabpanel) {
height: 100%;
}
:global(.tabpanel) {
height: 100%;
}
:global(.tabpanels) {
height: calc(100% - 2rem);
}
:global(.tabpanels) {
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) {
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 .flex) {
align-items: center;
gap: 0.25rem;
}
:global(.tab span|div) {
align-items: center;
gap: 0.25rem;
display: flex;
}
:global(.tab span|div) {
align-items: center;
gap: 0.25rem;
display: flex;
}
:global(.tab-selected svg) {
fill: var(--catch-detail-color-contrast);
}
:global(.tab-selected svg) {
fill: var(--catch-detail-color-contrast);
}
:global(.tab-unselected) {
background-color: var(--background-color) !important;
color: var(--foreground-color) !important;
}
:global(.tab-unselected) {
background-color: var(--background-color) !important;
color: var(--foreground-color) !important;
}
</style>

View file

@ -38,7 +38,7 @@
if (value.data === undefined) {
value.setData(coordinate)
}
if(coordinate === undefined){
if (coordinate === undefined) {
coordinate = value.data
}
export let snapToLayers: string[] | undefined
@ -47,8 +47,6 @@
export let snappedTo: UIEventSource<string | undefined>
let preciseLocation: UIEventSource<{ lon: number; lat: number }> = new UIEventSource<{
lon: number
lat: number
@ -75,7 +73,7 @@
rasterLayer: UIEventSource.feedFrom(state.mapProperties.rasterLayer),
}
if(targetLayer){
if (targetLayer) {
const featuresForLayer = state.perLayer.get(targetLayer.id)
if (featuresForLayer) {
new ShowDataLayer(map, {

View file

@ -1,21 +1,23 @@
<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"
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
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
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")}>
<div
class="pointer-events-auto flex flex-col"
on:click={() => changes.flushChanges("Pending changes indicator clicked")}
>
{#if $isUploading}
<Loading>
<Tr cls="thx" t={Translations.t.general.uploadingChanges} />
@ -23,10 +25,13 @@
{: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})} />
<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})} />
<Tr cls="alert" t={Translations.t.general.uploadError.Subs({ error })} />
{/each}
</div>

View file

@ -1,48 +1,48 @@
<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 ToSvelte from "../Base/ToSvelte.svelte";
import ThemeViewState from "../../Models/ThemeViewState";
import { Store, UIEventSource } from "../../Logic/UIEventSource";
import { SearchIcon } from "@rgossiaux/svelte-heroicons/solid";
import { twJoin } from "tailwind-merge";
import { Utils } from "../../Utils";
import type { GeolocationPermissionState } from "../../Logic/State/GeoLocationState";
import If from "../Base/If.svelte";
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 ToSvelte from "../Base/ToSvelte.svelte"
import ThemeViewState from "../../Models/ThemeViewState"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import { SearchIcon } from "@rgossiaux/svelte-heroicons/solid"
import { twJoin } from "tailwind-merge"
import { Utils } from "../../Utils"
import type { GeolocationPermissionState } from "../../Logic/State/GeoLocationState"
import If from "../Base/If.svelte"
/**
* 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
let geopermission: Store<GeolocationPermissionState> =
state.geolocation.geolocationState.permission;
let currentGPSLocation = state.geolocation.geolocationState.currentGPSLocation;
let geopermission: Store<GeolocationPermissionState> =
state.geolocation.geolocationState.permission
let currentGPSLocation = state.geolocation.geolocationState.currentGPSLocation
geopermission.addCallback((perm) => console.log(">>>> Permission", perm));
geopermission.addCallback((perm) => console.log(">>>> Permission", perm))
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>
<div class="flex h-full flex-col justify-between">
@ -63,7 +63,6 @@
</div>
</NextButton>
<div class="flex w-full flex-wrap sm:flex-nowrap">
<If condition={state.featureSwitches.featureSwitchGeolocation}>
{#if $currentGPSLocation !== undefined || $geopermission === "prompt"}
@ -73,12 +72,15 @@
</button>
<!-- No geolocation granted - we don't show the button -->
{:else if $geopermission === "requested"}
<button class="disabled flex w-full items-center gap-x-2" on:click={jumpToCurrentLocation}>
<button
class="disabled flex w-full items-center gap-x-2"
on:click={jumpToCurrentLocation}
>
<!-- Even though disabled, when clicking we request the location again in case the contributor dismissed the location popup -->
<ToSvelte
construct={Svg.crosshair_svg()
.SetClass("w-8 h-8")
.SetStyle("animation: 3s linear 0s infinite normal none running spin;")}
.SetClass("w-8 h-8")
.SetStyle("animation: 3s linear 0s infinite normal none running spin;")}
/>
<Tr t={Translations.t.general.waitingForGeopermission} />
</button>
@ -91,24 +93,25 @@
<button class="disabled flex w-full items-center gap-x-2">
<ToSvelte
construct={Svg.crosshair_svg()
.SetClass("w-8 h-8")
.SetStyle("animation: 3s linear 0s infinite normal none running spin;")}
.SetClass("w-8 h-8")
.SetStyle("animation: 3s linear 0s infinite normal none running spin;")}
/>
<Tr t={Translations.t.general.waitingForLocation} />
</button>
{/if}
</If>
<If condition={state.featureSwitches.featureSwitchSearch}>
<div class=".button low-interaction m-1 flex w-full items-center gap-x-2 rounded border p-2">
<div
class=".button low-interaction m-1 flex w-full items-center gap-x-2 rounded border p-2"
>
<div class="w-full">
<Geosearch
bounds={state.mapProperties.bounds}
on:searchCompleted={() => state.guistate.themeIsOpened.setData(false)}
on:searchIsValid={(isValid) => {
searchEnabled = isValid
}}
searchEnabled = isValid
}}
perLayer={state.perLayer}
{selectedElement}
{selectedLayer}
@ -116,7 +119,10 @@
/>
</div>
<button
class={twJoin("flex items-center justify-between gap-x-2", !searchEnabled && "disabled")}
class={twJoin(
"flex items-center justify-between gap-x-2",
!searchEnabled && "disabled"
)}
on:click={() => triggerSearch.ping()}
>
<Tr t={Translations.t.general.search.searchShort} />

View file

@ -1,24 +1,24 @@
<script lang="ts">
/**
* Shows information about how much images are uploaded for the given feature
*
* Either pass in a store with tags or a featureId.
*/
/**
* 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>
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
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}

View file

@ -1,14 +1,14 @@
import { Store, UIEventSource } from "../../Logic/UIEventSource";
import type { Map as MLMap } from "maplibre-gl";
import { Map as MlMap, SourceSpecification } from "maplibre-gl";
import { AvailableRasterLayers, RasterLayerPolygon } from "../../Models/RasterLayers";
import { Utils } from "../../Utils";
import { BBox } from "../../Logic/BBox";
import { ExportableMap, MapProperties } from "../../Models/MapProperties";
import SvelteUIElement from "../Base/SvelteUIElement";
import MaplibreMap from "./MaplibreMap.svelte";
import { RasterLayerProperties } from "../../Models/RasterLayerProperties";
import * as htmltoimage from "html-to-image";
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import type { Map as MLMap } from "maplibre-gl"
import { Map as MlMap, SourceSpecification } from "maplibre-gl"
import { AvailableRasterLayers, RasterLayerPolygon } from "../../Models/RasterLayers"
import { Utils } from "../../Utils"
import { BBox } from "../../Logic/BBox"
import { ExportableMap, MapProperties } from "../../Models/MapProperties"
import SvelteUIElement from "../Base/SvelteUIElement"
import MaplibreMap from "./MaplibreMap.svelte"
import { RasterLayerProperties } from "../../Models/RasterLayerProperties"
import * as htmltoimage from "html-to-image"
/**
* The 'MapLibreAdaptor' bridges 'MapLibre' with the various properties of the `MapProperties`

View file

@ -16,7 +16,7 @@ import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"
import PerLayerFeatureSourceSplitter from "../../Logic/FeatureSource/PerLayerFeatureSourceSplitter"
import FilteredLayer from "../../Models/FilteredLayer"
import SimpleFeatureSource from "../../Logic/FeatureSource/Sources/SimpleFeatureSource"
import { CLIENT_RENEG_LIMIT } from "tls";
import { CLIENT_RENEG_LIMIT } from "tls"
class PointRenderingLayer {
private readonly _config: PointRenderingConfig
@ -409,7 +409,7 @@ class LineRenderingLayer {
this._listenerInstalledOn.add(id)
tags.addCallbackAndRunD((properties) => {
// Make sure to use 'getSource' here, the layer names are different!
if(map.getSource(this._layername) === undefined){
if (map.getSource(this._layername) === undefined) {
return true
}
map.setFeatureState(

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 $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 $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,7 +30,12 @@
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)
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,50 +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 NewPointLocationInput from "../BigComponents/NewPointLocationInput.svelte";
import ToSvelte from "../Base/ToSvelte.svelte";
import Svg from "../../Svg";
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: UIEventSource<{ 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.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 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,
@ -56,22 +56,22 @@
text: txt,
html: txt,
user: state.osmConnection?.userDetails?.data?.name,
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);
if (state.featureProperties.trackFeature) {
state.featureProperties.trackFeature(feature);
uid: state.osmConnection?.userDetails?.data?.uid,
},
]),
},
}
comment.setData("");
created = true;
state.selectedElement.setData(feature);
state.selectedLayer.setData(state.layerState.filteredLayers.get("note"));
// 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)
if (state.featureProperties.trackFeature) {
state.featureProperties.trackFeature(feature)
}
comment.setData("")
created = true
state.selectedElement.setData(feature)
state.selectedLayer.setData(state.layerState.filteredLayers.get("note"))
}
</script>
@ -109,15 +109,14 @@
<ValidatedInput type="text" value={comment} />
</div>
<div class="w-full h-56">
<NewPointLocationInput value={coordinate} {state} >
<div class="h-56 w-full">
<NewPointLocationInput value={coordinate} {state}>
<div class="h-20 w-full pb-10" slot="image">
<ToSvelte construct={Svg.note_svg().SetClass("h-10 w-full")}/>
<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

@ -112,7 +112,10 @@
<button
slot="save-button"
on:click={onDelete}
class={twJoin(selectedTags === undefined && "disabled", "primary flex bg-red-600 items-center")}
class={twJoin(
selectedTags === undefined && "disabled",
"primary flex items-center bg-red-600"
)}
>
<TrashIcon
class={twJoin(

View file

@ -155,8 +155,8 @@
<ToSvelte
construct={() => new ExtraLinkButton(state, layout.extraLink).SetClass("pointer-events-auto")}
/>
<UploadingImageCounter {state} featureId="*" showThankYou={false}/>
<PendingChangesIndicator {state}/>
<UploadingImageCounter featureId="*" showThankYou={false} {state} />
<PendingChangesIndicator {state} />
<If condition={state.featureSwitchIsTesting}>
<div class="alert w-fit">Testmode</div>
</If>
@ -174,7 +174,12 @@
<div class="flex flex-col">
<If condition={featureSwitches.featureSwitchEnableLogin}>
{#if state.lastClickObject.hasPresets || state.lastClickObject.hasNoteLayer}
<button class="w-fit pointer-events-auto" on:click={() => {state.openNewDialog()}}>
<button
class="pointer-events-auto w-fit"
on:click={() => {
state.openNewDialog()
}}
>
{#if state.lastClickObject.hasPresets}
<Tr t={Translations.t.general.add.title} />
{:else}
@ -197,9 +202,9 @@
<a
class="bg-black-transparent pointer-events-auto h-fit max-h-12 cursor-pointer self-end overflow-hidden rounded-2xl pl-1 pr-2 text-white opacity-50 hover:opacity-100"
on:click={() => {
state.guistate.themeViewTab.setData("copyright")
state.guistate.themeIsOpened.setData(true)
}}
state.guistate.themeViewTab.setData("copyright")
state.guistate.themeIsOpened.setData(true)
}}
>
© OpenStreetMap, <span class="w-24">{rasterLayerName}</span>
</a>
@ -293,7 +298,10 @@
<!-- Theme menu -->
<FloatOver on:close={() => state.guistate.themeIsOpened.setData(false)}>
<span slot="close-button"><!-- Disable the close button --></span>
<TabbedGroup condition1={state.featureSwitches.featureSwitchFilter} tab={state.guistate.themeViewTabIndex}>
<TabbedGroup
condition1={state.featureSwitches.featureSwitchFilter}
tab={state.guistate.themeViewTabIndex}
>
<div slot="post-tablist">
<XCircleIcon
class="mr-2 h-8 w-8"
@ -362,7 +370,11 @@
<IfHidden condition={state.guistate.backgroundLayerSelectionIsOpened}>
<!-- background layer selector -->
<FloatOver on:close={() => {state.guistate.backgroundLayerSelectionIsOpened.setData(false)}}>
<FloatOver
on:close={() => {
state.guistate.backgroundLayerSelectionIsOpened.setData(false)
}}
>
<div class="h-full p-2">
<RasterLayerOverview
{availableLayers}
@ -377,11 +389,13 @@
<If condition={state.guistate.menuIsOpened}>
<!-- Menu page -->
<FloatOver on:close={() => state.guistate.menuIsOpened.setData(false) }>
<FloatOver on:close={() => state.guistate.menuIsOpened.setData(false)}>
<span slot="close-button"><!-- Hide the default close button --></span>
<TabbedGroup condition1={featureSwitches.featureSwitchEnableLogin}
condition2={state.featureSwitches. featureSwitchCommunityIndex}
tab={state.guistate.menuViewTabIndex}>
<TabbedGroup
condition1={featureSwitches.featureSwitchEnableLogin}
condition2={state.featureSwitches.featureSwitchCommunityIndex}
tab={state.guistate.menuViewTabIndex}
>
<div slot="post-tablist">
<XCircleIcon
class="mr-2 h-8 w-8"
@ -470,7 +484,7 @@
<OpenIdEditor mapProperties={state.mapProperties} />
<ToSvelte
construct={() =>
new OpenJosm(state.osmConnection, state.mapProperties.bounds).SetClass("w-full")}
new OpenJosm(state.osmConnection, state.mapProperties.bounds).SetClass("w-full")}
/>
<MapillaryLink mapProperties={state.mapProperties} />
</If>

View file

@ -672,16 +672,21 @@ class SvgToPdfPage {
}
public async PrepareLanguage(language: string) {
let host = window.location.host
if(host.startsWith("127.0.0.1")){
host = "mapcomplete.org"
}
// Always fetch the remote data - it's cached anyway
this.layerTranslations[language] = await Utils.downloadJsonCached(
window.location.protocol +
"//" +
window.location.host +
host +
"/assets/langs/layers/" +
language +
".json",
24 * 60 * 60 * 1000
)
}
public async Prepare() {

View file

@ -1,7 +1,7 @@
{
"contributors": [
{
"commits": 6092,
"commits": 6139,
"contributor": "Pieter Vander Vennet"
},
{
@ -30,14 +30,14 @@
},
{
"commits": 30,
"contributor": "paunofu"
},
{
"commits": 29,
"contributor": "Hosted Weblate"
},
{
"commits": 27,
"commits": 30,
"contributor": "paunofu"
},
{
"commits": 28,
"contributor": "riQQ"
},
{
@ -53,7 +53,7 @@
"contributor": "Ward"
},
{
"commits": 21,
"commits": 22,
"contributor": "dependabot[bot]"
},
{

View file

@ -30,10 +30,6 @@
"AT": [
"de"
],
"AU": [
"en",
"en"
],
"AZ": [
"az"
],

View file

@ -3009,7 +3009,6 @@
"_meta": {
"countries": [
"AG",
"AU",
"BB",
"BI",
"BN",

View file

@ -1,11 +1,11 @@
{
"contributors": [
{
"commits": 311,
"commits": 314,
"contributor": "kjon"
},
{
"commits": 288,
"commits": 292,
"contributor": "Pieter Vander Vennet"
},
{
@ -96,6 +96,10 @@
"commits": 13,
"contributor": "Joost"
},
{
"commits": 11,
"contributor": "Jaime Marquínez Ferrándiz"
},
{
"commits": 11,
"contributor": "Túllio Franca"
@ -124,6 +128,10 @@
"commits": 10,
"contributor": "Irina"
},
{
"commits": 9,
"contributor": "Piotr Strebski"
},
{
"commits": 9,
"contributor": "Ettore Atalan"
@ -132,10 +140,6 @@
"commits": 9,
"contributor": "deep map"
},
{
"commits": 9,
"contributor": "Jaime Marquínez Ferrándiz"
},
{
"commits": 9,
"contributor": "Fjuro"
@ -152,6 +156,10 @@
"commits": 8,
"contributor": "Vinicius"
},
{
"commits": 7,
"contributor": "gallegonovato"
},
{
"commits": 7,
"contributor": "NetworkedPoncho"
@ -172,6 +180,10 @@
"commits": 7,
"contributor": "Niels Elgaard Larsen"
},
{
"commits": 6,
"contributor": "macpac"
},
{
"commits": 6,
"contributor": "Juele juele"
@ -216,14 +228,6 @@
"commits": 6,
"contributor": "lvgx"
},
{
"commits": 5,
"contributor": "Piotr Strebski"
},
{
"commits": 5,
"contributor": "gallegonovato"
},
{
"commits": 5,
"contributor": "ⵣⵓⵀⵉⵔ ⴰⵎⴰⵣⵉⵖ ZOUHIR DEHBI"
@ -348,10 +352,6 @@
"commits": 3,
"contributor": "SiegbjornSitumeang"
},
{
"commits": 2,
"contributor": "macpac"
},
{
"commits": 2,
"contributor": "Peter Brodersen"
@ -448,6 +448,10 @@
"commits": 2,
"contributor": "Leo Alcaraz"
},
{
"commits": 1,
"contributor": "Julio Salas"
},
{
"commits": 1,
"contributor": "Michal Čermák"