mirror of
https://github.com/logos-storage/logos-storage-app-skeleton.git
synced 2026-06-13 20:09:28 +00:00
Move components
This commit is contained in:
parent
67dd5ed957
commit
1ff471a35c
2
.gitmodules
vendored
2
.gitmodules
vendored
@ -11,5 +11,5 @@
|
||||
path = vendor/logos-storage-nim
|
||||
url = https://github.com/logos-storage/logos-storage-nim
|
||||
[submodule "vendor/logos-design-system"]
|
||||
path = logos-design-system
|
||||
path = vendor/logos-design-system
|
||||
url = https://github.com/logos-co/logos-design-system
|
||||
|
||||
46
src/qml/ArcWidget.qml
Normal file
46
src/qml/ArcWidget.qml
Normal file
@ -0,0 +1,46 @@
|
||||
import QtQuick
|
||||
import Logos.Theme
|
||||
|
||||
// Reusable ring widget — Rectangle + ArcGauge + content overlay.
|
||||
//
|
||||
// Usage:
|
||||
// ArcWidget {
|
||||
// fraction: 0.65
|
||||
// fillColor: Theme.palette.success
|
||||
//
|
||||
// ColumnLayout { anchors.centerIn: parent; ... }
|
||||
// }
|
||||
//
|
||||
// Children are placed inside an overlay Item that fills the widget,
|
||||
// so anchors such as `anchors.centerIn: parent` work as expected.
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
width: 140
|
||||
height: 140
|
||||
radius: 14
|
||||
color: Theme.palette.backgroundSecondary
|
||||
border.color: Theme.palette.borderSecondary
|
||||
border.width: 1
|
||||
|
||||
// ── Arc properties ────────────────────────────────────────────────────────
|
||||
property real fraction: 0.0
|
||||
property color fillColor: Theme.palette.text
|
||||
property color trackColor: Theme.palette.textMuted
|
||||
|
||||
// ── Content slot ──────────────────────────────────────────────────────────
|
||||
// Children declared inside ArcWidget { … } land here, on top of the arc.
|
||||
default property alias content: overlay.data
|
||||
|
||||
ArcGauge {
|
||||
anchors.fill: parent
|
||||
fraction: root.fraction
|
||||
trackColor: root.trackColor
|
||||
fillColor: root.fillColor
|
||||
}
|
||||
|
||||
Item {
|
||||
id: overlay
|
||||
anchors.fill: parent
|
||||
}
|
||||
}
|
||||
53
src/qml/JsonEditor.qml
Normal file
53
src/qml/JsonEditor.qml
Normal file
@ -0,0 +1,53 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Logos.Theme
|
||||
|
||||
// Reusable JSON editor with live validation.
|
||||
// Usage:
|
||||
// JsonEditor {
|
||||
// id: editor
|
||||
// Layout.fillWidth: true
|
||||
// Layout.fillHeight: true
|
||||
// }
|
||||
// // Load content (e.g. when a popup opens):
|
||||
// editor.load(backend.configJson() || "{}")
|
||||
// // Read back:
|
||||
// editor.text // current text
|
||||
// editor.isValid // false when JSON.parse would throw
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property alias text: jsonArea.text
|
||||
property bool isValid: true
|
||||
|
||||
Component.onCompleted: root.validate()
|
||||
|
||||
color: Theme.palette.backgroundElevated
|
||||
radius: 8
|
||||
border.color: root.isValid ? Theme.palette.borderSecondary : Theme.palette.error
|
||||
border.width: 1
|
||||
|
||||
function validate() {
|
||||
try {
|
||||
JSON.parse(jsonArea.text)
|
||||
isValid = true
|
||||
} catch (e) {
|
||||
isValid = false
|
||||
}
|
||||
}
|
||||
|
||||
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 {}
|
||||
onTextChanged: root.validate()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,96 +0,0 @@
|
||||
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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,5 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import Logos.Controls
|
||||
import Logos.Theme
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -3,80 +3,32 @@ import QtQuick.Layouts
|
||||
import Logos.Theme
|
||||
import Logos.Controls
|
||||
|
||||
Rectangle {
|
||||
ArcWidget {
|
||||
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 bool running: false
|
||||
|
||||
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
|
||||
readonly property bool isDone: uploadProgress >= 100
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
fraction: root.uploadProgress / 100.0
|
||||
fillColor: root.isDone ? Theme.palette.success : Theme.palette.text
|
||||
|
||||
// ── Center content ────────────────────────────────────────────────────────
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
spacing: 2
|
||||
|
||||
// Idle: dot upload icon
|
||||
// Idle or done: upload icon
|
||||
UploadIcon {
|
||||
dotColor: Theme.palette.textSecondary
|
||||
dotSize: 4; dotSpacing: 3
|
||||
dotColor: Theme.palette.textSecondary
|
||||
dotSize: 4
|
||||
dotSpacing: 3
|
||||
activeOpacity: 0.5
|
||||
visible: !root.isUploading && !root.isDone
|
||||
visible: !root.isUploading
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
@ -89,19 +41,8 @@ Rectangle {
|
||||
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"
|
||||
text: root.isDone ? "DONE" : "UPLOAD"
|
||||
font.pixelSize: 9
|
||||
color: Theme.palette.textTertiary
|
||||
font.letterSpacing: 1.2
|
||||
@ -109,51 +50,19 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
// ── "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 ─────────────────────────────────────────────────────────
|
||||
// ── Hover overlay ─────────────────────────────────────────────────────────
|
||||
HoverHandler { id: widgetHover }
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: parent.radius
|
||||
color: widgetHover.hovered ? Qt.rgba(1, 1, 1, 0.04) : "transparent"
|
||||
radius: root.radius
|
||||
color: widgetHover.hovered && root.running ? Qt.rgba(1, 1, 1, 0.04) : "transparent"
|
||||
}
|
||||
|
||||
// ── Click → trigger upload when node is running ───────────────────────────
|
||||
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()
|
||||
}
|
||||
}
|
||||
cursorShape: root.running ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: if (root.running) root.uploadRequested()
|
||||
}
|
||||
}
|
||||
|
||||
17
src/qml/icons/AdvancedIcon.qml
Normal file
17
src/qml/icons/AdvancedIcon.qml
Normal file
@ -0,0 +1,17 @@
|
||||
import QtQuick
|
||||
|
||||
// X pattern — advanced / expert mode
|
||||
// ● . . . ●
|
||||
// . ● . ● .
|
||||
// . . ● . .
|
||||
// . ● . ● .
|
||||
// ● . . . ●
|
||||
DotIcon {
|
||||
pattern: [
|
||||
1, 0, 0, 0, 1,
|
||||
0, 1, 0, 1, 0,
|
||||
0, 0, 1, 0, 0,
|
||||
0, 1, 0, 1, 0,
|
||||
1, 0, 0, 0, 1
|
||||
]
|
||||
}
|
||||
56
src/qml/icons/ArcGauge.qml
Normal file
56
src/qml/icons/ArcGauge.qml
Normal file
@ -0,0 +1,56 @@
|
||||
import QtQuick
|
||||
import Logos.Theme
|
||||
|
||||
// Reusable arc gauge — same visual style as the Nothing OS ring widgets.
|
||||
//
|
||||
// Usage:
|
||||
// ArcGauge {
|
||||
// anchors.fill: parent
|
||||
// fraction: 0.65
|
||||
// fillColor: Theme.palette.success
|
||||
// }
|
||||
Canvas {
|
||||
id: root
|
||||
|
||||
// 0.0 – 1.0 (clamped internally)
|
||||
property real fraction: 0.0
|
||||
property color trackColor: Theme.palette.textMuted
|
||||
property color fillColor: Theme.palette.text
|
||||
property real arcRadius: 46
|
||||
property real arcWidth: 8
|
||||
|
||||
onFractionChanged: requestPaint()
|
||||
onTrackColorChanged: requestPaint()
|
||||
onFillColorChanged: requestPaint()
|
||||
|
||||
Component.onCompleted: requestPaint()
|
||||
|
||||
onPaint: {
|
||||
var ctx = getContext("2d")
|
||||
ctx.reset()
|
||||
|
||||
var cx = width / 2
|
||||
var cy = height / 2
|
||||
var startRad = 130 * Math.PI / 180
|
||||
var totalRad = 280 * Math.PI / 180
|
||||
|
||||
// ── Track (full arc, muted) ───────────────────────────────────────────
|
||||
ctx.beginPath()
|
||||
ctx.arc(cx, cy, root.arcRadius, startRad, startRad + totalRad)
|
||||
ctx.strokeStyle = root.trackColor.toString()
|
||||
ctx.lineWidth = root.arcWidth
|
||||
ctx.lineCap = "round"
|
||||
ctx.stroke()
|
||||
|
||||
// ── Fill (proportional) ───────────────────────────────────────────────
|
||||
var f = Math.min(Math.max(root.fraction, 0.0), 1.0)
|
||||
if (f > 0) {
|
||||
ctx.beginPath()
|
||||
ctx.arc(cx, cy, root.arcRadius, startRad, startRad + totalRad * f)
|
||||
ctx.strokeStyle = root.fillColor.toString()
|
||||
ctx.lineWidth = root.arcWidth
|
||||
ctx.lineCap = "round"
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
}
|
||||
76
src/qml/icons/ArcWidget.qml
Normal file
76
src/qml/icons/ArcWidget.qml
Normal file
@ -0,0 +1,76 @@
|
||||
import QtQuick
|
||||
import Logos.Theme
|
||||
|
||||
// Reusable ring widget — Rectangle + arc canvas + content overlay.
|
||||
// Usage:
|
||||
// ArcWidget {
|
||||
// fraction: 0.65
|
||||
// fillColor: Theme.palette.success
|
||||
// ColumnLayout { anchors.centerIn: parent; ... }
|
||||
// }
|
||||
// Children are placed inside an overlay Item that fills the widget,
|
||||
// so anchors such as `anchors.centerIn: parent` work as expected.
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
width: 140
|
||||
height: 140
|
||||
radius: 14
|
||||
color: Theme.palette.backgroundSecondary
|
||||
border.color: Theme.palette.borderSecondary
|
||||
border.width: 1
|
||||
|
||||
property real fraction: 0.0
|
||||
property color fillColor: Theme.palette.text
|
||||
property color trackColor: Theme.palette.textMuted
|
||||
property real arcRadius: 46
|
||||
property real arcWidth: 8
|
||||
|
||||
onFractionChanged: arc.requestPaint()
|
||||
onFillColorChanged: arc.requestPaint()
|
||||
onTrackColorChanged: arc.requestPaint()
|
||||
|
||||
default property alias content: overlay.data
|
||||
|
||||
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 startRad = 130 * Math.PI / 180
|
||||
var totalRad = 280 * Math.PI / 180
|
||||
|
||||
// Track (full arc, muted)
|
||||
ctx.beginPath()
|
||||
ctx.arc(cx, cy, root.arcRadius, startRad, startRad + totalRad)
|
||||
ctx.strokeStyle = root.trackColor.toString()
|
||||
ctx.lineWidth = root.arcWidth
|
||||
ctx.lineCap = "round"
|
||||
ctx.stroke()
|
||||
|
||||
// Fill (proportional)
|
||||
var f = Math.min(Math.max(root.fraction, 0.0), 1.0)
|
||||
if (f > 0) {
|
||||
ctx.beginPath()
|
||||
ctx.arc(cx, cy, root.arcRadius, startRad,
|
||||
startRad + totalRad * f)
|
||||
ctx.strokeStyle = root.fillColor.toString()
|
||||
ctx.lineWidth = root.arcWidth
|
||||
ctx.lineCap = "round"
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: overlay
|
||||
anchors.fill: parent
|
||||
}
|
||||
}
|
||||
18
src/qml/icons/DeleteIcon.qml
Normal file
18
src/qml/icons/DeleteIcon.qml
Normal file
@ -0,0 +1,18 @@
|
||||
import QtQuick
|
||||
|
||||
// X pattern — delete / remove
|
||||
// ● . . . ●
|
||||
// . ● . ● .
|
||||
// . . ● . .
|
||||
// . ● . ● .
|
||||
// ● . . . ●
|
||||
// qmllint disable unqualified
|
||||
DotIcon {
|
||||
pattern: [
|
||||
1, 0, 0, 0, 1,
|
||||
0, 1, 0, 1, 0,
|
||||
0, 0, 1, 0, 0,
|
||||
0, 1, 0, 1, 0,
|
||||
1, 0, 0, 0, 1
|
||||
]
|
||||
}
|
||||
74
src/qml/icons/DotIcon.qml
Normal file
74
src/qml/icons/DotIcon.qml
Normal file
@ -0,0 +1,74 @@
|
||||
import QtQuick
|
||||
|
||||
// qmllint disable unqualified
|
||||
Item {
|
||||
id: root
|
||||
|
||||
// Static pattern — flat array of 0/1, row-major
|
||||
property var pattern: []
|
||||
|
||||
// Dimensions
|
||||
property int columns: 5
|
||||
property int dotSize: 6
|
||||
property int dotSpacing: 4
|
||||
|
||||
// Appearance
|
||||
property color dotColor: "white"
|
||||
property real inactiveOpacity: 0.1
|
||||
property real activeOpacity: 0.9
|
||||
|
||||
// Animation
|
||||
property bool animated: false
|
||||
property int animPhase: 0
|
||||
|
||||
readonly property int rows: Math.max(1, Math.ceil(pattern.length / columns))
|
||||
readonly property int count: animated ? columns * columns : pattern.length
|
||||
|
||||
implicitWidth: columns * dotSize + Math.max(0, columns - 1) * dotSpacing
|
||||
implicitHeight: rows * dotSize + Math.max(0, rows - 1) * dotSpacing
|
||||
width: implicitWidth
|
||||
height: implicitHeight
|
||||
|
||||
Timer {
|
||||
interval: 140
|
||||
repeat: true
|
||||
running: root.animated
|
||||
onTriggered: root.animPhase = (root.animPhase + 1) % (root.columns * 2)
|
||||
}
|
||||
|
||||
Grid {
|
||||
columns: root.columns
|
||||
spacing: root.dotSpacing
|
||||
|
||||
Repeater {
|
||||
model: root.count
|
||||
|
||||
Rectangle {
|
||||
width: root.dotSize
|
||||
height: root.dotSize
|
||||
radius: root.dotSize * 0.3
|
||||
color: root.dotColor
|
||||
|
||||
opacity: {
|
||||
if (!root.animated) {
|
||||
return (index < root.pattern.length
|
||||
&& root.pattern[index]) ? root.activeOpacity : root.inactiveOpacity
|
||||
}
|
||||
// Wave from center
|
||||
const cx = Math.floor(root.columns / 2)
|
||||
const cy = Math.floor(root.columns / 2)
|
||||
const col = index % root.columns
|
||||
const row = Math.floor(index / root.columns)
|
||||
const d = Math.abs(col - cx) + Math.abs(row - cy)
|
||||
const wave = root.animPhase % root.columns
|
||||
const diff = Math.abs(d - wave)
|
||||
if (diff === 0)
|
||||
return root.activeOpacity
|
||||
if (diff === 1)
|
||||
return 0.35
|
||||
return root.inactiveOpacity
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
17
src/qml/icons/DownloadIcon.qml
Normal file
17
src/qml/icons/DownloadIcon.qml
Normal file
@ -0,0 +1,17 @@
|
||||
import QtQuick
|
||||
|
||||
// Downward arrow — download
|
||||
// . . ● . .
|
||||
// . . ● . .
|
||||
// ● ● ● ● ●
|
||||
// . ● ● ● .
|
||||
// . . ● . .
|
||||
DotIcon {
|
||||
pattern: [
|
||||
0, 0, 1, 0, 0,
|
||||
0, 0, 1, 0, 0,
|
||||
1, 1, 1, 1, 1,
|
||||
0, 1, 1, 1, 0,
|
||||
0, 0, 1, 0, 0
|
||||
]
|
||||
}
|
||||
17
src/qml/icons/GuideIcon.qml
Normal file
17
src/qml/icons/GuideIcon.qml
Normal file
@ -0,0 +1,17 @@
|
||||
import QtQuick
|
||||
|
||||
// Crosshair pattern — step-by-step guide
|
||||
// . . ● . .
|
||||
// . . ● . .
|
||||
// ● ● . ● ●
|
||||
// . . ● . .
|
||||
// . . ● . .
|
||||
DotIcon {
|
||||
pattern: [
|
||||
0, 0, 1, 0, 0,
|
||||
0, 0, 1, 0, 0,
|
||||
1, 1, 0, 1, 1,
|
||||
0, 0, 1, 0, 0,
|
||||
0, 0, 1, 0, 0
|
||||
]
|
||||
}
|
||||
72
src/qml/icons/NodeStatusIcon.qml
Normal file
72
src/qml/icons/NodeStatusIcon.qml
Normal file
@ -0,0 +1,72 @@
|
||||
import QtQuick
|
||||
import Logos.Theme
|
||||
|
||||
// qmllint disable unqualified
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property bool starting: true
|
||||
property bool success: false
|
||||
property int animPhase: 0
|
||||
|
||||
readonly property int columns: 7
|
||||
readonly property int dotSize: 8
|
||||
readonly property int dotSpacing: 5
|
||||
|
||||
implicitWidth: columns * dotSize + (columns - 1) * dotSpacing
|
||||
implicitHeight: columns * dotSize + (columns - 1) * dotSpacing
|
||||
width: implicitWidth
|
||||
height: implicitHeight
|
||||
|
||||
Timer {
|
||||
interval: 120
|
||||
repeat: true
|
||||
running: root.starting
|
||||
onTriggered: root.animPhase = (root.animPhase + 1) % 14
|
||||
}
|
||||
|
||||
Grid {
|
||||
columns: root.columns
|
||||
spacing: root.dotSpacing
|
||||
|
||||
Repeater {
|
||||
model: root.columns * root.columns
|
||||
|
||||
Rectangle {
|
||||
width: root.dotSize
|
||||
height: root.dotSize
|
||||
radius: root.dotSize * 0.25
|
||||
|
||||
color: {
|
||||
if (root.success)
|
||||
return Theme.palette.success
|
||||
if (!root.starting)
|
||||
return Theme.palette.error
|
||||
return Theme.palette.text
|
||||
}
|
||||
|
||||
opacity: {
|
||||
const col = index % root.columns
|
||||
const row = Math.floor(index / root.columns)
|
||||
const d = Math.abs(col - 3) + Math.abs(row - 3)
|
||||
|
||||
if (root.starting) {
|
||||
const wave = root.animPhase % root.columns
|
||||
const diff = Math.abs(d - wave)
|
||||
if (diff === 0)
|
||||
return 0.9
|
||||
if (diff === 1)
|
||||
return 0.35
|
||||
return 0.1
|
||||
}
|
||||
|
||||
if (root.success)
|
||||
return 0.85
|
||||
|
||||
// Error — X pattern
|
||||
return (col === row || col + row === 6) ? 0.9 : 0.1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
17
src/qml/icons/PlayIcon.qml
Normal file
17
src/qml/icons/PlayIcon.qml
Normal file
@ -0,0 +1,17 @@
|
||||
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
|
||||
]
|
||||
}
|
||||
17
src/qml/icons/PortIcon.qml
Normal file
17
src/qml/icons/PortIcon.qml
Normal file
@ -0,0 +1,17 @@
|
||||
import QtQuick
|
||||
|
||||
// Right arrow pattern — manual port forwarding
|
||||
// . . ● . .
|
||||
// . . . ● .
|
||||
// ● ● ● ● ●
|
||||
// . . . ● .
|
||||
// . . ● . .
|
||||
DotIcon {
|
||||
pattern: [
|
||||
0, 0, 1, 0, 0,
|
||||
0, 0, 0, 1, 0,
|
||||
1, 1, 1, 1, 1,
|
||||
0, 0, 0, 1, 0,
|
||||
0, 0, 1, 0, 0
|
||||
]
|
||||
}
|
||||
11
src/qml/icons/SettingsIcon.qml
Normal file
11
src/qml/icons/SettingsIcon.qml
Normal file
@ -0,0 +1,11 @@
|
||||
import QtQuick
|
||||
|
||||
// Gear / cog icon — 4 cardinal teeth + ring with center hole
|
||||
// . . ● . .
|
||||
// . ● ● ● .
|
||||
// ● ● . ● ●
|
||||
// . ● ● ● .
|
||||
// . . ● . .
|
||||
DotIcon {
|
||||
pattern: [0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0]
|
||||
}
|
||||
17
src/qml/icons/StopIcon.qml
Normal file
17
src/qml/icons/StopIcon.qml
Normal file
@ -0,0 +1,17 @@
|
||||
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
|
||||
]
|
||||
}
|
||||
19
src/qml/icons/StorageIcon.qml
Normal file
19
src/qml/icons/StorageIcon.qml
Normal file
@ -0,0 +1,19 @@
|
||||
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
|
||||
}
|
||||
17
src/qml/icons/UploadIcon.qml
Normal file
17
src/qml/icons/UploadIcon.qml
Normal file
@ -0,0 +1,17 @@
|
||||
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
|
||||
]
|
||||
}
|
||||
17
src/qml/icons/UpnpIcon.qml
Normal file
17
src/qml/icons/UpnpIcon.qml
Normal file
@ -0,0 +1,17 @@
|
||||
import QtQuick
|
||||
|
||||
// Diamond / network pattern — UPnP automatic port forwarding
|
||||
// . . ● . .
|
||||
// . ● . ● .
|
||||
// ● . ● . ●
|
||||
// . ● . ● .
|
||||
// . . ● . .
|
||||
DotIcon {
|
||||
pattern: [
|
||||
0, 0, 1, 0, 0,
|
||||
0, 1, 0, 1, 0,
|
||||
1, 0, 1, 0, 1,
|
||||
0, 1, 0, 1, 0,
|
||||
0, 0, 1, 0, 0
|
||||
]
|
||||
}
|
||||
1
vendor/logos-design-system
vendored
Submodule
1
vendor/logos-design-system
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 596811cbb0a0644322267368e87fab80e34203d8
|
||||
Loading…
x
Reference in New Issue
Block a user