mirror of
https://git.proxmox.com/git/pve-manager
synced 2025-07-25 23:42:41 +00:00
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
0a627d9468
commit
b6f9fc78c0
@ -656,3 +656,35 @@ table.osds td:first-of-type {
|
|||||||
padding-top: 0px;
|
padding-top: 0px;
|
||||||
padding-bottom: 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/iScsiProviderSelector.js \
|
||||||
form/TagColorGrid.js \
|
form/TagColorGrid.js \
|
||||||
form/ListField.js \
|
form/ListField.js \
|
||||||
|
form/Tag.js \
|
||||||
grid/BackupView.js \
|
grid/BackupView.js \
|
||||||
grid/FirewallAliases.js \
|
grid/FirewallAliases.js \
|
||||||
grid/FirewallOptions.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