From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 2B7DA702A8 for ; Tue, 21 Jun 2022 11:20:44 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id CC0A72C9A for ; Tue, 21 Jun 2022 11:20:27 +0200 (CEST) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [94.136.29.106]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS id 43B7C29DC for ; Tue, 21 Jun 2022 11:20:16 +0200 (CEST) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 122F64378F for ; Tue, 21 Jun 2022 11:20:16 +0200 (CEST) From: Dominik Csapak To: pve-devel@lists.proxmox.com Date: Tue, 21 Jun 2022 11:20:12 +0200 Message-Id: <20220621092012.1776825-25-d.csapak@proxmox.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20220621092012.1776825-1-d.csapak@proxmox.com> References: <20220621092012.1776825-1-d.csapak@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.101 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record T_SCC_BODY_TEXT_LINE -0.01 - URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [ddel.id] Subject: [pve-devel] [PATCH manager v7 14/14] ui: form/Tag(Edit): add drag & drop when editing tags X-BeenThere: pve-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox VE development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 21 Jun 2022 09:20:44 -0000 Signed-off-by: Dominik Csapak --- www/manager6/form/Tag.js | 22 +++++++-- www/manager6/form/TagEdit.js | 96 +++++++++++++++++++++++++++++++++++- 2 files changed, 114 insertions(+), 4 deletions(-) diff --git a/www/manager6/form/Tag.js b/www/manager6/form/Tag.js index 91190051..dcbd9597 100644 --- a/www/manager6/form/Tag.js +++ b/www/manager6/form/Tag.js @@ -31,6 +31,9 @@ Ext.define('Proxmox.Tag', { if (event.target.tagName !== 'I') { return; } + if (event.target.classList.contains('handle')) { + return; + } switch (me.mode) { case 'editable': if (me.addTag) { @@ -156,12 +159,14 @@ Ext.define('Proxmox.Tag', { let text = me.tag; let cursor = 'pointer'; let padding = '0px'; + let dragHandleStyle = 'none'; switch (mode) { case 'normal': iconStyle += 'display: none;'; padding = undefined; break; case 'editable': + dragHandleStyle = ''; break; case 'edit': me.tagEl().contentEditable = true; @@ -174,12 +179,14 @@ Ext.define('Proxmox.Tag', { if (me.addTag) { me.setText(text); me.setStyle('cursor', cursor); + dragHandleStyle = 'none'; } me.setStyle('padding-right', padding); me.iconEl().classList = `fa fa-${icon}${me.faIconStyle}`; me.iconEl().style = iconStyle; + me.dragEl().style.display = dragHandleStyle; me.mode = mode; }, @@ -233,14 +240,18 @@ Ext.define('Proxmox.Tag', { } }, - tagEl: function() { + dragEl: function() { return this.el?.dom?.children?.[0]; }, - iconEl: function() { + tagEl: function() { return this.el?.dom?.children?.[1]; }, + iconEl: function() { + return this.el?.dom?.children?.[2]; + }, + initComponent: function() { let me = this; if (me.tag === undefined && !me.addTag) { @@ -256,10 +267,15 @@ Ext.define('Proxmox.Tag', { let iconStyle = me.mode !== 'editable' ? 'display: none' : 'padding-right: 6px;'; let iconCls = me.icons[me.addTag ? 'addTag' : me.mode]; + let dragHandleStyle = 'cursor: grab; font-size: 14px;'; + if (me.addTag || me.mode !== 'editable') { + dragHandleStyle += 'display: none'; + } let adminCls = me.tag[0] === '+' ? 'admin-tag' : ''; + let dragHandle = ` `; let icon = ` `; - me.html = `${me.tag}${icon}`; + me.html = `${dragHandle}${me.tag}${icon}`; me.callParent(); if (me.addTag) { diff --git a/www/manager6/form/TagEdit.js b/www/manager6/form/TagEdit.js index 5a267169..85f9f63e 100644 --- a/www/manager6/form/TagEdit.js +++ b/www/manager6/form/TagEdit.js @@ -18,7 +18,7 @@ Ext.define('PVE.panel.TagEditContainer', { let tags = tagstring.split(/[;, ]/).filter((t) => !!t) || []; me.suspendLayout = true; - me.tags = {}; + me.tagList = []; me.removeAllTags(); tags.forEach((tag) => { me.addTag(tag, inEdit); @@ -87,6 +87,7 @@ Ext.define('PVE.panel.TagEditContainer', { me.remove(tag); }); me.tagFields = []; + me.tagList = []; me.noTagsField.setVisible(true); }, @@ -94,11 +95,13 @@ Ext.define('PVE.panel.TagEditContainer', { let me = this; let tagField = me.insert(me.tagFields.length + 1, { xtype: 'pmxTag', + tagIndex: me.tagList.length, tag, mode: inEdit ? 'editable' : 'normal', layoutCallback: () => me.updateLayout(), }); me.tagFields.push(tagField); + me.tagList.push(tag); me.noTagsField.setVisible(false); }, @@ -147,5 +150,96 @@ Ext.define('PVE.panel.TagEditContainer', { if (me.tags) { me.loadTags(me.tags); } + + me.on('render', function(v) { + me.dragzone = Ext.create('Ext.dd.DragZone', v.getEl(), { + getDragData: function(e) { + let source = e.getTarget('.handle'); + if (source) { + let sourceId = source.parentNode.id; + let cmp = Ext.getCmp(sourceId); + let tag = cmp.getTag(); + let ddel = document.createElement('div'); + ddel.classList.add('proxmox-tags-full'); + ddel.innerHTML = Proxmox.Utils.getTagElement(tag, PVE.Utils.tagOverrides); + let repairXY = Ext.fly(source).getXY(); + cmp.setDisabled(true); + ddel.id = Ext.id(); + return { + ddel, + repairXY, + tagIndex: cmp.tagIndex, + tag: cmp.getTag(), + sourceId, + }; + } + return undefined; + }, + onMouseUp: function(target, e, id) { + let cmp = Ext.getCmp(this.dragData.sourceId); + if (cmp && !cmp.isDestroyed) { + cmp.setDisabled(false); + } + }, + getRepairXY: function() { + return this.dragData.repairXY; + }, + beforeInvalidDrop: function(target, e, id) { + let cmp = Ext.getCmp(this.dragData.sourceId); + if (cmp && !cmp.isDestroyed) { + cmp.setDisabled(false); + } + }, + }); + me.dropzone = Ext.create('Ext.dd.DropZone', v.getEl(), { + getTargetFromEvent: function(e) { + return e.getTarget('.proxmox-tag-dark,.proxmox-tag-light'); + }, + getIndicator: function() { + if (!me.indicator) { + me.indicator = Ext.create('Ext.Component', { + floating: true, + html: '', + hidden: true, + shadow: false, + }); + } + return me.indicator; + }, + onContainerOver: function() { + this.getIndicator().setVisible(false); + }, + notifyOut: function() { + this.getIndicator().setVisible(false); + }, + onNodeOver: function(target, dd, e, data) { + let indicator = this.getIndicator(); + indicator.setVisible(true); + indicator.alignTo(Ext.getCmp(target.id), 't50-bl', [-1, -2]); + return this.dropAllowed; + }, + onNodeDrop: function(target, dd, e, data) { + this.getIndicator().setVisible(false); + let tagIdx = data.tagIndex; + let tag = data.tag; + me.tagList.splice(tagIdx, 1); + let targetCmp = Ext.getCmp(target.id); + let targetIdx = targetCmp.tagIndex ?? Infinity; + if (targetIdx > tagIdx) { + targetIdx--; + } + me.tagList.splice(targetIdx, 0, tag); + me.loadTags(me.tagList.join(','), true, true); + }, + }); + }); + }, + + destroy: function() { + let me = this; + Ext.destroy(me.dragzone); + Ext.destroy(me.dropzone); + Ext.destroy(me.indicator); + me.callParent(); }, }); -- 2.30.2