summaryrefslogtreecommitdiff
path: root/crates/flasher-qt/qml/Main.qml
diff options
context:
space:
mode:
Diffstat (limited to 'crates/flasher-qt/qml/Main.qml')
-rw-r--r--crates/flasher-qt/qml/Main.qml437
1 files changed, 437 insertions, 0 deletions
diff --git a/crates/flasher-qt/qml/Main.qml b/crates/flasher-qt/qml/Main.qml
new file mode 100644
index 0000000..805c6ed
--- /dev/null
+++ b/crates/flasher-qt/qml/Main.qml
@@ -0,0 +1,437 @@
+import QtQuick
+import QtQuick.Controls as Controls
+import QtQuick.Layouts
+import QtQuick.Dialogs
+import org.kde.kirigami as Kirigami
+
+Kirigami.ApplicationWindow {
+ id: root
+ visible: true
+ width: 760
+ height: 820
+ minimumWidth: 600
+ minimumHeight: 580
+ title: qsTr("ALT Image Writer")
+
+ readonly property string supportedBoardsText: @SUPPORTED_BOARDS@
+ readonly property string availableDrivesText: @AVAILABLE_DRIVES@
+ property bool sourceIsImage: false
+ property bool outputIsFile: false
+ property string sourcePath: ""
+ property string outputPath: ""
+ property string selectedBoard: ""
+ property string selectedPlatform: ""
+ property var driveNames: availableDrivesText.length ? availableDrivesText.split("\n") : []
+
+ function localPath(url) {
+ var value = url.toString()
+ return value.startsWith("file://") ? decodeURIComponent(value.substring(7)) : value
+ }
+
+ function fileName(path) {
+ var parts = path.split("/")
+ return parts[parts.length - 1]
+ }
+
+ function ready() {
+ return sourcePath.length > 0 && selectedBoard.length > 0
+ && (outputIsFile ? outputPath.length > 0 : driveBox.currentIndex >= 0 && driveNames.length > 0)
+ }
+
+ globalDrawer: Kirigami.GlobalDrawer {
+ isMenu: true
+ actions: [
+ Kirigami.Action {
+ text: qsTr("About ALT Image Writer")
+ icon.name: "help-about"
+ onTriggered: aboutDialog.open()
+ }
+ ]
+ }
+
+ pageStack.initialPage: Kirigami.ScrollablePage {
+ title: qsTr("Create Installation Media")
+ actions: [
+ Kirigami.Action {
+ text: qsTr("About")
+ icon.name: "help-about"
+ displayHint: Kirigami.DisplayHint.IconOnly
+ onTriggered: aboutDialog.open()
+ }
+ ]
+
+ ColumnLayout {
+ width: Math.min(parent.width, Kirigami.Units.gridUnit * 42)
+ anchors.horizontalCenter: parent.horizontalCenter
+ spacing: Kirigami.Units.largeSpacing
+
+ Controls.Label {
+ text: qsTr("Prepare an ALT Linux root filesystem or disk image for your board.")
+ color: Kirigami.Theme.disabledTextColor
+ horizontalAlignment: Text.AlignHCenter
+ wrapMode: Text.WordWrap
+ Layout.fillWidth: true
+ }
+
+ Kirigami.AbstractCard {
+ Layout.fillWidth: true
+ contentItem: ColumnLayout {
+ spacing: Kirigami.Units.smallSpacing
+ Kirigami.Heading { text: qsTr("Installation Source"); level: 3 }
+ Controls.Label {
+ text: qsTr("Choose a root filesystem archive or a ready-made disk image.")
+ color: Kirigami.Theme.disabledTextColor
+ wrapMode: Text.WordWrap
+ Layout.fillWidth: true
+ }
+ Kirigami.FormLayout {
+ Layout.fillWidth: true
+ RowLayout {
+ Kirigami.FormData.label: qsTr("Source type:")
+ Controls.RadioButton {
+ text: qsTr("Root filesystem")
+ checked: true
+ onCheckedChanged: if (checked) sourceIsImage = false
+ }
+ Controls.RadioButton {
+ text: qsTr("Disk image")
+ onCheckedChanged: if (checked) {
+ sourceIsImage = true
+ driveOutput.checked = true
+ }
+ }
+ }
+ RowLayout {
+ Kirigami.FormData.label: qsTr("Source:")
+ Controls.Label {
+ text: sourcePath.length ? fileName(sourcePath) : qsTr("No source selected")
+ elide: Text.ElideMiddle
+ Layout.fillWidth: true
+ }
+ Controls.Button {
+ text: qsTr("Choose…")
+ icon.name: "document-open"
+ onClicked: sourceDialog.open()
+ }
+ }
+ }
+ Controls.Label {
+ text: sourcePath.length ? sourcePath
+ : sourceIsImage ? qsTr("Supported: .img, .img.xz")
+ : qsTr("Supported: .tar, .tar.gz, .tar.xz")
+ color: Kirigami.Theme.disabledTextColor
+ elide: Text.ElideMiddle
+ Layout.fillWidth: true
+ }
+ }
+ }
+
+ Kirigami.AbstractCard {
+ Layout.fillWidth: true
+ contentItem: ColumnLayout {
+ Kirigami.Heading { text: qsTr("Target Hardware"); level: 3 }
+ Controls.Label {
+ text: qsTr("Boards are grouped by platform, SoC family, and architecture.")
+ color: Kirigami.Theme.disabledTextColor
+ wrapMode: Text.WordWrap
+ Layout.fillWidth: true
+ }
+ RowLayout {
+ Layout.fillWidth: true
+ Kirigami.Icon {
+ source: "computer-symbolic"
+ implicitWidth: Kirigami.Units.iconSizes.medium
+ implicitHeight: implicitWidth
+ }
+ ColumnLayout {
+ spacing: 0
+ Layout.fillWidth: true
+ Controls.Label {
+ text: selectedBoard.length ? selectedBoard : qsTr("No board selected")
+ font.bold: selectedBoard.length > 0
+ }
+ Controls.Label {
+ text: selectedBoard.length ? selectedPlatform : qsTr("Choose a supported target board")
+ color: Kirigami.Theme.disabledTextColor
+ }
+ }
+ Controls.Button {
+ text: qsTr("Select Board…")
+ onClicked: boardDialog.open()
+ }
+ }
+ }
+ }
+
+ Kirigami.AbstractCard {
+ Layout.fillWidth: true
+ contentItem: ColumnLayout {
+ Kirigami.Heading { text: qsTr("Destination"); level: 3 }
+ Controls.Label {
+ text: qsTr("Write to removable media or create an image file.")
+ color: Kirigami.Theme.disabledTextColor
+ }
+ Kirigami.FormLayout {
+ Layout.fillWidth: true
+ RowLayout {
+ Kirigami.FormData.label: qsTr("Output type:")
+ Controls.RadioButton {
+ id: driveOutput
+ text: qsTr("Drive")
+ checked: true
+ onCheckedChanged: if (checked) outputIsFile = false
+ }
+ Controls.RadioButton {
+ text: qsTr("Image file")
+ enabled: !sourceIsImage
+ onCheckedChanged: if (checked) outputIsFile = true
+ }
+ }
+ Controls.ComboBox {
+ id: driveBox
+ visible: !outputIsFile
+ Kirigami.FormData.label: qsTr("Storage device:")
+ Layout.fillWidth: true
+ model: driveNames
+ displayText: currentIndex < 0 || driveNames.length === 0
+ ? qsTr("No removable drives detected") : currentText
+ enabled: driveNames.length > 0
+ }
+ RowLayout {
+ visible: outputIsFile
+ Kirigami.FormData.label: qsTr("Image file:")
+ Controls.Label {
+ text: outputPath.length ? outputPath : qsTr("Choose where to save the image")
+ elide: Text.ElideMiddle
+ Layout.fillWidth: true
+ }
+ Controls.Button {
+ text: qsTr("Choose…")
+ icon.name: "document-save"
+ onClicked: outputDialog.open()
+ }
+ }
+ }
+ }
+ }
+
+ Kirigami.AbstractCard {
+ Layout.fillWidth: true
+ contentItem: ColumnLayout {
+ Kirigami.Heading { text: qsTr("Options"); level: 3 }
+ Kirigami.FormLayout {
+ Layout.fillWidth: true
+ Controls.ComboBox {
+ Kirigami.FormData.label: qsTr("Root filesystem:")
+ model: ["ext4", "f2fs"]
+ }
+ Controls.CheckBox {
+ Kirigami.FormData.label: qsTr("Resize root partition:")
+ text: qsTr("Grow to fill the destination")
+ checked: true
+ }
+ Controls.CheckBox {
+ Kirigami.FormData.label: qsTr("Boot partition:")
+ text: qsTr("Create a separate 512 MiB partition")
+ }
+ Controls.CheckBox {
+ Kirigami.FormData.label: qsTr("Encryption:")
+ text: qsTr("Encrypt root partition with LUKS")
+ }
+ Controls.CheckBox {
+ Kirigami.FormData.label: qsTr("Serial console:")
+ text: qsTr("Add to boot arguments")
+ }
+ Controls.ComboBox {
+ Kirigami.FormData.label: qsTr("EFI system partition:")
+ model: [qsTr("None"), qsTr("EFI (GPT)"), qsTr("EFI (MBR)")]
+ }
+ Controls.ComboBox {
+ Kirigami.FormData.label: qsTr("First-boot VNC:")
+ model: [qsTr("Use image default"), qsTr("Enabled"), qsTr("Disabled")]
+ }
+ }
+ }
+ }
+
+ Kirigami.InlineMessage {
+ id: previewMessage
+ Layout.fillWidth: true
+ type: Kirigami.MessageType.Information
+ text: qsTr("Flashing is not implemented in this interface preview.")
+ showCloseButton: true
+ }
+ }
+ }
+
+ footer: Controls.ToolBar {
+ contentItem: RowLayout {
+ Controls.Label {
+ text: qsTr("Nothing will be written until you confirm.")
+ color: Kirigami.Theme.disabledTextColor
+ Layout.leftMargin: Kirigami.Units.largeSpacing
+ Layout.fillWidth: true
+ }
+ Controls.Button {
+ text: qsTr("Review and Write")
+ icon.name: "media-flash-sd-mmc"
+ highlighted: true
+ enabled: root.ready()
+ Layout.rightMargin: Kirigami.Units.largeSpacing
+ onClicked: reviewDialog.open()
+ }
+ }
+ }
+
+ Controls.Dialog {
+ id: boardDialog
+ parent: Controls.Overlay.overlay
+ anchors.centerIn: parent
+ width: Math.min(root.width - Kirigami.Units.gridUnit * 2, Kirigami.Units.gridUnit * 34)
+ height: Math.min(root.height - Kirigami.Units.gridUnit * 4, Kirigami.Units.gridUnit * 34)
+ modal: true
+ title: qsTr("Select Target Board")
+ standardButtons: Controls.Dialog.Cancel
+
+ ListModel { id: allBoards }
+ ListModel { id: filteredBoards }
+
+ function populate() {
+ if (allBoards.count > 0) return
+ var platform = "Other"
+ var lines = supportedBoardsText.split("\n")
+ for (var i = 0; i < lines.length; ++i) {
+ var line = lines[i].trim()
+ if (!line.length) continue
+ if (line.endsWith(":")) {
+ platform = line.substring(0, line.length - 1)
+ } else {
+ var names = line.split(/\s+/)
+ for (var j = 0; j < names.length; ++j)
+ allBoards.append({ boardName: names[j], platformName: platform })
+ }
+ }
+ filter("")
+ }
+
+ function filter(query) {
+ filteredBoards.clear()
+ var needle = query.toLowerCase()
+ for (var i = 0; i < allBoards.count; ++i) {
+ var item = allBoards.get(i)
+ if (!needle.length || item.boardName.toLowerCase().includes(needle)
+ || item.platformName.toLowerCase().includes(needle))
+ filteredBoards.append({ boardName: item.boardName, platformName: item.platformName })
+ }
+ }
+
+ onOpened: {
+ populate()
+ searchField.forceActiveFocus()
+ }
+
+ contentItem: ColumnLayout {
+ Controls.TextField {
+ id: searchField
+ Layout.fillWidth: true
+ placeholderText: qsTr("Search boards or platforms…")
+ onTextChanged: boardDialog.filter(text)
+ }
+ Controls.Label {
+ text: qsTr("%1 matching boards").arg(filteredBoards.count)
+ color: Kirigami.Theme.disabledTextColor
+ }
+ ListView {
+ id: boardList
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ clip: true
+ model: filteredBoards
+ section.property: "platformName"
+ section.criteria: ViewSection.FullString
+ section.delegate: Kirigami.ListSectionHeader { text: section }
+ delegate: Controls.ItemDelegate {
+ required property string boardName
+ required property string platformName
+ width: boardList.width
+ text: boardName
+ icon.name: "computer-symbolic"
+ onClicked: {
+ selectedBoard = boardName
+ selectedPlatform = platformName
+ boardDialog.close()
+ }
+ }
+ Controls.ScrollBar.vertical: Controls.ScrollBar {}
+ }
+ }
+ }
+
+ Controls.Dialog {
+ id: reviewDialog
+ parent: Controls.Overlay.overlay
+ anchors.centerIn: parent
+ modal: true
+ title: qsTr("Ready to write?")
+ standardButtons: Controls.Dialog.Cancel | Controls.Dialog.Ok
+ onAccepted: previewMessage.visible = true
+ contentItem: Kirigami.FormLayout {
+ Controls.Label { Kirigami.FormData.label: qsTr("Board:"); text: selectedBoard }
+ Controls.Label { Kirigami.FormData.label: qsTr("Platform:"); text: selectedPlatform }
+ Controls.Label {
+ Kirigami.FormData.label: qsTr("Destination:")
+ text: outputIsFile ? outputPath : driveBox.currentText
+ elide: Text.ElideMiddle
+ }
+ Controls.Label {
+ text: qsTr("This is an interface preview. No data will be written.")
+ color: Kirigami.Theme.disabledTextColor
+ wrapMode: Text.WordWrap
+ }
+ }
+ }
+
+ Controls.Dialog {
+ id: aboutDialog
+ parent: Controls.Overlay.overlay
+ anchors.centerIn: parent
+ modal: true
+ title: qsTr("About ALT Image Writer")
+ standardButtons: Controls.Dialog.Ok
+ contentItem: ColumnLayout {
+ Kirigami.Icon {
+ source: "media-flash-sd-mmc"
+ implicitWidth: Kirigami.Units.iconSizes.huge
+ implicitHeight: implicitWidth
+ Layout.alignment: Qt.AlignHCenter
+ }
+ Kirigami.Heading { text: qsTr("ALT Image Writer"); level: 2; Layout.alignment: Qt.AlignHCenter }
+ Controls.Label { text: qsTr("Version 0.1.0"); Layout.alignment: Qt.AlignHCenter }
+ Controls.Label {
+ text: qsTr("Prepare ALT Linux installation media for supported single-board computers.")
+ wrapMode: Text.WordWrap
+ horizontalAlignment: Text.AlignHCenter
+ Layout.preferredWidth: Kirigami.Units.gridUnit * 22
+ }
+ }
+ }
+
+ FileDialog {
+ id: sourceDialog
+ title: qsTr("Choose Installation Source")
+ fileMode: FileDialog.OpenFile
+ nameFilters: sourceIsImage
+ ? [qsTr("Disk images (*.img *.img.xz)"), qsTr("All files (*)")]
+ : [qsTr("Root filesystem archives (*.tar *.tar.gz *.tar.xz)"), qsTr("All files (*)")]
+ onAccepted: sourcePath = localPath(selectedFile)
+ }
+
+ FileDialog {
+ id: outputDialog
+ title: qsTr("Create Disk Image")
+ fileMode: FileDialog.SaveFile
+ defaultSuffix: "img"
+ nameFilters: [qsTr("Raw disk images (*.img)"), qsTr("All files (*)")]
+ onAccepted: outputPath = localPath(selectedFile)
+ }
+}