This commit is contained in:
parent
39b0160064
commit
2347ecd201
71 changed files with 3779 additions and 1586 deletions
|
|
@ -9,6 +9,32 @@ import { activeVibe, settings } from '../settings';
|
|||
import { VIBE_PRESETS } from '../vibes';
|
||||
|
||||
type PaneContainer = Pick<FolderApi, 'addBinding' | 'addButton' | 'addFolder'>;
|
||||
type ColorReactionKey = (typeof colorReactionRows)[number]['keys'][number];
|
||||
|
||||
const colorReactionRows = [
|
||||
{
|
||||
colorIndex: 0,
|
||||
label: '1',
|
||||
keys: ['color1ToColor1', 'color1ToColor2', 'color1ToColor3'],
|
||||
},
|
||||
{
|
||||
colorIndex: 1,
|
||||
label: '2',
|
||||
keys: ['color2ToColor1', 'color2ToColor2', 'color2ToColor3'],
|
||||
},
|
||||
{
|
||||
colorIndex: 2,
|
||||
label: '3',
|
||||
keys: ['color3ToColor1', 'color3ToColor2', 'color3ToColor3'],
|
||||
},
|
||||
] as const;
|
||||
|
||||
const colorReactionKeySet = new Set<string>(
|
||||
colorReactionRows.flatMap((row) => [...row.keys])
|
||||
);
|
||||
|
||||
const isColorReactionKey = (key: string): key is ColorReactionKey =>
|
||||
colorReactionKeySet.has(key);
|
||||
|
||||
interface ConfigPaneOptions {
|
||||
onConfigChange: () => void;
|
||||
|
|
@ -37,6 +63,14 @@ const toLabel = (value: string): string =>
|
|||
.trim();
|
||||
|
||||
const normalizeNumber = (value: number, config: NumberControlConfig): number => {
|
||||
if (config.options) {
|
||||
const optionValues = Object.values(config.options);
|
||||
if (optionValues.includes(value)) {
|
||||
return value;
|
||||
}
|
||||
return optionValues.includes(0) ? 0 : (optionValues[0] ?? config.min);
|
||||
}
|
||||
|
||||
const finiteValue = Number.isFinite(value) ? value : config.min;
|
||||
const clampedValue = Math.min(config.max, Math.max(config.min, finiteValue));
|
||||
return config.integer ? Math.round(clampedValue) : clampedValue;
|
||||
|
|
@ -49,12 +83,21 @@ const getNumberBindingParams = (
|
|||
label: config.label ?? toLabel(key),
|
||||
min: config.min,
|
||||
max: config.max,
|
||||
options: config.options,
|
||||
step: config.step,
|
||||
});
|
||||
|
||||
export class ConfigPane {
|
||||
private readonly container: HTMLDivElement;
|
||||
private readonly pane: Pane;
|
||||
private readonly colorReactionSelects = new Map<
|
||||
ColorReactionKey,
|
||||
HTMLSelectElement
|
||||
>();
|
||||
private readonly colorReactionSwatches: Array<{
|
||||
colorIndex: number;
|
||||
element: HTMLElement;
|
||||
}> = [];
|
||||
private readonly state = {
|
||||
activeVibeId: activeVibe.id,
|
||||
};
|
||||
|
|
@ -106,6 +149,7 @@ export class ConfigPane {
|
|||
public refresh(): void {
|
||||
this.state.activeVibeId = activeVibe.id;
|
||||
this.pane.refresh();
|
||||
this.syncColorReactionMatrix();
|
||||
this.syncButton();
|
||||
}
|
||||
|
||||
|
|
@ -148,7 +192,19 @@ export class ConfigPane {
|
|||
.on('click', () => this.options.onRestart());
|
||||
|
||||
const folders = new Map<string, PaneContainer>();
|
||||
let hasAddedColorReactionMatrix = false;
|
||||
Object.entries(appConfig.runtimeSettings.controls).forEach(([key, config]) => {
|
||||
const settingKey = key as keyof GardenRuntimeSettings & string;
|
||||
settings[settingKey] = normalizeNumber(settings[settingKey], config);
|
||||
|
||||
if (isColorReactionKey(key)) {
|
||||
if (!hasAddedColorReactionMatrix) {
|
||||
this.addColorReactionMatrix(container);
|
||||
hasAddedColorReactionMatrix = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const folder =
|
||||
folders.get(config.folder) ??
|
||||
container.addFolder({
|
||||
|
|
@ -157,8 +213,6 @@ export class ConfigPane {
|
|||
});
|
||||
folders.set(config.folder, folder);
|
||||
|
||||
const settingKey = key as keyof GardenRuntimeSettings & string;
|
||||
settings[settingKey] = normalizeNumber(settings[settingKey], config);
|
||||
folder
|
||||
.addBinding(settings, settingKey, getNumberBindingParams(settingKey, config))
|
||||
.on('change', () => {
|
||||
|
|
@ -170,6 +224,117 @@ export class ConfigPane {
|
|||
this.options.onRuntimeChange();
|
||||
});
|
||||
});
|
||||
this.syncColorReactionMatrix();
|
||||
}
|
||||
|
||||
private addColorReactionMatrix(container: PaneContainer): void {
|
||||
const folder = container.addFolder({
|
||||
title: 'Color Reactions',
|
||||
expanded: true,
|
||||
});
|
||||
folder.element.classList.add('color-reaction-folder');
|
||||
|
||||
const content = Array.from(folder.element.children).find((child) =>
|
||||
child.classList.contains('tp-fldv_c')
|
||||
);
|
||||
if (!(content instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const doc = folder.element.ownerDocument;
|
||||
const matrix = doc.createElement('div');
|
||||
matrix.className = 'color-reaction-matrix';
|
||||
|
||||
matrix.appendChild(this.createColorReactionCorner(doc));
|
||||
colorReactionRows.forEach((row) => {
|
||||
matrix.appendChild(this.createColorReactionHeader(doc, row.colorIndex, row.label));
|
||||
});
|
||||
|
||||
colorReactionRows.forEach((row) => {
|
||||
matrix.appendChild(this.createColorReactionHeader(doc, row.colorIndex, row.label));
|
||||
row.keys.forEach((key, columnIndex) => {
|
||||
matrix.appendChild(
|
||||
this.createColorReactionCell(doc, key, row.colorIndex, columnIndex)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
content.appendChild(matrix);
|
||||
this.syncColorReactionMatrix();
|
||||
}
|
||||
|
||||
private createColorReactionCorner(doc: Document): HTMLDivElement {
|
||||
const corner = doc.createElement('div');
|
||||
corner.className = 'color-reaction-matrix__corner';
|
||||
corner.textContent = 'agent';
|
||||
return corner;
|
||||
}
|
||||
|
||||
private createColorReactionHeader(
|
||||
doc: Document,
|
||||
colorIndex: number,
|
||||
label: string
|
||||
): HTMLDivElement {
|
||||
const header = doc.createElement('div');
|
||||
header.className = 'color-reaction-matrix__header';
|
||||
|
||||
const swatch = doc.createElement('span');
|
||||
swatch.className = 'color-reaction-matrix__swatch';
|
||||
this.colorReactionSwatches.push({ colorIndex, element: swatch });
|
||||
header.appendChild(swatch);
|
||||
|
||||
const text = doc.createElement('span');
|
||||
text.textContent = label;
|
||||
header.appendChild(text);
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
private createColorReactionCell(
|
||||
doc: Document,
|
||||
key: ColorReactionKey,
|
||||
sourceColorIndex: number,
|
||||
targetColorIndex: number
|
||||
): HTMLLabelElement {
|
||||
const cell = doc.createElement('label');
|
||||
cell.className = 'color-reaction-matrix__cell';
|
||||
|
||||
const select = doc.createElement('select');
|
||||
select.setAttribute(
|
||||
'aria-label',
|
||||
`Color ${sourceColorIndex + 1} agents reacting to color ${targetColorIndex + 1}`
|
||||
);
|
||||
|
||||
const config = appConfig.runtimeSettings.controls[key];
|
||||
Object.entries(config.options ?? {}).forEach(([label, value]) => {
|
||||
const option = doc.createElement('option');
|
||||
option.value = String(value);
|
||||
option.textContent = label;
|
||||
select.appendChild(option);
|
||||
});
|
||||
|
||||
select.addEventListener('change', () => {
|
||||
settings[key] = normalizeNumber(Number(select.value), config);
|
||||
select.value = String(settings[key]);
|
||||
this.options.onRuntimeChange();
|
||||
});
|
||||
|
||||
this.colorReactionSelects.set(key, select);
|
||||
cell.appendChild(select);
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
private syncColorReactionMatrix(): void {
|
||||
this.colorReactionSelects.forEach((select, key) => {
|
||||
const config = appConfig.runtimeSettings.controls[key];
|
||||
settings[key] = normalizeNumber(settings[key], config);
|
||||
select.value = String(settings[key]);
|
||||
});
|
||||
|
||||
this.colorReactionSwatches.forEach(({ colorIndex, element }) => {
|
||||
element.style.backgroundColor = activeVibe.colors[colorIndex] ?? '#ffffff';
|
||||
});
|
||||
}
|
||||
|
||||
private setUpConfigTab(container: PaneContainer): void {
|
||||
|
|
@ -252,6 +417,7 @@ export class ConfigPane {
|
|||
}
|
||||
|
||||
private syncButton(): void {
|
||||
this.options.settingsButton.classList.toggle('active', this.isOpen);
|
||||
this.options.settingsButton.setAttribute('aria-expanded', String(this.isOpen));
|
||||
this.options.settingsButton.setAttribute(
|
||||
'aria-label',
|
||||
|
|
|
|||
Loading…
Reference in a new issue