From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [45.144.208.40]) by lore.proxmox.com (Postfix) with ESMTPS id 173111FF09B for ; Mon, 26 Oct 2026 23:04:53 +0100 (CET) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 4E3B521413; Mon, 26 Oct 2026 23:04:52 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=datenzelt.de; s=MBO0001; t=1793052277; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=u82/Aqy/eEAIhEZLc9Rl10NBgq558mFL6uIudQ7CNK0=; b=05u3He7SP6HkRnbPnto+mR1OtTDhCNU1XkXpnK9IAE2eF48sm+mTqlQV/r/gqx6X3/qzKf wDsgXxPihbBmpBfjUYkmsVGKL48nQDQNwU+NpjulY8cwyIRmdHNkof6WRj8C1bmy6oJGmh m7NwDu2+/Y1pbemmJ4x8CPCYejzYSafJ22eQWE9g93Uju/E2u/+bwEgk9vl320CEcSkEJt p26knouHWUR+lv3RJQu6kYukoahG39nvwlCIXGLC+L8W67qr24iduu4nQTo7MHiBc8l+ne ji4VI3uIqBL9qk5di2macQaiFPFQJYoxUn6iLtPCWHM06iMMWIEkzGACocHCsw== Authentication-Results: outgoing_mbo_mout; dkim=none; spf=pass (outgoing_mbo_mout: domain of timo.meschkat@datenzelt.de designates 2001:67c:2050:b231:465::1 as permitted sender) smtp.mailfrom=timo.meschkat@datenzelt.de From: Timo Meschkat To: pve-devel@lists.proxmox.com Subject: [RFC PATCH widget-toolkit 2/3] form: size field: accept a unit suffix like 8G Date: Mon, 26 Oct 2026 23:04:18 +0100 Message-ID: <20261026220423.127776-3-timo.meschkat@datenzelt.de> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20261026220423.127776-1-timo.meschkat@datenzelt.de> References: <20261026220423.127776-1-timo.meschkat@datenzelt.de> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Rspamd-Queue-Id: 4jD72Y0Hq1zMlML X-SPAM-LEVEL: Spam detection results: 0 AWL 0.070 Adjusted score from AWL reputation of From: address DKIM_SIGNED 0.1 Message has a DKIM or DK signature, not necessarily valid DKIM_VALID -0.1 Message has at least one valid DKIM or DK signature DKIM_VALID_AU -0.1 Message has a valid DKIM or DK signature from author's domain DKIM_VALID_EF -0.1 Message has a valid DKIM or DK signature from envelope-from domain DMARC_PASS -0.1 DMARC pass policy RCVD_IN_DNSWL_LOW -0.7 Sender listed at https://www.dnswl.org/, low trust SPF_HELO_PASS -0.001 SPF: HELO matches SPF record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: GHYZTPLK5KDDDZBRMI6OC6S27ZOGSPED X-Message-ID-Hash: GHYZTPLK5KDDDZBRMI6OC6S27ZOGSPED X-MailFrom: timo.meschkat@datenzelt.de X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Selecting the unit first is an extra step for keyboard users. Allow typing it right away, e.g. '8G', '512M' or '1.5 GiB', like the backend accepts it. The unit selector follows while typing, the text itself is only normalized on blur, so typing is not interrupted. SI suffixes like 'GB' are rejected, they would be ambiguous. Signed-off-by: Timo Meschkat --- src/form/SizeUnitField.js | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/src/form/SizeUnitField.js b/src/form/SizeUnitField.js index e103b59..9fd7308 100644 --- a/src/form/SizeUnitField.js +++ b/src/form/SizeUnitField.js @@ -41,6 +41,12 @@ Ext.define('Proxmox.form.SizeUnitField', { me.unit ??= me.units[0]; me.initialUnit = me.unit; + // e.g. '512M, 8G' + let examples = me.units.slice(0, 2).map((unit, i) => `${i === 0 ? 512 : 8}${unit[0]}`); + let hint = Ext.htmlEncode( + Ext.String.format(gettext('Also accepts a unit suffix, e.g. {0}'), examples.join(', ')), + ); + me.items = [ { // plain text, parsing and validation happen in this field @@ -50,6 +56,7 @@ Ext.define('Proxmox.form.SizeUnitField', { flex: 1, // the field label belongs to the container, not the inputs ariaLabel: me.fieldLabel, + inputAttrTpl: `data-qtip="${hint}" aria-description="${hint}"`, fieldStyle: 'text-align: right', allowBlank: me.allowBlank, emptyText: me.emptyText, @@ -122,18 +129,20 @@ Ext.define('Proxmox.form.SizeUnitField', { return !!this.userChanged; }, - // '8' or '1,5' in the current unit; returns undefined if empty, null if invalid + // '8', '1,5', '8G' or '1.5 GiB'; returns undefined if empty, null if invalid parseText: function (text) { let me = this; text = String(text ?? '').trim(); if (text === '') { return undefined; } - if (!/^\d+(?:[.,]\d*)?$/.test(text)) { + let match = text.match(/^(\d+(?:[.,]\d*)?)\s*(?:([KMGT])(?:i|iB)?)?$/i); + let unit = match?.[2] ? `${match[2].toUpperCase()}iB` : me.unit; + if (!match || !me.units.includes(unit)) { return null; } - let value = parseFloat(text.replace(',', '.')) * me.getUnitFactor(); - return me.allowFractionalValue ? value : Math.round(value); + let value = parseFloat(match[1].replace(',', '.')) * me.getUnitFactor(unit); + return { value: me.allowFractionalValue ? value : Math.round(value), unit }; }, parseValue: function (value) { @@ -209,25 +218,32 @@ Ext.define('Proxmox.form.SizeUnitField', { onTextChange: function (text) { let me = this; + let input = me.parseText(text); me.userChanged = true; - me.mixins.field.setValue.call(me, me.parseText(text) ?? null); + if (input && input.unit !== me.unit) { + me.changeUnit(input.unit); // the typed text stays until blur + } + me.mixins.field.setValue.call(me, input ? input.value : null); me.updateAriaValueText(); }, validateText: function (text) { let me = this; - let value = me.parseText(text); - if (value === undefined) { + let input = me.parseText(text); + if (input === undefined) { return true; // allowBlank handles this - } else if (value === null) { - return Ext.String.format(Ext.form.field.Number.prototype.nanText, text); + } else if (input === null) { + return Ext.String.format( + gettext('Invalid size, allowed units: {0}'), + me.units.join(', '), + ); } let format = (v) => Proxmox.Utils.format_size(v * Proxmox.Utils.SizeUnits[me.backendUnit]); let numberField = Ext.form.field.Number.prototype; - if (me.minValue !== undefined && value < me.minValue) { + if (me.minValue !== undefined && input.value < me.minValue) { return Ext.String.format(numberField.minText, format(me.minValue)); } - if (me.maxValue !== undefined && value > me.maxValue) { + if (me.maxValue !== undefined && input.value > me.maxValue) { return Ext.String.format(numberField.maxText, format(me.maxValue)); } return true; -- 2.47.3