From 618e335b16549ad98f973e1a63de16699921c1e4 Mon Sep 17 00:00:00 2001 From: Einar Andersson <72999+drdator@users.noreply.github.com> Date: Mon, 3 Aug 2026 14:04:13 +0200 Subject: [PATCH] Fix texture browser defaults and preferences --- src/preferences.ts | 7 +++++ src/style.css | 6 +++++ src/texture-browser.ts | 33 +++++++++++++++++++++++ src/texture-panel.ts | 49 ++++++++++++++++------------------- src/ui.ts | 2 +- tests/preferences.test.ts | 14 ++++++++++ tests/texture-browser.test.ts | 29 +++++++++++++++++++++ 7 files changed, 112 insertions(+), 28 deletions(-) create mode 100644 src/texture-browser.ts create mode 100644 tests/texture-browser.test.ts diff --git a/src/preferences.ts b/src/preferences.ts index f0f904d..8cf15e8 100644 --- a/src/preferences.ts +++ b/src/preferences.ts @@ -68,6 +68,7 @@ export interface GlobalPreferences { collapsedPanels: Record; sidebar: { visible: boolean; width: number }; mcpActivity: { visible: boolean; height: number }; + textureBrowser: { showThumbnails: boolean }; quickPlay: QuickPlayPreferences; buildProfiles: BuildProfilePreferences; theme: { preset: ThemePreset; colors: ThemeColors }; @@ -101,6 +102,7 @@ export const DEFAULT_GLOBAL_PREFERENCES: GlobalPreferences = { collapsedPanels: {}, sidebar: { visible: true, width: DEFAULT_SIDEBAR_WIDTH }, mcpActivity: { visible: false, height: DEFAULT_MCP_ACTIVITY_PANEL_HEIGHT }, + textureBrowser: { showThumbnails: false }, quickPlay: { quality: 'normal', generateAas: true, @@ -168,6 +170,7 @@ export function normalizeGlobalPreferences(value: unknown): GlobalPreferences { const theme = isRecord(value.theme) ? value.theme : {}; const sidebar = isRecord(value.sidebar) ? value.sidebar : {}; const mcpActivity = isRecord(value.mcpActivity) ? value.mcpActivity : {}; + const textureBrowser = isRecord(value.textureBrowser) ? value.textureBrowser : {}; const quickPlay = isRecord(value.quickPlay) ? value.quickPlay : {}; const buildProfiles = isRecord(value.buildProfiles) ? value.buildProfiles : {}; const colors = isRecord(theme.colors) ? theme.colors : {}; @@ -234,6 +237,10 @@ export function normalizeGlobalPreferences(value: unknown): GlobalPreferences { visible: typeof mcpActivity.visible === 'boolean' ? mcpActivity.visible : defaults.mcpActivity.visible, height: clampMcpActivityPanelHeight(Number(mcpActivity.height)), }, + textureBrowser: { + showThumbnails: typeof textureBrowser.showThumbnails === 'boolean' + ? textureBrowser.showThumbnails : defaults.textureBrowser.showThumbnails, + }, quickPlay: { quality: quickPlayQualities.includes(quickPlay.quality as QuickPlayQuality) ? quickPlay.quality as QuickPlayQuality : defaults.quickPlay.quality, diff --git a/src/style.css b/src/style.css index 97cee46..25a1e24 100644 --- a/src/style.css +++ b/src/style.css @@ -2077,6 +2077,12 @@ body.sidepanel-resizing * { gap: 1px; } +.texture-list-empty { + padding: 8px 4px; + color: var(--text-dim); + font-size: 10px; +} + .texture-tools { display: flex; flex-direction: column; diff --git a/src/texture-browser.ts b/src/texture-browser.ts new file mode 100644 index 0000000..f63b713 --- /dev/null +++ b/src/texture-browser.ts @@ -0,0 +1,33 @@ +const COMMON_TEXTURES = [ + 'common/caulk', + 'common/clip', + 'common/trigger', + 'common/nodraw', + 'base_wall/basewall03', + 'base_wall/basewall04', + 'base_wall/concrete', + 'base_floor/concrete', + 'base_floor/diamond2c', + 'base_floor/pjgrate1', + 'base_trim/pewter_shiney', + 'base_trim/dirty_pewter', + 'gothic_wall/iron01_e', + 'gothic_wall/skull4', + 'gothic_floor/blocks17floor', + 'gothic_trim/baseboard09', + 'skies/earthsky01', +]; + +function normalizedTextureName(texture: string): string { + return texture.toLowerCase().replace(/\\/g, '/').replace(/^textures\//, ''); +} + +export function defaultTextureBrowserEntries(availableTextures: readonly string[]): string[] { + const availableByName = new Map( + availableTextures.map(texture => [normalizedTextureName(texture), texture]), + ); + const commonTextures = COMMON_TEXTURES + .map(texture => availableByName.get(normalizedTextureName(texture))) + .filter((texture): texture is string => texture !== undefined); + return commonTextures.length > 0 ? commonTextures : [...availableTextures]; +} diff --git a/src/texture-panel.ts b/src/texture-panel.ts index 0717ffb..f71119a 100644 --- a/src/texture-panel.ts +++ b/src/texture-panel.ts @@ -5,30 +5,10 @@ import { textureSearchScore } from './texture-search'; import { listTextureTags, setTextureTags, textureTagsFor, type TextureTagMap } from './texture-tags'; import { openTextureTagsDialog } from './texture-tags-dialog'; import { panelSubhead } from './ui-controls'; - -const COMMON_TEXTURES = [ - 'common/caulk', - 'common/clip', - 'common/trigger', - 'common/nodraw', - 'base_wall/basewall03', - 'base_wall/basewall04', - 'base_wall/concrete', - 'base_floor/concrete', - 'base_floor/diamond2c', - 'base_floor/pjgrate1', - 'base_trim/pewter_shiney', - 'base_trim/dirty_pewter', - 'gothic_wall/iron01_e', - 'gothic_wall/skull4', - 'gothic_floor/blocks17floor', - 'gothic_trim/baseboard09', - 'skies/earthsky01', -]; +import { defaultTextureBrowserEntries } from './texture-browser'; export class TexturePanel { private textureManager: TextureManager | null = null; - private showThumbnails = false; private directory = ''; private search = ''; private tagFilter = ''; @@ -45,6 +25,10 @@ export class TexturePanel { private readonly managePakFiles: () => Promise | void, ) {} + private get showThumbnails(): boolean { + return this.editor.preferences.textureBrowser.showThumbnails; + } + mount(): void { this.rebuild(); } @@ -241,9 +225,9 @@ export class TexturePanel { } const list = document.createElement('div'); - list.className = 'texture-list'; + list.className = 'texture-list texture-list-empty'; list.id = 'texture-list'; - this.populateTextureList(list, COMMON_TEXTURES, null); + list.textContent = 'Texture assets are loading…'; body.appendChild(list); } @@ -258,14 +242,17 @@ export class TexturePanel { value: '', textContent: '-- select folder --', })); - for (const directory of textureManager.listTextureDirectories()) { + const directories = textureManager.listTextureDirectories(); + for (const directory of directories) { directorySelect.appendChild(Object.assign(document.createElement('option'), { value: directory, textContent: directory, })); } - if (Array.from(directorySelect.options).some(option => option.value === this.directory)) { + if (directories.includes(this.directory)) { directorySelect.value = this.directory; + } else { + this.directory = ''; } const toggle = document.createElement('button'); @@ -275,7 +262,8 @@ export class TexturePanel { toggle.setAttribute('aria-pressed', String(this.showThumbnails)); toggle.innerHTML = ''; toggle.addEventListener('click', () => { - this.showThumbnails = !this.showThumbnails; + this.editor.preferences.textureBrowser.showThumbnails = !this.showThumbnails; + this.editor.persistCurrentPreferences(); toggle.classList.toggle('active', this.showThumbnails); toggle.setAttribute('aria-pressed', String(this.showThumbnails)); repopulate(); @@ -359,7 +347,7 @@ export class TexturePanel { ? textureManager.listTexturesInDir(this.directory) : this.tagFilter ? allTextures - : COMMON_TEXTURES; + : defaultTextureBrowserEntries(allTextures); this.populateTextureList(list, baseTextures.filter(filterByTag), this.directory || null); }; @@ -383,6 +371,13 @@ export class TexturePanel { private populateTextureList(list: HTMLElement, textures: string[], selectedDirectory: string | null): void { list.replaceChildren(); list.classList.toggle('texture-grid', this.showThumbnails && Boolean(this.textureManager)); + list.classList.toggle('texture-list-empty', textures.length === 0); + + if (textures.length === 0) { + list.classList.remove('texture-grid'); + list.textContent = 'No textures found.'; + return; + } for (const texture of textures) { const item = document.createElement('div'); diff --git a/src/ui.ts b/src/ui.ts index 02bbfd9..a9d37cd 100644 --- a/src/ui.ts +++ b/src/ui.ts @@ -758,7 +758,7 @@ export class UI { render(); }; const defaultToggleText = document.createElement('span'); - defaultToggleText.textContent = model.openArenaEnabled ? 'Enabled' : 'Disabled'; + defaultToggleText.textContent = 'Enable'; defaultToggle.append(defaultCheckbox, defaultToggleText); defaultRow.append(defaultOrder, defaultInfo, defaultToggle); list.appendChild(defaultRow); diff --git a/tests/preferences.test.ts b/tests/preferences.test.ts index fb379fa..5679cec 100644 --- a/tests/preferences.test.ts +++ b/tests/preferences.test.ts @@ -23,6 +23,7 @@ describe('global preferences', () => { preferences.collapsedPanels['entity-panel'] = true; preferences.sidebar = { visible: false, width: 420 }; preferences.mcpActivity = { visible: true, height: 360 }; + preferences.textureBrowser.showThumbnails = true; preferences.display.rendererMode = 'editor-fill'; preferences.display.showGrid3D = false; preferences.quickPlay = { @@ -42,6 +43,7 @@ describe('global preferences', () => { expect(loaded.preferences.collapsedPanels).toEqual({ 'entity-panel': true }); expect(loaded.preferences.sidebar).toEqual({ visible: false, width: 420 }); expect(loaded.preferences.mcpActivity).toEqual({ visible: true, height: 360 }); + expect(loaded.preferences.textureBrowser).toEqual({ showThumbnails: true }); expect(loaded.preferences.display.rendererMode).toBe('editor-fill'); expect(loaded.preferences.display.showGrid3D).toBe(false); expect(loaded.preferences.quickPlay).toEqual({ @@ -101,6 +103,18 @@ describe('global preferences', () => { }); }); + it('normalizes persisted texture browser view mode', () => { + const storage = new MemoryStorage(); + storage.setItem(PREFERENCES_STORAGE_KEY, JSON.stringify({ + version: 2, + textureBrowser: { showThumbnails: 'yes' }, + })); + + expect(loadGlobalPreferences(storage).preferences.textureBrowser).toEqual({ + showThumbnails: false, + }); + }); + it('normalizes persisted Quick Play settings', () => { const storage = new MemoryStorage(); storage.setItem(PREFERENCES_STORAGE_KEY, JSON.stringify({ diff --git a/tests/texture-browser.test.ts b/tests/texture-browser.test.ts new file mode 100644 index 0000000..88a9d21 --- /dev/null +++ b/tests/texture-browser.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from 'vitest'; +import { defaultTextureBrowserEntries } from '../src/texture-browser'; + +describe('default texture browser entries', () => { + it('only shows common textures that exist in the active asset stack', () => { + expect(defaultTextureBrowserEntries([ + 'custom/wall', + 'Textures/Common/Caulk', + 'base_floor/diamond2c', + ])).toEqual([ + 'Textures/Common/Caulk', + 'base_floor/diamond2c', + ]); + }); + + it('falls back to all available textures for a custom-only asset stack', () => { + expect(defaultTextureBrowserEntries([ + 'custom/floor', + 'custom/wall', + ])).toEqual([ + 'custom/floor', + 'custom/wall', + ]); + }); + + it('shows no fake entries when the asset stack has no textures', () => { + expect(defaultTextureBrowserEntries([])).toEqual([]); + }); +});