Show validation errors

This commit is contained in:
Misode
2020-06-16 15:37:01 +02:00
parent 2d29de7903
commit 8cbb5fc776
6 changed files with 119 additions and 6 deletions

17
src/app/ErrorsView.ts Normal file
View File

@@ -0,0 +1,17 @@
import { AbstractView, Path, locale } from "minecraft-schemas";
export class ErrorsView extends AbstractView {
render(): void {
this.target.style.display = this.model.errors.count() > 0 ? 'flex' : 'none'
const errors = this.model.errors.get(new Path())
this.target.children[0].innerHTML = errors.map(err =>
`<div class="error">
<svg class="error-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="18" height="18"><path fill-rule="evenodd" d="M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM0 8a8 8 0 1116 0A8 8 0 010 8zm9 3a1 1 0 11-2 0 1 1 0 012 0zm-.25-6.25a.75.75 0 00-1.5 0v3.5a.75.75 0 001.5 0v-3.5z"></path></svg>
<span class="error-path">${err.path.toString()}</span>
<span>-</span>
<span class="error-message">${locale(err.error, err.params)}</span>
</div>`
).join('')
}
}

View File

@@ -16,6 +16,7 @@ import {
import Split from 'split.js'
import { SandboxSchema } from './Sandbox'
import { ErrorsView } from './ErrorsView'
const LOCAL_STORAGE_THEME = 'theme'
@@ -51,6 +52,12 @@ const registries = [
'structure_feature'
]
const treeViewObserver = (el: HTMLElement) => {
el.querySelectorAll('.node-header[data-error]').forEach(e => {
e.insertAdjacentHTML('beforeend', `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM0 8a8 8 0 1116 0A8 8 0 010 8zm9 3a1 1 0 11-2 0 1 1 0 012 0zm-.25-6.25a.75.75 0 00-1.5 0v3.5a.75.75 0 001.5 0v-3.5z"></path></svg>`)
})
}
const publicPath = process.env.NODE_ENV === 'production' ? '/dev/' : '/';
Promise.all([
fetch(publicPath + 'locales/schema/en.json').then(r => r.json()),
@@ -60,13 +67,15 @@ Promise.all([
LOCALES.register('en', {...responses[0], ...responses[1]})
const selectedModel = document.getElementById('selected-model')!
const modelSelector = (document.getElementById('model-selector') as HTMLInputElement)
const modelSelector = document.getElementById('model-selector')!
const modelSelectorMenu = document.getElementById('model-selector-menu')!
const languageSelector = document.getElementById('language-selector')!
const languageSelectorMenu = document.getElementById('language-selector-menu')!
const themeSelector = document.getElementById('theme-selector')!
const treeViewEl = document.getElementById('tree-view')!
const sourceViewEl = document.getElementById('source-view')!
const errorsViewEl = document.getElementById('errors-view')!
const errorsToggle = document.getElementById('errors-toggle')!
const sourceViewOutput = (document.getElementById('source-view-output') as HTMLTextAreaElement)
const sourceControlsToggle = document.getElementById('source-controls-toggle')!
const sourceControlsMenu = document.getElementById('source-controls-menu')!
@@ -86,8 +95,14 @@ Promise.all([
}
const views: { [key: string]: IView } = {
'tree': new TreeView(models[selected], treeViewEl),
'source': new SourceView(models[selected], sourceViewOutput, {indentation: 2})
'tree': new TreeView(models[selected], treeViewEl, {
showErrors: true,
observer: treeViewObserver
}),
'source': new SourceView(models[selected], sourceViewOutput, {
indentation: 2
}),
'errors': new ErrorsView(models[selected], errorsViewEl)
}
const updateModel = (newModel: string) => {
@@ -208,5 +223,15 @@ Promise.all([
downloadAnchor.click()
})
errorsToggle.addEventListener('click', evt => {
if (errorsViewEl.classList.contains('hidden')) {
errorsViewEl.classList.remove('hidden')
errorsToggle.classList.remove('toggled')
} else {
errorsViewEl.classList.add('hidden')
errorsToggle.classList.add('toggled')
}
})
document.body.style.visibility = 'initial'
})

View File

@@ -11,7 +11,7 @@
<div class="container">
<div class="header">
<div class="header-title">
<h2 id="selected-model">Loot Table Generator</h2>
<h2 id="selected-model"></h2>
<div class="nav-selector">
<svg id="model-selector" class="btn model-selector" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="32" height="32"><path fill-rule="evenodd" d="M12.78 6.22a.75.75 0 010 1.06l-4.25 4.25a.75.75 0 01-1.06 0L3.22 7.28a.75.75 0 011.06-1.06L8 9.94l3.72-3.72a.75.75 0 011.06 0z"></path></svg>
<div class="nav-selector-menu btn-group" id="model-selector-menu"></div>
@@ -61,6 +61,13 @@
<textarea id="source-view-output" spellcheck="false" autocorrect="off" autocapitalize="off"></textarea>
</div>
</div>
<div id="errors-view" class="errors" style="display: none;">
<div class="error-list"></div>
<div id="errors-toggle" class="toggle">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="24" height="24"><path fill-rule="evenodd" d="M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM0 8a8 8 0 1116 0A8 8 0 010 8zm9 3a1 1 0 11-2 0 1 1 0 012 0zm-.25-6.25a.75.75 0 00-1.5 0v3.5a.75.75 0 001.5 0v-3.5z"></path></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="32" height="32"><path fill-rule="evenodd" d="M12.78 6.22a.75.75 0 010 1.06l-4.25 4.25a.75.75 0 01-1.06 0L3.22 7.28a.75.75 0 011.06-1.06L8 9.94l3.72-3.72a.75.75 0 011.06 0z"></path></svg>
</div>
</div>
</div>
</body>
</html>

View File

@@ -111,6 +111,7 @@ body {
-o-tab-size: 4;
-webkit-tab-size: 4;
outline: none;
resize: none;
}
.source-controls {
@@ -168,6 +169,41 @@ body {
border-top-left-radius: 0px;
}
.errors {
position: absolute;
bottom: 0;
right: 17px;
margin: 5px;
border-radius: 3px;
}
.errors.hidden .error-list {
display: none;
}
.error {
display: flex;
align-items: center;
padding: 7px;
}
.error span {
padding-left: 11px;
}
.errors .toggle {
padding: 2px;
width: 36px;
height: 36px;
align-self: flex-end;
cursor: pointer;
user-select: none;
}
.errors .toggle.toggled {
padding: 6px;
}
@media screen and (max-width: 580px) {
body {
overflow-y: hidden;
@@ -293,6 +329,12 @@ body {
background-color: #5d5f5fa6;
}
.errors {
background-color: #f13000c5;
color: #000000cc;
fill: #000000cc;
}
/* DARK MODE */
body[data-style=dark] .container,
@@ -350,3 +392,9 @@ body[data-style=dark] .btn.selected {
body[data-style=dark] .btn:hover {
background-color: #383838a6;
}
body[data-style=dark] .errors {
background-color: #f13000c5;
color: #ffffffcc;
fill: #ffffffcc;
}

View File

@@ -67,7 +67,8 @@
border-bottom-left-radius: 3px;
}
.node-header > *:last-child {
.node-header > *:last-child,
.node-header[data-error] > *:nth-last-child(2) {
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
}
@@ -120,6 +121,13 @@ button.remove::before {
content: url('./images/trash.svg');
}
.node-header[data-error] > svg {
border: none;
height: 34px;
width: 34px;
padding: 6px;
}
.node-body {
display: flex;
flex-direction: column;
@@ -175,6 +183,10 @@ button.remove {
background-color: #e76f51;
}
.node-header[data-error] > svg {
fill: #e76f51;
}
.node-body {
border-color: #b9b9b9 !important;
transition: border-color var(--style-transition);
@@ -239,6 +251,10 @@ body[data-style=dark] button.remove {
background-color: #b64023;
}
body[data-style=dark] .node-header[data-error] > svg {
fill: #b64023;
}
body[data-style=dark] .node-body {
border-color: #454749 !important;
}