import { DarkLineEdit } from "../components/dark_line_edit.slint"; import { Theme } from "../theme.slint"; import { DarkButton } from "../components/dark_button.slint"; import { DarkComboBox } from "../components/dark_combo_box.slint"; import { FlasherBridge } from "../globals.slint"; export component TabFlasher inherits Rectangle { background: Theme.bg-primary; VerticalLayout { padding: 20px; spacing: 16px; alignment: start; Text { text: "Firmware Flasher"; color: Theme.fg-primary; font-size: 20px; font-weight: 700; } Text { text: "Flash firmware via ESP32 programming port (CH340/CP2102)."; color: Theme.fg-secondary; font-size: 12px; wrap: word-wrap; } // Port selection Rectangle { background: Theme.bg-secondary; border-radius: 8px; VerticalLayout { padding: 16px; spacing: 10px; Text { text: "Programming Port (CH340/CP210x only)"; color: Theme.accent-cyan; font-size: 14px; font-weight: 600; } HorizontalLayout { spacing: 8px; if FlasherBridge.prog-ports.length > 0 : DarkComboBox { horizontal-stretch: 1; model: FlasherBridge.prog-ports; selected(value) => { FlasherBridge.selected-prog-port = value; } } if FlasherBridge.prog-ports.length == 0 : DarkLineEdit { horizontal-stretch: 1; text <=> FlasherBridge.selected-prog-port; placeholder-text: "/dev/ttyUSB0"; } DarkButton { text: "Refresh"; clicked => { FlasherBridge.refresh-prog-ports(); } } } if FlasherBridge.prog-ports.length == 0 : Text { text: "No CH340/CP210x port detected. Enter path manually or plug in the programming cable."; color: Theme.accent-yellow; font-size: 11px; wrap: word-wrap; } } } // Flash target partition Rectangle { background: Theme.bg-secondary; border-radius: 8px; VerticalLayout { padding: 16px; spacing: 10px; Text { text: "Target Partition"; color: Theme.accent-cyan; font-size: 14px; font-weight: 600; } DarkComboBox { model: ["full (0x0)", "factory (0x20000)", "ota_0 (0x220000)"]; current-index <=> FlasherBridge.flash-offset-index; } } } // Firmware file Rectangle { background: Theme.bg-secondary; border-radius: 8px; VerticalLayout { padding: 16px; spacing: 10px; Text { text: "Firmware File"; color: Theme.accent-cyan; font-size: 14px; font-weight: 600; } HorizontalLayout { spacing: 8px; Text { horizontal-stretch: 1; text: FlasherBridge.firmware-path != "" ? FlasherBridge.firmware-path : "No file selected"; color: FlasherBridge.firmware-path != "" ? Theme.fg-primary : Theme.fg-secondary; font-size: 12px; vertical-alignment: center; overflow: elide; } DarkButton { text: "Browse..."; clicked => { FlasherBridge.browse-firmware(); } } } } } // Flash button + progress Rectangle { background: Theme.bg-secondary; border-radius: 8px; VerticalLayout { padding: 16px; spacing: 10px; HorizontalLayout { spacing: 12px; DarkButton { text: FlasherBridge.flashing ? "Flashing..." : "Flash Firmware"; enabled: !FlasherBridge.flashing && FlasherBridge.firmware-path != "" && FlasherBridge.selected-prog-port != ""; clicked => { FlasherBridge.flash(); } } Text { text: FlasherBridge.flash-status; color: Theme.fg-primary; font-size: 12px; vertical-alignment: center; horizontal-stretch: 1; } } if FlasherBridge.flashing || FlasherBridge.flash-progress > 0 : Rectangle { height: 20px; background: Theme.bg-primary; border-radius: 4px; Rectangle { x: 0; width: parent.width * clamp(FlasherBridge.flash-progress, 0, 1); height: 100%; background: FlasherBridge.flash-progress >= 1.0 ? Theme.accent-green : Theme.accent-purple; border-radius: 4px; } Text { text: round(FlasherBridge.flash-progress * 100) + "%"; color: Theme.fg-primary; font-size: 11px; horizontal-alignment: center; vertical-alignment: center; } } } } // Warning Rectangle { background: #ff555520; border-radius: 8px; border-width: 1px; border-color: Theme.accent-red; VerticalLayout { padding: 12px; spacing: 4px; Text { text: "Warning"; color: Theme.accent-red; font-size: 13px; font-weight: 600; } Text { text: "Do not disconnect the keyboard during flashing. The device will reboot automatically when done."; color: Theme.fg-secondary; font-size: 11px; wrap: word-wrap; } } } Rectangle { vertical-stretch: 1; } } }