UNPKG

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
/** * 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