]> git.proxmox.com Git - pve-manager.git/blob - www/manager6/form/TagColorGrid.js
ui: vm selector: gracefully handle undefined/null in setValue function
[pve-manager.git] / www / manager6 / form / TagColorGrid.js
1 Ext.define('PVE.form.ColorPicker', {
2 extend: 'Ext.form.FieldContainer',
3 alias: 'widget.pveColorPicker',
4
5 defaultBindProperty: 'value',
6
7 config: {
8 value: null,
9 },
10
11 height: 24,
12
13 layout: {
14 type: 'hbox',
15 align: 'stretch',
16 },
17
18 getValue: function() {
19 return this.realvalue.slice(1);
20 },
21
22 setValue: function(value) {
23 let me = this;
24 me.setColor(value);
25 if (value && value.length === 6) {
26 me.picker.value = value[0] !== '#' ? `#${value}` : value;
27 }
28 },
29
30 setColor: function(value) {
31 let me = this;
32 let oldValue = me.realvalue;
33 me.realvalue = value;
34 let color = value.length === 6 ? `#${value}` : undefined;
35 me.down('#picker').setStyle('background-color', color);
36 me.down('#text').setValue(value ?? "");
37 me.fireEvent('change', me, me.realvalue, oldValue);
38 },
39
40 initComponent: function() {
41 let me = this;
42 me.picker = document.createElement('input');
43 me.picker.type = 'color';
44 me.picker.style = `opacity: 0; border: 0px; width: 100%; height: ${me.height}px`;
45 me.picker.value = `${me.value}`;
46
47 me.items = [
48 {
49 xtype: 'textfield',
50 itemId: 'text',
51 minLength: !me.allowBlank ? 6 : undefined,
52 maxLength: 6,
53 enforceMaxLength: true,
54 allowBlank: me.allowBlank,
55 emptyText: me.allowBlank ? gettext('Automatic') : undefined,
56 maskRe: /[a-f0-9]/i,
57 regex: /^[a-f0-9]{6}$/i,
58 flex: 1,
59 listeners: {
60 change: function(field, value) {
61 me.setValue(value);
62 },
63 },
64 },
65 {
66 xtype: 'box',
67 style: {
68 'margin-left': '1px',
69 border: '1px solid #cfcfcf',
70 },
71 itemId: 'picker',
72 width: 24,
73 contentEl: me.picker,
74 },
75 ];
76
77 me.callParent();
78 me.picker.oninput = function() {
79 me.setColor(me.picker.value.slice(1));
80 };
81 },
82 });
83
84 Ext.define('PVE.form.TagColorGrid', {
85 extend: 'Ext.grid.Panel',
86 alias: 'widget.pveTagColorGrid',
87
88 mixins: [
89 'Ext.form.field.Field',
90 ],
91
92 allowBlank: true,
93 selectAll: false,
94 isFormField: true,
95 deleteEmpty: false,
96 selModel: 'checkboxmodel',
97
98 config: {
99 deleteEmpty: false,
100 },
101
102 emptyText: gettext('No Overrides'),
103 viewConfig: {
104 deferEmptyText: false,
105 },
106
107 setValue: function(value) {
108 let me = this;
109 let colors;
110 if (Ext.isObject(value)) {
111 colors = value.colors;
112 } else {
113 colors = value;
114 }
115 if (!colors) {
116 me.getStore().removeAll();
117 me.checkChange();
118 return me;
119 }
120 let entries = (colors.split(';') || []).map((entry) => {
121 let [tag, bg, fg] = entry.split(':');
122 fg = fg || "";
123 return {
124 tag,
125 color: bg,
126 text: fg,
127 };
128 });
129 me.getStore().setData(entries);
130 me.checkChange();
131 return me;
132 },
133
134 getValue: function() {
135 let me = this;
136 let values = [];
137 me.getStore().each((rec) => {
138 if (rec.data.tag) {
139 let val = `${rec.data.tag}:${rec.data.color}`;
140 if (rec.data.text) {
141 val += `:${rec.data.text}`;
142 }
143 values.push(val);
144 }
145 });
146 return values.join(';');
147 },
148
149 getErrors: function(value) {
150 let me = this;
151 let emptyTag = false;
152 let notValidColor = false;
153 let colorRegex = new RegExp(/^[0-9a-f]{6}$/i);
154 me.getStore().each((rec) => {
155 if (!rec.data.tag) {
156 emptyTag = true;
157 }
158 if (!rec.data.color?.match(colorRegex)) {
159 notValidColor = true;
160 }
161 if (rec.data.text && !rec.data.text?.match(colorRegex)) {
162 notValidColor = true;
163 }
164 });
165 let errors = [];
166 if (emptyTag) {
167 errors.push(gettext('Tag must not be empty.'));
168 }
169 if (notValidColor) {
170 errors.push(gettext('Not a valid color.'));
171 }
172 return errors;
173 },
174
175 // override framework function to implement deleteEmpty behaviour
176 getSubmitData: function() {
177 let me = this,
178 data = null,
179 val;
180 if (!me.disabled && me.submitValue) {
181 val = me.getValue();
182 if (val !== null && val !== '') {
183 data = {};
184 data[me.getName()] = val;
185 } else if (me.getDeleteEmpty()) {
186 data = {};
187 data.delete = me.getName();
188 }
189 }
190 return data;
191 },
192
193
194 controller: {
195 xclass: 'Ext.app.ViewController',
196
197 addLine: function() {
198 let me = this;
199 me.getView().getStore().add({
200 tag: '',
201 color: '',
202 text: '',
203 });
204 },
205
206 removeSelection: function() {
207 let me = this;
208 let view = me.getView();
209 let selection = view.getSelection();
210 if (selection === undefined) {
211 return;
212 }
213
214 selection.forEach((sel) => {
215 view.getStore().remove(sel);
216 });
217 view.checkChange();
218 },
219
220 tagChange: function(field, newValue, oldValue) {
221 let me = this;
222 let rec = field.getWidgetRecord();
223 if (!rec) {
224 return;
225 }
226 if (newValue && newValue !== oldValue) {
227 let newrgb = Proxmox.Utils.stringToRGB(newValue);
228 let newvalue = Proxmox.Utils.rgbToHex(newrgb);
229 if (!rec.get('color')) {
230 rec.set('color', newvalue);
231 } else if (oldValue) {
232 let oldrgb = Proxmox.Utils.stringToRGB(oldValue);
233 let oldvalue = Proxmox.Utils.rgbToHex(oldrgb);
234 if (rec.get('color') === oldvalue) {
235 rec.set('color', newvalue);
236 }
237 }
238 }
239 me.fieldChange(field, newValue, oldValue);
240 },
241
242 backgroundChange: function(field, newValue, oldValue) {
243 let me = this;
244 let rec = field.getWidgetRecord();
245 if (!rec) {
246 return;
247 }
248 if (newValue && newValue !== oldValue) {
249 let newrgb = Proxmox.Utils.hexToRGB(newValue);
250 let newcls = Proxmox.Utils.getTextContrastClass(newrgb);
251 let hexvalue = newcls === 'dark' ? '000000' : 'FFFFFF';
252 if (!rec.get('text')) {
253 rec.set('text', hexvalue);
254 } else if (oldValue) {
255 let oldrgb = Proxmox.Utils.hexToRGB(oldValue);
256 let oldcls = Proxmox.Utils.getTextContrastClass(oldrgb);
257 let oldvalue = oldcls === 'dark' ? '000000' : 'FFFFFF';
258 if (rec.get('text') === oldvalue) {
259 rec.set('text', hexvalue);
260 }
261 }
262 }
263 me.fieldChange(field, newValue, oldValue);
264 },
265
266 fieldChange: function(field, newValue, oldValue) {
267 let me = this;
268 let view = me.getView();
269 let rec = field.getWidgetRecord();
270 if (!rec) {
271 return;
272 }
273 let column = field.getWidgetColumn();
274 rec.set(column.dataIndex, newValue);
275 view.checkChange();
276 },
277 },
278
279 tbar: [
280 {
281 text: gettext('Add'),
282 handler: 'addLine',
283 },
284 {
285 xtype: 'proxmoxButton',
286 text: gettext('Remove'),
287 handler: 'removeSelection',
288 disabled: true,
289 },
290 ],
291
292 columns: [
293 {
294 header: 'Tag',
295 dataIndex: 'tag',
296 xtype: 'widgetcolumn',
297 onWidgetAttach: function(col, widget, rec) {
298 widget.getStore().setData(PVE.UIOptions.tagList.map(v => ({ tag: v })));
299 },
300 widget: {
301 xtype: 'combobox',
302 isFormField: false,
303 maskRe: PVE.Utils.tagCharRegex,
304 allowBlank: false,
305 queryMode: 'local',
306 displayField: 'tag',
307 valueField: 'tag',
308 store: {},
309 listeners: {
310 change: 'tagChange',
311 },
312 },
313 flex: 1,
314 },
315 {
316 header: gettext('Background'),
317 xtype: 'widgetcolumn',
318 flex: 1,
319 dataIndex: 'color',
320 widget: {
321 xtype: 'pveColorPicker',
322 isFormField: false,
323 listeners: {
324 change: 'backgroundChange',
325 },
326 },
327 },
328 {
329 header: gettext('Text'),
330 xtype: 'widgetcolumn',
331 flex: 1,
332 dataIndex: 'text',
333 widget: {
334 xtype: 'pveColorPicker',
335 allowBlank: true,
336 isFormField: false,
337 listeners: {
338 change: 'fieldChange',
339 },
340 },
341 },
342 ],
343
344 store: {
345 listeners: {
346 update: function() {
347 this.commitChanges();
348 },
349 },
350 },
351
352 initComponent: function() {
353 let me = this;
354 me.callParent();
355 me.initField();
356 },
357 });