ui: add form/Tag
displays a single tag, with the ability to edit inline on click (when the mode is set to editable). This brings up a list of globally available tags for simple selection. this is a basic ext component, with 'i' tags for the icons (handle and add/remove button) and a span (for the tag text) shows the tag by default, and if put in editable mode, makes the span editable. when actually starting the edit, shows a picker with available tags to select from Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
This commit is contained in:
parent
e2c29a883d
commit
d9dba35d00
@ -656,3 +656,35 @@ table.osds td:first-of-type {
|
||||
padding-top: 0px;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
.pve-edit-tag > i {
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.pve-edit-tag > i.handle {
|
||||
padding-right: 5px;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.pve-edit-tag > i.action {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.pve-edit-tag.normal > i {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pve-edit-tag.editable span,
|
||||
.pve-edit-tag.inEdit span {
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #a8a8a8;
|
||||
color: #000;
|
||||
padding-left: 2px;
|
||||
padding-right: 2px;
|
||||
min-width: 2em;
|
||||
}
|
||||
|
||||
.pve-edit-tag.inEdit span {
|
||||
border: 1px solid #000;
|
||||
}
|
||||
|
@ -75,6 +75,7 @@ JSSRC= \
|
||||
form/iScsiProviderSelector.js \
|
||||
form/TagColorGrid.js \
|
||||
form/ListField.js \
|
||||
form/Tag.js \
|
||||
grid/BackupView.js \
|
||||
grid/FirewallAliases.js \
|
||||
grid/FirewallOptions.js \
|
||||
|
232
www/manager6/form/Tag.js
Normal file
232
www/manager6/form/Tag.js
Normal file
@ -0,0 +1,232 @@
|
||||
Ext.define('Proxmox.form.Tag', {
|
||||
extend: 'Ext.Component',
|
||||
alias: 'widget.pveTag',
|
||||
|
||||
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>',
|
||||
],
|
||||
|
||||
// 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;
|
||||
}
|
||||
},
|
||||
|
||||
onClick: function(event) {
|
||||
let me = this;
|
||||
if (event.target.tagName !== 'SPAN' || me.mode !== 'editable') {
|
||||
return;
|
||||
}
|
||||
me.setMode('inEdit');
|
||||
|
||||
// select text in the element
|
||||
let tagEl = me.tagEl();
|
||||
tagEl.contentEditable = true;
|
||||
let range = document.createRange();
|
||||
range.selectNodeContents(tagEl);
|
||||
let sel = window.getSelection();
|
||||
sel.removeAllRanges();
|
||||
sel.addRange(range);
|
||||
|
||||
me.showPicker();
|
||||
},
|
||||
|
||||
showPicker: function() {
|
||||
let me = this;
|
||||
if (!me.picker) {
|
||||
me.picker = Ext.widget({
|
||||
xtype: 'boundlist',
|
||||
minWidth: 70,
|
||||
scrollable: true,
|
||||
floating: true,
|
||||
hidden: true,
|
||||
userCls: 'proxmox-tags-full',
|
||||
displayField: 'tag',
|
||||
itemTpl: [
|
||||
'{[Proxmox.Utils.getTagElement(values.tag, PVE.Utils.tagOverrides)]}',
|
||||
],
|
||||
store: [],
|
||||
listeners: {
|
||||
select: function(picker, rec) {
|
||||
me.setTag(rec.data.tag);
|
||||
me.setMode('editable');
|
||||
me.picker.hide();
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
me.picker.getStore()?.clearFilter();
|
||||
let taglist = PVE.Utils.tagList.map(v => ({ tag: v }));
|
||||
if (taglist.length < 1) {
|
||||
return;
|
||||
}
|
||||
me.picker.getStore().setData(taglist);
|
||||
me.picker.showBy(me, 'tl-bl');
|
||||
me.picker.setMaxHeight(200);
|
||||
},
|
||||
|
||||
setMode: function(mode) {
|
||||
let me = this;
|
||||
if (me.icons[mode] === undefined) {
|
||||
throw "invalid mode";
|
||||
}
|
||||
let tagEl = me.tagEl();
|
||||
if (tagEl) {
|
||||
tagEl.contentEditable = mode === 'inEdit';
|
||||
}
|
||||
me.removeCls(me.mode);
|
||||
me.addCls(mode);
|
||||
me.mode = mode;
|
||||
me.updateData();
|
||||
},
|
||||
|
||||
onKeyPress: function(event) {
|
||||
let me = this;
|
||||
let key = event.browserEvent.key;
|
||||
switch (key) {
|
||||
case 'Enter':
|
||||
if (me.tagEl().innerHTML !== '') {
|
||||
me.setTag(me.tagEl().innerHTML);
|
||||
me.setMode('editable');
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 'Escape':
|
||||
me.cancelEdit();
|
||||
return;
|
||||
case 'Backspace':
|
||||
case 'Delete':
|
||||
return;
|
||||
default:
|
||||
if (key.match(PVE.Utils.tagCharRegex)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
event.browserEvent.preventDefault();
|
||||
event.browserEvent.stopPropagation();
|
||||
},
|
||||
|
||||
beforeInput: function(event) {
|
||||
let me = this;
|
||||
me.updateLayout();
|
||||
let tag = event.event.data ?? event.event.dataTransfer?.getData('text/plain');
|
||||
if (!tag) {
|
||||
return;
|
||||
}
|
||||
if (tag.match(PVE.Utils.tagCharRegex) === null) {
|
||||
event.event.preventDefault();
|
||||
event.event.stopPropagation();
|
||||
}
|
||||
},
|
||||
|
||||
onInput: function(event) {
|
||||
let me = this;
|
||||
me.picker.getStore().filter({
|
||||
property: 'tag',
|
||||
value: me.tagEl().innerHTML,
|
||||
anyMatch: true,
|
||||
});
|
||||
},
|
||||
|
||||
cancelEdit: function(list, event) {
|
||||
let me = this;
|
||||
if (me.mode === 'inEdit') {
|
||||
me.setTag(me.tag);
|
||||
me.setMode('editable');
|
||||
}
|
||||
me.picker?.hide();
|
||||
},
|
||||
|
||||
|
||||
setTag: 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);
|
||||
let color = Proxmox.Utils.rgbToCss(rgb);
|
||||
me.setUserCls(`proxmox-tag-${cls}`);
|
||||
me.setStyle('background-color', color);
|
||||
if (rgb.length > 3) {
|
||||
let fgcolor = Proxmox.Utils.rgbToCss([rgb[3], rgb[4], rgb[5]]);
|
||||
|
||||
me.setStyle('color', fgcolor);
|
||||
} else {
|
||||
me.setStyle('color');
|
||||
}
|
||||
me.updateData();
|
||||
if (oldtag !== tag) {
|
||||
me.fireEvent('change', me, tag, oldtag);
|
||||
}
|
||||
},
|
||||
|
||||
updateData: function() {
|
||||
let me = this;
|
||||
if (me.destroying || me.destroyed) {
|
||||
return;
|
||||
}
|
||||
me.update({
|
||||
tag: me.tag,
|
||||
iconCls: me.icons[me.mode],
|
||||
});
|
||||
},
|
||||
|
||||
tagEl: function() {
|
||||
return this.el?.dom?.getElementsByTagName('span')?.[0];
|
||||
},
|
||||
|
||||
listeners: {
|
||||
mousedown: 'onMouseDown',
|
||||
click: 'onClick',
|
||||
focusleave: 'cancelEdit',
|
||||
keydown: 'onKeyPress',
|
||||
beforeInput: 'beforeInput',
|
||||
input: 'onInput',
|
||||
element: 'el',
|
||||
scope: 'this',
|
||||
},
|
||||
|
||||
initComponent: function() {
|
||||
let me = this;
|
||||
|
||||
me.setTag(me.tag);
|
||||
me.setMode(me.mode ?? 'normal');
|
||||
me.callParent();
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
let me = this;
|
||||
if (me.picker) {
|
||||
Ext.destroy(me.picker);
|
||||
}
|
||||
me.callParent();
|
||||
},
|
||||
});
|
Loading…
Reference in New Issue
Block a user