diff --git a/package-lock.json b/package-lock.json index 51ab9989..0be68b34 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index bf6b5067..88517c3b 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/app/app.ts b/src/app/app.ts index 72e4551a..bdac668a 100644 --- a/src/app/app.ts +++ b/src/app/app.ts @@ -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 `` + }) + .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) }) diff --git a/src/app/visualization/BiomeNoiseVisualizer.ts b/src/app/visualization/BiomeNoiseVisualizer.ts new file mode 100644 index 00000000..616ad3aa --- /dev/null +++ b/src/app/visualization/BiomeNoiseVisualizer.ts @@ -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 + } + } + } +} diff --git a/src/app/visualization/VisualizerView.ts b/src/app/visualization/VisualizerView.ts new file mode 100644 index 00000000..2f314a41 --- /dev/null +++ b/src/app/visualization/VisualizerView.ts @@ -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() + } +} diff --git a/src/index.html b/src/index.html index e7ab1868..ff4a3852 100644 --- a/src/index.html +++ b/src/index.html @@ -105,7 +105,10 @@ - +
+ + +
diff --git a/src/locales/en.json b/src/locales/en.json index cb2e3833..cfedbfea 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -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" } diff --git a/src/styles/global.css b/src/styles/global.css index 446ec6ba..1a07aac3 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -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 { diff --git a/src/styles/nodes.css b/src/styles/nodes.css index 0939c5c5..cdd2c753 100644 --- a/src/styles/nodes.css +++ b/src/styles/nodes.css @@ -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;