forked from MapComplete/MapComplete
chore: automated housekeeping...
This commit is contained in:
parent
14b2799f08
commit
4add2d1aff
151 changed files with 4561 additions and 3315 deletions
|
|
@ -38,12 +38,12 @@
|
|||
<slot name="close-button">
|
||||
<!-- The close button is placed _after_ the default slot in order to always paint it on top -->
|
||||
<div
|
||||
class="absolute right-10 top-10 cursor-pointer border-none p-0 m-0 bg-white rounded-full border-0"
|
||||
class="absolute right-10 top-10 m-0 cursor-pointer rounded-full border-0 border-none bg-white p-0"
|
||||
style="margin: -0.25rem"
|
||||
on:click={() => dispatch("close")}
|
||||
use:ariaLabel={Translations.t.general.backToMap}
|
||||
>
|
||||
<XCircleIcon class="w-8 h-8" />
|
||||
<XCircleIcon class="h-8 w-8" />
|
||||
</div>
|
||||
</slot>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -30,18 +30,18 @@ export default class Hotkeys {
|
|||
public static RegisterHotkey(
|
||||
key: (
|
||||
| {
|
||||
ctrl: string
|
||||
}
|
||||
ctrl: string
|
||||
}
|
||||
| {
|
||||
shift: string
|
||||
}
|
||||
shift: string
|
||||
}
|
||||
| {
|
||||
alt: string
|
||||
}
|
||||
alt: string
|
||||
}
|
||||
| {
|
||||
nomod: string
|
||||
}
|
||||
) & {
|
||||
nomod: string
|
||||
}
|
||||
) & {
|
||||
onUp?: boolean
|
||||
},
|
||||
documentation: string | Translation,
|
||||
|
|
@ -63,7 +63,7 @@ export default class Hotkeys {
|
|||
return
|
||||
}
|
||||
if (key["ctrl"] !== undefined) {
|
||||
document.addEventListener("keydown", function(event) {
|
||||
document.addEventListener("keydown", function (event) {
|
||||
if (event.ctrlKey && event.key === keycode) {
|
||||
if (action() !== false) {
|
||||
event.preventDefault()
|
||||
|
|
@ -71,7 +71,7 @@ export default class Hotkeys {
|
|||
}
|
||||
})
|
||||
} else if (key["shift"] !== undefined) {
|
||||
document.addEventListener(type, function(event) {
|
||||
document.addEventListener(type, function (event) {
|
||||
if (Hotkeys.textElementSelected(event)) {
|
||||
// A text element is selected, we don't do anything special
|
||||
return
|
||||
|
|
@ -83,7 +83,7 @@ export default class Hotkeys {
|
|||
}
|
||||
})
|
||||
} else if (key["alt"] !== undefined) {
|
||||
document.addEventListener(type, function(event) {
|
||||
document.addEventListener(type, function (event) {
|
||||
if (event.altKey && event.key === keycode) {
|
||||
if (action() !== false) {
|
||||
event.preventDefault()
|
||||
|
|
@ -91,7 +91,7 @@ export default class Hotkeys {
|
|||
}
|
||||
})
|
||||
} else if (key["nomod"] !== undefined) {
|
||||
document.addEventListener(type, function(event) {
|
||||
document.addEventListener(type, function (event) {
|
||||
if (Hotkeys.textElementSelected(event) && keycode !== "Escape") {
|
||||
// A text element is selected, we don't do anything special
|
||||
return
|
||||
|
|
@ -106,18 +106,17 @@ export default class Hotkeys {
|
|||
}
|
||||
}
|
||||
|
||||
static prepareDocumentation(docs: {
|
||||
key: { ctrl?: string; shift?: string; alt?: string; nomod?: string; onUp?: boolean }
|
||||
documentation: string | Translation
|
||||
alsoTriggeredBy: Translation[]
|
||||
}[]){
|
||||
static prepareDocumentation(
|
||||
docs: {
|
||||
key: { ctrl?: string; shift?: string; alt?: string; nomod?: string; onUp?: boolean }
|
||||
documentation: string | Translation
|
||||
alsoTriggeredBy: Translation[]
|
||||
}[]
|
||||
) {
|
||||
let byKey: [string, string | Translation, Translation[] | undefined][] = docs
|
||||
.map(({ key, documentation, alsoTriggeredBy }) => {
|
||||
const modifiers = Object.keys(key).filter(
|
||||
(k) => k !== "nomod" && k !== "onUp"
|
||||
)
|
||||
let keycode: string =
|
||||
key["ctrl"] ?? key["shift"] ?? key["alt"] ?? key["nomod"]
|
||||
const modifiers = Object.keys(key).filter((k) => k !== "nomod" && k !== "onUp")
|
||||
let keycode: string = key["ctrl"] ?? key["shift"] ?? key["alt"] ?? key["nomod"]
|
||||
if (keycode.length == 1) {
|
||||
keycode = keycode.toUpperCase()
|
||||
}
|
||||
|
|
@ -128,7 +127,7 @@ export default class Hotkeys {
|
|||
return <[string, string | Translation, Translation[] | undefined]>[
|
||||
modifiers.join("+"),
|
||||
documentation,
|
||||
alsoTriggeredBy
|
||||
alsoTriggeredBy,
|
||||
]
|
||||
})
|
||||
.sort()
|
||||
|
|
@ -141,36 +140,41 @@ export default class Hotkeys {
|
|||
return byKey
|
||||
}
|
||||
|
||||
static generateDocumentationFor(docs: {
|
||||
key: { ctrl?: string; shift?: string; alt?: string; nomod?: string; onUp?: boolean }
|
||||
documentation: string | Translation
|
||||
alsoTriggeredBy: Translation[]
|
||||
}[], language: string): string {
|
||||
|
||||
static generateDocumentationFor(
|
||||
docs: {
|
||||
key: { ctrl?: string; shift?: string; alt?: string; nomod?: string; onUp?: boolean }
|
||||
documentation: string | Translation
|
||||
alsoTriggeredBy: Translation[]
|
||||
}[],
|
||||
language: string
|
||||
): string {
|
||||
const tr = Translations.t.hotkeyDocumentation
|
||||
function t(t: Translation | string){
|
||||
if(typeof t === "string"){
|
||||
function t(t: Translation | string) {
|
||||
if (typeof t === "string") {
|
||||
return t
|
||||
}
|
||||
return t.textFor(language)
|
||||
}
|
||||
const contents: string[][] = this.prepareDocumentation(docs)
|
||||
.map(([key, doc, alsoTriggeredBy]) => {
|
||||
let keyEl: string = [key, ...(alsoTriggeredBy??[])].map(k => "`"+t(k)+"`").join(" ")
|
||||
return [keyEl, t(doc)]
|
||||
})
|
||||
const contents: string[][] = this.prepareDocumentation(docs).map(
|
||||
([key, doc, alsoTriggeredBy]) => {
|
||||
let keyEl: string = [key, ...(alsoTriggeredBy ?? [])]
|
||||
.map((k) => "`" + t(k) + "`")
|
||||
.join(" ")
|
||||
return [keyEl, t(doc)]
|
||||
}
|
||||
)
|
||||
return [
|
||||
"# "+t(tr.title),
|
||||
"# " + t(tr.title),
|
||||
t(tr.intro),
|
||||
MarkdownUtils.table(
|
||||
[t(tr.key), t(tr.action)],
|
||||
contents
|
||||
)
|
||||
].join("\n")
|
||||
MarkdownUtils.table([t(tr.key), t(tr.action)], contents),
|
||||
].join("\n")
|
||||
}
|
||||
|
||||
public static generateDocumentation(language?: string){
|
||||
return Hotkeys.generateDocumentationFor(Hotkeys._docs.data, language?? Locale.language.data)
|
||||
public static generateDocumentation(language?: string) {
|
||||
return Hotkeys.generateDocumentationFor(
|
||||
Hotkeys._docs.data,
|
||||
language ?? Locale.language.data
|
||||
)
|
||||
}
|
||||
|
||||
private static textElementSelected(event: KeyboardEvent): boolean {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import Combine from "./Combine"
|
|||
import Translations from "../i18n/Translations"
|
||||
import BaseUIElement from "../BaseUIElement"
|
||||
import SvelteUIElement from "./SvelteUIElement"
|
||||
import {default as LoadingSvg} from "../../assets/svg/Loading.svelte"
|
||||
import { default as LoadingSvg } from "../../assets/svg/Loading.svelte"
|
||||
export default class Loading extends Combine {
|
||||
constructor(msg?: BaseUIElement | string) {
|
||||
const t = Translations.W(msg) ?? Translations.t.general.loading
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export default class SvelteUIElement<
|
|||
|
||||
constructor(svelteElement, props?: Props, events?: Events, slots?: Slots) {
|
||||
super()
|
||||
this._svelteComponent = <any> svelteElement
|
||||
this._svelteComponent = <any>svelteElement
|
||||
this._props = props ?? <Props>{}
|
||||
this._events = events
|
||||
this._slots = slots
|
||||
|
|
@ -49,15 +49,15 @@ export default class SvelteUIElement<
|
|||
return el
|
||||
}
|
||||
|
||||
public getClass(){
|
||||
if(this.clss.size === 0){
|
||||
public getClass() {
|
||||
if (this.clss.size === 0) {
|
||||
return undefined
|
||||
}
|
||||
return this.clss
|
||||
}
|
||||
|
||||
public getStyle(){
|
||||
if(this.style === ""){
|
||||
public getStyle() {
|
||||
if (this.style === "") {
|
||||
return undefined
|
||||
}
|
||||
return this.style
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ export default class TableOfContents {
|
|||
const intro = md.substring(0, firstTitleIndex)
|
||||
const splitPoint = intro.lastIndexOf("\n")
|
||||
|
||||
return md.substring(0, splitPoint) +"\n" + toc + md.substring(splitPoint)
|
||||
return md.substring(0, splitPoint) + "\n" + toc + md.substring(splitPoint)
|
||||
}
|
||||
|
||||
public static generateStructure(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
<script lang="ts">
|
||||
|
||||
import Translations from "../i18n/Translations"
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -7,14 +7,14 @@
|
|||
let elem: HTMLElement
|
||||
let html: HTMLElement
|
||||
let isSvelte = false
|
||||
let uiElement : BaseUIElement | SvelteUIElement | undefined
|
||||
let uiElement: BaseUIElement | SvelteUIElement | undefined
|
||||
let svelteElem: SvelteUIElement
|
||||
onMount(() => {
|
||||
uiElement = typeof construct === "function" ? construct() : construct
|
||||
|
||||
if (uiElement?.["isSvelte"]) {
|
||||
isSvelte = true
|
||||
svelteElem = <SvelteUIElement> uiElement
|
||||
svelteElem = <SvelteUIElement>uiElement
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -32,7 +32,12 @@
|
|||
</script>
|
||||
|
||||
{#if isSvelte}
|
||||
<svelte:component this={svelteElem?._svelteComponent} {...svelteElem._props} class={svelteElem.getClass()} style={svelteElem.getStyle()}/>
|
||||
<svelte:component
|
||||
this={svelteElem?._svelteComponent}
|
||||
{...svelteElem._props}
|
||||
class={svelteElem.getClass()}
|
||||
style={svelteElem.getStyle()}
|
||||
/>
|
||||
{:else}
|
||||
<span bind:this={elem} />
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -53,31 +53,28 @@
|
|||
<Tr t={Translations.t.general.menu.filter} />
|
||||
</div>
|
||||
|
||||
{#each layout.layers as layer}
|
||||
<Filterview
|
||||
zoomlevel={state.mapProperties.zoom}
|
||||
filteredLayer={state.layerState.filteredLayers.get(layer.id)}
|
||||
highlightedLayer={state.guistate.highlightedLayerInFilters}
|
||||
/>
|
||||
{/each}
|
||||
<div class="mt-1 flex self-end">
|
||||
<button class="small" class:disabled={allEnabled} on:click={() => enableAll(true)}>
|
||||
<Tr t={Translations.t.general.filterPanel.enableAll} />
|
||||
</button>
|
||||
<button class="small" class:disabled={allDisabled} on:click={() => enableAll(false)}>
|
||||
<Tr t={Translations.t.general.filterPanel.disableAll} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#each layout.tileLayerSources as tilesource}
|
||||
<OverlayToggle
|
||||
layerproperties={tilesource}
|
||||
state={state.overlayLayerStates.get(tilesource.id)}
|
||||
highlightedLayer={state.guistate.highlightedLayerInFilters}
|
||||
zoomlevel={state.mapProperties.zoom}
|
||||
/>
|
||||
{/each}
|
||||
{#each layout.layers as layer}
|
||||
<Filterview
|
||||
zoomlevel={state.mapProperties.zoom}
|
||||
filteredLayer={state.layerState.filteredLayers.get(layer.id)}
|
||||
highlightedLayer={state.guistate.highlightedLayerInFilters}
|
||||
/>
|
||||
{/each}
|
||||
<div class="mt-1 flex self-end">
|
||||
<button class="small" class:disabled={allEnabled} on:click={() => enableAll(true)}>
|
||||
<Tr t={Translations.t.general.filterPanel.enableAll} />
|
||||
</button>
|
||||
<button class="small" class:disabled={allDisabled} on:click={() => enableAll(false)}>
|
||||
<Tr t={Translations.t.general.filterPanel.disableAll} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#each layout.tileLayerSources as tilesource}
|
||||
<OverlayToggle
|
||||
layerproperties={tilesource}
|
||||
state={state.overlayLayerStates.get(tilesource.id)}
|
||||
highlightedLayer={state.guistate.highlightedLayerInFilters}
|
||||
zoomlevel={state.mapProperties.zoom}
|
||||
/>
|
||||
{/each}
|
||||
</TitledPanel>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -43,13 +43,10 @@
|
|||
{#if filteredLayer.layerDef.name}
|
||||
<div class:focus={$highlightedLayer === filteredLayer.layerDef.id} class="mb-1.5">
|
||||
<Checkbox selected={isDisplayed}>
|
||||
<div class="block h-6 w-6 no-image-background" class:opacity-50={!$isDisplayed}>
|
||||
<ToSvelte
|
||||
construct={() => layer.defaultIcon()}
|
||||
/>
|
||||
<div class="no-image-background block h-6 w-6" class:opacity-50={!$isDisplayed}>
|
||||
<ToSvelte construct={() => layer.defaultIcon()} />
|
||||
</div>
|
||||
|
||||
|
||||
<Tr t={filteredLayer.layerDef.name} />
|
||||
|
||||
{#if $zoomlevel < layer.minzoom}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
<script lang="ts">
|
||||
|
||||
import Hotkeys from "../Base/Hotkeys"
|
||||
import { Translation } from "../i18n/Translation"
|
||||
import { Utils } from "../../Utils"
|
||||
|
|
@ -10,14 +9,13 @@
|
|||
let keys = Hotkeys._docs
|
||||
const t = Translations.t.hotkeyDocumentation
|
||||
|
||||
|
||||
let byKey = Hotkeys.prepareDocumentation($keys)
|
||||
$: {
|
||||
byKey = Hotkeys.prepareDocumentation($keys)
|
||||
}
|
||||
</script>
|
||||
<AccordionSingle>
|
||||
|
||||
<AccordionSingle>
|
||||
<div slot="header">
|
||||
<Tr t={t.title} />
|
||||
</div>
|
||||
|
|
@ -25,30 +23,27 @@
|
|||
<table>
|
||||
<tr>
|
||||
<th>
|
||||
<Tr t={t.key}></Tr>
|
||||
</th>
|
||||
<th>
|
||||
<Tr t={t.action} />
|
||||
</th>
|
||||
<Tr t={t.key} />
|
||||
</th>
|
||||
<th>
|
||||
<Tr t={t.action} />
|
||||
</th>
|
||||
</tr>
|
||||
{#each byKey as [key, doc, alsoTriggeredBy] }
|
||||
{#each byKey as [key, doc, alsoTriggeredBy]}
|
||||
<tr>
|
||||
<td class="flex items-center justify-center">
|
||||
{#if alsoTriggeredBy}
|
||||
<div class="flex items-center justify-center gap-x-1">
|
||||
|
||||
<div class="literal-code w-fit h-fit">{key}</div>
|
||||
<div class="literal-code w-fit h-fit">{alsoTriggeredBy}</div>
|
||||
|
||||
<div class="literal-code h-fit w-fit">{key}</div>
|
||||
<div class="literal-code h-fit w-fit">{alsoTriggeredBy}</div>
|
||||
</div>
|
||||
|
||||
{:else}
|
||||
<div class="literal-code w-fit h-fit flex items-center w-full">{key}</div>
|
||||
<div class="literal-code flex h-fit w-fit w-full items-center">{key}</div>
|
||||
{/if}
|
||||
</td>
|
||||
<td>
|
||||
<Tr t={doc} />
|
||||
</td>
|
||||
<Tr t={doc} />
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -9,22 +9,19 @@
|
|||
import { ariaLabel } from "../../Utils/ariaLabel"
|
||||
import { Translation } from "../i18n/Translation"
|
||||
|
||||
|
||||
const dispatch = createEventDispatcher<{search: string}>()
|
||||
const dispatch = createEventDispatcher<{ search: string }>()
|
||||
|
||||
export let searchValue: UIEventSource<string>
|
||||
export let placeholderText: Translation = Translations.t.general.search.search
|
||||
export let feedback = new UIEventSource<string>(undefined)
|
||||
|
||||
|
||||
let isRunning: boolean = false
|
||||
|
||||
let inputElement: HTMLInputElement
|
||||
|
||||
function _performSearch(){
|
||||
function _performSearch() {
|
||||
dispatch("search", searchValue.data)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="normal-background flex justify-between rounded-full">
|
||||
|
|
@ -32,21 +29,24 @@
|
|||
{#if isRunning}
|
||||
<Loading>{Translations.t.general.search.searching}</Loading>
|
||||
{:else}
|
||||
<div class="flex w-full border border-gray-300 rounded-full">
|
||||
|
||||
<input
|
||||
type="search"
|
||||
class="w-full outline-none mx-2"
|
||||
bind:this={inputElement}
|
||||
on:keypress={(keypr) => {
|
||||
feedback.set(undefined)
|
||||
return keypr.key === "Enter" ? _performSearch() : undefined
|
||||
}}
|
||||
bind:value={$searchValue}
|
||||
use:placeholder={placeholderText}
|
||||
use:ariaLabel={Translations.t.general.search.search}
|
||||
/>
|
||||
<SearchIcon aria-hidden="true" class="h-6 w-6 self-end" on:click={event => _performSearch()} />
|
||||
<div class="flex w-full rounded-full border border-gray-300">
|
||||
<input
|
||||
type="search"
|
||||
class="mx-2 w-full outline-none"
|
||||
bind:this={inputElement}
|
||||
on:keypress={(keypr) => {
|
||||
feedback.set(undefined)
|
||||
return keypr.key === "Enter" ? _performSearch() : undefined
|
||||
}}
|
||||
bind:value={$searchValue}
|
||||
use:placeholder={placeholderText}
|
||||
use:ariaLabel={Translations.t.general.search.search}
|
||||
/>
|
||||
<SearchIcon
|
||||
aria-hidden="true"
|
||||
class="h-6 w-6 self-end"
|
||||
on:click={(event) => _performSearch()}
|
||||
/>
|
||||
</div>
|
||||
{#if $feedback !== undefined}
|
||||
<!-- The feedback is _always_ shown for screenreaders and to make sure that the searchfield can still be selected by tabbing-->
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@
|
|||
</div>
|
||||
<slot name="close-button">
|
||||
<div
|
||||
class="mt-2 h-fit shrink-0 rounded-full border-none p-0 cursor-pointer"
|
||||
class="mt-2 h-fit shrink-0 cursor-pointer rounded-full border-none p-0"
|
||||
on:click={() => state.selectedElement.setData(undefined)}
|
||||
style="border: 0 !important; padding: 0 !important;"
|
||||
use:ariaLabel={Translations.t.general.backToMap}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,9 @@
|
|||
selectedElement.properties.id
|
||||
)
|
||||
|
||||
let isAddNew = tags.mapD(t => t?.id?.startsWith(LastClickFeatureSource.newPointElementId) ?? false)
|
||||
let isAddNew = tags.mapD(
|
||||
(t) => t?.id?.startsWith(LastClickFeatureSource.newPointElementId) ?? false
|
||||
)
|
||||
|
||||
function getLayer(properties: Record<string, string>) {
|
||||
if (properties.id === "settings") {
|
||||
|
|
@ -81,7 +83,6 @@
|
|||
class="selected-element-view flex h-full w-full flex-col gap-y-1 overflow-y-auto"
|
||||
class:p1={!$isAddNew}
|
||||
class:px-4={!$isAddNew}
|
||||
|
||||
tabindex="-1"
|
||||
>
|
||||
{#each $knownTagRenderings as config (config.id)}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,9 @@ export default class DeleteImage extends Toggle {
|
|||
.Clone()
|
||||
.SetClass("bg-white pl-4 pr-4")
|
||||
.SetStyle("border-bottom-left-radius:30rem; border-bottom-right-radius: 30rem;")
|
||||
const openDelete = new SvelteUIElement(Delete_icon).SetStyle("width: 2em; height: 2em; display:block;")
|
||||
const openDelete = new SvelteUIElement(Delete_icon).SetStyle(
|
||||
"width: 2em; height: 2em; display:block;"
|
||||
)
|
||||
const deleteDialog = new ClickableToggle(
|
||||
new Combine([deleteButton, cancelButton]).SetClass("flex flex-col background-black"),
|
||||
openDelete
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
/**
|
||||
* A small element showing the attribution of a single image
|
||||
*/
|
||||
export let image: Partial<ProvidedImage> & {id: string, url: string}
|
||||
export let image: Partial<ProvidedImage> & { id: string; url: string }
|
||||
let license: Store<LicenseInfo> = UIEventSource.FromPromise(
|
||||
image.provider?.DownloadAttribution(image)
|
||||
)
|
||||
|
|
@ -16,9 +16,9 @@
|
|||
</script>
|
||||
|
||||
{#if $license !== undefined}
|
||||
<div class="no-images flex rounded-lg bg-black p-0.5 pl-3 pr-3 text-sm text-white items-center">
|
||||
<div class="no-images flex items-center rounded-lg bg-black p-0.5 pl-3 pr-3 text-sm text-white">
|
||||
{#if icon !== undefined}
|
||||
<div class="w-6 h-6 mr-2">
|
||||
<div class="mr-2 h-6 w-6">
|
||||
<ToSvelte construct={icon} />
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -26,47 +26,63 @@
|
|||
|
||||
let imagesProvider = state.nearbyImageSearcher
|
||||
|
||||
let loadedImages = AllImageProviders.LoadImagesFor(tags).mapD(loaded => new Set(loaded.map(img => img.url)))
|
||||
let loadedImages = AllImageProviders.LoadImagesFor(tags).mapD(
|
||||
(loaded) => new Set(loaded.map((img) => img.url))
|
||||
)
|
||||
let imageState = imagesProvider.getImagesAround(lon, lat)
|
||||
let result: Store<P4CPicture[]> = imageState.images.mapD((pics: P4CPicture[]) => pics.filter((p: P4CPicture) =>
|
||||
!loadedImages.data.has(p.pictureUrl) // We don't show any image which is already linked
|
||||
&& !p.details.isSpherical,
|
||||
).slice(0, 25), [loadedImages])
|
||||
|
||||
let someLoading = imageState.state.mapD(stateRecord => Object.values(stateRecord).some(v => v === "loading"))
|
||||
let errors = imageState.state.mapD(stateRecord => Object.keys(stateRecord).filter(k => stateRecord[k] === "error"))
|
||||
let result: Store<P4CPicture[]> = imageState.images.mapD(
|
||||
(pics: P4CPicture[]) =>
|
||||
pics
|
||||
.filter(
|
||||
(p: P4CPicture) =>
|
||||
!loadedImages.data.has(p.pictureUrl) && // We don't show any image which is already linked
|
||||
!p.details.isSpherical
|
||||
)
|
||||
.slice(0, 25),
|
||||
[loadedImages]
|
||||
)
|
||||
|
||||
let someLoading = imageState.state.mapD((stateRecord) =>
|
||||
Object.values(stateRecord).some((v) => v === "loading")
|
||||
)
|
||||
let errors = imageState.state.mapD((stateRecord) =>
|
||||
Object.keys(stateRecord).filter((k) => stateRecord[k] === "error")
|
||||
)
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col">
|
||||
|
||||
{#if $result.length === 0}
|
||||
{#if $someLoading}
|
||||
<div class="flex justify-center m-4">
|
||||
<div class="m-4 flex justify-center">
|
||||
<Loading />
|
||||
</div>
|
||||
{:else }
|
||||
{:else}
|
||||
<Tr t={Translations.t.image.nearby.noNearbyImages} cls="alert" />
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="flex w-full space-x-1 overflow-x-auto" style="scroll-snap-type: x proximity">
|
||||
{#each $result as image (image.pictureUrl)}
|
||||
<span class="w-fit shrink-0" style="scroll-snap-align: start">
|
||||
<LinkableImage {tags} {image} {state} {feature} {layer} {linkable} />
|
||||
</span>
|
||||
<span class="w-fit shrink-0" style="scroll-snap-align: start">
|
||||
<LinkableImage {tags} {image} {state} {feature} {layer} {linkable} />
|
||||
</span>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex justify-between my-2">
|
||||
<div class="my-2 flex justify-between">
|
||||
<div>
|
||||
{#if $someLoading && $result.length > 0}
|
||||
<Loading />
|
||||
{/if}
|
||||
{#if $errors.length > 0}
|
||||
<Tr cls="alert font-sm block" t={Translations.t.image.nearby.failed.Subs({service: $errors.join(", ")}) } />
|
||||
<Tr
|
||||
cls="alert font-sm block"
|
||||
t={Translations.t.image.nearby.failed.Subs({ service: $errors.join(", ") })}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
<MapillaryLink large={false}
|
||||
mapProperties={{zoom: new ImmutableStore(16), location: new ImmutableStore({lon, lat})}} />
|
||||
<MapillaryLink
|
||||
large={false}
|
||||
mapProperties={{ zoom: new ImmutableStore(16), location: new ImmutableStore({ lon, lat }) }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
* Allows to search through wikidata and to select one value
|
||||
*/
|
||||
|
||||
|
||||
import Translations from "../../i18n/Translations"
|
||||
import Tr from "../../Base/Tr.svelte"
|
||||
import { ImmutableStore, Store, Stores, UIEventSource } from "../../../Logic/UIEventSource"
|
||||
|
|
@ -30,13 +29,13 @@
|
|||
let selectedMany: Record<string, boolean> = {}
|
||||
let previouslySeen = new Map<string, WikidataResponse>()
|
||||
|
||||
$:{
|
||||
$: {
|
||||
if (selectedWikidataSingle) {
|
||||
value.setData(selectedWikidataSingle.id)
|
||||
}
|
||||
}
|
||||
|
||||
$:{
|
||||
$: {
|
||||
const v = []
|
||||
for (const id in selectedMany) {
|
||||
if (selectedMany[id]) {
|
||||
|
|
@ -46,11 +45,10 @@
|
|||
value.setData(v.join(";"))
|
||||
}
|
||||
|
||||
|
||||
let tooShort = new ImmutableStore<{ success: WikidataResponse[] }>({ success: undefined })
|
||||
|
||||
let searchResult: Store<{ success?: WikidataResponse[]; error?: any }> = searchValue
|
||||
.bind((searchText) => {
|
||||
let searchResult: Store<{ success?: WikidataResponse[]; error?: any }> = searchValue.bind(
|
||||
(searchText) => {
|
||||
if (searchText.length < 3 && !searchText.match(/[qQ][0-9]+/)) {
|
||||
return tooShort
|
||||
}
|
||||
|
|
@ -62,18 +60,19 @@
|
|||
lang,
|
||||
maxCount: 5,
|
||||
notInstanceOf,
|
||||
instanceOf
|
||||
instanceOf,
|
||||
})
|
||||
WikidataValidator._searchCache.set(key, promise)
|
||||
}
|
||||
return Stores.FromPromiseWithErr(promise)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
let selectedWithoutSearch: Store<WikidataResponse[]> = searchResult.map(sr => {
|
||||
let selectedWithoutSearch: Store<WikidataResponse[]> = searchResult.map((sr) => {
|
||||
for (const wikidataItem of sr?.success ?? []) {
|
||||
previouslySeen.set(wikidataItem.id, wikidataItem)
|
||||
}
|
||||
let knownIds: Set<string> = new Set(sr?.success?.map(item => item.id))
|
||||
let knownIds: Set<string> = new Set(sr?.success?.map((item) => item.id))
|
||||
const seen = [selectedWikidataSingle]
|
||||
for (const id in selectedMany) {
|
||||
if (selectedMany[id]) {
|
||||
|
|
@ -81,9 +80,8 @@
|
|||
seen.push(item)
|
||||
}
|
||||
}
|
||||
return Utils.NoNull(seen).filter(i => !knownIds.has(i.id))
|
||||
return Utils.NoNull(seen).filter((i) => !knownIds.has(i.id))
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<h3>
|
||||
|
|
@ -91,33 +89,37 @@
|
|||
</h3>
|
||||
|
||||
<form>
|
||||
<SearchField {searchValue} placeholderText={placeholder}></SearchField>
|
||||
<SearchField {searchValue} placeholderText={placeholder} />
|
||||
|
||||
{#if $searchValue.trim().length === 0}
|
||||
<Tr cls="w-full flex justify-center p-4" t={ t.doSearch} />
|
||||
<Tr cls="w-full flex justify-center p-4" t={t.doSearch} />
|
||||
{:else if $searchValue.trim().length < 3}
|
||||
<Tr t={ t.searchToShort} />
|
||||
<Tr t={t.searchToShort} />
|
||||
{:else if $searchResult === undefined}
|
||||
<div class="w-full flex justify-center p-4">
|
||||
<div class="flex w-full justify-center p-4">
|
||||
<Loading>
|
||||
<Tr t={Translations.t.general.loading} />
|
||||
</Loading>
|
||||
</div>
|
||||
{:else if $searchResult.error !== undefined}
|
||||
<div class="w-full flex justify-center p-4">
|
||||
<div class="flex w-full justify-center p-4">
|
||||
<Tr cls="alert" t={t.failed} />
|
||||
</div>
|
||||
{:else if $searchResult.success}
|
||||
{#if $searchResult.success.length === 0}
|
||||
<Tr cls="w-full flex justify-center p-4" t={ t.noResults.Subs({search: $searchValue})} />
|
||||
<Tr cls="w-full flex justify-center p-4" t={t.noResults.Subs({ search: $searchValue })} />
|
||||
{:else}
|
||||
{#each $searchResult.success as wikidata}
|
||||
|
||||
<label class="low-interaction m-4 p-2 rounded-xl flex items-center">
|
||||
<label class="low-interaction m-4 flex items-center rounded-xl p-2">
|
||||
{#if allowMultiple}
|
||||
<input type="checkbox" bind:checked={selectedMany[wikidata.id]} />
|
||||
{:else}
|
||||
<input type="radio" name="selectedWikidata" value={wikidata} bind:group={selectedWikidataSingle} />
|
||||
<input
|
||||
type="radio"
|
||||
name="selectedWikidata"
|
||||
value={wikidata}
|
||||
bind:group={selectedWikidataSingle}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<Wikidatapreview {wikidata} />
|
||||
|
|
@ -127,17 +129,19 @@
|
|||
{/if}
|
||||
|
||||
{#each $selectedWithoutSearch as wikidata}
|
||||
|
||||
<label class="low-interaction m-4 p-2 rounded-xl flex items-center">
|
||||
<label class="low-interaction m-4 flex items-center rounded-xl p-2">
|
||||
{#if allowMultiple}
|
||||
<input type="checkbox" bind:checked={selectedMany[wikidata.id]} />
|
||||
{:else}
|
||||
<input type="radio" name="selectedWikidata" value={wikidata} bind:group={selectedWikidataSingle} />
|
||||
<input
|
||||
type="radio"
|
||||
name="selectedWikidata"
|
||||
value={wikidata}
|
||||
bind:group={selectedWikidataSingle}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<Wikidatapreview {wikidata} />
|
||||
</label>
|
||||
{/each}
|
||||
|
||||
</form>
|
||||
|
||||
|
|
|
|||
|
|
@ -28,13 +28,15 @@
|
|||
export let feature: Feature
|
||||
export let args: (string | number | boolean)[] = undefined
|
||||
export let state: SpecialVisualizationState
|
||||
|
||||
</script>
|
||||
|
||||
{#if type === "translation"}
|
||||
<TranslationInput {value} on:submit {args} />
|
||||
{:else if type === "direction"}
|
||||
<DirectionInput {value} mapProperties={InputHelpers.constructMapProperties( { feature, args: args ?? [] })} />
|
||||
<DirectionInput
|
||||
{value}
|
||||
mapProperties={InputHelpers.constructMapProperties({ feature, args: args ?? [] })}
|
||||
/>
|
||||
{:else if type === "date"}
|
||||
<DateInput {value} />
|
||||
{:else if type === "color"}
|
||||
|
|
@ -50,5 +52,5 @@
|
|||
{:else if type === "slope"}
|
||||
<SlopeInput {value} {feature} {state} />
|
||||
{:else if type === "wikidata"}
|
||||
<WikidataInputHelper {value} {feature} {state} {args}/>
|
||||
<WikidataInputHelper {value} {feature} {state} {args} />
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -63,6 +63,4 @@ export default class InputHelpers {
|
|||
}
|
||||
return mapProperties
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@
|
|||
preferredFiltered = preferredLanguages?.filter((l) => availableLanguages.indexOf(l) >= 0)
|
||||
})
|
||||
|
||||
|
||||
export let clss: string = undefined
|
||||
let current = Locale.language
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -100,9 +100,10 @@ export default class Validators {
|
|||
private static _byType = Validators._byTypeConstructor()
|
||||
|
||||
public static HelpText(): string {
|
||||
const explanations: string[] = Validators.AllValidators.flatMap((type) =>
|
||||
["### "+type.name, type.explanation]
|
||||
)
|
||||
const explanations: string[] = Validators.AllValidators.flatMap((type) => [
|
||||
"### " + type.name,
|
||||
type.explanation,
|
||||
])
|
||||
return [
|
||||
"# Available types for text fields",
|
||||
"The listed types here trigger a special input element. Use them in `tagrendering.freeform.type` of your tagrendering to activate them",
|
||||
|
|
|
|||
|
|
@ -3,12 +3,18 @@ import { Translation } from "../../i18n/Translation"
|
|||
import Translations from "../../i18n/Translations"
|
||||
|
||||
export default class StringValidator extends Validator {
|
||||
|
||||
constructor(type?: string, doc?: string, inputmode?: "none" | "text" | "tel" | "url" | "email" | "numeric" | "decimal" | "search", textArea?: boolean) {
|
||||
super(type ?? "string",
|
||||
constructor(
|
||||
type?: string,
|
||||
doc?: string,
|
||||
inputmode?: "none" | "text" | "tel" | "url" | "email" | "numeric" | "decimal" | "search",
|
||||
textArea?: boolean
|
||||
) {
|
||||
super(
|
||||
type ?? "string",
|
||||
doc ?? "A simple piece of text which is at most 255 characters long",
|
||||
inputmode,
|
||||
textArea)
|
||||
textArea
|
||||
)
|
||||
}
|
||||
|
||||
isValid(s: string): boolean {
|
||||
|
|
|
|||
|
|
@ -42,8 +42,10 @@ export default class WikidataValidator extends Validator {
|
|||
"notInstanceof",
|
||||
"A list of Q-identifiers which indicates that the search results _must not_ be an entity of this type, e.g. [`Q79007`](https://www.wikidata.org/wiki/Q79007) to filter away all streets from the search results",
|
||||
],
|
||||
["multiple",
|
||||
"If 'yes' or 'true', will allow to select multiple values at once"]
|
||||
[
|
||||
"multiple",
|
||||
"If 'yes' or 'true', will allow to select multiple values at once",
|
||||
],
|
||||
]
|
||||
),
|
||||
]),
|
||||
|
|
@ -145,7 +147,12 @@ Another example is to search for species and trees:
|
|||
* WikidataValidator.removePostAndPrefixes("Elf-Julistraat", [], {"nl":["straat", "laan"], "en": ["street"]}, "nl") // => "Elf-Juli"
|
||||
* WikidataValidator.removePostAndPrefixes("Elf-Julistraat", [], {"nl":["straat", "laan"], "en": ["street"]}, "en") // => "Elf-Julistraat"
|
||||
*/
|
||||
public static removePostAndPrefixes(searchTerm: string, prefixesToRemove: string[] | Record<string, string[]>, postfixesToRemove: string[] | Record<string, string[]>, language: string): string {
|
||||
public static removePostAndPrefixes(
|
||||
searchTerm: string,
|
||||
prefixesToRemove: string[] | Record<string, string[]>,
|
||||
postfixesToRemove: string[] | Record<string, string[]>,
|
||||
language: string
|
||||
): string {
|
||||
const prefixes = prefixesToRemove
|
||||
const postfixes = postfixesToRemove
|
||||
const prefixesUnwrapped: RegExp[] = (
|
||||
|
|
@ -156,7 +163,6 @@ Another example is to search for species and trees:
|
|||
Array.isArray(postfixes) ? postfixes : postfixes[language] ?? []
|
||||
).map((s) => new RegExp(s + "$", "i"))
|
||||
|
||||
|
||||
let clipped = searchTerm.trim()
|
||||
|
||||
for (const postfix of postfixesUnwrapped) {
|
||||
|
|
|
|||
|
|
@ -1,57 +1,57 @@
|
|||
<script lang="ts">/**
|
||||
<script lang="ts">
|
||||
/**
|
||||
*
|
||||
Wrapper around 'WikidataInput.svelte' which handles the arguments
|
||||
*/
|
||||
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import Locale from "../i18n/Locale"
|
||||
import { Utils } from "../../Utils"
|
||||
import Wikidata from "../../Logic/Web/Wikidata"
|
||||
import WikidataInput from "./Helpers/WikidataInput.svelte"
|
||||
import type { Feature } from "geojson"
|
||||
import { onDestroy } from "svelte"
|
||||
import WikidataValidator from "./Validators/WikidataValidator"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import Locale from "../i18n/Locale"
|
||||
import { Utils } from "../../Utils"
|
||||
import Wikidata from "../../Logic/Web/Wikidata"
|
||||
import WikidataInput from "./Helpers/WikidataInput.svelte"
|
||||
import type { Feature } from "geojson"
|
||||
import { onDestroy } from "svelte"
|
||||
import WikidataValidator from "./Validators/WikidataValidator"
|
||||
|
||||
export let args: (string | number | boolean)[] = []
|
||||
export let feature: Feature
|
||||
export let args: (string | number | boolean)[] = []
|
||||
export let feature: Feature
|
||||
|
||||
export let value: UIEventSource<string>
|
||||
export let value: UIEventSource<string>
|
||||
|
||||
let searchKey: string = <string>args[0] ?? "name"
|
||||
let searchKey: string = <string>args[0] ?? "name"
|
||||
|
||||
let searchFor: string =
|
||||
searchKey
|
||||
.split(";")
|
||||
.map((k) => feature?.properties[k]?.toLowerCase())
|
||||
.find((foundValue) => !!foundValue) ?? ""
|
||||
let searchFor: string =
|
||||
searchKey
|
||||
.split(";")
|
||||
.map((k) => feature?.properties[k]?.toLowerCase())
|
||||
.find((foundValue) => !!foundValue) ?? ""
|
||||
|
||||
const options: any = args[1]
|
||||
const options: any = args[1]
|
||||
|
||||
let searchForValue: UIEventSource<string> = new UIEventSource(searchFor)
|
||||
let searchForValue: UIEventSource<string> = new UIEventSource(searchFor)
|
||||
|
||||
onDestroy(
|
||||
Locale.language.addCallbackAndRunD(lg => {
|
||||
console.log(options)
|
||||
if (searchFor !== undefined && options !== undefined) {
|
||||
const post = options["removePostfixes"] ?? []
|
||||
const pre = options["removePrefixes"] ?? []
|
||||
const clipped = WikidataValidator.removePostAndPrefixes(searchFor, pre, post, lg)
|
||||
console.log("Got clipped value:", clipped, post, pre)
|
||||
searchForValue.setData(clipped)
|
||||
}
|
||||
})
|
||||
)
|
||||
onDestroy(
|
||||
Locale.language.addCallbackAndRunD((lg) => {
|
||||
console.log(options)
|
||||
if (searchFor !== undefined && options !== undefined) {
|
||||
const post = options["removePostfixes"] ?? []
|
||||
const pre = options["removePrefixes"] ?? []
|
||||
const clipped = WikidataValidator.removePostAndPrefixes(searchFor, pre, post, lg)
|
||||
console.log("Got clipped value:", clipped, post, pre)
|
||||
searchForValue.setData(clipped)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
let instanceOf: number[] = Utils.NoNull(
|
||||
(options?.instanceOf ?? []).map((i) => Wikidata.QIdToNumber(i))
|
||||
)
|
||||
let notInstanceOf: number[] = Utils.NoNull(
|
||||
(options?.notInstanceOf ?? []).map((i) => Wikidata.QIdToNumber(i))
|
||||
)
|
||||
|
||||
let instanceOf: number[] = Utils.NoNull(
|
||||
(options?.instanceOf ?? []).map((i) => Wikidata.QIdToNumber(i))
|
||||
)
|
||||
let notInstanceOf: number[] = Utils.NoNull(
|
||||
(options?.notInstanceOf ?? []).map((i) => Wikidata.QIdToNumber(i))
|
||||
)
|
||||
|
||||
let allowMultipleArg = options?.["multiple"]
|
||||
let allowMultiple = allowMultipleArg === "yes" || (""+allowMultipleArg) === "true"
|
||||
let allowMultipleArg = options?.["multiple"]
|
||||
let allowMultiple = allowMultipleArg === "yes" || "" + allowMultipleArg === "true"
|
||||
</script>
|
||||
|
||||
<WikidataInput searchValue={searchForValue} {value} {instanceOf} {notInstanceOf} {allowMultiple}/>
|
||||
<WikidataInput searchValue={searchForValue} {value} {instanceOf} {notInstanceOf} {allowMultiple} />
|
||||
|
|
|
|||
|
|
@ -55,24 +55,24 @@
|
|||
center: { lng: lon, lat },
|
||||
maxZoom: 24,
|
||||
interactive: true,
|
||||
attributionControl: false
|
||||
attributionControl: false,
|
||||
}
|
||||
_map = new maplibre.Map(options)
|
||||
window.requestAnimationFrame(() => {
|
||||
_map.resize()
|
||||
})
|
||||
|
||||
_map.on("load", function() {
|
||||
_map.on("load", function () {
|
||||
_map.resize()
|
||||
const canvas = _map.getCanvas()
|
||||
canvas.addEventListener("webglcontextlost", (e) => {
|
||||
console.warn("A MapLibreMap lost their context. Recovery is", autorecovery, e)
|
||||
try{
|
||||
try {
|
||||
_map?.remove()
|
||||
}catch (e) {
|
||||
} catch (e) {
|
||||
console.debug("Could not remove map due to", e)
|
||||
}
|
||||
if(autorecovery){
|
||||
if (autorecovery) {
|
||||
requestAnimationFrame(() => {
|
||||
console.warn("Attempting map recovery")
|
||||
_map = new maplibre.Map(options)
|
||||
|
|
@ -92,23 +92,19 @@
|
|||
map.set(_map)
|
||||
}
|
||||
|
||||
|
||||
onMount(() => initMap())
|
||||
|
||||
|
||||
onDestroy(async () => {
|
||||
await Utils.waitFor(100)
|
||||
requestAnimationFrame(
|
||||
() => {
|
||||
try {
|
||||
_map?.remove()
|
||||
console.log("Removed map")
|
||||
map = null
|
||||
} catch (e) {
|
||||
console.error("Could not destroy map")
|
||||
}
|
||||
requestAnimationFrame(() => {
|
||||
try {
|
||||
_map?.remove()
|
||||
console.log("Removed map")
|
||||
map = null
|
||||
} catch (e) {
|
||||
console.error("Could not destroy map")
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@
|
|||
</script>
|
||||
|
||||
<TitledPanel>
|
||||
<Tr slot="title" t={Translations.t.general.backgroundMap} />
|
||||
<Tr slot="title" t={Translations.t.general.backgroundMap} />
|
||||
|
||||
<div class="grid h-full w-full grid-cols-1 gap-2 md:grid-cols-2">
|
||||
<RasterLayerPicker
|
||||
|
|
|
|||
|
|
@ -77,7 +77,11 @@
|
|||
|
||||
{#if hasLayers}
|
||||
<form class="flex h-full w-full flex-col" on:submit|preventDefault={() => {}}>
|
||||
<button tabindex="-1" on:click={() => apply()} class="m-0 p-0 rounded-none h-full w-full cursor-pointer border-none">
|
||||
<button
|
||||
tabindex="-1"
|
||||
on:click={() => apply()}
|
||||
class="m-0 h-full w-full cursor-pointer rounded-none border-none p-0"
|
||||
>
|
||||
<span class="pointer-events-none relative h-full w-full">
|
||||
<OverlayMap
|
||||
interactive={false}
|
||||
|
|
|
|||
|
|
@ -152,8 +152,8 @@ class PointRenderingLayer {
|
|||
}
|
||||
const el = html.ConstructElement()
|
||||
|
||||
store.addCallbackAndRunD(tags => {
|
||||
if(tags._deleted === "yes"){
|
||||
store.addCallbackAndRunD((tags) => {
|
||||
if (tags._deleted === "yes") {
|
||||
html.SetClass("grayscale")
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -45,11 +45,15 @@
|
|||
/>
|
||||
</Loading>
|
||||
{:else}
|
||||
|
||||
<WikidatapreviewWithLoading wikidataId={wikidataId} imageStyle="max-width: 8rem; width: unset; height: 8rem">
|
||||
<WikidatapreviewWithLoading
|
||||
{wikidataId}
|
||||
imageStyle="max-width: 8rem; width: unset; height: 8rem"
|
||||
>
|
||||
<div slot="extra">
|
||||
<Tr cls="thanks w-fit self-center" t={ t.matchPercentage
|
||||
.Subs({ match: Math.round(species.score * 100) })}/>
|
||||
<Tr
|
||||
cls="thanks w-fit self-center"
|
||||
t={t.matchPercentage.Subs({ match: Math.round(species.score * 100) })}
|
||||
/>
|
||||
</div>
|
||||
</WikidatapreviewWithLoading>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@
|
|||
theme: state.layout?.id ?? "unkown",
|
||||
changeType: "create",
|
||||
snapOnto: snapToWay,
|
||||
reusePointWithinMeters: 1
|
||||
reusePointWithinMeters: 1,
|
||||
})
|
||||
await state.changes.applyAction(newElementAction)
|
||||
state.newFeatures.features.ping()
|
||||
|
|
@ -190,11 +190,12 @@
|
|||
<TitledPanel>
|
||||
<Tr slot="title" t={Translations.t.general.add.intro} />
|
||||
|
||||
|
||||
<div class="alert flex items-center justify-center">
|
||||
<EyeOffIcon class="w-8" />
|
||||
<Tr
|
||||
t={Translations.t.general.add.layerNotEnabled.Subs({ layer: selectedPreset.layer.name })}
|
||||
t={Translations.t.general.add.layerNotEnabled.Subs({
|
||||
layer: selectedPreset.layer.name,
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -202,9 +203,9 @@
|
|||
<button
|
||||
class="flex w-full gap-x-1"
|
||||
on:click={() => {
|
||||
abort()
|
||||
state.guistate.openFilterView(selectedPreset.layer)
|
||||
}}
|
||||
abort()
|
||||
state.guistate.openFilterView(selectedPreset.layer)
|
||||
}}
|
||||
>
|
||||
<Layers class="w-12" />
|
||||
<Tr t={Translations.t.general.add.openLayerControl} />
|
||||
|
|
@ -213,9 +214,9 @@
|
|||
<button
|
||||
class="primary flex w-full gap-x-1"
|
||||
on:click={() => {
|
||||
layerIsDisplayed.setData(true)
|
||||
abort()
|
||||
}}
|
||||
layerIsDisplayed.setData(true)
|
||||
abort()
|
||||
}}
|
||||
>
|
||||
<EyeIcon class="w-12" />
|
||||
<Tr
|
||||
|
|
@ -224,89 +225,86 @@
|
|||
</button>
|
||||
</div>
|
||||
</TitledPanel>
|
||||
|
||||
{:else if $layerHasFilters}
|
||||
<TitledPanel>
|
||||
<Tr slot="title" t={Translations.t.general.add.intro} />
|
||||
|
||||
|
||||
<!-- Some filters are enabled. The feature to add might already be mapped, but hidden -->
|
||||
<div class="alert flex items-center justify-center">
|
||||
<EyeOffIcon class="w-8" />
|
||||
<Tr t={Translations.t.general.add.disableFiltersExplanation} />
|
||||
</div>
|
||||
<div class="flex flex-wrap-reverse md:flex-nowrap">
|
||||
<button
|
||||
class="primary flex w-full gap-x-1"
|
||||
on:click={() => {
|
||||
abort()
|
||||
state.layerState.filteredLayers.get(selectedPreset.layer.id).disableAllFilters()
|
||||
}}
|
||||
>
|
||||
<EyeOffIcon class="w-12" />
|
||||
<Tr t={Translations.t.general.add.disableFilters} />
|
||||
</button>
|
||||
<button
|
||||
class="flex w-full gap-x-1"
|
||||
on:click={() => {
|
||||
abort()
|
||||
state.guistate.openFilterView(selectedPreset.layer)
|
||||
}}
|
||||
>
|
||||
<Layers class="w-12" />
|
||||
<Tr t={Translations.t.general.add.openLayerControl} />
|
||||
</button>
|
||||
</div>
|
||||
<div class="alert flex items-center justify-center">
|
||||
<EyeOffIcon class="w-8" />
|
||||
<Tr t={Translations.t.general.add.disableFiltersExplanation} />
|
||||
</div>
|
||||
<div class="flex flex-wrap-reverse md:flex-nowrap">
|
||||
<button
|
||||
class="primary flex w-full gap-x-1"
|
||||
on:click={() => {
|
||||
abort()
|
||||
state.layerState.filteredLayers.get(selectedPreset.layer.id).disableAllFilters()
|
||||
}}
|
||||
>
|
||||
<EyeOffIcon class="w-12" />
|
||||
<Tr t={Translations.t.general.add.disableFilters} />
|
||||
</button>
|
||||
<button
|
||||
class="flex w-full gap-x-1"
|
||||
on:click={() => {
|
||||
abort()
|
||||
state.guistate.openFilterView(selectedPreset.layer)
|
||||
}}
|
||||
>
|
||||
<Layers class="w-12" />
|
||||
<Tr t={Translations.t.general.add.openLayerControl} />
|
||||
</button>
|
||||
</div>
|
||||
</TitledPanel>
|
||||
{:else if !confirmedCategory}
|
||||
<!-- Second, confirm the category -->
|
||||
<TitledPanel>
|
||||
|
||||
<Tr slot="title"
|
||||
<Tr
|
||||
slot="title"
|
||||
t={Translations.t.general.add.confirmTitle.Subs({ title: selectedPreset.preset.title })}
|
||||
/>
|
||||
|
||||
{#if selectedPreset.preset.description}
|
||||
<Tr t={selectedPreset.preset.description} />
|
||||
{/if}
|
||||
{#if selectedPreset.preset.description}
|
||||
<Tr t={selectedPreset.preset.description} />
|
||||
{/if}
|
||||
|
||||
{#if selectedPreset.preset.exampleImages}
|
||||
<h3>
|
||||
{#if selectedPreset.preset.exampleImages.length === 1}
|
||||
<Tr t={Translations.t.general.example} />
|
||||
{:else}
|
||||
<Tr t={Translations.t.general.examples} />
|
||||
{/if}
|
||||
</h3>
|
||||
<span class="flex flex-wrap items-stretch">
|
||||
{#each selectedPreset.preset.exampleImages as src}
|
||||
<img {src} class="m-1 h-64 w-auto rounded-lg" />
|
||||
{/each}
|
||||
</span>
|
||||
{/if}
|
||||
<TagHint
|
||||
embedIn={(tags) => t.presetInfo.Subs({ tags })}
|
||||
{state}
|
||||
tags={new And(selectedPreset.preset.tags)}
|
||||
/>
|
||||
{#if selectedPreset.preset.exampleImages}
|
||||
<h3>
|
||||
{#if selectedPreset.preset.exampleImages.length === 1}
|
||||
<Tr t={Translations.t.general.example} />
|
||||
{:else}
|
||||
<Tr t={Translations.t.general.examples} />
|
||||
{/if}
|
||||
</h3>
|
||||
<span class="flex flex-wrap items-stretch">
|
||||
{#each selectedPreset.preset.exampleImages as src}
|
||||
<img {src} class="m-1 h-64 w-auto rounded-lg" />
|
||||
{/each}
|
||||
</span>
|
||||
{/if}
|
||||
<TagHint
|
||||
embedIn={(tags) => t.presetInfo.Subs({ tags })}
|
||||
{state}
|
||||
tags={new And(selectedPreset.preset.tags)}
|
||||
/>
|
||||
|
||||
<div class="flex w-full flex-wrap-reverse md:flex-nowrap">
|
||||
<BackButton on:click={() => (selectedPreset = undefined)} clss="w-full">
|
||||
<Tr t={t.backToSelect} />
|
||||
</BackButton>
|
||||
<div class="flex w-full flex-wrap-reverse md:flex-nowrap">
|
||||
<BackButton on:click={() => (selectedPreset = undefined)} clss="w-full">
|
||||
<Tr t={t.backToSelect} />
|
||||
</BackButton>
|
||||
|
||||
<NextButton on:click={() => (confirmedCategory = true)} clss="primary w-full">
|
||||
<div slot="image" class="relative">
|
||||
<ToSvelte construct={selectedPreset.icon} />
|
||||
<Confirm class="absolute bottom-0 right-0 h-4 w-4" />
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<Tr t={selectedPreset.text} />
|
||||
</div>
|
||||
</NextButton>
|
||||
</div>
|
||||
<NextButton on:click={() => (confirmedCategory = true)} clss="primary w-full">
|
||||
<div slot="image" class="relative">
|
||||
<ToSvelte construct={selectedPreset.icon} />
|
||||
<Confirm class="absolute bottom-0 right-0 h-4 w-4" />
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<Tr t={selectedPreset.text} />
|
||||
</div>
|
||||
</NextButton>
|
||||
</div>
|
||||
</TitledPanel>
|
||||
|
||||
{:else if _globalFilter?.length > 0 && _globalFilter?.length > checkedOfGlobalFilters}
|
||||
<Tr t={_globalFilter[checkedOfGlobalFilters].onNewPoint?.safetyCheck} cls="mx-12" />
|
||||
<SubtleButton
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@
|
|||
text: Translations.t.general.add.addNew.Subs(
|
||||
{ category: preset.title },
|
||||
preset.title["context"]
|
||||
)
|
||||
),
|
||||
}
|
||||
presets.push(simplified)
|
||||
}
|
||||
|
|
@ -100,7 +100,6 @@
|
|||
<TitledPanel>
|
||||
<Tr slot="title" t={Translations.t.general.add.intro} />
|
||||
|
||||
|
||||
{#each presets as preset}
|
||||
<NextButton on:click={() => dispatch("select", preset)}>
|
||||
<ToSvelte slot="image" construct={() => preset.icon} />
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@
|
|||
state.guistate.openFilterView(filteredLayer.layerDef)
|
||||
}}
|
||||
>
|
||||
<Layers class="w-12"/>
|
||||
<Layers class="w-12" />
|
||||
<Tr t={Translations.t.general.add.openLayerControl} />
|
||||
</button>
|
||||
|
||||
|
|
@ -119,7 +119,7 @@
|
|||
state.guistate.openFilterView(filteredLayer.layerDef)
|
||||
}}
|
||||
>
|
||||
<Layers class="w-12"/>
|
||||
<Layers class="w-12" />
|
||||
<Tr t={Translations.t.general.add.openLayerControl} />
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -31,11 +31,7 @@
|
|||
let idList = [value]
|
||||
if (Array.isArray(value)) {
|
||||
idList = value
|
||||
} else if (
|
||||
key !== "id" &&
|
||||
typeof value === "string" &&
|
||||
value?.startsWith("[")
|
||||
) {
|
||||
} else if (key !== "id" && typeof value === "string" && value?.startsWith("[")) {
|
||||
// This is a list of values
|
||||
idList = JSON.parse(value)
|
||||
}
|
||||
|
|
@ -59,7 +55,7 @@
|
|||
let mla = new MapLibreAdaptor(mlmap, {
|
||||
rasterLayer: state.mapProperties.rasterLayer,
|
||||
zoom: new UIEventSource<number>(17),
|
||||
maxzoom: new UIEventSource<number>(17)
|
||||
maxzoom: new UIEventSource<number>(17),
|
||||
})
|
||||
|
||||
mla.allowMoving.setData(false)
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@
|
|||
<div class="h-56 w-full">
|
||||
<NewPointLocationInput value={coordinate} {state}>
|
||||
<div class="h-20 w-full pb-10" slot="image">
|
||||
<Note class="h-10 w-full"/>
|
||||
<Note class="h-10 w-full" />
|
||||
</div>
|
||||
</NewPointLocationInput>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@
|
|||
allowDeleteOfFreeform &&
|
||||
!$freeformInput &&
|
||||
!$freeformInputUnvalidated &&
|
||||
!checkedMappings?.some(m => m) &&
|
||||
!checkedMappings?.some((m) => m) &&
|
||||
$tags[config.freeform.key] // We need to have a current value in order to delete it
|
||||
) {
|
||||
selectedTags = new Tag(config.freeform.key, "")
|
||||
|
|
@ -495,7 +495,7 @@
|
|||
<!-- TagRenderingQuestion-buttons -->
|
||||
<slot name="cancel" />
|
||||
<slot name="save-button" {selectedTags}>
|
||||
{#if config.freeform?.key && allowDeleteOfFreeform && !checkedMappings?.some(m => m) && !$freeformInput && !$freeformInputUnvalidated && $tags[config.freeform.key]}
|
||||
{#if config.freeform?.key && allowDeleteOfFreeform && !checkedMappings?.some((m) => m) && !$freeformInput && !$freeformInputUnvalidated && $tags[config.freeform.key]}
|
||||
<button
|
||||
class="primary flex"
|
||||
on:click|stopPropagation|preventDefault={() => onSave()}
|
||||
|
|
@ -521,13 +521,11 @@
|
|||
<TagHint {state} tags={selectedTags} currentProperties={$tags} />
|
||||
<span class="flex flex-wrap">
|
||||
{#if $featureSwitchIsTesting}
|
||||
<div class="alert">
|
||||
Testmode
|
||||
</div>
|
||||
<div class="alert">Testmode </div>
|
||||
{/if}
|
||||
{#if $featureSwitchIsTesting || $featureSwitchIsDebugging}
|
||||
<a class="small" on:click={() => console.log("Configuration is ", config)}>
|
||||
{config.id}
|
||||
{config.id}
|
||||
</a>
|
||||
{/if}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -17,14 +17,12 @@ export default class QueryParameterDocumentation {
|
|||
'"URL-parameters are extra parts of the URL used to set the state.',
|
||||
"For example, if the url is `https://mapcomplete.org/cyclofix?lat=51.0&lon=4.3&z=5&test=true#node/1234`, " +
|
||||
"the URL-parameters are stated in the part between the `?` and the `#`. There are multiple, all separated by `&`, namely: ",
|
||||
MarkdownUtils.list(
|
||||
[
|
||||
"The url-parameter `lat` is `51.0` in this instance",
|
||||
"The url-parameter `lon` is `4.3` in this instance",
|
||||
"The url-parameter `z` is `5` in this instance",
|
||||
"The url-parameter `test` is `true` in this instance",
|
||||
]
|
||||
),
|
||||
MarkdownUtils.list([
|
||||
"The url-parameter `lat` is `51.0` in this instance",
|
||||
"The url-parameter `lon` is `4.3` in this instance",
|
||||
"The url-parameter `z` is `5` in this instance",
|
||||
"The url-parameter `test` is `true` in this instance",
|
||||
]),
|
||||
"Finally, the URL-hash is the part after the `#`. It is `node/1234` in this case.",
|
||||
]
|
||||
|
||||
|
|
@ -72,7 +70,7 @@ export default class QueryParameterDocumentation {
|
|||
]
|
||||
this.UrlParamDocs().forEach((value, key) => {
|
||||
const c = [
|
||||
"## "+key,
|
||||
"## " + key,
|
||||
value,
|
||||
QueryParameters.defaults[key] === undefined
|
||||
? "No default value set"
|
||||
|
|
|
|||
|
|
@ -407,10 +407,10 @@ export default class SpecialVisualizations {
|
|||
new HistogramViz(),
|
||||
new StealViz(),
|
||||
{
|
||||
funcName : "minimap",
|
||||
docs :"A small map showing the selected feature.",
|
||||
needsUrls : [],
|
||||
args : [
|
||||
funcName: "minimap",
|
||||
docs: "A small map showing the selected feature.",
|
||||
needsUrls: [],
|
||||
args: [
|
||||
{
|
||||
doc: "The (maximum) zoomlevel: the target zoomlevel after fitting the entire feature. The minimap will fit the entire feature, then zoom out to this zoom level. The higher, the more zoomed in with 1 being the entire world and 19 being really close",
|
||||
name: "zoomlevel",
|
||||
|
|
@ -422,17 +422,17 @@ export default class SpecialVisualizations {
|
|||
defaultValue: "id",
|
||||
},
|
||||
],
|
||||
example: "`{minimap()}`, `{minimap(17, id, _list_of_embedded_feature_ids_calculated_by_calculated_tag):height:10rem; border: 2px solid black}`",
|
||||
example:
|
||||
"`{minimap()}`, `{minimap(17, id, _list_of_embedded_feature_ids_calculated_by_calculated_tag):height:10rem; border: 2px solid black}`",
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
args: string[],
|
||||
feature: Feature
|
||||
|
||||
): SvelteUIElement {
|
||||
return new SvelteUIElement(MinimapViz, {state, args, feature, tagSource})
|
||||
}
|
||||
return new SvelteUIElement(MinimapViz, { state, args, feature, tagSource })
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "split_button",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,6 @@ import { Store } from "../../Logic/UIEventSource"
|
|||
|
||||
export interface MCService {
|
||||
name: string
|
||||
status: Store<"online" | "degraded" | "offline">,
|
||||
status: Store<"online" | "degraded" | "offline">
|
||||
message?: Store<undefined | string>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,23 @@
|
|||
<script lang="ts">
|
||||
import StatusIcon from "./StatusIcon.svelte"
|
||||
import type { MCService } from "./MCService.js"
|
||||
import AccordionSingle from "../Flowbite/AccordionSingle.svelte"
|
||||
import StatusIcon from "./StatusIcon.svelte"
|
||||
import type { MCService } from "./MCService.js"
|
||||
import AccordionSingle from "../Flowbite/AccordionSingle.svelte"
|
||||
|
||||
export let service: MCService
|
||||
let status = service.status
|
||||
let msg = service.message
|
||||
export let service: MCService
|
||||
let status = service.status
|
||||
let msg = service.message
|
||||
</script>
|
||||
|
||||
|
||||
<AccordionSingle>
|
||||
|
||||
<h3 slot="header" class="flex items-center m-0"> <StatusIcon status={$status}/> {service.name}</h3>
|
||||
<div class="mx-4">
|
||||
{#if $msg}
|
||||
{$msg}
|
||||
<h3 slot="header" class="m-0 flex items-center">
|
||||
<StatusIcon status={$status} />
|
||||
{service.name}
|
||||
</h3>
|
||||
<div class="mx-4">
|
||||
{#if $msg}
|
||||
{$msg}
|
||||
{:else}
|
||||
No extra information available
|
||||
No extra information available
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</AccordionSingle>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
<script lang="ts">
|
||||
|
||||
import { Store, Stores, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import StatusIcon from "./StatusIcon.svelte"
|
||||
import type { MCService } from "./MCService"
|
||||
|
|
@ -10,7 +9,6 @@
|
|||
import Loading from "../Base/Loading.svelte"
|
||||
import Checkbox from "../Base/Checkbox.svelte"
|
||||
|
||||
|
||||
let services: MCService[] = []
|
||||
|
||||
let recheckSignal: UIEventSource<any> = new UIEventSource<any>(undefined)
|
||||
|
|
@ -22,25 +20,21 @@
|
|||
|
||||
function check() {
|
||||
const promise = raw ? Utils.download(url) : Utils.downloadJson(url)
|
||||
promise
|
||||
?.then((d) => src.setData({ success: d }))
|
||||
?.catch((err) => src.setData({ error: err }))
|
||||
promise?.then((d) => src.setData({ success: d }))?.catch((err) => src.setData({ error: err }))
|
||||
}
|
||||
|
||||
check()
|
||||
recheckSignal.addCallback(_ => check())
|
||||
checkSignal.addCallback(_ => {
|
||||
recheckSignal.addCallback((_) => check())
|
||||
checkSignal.addCallback((_) => {
|
||||
if (autoCheckAgain.data) {
|
||||
check()
|
||||
}
|
||||
})
|
||||
return src
|
||||
|
||||
}
|
||||
|
||||
|
||||
function simpleMessage(s: Store<{ success: any } | { error: any }>): Store<string> {
|
||||
return s.mapD(s => {
|
||||
return s.mapD((s) => {
|
||||
if (s["success"]) {
|
||||
return JSON.stringify(s["success"])
|
||||
}
|
||||
|
|
@ -53,7 +47,7 @@
|
|||
const osmApi = connection.apiIsOnline
|
||||
services.push({
|
||||
name: connection.Backend(),
|
||||
status: osmApi.mapD(serviceState => {
|
||||
status: osmApi.mapD((serviceState) => {
|
||||
switch (serviceState) {
|
||||
case "offline":
|
||||
return "offline"
|
||||
|
|
@ -76,7 +70,7 @@
|
|||
const status = testDownload(s + "/overview")
|
||||
services.push({
|
||||
name: s,
|
||||
status: status.mapD(s => {
|
||||
status: status.mapD((s) => {
|
||||
if (s["error"]) {
|
||||
return "offline"
|
||||
}
|
||||
|
|
@ -89,7 +83,7 @@
|
|||
}
|
||||
return "online"
|
||||
}),
|
||||
message: status.mapD(s => {
|
||||
message: status.mapD((s) => {
|
||||
if (s["error"]) {
|
||||
return s["error"]
|
||||
}
|
||||
|
|
@ -99,24 +93,20 @@
|
|||
})
|
||||
}
|
||||
{
|
||||
services.push(
|
||||
{
|
||||
name: Constants.GeoIpServer,
|
||||
status: testDownload(Constants.GeoIpServer + "/status").mapD(result => {
|
||||
if (result["success"].online) {
|
||||
return "online"
|
||||
}
|
||||
if (result["error"]) {
|
||||
return "offline"
|
||||
} else {
|
||||
return "degraded"
|
||||
}
|
||||
}),
|
||||
message: simpleMessage(
|
||||
testDownload(Constants.GeoIpServer + "/ip"),
|
||||
),
|
||||
},
|
||||
)
|
||||
services.push({
|
||||
name: Constants.GeoIpServer,
|
||||
status: testDownload(Constants.GeoIpServer + "/status").mapD((result) => {
|
||||
if (result["success"].online) {
|
||||
return "online"
|
||||
}
|
||||
if (result["error"]) {
|
||||
return "offline"
|
||||
} else {
|
||||
return "degraded"
|
||||
}
|
||||
}),
|
||||
message: simpleMessage(testDownload(Constants.GeoIpServer + "/ip")),
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
|
|
@ -124,7 +114,7 @@
|
|||
const status = testDownload(s.replace(/\/report$/, "/status"))
|
||||
services.push({
|
||||
name: s,
|
||||
status: status.mapD(s => {
|
||||
status: status.mapD((s) => {
|
||||
if (s["error"]) {
|
||||
return "offline"
|
||||
}
|
||||
|
|
@ -143,7 +133,7 @@
|
|||
const status = testDownload(s + "/status")
|
||||
services.push({
|
||||
name: s,
|
||||
status: status.mapD(s => {
|
||||
status: status.mapD((s) => {
|
||||
if (s["error"]) {
|
||||
return "offline"
|
||||
}
|
||||
|
|
@ -162,7 +152,7 @@
|
|||
const status = testDownload(s + "/summary/status.json")
|
||||
services.push({
|
||||
name: s,
|
||||
status: status.mapD(s => {
|
||||
status: status.mapD((s) => {
|
||||
if (s["error"]) {
|
||||
return "offline"
|
||||
}
|
||||
|
|
@ -180,7 +170,7 @@
|
|||
|
||||
return "online"
|
||||
}),
|
||||
message: status.mapD(s => {
|
||||
message: status.mapD((s) => {
|
||||
if (s["error"]) {
|
||||
return s["error"]
|
||||
}
|
||||
|
|
@ -201,7 +191,7 @@
|
|||
const status = testDownload(s + "/0.0.0.json")
|
||||
services.push({
|
||||
name: s,
|
||||
status: status.mapD(s => {
|
||||
status: status.mapD((s) => {
|
||||
if (s["error"]) {
|
||||
return "offline"
|
||||
}
|
||||
|
|
@ -211,12 +201,10 @@
|
|||
}
|
||||
return "degraded"
|
||||
}),
|
||||
message: status.map(s => JSON.stringify(s)),
|
||||
message: status.map((s) => JSON.stringify(s)),
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
for (const defaultOverpassUrl of Constants.defaultOverpassUrls) {
|
||||
const statusUrl = defaultOverpassUrl.replace(/\/interpreter$/, "/status")
|
||||
|
|
@ -224,7 +212,7 @@
|
|||
|
||||
services.push({
|
||||
name: "Overpass-server: " + defaultOverpassUrl,
|
||||
status: status.mapD(result => {
|
||||
status: status.mapD((result) => {
|
||||
if (result["error"]) {
|
||||
return "offline"
|
||||
}
|
||||
|
|
@ -247,7 +235,7 @@
|
|||
{
|
||||
services.push({
|
||||
name: "Mangrove reviews",
|
||||
status: testDownload("https://api.mangrove.reviews", true).mapD(r => {
|
||||
status: testDownload("https://api.mangrove.reviews", true).mapD((r) => {
|
||||
if (r["success"]) {
|
||||
return "online"
|
||||
}
|
||||
|
|
@ -256,18 +244,17 @@
|
|||
})
|
||||
}
|
||||
|
||||
|
||||
let all = new UIEventSource<"online" | "degraded" | "offline">("online")
|
||||
let someLoading = new UIEventSource(true)
|
||||
|
||||
function setAll() {
|
||||
const data = Utils.NoNull(services.map(s => s.status.data))
|
||||
const data = Utils.NoNull(services.map((s) => s.status.data))
|
||||
someLoading.setData(data.length !== services.length)
|
||||
if (data.some(d => d === "offline")) {
|
||||
if (data.some((d) => d === "offline")) {
|
||||
all.setData("offline")
|
||||
} else if (data.some(d => d === "degraded")) {
|
||||
} else if (data.some((d) => d === "degraded")) {
|
||||
all.setData("degraded")
|
||||
} else if (data.some(d => d === "online")) {
|
||||
} else if (data.some((d) => d === "online")) {
|
||||
all.setData("online")
|
||||
} else {
|
||||
all.setData(undefined)
|
||||
|
|
@ -291,7 +278,6 @@
|
|||
*/
|
||||
async function setTrafficLight(state: "online" | "degraded" | "offline") {
|
||||
try {
|
||||
|
||||
const url = trafficLightUrl
|
||||
const status = await Utils.downloadJson(url + "status")
|
||||
console.log(status)
|
||||
|
|
@ -300,7 +286,6 @@
|
|||
return
|
||||
}
|
||||
switch (state) {
|
||||
|
||||
case "offline":
|
||||
await Utils.download(url + "configure?mode=3")
|
||||
break
|
||||
|
|
@ -313,37 +298,32 @@
|
|||
default:
|
||||
await Utils.download(url + "configure?mode=7")
|
||||
break
|
||||
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Could not connect to the traffic light")
|
||||
}
|
||||
}
|
||||
|
||||
all.addCallbackAndRunD(state => {
|
||||
all.addCallbackAndRunD((state) => {
|
||||
setTrafficLight(state)
|
||||
})
|
||||
enableTrafficLight.addCallbackAndRunD(_ => {
|
||||
enableTrafficLight.addCallbackAndRunD((_) => {
|
||||
setTrafficLight(all.data)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<h1>MapComplete status indicators</h1>
|
||||
|
||||
<div class="flex">
|
||||
|
||||
{#if $someLoading}
|
||||
<Loading />
|
||||
{/if}
|
||||
<StatusIcon status={$all} cls="w-16 h-16" />
|
||||
<button on:click={() => recheckSignal.ping()}>Check again</button>
|
||||
<Checkbox selected={autoCheckAgain}>
|
||||
Automatically check again every 10s
|
||||
</Checkbox>
|
||||
<Checkbox selected={autoCheckAgain}>Automatically check again every 10s</Checkbox>
|
||||
</div>
|
||||
|
||||
{#if $trafficLightIsOnline?.["success"] }
|
||||
{#if $trafficLightIsOnline?.["success"]}
|
||||
<Checkbox selected={enableTrafficLight}>Enable traffic light</Checkbox>
|
||||
{/if}
|
||||
|
||||
|
|
@ -351,12 +331,16 @@
|
|||
<ServiceIndicator {service} />
|
||||
{/each}
|
||||
|
||||
<button on:click={() => {
|
||||
<button
|
||||
on:click={() => {
|
||||
fetch(Constants.ErrorReportServer, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
message: "Test via the status page, not an actual error",
|
||||
version: Constants.vNumber,
|
||||
}),
|
||||
})
|
||||
}}>Test error report function</button>
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
message: "Test via the status page, not an actual error",
|
||||
version: Constants.vNumber,
|
||||
}),
|
||||
})
|
||||
}}
|
||||
>
|
||||
Test error report function
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -13,16 +13,15 @@
|
|||
</script>
|
||||
|
||||
{#if status === "online"}
|
||||
<CheckCircle class={twJoin(cls,"rounded-full shrink-0")} style="color: #22cc22" />
|
||||
<CheckCircle class={twJoin(cls, "shrink-0 rounded-full")} style="color: #22cc22" />
|
||||
{:else if status === "degraded"}
|
||||
<Exclamation class={twJoin(cls,"rounded-full shrink-0")} style="color: #eecc22" />
|
||||
<Exclamation class={twJoin(cls, "shrink-0 rounded-full")} style="color: #eecc22" />
|
||||
{:else if status === "offline"}
|
||||
<XCircleIcon class={twJoin(cls,"rounded-full shrink-0")} style="color: #bb2222" />
|
||||
<XCircleIcon class={twJoin(cls, "shrink-0 rounded-full")} style="color: #bb2222" />
|
||||
{:else if status === undefined}
|
||||
<div class={twJoin(cls,"rounded-full shrink-0")}>
|
||||
<div class={twJoin(cls, "shrink-0 rounded-full")}>
|
||||
<Loading />
|
||||
</div>
|
||||
{:else}
|
||||
? {status}
|
||||
{/if}
|
||||
|
||||
|
|
|
|||
|
|
@ -99,13 +99,18 @@
|
|||
|
||||
function genTitle(value: any, singular: string, i: number): Translation {
|
||||
try {
|
||||
|
||||
if (schema.hints.title) {
|
||||
const v = Function("value", "return " + schema.hints.title)(value)
|
||||
return Translations.T(v)
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Warning: could not translate a title for " + `${singular} ${i} with function ` + schema.hints.title + " and value " + JSON.stringify(value))
|
||||
console.log(
|
||||
"Warning: could not translate a title for " +
|
||||
`${singular} ${i} with function ` +
|
||||
schema.hints.title +
|
||||
" and value " +
|
||||
JSON.stringify(value)
|
||||
)
|
||||
}
|
||||
return Translations.T(`${singular} ${i}`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -213,7 +213,7 @@
|
|||
|
||||
<main>
|
||||
<div class="absolute top-0 left-0 h-screen w-screen overflow-hidden">
|
||||
<MaplibreMap map={maplibremap} mapProperties={mapproperties} autorecovery={true}/>
|
||||
<MaplibreMap map={maplibremap} mapProperties={mapproperties} autorecovery={true} />
|
||||
</div>
|
||||
|
||||
{#if $visualFeedback}
|
||||
|
|
@ -286,10 +286,8 @@
|
|||
on:keydown={forwardEventToMap}
|
||||
htmlElem={openCurrentViewLayerButton}
|
||||
>
|
||||
<div class="w-8 h-8 cursor-pointer">
|
||||
<ToSvelte
|
||||
construct={() => currentViewLayer.defaultIcon()}
|
||||
/>
|
||||
<div class="h-8 w-8 cursor-pointer">
|
||||
<ToSvelte construct={() => currentViewLayer.defaultIcon()} />
|
||||
</div>
|
||||
</MapControlButton>
|
||||
{/if}
|
||||
|
|
@ -300,10 +298,8 @@
|
|||
<div class="alert w-fit">Testmode</div>
|
||||
</If>
|
||||
{#if state.osmConnection.Backend().startsWith("https://master.apis.dev.openstreetmap.org")}
|
||||
<div class="thanks">
|
||||
Testserver
|
||||
</div>
|
||||
{/if}
|
||||
<div class="thanks">Testserver</div>
|
||||
{/if}
|
||||
<If condition={state.featureSwitches.featureSwitchFakeUser}>
|
||||
<div class="alert w-fit">Faking a user (Testmode)</div>
|
||||
</If>
|
||||
|
|
@ -551,13 +547,13 @@
|
|||
state.guistate.backgroundLayerSelectionIsOpened.setData(false)
|
||||
}}
|
||||
>
|
||||
<RasterLayerOverview
|
||||
{availableLayers}
|
||||
map={state.map}
|
||||
mapproperties={state.mapProperties}
|
||||
userstate={state.userRelatedState}
|
||||
visible={state.guistate.backgroundLayerSelectionIsOpened}
|
||||
/>
|
||||
<RasterLayerOverview
|
||||
{availableLayers}
|
||||
map={state.map}
|
||||
mapproperties={state.mapProperties}
|
||||
userstate={state.userRelatedState}
|
||||
visible={state.guistate.backgroundLayerSelectionIsOpened}
|
||||
/>
|
||||
</FloatOver>
|
||||
</IfHidden>
|
||||
|
||||
|
|
@ -583,7 +579,7 @@
|
|||
<div slot="content0" class="flex flex-col">
|
||||
<AboutMapComplete {state} />
|
||||
<div class="m-2 flex flex-col">
|
||||
<HotkeyTable/>
|
||||
<HotkeyTable />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -6,45 +6,24 @@ import Gender_inter from "../../assets/svg/Gender_inter.svelte"
|
|||
import Gender_trans from "../../assets/svg/Gender_trans.svelte"
|
||||
import Gender_queer from "../../assets/svg/Gender_queer.svelte"
|
||||
|
||||
|
||||
export default class WikidataPreviewBox {
|
||||
export default class WikidataPreviewBox {
|
||||
private static isHuman = [{ p: 31 /*is a*/, q: 5 /* human */ }]
|
||||
public static extraProperties: {
|
||||
requires?: { p: number; q?: number }[]
|
||||
property: string
|
||||
textMode?: Map<string, string>
|
||||
display:
|
||||
| TypedTranslation<{ value }>
|
||||
| Map<string, any>,
|
||||
display: TypedTranslation<{ value }> | Map<string, any>
|
||||
}[] = [
|
||||
{
|
||||
requires: WikidataPreviewBox.isHuman,
|
||||
property: "P21",
|
||||
display: new Map([
|
||||
[
|
||||
"Q6581097",
|
||||
Gender_male
|
||||
],
|
||||
[
|
||||
"Q6581072",
|
||||
Gender_female
|
||||
],
|
||||
[
|
||||
"Q1097630",
|
||||
Gender_inter
|
||||
],
|
||||
[
|
||||
"Q1052281",
|
||||
Gender_trans /*'transwomen'*/
|
||||
],
|
||||
[
|
||||
"Q2449503",
|
||||
Gender_trans /*'transmen'*/
|
||||
],
|
||||
[
|
||||
"Q48270",
|
||||
Gender_queer
|
||||
]
|
||||
["Q6581097", Gender_male],
|
||||
["Q6581072", Gender_female],
|
||||
["Q1097630", Gender_inter],
|
||||
["Q1052281", Gender_trans /*'transwomen'*/],
|
||||
["Q2449503", Gender_trans /*'transmen'*/],
|
||||
["Q48270", Gender_queer],
|
||||
]),
|
||||
textMode: new Map([
|
||||
["Q6581097", "♂️"],
|
||||
|
|
@ -52,19 +31,18 @@ export default class WikidataPreviewBox {
|
|||
["Q1097630", "⚥️"],
|
||||
["Q1052281", "🏳️⚧️" /*'transwomen'*/],
|
||||
["Q2449503", "🏳️⚧️" /*'transmen'*/],
|
||||
["Q48270", "🏳️🌈 ⚧"]
|
||||
])
|
||||
["Q48270", "🏳️🌈 ⚧"],
|
||||
]),
|
||||
},
|
||||
{
|
||||
property: "P569",
|
||||
requires: WikidataPreviewBox.isHuman,
|
||||
display: Translations.t.general.wikipedia.previewbox.born
|
||||
display: Translations.t.general.wikipedia.previewbox.born,
|
||||
},
|
||||
{
|
||||
property: "P570",
|
||||
requires: WikidataPreviewBox.isHuman,
|
||||
display: Translations.t.general.wikipedia.previewbox.died
|
||||
}
|
||||
display: Translations.t.general.wikipedia.previewbox.died,
|
||||
},
|
||||
]
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
<script lang="ts">
|
||||
|
||||
import { Translation } from "../i18n/Translation"
|
||||
import WikidataPreviewBox from "./WikidataPreviewBox"
|
||||
import { WikidataResponse } from "../../Logic/Web/Wikidata"
|
||||
|
|
@ -7,7 +6,7 @@
|
|||
|
||||
export let wikidata: WikidataResponse
|
||||
|
||||
let propertiesToRender = WikidataPreviewBox.extraProperties.filter(property => {
|
||||
let propertiesToRender = WikidataPreviewBox.extraProperties.filter((property) => {
|
||||
for (const requirement of property.requires) {
|
||||
if (!wikidata.claims?.has("P" + requirement.p)) {
|
||||
return false
|
||||
|
|
@ -20,30 +19,30 @@
|
|||
if (wikidata.claims?.get(key) === undefined) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
function getProperty(property: {property: string}){
|
||||
function getProperty(property: { property: string }) {
|
||||
const key = property.property
|
||||
const value = Array.from(wikidata.claims?.get(key)).join(", ")
|
||||
return value
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
{#if propertiesToRender.length > 0}
|
||||
<div class="flex justify-start items-center">
|
||||
<div class="flex items-center justify-start">
|
||||
{#each propertiesToRender as property}
|
||||
{#if typeof property.display === "string" }
|
||||
{#if typeof property.display === "string"}
|
||||
{property.display}
|
||||
{:else if property.display instanceof Translation}
|
||||
<Tr cls="m-2 shrink-0"
|
||||
t={property.display.Subs({value: getProperty(property)}) } />
|
||||
<Tr cls="m-2 shrink-0" t={property.display.Subs({ value: getProperty(property) })} />
|
||||
{:else}
|
||||
<svelte:component this={property.display.get(getProperty(property))} class="h-6 w-fit m-1"/>
|
||||
<svelte:component
|
||||
this={property.display.get(getProperty(property))}
|
||||
class="m-1 h-6 w-fit"
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
<script lang="ts">
|
||||
|
||||
import Wikidata, { WikidataResponse } from "../../Logic/Web/Wikidata"
|
||||
import { Translation } from "../i18n/Translation"
|
||||
import { WikimediaImageProvider } from "../../Logic/ImageProviders/WikimediaImageProvider"
|
||||
|
|
@ -12,31 +11,31 @@
|
|||
|
||||
let imageProperty: string | undefined = Array.from(wikidata?.claims?.get("P18") ?? [])[0]
|
||||
let imageUrl = WikimediaImageProvider.singleton.PrepUrl(imageProperty)?.url
|
||||
|
||||
</script>
|
||||
|
||||
<div class="flex w-full p-2 flex-wrap">
|
||||
|
||||
<div class="flex w-full flex-wrap p-2">
|
||||
{#if imageUrl}
|
||||
<img src={imageUrl} style={imageStyle} class="mr-2" />
|
||||
{/if}
|
||||
|
||||
<div class="flex flex-col flex-grow">
|
||||
|
||||
<div class="flex w-full justify-between flex-wrap">
|
||||
<Tr cls="font-bold" t={ Translation.fromMap(wikidata.labels) } />
|
||||
<a href={Wikidata.IdToArticle(wikidata.id)} target="_blank" class="flex must-link items-center">
|
||||
<Wikidata_icon class="w-10" /> {wikidata.id}
|
||||
<div class="flex flex-grow flex-col">
|
||||
<div class="flex w-full flex-wrap justify-between">
|
||||
<Tr cls="font-bold" t={Translation.fromMap(wikidata.labels)} />
|
||||
<a
|
||||
href={Wikidata.IdToArticle(wikidata.id)}
|
||||
target="_blank"
|
||||
class="must-link flex items-center"
|
||||
>
|
||||
<Wikidata_icon class="w-10" />
|
||||
{wikidata.id}
|
||||
</a>
|
||||
</div>
|
||||
<Tr t={ Translation.fromMap(wikidata.descriptions, true)} />
|
||||
|
||||
|
||||
<Tr t={Translation.fromMap(wikidata.descriptions, true)} />
|
||||
|
||||
<div class="flex">
|
||||
<WikidataQuickfacts {wikidata} />
|
||||
<WikidataQuickfacts {wikidata} />
|
||||
</div>
|
||||
|
||||
<slot name="extra"></slot>
|
||||
<slot name="extra" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -10,13 +10,14 @@
|
|||
export let wikidataId: Store<string>
|
||||
export let imageStyle: string = undefined
|
||||
|
||||
let wikidata: Store<{ success: WikidataResponse } | { error: any }> = wikidataId.stabilized(100).bind((id) => {
|
||||
if (id === undefined || id === "" || id === "Q") {
|
||||
return null
|
||||
}
|
||||
return Wikidata.LoadWikidataEntry(id)
|
||||
})
|
||||
|
||||
let wikidata: Store<{ success: WikidataResponse } | { error: any }> = wikidataId
|
||||
.stabilized(100)
|
||||
.bind((id) => {
|
||||
if (id === undefined || id === "" || id === "Q") {
|
||||
return null
|
||||
}
|
||||
return Wikidata.LoadWikidataEntry(id)
|
||||
})
|
||||
</script>
|
||||
|
||||
{#if $wikidata === undefined}
|
||||
|
|
@ -28,7 +29,6 @@
|
|||
{$wikidata["error"]}
|
||||
</div>
|
||||
{:else}
|
||||
|
||||
<Wikidatapreview {imageStyle} wikidata={$wikidata["success"]}>
|
||||
<slot name="extra" slot="extra" />
|
||||
</Wikidatapreview>
|
||||
|
|
|
|||
|
|
@ -18,12 +18,11 @@
|
|||
)
|
||||
</script>
|
||||
|
||||
<div class="low-interaction border-gray-300 border-dashed rounded-xl p-2 flex flex-col">
|
||||
<div class="low-interaction flex flex-col rounded-xl border-dashed border-gray-300 p-2">
|
||||
{#if $titleOnly}
|
||||
<Loading>{$wikipediaDetails.title}</Loading>
|
||||
{/if}
|
||||
|
||||
|
||||
{#if $wikipediaDetails.wikidata}
|
||||
<Wikidatapreview wikidata={$wikipediaDetails.wikidata} />
|
||||
{/if}
|
||||
|
|
@ -36,7 +35,12 @@
|
|||
{:else}
|
||||
<FromHtml clss="wikipedia-article" src={$wikipediaDetails.firstParagraph} />
|
||||
{#if $wikipediaDetails.articleUrl}
|
||||
<a class="flex self-end my-2" href={$wikipediaDetails.articleUrl} rel="noreferrer" target="_blank">
|
||||
<a
|
||||
class="my-2 flex self-end"
|
||||
href={$wikipediaDetails.articleUrl}
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<Wikipedia class="h-6 w-6" />
|
||||
<Tr t={Translations.t.general.wikipedia.fromWikipedia} />
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export default class Locale {
|
|||
public static showLinkOnMobile: UIEventSource<boolean> = new UIEventSource<boolean>(false)
|
||||
public static language: UIEventSource<string> = Locale.setup()
|
||||
|
||||
public static getBestSupportedLanguage(browserLanguage?: string){
|
||||
public static getBestSupportedLanguage(browserLanguage?: string) {
|
||||
browserLanguage ??= navigator.languages?.[0] ?? navigator.language ?? "en"
|
||||
console.log("Browser language is", browserLanguage)
|
||||
const availableLanguages = Object.keys(native)
|
||||
|
|
@ -22,10 +22,10 @@ export default class Locale {
|
|||
}
|
||||
browserLanguage = browserLanguage.replace(/[-_].*/g, "")
|
||||
const hasBrowserLangFallback = availableLanguages.indexOf(browserLanguage) >= 0
|
||||
if(hasBrowserLangFallback){
|
||||
return browserLanguage
|
||||
if (hasBrowserLangFallback) {
|
||||
return browserLanguage
|
||||
}
|
||||
console.log("Language",browserLanguage,"not supported, defaulting to english")
|
||||
console.log("Language", browserLanguage, "not supported, defaulting to english")
|
||||
return "en"
|
||||
}
|
||||
|
||||
|
|
@ -72,7 +72,7 @@ export default class Locale {
|
|||
} else {
|
||||
let browserLanguage = "en"
|
||||
if (typeof navigator !== "undefined") {
|
||||
browserLanguage = Locale.getBestSupportedLanguage()
|
||||
browserLanguage = Locale.getBestSupportedLanguage()
|
||||
}
|
||||
source = LocalStorageSource.Get("language", browserLanguage)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ export class Translation extends BaseUIElement {
|
|||
constructor(
|
||||
translations: string | Record<string, string>,
|
||||
context?: string,
|
||||
strictLanguages?: boolean,
|
||||
strictLanguages?: boolean
|
||||
) {
|
||||
super()
|
||||
this._strictLanguages = strictLanguages
|
||||
|
|
@ -62,7 +62,7 @@ export class Translation extends BaseUIElement {
|
|||
`. The offending object is: `,
|
||||
translations[translationsKey],
|
||||
"\n current translations are: ",
|
||||
translations,
|
||||
translations
|
||||
)
|
||||
throw (
|
||||
"Error in an object depicting a translation: a non-string object was found. (" +
|
||||
|
|
@ -75,7 +75,7 @@ export class Translation extends BaseUIElement {
|
|||
if (count === 0) {
|
||||
console.error(
|
||||
"Constructing a translation, but the object containing translations is empty " +
|
||||
(context ?? "No context given"),
|
||||
(context ?? "No context given")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -94,7 +94,7 @@ export class Translation extends BaseUIElement {
|
|||
[],
|
||||
(f) => {
|
||||
this.onDestroy = f
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
return this._currentLanguage
|
||||
|
|
@ -113,7 +113,7 @@ export class Translation extends BaseUIElement {
|
|||
|
||||
static ExtractAllTranslationsFrom(
|
||||
object: any,
|
||||
context = "",
|
||||
context = ""
|
||||
): { context: string; tr: Translation }[] {
|
||||
const allTranslations: { context: string; tr: Translation }[] = []
|
||||
for (const key in object) {
|
||||
|
|
@ -127,7 +127,7 @@ export class Translation extends BaseUIElement {
|
|||
}
|
||||
if (typeof v === "object") {
|
||||
allTranslations.push(
|
||||
...Translation.ExtractAllTranslationsFrom(v, context + "." + key),
|
||||
...Translation.ExtractAllTranslationsFrom(v, context + "." + key)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -254,7 +254,7 @@ export class Translation extends BaseUIElement {
|
|||
*/
|
||||
public OnEveryLanguage(
|
||||
f: (s: string, language: string) => string,
|
||||
context?: string,
|
||||
context?: string
|
||||
): Translation {
|
||||
const newTranslations = {}
|
||||
for (const lang in this.translations) {
|
||||
|
|
@ -337,7 +337,7 @@ export class Translation extends BaseUIElement {
|
|||
const htmlElement = document.createElement("div")
|
||||
htmlElement.innerHTML = render
|
||||
const images = Array.from(htmlElement.getElementsByTagName("img")).map(
|
||||
(img) => img.src,
|
||||
(img) => img.src
|
||||
)
|
||||
allIcons.push(...images)
|
||||
} else {
|
||||
|
|
@ -350,7 +350,7 @@ export class Translation extends BaseUIElement {
|
|||
.map((img) => img.match(/src=("[^"]+"|'[^']+'|[^/ ]+)/))
|
||||
.filter((match) => match != null)
|
||||
.map((match) =>
|
||||
match[1].trim().replace(/^['"]/, "").replace(/['"]$/, ""),
|
||||
match[1].trim().replace(/^['"]/, "").replace(/['"]$/, "")
|
||||
)
|
||||
allIcons.push(...sources)
|
||||
}
|
||||
|
|
@ -398,7 +398,7 @@ export class TypedTranslation<T extends Record<string, any>> extends Translation
|
|||
}
|
||||
|
||||
PartialSubs<X extends string>(
|
||||
text: Partial<T> & Record<X, string>,
|
||||
text: Partial<T> & Record<X, string>
|
||||
): TypedTranslation<Omit<T, X>> {
|
||||
const newTranslations: Record<string, string> = {}
|
||||
for (const lang in this.translations) {
|
||||
|
|
@ -415,7 +415,7 @@ export class TypedTranslation<T extends Record<string, any>> extends Translation
|
|||
|
||||
PartialSubsTr<K extends string>(
|
||||
key: string,
|
||||
replaceWith: Translation,
|
||||
replaceWith: Translation
|
||||
): TypedTranslation<Omit<T, K>> {
|
||||
const newTranslations: Record<string, string> = {}
|
||||
const toSearch = "{" + key + "}"
|
||||
|
|
@ -434,7 +434,7 @@ export class TypedTranslation<T extends Record<string, any>> extends Translation
|
|||
for (const missingLanguage of missingLanguages) {
|
||||
newTranslations[missingLanguage] = baseTemplate.replaceAll(
|
||||
toSearch,
|
||||
replaceWith.textFor(missingLanguage),
|
||||
replaceWith.textFor(missingLanguage)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue