Skip to content

Commit ab3dcc6

Browse files
committed
✨ Improve block display block input and undo history
1 parent 67e7f53 commit ab3dcc6

File tree

7 files changed

+153
-93
lines changed

7 files changed

+153
-93
lines changed

src/components/vanillaBlockDisplayElementPanel.svelte

Lines changed: 34 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,57 @@
11
<script lang="ts" context="module">
2+
import { validateBlock } from 'src/util/minecraftUtil'
23
import { VanillaBlockDisplay } from '../outliner/vanillaBlockDisplay'
3-
import EVENTS from '../util/events'
4-
import { Valuable } from '../util/stores'
54
import { translate } from '../util/translation'
65
</script>
76

87
<script lang="ts">
9-
let selectedDisplay = VanillaBlockDisplay.selected.at(0)
10-
let lastSelected = selectedDisplay
8+
export let selected: VanillaBlockDisplay
119
12-
let block = new Valuable<string>('')
13-
let error = new Valuable<string>('')
14-
let visible = false
10+
let block = selected.block
11+
let error = selected.error
1512
16-
let unsub: (() => void) | undefined
13+
$: {
14+
$error = ''
15+
if (selected.block !== block) {
16+
void validateBlock(block)
17+
.then(err => {
18+
if (err) {
19+
$error = err
20+
console.log('Block validation error:', err)
21+
return
22+
}
23+
console.log('Changing block to', block)
24+
Undo.initEdit({ elements: [selected] })
1725
18-
EVENTS.UPDATE_SELECTION.subscribe(() => {
19-
unsub?.()
26+
selected.block = block
27+
Project!.saved = false
2028
21-
lastSelected = selectedDisplay
22-
selectedDisplay = VanillaBlockDisplay.selected.at(0)
23-
24-
if (!selectedDisplay) {
25-
visible = false
26-
return
29+
Undo.finishEdit(`Change Block Display Block to "${block}"`, {
30+
elements: [selected],
31+
})
32+
})
33+
.catch(err => {
34+
$error = err.message
35+
})
2736
}
28-
29-
$block = selectedDisplay.block
30-
error = selectedDisplay.error
31-
visible = true
32-
33-
unsub = block.subscribe(value => {
34-
if (selectedDisplay == undefined || selectedDisplay !== lastSelected) {
35-
lastSelected = selectedDisplay
36-
return
37-
}
38-
if (value === selectedDisplay.block) return
39-
40-
Undo.initEdit({ elements: VanillaBlockDisplay.selected })
41-
42-
if (VanillaBlockDisplay.selected.length > 1) {
43-
for (const display of VanillaBlockDisplay.selected) {
44-
display.block = value
45-
}
46-
} else {
47-
selectedDisplay.block = value
48-
}
49-
Project!.saved = false
50-
51-
Undo.finishEdit(`Change Block Display Block to "${$block}"`, {
52-
elements: VanillaBlockDisplay.selected,
53-
})
54-
})
55-
})
37+
}
5638
</script>
5739

58-
<p class="panel_toolbar_label label" style={!!visible ? '' : 'visibility:hidden; height: 0px;'}>
40+
<p class="panel_toolbar_label label">
5941
{translate('panel.vanilla_block_display.title')}
6042
</p>
6143

62-
<div
63-
class="toolbar custom-toolbar"
64-
style={!!visible ? '' : 'visibility:hidden; height: 0px;'}
65-
title={translate('panel.vanilla_block_display.description')}
66-
>
44+
<div class="toolbar custom-toolbar" title={translate('panel.vanilla_block_display.description')}>
6745
<div class="content" style="width: 95%;">
68-
<input type="text" bind:value={$block} />
46+
<input type="text" bind:value={block} />
6947
</div>
7048
</div>
7149

72-
<div
73-
class="error"
74-
style={!!$error ? '' : 'visibility:hidden; height: 0px; color: var(--color-error);'}
75-
>
76-
{$error}
77-
</div>
50+
{#if $error}
51+
<div class="error">
52+
{$error}
53+
</div>
54+
{/if}
7855

7956
<style>
8057
input {

src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import { JsonText } from './systems/jsonText'
3939
import * as assetManager from './systems/minecraft/assetManager'
4040
import { getLatestVersionClientDownloadUrl } from './systems/minecraft/assetManager'
4141
import * as blockModelManager from './systems/minecraft/blockModelManager'
42-
import { BLOCKSTATE_REGISTRY } from './systems/minecraft/blockstateManager'
42+
import { BLOCKSTATE_REGISTRY, getBlockState } from './systems/minecraft/blockstateManager'
4343
import { getVanillaFont } from './systems/minecraft/fontManager'
4444
import * as itemModelManager from './systems/minecraft/itemModelManager'
4545
import './systems/minecraft/registryManager'
@@ -109,6 +109,7 @@ const AnimatedJavaApi = {
109109
},
110110
TELLRAW,
111111
JsonText,
112+
getBlockState,
112113
}
113114
window.AnimatedJava = AnimatedJavaApi
114115

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,45 @@
1-
import { registerMountSvelteComponentMod } from 'src/util/mountSvelteComponent'
1+
import { BLUEPRINT_FORMAT_ID } from 'src/formats/blueprint'
2+
import { VanillaBlockDisplay } from 'src/outliner/vanillaBlockDisplay'
3+
import EVENTS from 'src/util/events'
4+
import { registerProjectMod } from 'src/util/moddingTools'
5+
import { mountSvelteComponent } from 'src/util/mountSvelteComponent'
26
import VanillaBlockDisplayElementPanel from '../../components/vanillaBlockDisplayElementPanel.svelte'
37

4-
registerMountSvelteComponentMod({
8+
let mounted: VanillaBlockDisplayElementPanel | null = null
9+
10+
const destroyMounted = () => {
11+
mounted?.$destroy()
12+
mounted = null
13+
}
14+
15+
const updatePanel = () => {
16+
destroyMounted()
17+
const blockDisplay = VanillaBlockDisplay.selected.at(0)
18+
if (blockDisplay) {
19+
mounted = mountSvelteComponent({
20+
component: VanillaBlockDisplayElementPanel,
21+
props: { selected: blockDisplay },
22+
target: '#panel_element',
23+
})
24+
}
25+
}
26+
27+
registerProjectMod({
528
id: 'animated-java:append-element-panel/vanilla-block-display',
6-
component: VanillaBlockDisplayElementPanel,
7-
target: '#panel_element',
29+
30+
condition: project => project.format.id === BLUEPRINT_FORMAT_ID,
31+
32+
apply: () => {
33+
const unsubscribers = [
34+
EVENTS.UNDO.subscribe(updatePanel),
35+
EVENTS.REDO.subscribe(updatePanel),
36+
EVENTS.UPDATE_SELECTION.subscribe(updatePanel),
37+
]
38+
return { unsubscribers }
39+
},
40+
41+
revert: ({ unsubscribers }) => {
42+
unsubscribers.forEach(u => u())
43+
destroyMounted()
44+
},
845
})

src/mods/globalCssMod.css

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,14 @@ dialog.dialog pre:has(code) {
1515
#collection_properties_vue > ul > li {
1616
max-width: unset !important;
1717
}
18+
19+
#edit_history_list ul li {
20+
height: unset !important;
21+
}
22+
23+
#edit_history_list > ul > li label {
24+
margin-right: auto !important;
25+
overflow-x: auto !important;
26+
white-space: nowrap !important;
27+
max-width: 75% !important;
28+
}

src/outliner/vanillaBlockDisplay.ts

Lines changed: 21 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@ import { BoneConfig } from '../nodeConfigs'
55
import { BlockModelMesh, getBlockModel } from '../systems/minecraft/blockModelManager'
66
import { type BlockStateValue, getBlockState } from '../systems/minecraft/blockstateManager'
77
import { MINECRAFT_REGISTRY } from '../systems/minecraft/registryManager'
8-
import { getCurrentVersion } from '../systems/minecraft/versionManager'
98
import EVENTS from '../util/events'
10-
import { parseBlock } from '../util/minecraftUtil'
9+
import { validateBlock } from '../util/minecraftUtil'
1110
import { Valuable } from '../util/stores'
1211
import { translate } from '../util/translation'
1312
import { ResizableOutlinerElement } from './resizableOutlinerElement'
@@ -61,31 +60,8 @@ export class VanillaBlockDisplay extends ResizableOutlinerElement {
6160

6261
this.sanitizeName()
6362

64-
const updateBlock = async (newBlock: string) => {
65-
if (!MINECRAFT_REGISTRY.block) {
66-
requestAnimationFrame(() => void updateBlock(newBlock))
67-
return
68-
}
69-
const parsed = await parseBlock(newBlock)
70-
if (!parsed) {
71-
this.error.set('Invalid block ID.')
72-
} else if (
73-
(parsed.resource.namespace === 'minecraft' || parsed.resource.namespace === '') &&
74-
MINECRAFT_REGISTRY.block.has(parsed.resource.name)
75-
) {
76-
this.error.set('')
77-
this.preview_controller.updateGeometry(this)
78-
} else {
79-
this.error.set(`This block does not exist in Minecraft ${getCurrentVersion()!.id}.`)
80-
}
81-
if (this.mesh?.outline instanceof THREE.LineSegments) {
82-
if (this.error.get()) this.mesh.outline.material = ERROR_OUTLINE_MATERIAL
83-
else this.mesh.outline.material = Canvas.outlineMaterial
84-
}
85-
}
86-
87-
this.__block.subscribe(value => {
88-
void updateBlock(value)
63+
this.__block.subscribe(() => {
64+
void this.updateBlock()
8965
})
9066
}
9167

@@ -99,6 +75,10 @@ export class VanillaBlockDisplay extends ResizableOutlinerElement {
9975
this.__block.set(value)
10076
}
10177

78+
getBlockValuable() {
79+
return this.__block
80+
}
81+
10282
sanitizeName(): string {
10383
this.name = sanitizeOutlinerElementName(this.name, this.uuid)
10484
return this.name
@@ -164,6 +144,19 @@ export class VanillaBlockDisplay extends ResizableOutlinerElement {
164144
this.preview_controller.updateHighlight(this)
165145
}
166146

147+
async updateBlock() {
148+
const error = await validateBlock(this.block)
149+
if (error) {
150+
this.error.set(error)
151+
if (this.mesh?.outline instanceof THREE.LineSegments) {
152+
if (this.error.get()) this.mesh.outline.material = ERROR_OUTLINE_MATERIAL
153+
else this.mesh.outline.material = Canvas.outlineMaterial
154+
}
155+
return
156+
}
157+
this.preview_controller.updateGeometry(this)
158+
}
159+
167160
applyBlockModel(blockModel: BlockModelMesh) {
168161
const mesh = this.mesh as THREE.Mesh
169162
mesh.name = this.uuid
@@ -180,7 +173,6 @@ export class VanillaBlockDisplay extends ResizableOutlinerElement {
180173
this.preview_controller.updateHighlight(this)
181174
this.preview_controller.updateTransform(this)
182175
mesh.visible = this.visibility
183-
TickUpdates.selection = true
184176
}
185177
}
186178
VanillaBlockDisplay.prototype.icon = VanillaBlockDisplay.icon
@@ -223,10 +215,9 @@ export const PREVIEW_CONTROLLER = new NodePreviewController(VanillaBlockDisplay,
223215
.then(result => {
224216
if (!result?.mesh) return
225217
el.applyBlockModel(result)
226-
TickUpdates.selection = true
227218
})
228219
.catch(err => {
229-
console.error(err)
220+
console.error('Failed to get block model:', err)
230221
if (typeof err.message === 'string') {
231222
el.error.set(err.message as string)
232223
}

src/systems/minecraft/blockModelManager.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ export async function getBlockModel(block: string): Promise<BlockModelMesh | und
5151
await assetsLoaded()
5252
let result = BLOCK_MODEL_CACHE.get(block)
5353
if (!result) {
54-
// console.warn(`Found no cached item model mesh for '${block}'`)
5554
const parsed = await parseBlock(block)
5655
if (!parsed) return undefined
5756
if (BLACKLISTED_BLOCKS.has(block)) {
@@ -404,6 +403,31 @@ async function loadTexture(textures: IBlockModel['textures'], key: string): Prom
404403
return texture
405404
}
406405

406+
export function validateBlockState(block: IParsedBlock) {
407+
if (!block.blockStateRegistryEntry) {
408+
if (Object.keys(block.states).length > 0) {
409+
return `${block.resource.name} has no block states`
410+
}
411+
} else {
412+
for (const [k, v] of Object.entries(block.states)) {
413+
if (!block.blockStateRegistryEntry.stateValues[k]) {
414+
return (
415+
`Invalid block state '${k}' for '${block.resource.name}'` +
416+
` Expected one of: ${Object.keys(
417+
block.blockStateRegistryEntry.stateValues
418+
).join(', ')}`
419+
)
420+
} else if (!block.blockStateRegistryEntry.stateValues[k].includes(v)) {
421+
return (
422+
`Invalid block state value '${v.toString()}' for '${k}'.` +
423+
` Expected one of: ${block.blockStateRegistryEntry.stateValues[k].join(', ')}`
424+
)
425+
}
426+
}
427+
}
428+
return ''
429+
}
430+
407431
export async function parseBlockState(block: IParsedBlock): Promise<BlockModelMesh> {
408432
const path = getPathFromResourceLocation(block.resourceLocation, 'blockstates')
409433
const blockstate = (await getJSONAsset(path + '.json')) as IBlockState

src/util/minecraftUtil.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
import * as pathjs from 'path'
2+
import { assetsLoaded } from 'src/systems/minecraft/assetManager'
3+
import { validateBlockState } from 'src/systems/minecraft/blockModelManager'
4+
import { MINECRAFT_REGISTRY } from 'src/systems/minecraft/registryManager'
5+
import { getCurrentVersion } from 'src/systems/minecraft/versionManager'
26
import { SUPPORTED_MINECRAFT_VERSIONS } from '../systems/global'
37
import {
48
BlockStateRegistryEntry,
@@ -259,6 +263,21 @@ export interface IParsedBlock {
259263
blockStateRegistryEntry: BlockStateRegistryEntry | undefined
260264
}
261265

266+
export async function validateBlock(block: string) {
267+
if (!MINECRAFT_REGISTRY.block) await assetsLoaded()
268+
const parsed = await parseBlock(block)
269+
if (!parsed) {
270+
return 'Invalid block ID.'
271+
} else if (
272+
(parsed.resource.namespace === 'minecraft' || parsed.resource.namespace === '') &&
273+
MINECRAFT_REGISTRY.block.has(parsed.resource.name)
274+
) {
275+
return validateBlockState(parsed)
276+
} else {
277+
return `This block does not exist in Minecraft ${getCurrentVersion()!.id}.`
278+
}
279+
}
280+
262281
export async function parseBlock(block: string): Promise<IParsedBlock | undefined> {
263282
const states: Record<string, ReturnType<typeof resolveBlockstateValueType>> = {}
264283
if (block.includes('[')) {

0 commit comments

Comments
 (0)