mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-20 02:00:55 +07:00
input validation, numeric limits and live Save on settings pages
This commit is contained in:
@@ -15,6 +15,8 @@ import "../Components"
|
|||||||
PageType {
|
PageType {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
property bool editDirty: false
|
||||||
|
|
||||||
BackButtonType {
|
BackButtonType {
|
||||||
id: backButton
|
id: backButton
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
@@ -90,6 +92,7 @@ PageType {
|
|||||||
|
|
||||||
DropDownType {
|
DropDownType {
|
||||||
id: tlsAlpnDropDown
|
id: tlsAlpnDropDown
|
||||||
|
fitContent: true
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
@@ -133,6 +136,7 @@ PageType {
|
|||||||
|
|
||||||
DropDownType {
|
DropDownType {
|
||||||
id: tlsFingerprintDropDown
|
id: tlsFingerprintDropDown
|
||||||
|
fitContent: true
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 8
|
Layout.topMargin: 8
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
@@ -175,14 +179,21 @@ PageType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TextFieldWithHeaderType {
|
TextFieldWithHeaderType {
|
||||||
|
id: sniFieldTls
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
Layout.topMargin: 8
|
Layout.topMargin: 8
|
||||||
headerText: qsTr("Server Name (SNI)")
|
headerText: qsTr("Server Name (SNI)")
|
||||||
textField.text: sni
|
textField.text: sni
|
||||||
|
textField.validator: RegularExpressionValidator { regularExpression: /^[A-Za-z0-9.*_-]*$/ }
|
||||||
|
textField.onTextEdited: root.editDirty = (textField.text !== sni)
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textField.text !== sni) sni = textField.text
|
var v = textField.text.trim()
|
||||||
|
if (v !== sni) sni = v
|
||||||
|
else if (textField.text !== v) textField.text = v
|
||||||
|
sniFieldTls.errorText = XrayConfigModel.isValidSni(v) ? "" : qsTr("Enter a valid IP address or domain name")
|
||||||
|
root.editDirty = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -195,6 +206,7 @@ PageType {
|
|||||||
|
|
||||||
DropDownType {
|
DropDownType {
|
||||||
id: realityFingerprintDropDown
|
id: realityFingerprintDropDown
|
||||||
|
fitContent: true
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
@@ -237,14 +249,21 @@ PageType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TextFieldWithHeaderType {
|
TextFieldWithHeaderType {
|
||||||
|
id: sniFieldReality
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
Layout.topMargin: 8
|
Layout.topMargin: 8
|
||||||
headerText: qsTr("Server Name (SNI)")
|
headerText: qsTr("Server Name (SNI)")
|
||||||
textField.text: sni
|
textField.text: sni
|
||||||
|
textField.validator: RegularExpressionValidator { regularExpression: /^[A-Za-z0-9.*_-]*$/ }
|
||||||
|
textField.onTextEdited: root.editDirty = (textField.text !== sni)
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textField.text !== sni) sni = textField.text
|
var v = textField.text.trim()
|
||||||
|
if (v !== sni) sni = v
|
||||||
|
else if (textField.text !== v) textField.text = v
|
||||||
|
sniFieldReality.errorText = XrayConfigModel.isValidSni(v) ? "" : qsTr("Enter a valid IP address or domain name")
|
||||||
|
root.editDirty = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -265,10 +284,15 @@ PageType {
|
|||||||
anchors.rightMargin: 16
|
anchors.rightMargin: 16
|
||||||
anchors.bottomMargin: 16 + PageController.safeAreaBottomMargin
|
anchors.bottomMargin: 16 + PageController.safeAreaBottomMargin
|
||||||
|
|
||||||
visible: listView.enabled && XrayConfigModel.hasUnsavedChanges
|
visible: listView.enabled && (XrayConfigModel.hasUnsavedChanges || root.editDirty)
|
||||||
enabled: visible
|
enabled: visible
|
||||||
text: qsTr("Save")
|
text: qsTr("Save")
|
||||||
clickedFunc: function () {
|
clickedFunc: function () {
|
||||||
|
var errs = XrayConfigModel.validationErrors()
|
||||||
|
if (errs.length > 0) {
|
||||||
|
PageController.showErrorMessage(errs.join("\n"))
|
||||||
|
return
|
||||||
|
}
|
||||||
var headerText = qsTr("Save settings?")
|
var headerText = qsTr("Save settings?")
|
||||||
var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.")
|
var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.")
|
||||||
var yesButtonText = qsTr("Continue")
|
var yesButtonText = qsTr("Continue")
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ PageType {
|
|||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
enabled: listView.enabled
|
enabled: listView.enabled
|
||||||
headerText: qsTr("Port")
|
headerText: qsTr("Port")
|
||||||
|
subtitleText: qsTr("1–65535")
|
||||||
|
|
||||||
Binding {
|
Binding {
|
||||||
target: textFieldWithHeaderType.textField
|
target: textFieldWithHeaderType.textField
|
||||||
@@ -119,8 +120,8 @@ PageType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
textField.maximumLength: 5
|
textField.maximumLength: 5
|
||||||
textField.validator: IntValidator {
|
textField.validator: RegularExpressionValidator {
|
||||||
bottom: 1; top: 65535
|
regularExpression: /^(|\d{1,4}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$/
|
||||||
}
|
}
|
||||||
textField.onActiveFocusChanged: {
|
textField.onActiveFocusChanged: {
|
||||||
if (textField.activeFocus && textField.text === "" && port !== "") {
|
if (textField.activeFocus && textField.text === "" && port !== "") {
|
||||||
@@ -131,9 +132,19 @@ PageType {
|
|||||||
root.portDirty = (textField.text !== port)
|
root.portDirty = (textField.text !== port)
|
||||||
}
|
}
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textField.text !== port) {
|
var v = textFieldWithHeaderType.textField.text
|
||||||
port = textField.text
|
if (v !== "") {
|
||||||
|
var n = parseInt(v, 10)
|
||||||
|
if (isNaN(n) || n < 1)
|
||||||
|
n = 1
|
||||||
|
if (n > 65535)
|
||||||
|
n = 65535
|
||||||
|
v = String(n)
|
||||||
|
if (textFieldWithHeaderType.textField.text !== v)
|
||||||
|
textFieldWithHeaderType.textField.text = v
|
||||||
}
|
}
|
||||||
|
if (v !== port)
|
||||||
|
port = v
|
||||||
root.portDirty = false
|
root.portDirty = false
|
||||||
}
|
}
|
||||||
checkEmptyText: true
|
checkEmptyText: true
|
||||||
@@ -198,6 +209,11 @@ PageType {
|
|||||||
text: qsTr("Save")
|
text: qsTr("Save")
|
||||||
onClicked: function() {
|
onClicked: function() {
|
||||||
forceActiveFocus()
|
forceActiveFocus()
|
||||||
|
var errs = XrayConfigModel.validationErrors()
|
||||||
|
if (errs.length > 0) {
|
||||||
|
PageController.showErrorMessage(errs.join("\n"))
|
||||||
|
return
|
||||||
|
}
|
||||||
var headerText = qsTr("Save settings?")
|
var headerText = qsTr("Save settings?")
|
||||||
var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.")
|
var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.")
|
||||||
var yesButtonText = qsTr("Continue")
|
var yesButtonText = qsTr("Continue")
|
||||||
|
|||||||
@@ -15,6 +15,21 @@ import "../Components"
|
|||||||
PageType {
|
PageType {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
property bool editDirty: false
|
||||||
|
|
||||||
|
function clampInt(text, lo, hi) {
|
||||||
|
if (text === "")
|
||||||
|
return ""
|
||||||
|
var n = parseInt(text, 10)
|
||||||
|
if (isNaN(n))
|
||||||
|
return ""
|
||||||
|
if (n < lo)
|
||||||
|
n = lo
|
||||||
|
if (n > hi)
|
||||||
|
n = hi
|
||||||
|
return String(n)
|
||||||
|
}
|
||||||
|
|
||||||
BackButtonType {
|
BackButtonType {
|
||||||
id: backButton
|
id: backButton
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
@@ -108,10 +123,16 @@ PageType {
|
|||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
Layout.topMargin: 8
|
Layout.topMargin: 8
|
||||||
headerText: qsTr("TTI")
|
headerText: qsTr("TTI")
|
||||||
subtitleText: qsTr("Default: %1 ms", "mKCP TTI").arg(XrayConfigModel.mkcpDefaultTti())
|
subtitleText: qsTr("Range 10–100, default %1 ms", "mKCP TTI").arg(XrayConfigModel.mkcpDefaultTti())
|
||||||
textField.text: mkcpTti
|
textField.text: mkcpTti
|
||||||
|
textField.maximumLength: 3
|
||||||
|
textField.validator: RegularExpressionValidator { regularExpression: /^(|\d{1,2}|100)$/ }
|
||||||
|
textField.onTextEdited: root.editDirty = (textField.text !== mkcpTti)
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textField.text !== mkcpTti) mkcpTti = textField.text
|
var v = root.clampInt(textField.text, 10, 100)
|
||||||
|
if (v !== mkcpTti) mkcpTti = v
|
||||||
|
else if (textField.text !== v) textField.text = v
|
||||||
|
root.editDirty = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,10 +142,16 @@ PageType {
|
|||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
Layout.topMargin: 8
|
Layout.topMargin: 8
|
||||||
headerText: qsTr("uplinkCapacity")
|
headerText: qsTr("uplinkCapacity")
|
||||||
subtitleText: qsTr("Default: %1 Mbit/s", "mKCP uplink").arg(XrayConfigModel.mkcpDefaultUplinkCapacity())
|
subtitleText: qsTr("≥ 0, default %1 MB/s", "mKCP uplink").arg(XrayConfigModel.mkcpDefaultUplinkCapacity())
|
||||||
textField.text: mkcpUplinkCapacity
|
textField.text: mkcpUplinkCapacity
|
||||||
|
textField.maximumLength: 10
|
||||||
|
textField.validator: RegularExpressionValidator { regularExpression: /^\d*$/ }
|
||||||
|
textField.onTextEdited: root.editDirty = (textField.text !== mkcpUplinkCapacity)
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textField.text !== mkcpUplinkCapacity) mkcpUplinkCapacity = textField.text
|
var v = root.clampInt(textField.text, 0, 2147483647)
|
||||||
|
if (v !== mkcpUplinkCapacity) mkcpUplinkCapacity = v
|
||||||
|
else if (textField.text !== v) textField.text = v
|
||||||
|
root.editDirty = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,10 +161,16 @@ PageType {
|
|||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
Layout.topMargin: 8
|
Layout.topMargin: 8
|
||||||
headerText: qsTr("downlinkCapacity")
|
headerText: qsTr("downlinkCapacity")
|
||||||
subtitleText: qsTr("Default: %1 Mbit/s", "mKCP downlink").arg(XrayConfigModel.mkcpDefaultDownlinkCapacity())
|
subtitleText: qsTr("≥ 0, default %1 MB/s", "mKCP downlink").arg(XrayConfigModel.mkcpDefaultDownlinkCapacity())
|
||||||
textField.text: mkcpDownlinkCapacity
|
textField.text: mkcpDownlinkCapacity
|
||||||
|
textField.maximumLength: 10
|
||||||
|
textField.validator: RegularExpressionValidator { regularExpression: /^\d*$/ }
|
||||||
|
textField.onTextEdited: root.editDirty = (textField.text !== mkcpDownlinkCapacity)
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textField.text !== mkcpDownlinkCapacity) mkcpDownlinkCapacity = textField.text
|
var v = root.clampInt(textField.text, 0, 2147483647)
|
||||||
|
if (v !== mkcpDownlinkCapacity) mkcpDownlinkCapacity = v
|
||||||
|
else if (textField.text !== v) textField.text = v
|
||||||
|
root.editDirty = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,10 +180,16 @@ PageType {
|
|||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
Layout.topMargin: 8
|
Layout.topMargin: 8
|
||||||
headerText: qsTr("readBufferSize")
|
headerText: qsTr("readBufferSize")
|
||||||
subtitleText: qsTr("Default: %1 MiB").arg(XrayConfigModel.mkcpDefaultReadBufferSize())
|
subtitleText: qsTr("≥ 1, default %1 MB").arg(XrayConfigModel.mkcpDefaultReadBufferSize())
|
||||||
textField.text: mkcpReadBufferSize
|
textField.text: mkcpReadBufferSize
|
||||||
|
textField.maximumLength: 10
|
||||||
|
textField.validator: RegularExpressionValidator { regularExpression: /^\d*$/ }
|
||||||
|
textField.onTextEdited: root.editDirty = (textField.text !== mkcpReadBufferSize)
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textField.text !== mkcpReadBufferSize) mkcpReadBufferSize = textField.text
|
var v = root.clampInt(textField.text, 1, 2147483647)
|
||||||
|
if (v !== mkcpReadBufferSize) mkcpReadBufferSize = v
|
||||||
|
else if (textField.text !== v) textField.text = v
|
||||||
|
root.editDirty = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,10 +199,16 @@ PageType {
|
|||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
Layout.topMargin: 8
|
Layout.topMargin: 8
|
||||||
headerText: qsTr("writeBufferSize")
|
headerText: qsTr("writeBufferSize")
|
||||||
subtitleText: qsTr("Default: %1 MiB").arg(XrayConfigModel.mkcpDefaultWriteBufferSize())
|
subtitleText: qsTr("≥ 1, default %1 MB").arg(XrayConfigModel.mkcpDefaultWriteBufferSize())
|
||||||
textField.text: mkcpWriteBufferSize
|
textField.text: mkcpWriteBufferSize
|
||||||
|
textField.maximumLength: 10
|
||||||
|
textField.validator: RegularExpressionValidator { regularExpression: /^\d*$/ }
|
||||||
|
textField.onTextEdited: root.editDirty = (textField.text !== mkcpWriteBufferSize)
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textField.text !== mkcpWriteBufferSize) mkcpWriteBufferSize = textField.text
|
var v = root.clampInt(textField.text, 1, 2147483647)
|
||||||
|
if (v !== mkcpWriteBufferSize) mkcpWriteBufferSize = v
|
||||||
|
else if (textField.text !== v) textField.text = v
|
||||||
|
root.editDirty = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,6 +232,7 @@ PageType {
|
|||||||
|
|
||||||
DropDownType {
|
DropDownType {
|
||||||
id: modeDropDown
|
id: modeDropDown
|
||||||
|
fitContent: true
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
@@ -239,31 +285,46 @@ PageType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TextFieldWithHeaderType {
|
TextFieldWithHeaderType {
|
||||||
|
id: hostField
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
Layout.topMargin: 8
|
Layout.topMargin: 8
|
||||||
headerText: qsTr("Host")
|
headerText: qsTr("Host")
|
||||||
textField.text: xhttpHost
|
textField.text: xhttpHost
|
||||||
|
textField.validator: RegularExpressionValidator { regularExpression: /^[A-Za-z0-9._:,-]*$/ }
|
||||||
|
textField.onTextEdited: root.editDirty = (textField.text !== xhttpHost)
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textField.text !== xhttpHost) xhttpHost = textField.text
|
var v = textField.text.trim()
|
||||||
|
if (v !== xhttpHost) xhttpHost = v
|
||||||
|
else if (textField.text !== v) textField.text = v
|
||||||
|
hostField.errorText = XrayConfigModel.isValidHost(v) ? "" : qsTr("Enter a valid IP address or domain name")
|
||||||
|
root.editDirty = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TextFieldWithHeaderType {
|
TextFieldWithHeaderType {
|
||||||
|
id: pathField
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
Layout.topMargin: 8
|
Layout.topMargin: 8
|
||||||
headerText: qsTr("Path")
|
headerText: qsTr("Path")
|
||||||
textField.text: xhttpPath
|
textField.text: xhttpPath
|
||||||
|
textField.validator: RegularExpressionValidator { regularExpression: /^[A-Za-z0-9\-._~:\/?#\[\]@!$&'()*+,;=%]*$/ }
|
||||||
|
textField.onTextEdited: root.editDirty = (textField.text !== xhttpPath)
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textField.text !== xhttpPath) xhttpPath = textField.text
|
var v = textField.text.trim()
|
||||||
|
if (v !== xhttpPath) xhttpPath = v
|
||||||
|
else if (textField.text !== v) textField.text = v
|
||||||
|
pathField.errorText = XrayConfigModel.isValidPath(v) ? "" : qsTr("Path must start with \"/\"")
|
||||||
|
root.editDirty = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DropDownType {
|
DropDownType {
|
||||||
id: headersDropDown
|
id: headersDropDown
|
||||||
|
fitContent: true
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 8
|
Layout.topMargin: 8
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
@@ -307,6 +368,7 @@ PageType {
|
|||||||
|
|
||||||
DropDownType {
|
DropDownType {
|
||||||
id: uplinkMethodDropDown
|
id: uplinkMethodDropDown
|
||||||
|
fitContent: true
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 8
|
Layout.topMargin: 8
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
@@ -386,6 +448,7 @@ PageType {
|
|||||||
|
|
||||||
DropDownType {
|
DropDownType {
|
||||||
id: sessionPlacementDropDown
|
id: sessionPlacementDropDown
|
||||||
|
fitContent: true
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 8
|
Layout.topMargin: 8
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
@@ -429,6 +492,7 @@ PageType {
|
|||||||
|
|
||||||
DropDownType {
|
DropDownType {
|
||||||
id: sessionKeyDropDown
|
id: sessionKeyDropDown
|
||||||
|
fitContent: true
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 8
|
Layout.topMargin: 8
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
@@ -472,6 +536,7 @@ PageType {
|
|||||||
|
|
||||||
DropDownType {
|
DropDownType {
|
||||||
id: seqPlacementDropDown
|
id: seqPlacementDropDown
|
||||||
|
fitContent: true
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 8
|
Layout.topMargin: 8
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
@@ -520,13 +585,19 @@ PageType {
|
|||||||
Layout.topMargin: 8
|
Layout.topMargin: 8
|
||||||
headerText: qsTr("SeqKey")
|
headerText: qsTr("SeqKey")
|
||||||
textField.text: xhttpSeqKey
|
textField.text: xhttpSeqKey
|
||||||
|
textField.validator: RegularExpressionValidator { regularExpression: /^[A-Za-z0-9_-]*$/ }
|
||||||
|
textField.onTextEdited: root.editDirty = (textField.text !== xhttpSeqKey)
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textField.text !== xhttpSeqKey) xhttpSeqKey = textField.text
|
var v = textField.text.trim()
|
||||||
|
if (v !== xhttpSeqKey) xhttpSeqKey = v
|
||||||
|
else if (textField.text !== v) textField.text = v
|
||||||
|
root.editDirty = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DropDownType {
|
DropDownType {
|
||||||
id: uplinkDataPlacementDropDown
|
id: uplinkDataPlacementDropDown
|
||||||
|
fitContent: true
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 8
|
Layout.topMargin: 8
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
@@ -575,8 +646,13 @@ PageType {
|
|||||||
Layout.topMargin: 8
|
Layout.topMargin: 8
|
||||||
headerText: qsTr("UplinkDataKey")
|
headerText: qsTr("UplinkDataKey")
|
||||||
textField.text: xhttpUplinkDataKey
|
textField.text: xhttpUplinkDataKey
|
||||||
|
textField.validator: RegularExpressionValidator { regularExpression: /^[A-Za-z0-9_-]*$/ }
|
||||||
|
textField.onTextEdited: root.editDirty = (textField.text !== xhttpUplinkDataKey)
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textField.text !== xhttpUplinkDataKey) xhttpUplinkDataKey = textField.text
|
var v = textField.text.trim()
|
||||||
|
if (v !== xhttpUplinkDataKey) xhttpUplinkDataKey = v
|
||||||
|
else if (textField.text !== v) textField.text = v
|
||||||
|
root.editDirty = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -597,12 +673,16 @@ PageType {
|
|||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
Layout.topMargin: 8
|
Layout.topMargin: 8
|
||||||
headerText: qsTr("UplinkChunkSize")
|
headerText: qsTr("UplinkChunkSize")
|
||||||
|
subtitleText: qsTr("≥ 0 (0 = off)")
|
||||||
textField.text: xhttpUplinkChunkSize
|
textField.text: xhttpUplinkChunkSize
|
||||||
textField.validator: IntValidator {
|
textField.maximumLength: 10
|
||||||
bottom: 0
|
textField.validator: RegularExpressionValidator { regularExpression: /^\d*$/ }
|
||||||
}
|
textField.onTextEdited: root.editDirty = (textField.text !== xhttpUplinkChunkSize)
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textField.text !== xhttpUplinkChunkSize) xhttpUplinkChunkSize = textField.text
|
var v = root.clampInt(textField.text, 0, 2147483647)
|
||||||
|
if (v !== xhttpUplinkChunkSize) xhttpUplinkChunkSize = v
|
||||||
|
else if (textField.text !== v) textField.text = v
|
||||||
|
root.editDirty = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -612,9 +692,16 @@ PageType {
|
|||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
Layout.topMargin: 8
|
Layout.topMargin: 8
|
||||||
headerText: qsTr("scMaxBufferedPosts")
|
headerText: qsTr("scMaxBufferedPosts")
|
||||||
|
subtitleText: qsTr("≥ 0")
|
||||||
textField.text: xhttpScMaxBufferedPosts
|
textField.text: xhttpScMaxBufferedPosts
|
||||||
|
textField.maximumLength: 10
|
||||||
|
textField.validator: RegularExpressionValidator { regularExpression: /^\d*$/ }
|
||||||
|
textField.onTextEdited: root.editDirty = (textField.text !== xhttpScMaxBufferedPosts)
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textField.text !== xhttpScMaxBufferedPosts) xhttpScMaxBufferedPosts = textField.text
|
var v = root.clampInt(textField.text, 0, 2147483647)
|
||||||
|
if (v !== xhttpScMaxBufferedPosts) xhttpScMaxBufferedPosts = v
|
||||||
|
else if (textField.text !== v) textField.text = v
|
||||||
|
root.editDirty = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -633,8 +720,9 @@ PageType {
|
|||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
minValue: xhttpScMaxEachPostBytesMin
|
minValue: xhttpScMaxEachPostBytesMin
|
||||||
maxValue: xhttpScMaxEachPostBytesMax
|
maxValue: xhttpScMaxEachPostBytesMax
|
||||||
onMinChanged: xhttpScMaxEachPostBytesMin = val
|
onMinChanged: function(val) { xhttpScMaxEachPostBytesMin = val; root.editDirty = false }
|
||||||
onMaxChanged: xhttpScMaxEachPostBytesMax = val
|
onMaxChanged: function(val) { xhttpScMaxEachPostBytesMax = val; root.editDirty = false }
|
||||||
|
onEdited: root.editDirty = true
|
||||||
}
|
}
|
||||||
|
|
||||||
CaptionTextType {
|
CaptionTextType {
|
||||||
@@ -652,8 +740,9 @@ PageType {
|
|||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
minValue: xhttpScStreamUpServerSecsMin
|
minValue: xhttpScStreamUpServerSecsMin
|
||||||
maxValue: xhttpScStreamUpServerSecsMax
|
maxValue: xhttpScStreamUpServerSecsMax
|
||||||
onMinChanged: xhttpScStreamUpServerSecsMin = val
|
onMinChanged: function(val) { xhttpScStreamUpServerSecsMin = val; root.editDirty = false }
|
||||||
onMaxChanged: xhttpScStreamUpServerSecsMax = val
|
onMaxChanged: function(val) { xhttpScStreamUpServerSecsMax = val; root.editDirty = false }
|
||||||
|
onEdited: root.editDirty = true
|
||||||
}
|
}
|
||||||
|
|
||||||
CaptionTextType {
|
CaptionTextType {
|
||||||
@@ -671,8 +760,9 @@ PageType {
|
|||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
minValue: xhttpScMinPostsIntervalMsMin
|
minValue: xhttpScMinPostsIntervalMsMin
|
||||||
maxValue: xhttpScMinPostsIntervalMsMax
|
maxValue: xhttpScMinPostsIntervalMsMax
|
||||||
onMinChanged: xhttpScMinPostsIntervalMsMin = val
|
onMinChanged: function(val) { xhttpScMinPostsIntervalMsMin = val; root.editDirty = false }
|
||||||
onMaxChanged: xhttpScMinPostsIntervalMsMax = val
|
onMaxChanged: function(val) { xhttpScMinPostsIntervalMsMax = val; root.editDirty = false }
|
||||||
|
onEdited: root.editDirty = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Padding and multiplexing ──────────────────────────
|
// ── Padding and multiplexing ──────────────────────────
|
||||||
@@ -728,10 +818,15 @@ PageType {
|
|||||||
anchors.rightMargin: 16
|
anchors.rightMargin: 16
|
||||||
anchors.bottomMargin: 16 + PageController.safeAreaBottomMargin
|
anchors.bottomMargin: 16 + PageController.safeAreaBottomMargin
|
||||||
|
|
||||||
visible: listView.enabled && XrayConfigModel.hasUnsavedChanges
|
visible: listView.enabled && (XrayConfigModel.hasUnsavedChanges || root.editDirty)
|
||||||
enabled: visible
|
enabled: visible
|
||||||
text: qsTr("Save")
|
text: qsTr("Save")
|
||||||
clickedFunc: function () {
|
clickedFunc: function () {
|
||||||
|
var errs = XrayConfigModel.validationErrors()
|
||||||
|
if (errs.length > 0) {
|
||||||
|
PageController.showErrorMessage(errs.join("\n"))
|
||||||
|
return
|
||||||
|
}
|
||||||
var headerText = qsTr("Save settings?")
|
var headerText = qsTr("Save settings?")
|
||||||
var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.")
|
var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.")
|
||||||
var yesButtonText = qsTr("Continue")
|
var yesButtonText = qsTr("Continue")
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import "../Components"
|
|||||||
PageType {
|
PageType {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
property bool editDirty: false
|
||||||
|
|
||||||
BackButtonType {
|
BackButtonType {
|
||||||
id: backButton
|
id: backButton
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
@@ -61,8 +63,9 @@ PageType {
|
|||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
minValue: xPaddingBytesMin
|
minValue: xPaddingBytesMin
|
||||||
maxValue: xPaddingBytesMax
|
maxValue: xPaddingBytesMax
|
||||||
onMinChanged: xPaddingBytesMin = val
|
onMinChanged: function(val) { xPaddingBytesMin = val; root.editDirty = false }
|
||||||
onMaxChanged: xPaddingBytesMax = val
|
onMaxChanged: function(val) { xPaddingBytesMax = val; root.editDirty = false }
|
||||||
|
onEdited: root.editDirty = true
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
@@ -81,7 +84,7 @@ PageType {
|
|||||||
anchors.rightMargin: 16
|
anchors.rightMargin: 16
|
||||||
anchors.bottomMargin: 16 + PageController.safeAreaBottomMargin
|
anchors.bottomMargin: 16 + PageController.safeAreaBottomMargin
|
||||||
|
|
||||||
visible: listView.enabled && XrayConfigModel.hasUnsavedChanges
|
visible: listView.enabled && (XrayConfigModel.hasUnsavedChanges || root.editDirty)
|
||||||
enabled: visible
|
enabled: visible
|
||||||
text: qsTr("Save")
|
text: qsTr("Save")
|
||||||
clickedFunc: function () {
|
clickedFunc: function () {
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import "../Components"
|
|||||||
PageType {
|
PageType {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
property bool editDirty: false
|
||||||
|
|
||||||
BackButtonType {
|
BackButtonType {
|
||||||
id: backButton
|
id: backButton
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
@@ -78,8 +80,13 @@ PageType {
|
|||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
headerText: qsTr("xPaddingKey")
|
headerText: qsTr("xPaddingKey")
|
||||||
textField.text: xPaddingKey
|
textField.text: xPaddingKey
|
||||||
|
textField.validator: RegularExpressionValidator { regularExpression: /^[A-Za-z0-9_-]*$/ }
|
||||||
|
textField.onTextEdited: root.editDirty = (textField.text !== xPaddingKey)
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textField.text !== xPaddingKey) xPaddingKey = textField.text
|
var v = textField.text.trim()
|
||||||
|
if (v !== xPaddingKey) xPaddingKey = v
|
||||||
|
else if (textField.text !== v) textField.text = v
|
||||||
|
root.editDirty = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,13 +97,19 @@ PageType {
|
|||||||
Layout.topMargin: 8
|
Layout.topMargin: 8
|
||||||
headerText: qsTr("xPaddingHeader")
|
headerText: qsTr("xPaddingHeader")
|
||||||
textField.text: xPaddingHeader
|
textField.text: xPaddingHeader
|
||||||
|
textField.validator: RegularExpressionValidator { regularExpression: /^[A-Za-z0-9_-]*$/ }
|
||||||
|
textField.onTextEdited: root.editDirty = (textField.text !== xPaddingHeader)
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textField.text !== xPaddingHeader) xPaddingHeader = textField.text
|
var v = textField.text.trim()
|
||||||
|
if (v !== xPaddingHeader) xPaddingHeader = v
|
||||||
|
else if (textField.text !== v) textField.text = v
|
||||||
|
root.editDirty = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DropDownType {
|
DropDownType {
|
||||||
id: placementDropDown
|
id: placementDropDown
|
||||||
|
fitContent: true
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 8
|
Layout.topMargin: 8
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
@@ -140,6 +153,7 @@ PageType {
|
|||||||
|
|
||||||
DropDownType {
|
DropDownType {
|
||||||
id: methodDropDown
|
id: methodDropDown
|
||||||
|
fitContent: true
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 8
|
Layout.topMargin: 8
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
@@ -197,7 +211,7 @@ PageType {
|
|||||||
anchors.rightMargin: 16
|
anchors.rightMargin: 16
|
||||||
anchors.bottomMargin: 16 + PageController.safeAreaBottomMargin
|
anchors.bottomMargin: 16 + PageController.safeAreaBottomMargin
|
||||||
|
|
||||||
visible: listView.enabled && XrayConfigModel.hasUnsavedChanges
|
visible: listView.enabled && (XrayConfigModel.hasUnsavedChanges || root.editDirty)
|
||||||
enabled: visible
|
enabled: visible
|
||||||
text: qsTr("Save")
|
text: qsTr("Save")
|
||||||
clickedFunc: function () {
|
clickedFunc: function () {
|
||||||
|
|||||||
@@ -15,6 +15,21 @@ import "../Components"
|
|||||||
PageType {
|
PageType {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
property bool editDirty: false
|
||||||
|
|
||||||
|
function clampSigned(text) {
|
||||||
|
if (text === "" || text === "-")
|
||||||
|
return ""
|
||||||
|
var n = parseInt(text, 10)
|
||||||
|
if (isNaN(n))
|
||||||
|
return ""
|
||||||
|
if (n > 2147483647)
|
||||||
|
n = 2147483647
|
||||||
|
if (n < -2147483648)
|
||||||
|
n = -2147483648
|
||||||
|
return String(n)
|
||||||
|
}
|
||||||
|
|
||||||
BackButtonType {
|
BackButtonType {
|
||||||
id: backButton
|
id: backButton
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
@@ -78,8 +93,9 @@ PageType {
|
|||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
minValue: xmuxMaxConcurrencyMin
|
minValue: xmuxMaxConcurrencyMin
|
||||||
maxValue: xmuxMaxConcurrencyMax
|
maxValue: xmuxMaxConcurrencyMax
|
||||||
onMinChanged: xmuxMaxConcurrencyMin = val
|
onMinChanged: function(val) { xmuxMaxConcurrencyMin = val; root.editDirty = false }
|
||||||
onMaxChanged: xmuxMaxConcurrencyMax = val
|
onMaxChanged: function(val) { xmuxMaxConcurrencyMax = val; root.editDirty = false }
|
||||||
|
onEdited: root.editDirty = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// maxConnections
|
// maxConnections
|
||||||
@@ -98,8 +114,9 @@ PageType {
|
|||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
minValue: xmuxMaxConnectionsMin
|
minValue: xmuxMaxConnectionsMin
|
||||||
maxValue: xmuxMaxConnectionsMax
|
maxValue: xmuxMaxConnectionsMax
|
||||||
onMinChanged: xmuxMaxConnectionsMin = val
|
onMinChanged: function(val) { xmuxMaxConnectionsMin = val; root.editDirty = false }
|
||||||
onMaxChanged: xmuxMaxConnectionsMax = val
|
onMaxChanged: function(val) { xmuxMaxConnectionsMax = val; root.editDirty = false }
|
||||||
|
onEdited: root.editDirty = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// cMaxReuseTimes
|
// cMaxReuseTimes
|
||||||
@@ -118,8 +135,9 @@ PageType {
|
|||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
minValue: xmuxCMaxReuseTimesMin
|
minValue: xmuxCMaxReuseTimesMin
|
||||||
maxValue: xmuxCMaxReuseTimesMax
|
maxValue: xmuxCMaxReuseTimesMax
|
||||||
onMinChanged: xmuxCMaxReuseTimesMin = val
|
onMinChanged: function(val) { xmuxCMaxReuseTimesMin = val; root.editDirty = false }
|
||||||
onMaxChanged: xmuxCMaxReuseTimesMax = val
|
onMaxChanged: function(val) { xmuxCMaxReuseTimesMax = val; root.editDirty = false }
|
||||||
|
onEdited: root.editDirty = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// hMaxRequestTimes
|
// hMaxRequestTimes
|
||||||
@@ -138,8 +156,9 @@ PageType {
|
|||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
minValue: xmuxHMaxRequestTimesMin
|
minValue: xmuxHMaxRequestTimesMin
|
||||||
maxValue: xmuxHMaxRequestTimesMax
|
maxValue: xmuxHMaxRequestTimesMax
|
||||||
onMinChanged: xmuxHMaxRequestTimesMin = val
|
onMinChanged: function(val) { xmuxHMaxRequestTimesMin = val; root.editDirty = false }
|
||||||
onMaxChanged: xmuxHMaxRequestTimesMax = val
|
onMaxChanged: function(val) { xmuxHMaxRequestTimesMax = val; root.editDirty = false }
|
||||||
|
onEdited: root.editDirty = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// hMaxReusableSecs
|
// hMaxReusableSecs
|
||||||
@@ -158,8 +177,9 @@ PageType {
|
|||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
minValue: xmuxHMaxReusableSecsMin
|
minValue: xmuxHMaxReusableSecsMin
|
||||||
maxValue: xmuxHMaxReusableSecsMax
|
maxValue: xmuxHMaxReusableSecsMax
|
||||||
onMinChanged: xmuxHMaxReusableSecsMin = val
|
onMinChanged: function(val) { xmuxHMaxReusableSecsMin = val; root.editDirty = false }
|
||||||
onMaxChanged: xmuxHMaxReusableSecsMax = val
|
onMaxChanged: function(val) { xmuxHMaxReusableSecsMax = val; root.editDirty = false }
|
||||||
|
onEdited: root.editDirty = true
|
||||||
}
|
}
|
||||||
|
|
||||||
TextFieldWithHeaderType {
|
TextFieldWithHeaderType {
|
||||||
@@ -168,12 +188,16 @@ PageType {
|
|||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
headerText: qsTr("hKeepAlivePeriod")
|
headerText: qsTr("hKeepAlivePeriod")
|
||||||
|
subtitleText: qsTr("Integer, may be negative")
|
||||||
textField.text: xmuxHKeepAlivePeriod
|
textField.text: xmuxHKeepAlivePeriod
|
||||||
textField.validator: IntValidator {
|
textField.maximumLength: 11
|
||||||
bottom: 0
|
textField.validator: RegularExpressionValidator { regularExpression: /^-?\d*$/ }
|
||||||
}
|
textField.onTextEdited: root.editDirty = (textField.text !== xmuxHKeepAlivePeriod)
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textField.text !== xmuxHKeepAlivePeriod) xmuxHKeepAlivePeriod = textField.text
|
var v = root.clampSigned(textField.text)
|
||||||
|
if (v !== xmuxHKeepAlivePeriod) xmuxHKeepAlivePeriod = v
|
||||||
|
else if (textField.text !== v) textField.text = v
|
||||||
|
root.editDirty = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -194,7 +218,7 @@ PageType {
|
|||||||
anchors.rightMargin: 16
|
anchors.rightMargin: 16
|
||||||
anchors.bottomMargin: 16 + PageController.safeAreaBottomMargin
|
anchors.bottomMargin: 16 + PageController.safeAreaBottomMargin
|
||||||
|
|
||||||
visible: listView.enabled && XrayConfigModel.hasUnsavedChanges
|
visible: listView.enabled && (XrayConfigModel.hasUnsavedChanges || root.editDirty)
|
||||||
enabled: visible
|
enabled: visible
|
||||||
text: qsTr("Save")
|
text: qsTr("Save")
|
||||||
clickedFunc: function () {
|
clickedFunc: function () {
|
||||||
|
|||||||
Reference in New Issue
Block a user