diff --git a/src/app/ErrorsView.ts b/src/app/ErrorsView.ts new file mode 100644 index 00000000..41c5d2d1 --- /dev/null +++ b/src/app/ErrorsView.ts @@ -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 => + `
+ + ${err.path.toString()} + - + ${locale(err.error, err.params)} +
` + ).join('') + } +} diff --git a/src/app/app.ts b/src/app/app.ts index 2b981d74..e54f875f 100644 --- a/src/app/app.ts +++ b/src/app/app.ts @@ -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', ``) + }) +} + 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' }) diff --git a/src/generator.html b/src/generator.html index c581ba70..b67c04e7 100644 --- a/src/generator.html +++ b/src/generator.html @@ -11,7 +11,7 @@
-

Loot Table Generator

+

+
diff --git a/src/styles/global.css b/src/styles/global.css index e89ea66a..6ea76404 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -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; +} diff --git a/src/styles/nodes.css b/src/styles/nodes.css index e927fddb..8c6cdf16 100644 --- a/src/styles/nodes.css +++ b/src/styles/nodes.css @@ -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; } diff --git a/tsconfig.json b/tsconfig.json index ba093079..c270df69 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es5", + "target": "es6", "module": "esnext", "strict": true, "moduleResolution": "node",