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) } } }