agentscape
Version:
Agentscape is a library for creating agent-based simulations. It provides a simple API for defining agents and their behavior, and for defining the environment in which the agents interact. Agentscape is designed to be flexible and extensible, allowing
173 lines • 6.76 kB
JavaScript
/**
* This is responsible for creating the controls pane in the UI.
*/
import LocalStoreContext from '../runtime/ui/LocalStoreContext';
export default class Controls {
constructor(opts) {
this.settings = [];
const { root, settings, title = 'Controls', id = '_controls_' } = opts;
this.context = new LocalStoreContext(id);
// infer a type for each setting
settings.forEach(setting => {
if (typeof setting.default === 'string') {
// setting.type = 'string'
this.settings.push({ type: 'string', ...setting });
}
else if (typeof setting.default === 'number') {
// setting.type = 'number'
this.settings.push({ type: 'number', ...setting });
}
else if (typeof setting.default === 'boolean') {
// setting.type = 'boolean'
this.settings.push({ type: 'boolean', ...setting });
}
else {
throw new Error(`Unknown type for setting ${setting.name}`);
}
});
if (this.context.length() === 0) {
this.settings.forEach(setting => {
this.context.set(setting.name, setting.default, setting.type);
});
}
const controlsPane = document.createElement('drag-pane');
controlsPane.setAttribute('heading', title);
controlsPane.setAttribute('key', 'controls');
const controlsContainer = document.createElement('div');
controlsContainer.style.display = 'flex';
controlsContainer.style.flexDirection = 'column';
controlsContainer.style.alignItems = 'flex-start';
controlsContainer.style.padding = '5px';
controlsContainer.style.width = 300 + 'px';
const resetButton = document.createElement('button');
resetButton.innerText = 'Reset Values';
resetButton.style.margin = '5px';
resetButton.addEventListener('click', () => {
this.reset();
window.location.reload();
});
this.settings.forEach(setting => {
const control = (setting === null || setting === void 0 ? void 0 : setting.options) && setting.options.length > 0
? generateSelection(setting, this.context)
: generateInput(setting, this.context);
controlsContainer.appendChild(control);
});
controlsContainer.appendChild(resetButton);
controlsPane.appendChild(controlsContainer);
root.appendChild(controlsPane);
}
getSetting(name) {
return this.context.get(name);
}
setSetting(name, value, type) {
this.context.set(name, value, type);
}
reset() {
this.context.clear();
this.settings.forEach(setting => {
this.context.set(setting.name, setting.default, setting.type);
});
}
}
// generates select & option elements based on the options of the setting
const generateSelection = (setting, context) => {
var _a;
// create a div to hold the input and label
const wrapper = document.createElement('div');
const select = document.createElement('select');
select.style.margin = '1px 3px';
select.style.width = '200px';
(_a = setting.options) === null || _a === void 0 ? void 0 : _a.forEach(option => {
const optionElement = document.createElement('option');
optionElement.value = option.value;
optionElement.innerText = option.label;
select.appendChild(optionElement);
});
select.value = context.get(setting.name);
select.id = setting.name;
const label = document.createElement('label');
label.htmlFor = setting.name;
label.innerText = setting.label;
label.title = setting.tooltip || '';
// underline the label if it has a tooltip
if (setting.tooltip) {
label.style.cursor = 'help';
}
wrapper.appendChild(label);
wrapper.appendChild(select);
wrapper.querySelector('select').addEventListener('input', (event) => {
context.set(setting.name, event.target.value, setting.type);
});
return wrapper;
};
// generates an input element based on the type of the setting
const generateInput = (setting, context) => {
var _a;
// create a div to hold the input and label
const wrapper = document.createElement('div');
const input = document.createElement('input');
input.style.margin = '1px 3px';
input.style.width = '50px';
// counts the number of decimal places in a float
const numericalPrecision = typeof context.get(setting.name) === 'number'
? ((_a = context.get(setting.name).toString().split('.')[1]) !== null && _a !== void 0 ? _a : []).length
: null;
switch (setting.type) {
case 'number':
input.type = 'number';
input.value = context.get(setting.name);
if (numericalPrecision) {
input.step = '0.' + '0'.repeat(numericalPrecision - 1) + '1';
}
else {
input.step = '1';
}
break;
case 'string':
input.type = 'text';
input.value = context.get(setting.name);
break;
case 'boolean':
input.type = 'checkbox';
input.checked = context.get(setting.name);
break;
}
input.id = setting.name;
const label = document.createElement('label');
label.htmlFor = setting.name;
label.innerText = setting.label;
label.title = setting.tooltip || '';
// underline the label if it has a tooltip
if (setting.tooltip) {
label.style.cursor = 'help';
}
wrapper.appendChild(label);
wrapper.appendChild(input);
wrapper.querySelector('input').addEventListener('input', (event) => {
var _a;
let newValue;
let oldValue = context.get(setting.name);
if (setting.type === 'boolean') {
newValue = event.target.checked;
}
else {
newValue = event.target.value;
}
if (setting.validation) {
const { isValid = true, message = '' } = (_a = setting.validation(newValue)) !== null && _a !== void 0 ? _a : {};
if (!isValid) {
console.error(`Invalid value for ${setting.name}: ${message}`);
context.set(setting.name, oldValue, setting.type);
event.target.value = oldValue;
}
else {
context.set(setting.name, newValue, setting.type);
}
}
else {
context.set(setting.name, newValue, setting.type);
}
});
return wrapper;
};
//# sourceMappingURL=Controls.js.map