Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/preferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export interface GlobalPreferences {
collapsedPanels: Record<string, boolean>;
sidebar: { visible: boolean; width: number };
mcpActivity: { visible: boolean; height: number };
textureBrowser: { showThumbnails: boolean };
quickPlay: QuickPlayPreferences;
buildProfiles: BuildProfilePreferences;
theme: { preset: ThemePreset; colors: ThemeColors };
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 : {};
Expand Down Expand Up @@ -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,
Expand Down
6 changes: 6 additions & 0 deletions src/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
33 changes: 33 additions & 0 deletions src/texture-browser.ts
Original file line number Diff line number Diff line change
@@ -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];
}
49 changes: 22 additions & 27 deletions src/texture-panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '';
Expand All @@ -45,6 +25,10 @@ export class TexturePanel {
private readonly managePakFiles: () => Promise<void> | void,
) {}

private get showThumbnails(): boolean {
return this.editor.preferences.textureBrowser.showThumbnails;
}

mount(): void {
this.rebuild();
}
Expand Down Expand Up @@ -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);
}

Expand All @@ -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');
Expand All @@ -275,7 +262,8 @@ export class TexturePanel {
toggle.setAttribute('aria-pressed', String(this.showThumbnails));
toggle.innerHTML = '<i class="ph ph-image" aria-hidden="true"></i>';
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();
Expand Down Expand Up @@ -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);
};

Expand All @@ -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');
Expand Down
2 changes: 1 addition & 1 deletion src/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
14 changes: 14 additions & 0 deletions tests/preferences.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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({
Expand Down Expand Up @@ -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({
Expand Down
29 changes: 29 additions & 0 deletions tests/texture-browser.test.ts
Original file line number Diff line number Diff line change
@@ -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([]);
});
});
Loading