From 9b85c68aaf4e1b5aa58d409d52af1fa8d436be4f Mon Sep 17 00:00:00 2001 From: Arnaud Date: Sun, 22 Feb 2026 16:26:21 +0400 Subject: [PATCH] Add components --- src/qml/DebugPanel.qml | 91 ++++++++++++++++++++++ src/qml/NodeHeader.qml | 147 ++++++++++++++++++++++++++++++++++++ src/qml/StatusWidgets.qml | 155 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 393 insertions(+) create mode 100644 src/qml/DebugPanel.qml create mode 100644 src/qml/NodeHeader.qml create mode 100644 src/qml/StatusWidgets.qml diff --git a/src/qml/DebugPanel.qml b/src/qml/DebugPanel.qml new file mode 100644 index 0000000..19e0a59 --- /dev/null +++ b/src/qml/DebugPanel.qml @@ -0,0 +1,91 @@ +import QtQuick +import QtQuick.Layouts +import Logos.Theme +import Logos.Controls + +// qmllint disable unqualified +Rectangle { + id: root + + property var backend + property bool running: false + + color: Theme.palette.backgroundElevated + border.color: Theme.palette.borderSecondary + border.width: 1 + + ColumnLayout { + anchors.fill: parent + spacing: 0 + + RowLayout { + Layout.fillWidth: true + Layout.leftMargin: 10 + Layout.topMargin: 6 + Layout.bottomMargin: 4 + spacing: 6 + + LogosStorageButton { + text: "Debug" + enabled: root.running + onClicked: root.backend.tryDebug() + } + LogosStorageButton { + text: "Peer ID" + enabled: root.running + onClicked: root.backend.showPeerId() + } + LogosStorageButton { + text: "Data dir" + enabled: root.running + onClicked: root.backend.dataDir() + } + LogosStorageButton { + text: "SPR" + enabled: root.running + onClicked: root.backend.spr() + } + LogosStorageButton { + text: "Version" + enabled: root.running + onClicked: root.backend.version() + } + + Item { + Layout.fillWidth: true + } + } + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: Theme.palette.borderSecondary + } + + Flickable { + id: logFlick + Layout.fillWidth: true + Layout.fillHeight: true + clip: true + contentWidth: width + contentHeight: debugText.paintedHeight + + TextEdit { + id: debugText + width: logFlick.width + text: root.backend.debugLogs + color: Theme.palette.textSecondary + font.family: "monospace" + font.pixelSize: 11 + wrapMode: Text.WrapAnywhere + readOnly: true + padding: 8 + bottomPadding: 20 + + onTextChanged: Qt.callLater(function () { + logFlick.contentY = Math.max(0, logFlick.contentHeight - logFlick.height) + }) + } + } + } +} diff --git a/src/qml/NodeHeader.qml b/src/qml/NodeHeader.qml new file mode 100644 index 0000000..8f31498 --- /dev/null +++ b/src/qml/NodeHeader.qml @@ -0,0 +1,147 @@ +import QtQuick +import QtQuick.Layouts +import Logos.Theme +import Logos.Controls + +// qmllint disable unqualified +RowLayout { + id: root + + property var backend + property bool nodeIsUp: false + property bool blinkOn: false + + readonly property int stopped: 0 + readonly property int starting: 1 + readonly property int running: 2 + readonly property int stopping: 3 + readonly property int destroyed: 4 + + signal settingsRequested() + + spacing: Theme.spacing.medium + + StorageIcon { + animated: root.backend.status === root.starting + || root.backend.status === root.stopping + dotColor: { + if (root.backend.status === root.starting) + return Theme.palette.warning + if (root.backend.status !== root.running) + return Theme.palette.textMuted + return root.nodeIsUp ? Theme.palette.success : Theme.palette.error + } + } + + ColumnLayout { + spacing: 6 + + LogosText { + text: "Logos Storage" + font.pixelSize: Theme.typography.titleText + } + + RowLayout { + spacing: 7 + + Rectangle { + Layout.preferredWidth: 7 + Layout.preferredHeight: 7 + radius: 3.5 + Layout.alignment: Qt.AlignVCenter + color: { + if (root.backend.status === root.starting) + return Theme.palette.warning + if (root.backend.status !== root.running) + return Theme.palette.textMuted + return root.nodeIsUp ? Theme.palette.success : Theme.palette.error + } + opacity: root.backend.status === root.running + ? (root.blinkOn ? 1.0 : 0.15) : 1.0 + } + + LogosText { + text: { + switch (root.backend.status) { + case root.stopped: return "Stopped" + case root.starting: return "Starting…" + case root.running: return "Running" + case root.stopping: return "Stopping…" + case root.destroyed: return "Not initialised" + default: return "" + } + } + font.pixelSize: Theme.typography.primaryText + color: Theme.palette.textSecondary + Layout.alignment: Qt.AlignVCenter + } + } + } + + Item { + Layout.fillWidth: true + } + + Rectangle { + Layout.preferredWidth: 44 + Layout.preferredHeight: 44 + radius: 8 + color: settingsHover.hovered ? Theme.palette.backgroundElevated : "transparent" + border.color: Theme.palette.borderSecondary + border.width: 1 + + SettingsIcon { + anchors.centerIn: parent + dotColor: Theme.palette.text + dotSize: 5 + dotSpacing: 2 + } + + HoverHandler { + id: settingsHover + } + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: root.settingsRequested() + } + } + + Rectangle { + Layout.preferredWidth: 44 + Layout.preferredHeight: 44 + radius: 8 + color: startStopHover.hovered ? Theme.palette.backgroundElevated : "transparent" + border.color: Theme.palette.borderSecondary + border.width: 1 + opacity: (root.backend.status === root.running + || root.backend.status === root.stopped) ? 1.0 : 0.4 + + PlayIcon { + anchors.centerIn: parent + dotColor: Theme.palette.text + dotSize: 5 + dotSpacing: 2 + visible: root.backend.status !== root.running + } + StopIcon { + anchors.centerIn: parent + dotColor: Theme.palette.text + dotSize: 5 + dotSpacing: 2 + visible: root.backend.status === root.running + } + + HoverHandler { + id: startStopHover + } + MouseArea { + anchors.fill: parent + enabled: root.backend.status === root.running + || root.backend.status === root.stopped + cursorShape: Qt.PointingHandCursor + onClicked: root.backend.status === root.running + ? root.backend.stop() : root.backend.start() + } + } +} diff --git a/src/qml/StatusWidgets.qml b/src/qml/StatusWidgets.qml new file mode 100644 index 0000000..604be4a --- /dev/null +++ b/src/qml/StatusWidgets.qml @@ -0,0 +1,155 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs +import QtQuick.Layouts +import Logos.Theme +import Logos.Controls + +// qmllint disable unqualified +ColumnLayout { + id: root + + property var backend + property bool running: false + + spacing: 0 + + TextEdit { + id: clipHelper + visible: false + function copyText(str) { + clipHelper.text = str + clipHelper.selectAll() + clipHelper.copy() + } + } + + FileDialog { + id: uploadDialog + onAccepted: root.backend.tryUploadFile(selectedFile) + } + + Connections { + target: root.backend + function onUploadCompleted(cid) { root._lastCid = cid } + function onDownloadCompleted(cid) { root._lastCid = cid } + } + + property string _lastCid: "" + + RowLayout { + Layout.fillWidth: true + spacing: Theme.spacing.medium + + UploadWidget { + uploadProgress: root.backend.uploadProgress + running: root.running + onUploadRequested: uploadDialog.open() + } + + DiskWidget { + backend: root.backend + } + + PeersWidget { + backend: root.backend + } + + Item { + Layout.fillWidth: true + } + } + + Item { + Layout.fillWidth: true + Layout.topMargin: 10 + Layout.bottomMargin: 10 + Layout.preferredHeight: 36 + + opacity: root._lastCid.length > 0 ? 1.0 : 0.0 + + Behavior on opacity { + NumberAnimation { + duration: 200 + } + } + + Rectangle { + height: 36 + width: cidBadgeRow.implicitWidth + 28 + radius: 6 + color: Theme.palette.backgroundSecondary + border.color: Theme.palette.borderSecondary + border.width: 1 + + RowLayout { + id: cidBadgeRow + anchors.centerIn: parent + spacing: 8 + + LogosText { + text: "CID" + font.pixelSize: 10 + color: Theme.palette.textTertiary + } + + LogosText { + text: { + var c = root._lastCid + return c.length > 20 ? c.substring(0, 8) + "…" + c.slice(-6) : c + } + font.pixelSize: 11 + font.family: "monospace" + color: Theme.palette.text + } + + LogosText { + text: "COPY" + font.pixelSize: 9 + color: Theme.palette.textTertiary + font.letterSpacing: 0.8 + } + } + + Rectangle { + id: copyFlash + anchors.fill: parent + radius: parent.radius + color: Theme.palette.success + opacity: 0 + + SequentialAnimation on opacity { + id: copyFlashAnim + running: false + NumberAnimation { + to: 0.18 + duration: 80 + } + NumberAnimation { + to: 0 + duration: 500 + } + } + } + + HoverHandler { + id: cidBadgeHover + } + + Rectangle { + anchors.fill: parent + radius: parent.radius + color: cidBadgeHover.hovered ? Qt.rgba(1, 1, 1, 0.04) : "transparent" + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + clipHelper.copyText(root._lastCid) + copyFlashAnim.restart() + } + } + } + } +}