Files
PozzettiAndrea-ComfyUI-Geom…/javascript/js/viewer/ui/SettingsPanel.js
T
PozzettiAndrea 414f759791 Vendor frontend JS into javascript/; drop the prestartup copy dance
The viewer JS/HTML/bundles are now committed in javascript/ (declared via
pyproject [tool.comfy] web = "javascript") instead of being copied from the
shared comfy-3d-viewers pip package at prestartup. Consequences:

- requirements.txt is now just comfy-env (host env installs nothing else,
  per the never-touch-the-host-env principle)
- prestartup_script.py no longer copies viewers or web_src; it keeps only
  setup_env() and the input-asset copies
- WEB_DIRECTORY attribute removed; the pyproject declaration is the single
  source (served at /extensions/comfyui-geometrypack/, keyed by project name)
- web_src/ deleted; its 6 widgets now live in javascript/js/ as the tracked
  source, edited directly
- viewer_pbr.html hardcoded-fallback URL updated to the new mount name

Iframe URLs auto-detect the mount from import.meta.url, so the folder rename
and mount-name change are transparent at runtime.
2026-08-12 17:37:09 +02:00

384 lines
10 KiB
JavaScript

/**
* SettingsPanel - Configurable settings modal for VTK viewers
*
* Generates and manages a settings panel with configurable fields.
* Different viewers can specify which settings to show.
*/
/**
* Available setting types
*/
export const SettingTypes = {
NUMBER: 'number',
COLOR: 'color',
SELECT: 'select',
CHECKBOX: 'checkbox'
};
/**
* Default settings configuration
*/
const DEFAULT_SETTINGS_CONFIG = [
{
id: 'pointSize',
label: 'Point Size',
type: SettingTypes.NUMBER,
min: 1,
max: 20,
step: 1,
default: 5
},
{
id: 'meshColor',
label: 'Mesh Color',
type: SettingTypes.COLOR,
default: '#ffffff'
},
{
id: 'edgeWidth',
label: 'Edge Width',
type: SettingTypes.NUMBER,
min: 1,
max: 10,
step: 1,
default: 1
},
{
id: 'representation',
label: 'Render Mode',
type: SettingTypes.SELECT,
options: [
{ value: '2', label: 'Surface' },
{ value: '1', label: 'Wireframe' },
{ value: '0', label: 'Points' }
],
default: '2'
},
{
id: 'parallelProjection',
label: 'Parallel Projection',
type: SettingTypes.CHECKBOX,
default: false
},
{
id: 'showAxisIndicator',
label: 'Show Axis Indicator',
type: SettingTypes.CHECKBOX,
default: false
}
];
/**
* Colormap range settings (optional, for field visualization)
*/
const COLORMAP_SETTINGS_CONFIG = [
{
id: 'autoColorRange',
label: 'Auto Range',
type: SettingTypes.CHECKBOX,
default: true,
section: 'Colormap Range'
},
{
id: 'colormapMin',
label: 'Min',
type: SettingTypes.NUMBER,
step: 'any',
placeholder: 'Auto',
default: '',
section: 'Colormap Range'
},
{
id: 'colormapMax',
label: 'Max',
type: SettingTypes.NUMBER,
step: 'any',
placeholder: 'Auto',
default: '',
section: 'Colormap Range'
}
];
export class SettingsPanel {
/**
* Create a SettingsPanel
* @param {Object} options - Configuration options
* @param {HTMLElement} options.container - Container element for the panel
* @param {Function} options.onApply - Callback when settings are applied
* @param {string[]} options.enabledSettings - Array of setting IDs to show (null = all)
* @param {boolean} options.includeColormapSettings - Whether to include colormap range settings
*/
constructor(options = {}) {
this.container = options.container || document.body;
this.onApply = options.onApply || (() => {});
this.enabledSettings = options.enabledSettings || null;
this.includeColormapSettings = options.includeColormapSettings || false;
this.panelElement = null;
this.overlayElement = null;
this.currentValues = {};
// Build settings config
this.settingsConfig = this._buildSettingsConfig();
// Initialize default values
this.settingsConfig.forEach(setting => {
this.currentValues[setting.id] = setting.default;
});
}
/**
* Build the settings configuration based on enabled settings
* @private
*/
_buildSettingsConfig() {
let config = [...DEFAULT_SETTINGS_CONFIG];
if (this.includeColormapSettings) {
config = config.concat(COLORMAP_SETTINGS_CONFIG);
}
if (this.enabledSettings) {
config = config.filter(s => this.enabledSettings.includes(s.id));
}
return config;
}
/**
* Generate the HTML for the settings panel
* @returns {string} HTML string
*/
generateHTML() {
let currentSection = null;
let html = `
<div id="settingsOverlay"></div>
<div id="settingsPanel">
<h3>Appearance Settings</h3>
`;
this.settingsConfig.forEach(setting => {
// Add section header if needed
if (setting.section && setting.section !== currentSection) {
currentSection = setting.section;
html += `
<div class="setting-row setting-section">
<label class="setting-section-label">${setting.section}:</label>
</div>
`;
}
html += this._generateSettingRow(setting);
});
html += `
<div class="button-row">
<button id="applySettings">Apply</button>
<button id="cancelSettings">Cancel</button>
</div>
</div>
`;
return html;
}
/**
* Generate HTML for a single setting row
* @private
*/
_generateSettingRow(setting) {
let inputHtml = '';
switch (setting.type) {
case SettingTypes.NUMBER:
inputHtml = `
<input type="number" id="${setting.id}"
${setting.min !== undefined ? `min="${setting.min}"` : ''}
${setting.max !== undefined ? `max="${setting.max}"` : ''}
${setting.step !== undefined ? `step="${setting.step}"` : ''}
${setting.placeholder ? `placeholder="${setting.placeholder}"` : ''}
value="${setting.default}">
`;
break;
case SettingTypes.COLOR:
inputHtml = `<input type="color" id="${setting.id}" value="${setting.default}">`;
break;
case SettingTypes.SELECT:
inputHtml = `<select id="${setting.id}">`;
setting.options.forEach(opt => {
const selected = opt.value === setting.default ? 'selected' : '';
inputHtml += `<option value="${opt.value}" ${selected}>${opt.label}</option>`;
});
inputHtml += '</select>';
break;
case SettingTypes.CHECKBOX:
const checked = setting.default ? 'checked' : '';
return `
<div class="setting-row">
<label>
<input type="checkbox" id="${setting.id}" ${checked}> ${setting.label}
</label>
</div>
`;
}
return `
<div class="setting-row">
<label>${setting.label}:</label>
${inputHtml}
</div>
`;
}
/**
* Render the panel into the container
*/
render() {
// Create wrapper if needed
const wrapper = document.createElement('div');
wrapper.innerHTML = this.generateHTML();
// Find or create panel elements in container
this.overlayElement = wrapper.querySelector('#settingsOverlay');
this.panelElement = wrapper.querySelector('#settingsPanel');
// Append to container
this.container.appendChild(this.overlayElement);
this.container.appendChild(this.panelElement);
// Setup event listeners
this._setupEventListeners();
}
/**
* Attach to existing panel elements in the DOM
* @param {HTMLElement} panel - The settings panel element
* @param {HTMLElement} overlay - The overlay element
*/
attachToExisting(panel, overlay) {
this.panelElement = panel;
this.overlayElement = overlay;
this._setupEventListeners();
}
/**
* Setup event listeners
* @private
*/
_setupEventListeners() {
// Apply button
const applyBtn = this.panelElement.querySelector('#applySettings');
if (applyBtn) {
applyBtn.addEventListener('click', () => this._handleApply());
}
// Cancel button
const cancelBtn = this.panelElement.querySelector('#cancelSettings');
if (cancelBtn) {
cancelBtn.addEventListener('click', () => this.close());
}
// Overlay click
if (this.overlayElement) {
this.overlayElement.addEventListener('click', () => this.close());
}
}
/**
* Handle apply button click
* @private
*/
_handleApply() {
const values = this.getValues();
this.currentValues = { ...values };
this.onApply(values);
this.close();
}
/**
* Open the settings panel
*/
open() {
// Update inputs with current values
this.setValues(this.currentValues);
if (this.panelElement) {
this.panelElement.classList.add('visible');
}
if (this.overlayElement) {
this.overlayElement.classList.add('visible');
}
}
/**
* Close the settings panel
*/
close() {
if (this.panelElement) {
this.panelElement.classList.remove('visible');
}
if (this.overlayElement) {
this.overlayElement.classList.remove('visible');
}
}
/**
* Get current values from all inputs
* @returns {Object} Object with setting id -> value pairs
*/
getValues() {
const values = {};
this.settingsConfig.forEach(setting => {
const element = document.getElementById(setting.id);
if (!element) return;
if (setting.type === SettingTypes.CHECKBOX) {
values[setting.id] = element.checked;
} else {
values[setting.id] = element.value;
}
});
return values;
}
/**
* Set values on all inputs
* @param {Object} values - Object with setting id -> value pairs
*/
setValues(values) {
this.settingsConfig.forEach(setting => {
const element = document.getElementById(setting.id);
if (!element || values[setting.id] === undefined) return;
if (setting.type === SettingTypes.CHECKBOX) {
element.checked = values[setting.id];
} else {
element.value = values[setting.id];
}
});
}
/**
* Update stored current values
* @param {Object} values - Partial values to update
*/
updateCurrentValues(values) {
this.currentValues = { ...this.currentValues, ...values };
}
/**
* Get current stored values
* @returns {Object} Current values
*/
getCurrentValues() {
return { ...this.currentValues };
}
}
export default SettingsPanel;