From e6cd77d8986376cf74bc951ae479811e56e0daa1 Mon Sep 17 00:00:00 2001 From: Arnaud Date: Fri, 20 Feb 2026 16:42:33 +0400 Subject: [PATCH] Add components --- src/qml/ManifestTable.qml | 218 ++++++++++++++++++++++++++++++++++++++ src/qml/SpaceBar.qml | 96 +++++++++++++++++ src/qml/StorageIcon.qml | 14 +++ 3 files changed, 328 insertions(+) create mode 100644 src/qml/ManifestTable.qml create mode 100644 src/qml/SpaceBar.qml create mode 100644 src/qml/StorageIcon.qml diff --git a/src/qml/ManifestTable.qml b/src/qml/ManifestTable.qml new file mode 100644 index 0000000..e3ac830 --- /dev/null +++ b/src/qml/ManifestTable.qml @@ -0,0 +1,218 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Logos.Theme +import Logos.Controls + +ColumnLayout { + id: root + + property var backend + property bool running: false + + signal downloadRequested(var manifest) + + spacing: Theme.spacing.small + + function formatBytes(bytes) { + if (bytes <= 0) return "0 B" + if (bytes < 1024) return bytes + " B" + if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB" + if (bytes < 1024 * 1024 * 1024) return (bytes / (1024 * 1024)).toFixed(1) + " MB" + return (bytes / (1024 * 1024 * 1024)).toFixed(2) + " GB" + } + + // ── Section title ───────────────────────────────────────────────────────── + LogosText { + text: "MANIFESTS" + font.pixelSize: 11 + color: Theme.palette.textTertiary + font.letterSpacing: 1.5 + } + + // ── CID input + fetch button ────────────────────────────────────────────── + RowLayout { + Layout.fillWidth: true + spacing: Theme.spacing.small + + LogosTextField { + id: cidInput + Layout.fillWidth: true + placeholderText: "Enter CID to fetch manifest…" + } + + LogosStorageButton { + text: "↓ Fetch" + enabled: root.running && cidInput.text.length > 0 + onClicked: { + root.backend.downloadManifest(cidInput.text) + cidInput.clear() + } + } + } + + // ── Table header ────────────────────────────────────────────────────────── + Rectangle { + Layout.fillWidth: true + height: 30 + color: Theme.palette.backgroundElevated + radius: 4 + + Row { + anchors.fill: parent + anchors.leftMargin: 10 + + Text { width: 160; text: "CID"; color: Theme.palette.textSecondary; font.pixelSize: 11; font.bold: true; elide: Text.ElideRight; anchors.verticalCenter: parent.verticalCenter } + Text { width: 130; text: "Filename"; color: Theme.palette.textSecondary; font.pixelSize: 11; font.bold: true; elide: Text.ElideRight; anchors.verticalCenter: parent.verticalCenter } + Text { width: 90; text: "MIME"; color: Theme.palette.textSecondary; font.pixelSize: 11; font.bold: true; elide: Text.ElideRight; anchors.verticalCenter: parent.verticalCenter } + Text { width: 80; text: "Size"; color: Theme.palette.textSecondary; font.pixelSize: 11; font.bold: true; elide: Text.ElideRight; anchors.verticalCenter: parent.verticalCenter } + } + } + + // ── Table body ──────────────────────────────────────────────────────────── + Rectangle { + Layout.fillWidth: true + height: 240 + color: Theme.palette.background + border.color: Theme.palette.borderSecondary + border.width: 1 + radius: 4 + clip: true + + ListView { + id: manifestList + anchors.fill: parent + model: root.backend ? root.backend.manifests : [] + clip: true + + delegate: Rectangle { + width: manifestList.width + height: 36 + color: index % 2 === 0 ? Theme.palette.background : Theme.palette.backgroundSecondary + + Row { + anchors.fill: parent + anchors.leftMargin: 10 + anchors.rightMargin: 8 + + Text { + width: 160 + text: modelData["cid"] ?? "" + color: Theme.palette.text + font.pixelSize: 11 + font.family: "monospace" + elide: Text.ElideMiddle + anchors.verticalCenter: parent.verticalCenter + ToolTip.visible: cidHover.hovered + ToolTip.text: modelData["cid"] ?? "" + HoverHandler { id: cidHover } + } + Text { + width: 130 + text: modelData["filename"] ?? "" + color: Theme.palette.textSecondary + font.pixelSize: 11 + elide: Text.ElideRight + anchors.verticalCenter: parent.verticalCenter + } + Text { + width: 90 + text: modelData["mimetype"] ?? "" + color: Theme.palette.textSecondary + font.pixelSize: 11 + elide: Text.ElideRight + anchors.verticalCenter: parent.verticalCenter + } + Text { + width: 80 + text: root.formatBytes(parseInt(modelData["datasetSize"] ?? "0")) + color: Theme.palette.textSecondary + font.pixelSize: 11 + elide: Text.ElideRight + anchors.verticalCenter: parent.verticalCenter + } + + // ── Action buttons ──────────────────────────────────────── + Row { + spacing: 6 + anchors.verticalCenter: parent.verticalCenter + + // Download + Rectangle { + width: 28; height: 28; radius: 4 + color: dlHover.hovered ? Theme.palette.backgroundElevated : "transparent" + border.color: Theme.palette.borderSecondary + border.width: 1 + opacity: root.running ? 1.0 : 0.35 + + Text { + anchors.centerIn: parent + text: "↓" + color: Theme.palette.text + font.pixelSize: 14 + } + HoverHandler { id: dlHover } + MouseArea { + anchors.fill: parent + enabled: root.running + cursorShape: Qt.PointingHandCursor + onClicked: root.downloadRequested(modelData) + } + } + + // Delete + Rectangle { + width: 28; height: 28; radius: 4 + color: rmHover.hovered ? Theme.palette.backgroundElevated : "transparent" + border.color: Theme.palette.borderSecondary + border.width: 1 + opacity: root.running ? 1.0 : 0.35 + + Text { + anchors.centerIn: parent + text: "×" + color: Theme.palette.error + font.pixelSize: 16 + font.bold: true + } + HoverHandler { id: rmHover } + MouseArea { + anchors.fill: parent + enabled: root.running + cursorShape: Qt.PointingHandCursor + onClicked: root.backend.remove(modelData["cid"] ?? "") + } + } + } + } + } + + // ── Empty state ─────────────────────────────────────────────────── + ColumnLayout { + anchors.centerIn: parent + spacing: 10 + visible: manifestList.count === 0 + + DotIcon { + pattern: [ + 0, 0, 1, 0, 0, + 0, 1, 0, 1, 0, + 1, 0, 0, 0, 1, + 0, 1, 0, 1, 0, + 0, 0, 1, 0, 0 + ] + dotColor: Theme.palette.textMuted + activeOpacity: 0.25 + Layout.alignment: Qt.AlignHCenter + } + + LogosText { + text: "No manifests yet" + color: Theme.palette.textMuted + font.pixelSize: 12 + Layout.alignment: Qt.AlignHCenter + } + } + } + } +} diff --git a/src/qml/SpaceBar.qml b/src/qml/SpaceBar.qml new file mode 100644 index 0000000..40b37f3 --- /dev/null +++ b/src/qml/SpaceBar.qml @@ -0,0 +1,96 @@ +import QtQuick +import QtQuick.Layouts +import Logos.Theme +import Logos.Controls + +ColumnLayout { + id: root + + property real total: 0 + property real used: 0 + property real reserved: 0 + readonly property real free: Math.max(0, total - used - reserved) + + spacing: 8 + + function formatBytes(bytes) { + if (bytes <= 0) return "0 B" + if (bytes < 1024) return bytes + " B" + if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB" + if (bytes < 1024 * 1024 * 1024) return (bytes / (1024 * 1024)).toFixed(1) + " MB" + return (bytes / (1024 * 1024 * 1024)).toFixed(2) + " GB" + } + + // ── Section title ───────────────────────────────────────────────────────── + LogosText { + text: "DISK USAGE" + font.pixelSize: 11 + color: Theme.palette.textTertiary + font.letterSpacing: 1.5 + } + + // ── No quota message ────────────────────────────────────────────────────── + LogosText { + text: "No quota configured" + color: Theme.palette.textMuted + font.pixelSize: 12 + visible: root.total <= 0 + } + + // ── Progress track ──────────────────────────────────────────────────────── + Rectangle { + Layout.fillWidth: true + height: 10 + radius: 5 + color: Theme.palette.backgroundElevated + border.color: Theme.palette.borderSecondary + border.width: 1 + visible: root.total > 0 + clip: true + + // Used (green) + Rectangle { + width: Math.min(parent.width * (root.used / root.total), parent.width) + height: parent.height + radius: parent.radius + color: Theme.palette.success + } + + // Reserved (orange), stacked after used + Rectangle { + x: parent.width * (root.used / root.total) + width: Math.min( + parent.width * (root.reserved / root.total), + parent.width - x) + height: parent.height + color: Theme.palette.warning + } + } + + // ── Legend ──────────────────────────────────────────────────────────────── + Row { + visible: root.total > 0 + spacing: 18 + + Row { + spacing: 5 + Rectangle { width: 7; height: 7; radius: 2; color: Theme.palette.success; anchors.verticalCenter: parent.verticalCenter } + Text { text: "Used · " + root.formatBytes(root.used); color: Theme.palette.success; font.pixelSize: 11 } + } + Row { + spacing: 5 + Rectangle { width: 7; height: 7; radius: 2; color: Theme.palette.warning; anchors.verticalCenter: parent.verticalCenter } + Text { text: "Reserved · " + root.formatBytes(root.reserved); color: Theme.palette.warning; font.pixelSize: 11 } + } + Row { + spacing: 5 + Rectangle { width: 7; height: 7; radius: 2; color: Theme.palette.textSecondary; anchors.verticalCenter: parent.verticalCenter } + Text { text: "Free · " + root.formatBytes(root.free); color: Theme.palette.textSecondary; font.pixelSize: 11 } + } + Row { + spacing: 5 + Rectangle { width: 7; height: 7; radius: 2; color: Theme.palette.textMuted; anchors.verticalCenter: parent.verticalCenter } + Text { text: "Total · " + root.formatBytes(root.total); color: Theme.palette.textMuted; font.pixelSize: 11 } + } + } +} diff --git a/src/qml/StorageIcon.qml b/src/qml/StorageIcon.qml new file mode 100644 index 0000000..121e720 --- /dev/null +++ b/src/qml/StorageIcon.qml @@ -0,0 +1,14 @@ +import QtQuick + +// Ring / node pattern — used in the StorageView header +DotIcon { + pattern: [ + 0, 1, 1, 1, 0, + 1, 0, 0, 0, 1, + 1, 0, 1, 0, 1, + 1, 0, 0, 0, 1, + 0, 1, 1, 1, 0 + ] + dotSize: 7 + dotSpacing: 5 +}