feat: color picker
This commit is contained in:
@@ -5,6 +5,28 @@ class NodeDetailsComponent extends Component {
|
||||
this.suppressLoadingUI = false;
|
||||
}
|
||||
|
||||
// Helper functions for color conversion
|
||||
rgbIntToHex(rgbInt) {
|
||||
if (!rgbInt && rgbInt !== 0) return '#000000';
|
||||
const num = parseInt(rgbInt);
|
||||
if (isNaN(num)) return '#000000';
|
||||
const r = (num >> 16) & 255;
|
||||
const g = (num >> 8) & 255;
|
||||
const b = num & 255;
|
||||
return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
|
||||
}
|
||||
|
||||
hexToRgbInt(hex) {
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||
if (!result) return 0;
|
||||
const r = parseInt(result[1], 16);
|
||||
const g = parseInt(result[2], 16);
|
||||
const b = parseInt(result[3], 16);
|
||||
return (r << 16) + (g << 8) + b;
|
||||
}
|
||||
|
||||
|
||||
|
||||
setupViewModelListeners() {
|
||||
this.subscribeToProperty('nodeStatus', this.handleNodeStatusUpdate.bind(this));
|
||||
this.subscribeToProperty('tasks', this.handleTasksUpdate.bind(this));
|
||||
@@ -421,7 +443,31 @@ class NodeDetailsComponent extends Component {
|
||||
<span class="param-name">${p.name}${p.required ? ' *' : ''}</span>
|
||||
${ (Array.isArray(p.values) && p.values.length > 1)
|
||||
? `<select id="${formId}-field-${pidx}" data-param-name="${p.name}" data-param-location="${p.location || 'body'}" data-param-type="${p.type || 'string'}" data-param-required="${p.required ? '1' : '0'}" class="param-input">${p.values.map(v => `<option value="${v}">${v}</option>`).join('')}</select>`
|
||||
: `<input id="${formId}-field-${pidx}" data-param-name="${p.name}" data-param-location="${p.location || 'body'}" data-param-type="${p.type || 'string'}" data-param-required="${p.required ? '1' : '0'}" class="param-input" type="text" placeholder="${p.location || 'body'} • ${p.type || 'string'}" value="${p.default !== undefined ? p.default : (Array.isArray(p.values) && p.values.length === 1) ? p.values[0] : ''}">`
|
||||
: (p.type === 'color')
|
||||
? `<div class="color-input-container">
|
||||
<input id="${formId}-field-${pidx}"
|
||||
data-param-name="${p.name}"
|
||||
data-param-location="${p.location || 'body'}"
|
||||
data-param-type="${p.type || 'string'}"
|
||||
data-param-required="${p.required ? '1' : '0'}"
|
||||
class="param-input color-picker"
|
||||
type="color"
|
||||
value="${this.rgbIntToHex(p.default !== undefined ? p.default : (Array.isArray(p.values) && p.values.length === 1) ? p.values[0] : 0)}">
|
||||
<input type="text"
|
||||
class="color-rgb-display"
|
||||
readonly
|
||||
value="${p.default !== undefined ? p.default : (Array.isArray(p.values) && p.values.length === 1) ? p.values[0] : 0}"
|
||||
style="margin-left: 5px; width: 80px; font-size: 0.8em;">
|
||||
</div>`
|
||||
: `<input id="${formId}-field-${pidx}"
|
||||
data-param-name="${p.name}"
|
||||
data-param-location="${p.location || 'body'}"
|
||||
data-param-type="${p.type || 'string'}"
|
||||
data-param-required="${p.required ? '1' : '0'}"
|
||||
class="param-input"
|
||||
type="text"
|
||||
placeholder="${p.location || 'body'} • ${p.type || 'string'}"
|
||||
value="${p.default !== undefined ? p.default : (Array.isArray(p.values) && p.values.length === 1) ? p.values[0] : ''}">`
|
||||
}
|
||||
</label>
|
||||
`).join('')}</div>`
|
||||
@@ -486,13 +532,21 @@ class NodeDetailsComponent extends Component {
|
||||
if (!formEl || !resultEl) return;
|
||||
|
||||
const inputs = Array.from(formEl.querySelectorAll('.param-input'));
|
||||
const params = inputs.map(input => ({
|
||||
name: input.dataset.paramName,
|
||||
location: input.dataset.paramLocation || 'body',
|
||||
type: input.dataset.paramType || 'string',
|
||||
required: input.dataset.paramRequired === '1',
|
||||
value: input.value
|
||||
}));
|
||||
const params = inputs.map(input => {
|
||||
let value = input.value;
|
||||
// For color type, convert hex to RGB integer
|
||||
if (input.dataset.paramType === 'color' && input.type === 'color') {
|
||||
const rgbDisplay = input.parentElement.querySelector('.color-rgb-display');
|
||||
value = rgbDisplay ? rgbDisplay.value : this.hexToRgbInt(input.value);
|
||||
}
|
||||
return {
|
||||
name: input.dataset.paramName,
|
||||
location: input.dataset.paramLocation || 'body',
|
||||
type: input.dataset.paramType || 'string',
|
||||
required: input.dataset.paramRequired === '1',
|
||||
value: value
|
||||
};
|
||||
});
|
||||
|
||||
// Required validation
|
||||
const missing = params.filter(p => p.required && (!p.value || String(p.value).trim() === ''));
|
||||
@@ -528,6 +582,32 @@ class NodeDetailsComponent extends Component {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Add event listeners for color pickers
|
||||
const colorPickers = this.findAllElements('.color-picker');
|
||||
colorPickers.forEach(colorInput => {
|
||||
this.addEventListener(colorInput, 'input', (e) => {
|
||||
const rgbDisplay = colorInput.parentElement.querySelector('.color-rgb-display');
|
||||
if (rgbDisplay) {
|
||||
const hexValue = e.target.value;
|
||||
const rgbInt = this.hexToRgbInt(hexValue);
|
||||
rgbDisplay.value = rgbInt;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Update color picker when RGB display is manually changed (if we make it editable later)
|
||||
const rgbDisplays = this.findAllElements('.color-rgb-display');
|
||||
rgbDisplays.forEach(rgbInput => {
|
||||
this.addEventListener(rgbInput, 'input', (e) => {
|
||||
const colorPicker = rgbInput.parentElement.querySelector('.color-picker');
|
||||
if (colorPicker) {
|
||||
const rgbInt = parseInt(e.target.value) || 0;
|
||||
const hexValue = this.rgbIntToHex(rgbInt);
|
||||
colorPicker.value = hexValue;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
escapeHtml(str) {
|
||||
|
||||
Reference in New Issue
Block a user