Studio: move some validation from throwing errors in LayerConfig.ts into errors in Validation
This commit is contained in:
parent
632dd6dfb1
commit
ec79672fa6
10 changed files with 160 additions and 79 deletions
|
@ -194,9 +194,9 @@ export class Each<X, Y> extends Conversion<X[], Y[]> {
|
|||
}
|
||||
const step = this._step
|
||||
const result: Y[] = []
|
||||
|
||||
const c = context.inOperation("each")
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
const context_ = context.enter(i).inOperation("each")
|
||||
const context_ = c.enter(i - 1)
|
||||
const r = step.convert(values[i], context_)
|
||||
result.push(r)
|
||||
}
|
||||
|
|
|
@ -289,7 +289,11 @@ class ExpandTagRendering extends Conversion<
|
|||
lookup = this.lookup(tr)
|
||||
}
|
||||
if (lookup === undefined) {
|
||||
if (this._state.sharedLayers?.size > 0) {
|
||||
if (
|
||||
this._state.sharedLayers?.size > 0 &&
|
||||
ctx.path.at(-1) !== "icon" &&
|
||||
!ctx.path.find((p) => p === "pointRendering")
|
||||
) {
|
||||
ctx.warn(
|
||||
`A literal rendering was detected: ${tr}
|
||||
Did you perhaps forgot to add a layer name as 'layername.${tr}'? ` +
|
||||
|
|
|
@ -665,6 +665,76 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> {
|
|||
context.err('Groups are deprecated, use `"label": ["' + json["group"] + '"]` instead')
|
||||
}
|
||||
|
||||
if (json.freeform) {
|
||||
const c = context.enters("freeform", "render")
|
||||
if (json.render === undefined) {
|
||||
c.err(
|
||||
"This tagRendering allows to set a freeform, but does not define a way to `render` this value"
|
||||
)
|
||||
} else {
|
||||
const render = new Translation(<any>json.render)
|
||||
|
||||
for (const ln in render.translations) {
|
||||
if (ln.startsWith("_")) {
|
||||
continue
|
||||
}
|
||||
const txt: string = render.translations[ln]
|
||||
if (txt === "") {
|
||||
c.err(" Rendering for language " + ln + " is empty")
|
||||
}
|
||||
if (
|
||||
txt.indexOf("{" + json.freeform.key + "}") >= 0 ||
|
||||
txt.indexOf("&LBRACE" + json.freeform.key + "&RBRACE")
|
||||
) {
|
||||
continue
|
||||
}
|
||||
if (txt.indexOf("{" + json.freeform.key + ":") >= 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (
|
||||
json.freeform["type"] === "opening_hours" &&
|
||||
txt.indexOf("{opening_hours_table(") >= 0
|
||||
) {
|
||||
continue
|
||||
}
|
||||
const keyFirstArg = ["canonical", "fediverse_link", "translated"]
|
||||
if (
|
||||
keyFirstArg.some(
|
||||
(funcName) => txt.indexOf(`{${funcName}(${json.freeform.key}`) >= 0
|
||||
)
|
||||
) {
|
||||
continue
|
||||
}
|
||||
if (
|
||||
json.freeform["type"] === "wikidata" &&
|
||||
txt.indexOf("{wikipedia(" + json.freeform.key) >= 0
|
||||
) {
|
||||
continue
|
||||
}
|
||||
if (json.freeform.key === "wikidata" && txt.indexOf("{wikipedia()") >= 0) {
|
||||
continue
|
||||
}
|
||||
if (
|
||||
json.freeform["type"] === "wikidata" &&
|
||||
txt.indexOf(`{wikidata_label(${json.freeform.key})`) >= 0
|
||||
) {
|
||||
continue
|
||||
}
|
||||
c.err(
|
||||
`The rendering for language ${ln} does not contain the freeform key {${json.freeform.key}}. This is a bug, as this rendering should show exactly this freeform key!\nThe rendering is ${txt} `
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (json.render && json["question"] && json.freeform === undefined) {
|
||||
context.err(
|
||||
`Detected a tagrendering which takes input without freeform key in ${context}; the question is ${new Translation(
|
||||
json["question"]
|
||||
).textFor("en")}`
|
||||
)
|
||||
}
|
||||
|
||||
const freeformType = json["freeform"]?.["type"]
|
||||
if (freeformType) {
|
||||
if (Validators.availableTypes.indexOf(freeformType) < 0) {
|
||||
|
@ -806,14 +876,20 @@ export class ValidateLayer extends Conversion<
|
|||
try {
|
||||
layerConfig = new LayerConfig(json, "validation", true)
|
||||
} catch (e) {
|
||||
context.err(e)
|
||||
console.error(e)
|
||||
context.err("Could not parse layer due to:" + e)
|
||||
return undefined
|
||||
}
|
||||
for (const [_, code, __] of layerConfig.calculatedTags ?? []) {
|
||||
for (let i = 0; i < (layerConfig.calculatedTags ?? []).length; i++) {
|
||||
const [_, code, __] = layerConfig.calculatedTags[i]
|
||||
try {
|
||||
new Function("feat", "return " + code + ";")
|
||||
} catch (e) {
|
||||
throw `Invalid function definition: the custom javascript is invalid:${e} (at ${context}). The offending javascript code is:\n ${code}`
|
||||
context
|
||||
.enters("calculatedTags", i)
|
||||
.err(
|
||||
`Invalid function definition: the custom javascript is invalid:${e}. The offending javascript code is:\n ${code}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -828,6 +904,7 @@ export class ValidateLayer extends Conversion<
|
|||
}
|
||||
|
||||
if (json.tagRenderings !== undefined && json.tagRenderings.length > 0) {
|
||||
new On("tagRendering", new Each(new ValidateTagRenderings(json)))
|
||||
if (json.title === undefined && json.source !== "special:library") {
|
||||
context.err(
|
||||
"This layer does not have a title defined but it does have tagRenderings. Not having a title will disable the popups, resulting in an unclickable element. Please add a title. If not having a popup is intended and the tagrenderings need to be kept (e.g. in a library layer), set `title: null` to disable this error."
|
||||
|
|
|
@ -244,65 +244,6 @@ export default class TagRenderingConfig {
|
|||
throw `${context}: A question is defined, but no mappings nor freeform (key) are. The question is ${this.question.txt} at ${context}`
|
||||
}
|
||||
|
||||
if (this.freeform) {
|
||||
if (this.render === undefined) {
|
||||
throw `${context}: Detected a freeform key without rendering... Key: ${this.freeform.key} in ${context}`
|
||||
}
|
||||
for (const ln in this.render.translations) {
|
||||
if (ln.startsWith("_")) {
|
||||
continue
|
||||
}
|
||||
const txt: string = this.render.translations[ln]
|
||||
if (txt === "") {
|
||||
throw context + " Rendering for language " + ln + " is empty"
|
||||
}
|
||||
if (
|
||||
txt.indexOf("{" + this.freeform.key + "}") >= 0 ||
|
||||
txt.indexOf("&LBRACE" + this.freeform.key + "&RBRACE")
|
||||
) {
|
||||
continue
|
||||
}
|
||||
if (txt.indexOf("{" + this.freeform.key + ":") >= 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (
|
||||
this.freeform.type === "opening_hours" &&
|
||||
txt.indexOf("{opening_hours_table(") >= 0
|
||||
) {
|
||||
continue
|
||||
}
|
||||
const keyFirstArg = ["canonical", "fediverse_link", "translated"]
|
||||
if (
|
||||
keyFirstArg.some(
|
||||
(funcName) => txt.indexOf(`{${funcName}(${this.freeform.key}`) >= 0
|
||||
)
|
||||
) {
|
||||
continue
|
||||
}
|
||||
if (
|
||||
this.freeform.type === "wikidata" &&
|
||||
txt.indexOf("{wikipedia(" + this.freeform.key) >= 0
|
||||
) {
|
||||
continue
|
||||
}
|
||||
if (this.freeform.key === "wikidata" && txt.indexOf("{wikipedia()") >= 0) {
|
||||
continue
|
||||
}
|
||||
if (
|
||||
this.freeform.type === "wikidata" &&
|
||||
txt.indexOf(`{wikidata_label(${this.freeform.key})`) >= 0
|
||||
) {
|
||||
continue
|
||||
}
|
||||
throw `${context}: The rendering for language ${ln} does not contain the freeform key {${this.freeform.key}}. This is a bug, as this rendering should show exactly this freeform key!\nThe rendering is ${txt} `
|
||||
}
|
||||
}
|
||||
|
||||
if (this.render && this.question && this.freeform === undefined) {
|
||||
throw `${context}: Detected a tagrendering which takes input without freeform key in ${context}; the question is ${this.question.txt}`
|
||||
}
|
||||
|
||||
if (!json.multiAnswer && this.mappings !== undefined && this.question !== undefined) {
|
||||
let keys = []
|
||||
for (let i = 0; i < this.mappings.length; i++) {
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
export let condition3: Store<boolean> = tr
|
||||
export let condition4: Store<boolean> = tr
|
||||
|
||||
export let tab: UIEventSource<number>;
|
||||
export let tab: UIEventSource<number> = new UIEventSource<number>(0);
|
||||
let tabElements: HTMLElement[] = [];
|
||||
$: tabElements[$tab]?.click();
|
||||
$: {
|
||||
|
|
|
@ -4,11 +4,12 @@
|
|||
import layerSchemaRaw from "../../assets/schemas/layerconfigmeta.json";
|
||||
import Region from "./Region.svelte";
|
||||
import TabbedGroup from "../Base/TabbedGroup.svelte";
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource";
|
||||
import { Store } from "../../Logic/UIEventSource";
|
||||
import type { ConfigMeta } from "./configMeta";
|
||||
import { Utils } from "../../Utils";
|
||||
import type { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson";
|
||||
import type { ConversionMessage } from "../../Models/ThemeConfig/Conversion/Conversion";
|
||||
import ErrorIndicatorForRegion from "./ErrorIndicatorForRegion.svelte";
|
||||
|
||||
const layerSchema: ConfigMeta[] = <any>layerSchemaRaw;
|
||||
|
||||
|
@ -37,10 +38,22 @@
|
|||
console.error("BaseLayerRegions in editLayer: no items have group '" + baselayerRegion + "\"");
|
||||
}
|
||||
}
|
||||
const leftoverRegions: string[] = allNames.filter(r => regionBlacklist.indexOf(r) < 0 && baselayerRegions.indexOf(r) < 0);
|
||||
const title: Store<string> = state.getStoreFor(["id"]);
|
||||
const wl = window.location
|
||||
const baseUrl = wl.protocol+"//"+wl.host+"/theme.html?userlayout="
|
||||
const wl = window.location;
|
||||
const baseUrl = wl.protocol + "//" + wl.host + "/theme.html?userlayout=";
|
||||
|
||||
function firstPathsFor(...regionNames: string[]): Set<string> {
|
||||
const pathNames = new Set<string>();
|
||||
for (const regionName of regionNames) {
|
||||
const region: ConfigMeta[] = perRegion[regionName]
|
||||
for (const configMeta of region) {
|
||||
pathNames.add(configMeta.path[0])
|
||||
}
|
||||
}
|
||||
return pathNames;
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="w-full flex justify-between">
|
||||
|
@ -48,31 +61,37 @@
|
|||
{#if $hasErrors > 0}
|
||||
<div class="alert">{$hasErrors} errors detected</div>
|
||||
{:else}
|
||||
<a class="primary button" href={baseUrl+state.server.layerUrl(title.data)} target="_blank" rel="noopener">Try it out</a>
|
||||
<a class="primary button" href={baseUrl+state.server.layerUrl(title.data)} target="_blank" rel="noopener">Try it
|
||||
out</a>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="m4">
|
||||
<TabbedGroup tab={new UIEventSource(2)}>
|
||||
<div slot="title0">General properties</div>
|
||||
<TabbedGroup>
|
||||
<div slot="title0" class="flex">General properties
|
||||
<ErrorIndicatorForRegion firstPaths={firstPathsFor(...baselayerRegions)} {state} />
|
||||
</div>
|
||||
<div class="flex flex-col" slot="content0">
|
||||
{#each baselayerRegions as region}
|
||||
<Region {state} configs={perRegion[region]} title={region} />
|
||||
{/each}
|
||||
</div>
|
||||
<div slot="title1">Information panel (questions and answers)</div>
|
||||
<div slot="title1" class="flex">Information panel (questions and answers)
|
||||
<ErrorIndicatorForRegion firstPaths={firstPathsFor("title","tagrenderings","editing")} {state} /></div>
|
||||
<div slot="content1">
|
||||
<Region configs={perRegion["title"]} {state} title="Popup title" />
|
||||
<Region configs={perRegion["tagrenderings"]} {state} title="Popup contents" />
|
||||
<Region configs={perRegion["editing"]} {state} title="Other editing elements" />
|
||||
</div>
|
||||
|
||||
<div slot="title2">Rendering on the map</div>
|
||||
<div slot="title2" class="flex">Rendering on the map
|
||||
<ErrorIndicatorForRegion firstPaths={firstPathsFor("linerendering","pointrendering")} {state} /></div>
|
||||
<div slot="content2">
|
||||
<Region configs={perRegion["linerendering"]} {state} />
|
||||
<Region configs={perRegion["pointrendering"]} {state} />
|
||||
</div>
|
||||
|
||||
<div slot="title3">Advanced functionality</div>
|
||||
<div slot="title3" class="flex">Advanced functionality
|
||||
<ErrorIndicatorForRegion firstPaths={firstPathsFor("advanced","expert")} {state} /></div>
|
||||
<div slot="content3">
|
||||
<Region configs={perRegion["advanced"]} {state} />
|
||||
<Region configs={perRegion["expert"]} {state} />
|
||||
|
@ -91,6 +110,9 @@
|
|||
{message.level}
|
||||
<span class="literal-code">{message.context.path.join(".")}</span>
|
||||
{message.message}
|
||||
<span class="literal-code">
|
||||
{message.context.operation.join(".")}
|
||||
</span>
|
||||
</li>
|
||||
{/each}
|
||||
</div>
|
||||
|
|
|
@ -72,7 +72,6 @@ export default class EditLayerState {
|
|||
}
|
||||
}
|
||||
this.messages = this.configuration.mapD((config) => {
|
||||
const context = ConversionContext.construct([], ["prepare"])
|
||||
const trs = Utils.NoNull(config.tagRenderings ?? [])
|
||||
for (let i = 0; i < trs.length; i++) {
|
||||
const tr = trs[i]
|
||||
|
@ -98,6 +97,8 @@ export default class EditLayerState {
|
|||
new PrepareLayer(state),
|
||||
new ValidateLayer("dynamic", false, undefined)
|
||||
)
|
||||
|
||||
const context = ConversionContext.construct([], ["prepare"])
|
||||
prepare.convert(<LayerConfigJson>config, context)
|
||||
return context.messages
|
||||
})
|
||||
|
|
19
src/UI/Studio/ErrorIndicatorForRegion.svelte
Normal file
19
src/UI/Studio/ErrorIndicatorForRegion.svelte
Normal file
|
@ -0,0 +1,19 @@
|
|||
<script lang="ts">
|
||||
import EditLayerState from "./EditLayerState";
|
||||
import { ExclamationIcon } from "@rgossiaux/svelte-heroicons/solid";
|
||||
|
||||
export let firstPaths: Set<string>;
|
||||
export let state: EditLayerState;
|
||||
let messagesCount = state.messages.map(msgs => msgs.filter(msg => {
|
||||
const pth = msg.context.path
|
||||
return firstPaths.has(pth[0]) || (pth.length > 1 && firstPaths.has(pth[1]));
|
||||
}).length);
|
||||
|
||||
</script>
|
||||
|
||||
{#if $messagesCount > 0}
|
||||
<span class="alert flex w-min">
|
||||
<ExclamationIcon class="w-6 h-6" />
|
||||
{$messagesCount}
|
||||
</span>
|
||||
{/if}
|
|
@ -89,6 +89,15 @@
|
|||
}
|
||||
let config: TagRenderingConfig
|
||||
let err: string = undefined
|
||||
let messages = state.messages.mapD(msgs => msgs.filter(msg => {
|
||||
const pth = msg.context.path
|
||||
for (let i = 0; i < Math.min(pth.length, path.length); i++) {
|
||||
if(pth[i] !== path[i]){
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}))
|
||||
try {
|
||||
config = new TagRenderingConfig(configJson, "config based on " + schema.path.join("."))
|
||||
} catch (e) {
|
||||
|
@ -133,11 +142,15 @@
|
|||
console.error("Could not register", path,"due to",e)
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if err !== undefined}
|
||||
<span class="alert">{err}</span>
|
||||
{:else}
|
||||
<div class="w-full flex flex-col">
|
||||
<TagRenderingEditable {config} selectedElement={undefined} showQuestionIfUnknown={true} {state} {tags}/>
|
||||
{#if $messages.length > 0}
|
||||
{#each $messages as msg}
|
||||
<div class="alert">{msg.message}</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue