forked from MapComplete/MapComplete
Merge develop
This commit is contained in:
commit
fec3608ca4
57 changed files with 1160 additions and 594 deletions
|
|
@ -8,6 +8,8 @@
|
|||
import { Utils } from "../../Utils"
|
||||
import type { ValidatorType } from "../InputElement/Validators"
|
||||
import InputHelper from "../InputElement/InputHelper.svelte"
|
||||
import { Translation } from "../i18n/Translation"
|
||||
import Tr from "../Base/Tr.svelte"
|
||||
|
||||
export let filteredLayer: FilteredLayer
|
||||
export let option: FilterConfigOption
|
||||
|
|
@ -36,7 +38,7 @@
|
|||
appliedFilter?.setData(FilteredLayer.fieldsToString(properties))
|
||||
}
|
||||
|
||||
let firstValue : UIEventSource<string>
|
||||
let firstValue: UIEventSource<string>
|
||||
for (const field of option.fields) {
|
||||
// A bit of cheating: the 'parts' will have '}' suffixed for fields
|
||||
const src = new UIEventSource<string>(initialState[field.name] ?? "")
|
||||
|
|
@ -47,9 +49,10 @@
|
|||
onDestroy(
|
||||
src.stabilized(200).addCallback(() => {
|
||||
setFields()
|
||||
}),
|
||||
})
|
||||
)
|
||||
}
|
||||
let feedback: UIEventSource<Translation> = new UIEventSource<Translation>(undefined)
|
||||
</script>
|
||||
|
||||
<div class="low-interaction p-1 rounded-2xl px-3" class:interactive={$firstValue?.length > 0}>
|
||||
|
|
@ -58,11 +61,15 @@
|
|||
<!-- This is a field! -->
|
||||
<span class="mx-1">
|
||||
<InputHelper value={fieldValues[part["subs"]]} type={fieldTypes[part["subs"]]}>
|
||||
<ValidatedInput slot="fallback" value={fieldValues[part["subs"]]} type={fieldTypes[part["subs"]]} />
|
||||
<ValidatedInput slot="fallback" value={fieldValues[part["subs"]]} type={fieldTypes[part["subs"]]}
|
||||
{feedback} />
|
||||
</InputHelper>
|
||||
</span>
|
||||
{:else}
|
||||
{@html part["message"]}
|
||||
{/if}
|
||||
{/each}
|
||||
{#if $feedback}
|
||||
<Tr cls="alert" t={$feedback}/>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -90,13 +90,13 @@
|
|||
<LoginButton osmConnection={state.osmConnection} slot="not-logged-in" />
|
||||
<div class="flex items-center gap-x-4">
|
||||
{#if $userdetails.img}
|
||||
<img src={$userdetails.img} class="h-14 w-14 rounded-full" />
|
||||
<img alt="avatar" src={$userdetails.img} class="h-14 w-14 rounded-full" />
|
||||
{/if}
|
||||
<b>{$userdetails.name}</b>
|
||||
</div>
|
||||
</LoginToggle>
|
||||
|
||||
<Page {onlyLink} shown={pg.usersettings} bodyPadding="p-0">
|
||||
<Page {onlyLink} shown={pg.usersettings} bodyPadding="p-0 pb-4">
|
||||
<svelte:fragment slot="header">
|
||||
<CogIcon />
|
||||
<Tr t={UserRelatedState.usersettingsConfig.title.GetRenderValue({})} />
|
||||
|
|
|
|||
|
|
@ -220,8 +220,8 @@
|
|||
<td/>
|
||||
{/if}
|
||||
{#each range(7) as wd}
|
||||
<OHCell type="half" {h} {wd} on:start={() => startSelection(wd, h)} on:end={() => endSelection(wd, h)}
|
||||
on:move={() => moved(wd, h)} on:clear={() => clearSelection()} />
|
||||
<OHCell type="half" {h} {wd} on:start={() => startSelection(wd, h + 0.5)} on:end={() => endSelection(wd, h + 0.5)}
|
||||
on:move={() => moved(wd, h + 0.5)} on:clear={() => clearSelection()} />
|
||||
{/each}
|
||||
</tr>
|
||||
|
||||
|
|
|
|||
|
|
@ -15,9 +15,6 @@ import UrlValidator from "./Validators/UrlValidator"
|
|||
import PhoneValidator from "./Validators/PhoneValidator"
|
||||
import OpeningHoursValidator from "./Validators/OpeningHoursValidator"
|
||||
import ColorValidator from "./Validators/ColorValidator"
|
||||
import BaseUIElement from "../BaseUIElement"
|
||||
import Combine from "../Base/Combine"
|
||||
import Title from "../Base/Title"
|
||||
import SimpleTagValidator from "./Validators/SimpleTagValidator"
|
||||
import ImageUrlValidator from "./Validators/ImageUrlValidator"
|
||||
import TagKeyValidator from "./Validators/TagKeyValidator"
|
||||
|
|
@ -30,6 +27,7 @@ import SlopeValidator from "./Validators/SlopeValidator"
|
|||
import VeloparkValidator from "./Validators/VeloparkValidator"
|
||||
import NameSuggestionIndexValidator from "./Validators/NameSuggestionIndexValidator"
|
||||
import CurrencyValidator from "./Validators/CurrencyValidator"
|
||||
import RegexValidator from "./Validators/RegexValidator"
|
||||
|
||||
export type ValidatorType = (typeof Validators.availableTypes)[number]
|
||||
|
||||
|
|
@ -64,6 +62,7 @@ export default class Validators {
|
|||
"velopark",
|
||||
"nsi",
|
||||
"currency",
|
||||
"regex"
|
||||
] as const
|
||||
|
||||
public static readonly AllValidators: ReadonlyArray<Validator> = [
|
||||
|
|
@ -95,6 +94,7 @@ export default class Validators {
|
|||
new VeloparkValidator(),
|
||||
new NameSuggestionIndexValidator(),
|
||||
new CurrencyValidator(),
|
||||
new RegexValidator()
|
||||
]
|
||||
|
||||
private static _byType = Validators._byTypeConstructor()
|
||||
|
|
|
|||
22
src/UI/InputElement/Validators/RegexValidator.ts
Normal file
22
src/UI/InputElement/Validators/RegexValidator.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import StringValidator from "./StringValidator"
|
||||
import { s } from "vitest/dist/env-afee91f0"
|
||||
import { Translation } from "../../i18n/Translation"
|
||||
import Translations from "../../i18n/Translations"
|
||||
|
||||
export default class RegexValidator extends StringValidator{
|
||||
constructor() {
|
||||
super("regex", "Validates a regex")
|
||||
}
|
||||
|
||||
getFeedback(s: string): Translation | undefined {
|
||||
try{
|
||||
new RegExp(s)
|
||||
}catch (e) {
|
||||
return Translations.T("Not a valid Regex: "+e)
|
||||
}
|
||||
}
|
||||
|
||||
isValid(s: string): boolean {
|
||||
return this.getFeedback(s) === undefined
|
||||
}
|
||||
}
|
||||
|
|
@ -9,8 +9,8 @@
|
|||
import BackButton from "../Base/BackButton.svelte"
|
||||
import NextButton from "../Base/NextButton.svelte"
|
||||
import WikipediaPanel from "../Wikipedia/WikipediaPanel.svelte"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import Plantnet_logo from "../../assets/svg/Plantnet_logo.svelte"
|
||||
import ArrowPath from "@babeard/svelte-heroicons/mini/ArrowPath"
|
||||
|
||||
/**
|
||||
* The main entry point for the plantnet wizard
|
||||
|
|
@ -23,7 +23,6 @@
|
|||
*/
|
||||
export let imageUrls: Store<string[]>
|
||||
export let onConfirm: (wikidataId: string) => void
|
||||
const dispatch = createEventDispatcher<{ selected: string }>()
|
||||
let collapsedMode = true
|
||||
let options: UIEventSource<PlantNetSpeciesMatch[]> = new UIEventSource<PlantNetSpeciesMatch[]>(
|
||||
undefined
|
||||
|
|
@ -38,18 +37,20 @@
|
|||
|
||||
let done = false
|
||||
|
||||
function speciesSelected(species: PlantNetSpeciesMatch) {
|
||||
function speciesSelected(species: string) {
|
||||
console.log("Selected:", species)
|
||||
selectedOption = species
|
||||
}
|
||||
|
||||
async function detectSpecies() {
|
||||
error = undefined
|
||||
collapsedMode = false
|
||||
|
||||
try {
|
||||
const result = await PlantNet.query(imageUrls.data.slice(0, 5))
|
||||
options.set(result.results.filter((r) => r.score > 0.005).slice(0, 8))
|
||||
} catch (e) {
|
||||
console.error("Caught", e)
|
||||
error = e
|
||||
}
|
||||
}
|
||||
|
|
@ -60,8 +61,12 @@
|
|||
<button class="w-full" on:click={detectSpecies}>
|
||||
<Tr t={t.button} />
|
||||
</button>
|
||||
{:else if $error !== undefined}
|
||||
{:else if error !== undefined}
|
||||
<Tr cls="alert" t={t.error.Subs({ error })} />
|
||||
<button on:click={() => detectSpecies()}>
|
||||
<ArrowPath class="w-6 h-6"/>
|
||||
<Tr t={Translations.t.general.retry}/>
|
||||
</button>
|
||||
{:else if $imageUrls.length === 0}
|
||||
<!-- No urls are available, show the explanation instead-->
|
||||
<div class=" border-region relative mb-1 p-2">
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import Searchbar from "../Base/Searchbar.svelte"
|
||||
import Translations from "../i18n/Translations"
|
||||
import { Utils } from "../../Utils"
|
||||
|
||||
export let tags: UIEventSource<Record<string, any>>
|
||||
export let tagKeys = tags.map((tgs) => (tgs === undefined ? [] : Object.keys(tgs)))
|
||||
|
|
@ -34,10 +35,19 @@
|
|||
const metaKeys: string[] = [].concat(...SimpleMetaTaggers.metatags.map((k) => k.keys))
|
||||
let allCalculatedTags = new Set<string>([...calculatedTags, ...metaKeys])
|
||||
let search = new UIEventSource<string>("")
|
||||
|
||||
function downloadAsJson(){
|
||||
Utils.offerContentsAsDownloadableFile(
|
||||
JSON.stringify(tags.data, null, " "), "tags-"+(tags.data.id ?? layer?.id ?? "")+".json"
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<section>
|
||||
<Searchbar value={search} placeholder={Translations.T("Search a key")}></Searchbar>
|
||||
<button class="as-link" on:click={() => downloadAsJson()}>
|
||||
Download as JSON
|
||||
</button>
|
||||
<table class="zebra-table break-all">
|
||||
<tr>
|
||||
<th>Key</th>
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@
|
|||
.getSchemaStartingWith(schema.path)
|
||||
.filter((part) => part.path.length - 1 === schema.path.length)
|
||||
|
||||
let usesOverride = value["builtin"] !== undefined
|
||||
let usesOverride = value?.["builtin"] !== undefined
|
||||
|
||||
function schemaForMultitype() {
|
||||
const sch = { ...schema }
|
||||
|
|
@ -138,7 +138,7 @@
|
|||
</div>
|
||||
{:else if typeof value === "string"}
|
||||
Builtin: <b>{value}</b>
|
||||
{:else if value["builtin"]}
|
||||
{:else if value?.["builtin"]}
|
||||
reused tagrendering <span class="font-bold">{JSON.stringify(value["builtin"])}</span>
|
||||
{:else}
|
||||
<Tr cls="font-bold" t={Translations.T(value?.question ?? value?.render)} />
|
||||
|
|
|
|||
|
|
@ -159,6 +159,9 @@ export abstract class EditJsonState<T> {
|
|||
}
|
||||
|
||||
public getSchemaStartingWith(path: string[]) {
|
||||
if(path === undefined){
|
||||
return undefined
|
||||
}
|
||||
return this.schema.filter(
|
||||
(sch) =>
|
||||
!path.some((part, i) => !(sch.path.length > path.length && sch.path[i] === part))
|
||||
|
|
|
|||
|
|
@ -5,18 +5,23 @@
|
|||
import { TrashIcon } from "@babeard/svelte-heroicons/mini"
|
||||
import ShowConversionMessage from "./ShowConversionMessage.svelte"
|
||||
import Markdown from "../Base/Markdown.svelte"
|
||||
import type { QuestionableTagRenderingConfigJson } from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
|
||||
import type {
|
||||
QuestionableTagRenderingConfigJson
|
||||
} from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
|
||||
import CollapsedTagRenderingPreview from "./CollapsedTagRenderingPreview.svelte"
|
||||
import { Accordion } from "flowbite-svelte"
|
||||
import { Utils } from "../../Utils"
|
||||
|
||||
export let state: EditJsonState<any>
|
||||
export let path: (string | number)[] = []
|
||||
let schema: ConfigMeta = state.getSchema(path)[0]
|
||||
console.log("SBA got schema", schema, "for path", path)
|
||||
|
||||
let title = schema.path.at(-1)
|
||||
|
||||
let title = schema?.path?.at(-1)
|
||||
let singular = title
|
||||
if (title?.endsWith("s")) {
|
||||
singular = title.slice(0, title.length - 1)
|
||||
singular = title?.slice(0, title.length - 1)
|
||||
}
|
||||
let article = "a"
|
||||
if (singular?.match(/^[aeoui]/)) {
|
||||
|
|
@ -25,18 +30,20 @@
|
|||
|
||||
const isTagRenderingBlock = path.length === 1 && path[0] === "tagRenderings"
|
||||
|
||||
if (isTagRenderingBlock) {
|
||||
if (isTagRenderingBlock && schema !== undefined) {
|
||||
schema = { ...schema }
|
||||
schema.description = undefined
|
||||
}
|
||||
|
||||
const subparts: ConfigMeta[] = state
|
||||
.getSchemaStartingWith(schema.path)
|
||||
.filter((part) => part.path.length - 1 === schema.path.length)
|
||||
.getSchemaStartingWith(schema?.path)
|
||||
?.filter((part) => part.path.length - 1 === schema?.path?.length)
|
||||
let messages = state.messagesFor(path)
|
||||
|
||||
let datapath = path
|
||||
const currentValue = state.getStoreFor<(string | QuestionableTagRenderingConfigJson)[]>(datapath)
|
||||
currentValue.set(Utils.DedupT(currentValue.data))
|
||||
console.log("Current value is", currentValue.data)
|
||||
if (currentValue.data === undefined) {
|
||||
currentValue.setData([])
|
||||
}
|
||||
|
|
@ -62,68 +69,69 @@
|
|||
currentValue.ping()
|
||||
}
|
||||
</script>
|
||||
{#if schema !== undefined}
|
||||
<div class="pl-2">
|
||||
<h3>{schema.path.at(-1)}</h3>
|
||||
|
||||
<div class="pl-2">
|
||||
<h3>{schema.path.at(-1)}</h3>
|
||||
|
||||
{#if subparts.length > 0}
|
||||
<Markdown src={schema.description} />
|
||||
{/if}
|
||||
{#if $currentValue === undefined}
|
||||
No array defined
|
||||
{:else if !Array.isArray($currentValue)}
|
||||
Not an array: {typeof $currentValue}
|
||||
{JSON.stringify(path)}
|
||||
{JSON.stringify($currentValue).slice(0, 120)}
|
||||
{:else if $currentValue?.length === 0}
|
||||
No values are defined
|
||||
{#if $messages.length > 0}
|
||||
{#each $messages as message}
|
||||
<ShowConversionMessage {message} />
|
||||
{/each}
|
||||
{#if subparts.length > 0}
|
||||
<Markdown src={schema.description} />
|
||||
{/if}
|
||||
{:else if subparts.length === 0}
|
||||
<!-- We need an array of values, so we use the typehint of the _parent_ element as field -->
|
||||
{#each $currentValue as value, i}
|
||||
<div class="flex w-full">
|
||||
<SchemaBasedField {state} {schema} path={fusePath(i)} />
|
||||
<button
|
||||
class="h-fit w-fit rounded-full border border-black p-1"
|
||||
on:click={() => {
|
||||
{#if $currentValue === undefined}
|
||||
No array defined
|
||||
{:else if !Array.isArray($currentValue)}
|
||||
Not an array: {typeof $currentValue}
|
||||
{JSON.stringify(path)}
|
||||
{JSON.stringify($currentValue).slice(0, 120)}
|
||||
{:else if $currentValue?.length === 0}
|
||||
No values are defined
|
||||
{#if $messages.length > 0}
|
||||
{#each $messages as message}
|
||||
<ShowConversionMessage {message} />
|
||||
{/each}
|
||||
{/if}
|
||||
{:else if subparts.length === 0}
|
||||
<!-- We need an array of values, so we use the typehint of the _parent_ element as field -->
|
||||
{#each $currentValue as value, i}
|
||||
<div class="flex w-full">
|
||||
<SchemaBasedField {state} {schema} path={fusePath(i)} />
|
||||
<button
|
||||
class="h-fit w-fit rounded-full border border-black p-1"
|
||||
on:click={() => {
|
||||
del(i)
|
||||
}}
|
||||
>
|
||||
<TrashIcon class="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
{:else}
|
||||
<Accordion>
|
||||
{#each $currentValue as value, i (value)}
|
||||
<CollapsedTagRenderingPreview
|
||||
{state}
|
||||
{isTagRenderingBlock}
|
||||
{schema}
|
||||
{currentValue}
|
||||
{value}
|
||||
{i}
|
||||
{singular}
|
||||
path={fusePath(i)}
|
||||
/>
|
||||
>
|
||||
<TrashIcon class="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
</Accordion>
|
||||
{/if}
|
||||
<div class="flex">
|
||||
<button on:click={() => createItem()}>Add {article} {singular}</button>
|
||||
{#if path.length === 1 && path[0] === "tagRenderings"}
|
||||
<button
|
||||
on:click={() => {
|
||||
{:else}
|
||||
<Accordion>
|
||||
{#each $currentValue as value, i}
|
||||
<CollapsedTagRenderingPreview
|
||||
{state}
|
||||
{isTagRenderingBlock}
|
||||
{schema}
|
||||
{currentValue}
|
||||
{value}
|
||||
{i}
|
||||
{singular}
|
||||
path={fusePath(i)}
|
||||
/>
|
||||
{/each}
|
||||
</Accordion>
|
||||
{/if}
|
||||
<div class="flex">
|
||||
<button on:click={() => createItem()}>Add {article} {singular}</button>
|
||||
{#if path.length === 1 && path[0] === "tagRenderings"}
|
||||
<button
|
||||
on:click={() => {
|
||||
createItem("images")
|
||||
}}
|
||||
>
|
||||
Add a builtin tagRendering
|
||||
</button>
|
||||
{/if}
|
||||
<slot name="extra-button" />
|
||||
>
|
||||
Add a builtin tagRendering
|
||||
</button>
|
||||
{/if}
|
||||
<slot name="extra-button" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@
|
|||
import ChevronRight from "@babeard/svelte-heroicons/mini/ChevronRight"
|
||||
import ChevronLeft from "@babeard/svelte-heroicons/solid/ChevronLeft"
|
||||
import { Drawer } from "flowbite-svelte"
|
||||
import { linear, sineIn } from "svelte/easing"
|
||||
import { linear } from "svelte/easing"
|
||||
|
||||
export let state: ThemeViewState
|
||||
|
||||
|
|
@ -440,7 +440,6 @@
|
|||
<MenuDrawer onlyLink={true} {state} />
|
||||
</div>
|
||||
</DrawerLeft>
|
||||
<MenuDrawer onlyLink={false} {state} />
|
||||
|
||||
{#if $selectedElement !== undefined && $selectedLayer !== undefined && !$selectedLayer.popupInFloatover}
|
||||
<!-- right modal with the selected element view -->
|
||||
|
|
@ -494,4 +493,6 @@
|
|||
{/if}
|
||||
{/if}
|
||||
|
||||
<MenuDrawer onlyLink={false} {state} />
|
||||
|
||||
</main>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue