Merge pull request 'feat: color picker' (#7) from feature/color-picker into main
Reviewed-on: #7
This commit is contained in:
@@ -5,6 +5,28 @@ class NodeDetailsComponent extends Component {
|
|||||||
this.suppressLoadingUI = false;
|
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() {
|
setupViewModelListeners() {
|
||||||
this.subscribeToProperty('nodeStatus', this.handleNodeStatusUpdate.bind(this));
|
this.subscribeToProperty('nodeStatus', this.handleNodeStatusUpdate.bind(this));
|
||||||
this.subscribeToProperty('tasks', this.handleTasksUpdate.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>
|
<span class="param-name">${p.name}${p.required ? ' *' : ''}</span>
|
||||||
${ (Array.isArray(p.values) && p.values.length > 1)
|
${ (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>`
|
? `<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>
|
</label>
|
||||||
`).join('')}</div>`
|
`).join('')}</div>`
|
||||||
@@ -486,13 +532,21 @@ class NodeDetailsComponent extends Component {
|
|||||||
if (!formEl || !resultEl) return;
|
if (!formEl || !resultEl) return;
|
||||||
|
|
||||||
const inputs = Array.from(formEl.querySelectorAll('.param-input'));
|
const inputs = Array.from(formEl.querySelectorAll('.param-input'));
|
||||||
const params = inputs.map(input => ({
|
const params = inputs.map(input => {
|
||||||
name: input.dataset.paramName,
|
let value = input.value;
|
||||||
location: input.dataset.paramLocation || 'body',
|
// For color type, convert hex to RGB integer
|
||||||
type: input.dataset.paramType || 'string',
|
if (input.dataset.paramType === 'color' && input.type === 'color') {
|
||||||
required: input.dataset.paramRequired === '1',
|
const rgbDisplay = input.parentElement.querySelector('.color-rgb-display');
|
||||||
value: input.value
|
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
|
// Required validation
|
||||||
const missing = params.filter(p => p.required && (!p.value || String(p.value).trim() === ''));
|
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) {
|
escapeHtml(str) {
|
||||||
|
|||||||
@@ -3771,3 +3771,30 @@ html {
|
|||||||
#cluster-members-container .error br {
|
#cluster-members-container .error br {
|
||||||
display: none; /* tighten layout by avoiding forced line-breaks */
|
display: none; /* tighten layout by avoiding forced line-breaks */
|
||||||
}
|
}
|
||||||
|
/* Color picker styles */
|
||||||
|
.color-input-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-picker {
|
||||||
|
width: 50px !important;
|
||||||
|
height: 35px !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
border-radius: 6px !important;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-rgb-display {
|
||||||
|
background: rgba(0, 0, 0, 0.3) !important;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2) !important;
|
||||||
|
color: rgba(255, 255, 255, 0.9) !important;
|
||||||
|
text-align: center;
|
||||||
|
font-family: 'Courier New', monospace !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-rgb-display:focus {
|
||||||
|
border-color: rgba(139, 92, 246, 0.5) !important;
|
||||||
|
background: rgba(139, 92, 246, 0.08) !important;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user