Temporary commit

This commit is contained in:
Arnaud 2026-02-21 07:07:42 +04:00
parent e6cd77d898
commit d5d7bf7af3
No known key found for this signature in database
GPG Key ID: 20E40A5D3110766F
8 changed files with 489 additions and 0 deletions

88
src/qml/DiskWidget.qml Normal file
View File

@ -0,0 +1,88 @@
import QtQuick
import QtQuick.Layouts
import Logos.Theme
import Logos.Controls
Rectangle {
id: root
width: 140; height: 140
radius: 14
color: Theme.palette.backgroundSecondary
border.color: Theme.palette.borderSecondary
border.width: 1
property real total: 0
property real used: 0
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"
}
onTotalChanged: arc.requestPaint()
onUsedChanged: arc.requestPaint()
Canvas {
id: arc
anchors.fill: parent
Component.onCompleted: requestPaint()
onPaint: {
var ctx = getContext("2d")
ctx.reset()
var cx = width / 2
var cy = height / 2
var r = 46
var lw = 8
var startRad = 130 * Math.PI / 180
var totalRad = 280 * Math.PI / 180
// Background track (available / grey)
ctx.beginPath()
ctx.arc(cx, cy, r, startRad, startRad + totalRad)
ctx.strokeStyle = Theme.palette.textMuted.toString()
ctx.lineWidth = lw
ctx.lineCap = "round"
ctx.stroke()
// Fill (used / white)
if (root.total > 0) {
var fraction = Math.min(root.used / root.total, 1.0)
if (fraction > 0) {
ctx.beginPath()
ctx.arc(cx, cy, r, startRad, startRad + totalRad * fraction)
ctx.strokeStyle = Theme.palette.text.toString()
ctx.lineWidth = lw
ctx.lineCap = "round"
ctx.stroke()
}
}
}
}
ColumnLayout {
anchors.centerIn: parent
spacing: 2
LogosText {
text: root.total > 0 ? root.formatBytes(root.used) : "—"
font.pixelSize: 15
font.bold: true
Layout.alignment: Qt.AlignHCenter
}
LogosText {
text: "STORAGE"
font.pixelSize: 9
color: Theme.palette.textTertiary
font.letterSpacing: 1.3
Layout.alignment: Qt.AlignHCenter
}
}
}

79
src/qml/PeersWidget.qml Normal file
View File

@ -0,0 +1,79 @@
import QtQuick
import QtQuick.Layouts
import Logos.Theme
import Logos.Controls
Rectangle {
id: root
width: 140; height: 140
radius: 14
color: Theme.palette.backgroundSecondary
border.color: Theme.palette.borderSecondary
border.width: 1
property int peerCount: 0
// Soft ceiling: arc is full at maxPeers connected peers
property int maxPeers: 20
onPeerCountChanged: arc.requestPaint()
Canvas {
id: arc
anchors.fill: parent
Component.onCompleted: requestPaint()
onPaint: {
var ctx = getContext("2d")
ctx.reset()
var cx = width / 2
var cy = height / 2
var r = 46
var lw = 8
var startRad = 130 * Math.PI / 180
var totalRad = 280 * Math.PI / 180
// Background track
ctx.beginPath()
ctx.arc(cx, cy, r, startRad, startRad + totalRad)
ctx.strokeStyle = Theme.palette.textMuted.toString()
ctx.lineWidth = lw
ctx.lineCap = "round"
ctx.stroke()
// Fill (peers / white)
var fraction = root.maxPeers > 0
? Math.min(root.peerCount / root.maxPeers, 1.0) : 0
if (fraction > 0) {
ctx.beginPath()
ctx.arc(cx, cy, r, startRad, startRad + totalRad * fraction)
ctx.strokeStyle = Theme.palette.text.toString()
ctx.lineWidth = lw
ctx.lineCap = "round"
ctx.stroke()
}
}
}
ColumnLayout {
anchors.centerIn: parent
spacing: 2
LogosText {
text: root.peerCount
font.pixelSize: 22
font.bold: true
Layout.alignment: Qt.AlignHCenter
}
LogosText {
text: "PEERS"
font.pixelSize: 9
color: Theme.palette.textTertiary
font.letterSpacing: 1.3
Layout.alignment: Qt.AlignHCenter
}
}
}

12
src/qml/PlayIcon.qml Normal file
View File

@ -0,0 +1,12 @@
import QtQuick
// Right-pointing triangle play / start
DotIcon {
pattern: [
1, 0, 0, 0, 0,
1, 1, 0, 0, 0,
1, 1, 1, 0, 0,
1, 1, 0, 0, 0,
1, 0, 0, 0, 0
]
}

12
src/qml/SettingsIcon.qml Normal file
View File

@ -0,0 +1,12 @@
import QtQuick
// Settings / gear icon
DotIcon {
pattern: [
0, 1, 0, 1, 0,
1, 1, 1, 1, 1,
0, 1, 1, 1, 0,
1, 1, 1, 1, 1,
0, 1, 0, 1, 0
]
}

115
src/qml/SettingsPopup.qml Normal file
View File

@ -0,0 +1,115 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Logos.Theme
import Logos.Controls
Popup {
id: root
property var backend
modal: true
width: 520
height: 400
anchors.centerIn: Overlay.overlay
padding: 24
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
background: Rectangle {
color: Theme.palette.backgroundSecondary
border.color: Theme.palette.borderSecondary
border.width: 1
radius: 14
}
ColumnLayout {
anchors.fill: parent
spacing: Theme.spacing.small
LogosText {
text: "Configuration"
font.pixelSize: Theme.typography.titleText
Layout.alignment: Qt.AlignHCenter
}
LogosText {
text: "Edit the JSON configuration below, then click Save."
font.pixelSize: Theme.typography.primaryText
color: Theme.palette.textSecondary
Layout.alignment: Qt.AlignHCenter
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
// JSON editor
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: Theme.palette.backgroundElevated
radius: 8
border.color: jsonArea.isValid
? Theme.palette.borderSecondary : Theme.palette.error
border.width: 1
ScrollView {
anchors.fill: parent
anchors.margins: 2
TextArea {
id: jsonArea
font.family: "monospace"
font.pixelSize: 12
color: Theme.palette.text
wrapMode: Text.WrapAnywhere
background: Item {}
property bool isValid: true
function validate() {
try { JSON.parse(text); isValid = true }
catch (e) { isValid = false }
}
onTextChanged: validate()
Component.onCompleted: {
text = (root.backend && root.backend.configJson)
? root.backend.configJson : "{}"
validate()
}
Connections {
target: root.backend
function onConfigJsonChanged() {
jsonArea.text = root.backend.configJson
}
}
}
}
}
// Buttons
RowLayout {
Layout.alignment: Qt.AlignHCenter
spacing: Theme.spacing.medium
LogosStorageButton {
text: "Cancel"
onClicked: root.close()
}
LogosStorageButton {
text: "Save"
variant: "success"
enabled: jsonArea.isValid
onClicked: {
root.backend.saveUserConfig(jsonArea.text)
root.backend.reloadIfChanged(jsonArea.text)
root.close()
}
}
}
}
}

12
src/qml/StopIcon.qml Normal file
View File

@ -0,0 +1,12 @@
import QtQuick
// Filled square stop
DotIcon {
pattern: [
0, 0, 0, 0, 0,
0, 1, 1, 1, 0,
0, 1, 1, 1, 0,
0, 1, 1, 1, 0,
0, 0, 0, 0, 0
]
}

12
src/qml/UploadIcon.qml Normal file
View File

@ -0,0 +1,12 @@
import QtQuick
// Upward arrow upload
DotIcon {
pattern: [
0, 0, 1, 0, 0,
0, 1, 1, 1, 0,
1, 1, 1, 1, 1,
0, 0, 1, 0, 0,
0, 0, 1, 0, 0
]
}

159
src/qml/UploadWidget.qml Normal file
View File

@ -0,0 +1,159 @@
import QtQuick
import QtQuick.Layouts
import Logos.Theme
import Logos.Controls
Rectangle {
id: root
width: 140; height: 140
radius: 14
color: Theme.palette.backgroundSecondary
border.color: Theme.palette.borderSecondary
border.width: 1
property int uploadProgress: 0 // 0100
property string uploadCid: ""
property bool running: false
// States
readonly property bool isUploading: uploadProgress > 0 && uploadProgress < 100
readonly property bool isDone: uploadCid.length > 0
signal uploadRequested
onUploadProgressChanged: arc.requestPaint()
onUploadCidChanged: arc.requestPaint()
// Arc
Canvas {
id: arc
anchors.fill: parent
Component.onCompleted: requestPaint()
onPaint: {
var ctx = getContext("2d")
ctx.reset()
var cx = width / 2
var cy = height / 2
var r = 46
var lw = 8
var startRad = 130 * Math.PI / 180
var totalRad = 280 * Math.PI / 180
// Background track
ctx.beginPath()
ctx.arc(cx, cy, r, startRad, startRad + totalRad)
ctx.strokeStyle = Theme.palette.textMuted.toString()
ctx.lineWidth = lw
ctx.lineCap = "round"
ctx.stroke()
// Fill
var fraction = root.isDone ? 1.0 : root.uploadProgress / 100.0
if (fraction > 0) {
ctx.beginPath()
ctx.arc(cx, cy, r, startRad, startRad + totalRad * fraction)
ctx.strokeStyle = root.isDone
? Theme.palette.success.toString()
: Theme.palette.text.toString()
ctx.lineWidth = lw
ctx.lineCap = "round"
ctx.stroke()
}
}
}
// Center content
ColumnLayout {
anchors.centerIn: parent
spacing: 2
// Idle: dot upload icon
UploadIcon {
dotColor: Theme.palette.textSecondary
dotSize: 4; dotSpacing: 3
activeOpacity: 0.5
visible: !root.isUploading && !root.isDone
Layout.alignment: Qt.AlignHCenter
}
// Uploading: percentage
LogosText {
text: root.uploadProgress + "%"
font.pixelSize: 22
font.bold: true
visible: root.isUploading
Layout.alignment: Qt.AlignHCenter
}
// Done: abbreviated CID
LogosText {
text: root.uploadCid.length > 10
? root.uploadCid.substring(0, 6) + "…" + root.uploadCid.slice(-4)
: root.uploadCid
font.pixelSize: 11
font.family: "monospace"
visible: root.isDone && !root.isUploading
Layout.alignment: Qt.AlignHCenter
}
LogosText {
text: root.isDone ? "TAP TO COPY" : "UPLOAD"
font.pixelSize: 9
color: Theme.palette.textTertiary
font.letterSpacing: 1.2
Layout.alignment: Qt.AlignHCenter
}
}
// "Copied!" toast
Rectangle {
id: copiedToast
anchors.centerIn: parent
width: 80; height: 26
radius: 6
color: Theme.palette.backgroundElevated
border.color: Theme.palette.success
border.width: 1
visible: false
LogosText {
anchors.centerIn: parent
text: "Copied ✓"
font.pixelSize: 11
color: Theme.palette.success
}
}
SequentialAnimation {
id: copiedAnim
ScriptAction { script: copiedToast.visible = true }
PauseAnimation{ duration: 1400 }
ScriptAction { script: copiedToast.visible = false }
}
// Click handler
HoverHandler { id: widgetHover }
Rectangle {
anchors.fill: parent
radius: parent.radius
color: widgetHover.hovered ? Qt.rgba(1, 1, 1, 0.04) : "transparent"
}
MouseArea {
anchors.fill: parent
cursorShape: (root.running || root.isDone) ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: {
if (root.isDone) {
Qt.copyToClipboard(root.uploadCid)
copiedAnim.restart()
} else if (root.running) {
root.uploadRequested()
}
}
}
}