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
|
@ -2,7 +2,6 @@ import { LayerConfigJson } from "../Json/LayerConfigJson"
|
||||||
import { Utils } from "../../../Utils"
|
import { Utils } from "../../../Utils"
|
||||||
import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson"
|
import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson"
|
||||||
import { ConversionContext } from "./ConversionContext"
|
import { ConversionContext } from "./ConversionContext"
|
||||||
import { T } from "vitest/dist/types-aac763a5"
|
|
||||||
|
|
||||||
export interface DesugaringContext {
|
export interface DesugaringContext {
|
||||||
tagRenderings: Map<string, QuestionableTagRenderingConfigJson>
|
tagRenderings: Map<string, QuestionableTagRenderingConfigJson>
|
||||||
|
@ -11,10 +10,11 @@ export interface DesugaringContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ConversionMsgLevel = "debug" | "information" | "warning" | "error"
|
export type ConversionMsgLevel = "debug" | "information" | "warning" | "error"
|
||||||
|
|
||||||
export interface ConversionMessage {
|
export interface ConversionMessage {
|
||||||
context: ConversionContext
|
readonly context: ConversionContext
|
||||||
message: string
|
readonly message: string
|
||||||
level: ConversionMsgLevel
|
readonly level: ConversionMsgLevel
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class Conversion<TIn, TOut> {
|
export abstract class Conversion<TIn, TOut> {
|
||||||
|
@ -85,6 +85,7 @@ export class Pure<TIn, TOut> extends Conversion<TIn, TOut> {
|
||||||
export class Bypass<T> extends DesugaringStep<T> {
|
export class Bypass<T> extends DesugaringStep<T> {
|
||||||
private readonly _applyIf: (t: T) => boolean
|
private readonly _applyIf: (t: T) => boolean
|
||||||
private readonly _step: DesugaringStep<T>
|
private readonly _step: DesugaringStep<T>
|
||||||
|
|
||||||
constructor(applyIf: (t: T) => boolean, step: DesugaringStep<T>) {
|
constructor(applyIf: (t: T) => boolean, step: DesugaringStep<T>) {
|
||||||
super("Applies the step on the object, if the object satisfies the predicate", [], "Bypass")
|
super("Applies the step on the object, if the object satisfies the predicate", [], "Bypass")
|
||||||
this._applyIf = applyIf
|
this._applyIf = applyIf
|
||||||
|
@ -102,7 +103,6 @@ export class Bypass<T> extends DesugaringStep<T> {
|
||||||
export class Each<X, Y> extends Conversion<X[], Y[]> {
|
export class Each<X, Y> extends Conversion<X[], Y[]> {
|
||||||
private readonly _step: Conversion<X, Y>
|
private readonly _step: Conversion<X, Y>
|
||||||
private readonly _msg: string
|
private readonly _msg: string
|
||||||
private readonly _filter: (x: X) => boolean
|
|
||||||
|
|
||||||
constructor(step: Conversion<X, Y>, options?: { msg?: string }) {
|
constructor(step: Conversion<X, Y>, options?: { msg?: string }) {
|
||||||
super(
|
super(
|
||||||
|
@ -224,6 +224,7 @@ export class FirstOf<T, X> extends Conversion<T, X> {
|
||||||
export class Cached<TIn, TOut> extends Conversion<TIn, TOut> {
|
export class Cached<TIn, TOut> extends Conversion<TIn, TOut> {
|
||||||
private _step: Conversion<TIn, TOut>
|
private _step: Conversion<TIn, TOut>
|
||||||
private readonly key: string
|
private readonly key: string
|
||||||
|
|
||||||
constructor(step: Conversion<TIn, TOut>) {
|
constructor(step: Conversion<TIn, TOut>) {
|
||||||
super("Secretly caches the output for the given input", [], "cached")
|
super("Secretly caches the output for the given input", [], "cached")
|
||||||
this._step = step
|
this._step = step
|
||||||
|
@ -242,9 +243,11 @@ export class Cached<TIn, TOut> extends Conversion<TIn, TOut> {
|
||||||
return converted
|
return converted
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Fuse<T> extends DesugaringStep<T> {
|
export class Fuse<T> extends DesugaringStep<T> {
|
||||||
private readonly steps: DesugaringStep<T>[]
|
|
||||||
protected debug = false
|
protected debug = false
|
||||||
|
private readonly steps: DesugaringStep<T>[]
|
||||||
|
|
||||||
constructor(doc: string, ...steps: DesugaringStep<T>[]) {
|
constructor(doc: string, ...steps: DesugaringStep<T>[]) {
|
||||||
super(
|
super(
|
||||||
(doc ?? "") +
|
(doc ?? "") +
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { ConversionMessage, ConversionMsgLevel } from "./Conversion"
|
import { ConversionMessage, ConversionMsgLevel } from "./Conversion"
|
||||||
|
import { Context } from "maplibre-gl"
|
||||||
|
|
||||||
export class ConversionContext {
|
export class ConversionContext {
|
||||||
/**
|
/**
|
||||||
|
@ -42,6 +43,31 @@ export class ConversionContext {
|
||||||
return new ConversionContext([], msg ? [msg] : [], ["test"])
|
return new ConversionContext([], msg ? [msg] : [], ["test"])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does an inline edit of the messages for which a new path is defined
|
||||||
|
* This is a slight hack
|
||||||
|
* @param rewritePath
|
||||||
|
*/
|
||||||
|
public rewriteMessages(
|
||||||
|
rewritePath: (
|
||||||
|
p: ReadonlyArray<number | string>
|
||||||
|
) => undefined | ReadonlyArray<number | string>
|
||||||
|
): void {
|
||||||
|
for (let i = 0; i < this.messages.length; i++) {
|
||||||
|
const m = this.messages[i]
|
||||||
|
const newPath = rewritePath(m.context.path)
|
||||||
|
if (!newPath) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const rewrittenContext = new ConversionContext(
|
||||||
|
this.messages,
|
||||||
|
newPath,
|
||||||
|
m.context.operation
|
||||||
|
)
|
||||||
|
this.messages[i] = <ConversionMessage>{ ...m, context: rewrittenContext }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static print(msg: ConversionMessage) {
|
static print(msg: ConversionMessage) {
|
||||||
const noString = msg.context.path.filter(
|
const noString = msg.context.path.filter(
|
||||||
(p) => typeof p !== "string" && typeof p !== "number"
|
(p) => typeof p !== "string" && typeof p !== "number"
|
||||||
|
|
|
@ -13,7 +13,10 @@ import { And } from "../../../Logic/Tags/And"
|
||||||
import Translations from "../../../UI/i18n/Translations"
|
import Translations from "../../../UI/i18n/Translations"
|
||||||
import FilterConfigJson from "../Json/FilterConfigJson"
|
import FilterConfigJson from "../Json/FilterConfigJson"
|
||||||
import DeleteConfig from "../DeleteConfig"
|
import DeleteConfig from "../DeleteConfig"
|
||||||
import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson"
|
import {
|
||||||
|
MappingConfigJson,
|
||||||
|
QuestionableTagRenderingConfigJson,
|
||||||
|
} from "../Json/QuestionableTagRenderingConfigJson"
|
||||||
import Validators from "../../../UI/InputElement/Validators"
|
import Validators from "../../../UI/InputElement/Validators"
|
||||||
import TagRenderingConfig from "../TagRenderingConfig"
|
import TagRenderingConfig from "../TagRenderingConfig"
|
||||||
import { parse as parse_html } from "node-html-parser"
|
import { parse as parse_html } from "node-html-parser"
|
||||||
|
@ -21,9 +24,7 @@ import PresetConfig from "../PresetConfig"
|
||||||
import { TagsFilter } from "../../../Logic/Tags/TagsFilter"
|
import { TagsFilter } from "../../../Logic/Tags/TagsFilter"
|
||||||
import { Translatable } from "../Json/Translatable"
|
import { Translatable } from "../Json/Translatable"
|
||||||
import { ConversionContext } from "./ConversionContext"
|
import { ConversionContext } from "./ConversionContext"
|
||||||
import * as eli from "../../../assets/editor-layer-index.json"
|
|
||||||
import { AvailableRasterLayers } from "../../RasterLayers"
|
import { AvailableRasterLayers } from "../../RasterLayers"
|
||||||
import Back from "../../../assets/svg/Back.svelte"
|
|
||||||
import PointRenderingConfigJson from "../Json/PointRenderingConfigJson"
|
import PointRenderingConfigJson from "../Json/PointRenderingConfigJson"
|
||||||
|
|
||||||
class ValidateLanguageCompleteness extends DesugaringStep<LayoutConfig> {
|
class ValidateLanguageCompleteness extends DesugaringStep<LayoutConfig> {
|
||||||
|
@ -831,6 +832,7 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> {
|
||||||
json: TagRenderingConfigJson | QuestionableTagRenderingConfigJson,
|
json: TagRenderingConfigJson | QuestionableTagRenderingConfigJson,
|
||||||
context: ConversionContext
|
context: ConversionContext
|
||||||
): TagRenderingConfigJson {
|
): TagRenderingConfigJson {
|
||||||
|
console.log(">>> Validating TR", context.path.join("."), json)
|
||||||
if (json["special"] !== undefined) {
|
if (json["special"] !== undefined) {
|
||||||
context.err(
|
context.err(
|
||||||
'Detected `special` on the top level. Did you mean `{"render":{ "special": ... }}`'
|
'Detected `special` on the top level. Did you mean `{"render":{ "special": ... }}`'
|
||||||
|
@ -848,13 +850,32 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> {
|
||||||
CheckTranslation.allowUndefined.convert(json[key], context.enter(key))
|
CheckTranslation.allowUndefined.convert(json[key], context.enter(key))
|
||||||
}
|
}
|
||||||
for (let i = 0; i < json.mappings?.length ?? 0; i++) {
|
for (let i = 0; i < json.mappings?.length ?? 0; i++) {
|
||||||
const mapping = json.mappings[i]
|
const mapping: MappingConfigJson = json.mappings[i]
|
||||||
CheckTranslation.noUndefined.convert(
|
CheckTranslation.noUndefined.convert(
|
||||||
mapping.then,
|
mapping.then,
|
||||||
context.enters("mappings", i, "then")
|
context.enters("mappings", i, "then")
|
||||||
)
|
)
|
||||||
if (!mapping.if) {
|
if (!mapping.if) {
|
||||||
context.enters("mappings", i).err("No `if` is defined")
|
console.log(
|
||||||
|
"Checking mappings",
|
||||||
|
i,
|
||||||
|
"if",
|
||||||
|
mapping.if,
|
||||||
|
context.path.join("."),
|
||||||
|
mapping.then
|
||||||
|
)
|
||||||
|
context.enters("mappings", i, "if").err("No `if` is defined")
|
||||||
|
}
|
||||||
|
if (mapping.addExtraTags) {
|
||||||
|
for (let j = 0; j < mapping.addExtraTags.length; j++) {
|
||||||
|
if (!mapping.addExtraTags[j]) {
|
||||||
|
context
|
||||||
|
.enters("mappings", i, "addExtraTags", j)
|
||||||
|
.err(
|
||||||
|
"Detected a 'null' or 'undefined' value. Either specify a tag or delete this item"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const en = mapping?.then?.["en"]
|
const en = mapping?.then?.["en"]
|
||||||
if (en && this.detectYesOrNo(en)) {
|
if (en && this.detectYesOrNo(en)) {
|
||||||
|
@ -977,6 +998,9 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (context.hasErrors()) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
return json
|
return json
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -996,6 +1020,7 @@ export class ValidateTagRenderings extends Fuse<TagRenderingConfigJson> {
|
||||||
constructor(layerConfig?: LayerConfigJson, doesImageExist?: DoesImageExist) {
|
constructor(layerConfig?: LayerConfigJson, doesImageExist?: DoesImageExist) {
|
||||||
super(
|
super(
|
||||||
"Various validation on tagRenderingConfigs",
|
"Various validation on tagRenderingConfigs",
|
||||||
|
new MiscTagRenderingChecks(),
|
||||||
new DetectShadowedMappings(layerConfig),
|
new DetectShadowedMappings(layerConfig),
|
||||||
new DetectConflictingAddExtraTags(),
|
new DetectConflictingAddExtraTags(),
|
||||||
// TODO enable new DetectNonErasedKeysInMappings(),
|
// TODO enable new DetectNonErasedKeysInMappings(),
|
||||||
|
@ -1003,8 +1028,7 @@ export class ValidateTagRenderings extends Fuse<TagRenderingConfigJson> {
|
||||||
new On("render", new ValidatePossibleLinks()),
|
new On("render", new ValidatePossibleLinks()),
|
||||||
new On("question", new ValidatePossibleLinks()),
|
new On("question", new ValidatePossibleLinks()),
|
||||||
new On("questionHint", new ValidatePossibleLinks()),
|
new On("questionHint", new ValidatePossibleLinks()),
|
||||||
new On("mappings", new Each(new On("then", new ValidatePossibleLinks()))),
|
new On("mappings", new Each(new On("then", new ValidatePossibleLinks())))
|
||||||
new MiscTagRenderingChecks()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1107,7 +1131,9 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
|
||||||
context.enter("pointRendering").err("There are no pointRenderings at all...")
|
context.enter("pointRendering").err("There are no pointRenderings at all...")
|
||||||
}
|
}
|
||||||
|
|
||||||
json.pointRendering?.forEach((pr,i) => this._validatePointRendering.convert(pr, context.enters("pointeRendering", i)))
|
json.pointRendering?.forEach((pr, i) =>
|
||||||
|
this._validatePointRendering.convert(pr, context.enters("pointeRendering", i))
|
||||||
|
)
|
||||||
|
|
||||||
if (json["mapRendering"]) {
|
if (json["mapRendering"]) {
|
||||||
context.enter("mapRendering").err("This layer has a legacy 'mapRendering'")
|
context.enter("mapRendering").err("This layer has a legacy 'mapRendering'")
|
||||||
|
@ -1134,7 +1160,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (json.tagRenderings !== undefined && json.tagRenderings.length > 0) {
|
if (json.tagRenderings !== undefined && json.tagRenderings.length > 0) {
|
||||||
new On("tagRendering", new Each(new ValidateTagRenderings(json)))
|
new On("tagRenderings", new Each(new ValidateTagRenderings(json)))
|
||||||
if (json.title === undefined && json.source !== "special:library") {
|
if (json.title === undefined && json.source !== "special:library") {
|
||||||
context
|
context
|
||||||
.enter("title")
|
.enter("title")
|
||||||
|
@ -1424,29 +1450,33 @@ class ValidatePointRendering extends DesugaringStep<PointRenderingConfigJson> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (json["markers"]) {
|
if (json["markers"]) {
|
||||||
context.enter("markers").err(`Detected a field 'markerS' in pointRendering. It is written as a singular case`)
|
context
|
||||||
}
|
.enter("markers")
|
||||||
if (json.marker && !Array.isArray(json.marker)) {
|
.err(
|
||||||
context.enter("marker").err(
|
`Detected a field 'markerS' in pointRendering. It is written as a singular case`
|
||||||
"The marker in a pointRendering should be an array"
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (json.marker && !Array.isArray(json.marker)) {
|
||||||
|
context.enter("marker").err("The marker in a pointRendering should be an array")
|
||||||
|
}
|
||||||
if (json.location.length == 0) {
|
if (json.location.length == 0) {
|
||||||
context.enter("location").err (
|
context
|
||||||
|
.enter("location")
|
||||||
|
.err(
|
||||||
"A pointRendering should have at least one 'location' to defined where it should be rendered. "
|
"A pointRendering should have at least one 'location' to defined where it should be rendered. "
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return json
|
return json
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ValidateLayer extends Conversion<
|
export class ValidateLayer extends Conversion<
|
||||||
LayerConfigJson,
|
LayerConfigJson,
|
||||||
{ parsed: LayerConfig; raw: LayerConfigJson }
|
{ parsed: LayerConfig; raw: LayerConfigJson }
|
||||||
> {
|
> {
|
||||||
private readonly _skipDefaultLayers: boolean
|
private readonly _skipDefaultLayers: boolean
|
||||||
private readonly _prevalidation: PrevalidateLayer
|
private readonly _prevalidation: PrevalidateLayer
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
path: string,
|
path: string,
|
||||||
isBuiltin: boolean,
|
isBuiltin: boolean,
|
||||||
|
|
|
@ -180,7 +180,7 @@
|
||||||
|
|
||||||
<div slot="title4" class="flex">
|
<div slot="title4" class="flex">
|
||||||
Advanced functionality
|
Advanced functionality
|
||||||
<ErrorIndicatorForRegion firstPaths={firstPathsFor("advanced", "expert")} {state} />
|
<ErrorIndicatorForRegion firstPaths={firstPathsFor("advanced")} {state} />
|
||||||
</div>
|
</div>
|
||||||
<div slot="content4">
|
<div slot="content4">
|
||||||
<Region configs={perRegion["advanced"]} {state} />
|
<Region configs={perRegion["advanced"]} {state} />
|
||||||
|
|
|
@ -22,6 +22,7 @@ import { LayoutConfigJson } from "../../Models/ThemeConfig/Json/LayoutConfigJson
|
||||||
import { PrepareTheme } from "../../Models/ThemeConfig/Conversion/PrepareTheme"
|
import { PrepareTheme } from "../../Models/ThemeConfig/Conversion/PrepareTheme"
|
||||||
import { ConversionContext } from "../../Models/ThemeConfig/Conversion/ConversionContext"
|
import { ConversionContext } from "../../Models/ThemeConfig/Conversion/ConversionContext"
|
||||||
import { LocalStorageSource } from "../../Logic/Web/LocalStorageSource"
|
import { LocalStorageSource } from "../../Logic/Web/LocalStorageSource"
|
||||||
|
import { TagRenderingConfigJson } from "../../Models/ThemeConfig/Json/TagRenderingConfigJson"
|
||||||
|
|
||||||
export interface HighlightedTagRendering {
|
export interface HighlightedTagRendering {
|
||||||
path: ReadonlyArray<string | number>
|
path: ReadonlyArray<string | number>
|
||||||
|
@ -66,7 +67,6 @@ export abstract class EditJsonState<T> {
|
||||||
this.messages = this.setupErrorsForLayers()
|
this.messages = this.setupErrorsForLayers()
|
||||||
|
|
||||||
const layerId = this.getId()
|
const layerId = this.getId()
|
||||||
this.highlightedItem.addCallbackD((hl) => console.log("Highlighted item is", hl))
|
|
||||||
this.configuration
|
this.configuration
|
||||||
.mapD((config) => {
|
.mapD((config) => {
|
||||||
if (!this.sendingUpdates) {
|
if (!this.sendingUpdates) {
|
||||||
|
@ -110,6 +110,7 @@ export abstract class EditJsonState<T> {
|
||||||
public async delete() {
|
public async delete() {
|
||||||
await this.server.delete(this.getId().data, this.category)
|
await this.server.delete(this.getId().data, this.category)
|
||||||
}
|
}
|
||||||
|
|
||||||
public getStoreFor<T>(path: ReadonlyArray<string | number>): UIEventSource<T | undefined> {
|
public getStoreFor<T>(path: ReadonlyArray<string | number>): UIEventSource<T | undefined> {
|
||||||
const key = path.join(".")
|
const key = path.join(".")
|
||||||
|
|
||||||
|
@ -172,7 +173,6 @@ export abstract class EditJsonState<T> {
|
||||||
|
|
||||||
public setValueAt(path: ReadonlyArray<string | number>, v: any) {
|
public setValueAt(path: ReadonlyArray<string | number>, v: any) {
|
||||||
let entry = this.configuration.data
|
let entry = this.configuration.data
|
||||||
console.trace("Setting value at", path,"to",v)
|
|
||||||
const isUndefined =
|
const isUndefined =
|
||||||
v === undefined ||
|
v === undefined ||
|
||||||
v === null ||
|
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> {
|
export default class EditLayerState extends EditJsonState<LayerConfigJson> {
|
||||||
// Needed for the special visualisations
|
// Needed for the special visualisations
|
||||||
public readonly osmConnection: OsmConnection
|
public readonly osmConnection: OsmConnection
|
||||||
|
@ -334,9 +390,10 @@ export default class EditLayerState extends EditJsonState<LayerConfigJson> {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected buildValidation(state: DesugaringContext) {
|
protected buildValidation(state: DesugaringContext) {
|
||||||
return new Pipe(
|
return new ContextRewritingStep(
|
||||||
new PrepareLayer(state),
|
state,
|
||||||
new ValidateLayer("dynamic", false, undefined, true)
|
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 EditLayerState from "./EditLayerState"
|
||||||
import { ExclamationIcon } from "@rgossiaux/svelte-heroicons/solid"
|
import { ExclamationIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||||
|
|
||||||
export let firstPaths: Set<string>
|
export let firstPaths: Set<string | number>
|
||||||
export let state: EditLayerState
|
export let state: EditLayerState
|
||||||
let messagesCount = state.messages.map(
|
let messagesCount = state.messages.map(
|
||||||
(msgs) =>
|
(msgs) =>
|
||||||
|
|
|
@ -11,9 +11,11 @@
|
||||||
import { Utils } from "../../Utils"
|
import { Utils } from "../../Utils"
|
||||||
import ToSvelte from "../Base/ToSvelte.svelte"
|
import ToSvelte from "../Base/ToSvelte.svelte"
|
||||||
import { VariableUiElement } from "../Base/VariableUIElement"
|
import { VariableUiElement } from "../Base/VariableUIElement"
|
||||||
|
import { ExclamationTriangle } from "@babeard/svelte-heroicons/solid/ExclamationTriangle"
|
||||||
|
|
||||||
export let state: EditLayerState
|
export let state: EditLayerState
|
||||||
export let path: (string | number)[]
|
export let path: (string | number)[]
|
||||||
|
let messages = state.messagesFor(path)
|
||||||
let tag: UIEventSource<TagConfigJson> = state.getStoreFor([...path, "if"])
|
let tag: UIEventSource<TagConfigJson> = state.getStoreFor([...path, "if"])
|
||||||
let parsedTag = tag.map((t) => (t ? TagUtils.Tag(t) : undefined))
|
let parsedTag = tag.map((t) => (t ? TagUtils.Tag(t) : undefined))
|
||||||
let exampleTags = parsedTag.map((pt) => {
|
let exampleTags = parsedTag.map((pt) => {
|
||||||
|
@ -27,7 +29,6 @@
|
||||||
}
|
}
|
||||||
return o
|
return o
|
||||||
})
|
})
|
||||||
let uploadableOnly: boolean = true
|
|
||||||
|
|
||||||
let thenText: UIEventSource<Record<string, string>> = state.getStoreFor([...path, "then"])
|
let thenText: UIEventSource<Record<string, string>> = state.getStoreFor([...path, "then"])
|
||||||
let thenTextEn = thenText.mapD((translation) =>
|
let thenTextEn = thenText.mapD((translation) =>
|
||||||
|
@ -71,5 +72,11 @@
|
||||||
<i>No then is set</i>
|
<i>No then is set</i>
|
||||||
{/if}
|
{/if}
|
||||||
<FromHtml src={$parsedTag?.asHumanString(false, false, $exampleTags)} />
|
<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>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { Utils } from "../../Utils"
|
||||||
import Constants from "../../Models/Constants"
|
import Constants from "../../Models/Constants"
|
||||||
import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"
|
import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"
|
||||||
import { Store } from "../../Logic/UIEventSource"
|
import { Store } from "../../Logic/UIEventSource"
|
||||||
|
import { LayoutConfigJson } from "../../Models/ThemeConfig/Json/LayoutConfigJson"
|
||||||
|
|
||||||
export default class StudioServer {
|
export default class StudioServer {
|
||||||
private readonly url: string
|
private readonly url: string
|
||||||
|
@ -47,11 +48,13 @@ export default class StudioServer {
|
||||||
return layerOverview
|
return layerOverview
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fetch(layerId: string, category: "layers", uid?: number): Promise<LayerConfigJson>
|
||||||
|
async fetch(layerId: string, category: "themes", uid?: number): Promise<LayoutConfigJson>
|
||||||
async fetch(
|
async fetch(
|
||||||
layerId: string,
|
layerId: string,
|
||||||
category: "layers" | "themes",
|
category: "layers" | "themes",
|
||||||
uid?: number
|
uid?: number
|
||||||
): Promise<LayerConfigJson> {
|
): Promise<LayerConfigJson | LayoutConfigJson> {
|
||||||
try {
|
try {
|
||||||
return await Utils.downloadJson(this.urlFor(layerId, category, uid))
|
return await Utils.downloadJson(this.urlFor(layerId, category, uid))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -24,13 +24,13 @@
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
export let state: EditLayerState
|
export let state: EditLayerState
|
||||||
export let schema: ConfigMeta
|
export let path: ReadonlyArray<string | number>
|
||||||
export let path: (string | number)[]
|
let messages = state.messagesFor(path)
|
||||||
let expertMode = state.expertMode
|
let expertMode = state.expertMode
|
||||||
const store = state.getStoreFor(path)
|
const store = state.getStoreFor(path)
|
||||||
let value = store.data
|
let value = store.data
|
||||||
let hasSeenIntro = UIEventSource.asBoolean(
|
let hasSeenIntro = UIEventSource.asBoolean(
|
||||||
LocalStorageSource.Get("studio-seen-tagrendering-tutorial", "false")
|
LocalStorageSource.Get("studio-seen-tagrendering-tutorial", "false"),
|
||||||
)
|
)
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (!hasSeenIntro.data) {
|
if (!hasSeenIntro.data) {
|
||||||
|
@ -43,7 +43,7 @@
|
||||||
* Should only be enabled for 'tagrenderings' in the theme, if the source is OSM
|
* Should only be enabled for 'tagrenderings' in the theme, if the source is OSM
|
||||||
*/
|
*/
|
||||||
let allowQuestions: Store<boolean> = state.configuration.mapD(
|
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[] = []
|
let mappingsBuiltin: MappingConfigJson[] = []
|
||||||
|
@ -119,7 +119,7 @@
|
||||||
|
|
||||||
const freeformSchemaAll = <ConfigMeta[]>(
|
const freeformSchemaAll = <ConfigMeta[]>(
|
||||||
questionableTagRenderingSchemaRaw.filter(
|
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
|
let freeformSchema = $expertMode
|
||||||
|
@ -128,7 +128,7 @@
|
||||||
const missing: string[] = questionableTagRenderingSchemaRaw
|
const missing: string[] = questionableTagRenderingSchemaRaw
|
||||||
.filter(
|
.filter(
|
||||||
(schema) =>
|
(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("."))
|
.map((schema) => schema.path.join("."))
|
||||||
console.log({ state })
|
console.log({ state })
|
||||||
|
@ -164,7 +164,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
{#each $mappings ?? [] as mapping, i (mapping)}
|
{#each $mappings ?? [] as mapping, i (mapping)}
|
||||||
<div class="interactive flex w-full">
|
<div class="interactive flex w-full">
|
||||||
<MappingInput {state} path={path.concat(["mappings", i])}>
|
<MappingInput {state} path={[...path, "mappings", i]}>
|
||||||
<button
|
<button
|
||||||
slot="delete"
|
slot="delete"
|
||||||
class="no-image-background rounded-full"
|
class="no-image-background rounded-full"
|
||||||
|
@ -178,6 +178,7 @@
|
||||||
</button>
|
</button>
|
||||||
</MappingInput>
|
</MappingInput>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
|
|
@ -35,19 +35,18 @@
|
||||||
? "http://127.0.0.1:1235"
|
? "http://127.0.0.1:1235"
|
||||||
: "https://studio.mapcomplete.org"
|
: "https://studio.mapcomplete.org"
|
||||||
|
|
||||||
let osmConnection = new OsmConnection(
|
const oauth_token = QueryParameters.GetQueryParameter(
|
||||||
new OsmConnection({
|
|
||||||
oauth_token: QueryParameters.GetQueryParameter(
|
|
||||||
"oauth_token",
|
"oauth_token",
|
||||||
undefined,
|
undefined,
|
||||||
"Used to complete the login"
|
"Used to complete the login",
|
||||||
),
|
|
||||||
})
|
|
||||||
)
|
)
|
||||||
|
let osmConnection = new OsmConnection({
|
||||||
|
oauth_token,
|
||||||
|
})
|
||||||
const expertMode = UIEventSource.asBoolean(
|
const expertMode = UIEventSource.asBoolean(
|
||||||
osmConnection.GetPreference("studio-expert-mode", "false", {
|
osmConnection.GetPreference("studio-expert-mode", "false", {
|
||||||
documentation: "Indicates if more options are shown in mapcomplete studio",
|
documentation: "Indicates if more options are shown in mapcomplete studio",
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
expertMode.addCallbackAndRunD((expert) => console.log("Expert mode is", expert))
|
expertMode.addCallbackAndRunD((expert) => console.log("Expert mode is", expert))
|
||||||
const createdBy = osmConnection.userDetails.data.name
|
const createdBy = osmConnection.userDetails.data.name
|
||||||
|
@ -55,23 +54,23 @@
|
||||||
const studio = new StudioServer(studioUrl, uid)
|
const studio = new StudioServer(studioUrl, uid)
|
||||||
|
|
||||||
let layersWithErr = UIEventSource.FromPromiseWithErr(studio.fetchOverview())
|
let layersWithErr = UIEventSource.FromPromiseWithErr(studio.fetchOverview())
|
||||||
let layers: Store<{ owner: number }[]> = layersWithErr.mapD((l) =>
|
let layers: Store<{ owner: number, id: string }[]> = layersWithErr.mapD((l) =>
|
||||||
l.success?.filter((l) => l.category === "layers")
|
l["success"]?.filter((l) => l.category === "layers"),
|
||||||
)
|
)
|
||||||
let selfLayers = layers.mapD((ls) => ls.filter((l) => l.owner === uid.data), [uid])
|
let selfLayers = layers.mapD((ls) => ls.filter((l) => l.owner === uid.data), [uid])
|
||||||
let otherLayers = layers.mapD(
|
let otherLayers = layers.mapD(
|
||||||
(ls) => ls.filter((l) => l.owner !== undefined && l.owner !== uid.data),
|
(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 officialLayers = layers.mapD((ls) => ls.filter((l) => l.owner === undefined), [uid])
|
||||||
|
|
||||||
let themes: Store<{ owner: number }[]> = layersWithErr.mapD((l) =>
|
let themes: Store<{ owner: number, id: string }[]> = layersWithErr.mapD((l) =>
|
||||||
l.success?.filter((l) => l.category === "themes")
|
l["success"]?.filter((l) => l.category === "themes"),
|
||||||
)
|
)
|
||||||
let selfThemes = themes.mapD((ls) => ls.filter((l) => l.owner === uid.data), [uid])
|
let selfThemes = themes.mapD((ls) => ls.filter((l) => l.owner === uid.data), [uid])
|
||||||
let otherThemes = themes.mapD(
|
let otherThemes = themes.mapD(
|
||||||
(ls) => ls.filter((l) => l.owner !== undefined && l.owner !== uid.data),
|
(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])
|
let officialThemes = themes.mapD((ls) => ls.filter((l) => l.owner === undefined), [uid])
|
||||||
|
|
||||||
|
@ -90,8 +89,6 @@
|
||||||
const layoutSchema: ConfigMeta[] = <any>layoutSchemaRaw
|
const layoutSchema: ConfigMeta[] = <any>layoutSchemaRaw
|
||||||
let editThemeState = new EditThemeState(layoutSchema, studio, { expertMode })
|
let editThemeState = new EditThemeState(layoutSchema, studio, { expertMode })
|
||||||
|
|
||||||
let layerId = editLayerState.configuration.map((layerConfig) => layerConfig.id)
|
|
||||||
|
|
||||||
const version = meta.version
|
const version = meta.version
|
||||||
|
|
||||||
async function editLayer(event: Event) {
|
async function editLayer(event: Event) {
|
||||||
|
@ -107,7 +104,8 @@
|
||||||
const id: { id: string; owner: number } = event["detail"]
|
const id: { id: string; owner: number } = event["detail"]
|
||||||
state = "loading"
|
state = "loading"
|
||||||
editThemeState.startSavingUpdates(false)
|
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()
|
editThemeState.startSavingUpdates()
|
||||||
state = "editing_theme"
|
state = "editing_theme"
|
||||||
}
|
}
|
||||||
|
@ -142,7 +140,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<If condition={layersWithErr.map((d) => d?.error !== undefined)}>
|
<If condition={layersWithErr.map((d) => d?.["error"] !== undefined) }>
|
||||||
<div>
|
<div>
|
||||||
<div class="alert">
|
<div class="alert">
|
||||||
Something went wrong while contacting the MapComplete Studio Server: {$layersWithErr["error"]}
|
Something went wrong while contacting the MapComplete Studio Server: {$layersWithErr["error"]}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue