Add components

This commit is contained in:
Arnaud 2026-02-20 16:42:33 +04:00
parent 30dd14c966
commit e6cd77d898
No known key found for this signature in database
GPG Key ID: 20E40A5D3110766F
3 changed files with 328 additions and 0 deletions

218
src/qml/ManifestTable.qml Normal file
View File

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

96
src/qml/SpaceBar.qml Normal file
View File

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

14
src/qml/StorageIcon.qml Normal file
View File

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