mirror of
https://github.com/logos-storage/logos-storage-app-skeleton.git
synced 2026-07-01 20:59:30 +00:00
Add components
This commit is contained in:
parent
30dd14c966
commit
e6cd77d898
218
src/qml/ManifestTable.qml
Normal file
218
src/qml/ManifestTable.qml
Normal 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
96
src/qml/SpaceBar.qml
Normal 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
14
src/qml/StorageIcon.qml
Normal 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
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user