[pve-devel] [PATCH manager v6 05/11] ui: add form/TagColorGrid
Dominik Csapak
d.csapak at proxmox.com
Tue Apr 12 15:34:17 CEST 2022
this provides a basic grid to edit a list of tag color overrides.
We'll use this for editing the datacenter.cfg overrides and the
browser storage overrides.
Signed-off-by: Dominik Csapak <d.csapak at proxmox.com>
---
www/css/ext6-pve.css | 5 +
www/manager6/Makefile | 1 +
www/manager6/Utils.js | 2 +
www/manager6/form/TagColorGrid.js | 355 ++++++++++++++++++++++++++++++
4 files changed, 363 insertions(+)
create mode 100644 www/manager6/form/TagColorGrid.js
diff --git a/www/css/ext6-pve.css b/www/css/ext6-pve.css
index dadb84a9..f7d0c420 100644
--- a/www/css/ext6-pve.css
+++ b/www/css/ext6-pve.css
@@ -651,3 +651,8 @@ table.osds td:first-of-type {
background-color: rgb(245, 245, 245);
color: #000;
}
+
+.x-pveColorPicker-default-cell > .x-grid-cell-inner {
+ padding-top: 0px;
+ padding-bottom: 0px;
+}
diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index d488c3a8..8d14f684 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -73,6 +73,7 @@ JSSRC= \
form/VNCKeyboardSelector.js \
form/ViewSelector.js \
form/iScsiProviderSelector.js \
+ form/TagColorGrid.js \
grid/BackupView.js \
grid/FirewallAliases.js \
grid/FirewallOptions.js \
diff --git a/www/manager6/Utils.js b/www/manager6/Utils.js
index ff86f16b..63687d08 100644
--- a/www/manager6/Utils.js
+++ b/www/manager6/Utils.js
@@ -1861,6 +1861,8 @@ Ext.define('PVE.Utils', {
Ext.ComponentQuery.query('pveResourceTree')[0].setUserCls(`proxmox-tags-${style}`);
},
+
+ tagCharRegex: /^[a-z0-9+_.-]$/i,
},
singleton: true,
diff --git a/www/manager6/form/TagColorGrid.js b/www/manager6/form/TagColorGrid.js
new file mode 100644
index 00000000..f2c248b3
--- /dev/null
+++ b/www/manager6/form/TagColorGrid.js
@@ -0,0 +1,355 @@
+Ext.define('PVE.form.ColorPicker', {
+ extend: 'Ext.form.FieldContainer',
+ alias: 'widget.pveColorPicker',
+
+ defaultBindProperty: 'value',
+
+ config: {
+ value: null,
+ },
+
+ height: 24,
+
+ layout: {
+ type: 'hbox',
+ align: 'stretch',
+ },
+
+ getValue: function() {
+ return this.realvalue.slice(1);
+ },
+
+ setValue: function(value) {
+ let me = this;
+ me.setColor(value);
+ if (value && value.length === 6) {
+ me.picker.value = value[0] !== '#' ? `#${value}` : value;
+ }
+ },
+
+ setColor: function(value) {
+ let me = this;
+ let oldValue = me.realvalue;
+ me.realvalue = value;
+ let color = value.length === 6 ? `#${value}` : undefined;
+ me.down('#picker').setStyle('background-color', color);
+ me.down('#text').setValue(value ?? "");
+ me.fireEvent('change', me, me.realvalue, oldValue);
+ },
+
+ initComponent: function() {
+ let me = this;
+ me.picker = document.createElement('input');
+ me.picker.type = 'color';
+ me.picker.style = `opacity: 0; border: 0px; width: 100%; height: ${me.height}px`;
+ me.picker.value = `${me.value}`;
+
+ me.items = [
+ {
+ xtype: 'textfield',
+ itemId: 'text',
+ minLength: !me.allowBlank ? 6 : undefined,
+ maxLength: 6,
+ enforceMaxLength: true,
+ allowBlank: me.allowBlank,
+ emptyText: me.allowBlank ? gettext('Automatic') : undefined,
+ maskRe: /[a-f0-9]/i,
+ regex: /^[a-f0-9]{6}$/i,
+ flex: 1,
+ listeners: {
+ change: function(field, value) {
+ me.setValue(value);
+ },
+ },
+ },
+ {
+ xtype: 'box',
+ style: {
+ 'margin-left': '1px',
+ border: '1px solid #cfcfcf',
+ },
+ itemId: 'picker',
+ width: 24,
+ contentEl: me.picker,
+ },
+ ];
+
+ me.callParent();
+ me.picker.oninput = function() {
+ me.setColor(me.picker.value.slice(1));
+ };
+ },
+});
+
+Ext.define('PVE.form.TagColorGrid', {
+ extend: 'Ext.grid.Panel',
+ alias: 'widget.pveTagColorGrid',
+
+ mixins: [
+ 'Ext.form.field.Field',
+ ],
+
+ allowBlank: true,
+ selectAll: false,
+ isFormField: true,
+ deleteEmpty: false,
+ selModel: 'checkboxmodel',
+
+ config: {
+ deleteEmpty: false,
+ },
+
+ emptyText: gettext('No Overrides'),
+ viewConfig: {
+ deferEmptyText: false,
+ },
+
+ setValue: function(value) {
+ let me = this;
+ if (!value) {
+ me.getStore().removeAll();
+ me.checkChange();
+ return me;
+ }
+ let entries = (value.split(/[;, ]/) || []).map((entry) => {
+ let [tag, color] = entry.split(/[=]/);
+ let bg = color;
+ let fg = "";
+ if (color.length > 6) {
+ [bg, fg] = color.split(/[:]/);
+ }
+ return {
+ tag,
+ color: bg,
+ text: fg,
+ };
+ });
+ me.getStore().setData(entries);
+ me.checkChange();
+ return me;
+ },
+
+ getValue: function() {
+ let me = this;
+ let values = [];
+ me.getStore().each((rec) => {
+ if (rec.data.tag) {
+ let val = `${rec.data.tag}=${rec.data.color}`;
+ if (rec.data.text) {
+ val += `:${rec.data.text}`;
+ }
+ values.push(val);
+ }
+ });
+ return values.join(',');
+ },
+
+ getErrors: function(value) {
+ let me = this;
+ let emptyTag = false;
+ let notValidColor = false;
+ let colorRegex = new RegExp(/^[0-9a-f]{6}$/i);
+ me.getStore().each((rec) => {
+ if (!rec.data.tag) {
+ emptyTag = true;
+ }
+ if (!rec.data.color?.match(colorRegex)) {
+ notValidColor = true;
+ }
+ if (rec.data.text && !rec.data.text?.match(colorRegex)) {
+ notValidColor = true;
+ }
+ });
+ let errors = [];
+ if (emptyTag) {
+ errors.push(gettext('Tag must not be empty.'));
+ }
+ if (notValidColor) {
+ errors.push(gettext('Not a valid color.'));
+ }
+ return errors;
+ },
+
+ // override framework function to implement deleteEmpty behaviour
+ getSubmitData: function() {
+ let me = this,
+ data = null,
+ val;
+ if (!me.disabled && me.submitValue) {
+ val = me.getValue();
+ if (val !== null && val !== '') {
+ data = {};
+ data[me.getName()] = val;
+ } else if (me.getDeleteEmpty()) {
+ data = {};
+ data.delete = me.getName();
+ }
+ }
+ return data;
+ },
+
+
+ controller: {
+ xclass: 'Ext.app.ViewController',
+
+ addLine: function() {
+ let me = this;
+ me.getView().getStore().add({
+ tag: '',
+ color: '',
+ text: '',
+ });
+ },
+
+ removeSelection: function() {
+ let me = this;
+ let view = me.getView();
+ let selection = view.getSelection();
+ if (selection === undefined) {
+ return;
+ }
+
+ selection.forEach((sel) => {
+ view.getStore().remove(sel);
+ });
+ view.checkChange();
+ },
+
+ tagChange: function(field, newValue, oldValue) {
+ let me = this;
+ let rec = field.getWidgetRecord();
+ if (!rec) {
+ return;
+ }
+ if (newValue && newValue !== oldValue) {
+ let newrgb = Proxmox.Utils.stringToRGB(newValue);
+ let newvalue = Proxmox.Utils.rgbToHex(newrgb);
+ if (!rec.get('color')) {
+ rec.set('color', newvalue);
+ } else if (oldValue) {
+ let oldrgb = Proxmox.Utils.stringToRGB(oldValue);
+ let oldvalue = Proxmox.Utils.rgbToHex(oldrgb);
+ if (rec.get('color') === oldvalue) {
+ rec.set('color', newvalue);
+ }
+ }
+ }
+ me.fieldChange(field, newValue, oldValue);
+ },
+
+ backgroundChange: function(field, newValue, oldValue) {
+ let me = this;
+ let rec = field.getWidgetRecord();
+ if (!rec) {
+ return;
+ }
+ if (newValue && newValue !== oldValue) {
+ let newrgb = Proxmox.Utils.hexToRGB(newValue);
+ let newcls = Proxmox.Utils.getTextContrastClass(newrgb);
+ let hexvalue = newcls === 'dark' ? '000000' : 'FFFFFF';
+ if (!rec.get('text')) {
+ rec.set('text', hexvalue);
+ } else if (oldValue) {
+ let oldrgb = Proxmox.Utils.hexToRGB(oldValue);
+ let oldcls = Proxmox.Utils.getTextContrastClass(oldrgb);
+ let oldvalue = oldcls === 'dark' ? '000000' : 'FFFFFF';
+ if (rec.get('text') === oldvalue) {
+ rec.set('text', hexvalue);
+ }
+ }
+ }
+ me.fieldChange(field, newValue, oldValue);
+ },
+
+ fieldChange: function(field, newValue, oldValue) {
+ let me = this;
+ let view = me.getView();
+ let rec = field.getWidgetRecord();
+ if (!rec) {
+ return;
+ }
+ let column = field.getWidgetColumn();
+ rec.set(column.dataIndex, newValue);
+ view.checkChange();
+ },
+ },
+
+ tbar: [
+ {
+ text: gettext('Add'),
+ handler: 'addLine',
+ },
+ {
+ xtype: 'proxmoxButton',
+ text: gettext('Remove'),
+ handler: 'removeSelection',
+ disabled: true,
+ },
+ ],
+
+ columns: [
+ {
+ header: 'Tag',
+ dataIndex: 'tag',
+ xtype: 'widgetcolumn',
+ onWidgetAttach: function(col, widget, rec) {
+ widget.getStore().setData(PVE.Utils.tagList.map(v => ({ tag: v })));
+ },
+ widget: {
+ xtype: 'combobox',
+ isFormField: false,
+ maskRe: PVE.Utils.tagCharRegex,
+ allowBlank: false,
+ queryMode: 'local',
+ displayField: 'tag',
+ valueField: 'tag',
+ store: {},
+ listeners: {
+ change: 'tagChange',
+ },
+ },
+ flex: 1,
+ },
+ {
+ header: gettext('Background'),
+ xtype: 'widgetcolumn',
+ flex: 1,
+ dataIndex: 'color',
+ widget: {
+ xtype: 'pveColorPicker',
+ isFormField: false,
+ listeners: {
+ change: 'backgroundChange',
+ },
+ },
+ },
+ {
+ header: gettext('Text'),
+ xtype: 'widgetcolumn',
+ flex: 1,
+ dataIndex: 'text',
+ widget: {
+ xtype: 'pveColorPicker',
+ allowBlank: true,
+ isFormField: false,
+ listeners: {
+ change: 'fieldChange',
+ },
+ },
+ },
+ ],
+
+ store: {
+ listeners: {
+ update: function() {
+ this.commitChanges();
+ },
+ },
+ },
+
+ initComponent: function() {
+ let me = this;
+ me.callParent();
+ me.initField();
+ },
+});
--
2.30.2
More information about the pve-devel
mailing list