forked from MapComplete/MapComplete
Studio: improve error handling, fix renumbering
This commit is contained in:
parent
8888c57ac6
commit
cfa58b6387
10 changed files with 187 additions and 62 deletions
|
|
@ -180,7 +180,7 @@
|
|||
|
||||
<div slot="title4" class="flex">
|
||||
Advanced functionality
|
||||
<ErrorIndicatorForRegion firstPaths={firstPathsFor("advanced", "expert")} {state} />
|
||||
<ErrorIndicatorForRegion firstPaths={firstPathsFor("advanced")} {state} />
|
||||
</div>
|
||||
<div slot="content4">
|
||||
<Region configs={perRegion["advanced"]} {state} />
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import { LayoutConfigJson } from "../../Models/ThemeConfig/Json/LayoutConfigJson
|
|||
import { PrepareTheme } from "../../Models/ThemeConfig/Conversion/PrepareTheme"
|
||||
import { ConversionContext } from "../../Models/ThemeConfig/Conversion/ConversionContext"
|
||||
import { LocalStorageSource } from "../../Logic/Web/LocalStorageSource"
|
||||
import { TagRenderingConfigJson } from "../../Models/ThemeConfig/Json/TagRenderingConfigJson"
|
||||
|
||||
export interface HighlightedTagRendering {
|
||||
path: ReadonlyArray<string | number>
|
||||
|
|
@ -66,7 +67,6 @@ export abstract class EditJsonState<T> {
|
|||
this.messages = this.setupErrorsForLayers()
|
||||
|
||||
const layerId = this.getId()
|
||||
this.highlightedItem.addCallbackD((hl) => console.log("Highlighted item is", hl))
|
||||
this.configuration
|
||||
.mapD((config) => {
|
||||
if (!this.sendingUpdates) {
|
||||
|
|
@ -110,6 +110,7 @@ export abstract class EditJsonState<T> {
|
|||
public async delete() {
|
||||
await this.server.delete(this.getId().data, this.category)
|
||||
}
|
||||
|
||||
public getStoreFor<T>(path: ReadonlyArray<string | number>): UIEventSource<T | undefined> {
|
||||
const key = path.join(".")
|
||||
|
||||
|
|
@ -172,7 +173,6 @@ export abstract class EditJsonState<T> {
|
|||
|
||||
public setValueAt(path: ReadonlyArray<string | number>, v: any) {
|
||||
let entry = this.configuration.data
|
||||
console.trace("Setting value at", path,"to",v)
|
||||
const isUndefined =
|
||||
v === undefined ||
|
||||
v === null ||
|
||||
|
|
@ -249,6 +249,62 @@ export abstract class EditJsonState<T> {
|
|||
}
|
||||
}
|
||||
|
||||
class ContextRewritingStep<T> extends Conversion<LayerConfigJson, T> {
|
||||
private readonly _step: Conversion<LayerConfigJson, T>
|
||||
private readonly _state: DesugaringContext
|
||||
private readonly _getTagRenderings: (t: T) => TagRenderingConfigJson[]
|
||||
|
||||
constructor(
|
||||
state: DesugaringContext,
|
||||
step: Conversion<LayerConfigJson, T>,
|
||||
getTagRenderings: (t: T) => TagRenderingConfigJson[]
|
||||
) {
|
||||
super(
|
||||
"When validating a layer, the tagRenderings are first expanded. Some builtin tagRendering-calls (e.g. `contact`) will introduce _multiple_ tagRenderings, causing the count to be off. This class rewrites the error messages to fix this",
|
||||
[],
|
||||
"ContextRewritingStep"
|
||||
)
|
||||
this._state = state
|
||||
this._step = step
|
||||
this._getTagRenderings = getTagRenderings
|
||||
}
|
||||
|
||||
convert(json: LayerConfigJson, context: ConversionContext): T {
|
||||
const converted = this._step.convert(json, context)
|
||||
const originalIds = json.tagRenderings?.map(
|
||||
(tr) => (<QuestionableTagRenderingConfigJson>tr)["id"]
|
||||
)
|
||||
if (!originalIds) {
|
||||
return converted
|
||||
}
|
||||
|
||||
let newTagRenderings: TagRenderingConfigJson[]
|
||||
if (converted === undefined) {
|
||||
const prepared = new PrepareLayer(this._state)
|
||||
newTagRenderings = <TagRenderingConfigJson[]>(
|
||||
prepared.convert(json, context).tagRenderings
|
||||
)
|
||||
} else {
|
||||
newTagRenderings = this._getTagRenderings(converted)
|
||||
}
|
||||
context.rewriteMessages((path) => {
|
||||
if (path[0] !== "tagRenderings") {
|
||||
return undefined
|
||||
}
|
||||
const newPath = [...path]
|
||||
const idToSearch = newTagRenderings[newPath[1]].id
|
||||
const oldIndex = originalIds.indexOf(idToSearch)
|
||||
if (oldIndex < 0) {
|
||||
console.warn("Original ID was not found: ", idToSearch)
|
||||
return undefined // We don't modify the message
|
||||
}
|
||||
newPath[1] = oldIndex
|
||||
return newPath
|
||||
})
|
||||
return converted
|
||||
}
|
||||
}
|
||||
|
||||
export default class EditLayerState extends EditJsonState<LayerConfigJson> {
|
||||
// Needed for the special visualisations
|
||||
public readonly osmConnection: OsmConnection
|
||||
|
|
@ -334,9 +390,10 @@ export default class EditLayerState extends EditJsonState<LayerConfigJson> {
|
|||
}
|
||||
|
||||
protected buildValidation(state: DesugaringContext) {
|
||||
return new Pipe(
|
||||
new PrepareLayer(state),
|
||||
new ValidateLayer("dynamic", false, undefined, true)
|
||||
return new ContextRewritingStep(
|
||||
state,
|
||||
new Pipe(new PrepareLayer(state), new ValidateLayer("dynamic", false, undefined, true)),
|
||||
(t) => <TagRenderingConfigJson[]>t.raw.tagRenderings
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import EditLayerState from "./EditLayerState"
|
||||
import { ExclamationIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
|
||||
export let firstPaths: Set<string>
|
||||
export let firstPaths: Set<string | number>
|
||||
export let state: EditLayerState
|
||||
let messagesCount = state.messages.map(
|
||||
(msgs) =>
|
||||
|
|
|
|||
|
|
@ -11,9 +11,11 @@
|
|||
import { Utils } from "../../Utils"
|
||||
import ToSvelte from "../Base/ToSvelte.svelte"
|
||||
import { VariableUiElement } from "../Base/VariableUIElement"
|
||||
import { ExclamationTriangle } from "@babeard/svelte-heroicons/solid/ExclamationTriangle"
|
||||
|
||||
export let state: EditLayerState
|
||||
export let path: (string | number)[]
|
||||
let messages = state.messagesFor(path)
|
||||
let tag: UIEventSource<TagConfigJson> = state.getStoreFor([...path, "if"])
|
||||
let parsedTag = tag.map((t) => (t ? TagUtils.Tag(t) : undefined))
|
||||
let exampleTags = parsedTag.map((pt) => {
|
||||
|
|
@ -27,7 +29,6 @@
|
|||
}
|
||||
return o
|
||||
})
|
||||
let uploadableOnly: boolean = true
|
||||
|
||||
let thenText: UIEventSource<Record<string, string>> = state.getStoreFor([...path, "then"])
|
||||
let thenTextEn = thenText.mapD((translation) =>
|
||||
|
|
@ -71,5 +72,11 @@
|
|||
<i>No then is set</i>
|
||||
{/if}
|
||||
<FromHtml src={$parsedTag?.asHumanString(false, false, $exampleTags)} />
|
||||
{#if $messages.length > 0}
|
||||
<div class="alert m-2 flex">
|
||||
<ExclamationTriangle class="w-6 h-6"/>
|
||||
{$messages.length} errors
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { Utils } from "../../Utils"
|
|||
import Constants from "../../Models/Constants"
|
||||
import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"
|
||||
import { Store } from "../../Logic/UIEventSource"
|
||||
import { LayoutConfigJson } from "../../Models/ThemeConfig/Json/LayoutConfigJson"
|
||||
|
||||
export default class StudioServer {
|
||||
private readonly url: string
|
||||
|
|
@ -47,11 +48,13 @@ export default class StudioServer {
|
|||
return layerOverview
|
||||
}
|
||||
|
||||
async fetch(layerId: string, category: "layers", uid?: number): Promise<LayerConfigJson>
|
||||
async fetch(layerId: string, category: "themes", uid?: number): Promise<LayoutConfigJson>
|
||||
async fetch(
|
||||
layerId: string,
|
||||
category: "layers" | "themes",
|
||||
uid?: number
|
||||
): Promise<LayerConfigJson> {
|
||||
): Promise<LayerConfigJson | LayoutConfigJson> {
|
||||
try {
|
||||
return await Utils.downloadJson(this.urlFor(layerId, category, uid))
|
||||
} catch (e) {
|
||||
|
|
|
|||
|
|
@ -24,13 +24,13 @@
|
|||
import { onMount } from "svelte"
|
||||
|
||||
export let state: EditLayerState
|
||||
export let schema: ConfigMeta
|
||||
export let path: (string | number)[]
|
||||
export let path: ReadonlyArray<string | number>
|
||||
let messages = state.messagesFor(path)
|
||||
let expertMode = state.expertMode
|
||||
const store = state.getStoreFor(path)
|
||||
let value = store.data
|
||||
let hasSeenIntro = UIEventSource.asBoolean(
|
||||
LocalStorageSource.Get("studio-seen-tagrendering-tutorial", "false")
|
||||
LocalStorageSource.Get("studio-seen-tagrendering-tutorial", "false"),
|
||||
)
|
||||
onMount(() => {
|
||||
if (!hasSeenIntro.data) {
|
||||
|
|
@ -43,7 +43,7 @@
|
|||
* Should only be enabled for 'tagrenderings' in the theme, if the source is OSM
|
||||
*/
|
||||
let allowQuestions: Store<boolean> = state.configuration.mapD(
|
||||
(config) => path.at(0) === "tagRenderings" && config.source?.geoJson === undefined
|
||||
(config) => path.at(0) === "tagRenderings" && config.source?.["geoJson"] === undefined,
|
||||
)
|
||||
|
||||
let mappingsBuiltin: MappingConfigJson[] = []
|
||||
|
|
@ -119,7 +119,7 @@
|
|||
|
||||
const freeformSchemaAll = <ConfigMeta[]>(
|
||||
questionableTagRenderingSchemaRaw.filter(
|
||||
(schema) => schema.path.length == 2 && schema.path[0] === "freeform" && $allowQuestions
|
||||
(schema) => schema.path.length == 2 && schema.path[0] === "freeform" && $allowQuestions,
|
||||
)
|
||||
)
|
||||
let freeformSchema = $expertMode
|
||||
|
|
@ -128,7 +128,7 @@
|
|||
const missing: string[] = questionableTagRenderingSchemaRaw
|
||||
.filter(
|
||||
(schema) =>
|
||||
schema.path.length >= 1 && !items.has(schema.path[0]) && !ignored.has(schema.path[0])
|
||||
schema.path.length >= 1 && !items.has(schema.path[0]) && !ignored.has(schema.path[0]),
|
||||
)
|
||||
.map((schema) => schema.path.join("."))
|
||||
console.log({ state })
|
||||
|
|
@ -164,7 +164,7 @@
|
|||
{/if}
|
||||
{#each $mappings ?? [] as mapping, i (mapping)}
|
||||
<div class="interactive flex w-full">
|
||||
<MappingInput {state} path={path.concat(["mappings", i])}>
|
||||
<MappingInput {state} path={[...path, "mappings", i]}>
|
||||
<button
|
||||
slot="delete"
|
||||
class="no-image-background rounded-full"
|
||||
|
|
@ -178,6 +178,7 @@
|
|||
</button>
|
||||
</MappingInput>
|
||||
</div>
|
||||
|
||||
{/each}
|
||||
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -35,19 +35,18 @@
|
|||
? "http://127.0.0.1:1235"
|
||||
: "https://studio.mapcomplete.org"
|
||||
|
||||
let osmConnection = new OsmConnection(
|
||||
new OsmConnection({
|
||||
oauth_token: QueryParameters.GetQueryParameter(
|
||||
"oauth_token",
|
||||
undefined,
|
||||
"Used to complete the login"
|
||||
),
|
||||
})
|
||||
const oauth_token = QueryParameters.GetQueryParameter(
|
||||
"oauth_token",
|
||||
undefined,
|
||||
"Used to complete the login",
|
||||
)
|
||||
let osmConnection = new OsmConnection({
|
||||
oauth_token,
|
||||
})
|
||||
const expertMode = UIEventSource.asBoolean(
|
||||
osmConnection.GetPreference("studio-expert-mode", "false", {
|
||||
documentation: "Indicates if more options are shown in mapcomplete studio",
|
||||
})
|
||||
}),
|
||||
)
|
||||
expertMode.addCallbackAndRunD((expert) => console.log("Expert mode is", expert))
|
||||
const createdBy = osmConnection.userDetails.data.name
|
||||
|
|
@ -55,23 +54,23 @@
|
|||
const studio = new StudioServer(studioUrl, uid)
|
||||
|
||||
let layersWithErr = UIEventSource.FromPromiseWithErr(studio.fetchOverview())
|
||||
let layers: Store<{ owner: number }[]> = layersWithErr.mapD((l) =>
|
||||
l.success?.filter((l) => l.category === "layers")
|
||||
let layers: Store<{ owner: number, id: string }[]> = layersWithErr.mapD((l) =>
|
||||
l["success"]?.filter((l) => l.category === "layers"),
|
||||
)
|
||||
let selfLayers = layers.mapD((ls) => ls.filter((l) => l.owner === uid.data), [uid])
|
||||
let otherLayers = layers.mapD(
|
||||
(ls) => ls.filter((l) => l.owner !== undefined && l.owner !== uid.data),
|
||||
[uid]
|
||||
[uid],
|
||||
)
|
||||
let officialLayers = layers.mapD((ls) => ls.filter((l) => l.owner === undefined), [uid])
|
||||
|
||||
let themes: Store<{ owner: number }[]> = layersWithErr.mapD((l) =>
|
||||
l.success?.filter((l) => l.category === "themes")
|
||||
let themes: Store<{ owner: number, id: string }[]> = layersWithErr.mapD((l) =>
|
||||
l["success"]?.filter((l) => l.category === "themes"),
|
||||
)
|
||||
let selfThemes = themes.mapD((ls) => ls.filter((l) => l.owner === uid.data), [uid])
|
||||
let otherThemes = themes.mapD(
|
||||
(ls) => ls.filter((l) => l.owner !== undefined && l.owner !== uid.data),
|
||||
[uid]
|
||||
[uid],
|
||||
)
|
||||
let officialThemes = themes.mapD((ls) => ls.filter((l) => l.owner === undefined), [uid])
|
||||
|
||||
|
|
@ -90,8 +89,6 @@
|
|||
const layoutSchema: ConfigMeta[] = <any>layoutSchemaRaw
|
||||
let editThemeState = new EditThemeState(layoutSchema, studio, { expertMode })
|
||||
|
||||
let layerId = editLayerState.configuration.map((layerConfig) => layerConfig.id)
|
||||
|
||||
const version = meta.version
|
||||
|
||||
async function editLayer(event: Event) {
|
||||
|
|
@ -107,7 +104,8 @@
|
|||
const id: { id: string; owner: number } = event["detail"]
|
||||
state = "loading"
|
||||
editThemeState.startSavingUpdates(false)
|
||||
editThemeState.configuration.setData(await studio.fetch(id.id, "themes", id.owner))
|
||||
const layout = await studio.fetch(id.id, "themes", id.owner)
|
||||
editThemeState.configuration.setData(layout)
|
||||
editThemeState.startSavingUpdates()
|
||||
state = "editing_theme"
|
||||
}
|
||||
|
|
@ -142,7 +140,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<If condition={layersWithErr.map((d) => d?.error !== undefined)}>
|
||||
<If condition={layersWithErr.map((d) => d?.["error"] !== undefined) }>
|
||||
<div>
|
||||
<div class="alert">
|
||||
Something went wrong while contacting the MapComplete Studio Server: {$layersWithErr["error"]}
|
||||
|
|
@ -152,8 +150,8 @@
|
|||
<li>Try again in a few minutes</li>
|
||||
<li>
|
||||
Contact <a href="https://app.element.io/#/room/#MapComplete:matrix.org">
|
||||
the MapComplete community via the chat.
|
||||
</a>
|
||||
the MapComplete community via the chat.
|
||||
</a>
|
||||
Someone might be able to help you
|
||||
</li>
|
||||
<li>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue