337 lines
12 KiB
QML
337 lines
12 KiB
QML
import QtQuick 2.12
|
|
import QtQuick.Controls 2.12
|
|
import QtQuick.Layouts 1.12
|
|
import "../components"
|
|
|
|
Dialog {
|
|
id: editorDialog
|
|
modal: true
|
|
parent: Overlay.overlay
|
|
title: editId === "" ? "Новая кнопка" : "Редактирование"
|
|
anchors.centerIn: parent
|
|
width: Math.min(520, parent ? parent.width * 0.95 : 520)
|
|
height: Math.min(720, parent ? parent.height * 0.92 : 720)
|
|
standardButtons: Dialog.Ok | Dialog.Cancel
|
|
|
|
readonly property color panelBg: "#161628"
|
|
readonly property color panelFg: "#ffffff"
|
|
readonly property color controlBg: "#2a2a40"
|
|
readonly property bool isVoice: kindCombo.currentIndex === 1
|
|
|
|
palette.button: controlBg
|
|
palette.buttonText: panelFg
|
|
palette.window: panelBg
|
|
palette.windowText: panelFg
|
|
palette.text: panelFg
|
|
palette.base: "#222236"
|
|
palette.highlight: "#4a4a70"
|
|
palette.highlightedText: panelFg
|
|
|
|
background: Rectangle {
|
|
color: panelBg
|
|
radius: 4
|
|
}
|
|
|
|
property string editId: ""
|
|
property bool responseCheckWasEnabled: false
|
|
|
|
function openForCreate() {
|
|
editId = ""
|
|
labelF.text = ""
|
|
urlF.text = ""
|
|
successF.text = ""
|
|
errorF.text = "Ошибка"
|
|
pendingF.text = "..."
|
|
colorF.text = "#7b007b"
|
|
colorPicker.hex = "#7b007b"
|
|
iconCombo.model = btnController.listIcons()
|
|
iconCombo.currentIndex = 0
|
|
typeCombo.currentIndex = 0
|
|
kindCombo.currentIndex = 0
|
|
holdSwitch.checked = true
|
|
holdSlider.value = 800
|
|
latchSwitch.checked = true
|
|
resetUrlF.text = ""
|
|
successMatchF.text = "OK"
|
|
fireAndForgetSwitch.checked = false
|
|
responseCheckSwitch.checked = false
|
|
responseCheckWasEnabled = false
|
|
cancelUrlF.text = ""
|
|
sourceIdF.text = "panel-01"
|
|
apiKeyF.text = ""
|
|
open()
|
|
}
|
|
|
|
function openForEdit(o) {
|
|
editId = o.btnId
|
|
labelF.text = o.label
|
|
urlF.text = o.actionUrl
|
|
successF.text = o.successText
|
|
errorF.text = o.errorText
|
|
pendingF.text = o.pendingText || "..."
|
|
colorF.text = o.btnColor || "#7b007b"
|
|
colorPicker.hex = o.btnColor || "#7b007b"
|
|
iconCombo.model = btnController.listIcons()
|
|
var ip = o.iconPath || ""
|
|
var fname = ip
|
|
var slash = ip.lastIndexOf("/")
|
|
if (slash >= 0) fname = ip.substring(slash + 1)
|
|
var idx = iconCombo.model.indexOf(fname)
|
|
iconCombo.currentIndex = idx >= 0 ? idx : 0
|
|
typeCombo.currentIndex = (o.actionType === "http_post") ? 1 : 0
|
|
kindCombo.currentIndex = (o.kind === "voice") ? 1 : 0
|
|
holdSwitch.checked = (o.triggerMode !== "click")
|
|
holdSlider.value = (o.holdMs && o.holdMs > 0) ? o.holdMs : 800
|
|
latchSwitch.checked = !!o.latchResetEnabled
|
|
resetUrlF.text = o.latchResetUrl || ""
|
|
successMatchF.text = o.latchSuccessMatch || "OK"
|
|
fireAndForgetSwitch.checked = !!o.latchFireAndForget
|
|
responseCheckSwitch.checked = !!o.responseCheckEnabled
|
|
responseCheckWasEnabled = responseCheckSwitch.checked
|
|
cancelUrlF.text = o.voiceCancelUrl || ""
|
|
sourceIdF.text = o.voiceSourceId || "panel-01"
|
|
apiKeyF.text = o.apiKey || ""
|
|
open()
|
|
}
|
|
|
|
contentItem: ScrollView {
|
|
id: scroll
|
|
clip: true
|
|
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
|
|
|
ColumnLayout {
|
|
width: scroll.availableWidth
|
|
spacing: 6
|
|
TextField {
|
|
id: labelF
|
|
Layout.fillWidth: true
|
|
placeholderText: "Подпись"
|
|
}
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
Text { text: "Тип:"; color: editorDialog.panelFg }
|
|
ComboBox {
|
|
id: kindCombo
|
|
Layout.fillWidth: true
|
|
model: ["Нажатие", "Голос (запись)"]
|
|
}
|
|
}
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
Text { text: "Иконка:"; color: editorDialog.panelFg }
|
|
ComboBox {
|
|
id: iconCombo
|
|
Layout.fillWidth: true
|
|
model: btnController.listIcons()
|
|
}
|
|
Button {
|
|
text: "\u21BB"
|
|
onClicked: iconCombo.model = btnController.listIcons()
|
|
}
|
|
}
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
visible: !editorDialog.isVoice
|
|
Text { text: "Метод:"; color: editorDialog.panelFg }
|
|
ComboBox {
|
|
id: typeCombo
|
|
model: ["http_get", "http_post"]
|
|
}
|
|
}
|
|
TextField {
|
|
id: urlF
|
|
Layout.fillWidth: true
|
|
placeholderText: editorDialog.isVoice ? "URL classify-audio" : "URL"
|
|
}
|
|
|
|
ColumnLayout {
|
|
Layout.fillWidth: true
|
|
spacing: 6
|
|
visible: editorDialog.isVoice
|
|
TextField {
|
|
id: cancelUrlF
|
|
Layout.fillWidth: true
|
|
placeholderText: "URL отмены (cancel)"
|
|
}
|
|
TextField {
|
|
id: sourceIdF
|
|
Layout.fillWidth: true
|
|
placeholderText: "source_id устройства"
|
|
}
|
|
TextField {
|
|
id: apiKeyF
|
|
Layout.fillWidth: true
|
|
placeholderText: "X-API-Key"
|
|
echoMode: TextInput.Password
|
|
}
|
|
Text {
|
|
Layout.fillWidth: true
|
|
text: "Удержание: запись пока зажата. Клик: старт/стоп записи. Повторное нажатие на горящей кнопке — отмена."
|
|
color: "#aaaaaa"
|
|
font.pixelSize: 10
|
|
wrapMode: Text.WordWrap
|
|
}
|
|
}
|
|
|
|
ColumnLayout {
|
|
Layout.fillWidth: true
|
|
spacing: 6
|
|
visible: !editorDialog.isVoice
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
Text { text: "Сброс повторным нажатием:"; color: editorDialog.panelFg }
|
|
Switch { id: latchSwitch; checked: true }
|
|
}
|
|
Text {
|
|
Layout.fillWidth: true
|
|
visible: latchSwitch.checked
|
|
text: "Первое нажатие — URL выше. Успех, если в ответе есть указанный текст или HTTP < 400. Зелёный статус держится до повторного нажатия."
|
|
color: "#aaaaaa"
|
|
font.pixelSize: 10
|
|
wrapMode: Text.WordWrap
|
|
}
|
|
TextField {
|
|
id: resetUrlF
|
|
Layout.fillWidth: true
|
|
visible: latchSwitch.checked
|
|
placeholderText: "URL сброса"
|
|
}
|
|
TextField {
|
|
id: successMatchF
|
|
Layout.fillWidth: true
|
|
visible: latchSwitch.checked
|
|
placeholderText: "Успех если в ответе (OK)"
|
|
}
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
visible: latchSwitch.checked
|
|
Text {
|
|
text: "Мгновенный отклик:"
|
|
color: editorDialog.panelFg
|
|
Layout.fillWidth: true
|
|
wrapMode: Text.WordWrap
|
|
}
|
|
Switch { id: fireAndForgetSwitch }
|
|
}
|
|
Text {
|
|
Layout.fillWidth: true
|
|
visible: latchSwitch.checked && fireAndForgetSwitch.checked
|
|
text: "Статус обновляется сразу после нажатия; команда на сервер уходит параллельно."
|
|
color: "#aaaaaa"
|
|
font.pixelSize: 10
|
|
wrapMode: Text.WordWrap
|
|
}
|
|
}
|
|
|
|
TextField {
|
|
id: successF
|
|
Layout.fillWidth: true
|
|
placeholderText: editorDialog.isVoice ? "Текст успеха (пусто = категория)" : "Текст успеха"
|
|
}
|
|
TextField {
|
|
id: errorF
|
|
Layout.fillWidth: true
|
|
placeholderText: "Текст ошибки"
|
|
}
|
|
TextField {
|
|
id: pendingF
|
|
Layout.fillWidth: true
|
|
placeholderText: editorDialog.isVoice ? "Текст записи (Слушаю...)" : "Текст ожидания"
|
|
}
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
ColorPickerField {
|
|
id: colorPicker
|
|
label: "Цвет:"
|
|
onColorChanged: colorF.text = hex
|
|
}
|
|
TextField {
|
|
id: colorF
|
|
visible: false
|
|
}
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
Text {
|
|
text: editorDialog.isVoice ? "Жест записи:" : "Срабатывание:"
|
|
color: editorDialog.panelFg
|
|
}
|
|
Switch {
|
|
id: holdSwitch
|
|
checked: true
|
|
}
|
|
Text {
|
|
text: holdSwitch.checked
|
|
? (editorDialog.isVoice ? "Удержание + речь" : "Удержание")
|
|
: (editorDialog.isVoice ? "Клик старт/стоп" : "Клик")
|
|
color: "#cccccc"
|
|
}
|
|
Item { Layout.fillWidth: true }
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
visible: holdSwitch.checked && !editorDialog.isVoice
|
|
Text {
|
|
text: "Длительность: " + Math.round(holdSlider.value) + " мс"
|
|
color: editorDialog.panelFg
|
|
Layout.preferredWidth: 180
|
|
}
|
|
Slider {
|
|
id: holdSlider
|
|
Layout.fillWidth: true
|
|
from: 200; to: 3000; stepSize: 50
|
|
value: 800
|
|
}
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
visible: !editorDialog.isVoice && !latchSwitch.checked
|
|
Text { text: "Расширенная проверка:"; color: editorDialog.panelFg }
|
|
Switch { id: responseCheckSwitch }
|
|
Text {
|
|
Layout.fillWidth: true
|
|
text: "Правила JSON — в веб-конфигураторе"
|
|
color: "#cccccc"
|
|
font.pixelSize: 11
|
|
wrapMode: Text.WordWrap
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
onAccepted: {
|
|
var mode = holdSwitch.checked ? "hold" : "click"
|
|
var hold = Math.round(holdSlider.value)
|
|
var kind = editorDialog.isVoice ? "voice" : "press"
|
|
var latchOn = !editorDialog.isVoice && latchSwitch.checked
|
|
var resetUrl = resetUrlF.text.trim()
|
|
var successMatch = successMatchF.text.trim() || "OK"
|
|
var fireAndForget = latchOn && fireAndForgetSwitch.checked
|
|
var cancelUrl = cancelUrlF.text.trim()
|
|
var sourceId = sourceIdF.text.trim() || "panel-01"
|
|
var apiKey = apiKeyF.text.trim()
|
|
var actionType = editorDialog.isVoice ? "http_post" : typeCombo.currentText
|
|
if (editId === "") {
|
|
btnController.addButton(labelF.text, iconCombo.currentText,
|
|
actionType, urlF.text,
|
|
successF.text, errorF.text, pendingF.text,
|
|
colorF.text, mode, hold,
|
|
latchOn, resetUrl, successMatch, fireAndForget,
|
|
kind, cancelUrl, sourceId, 30000, apiKey)
|
|
} else {
|
|
btnController.updateButton(editId, labelF.text, iconCombo.currentText,
|
|
actionType, urlF.text,
|
|
successF.text, errorF.text, pendingF.text,
|
|
colorF.text, mode, hold,
|
|
latchOn, resetUrl, successMatch, fireAndForget,
|
|
kind, cancelUrl, sourceId, 30000, apiKey)
|
|
if (!latchOn && !editorDialog.isVoice
|
|
&& responseCheckSwitch.checked !== responseCheckWasEnabled)
|
|
btnController.setResponseCheckEnabled(editId, responseCheckSwitch.checked)
|
|
}
|
|
}
|
|
}
|