mirror of
https://github.com/logos-storage/logos-storage-app-skeleton.git
synced 2026-06-13 20:09:28 +00:00
Temporary commit
This commit is contained in:
parent
e6cd77d898
commit
d5d7bf7af3
88
src/qml/DiskWidget.qml
Normal file
88
src/qml/DiskWidget.qml
Normal 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
79
src/qml/PeersWidget.qml
Normal 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
12
src/qml/PlayIcon.qml
Normal 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
12
src/qml/SettingsIcon.qml
Normal 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
115
src/qml/SettingsPopup.qml
Normal 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
12
src/qml/StopIcon.qml
Normal 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
12
src/qml/UploadIcon.qml
Normal 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
159
src/qml/UploadWidget.qml
Normal 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 // 0‒100
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user