Add visualizer view

This commit is contained in:
Misode
2020-08-06 23:35:50 +02:00
parent 78daf24e9e
commit 0ffe97dda5
9 changed files with 199 additions and 42 deletions

19
package-lock.json generated
View File

@@ -5,16 +5,16 @@
"requires": true,
"dependencies": {
"@mcschema/core": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/@mcschema/core/-/core-0.6.1.tgz",
"integrity": "sha512-9DIrcDw/OLs/SrPWBKK/8Y7JVUP1zu9MZfGv+JUrHahkHGsG7MZEAzPP8xqTWiMino2Z80F2khh2UPZX4zlRAQ=="
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/@mcschema/core/-/core-0.6.2.tgz",
"integrity": "sha512-L/ga6SSeJ1fzDxXN+kYs0YloFsVKipsK+WrRBtkdGm1PrccYw9jwGalVLneVs/JnlDicu60ExrZvbygV9TECag=="
},
"@mcschema/java-1.16": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@mcschema/java-1.16/-/java-1.16-0.3.1.tgz",
"integrity": "sha512-f9QOtKVSIngJGoBX2enC6cYJvmALzIy0sf7V7HCICYcWkzDj5Nj8uPEfKttpwDYgXC+jBcKI99niK/psP/dRYQ==",
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@mcschema/java-1.16/-/java-1.16-0.3.2.tgz",
"integrity": "sha512-wEWcAJdnFy8a6Q33sjub9Qrg21EIBiYYCswQe/kE6SBqHjYx6TsKt7733QKhJHPE71PtlZyGPkkw4+6x//nIiw==",
"requires": {
"@mcschema/core": "^0.6.1"
"@mcschema/core": "^0.6.2"
}
},
"@mcschema/locales": {
@@ -4403,6 +4403,11 @@
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
"integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA=="
},
"simplex-noise": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/simplex-noise/-/simplex-noise-2.4.0.tgz",
"integrity": "sha512-OjyDWm/QZjVbMrPxDVi9b2as+SeNn9EBXlrWVRlFW+TSyWMSXouDryXkQN0vf5YP+QZKobrmkvx1eQYPLtuqfw=="
},
"slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",

View File

@@ -12,14 +12,15 @@
"author": "Misode",
"license": "MIT",
"dependencies": {
"@mcschema/core": "^0.6.1",
"@mcschema/core": "^0.6.2",
"@mcschema/java-1.16": "^0.3.2",
"@mcschema/locales": "^0.1.5",
"@mcschema/java-1.16": "^0.3.1",
"@types/google.analytics": "0.0.40",
"@types/split.js": "^1.4.0",
"copy-webpack-plugin": "^6.0.1",
"html-webpack-plugin": "^4.3.0",
"merge-jsons-webpack-plugin": "^1.0.21",
"simplex-noise": "^2.4.0",
"split.js": "^1.5.11",
"ts-loader": "^7.0.4",
"typescript": "^3.9.3",

View File

@@ -2,14 +2,16 @@ import Split from 'split.js'
import {
AbstractView,
Base,
CollectionRegistry,
DataModel,
locale,
LOCALES,
ModelPath,
SourceView,
TreeView,
Path,
} from '@mcschema/core'
import { getCollections, getSchemas } from '@mcschema/java-1.16'
import { VisualizerView } from './visualization/VisualizerView'
import { RegistryFetcher } from './RegistryFetcher'
import { ErrorsView } from './ErrorsView'
import config from '../config.json'
@@ -61,6 +63,20 @@ const treeViewObserver = (el: HTMLElement) => {
})
}
const treeViewNodeInjector = (path: ModelPath, view: TreeView) => {
return Object.keys(VisualizerView.visualizers)
.map(id => VisualizerView.visualizers[id])
.filter(v => path.equals(v.path()))
.filter(v => v.active(path.getModel()))
.map(v => {
const id = view.registerClick(() => {
views.visualizer.set(v)
})
return `<button data-id=${id}>${locale('visualize')} <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.5 8a6.5 6.5 0 1113 0 6.5 6.5 0 01-13 0zM8 0a8 8 0 100 16A8 8 0 008 0zM6.379 5.227A.25.25 0 006 5.442v5.117a.25.25 0 00.379.214l4.264-2.559a.25.25 0 000-.428L6.379 5.227z"></path></svg></button>`
})
.join('')
}
const fetchLocale = async (id: string) => {
const response = await fetch(publicPath + `locales/${id}.json`)
LOCALES.register(id, await response.json())
@@ -95,18 +111,30 @@ const treeControlsVersionMenu = document.getElementById('tree-controls-version-m
const treeControlsReset = document.getElementById('tree-controls-reset')!
const treeControlsUndo = document.getElementById('tree-controls-undo')!
const treeControlsRedo = document.getElementById('tree-controls-redo')!
const visualizerOutput = document.getElementById('visualizer-output')!
Split([treeViewEl, sourceViewEl], {
sizes: [66, 34]
})
Split([sourceViewOutput, visualizerOutput], {
sizes: [60, 40],
direction: 'vertical'
})
const dummyModel = new DataModel(Base)
const views: {[key: string]: AbstractView} = {
const views = {
'tree': new TreeView(dummyModel, treeViewOutput, {
showErrors: true,
observer: treeViewObserver
observer: treeViewObserver,
nodeInjector: treeViewNodeInjector
}),
'source': new SourceView(dummyModel, sourceViewOutput, {
indentation: 2
}),
'errors': new ErrorsView(dummyModel, errorsViewEl)
'errors': new ErrorsView(dummyModel, errorsViewEl),
'visualizer': new VisualizerView(dummyModel, visualizerOutput as HTMLCanvasElement)
}
const COLLECTIONS = getCollections()
@@ -184,10 +212,6 @@ Promise.all([
}
}
Split([treeViewEl, sourceViewEl], {
sizes: [66, 34]
})
homeLink.addEventListener('click', evt => {
reload(publicPath)
})

View File

@@ -0,0 +1,36 @@
import SimplexNoise from 'simplex-noise'
import { DataModel, Path } from "@mcschema/core";
import { Visualizer } from './VisualizerView';
export class BiomeNoiseVisualizer implements Visualizer {
private noise: SimplexNoise
constructor() {
this.noise = new SimplexNoise()
}
path() {
return new Path(['generator', 'biome_source'])
}
active(model: DataModel) {
const biomeSource = new Path(['generator', 'biome_source'])
return model.get(biomeSource) !== undefined
&& model.get(biomeSource.push('type')) === 'minecraft:multi_noise'
}
draw(model: DataModel, img: ImageData) {
const biomeSource = model.get(new Path(['generator', 'biome_source']))
const data = img.data
for (let x = 0; x < 200; x += 1) {
for (let y = 0; y < 100; y += 1) {
const i = (y * (img.width * 4)) + (x * 4)
const b = (this.noise.noise2D(x/50, y/50) > 0) ? 0 : 255
data[i] = b
data[i + 1] = b
data[i + 2] = b
data[i + 3] = 255
}
}
}
}

View File

@@ -0,0 +1,57 @@
import { AbstractView, DataModel, Path } from "@mcschema/core";
import { BiomeNoiseVisualizer } from "./BiomeNoiseVisualizer";
export interface Visualizer {
path(): Path
active(model: DataModel): boolean
draw(model: DataModel, img: ImageData): void
}
export class VisualizerView extends AbstractView {
ctx: CanvasRenderingContext2D
visualizer?: Visualizer
active: boolean
canvas: HTMLElement
sourceView: HTMLElement
gutter: HTMLElement
lastHeight?: string
constructor(model: DataModel, canvas: HTMLCanvasElement) {
super(model)
this.ctx = canvas.getContext('2d')!
this.active = false
this.canvas = canvas
this.gutter = canvas.parentElement!.querySelector('.gutter') as HTMLElement
this.sourceView = canvas.parentElement!.getElementsByTagName('textarea')[0] as HTMLElement
}
invalidated() {
if (this.active && this.visualizer && this.visualizer.active(this.model)) {
const img = this.ctx.createImageData(200, 100)
this.visualizer.draw(this.model, img)
this.ctx.putImageData(img, 0, 0)
this.canvas.style.display = 'block'
this.gutter.style.display = 'block'
if (this.lastHeight) {
this.sourceView.style.height = this.lastHeight
this.lastHeight = undefined
}
} else {
this.canvas.style.display = 'none'
this.gutter.style.display = 'none'
this.lastHeight = this.sourceView.style.height
this.sourceView.style.height = '100%'
this.ctx.clearRect(0, 0, 200, 100)
}
}
set(visualizer: Visualizer) {
this.active = true
this.visualizer = visualizer
this.invalidated()
}
static visualizers: {[key: string]: Visualizer} = {
'biome-noise': new BiomeNoiseVisualizer()
}
}

View File

@@ -105,7 +105,10 @@
<span data-i18n="share"></span>
</button>
</div>
<textarea id="source-view-output" spellcheck="false" autocorrect="off" autocapitalize="off"></textarea>
<div class="source-content">
<textarea id="source-view-output" spellcheck="false" autocorrect="off" autocapitalize="off"></textarea>
<canvas width="200" height="100" id="visualizer-output"></canvas>
</div>
</div>
<div id="home-view" class="home">
<div id="home-generators" class="generators-list"></div>

View File

@@ -1,22 +1,23 @@
{
"title.home": "Data Pack Generators",
"title.generator": "%0% Generator",
"advancement": "Advancement",
"copy": "Copy",
"dimension-type": "Dimension Type",
"dimension": "Dimension",
"download": "Download",
"language": "Language",
"loot-table": "Loot Table",
"predicate": "Predicate",
"advancement": "Advancement",
"dimension": "Dimension",
"dimension-type": "Dimension Type",
"reset": "Reset",
"share": "Share",
"title.generator": "%0% Generator",
"title.home": "Data Pack Generators",
"visualize": "Visualize",
"worldgen/biome": "Biome",
"worldgen/carver": "Carver",
"worldgen/feature": "Feature",
"worldgen/noise-settings": "Noise Settings",
"worldgen/processor-list": "Processor List",
"worldgen/structure-feature": "Structure Feature",
"worldgen/surface-builder": "Surface Builder",
"worldgen/processor-list": "Processor List",
"worldgen/template-pool": "Template Pool",
"language": "Language",
"reset": "Reset",
"copy": "Copy",
"download": "Download",
"share": "Share"
"worldgen/template-pool": "Template Pool"
}

View File

@@ -152,11 +152,23 @@ body {
padding: 1rem;
overflow-y: auto;
padding-bottom: 50vh;
position: relative;
}
.source {
position: relative;
}
.source-content {
display: flex;
flex-direction: column;
height: 100%;
}
.source textarea {
width: 100%;
height: calc(100vh - 56px);
/* height: calc(100vh - 56px); */
height: 100%;
padding: 1.3rem 0.4rem;
border: none;
white-space: pre;
@@ -170,18 +182,13 @@ body {
resize: none;
background-color: var(--background);
color: var(--text);
transition:all var(--style-transition);
/* transition:all var(--style-transition); */
}
.source textarea::selection {
background-color: var(--selection);
}
.tree,
.source {
position: relative;
}
.tree-controls,
.source-controls {
display: flex;
@@ -213,12 +220,36 @@ body {
right: 17px
}
.gutter {
border-color: var(--border) !important;
transition: border-color var(--style-transition);
}
.gutter.gutter-vertical {
border-top: 2px solid;
border-bottom: 2px solid;
cursor: ns-resize;
}
.gutter.gutter-horizontal {
border-left: 2px solid;
float: left;
border-right: 2px solid;
cursor: ew-resize;
border-color: var(--border);
transition: border-color var(--style-transition);
}
.source canvas {
width: 100%;
max-width: 100%;
/* position: absolute; */
/* bottom: 0; */
background-color: var(--nav-faded);
display: block;
image-rendering: optimizeSpeed;
image-rendering: -moz-crisp-edges;
image-rendering: -webkit-optimize-contrast;
image-rendering: -o-crisp-edges;
image-rendering: pixelated;
-ms-interpolation-mode: nearest-neighbor;
}
.btn {

View File

@@ -176,8 +176,7 @@ button.remove {
border-color: var(--node-remove-border);
}
button.remove svg,
button.add svg {
.node-header > button svg {
display: inline-block;
position: relative;
top: 2px;