* [pve-devel] [PATCH manager 1/5] ui: rework inline tag editing
2022-11-17 14:56 [pve-devel] [PATCH manager 0/5] tags ui follow up patches Dominik Csapak
@ 2022-11-17 14:56 ` Dominik Csapak
2022-11-17 14:56 ` [pve-devel] [PATCH manager 2/5] ui: tags: make sorting more natural Dominik Csapak
` (4 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Dominik Csapak @ 2022-11-17 14:56 UTC (permalink / raw)
To: pve-devel
things that changed:
* removed 'add Tag' inline button with proper button that adds
empty tag
* don't require to confirm each tag, simply update the color "live"
* set a minimum width for the editing box, so that it's easier to click
* replace cancel/finish icons with proper buttons
* fix tagCharRegex for multichar text (necessary for paste)
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
www/css/ext6-pve.css | 28 +++-----
www/manager6/Utils.js | 2 +-
www/manager6/form/Tag.js | 116 +++++++++++++++-----------------
www/manager6/form/TagEdit.js | 124 ++++++++++++++++-------------------
4 files changed, 120 insertions(+), 150 deletions(-)
diff --git a/www/css/ext6-pve.css b/www/css/ext6-pve.css
index b8c713c48..a9ead5d3b 100644
--- a/www/css/ext6-pve.css
+++ b/www/css/ext6-pve.css
@@ -657,10 +657,11 @@ table.osds td:first-of-type {
padding-bottom: 0px;
}
-.pve-edit-tag > i,
-.pve-add-tag > i {
+.pve-edit-tag > i {
cursor: pointer;
font-size: 14px;
+ line-height: 15px;
+ height: 15px;
}
.pve-edit-tag > i.handle {
@@ -673,8 +674,7 @@ table.osds td:first-of-type {
padding-right: 0px;
}
-.pve-edit-tag > i.action,
-.pve-add-tag > i.action {
+.pve-edit-tag > i.action {
padding-left: 5px;
}
@@ -682,26 +682,18 @@ table.osds td:first-of-type {
display: none;
}
-.pve-edit-tag.editable span,
-.pve-edit-tag.inEdit span,
-.pve-add-tag.editable span,
-.pve-add-tag.inEdit span {
+.pve-edit-tag.editable span {
background-color: #ffffff;
border: 1px solid #a8a8a8;
color: #000;
padding-left: 2px;
padding-right: 2px;
min-width: 2em;
-}
-
-.pve-edit-tag.inEdit span,
-.pve-add-tag.inEdit span {
- border: 1px solid #000;
-}
-
-.pve-add-tag {
- background-color: #d5d5d5 ! important;
- color: #000000 ! important;
+ display: inline-block;
+ line-height: 15px;
+ height: 15px;
+ vertical-align: top;
+ box-sizing: content-box;
}
.pve-tag-inline-button {
diff --git a/www/manager6/Utils.js b/www/manager6/Utils.js
index 8484372f2..e4b6207c6 100644
--- a/www/manager6/Utils.js
+++ b/www/manager6/Utils.js
@@ -1955,7 +1955,7 @@ Ext.define('PVE.Utils', {
return !(PVE.UIOptions?.['tag-style']?.ordering === 'config');
},
- tagCharRegex: /^[a-z0-9+_.-]$/i,
+ tagCharRegex: /^[a-z0-9+_.-]+$/i,
},
singleton: true,
diff --git a/www/manager6/form/Tag.js b/www/manager6/form/Tag.js
index 9acedb527..9da0db951 100644
--- a/www/manager6/form/Tag.js
+++ b/www/manager6/form/Tag.js
@@ -4,53 +4,44 @@ Ext.define('Proxmox.form.Tag', {
mode: 'editable',
- icons: {
- editable: 'fa fa-minus-square',
- normal: '',
- inEdit: 'fa fa-check-square',
- },
-
tag: '',
cls: 'pve-edit-tag',
tpl: [
'<i class="handle fa fa-bars"></i>',
'<span>{tag}</span>',
- '<i class="action {iconCls}"></i>',
+ '<i class="action fa fa-minus-square"></i>',
],
- // we need to do this in mousedown, because that triggers before
- // focusleave (which triggers before click)
- onMouseDown: function(event) {
- let me = this;
- if (event.target.tagName !== 'I' || event.target.classList.contains('handle')) {
- return;
- }
- switch (me.mode) {
- case 'editable':
- me.setVisible(false);
- me.setTag('');
- break;
- case 'inEdit':
- me.setTag(me.tagEl().innerHTML);
- me.setMode('editable');
- break;
- default: break;
- }
+ // contains tags not to show in the picker and not allowing to set
+ filter: [],
+
+ updateFilter: function(tags) {
+ this.filter = tags;
},
onClick: function(event) {
let me = this;
- if (event.target.tagName !== 'SPAN' || me.mode !== 'editable') {
+ if (event.target.tagName === 'I' && !event.target.classList.contains('handle')) {
+ if (me.mode === 'editable') {
+ me.destroy();
+ return;
+ }
+ } else if (event.target.tagName !== 'SPAN' || me.mode !== 'editable') {
return;
}
- me.setMode('inEdit');
+ me.selectText();
+ },
- // select text in the element
+ selectText: function(collapseToEnd) {
+ let me = this;
let tagEl = me.tagEl();
tagEl.contentEditable = true;
let range = document.createRange();
range.selectNodeContents(tagEl);
+ if (collapseToEnd) {
+ range.collapse(false);
+ }
let sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
@@ -75,8 +66,10 @@ Ext.define('Proxmox.form.Tag', {
store: [],
listeners: {
select: function(picker, rec) {
- me.setTag(rec.data.tag);
- me.setMode('editable');
+ me.tagEl().innerHTML = rec.data.tag;
+ me.setTag(rec.data.tag, true);
+ me.selectText(true);
+ me.setColor(rec.data.tag);
me.picker.hide();
},
},
@@ -94,17 +87,16 @@ Ext.define('Proxmox.form.Tag', {
setMode: function(mode) {
let me = this;
- if (me.icons[mode] === undefined) {
- throw "invalid mode";
- }
let tagEl = me.tagEl();
if (tagEl) {
- tagEl.contentEditable = mode === 'inEdit';
+ tagEl.contentEditable = mode === 'editable';
}
me.removeCls(me.mode);
me.addCls(mode);
me.mode = mode;
- me.updateData();
+ if (me.mode !== 'editable') {
+ me.picker?.hide();
+ }
},
onKeyPress: function(event) {
@@ -112,15 +104,10 @@ Ext.define('Proxmox.form.Tag', {
let key = event.browserEvent.key;
switch (key) {
case 'Enter':
- if (me.tagEl().innerHTML !== '') {
- me.setTag(me.tagEl().innerHTML);
- me.setMode('editable');
- return;
- }
break;
+ case 'ArrowLeft':
+ case 'ArrowRight':
case 'Escape':
- me.cancelEdit();
- return;
case 'Backspace':
case 'Delete':
return;
@@ -128,11 +115,13 @@ Ext.define('Proxmox.form.Tag', {
if (key.match(PVE.Utils.tagCharRegex)) {
return;
}
+ me.setTag(me.tagEl().innerHTML);
}
event.browserEvent.preventDefault();
event.browserEvent.stopPropagation();
},
+ // for pasting text
beforeInput: function(event) {
let me = this;
me.updateLayout();
@@ -153,22 +142,17 @@ Ext.define('Proxmox.form.Tag', {
value: me.tagEl().innerHTML,
anyMatch: true,
});
+ me.setTag(me.tagEl().innerHTML);
},
- cancelEdit: function(list, event) {
+ lostFocus: function(list, event) {
let me = this;
- if (me.mode === 'inEdit') {
- me.setTag(me.tag);
- me.setMode('editable');
- }
me.picker?.hide();
+ window.getSelection().removeAllRanges();
},
-
- setTag: function(tag) {
+ setColor: function(tag) {
let me = this;
- let oldtag = me.tag;
- me.tag = tag;
let rgb = PVE.Utils.tagOverrides[tag] ?? Proxmox.Utils.stringToRGB(tag);
let cls = Proxmox.Utils.getTextContrastClass(rgb);
@@ -182,21 +166,20 @@ Ext.define('Proxmox.form.Tag', {
} else {
me.setStyle('color');
}
- me.updateData();
- if (oldtag !== tag) {
- me.fireEvent('change', me, tag, oldtag);
- }
},
- updateData: function() {
+ setTag: function(tag) {
let me = this;
- if (me.destroying || me.destroyed) {
- return;
+ let oldtag = me.tag;
+ me.tag = tag;
+
+ clearTimeout(me.colorTimeout);
+ me.colorTimeout = setTimeout(() => me.setColor(tag), 200);
+
+ me.updateLayout();
+ if (oldtag !== tag) {
+ me.fireEvent('change', me, tag, oldtag);
}
- me.update({
- tag: me.tag,
- iconCls: me.icons[me.mode],
- });
},
tagEl: function() {
@@ -204,9 +187,8 @@ Ext.define('Proxmox.form.Tag', {
},
listeners: {
- mousedown: 'onMouseDown',
click: 'onClick',
- focusleave: 'cancelEdit',
+ focusleave: 'lostFocus',
keydown: 'onKeyPress',
beforeInput: 'beforeInput',
input: 'onInput',
@@ -217,7 +199,12 @@ Ext.define('Proxmox.form.Tag', {
initComponent: function() {
let me = this;
+ me.data = {
+ tag: me.tag,
+ };
+
me.setTag(me.tag);
+ me.setColor(me.tag);
me.setMode(me.mode ?? 'normal');
me.callParent();
},
@@ -227,6 +214,7 @@ Ext.define('Proxmox.form.Tag', {
if (me.picker) {
Ext.destroy(me.picker);
}
+ clearTimeout(me.colorTimeout);
me.callParent();
},
});
diff --git a/www/manager6/form/TagEdit.js b/www/manager6/form/TagEdit.js
index 6325d39df..4e3fec384 100644
--- a/www/manager6/form/TagEdit.js
+++ b/www/manager6/form/TagEdit.js
@@ -4,7 +4,7 @@ Ext.define('PVE.panel.TagEditContainer', {
layout: {
type: 'hbox',
- align: 'stretch',
+ align: 'middle',
},
controller: {
@@ -120,9 +120,6 @@ Ext.define('PVE.panel.TagEditContainer', {
let me = this;
let view = me.getView();
view.items.each((field) => {
- if (field.reference === 'addTagBtn') {
- return false;
- }
if (field.getXType() === 'pveTag') {
func(field);
}
@@ -133,6 +130,7 @@ Ext.define('PVE.panel.TagEditContainer', {
toggleEdit: function(cancel) {
let me = this;
let vm = me.getViewModel();
+ let view = me.getView();
let editMode = !vm.get('editMode');
vm.set('editMode', editMode);
@@ -150,14 +148,19 @@ Ext.define('PVE.panel.TagEditContainer', {
if (cancel) {
me.loadTags(me.oldTags, true);
} else {
+ let toRemove = [];
me.forEachTag((cmp) => {
if (cmp.isVisible() && cmp.tag) {
tags.push(cmp.tag);
+ } else {
+ toRemove.push(cmp);
}
});
+ toRemove.forEach(cmp => view.remove(cmp));
tags = tags.join(',');
if (me.oldTags !== tags) {
me.oldTags = tags;
+ me.loadTags(tags, true);
me.getView().fireEvent('change', tags);
}
}
@@ -165,60 +168,44 @@ Ext.define('PVE.panel.TagEditContainer', {
me.getView().updateLayout();
},
- addTag: function(tag) {
+ addTag: function(tag, isNew) {
let me = this;
let view = me.getView();
let vm = me.getViewModel();
- let index = view.items.indexOf(me.lookup('addTagBtn'));
- if (PVE.Utils.shouldSortTags()) {
+ let index = view.items.length - 5;
+ if (PVE.Utils.shouldSortTags() && !isNew) {
index = view.items.findIndexBy(tagField => {
- if (tagField.reference === 'addTagBtn') {
+ if (tagField.reference === 'noTagsField') {
+ return false;
+ }
+ if (tagField.xtype !== 'pveTag') {
return true;
}
return tagField.tag >= tag;
}, 1);
}
- view.insert(index, {
+ let tagField = view.insert(index, {
xtype: 'pveTag',
tag,
mode: vm.get('editMode') ? 'editable' : 'normal',
listeners: {
- change: (field, newTag) => {
- if (newTag === '') {
- view.remove(field);
- vm.set('tagCount', vm.get('tagCount') - 1);
- }
+ destroy: function() {
+ vm.set('tagCount', vm.get('tagCount') - 1);
},
},
});
- vm.set('tagCount', vm.get('tagCount') + 1);
- },
-
- addTagClick: function(event) {
- let me = this;
- if (event.target.tagName === 'SPAN') {
- me.lookup('addTagBtn').tagEl().innerHTML = '';
- me.lookup('addTagBtn').updateLayout();
+ if (isNew) {
+ tagField.selectText();
}
- },
- addTagMouseDown: function(event) {
- let me = this;
- if (event.target.tagName === 'I') {
- let tag = me.lookup('addTagBtn').tagEl().innerHTML;
- if (tag !== '') {
- me.addTag(tag, true);
- }
- }
+ vm.set('tagCount', vm.get('tagCount') + 1);
},
- addTagChange: function(field, tag) {
+ addTagClick: function(event) {
let me = this;
- if (tag !== '') {
- me.addTag(tag, true);
- }
- field.tag = '';
+ me.lookup('noTagsField').setVisible(false);
+ me.addTag('', true);
},
cancelClick: function() {
@@ -250,12 +237,7 @@ Ext.define('PVE.panel.TagEditContainer', {
formulas: {
hideNoTags: function(get) {
- return get('editMode') || get('tagCount') !== 0;
- },
- editBtnHtml: function(get) {
- let cls = get('editMode') ? 'check' : 'pencil';
- let qtip = get('editMode') ? gettext('Apply Changes') : gettext('Edit Tags');
- return `<i data-qtip="${qtip}" class="fa fa-${cls}"></i>`;
+ return get('tagCount') !== 0;
},
},
},
@@ -267,53 +249,61 @@ Ext.define('PVE.panel.TagEditContainer', {
items: [
{
xtype: 'box',
+ reference: 'noTagsField',
bind: {
hidden: '{hideNoTags}',
},
html: gettext('No Tags'),
},
{
- xtype: 'pveTag',
- reference: 'addTagBtn',
- cls: 'pve-add-tag',
- mode: 'editable',
- tag: '',
- tpl: `<span>${gettext('Add Tag')}</span><i class="action fa fa-plus-square"></i>`,
+ xtype: 'button',
+ iconCls: 'fa fa-plus',
+ tooltip: gettext('Add Tag'),
bind: {
hidden: '{!editMode}',
},
hidden: true,
- onMouseDown: Ext.emptyFn, // prevent default behaviour
- listeners: {
- click: {
- element: 'el',
- fn: 'addTagClick',
- },
- mousedown: {
- element: 'el',
- fn: 'addTagMouseDown',
- },
- change: 'addTagChange',
- },
+ margin: '0 8 0 5',
+ ui: 'default-toolbar',
+ handler: 'addTagClick',
},
{
- xtype: 'box',
- html: `<i data-qtip="${gettext('Cancel')}" class="fa fa-times"></i>`,
- cls: 'pve-tag-inline-button',
+ xtype: 'tbseparator',
+ ui: 'horizontal',
+ bind: {
+ hidden: '{!editMode}',
+ },
hidden: true,
+ },
+ {
+ xtype: 'button',
+ iconCls: 'fa fa-times',
+ tooltip: gettext('Cancel Edit'),
bind: {
hidden: '{!editMode}',
},
- listeners: {
- click: 'cancelClick',
- element: 'el',
+ hidden: true,
+ margin: '0 5 0 0',
+ ui: 'default-toolbar',
+ handler: 'cancelClick',
+ },
+ {
+ xtype: 'button',
+ iconCls: 'fa fa-check',
+ tooltip: gettext('Finish Edit'),
+ bind: {
+ hidden: '{!editMode}',
},
+ hidden: true,
+ ui: 'default-toolbar',
+ handler: 'editClick',
},
{
xtype: 'box',
cls: 'pve-tag-inline-button',
+ html: `<i data-qtip="${gettext('Edit Tags')}" class="fa fa-pencil"></i>`,
bind: {
- html: '{editBtnHtml}',
+ hidden: '{editMode}',
},
listeners: {
click: 'editClick',
--
2.30.2
^ permalink raw reply [flat|nested] 7+ messages in thread
* [pve-devel] [PATCH manager 4/5] ui: change style of ListField
2022-11-17 14:56 [pve-devel] [PATCH manager 0/5] tags ui follow up patches Dominik Csapak
` (2 preceding siblings ...)
2022-11-17 14:56 ` [pve-devel] [PATCH manager 3/5] ui: tags: hide already set tags in dropdown Dominik Csapak
@ 2022-11-17 14:56 ` Dominik Csapak
2022-11-17 14:56 ` [pve-devel] [PATCH manager 5/5] ui: tags: add preview to tag shape option Dominik Csapak
2022-11-17 17:22 ` [pve-devel] applied-series: [PATCH manager 0/5] tags ui follow up patches Thomas Lamprecht
5 siblings, 0 replies; 7+ messages in thread
From: Dominik Csapak @ 2022-11-17 14:56 UTC (permalink / raw)
To: pve-devel
and make it more like the 'traffic control' time grid in pbs
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
www/manager6/dc/RegisteredTagsEdit.js | 6 +-
www/manager6/dc/UserTagAccessEdit.js | 6 +-
www/manager6/form/ListField.js | 86 ++++++++++++++++-----------
3 files changed, 60 insertions(+), 38 deletions(-)
diff --git a/www/manager6/dc/RegisteredTagsEdit.js b/www/manager6/dc/RegisteredTagsEdit.js
index 92e076e0a..75b4f9ba3 100644
--- a/www/manager6/dc/RegisteredTagsEdit.js
+++ b/www/manager6/dc/RegisteredTagsEdit.js
@@ -52,8 +52,10 @@ Ext.define('PVE.dc.RegisteredTagsEdit', {
emptyText: gettext('No Tags defined'),
fieldTitle: gettext('Tag'),
maskRe: PVE.Utils.tagCharRegex,
- height: 200,
- scrollable: true,
+ gridConfig: {
+ height: 200,
+ scrollable: true,
+ },
listeners: {
change: 'tagChange',
},
diff --git a/www/manager6/dc/UserTagAccessEdit.js b/www/manager6/dc/UserTagAccessEdit.js
index 701d1de4f..f22ac9b3d 100644
--- a/www/manager6/dc/UserTagAccessEdit.js
+++ b/www/manager6/dc/UserTagAccessEdit.js
@@ -72,8 +72,10 @@ Ext.define('PVE.dc.UserTagAccessEdit', {
emptyText: gettext('No Tags defined'),
fieldTitle: gettext('Tag'),
maskRe: PVE.Utils.tagCharRegex,
- height: 200,
- scrollable: true,
+ gridConfig: {
+ height: 200,
+ scrollable: true,
+ },
listeners: {
change: 'tagChange',
},
diff --git a/www/manager6/form/ListField.js b/www/manager6/form/ListField.js
index faa8e168b..77e9ebfca 100644
--- a/www/manager6/form/ListField.js
+++ b/www/manager6/form/ListField.js
@@ -1,5 +1,5 @@
Ext.define('PVE.form.ListField', {
- extend: 'Ext.grid.Panel',
+ extend: 'Ext.container.Container',
alias: 'widget.pveListField',
mixins: [
@@ -16,23 +16,18 @@ Ext.define('PVE.form.ListField', {
selectAll: false,
isFormField: true,
deleteEmpty: false,
- selModel: 'checkboxmodel',
-
config: {
deleteEmpty: false,
},
- viewConfig: {
- deferEmptyText: false,
- },
-
setValue: function(list) {
let me = this;
list = Ext.isArray(list) ? list : (list ?? '').split(';');
+ let store = me.lookup('grid').getStore();
if (list.length > 0) {
- me.getStore().setData(list.map(item => ({ item })));
+ store.setData(list.map(item => ({ item })));
} else {
- me.getStore().removeAll();
+ store.removeAll();
}
me.checkChange();
return me;
@@ -41,7 +36,7 @@ Ext.define('PVE.form.ListField', {
getValue: function() {
let me = this;
let values = [];
- me.getStore().each((rec) => {
+ me.lookup('grid').getStore().each((rec) => {
if (rec.data.item) {
values.push(rec.data.item);
}
@@ -52,7 +47,7 @@ Ext.define('PVE.form.ListField', {
getErrors: function(value) {
let me = this;
let empty = false;
- me.getStore().each((rec) => {
+ me.lookup('grid').getStore().each((rec) => {
if (!rec.data.item) {
empty = true;
}
@@ -86,22 +81,23 @@ Ext.define('PVE.form.ListField', {
addLine: function() {
let me = this;
- me.getView().getStore().add({
+ me.lookup('grid').getStore().add({
item: '',
});
},
- removeSelection: function() {
+ removeSelection: function(field) {
let me = this;
let view = me.getView();
- let selection = view.getSelection();
- if (selection === undefined) {
+ let grid = me.lookup('grid');
+
+ let record = field.getWidgetRecord();
+ if (record === undefined) {
+ // this is sometimes called before a record/column is initialized
return;
}
- selection.forEach((sel) => {
- view.getStore().remove(sel);
- });
+ grid.getStore().remove(record);
view.checkChange();
},
@@ -114,33 +110,47 @@ Ext.define('PVE.form.ListField', {
rec.set(column.dataIndex, newValue);
field.up('pveListField').checkChange();
},
+
+ control: {
+ 'grid button': {
+ click: 'removeSelection',
+ },
+ },
},
- tbar: [
+ items: [
{
- text: gettext('Add'),
- handler: 'addLine',
+ xtype: 'grid',
+ reference: 'grid',
+
+ viewConfig: {
+ deferEmptyText: false,
+ },
+
+ store: {
+ listeners: {
+ update: function() {
+ this.commitChanges();
+ },
+ },
+ },
},
{
- xtype: 'proxmoxButton',
- text: gettext('Remove'),
- handler: 'removeSelection',
- disabled: true,
+ xtype: 'button',
+ text: gettext('Add'),
+ iconCls: 'fa fa-plus-circle',
+ handler: 'addLine',
},
],
- store: {
- listeners: {
- update: function() {
- this.commitChanges();
- },
- },
- },
-
initComponent: function() {
let me = this;
- me.columns = [
+ for (const [key, value] of Object.entries(me.gridConfig ?? {})) {
+ me.items[0][key] = value;
+ }
+
+ me.items[0].columns = [
{
header: me.fieldTtitle,
dataIndex: 'item',
@@ -157,6 +167,14 @@ Ext.define('PVE.form.ListField', {
},
flex: 1,
},
+ {
+ xtype: 'widgetcolumn',
+ width: 40,
+ widget: {
+ xtype: 'button',
+ iconCls: 'fa fa-trash-o',
+ },
+ },
];
me.callParent();
--
2.30.2
^ permalink raw reply [flat|nested] 7+ messages in thread