- hypr/apps.lua - hypr/autostart.lua - hypr/envs.lua - hypr/hyprland.lua - hypr/hyprsunset.conf - hypr/input.lua - hypr/looknfeel.lua - hypr/omasettings.lua - hypr/xdph.conf - omarchy/branding/about.txt - omarchy/branding/screensaver.txt - omarchy/extensions/omarchy-menu.jsonc - omarchy/hooks/battery-low.d/play-warning-sound.sample - omarchy/hooks/font-set.d/show-font-notification.sample - omarchy/hooks/post-boot.d/weather.sample - omarchy/hooks/post-update.d/install-voxtype.hook - omarchy/hooks/post-update.d/setup-agent.hook - omarchy/hooks/post-update.d/setup-fingerprint.hook - omarchy/hooks/post-update.d/show-update-notification.sample - omarchy/hooks/pre-refresh-pacman.d/add-custom-repo.sample - omarchy/hooks/theme-set.d/show-theme-notification.sample - omarchy/shell.json - omarchy/shell.toml - omarchy/theme.name - omarchy/themes/azure-glow/README.md - omarchy/themes/azure-glow/alacritty.toml - omarchy/themes/azure-glow/btop.theme - omarchy/themes/azure-glow/hyprland.conf - omarchy/themes/azure-glow/hyprlock.conf - omarchy/themes/azure-glow/icons.theme - … 269 more
621 lines
20 KiB
QML
621 lines
20 KiB
QML
pragma ComponentBehavior: Bound
|
||
|
||
import QtQuick
|
||
import qs.Commons
|
||
import qs.Ui
|
||
import "readout.js" as Readout
|
||
|
||
// The local leg in detail: signal on a labelled scale, link facts, and the
|
||
// airtime counters that explain "signal looks fine but Wi-Fi feels slow".
|
||
Column {
|
||
id: tab
|
||
|
||
required property var panel
|
||
|
||
Component.onCompleted: panel.requestEvents("7d")
|
||
|
||
readonly property var linkEvents: {
|
||
var all = panel.eventsData && panel.eventsData.events
|
||
? panel.eventsData.events : []
|
||
var kinds = {"roam": 1, "kick": 1, "drop": 1, "associate": 1,
|
||
"rate-drop": 1, "channel-change": 1}
|
||
var cut = Date.now() / 1000 - 86400
|
||
var out = []
|
||
for (var i = 0; i < all.length && out.length < 6; i++)
|
||
if (kinds[all[i].kind] && all[i].ts >= cut) out.push(all[i])
|
||
return out
|
||
}
|
||
|
||
// Every change of access point inside the chart window, with who ended
|
||
// the previous association: a roam was this machine's choice, a kick the
|
||
// access point's, a drop a link that fell over.
|
||
readonly property var roamMarks: {
|
||
var all = panel.eventsData && panel.eventsData.events
|
||
? panel.eventsData.events : []
|
||
var kinds = {"roam": 1, "kick": 1, "drop": 1}
|
||
var cut = Date.now() / 1000 - 1800
|
||
var out = []
|
||
for (var i = 0; i < all.length; i++)
|
||
if (kinds[all[i].kind] && all[i].ts >= cut)
|
||
out.push({ts: all[i].ts, kind: all[i].kind})
|
||
return out
|
||
}
|
||
|
||
readonly property var link: panel.live ? panel.live.link : null
|
||
readonly property var station: link ? link.station : null
|
||
readonly property bool isWifi: link && link.kind === "wifi"
|
||
|
||
spacing: Style.space(12)
|
||
|
||
Text {
|
||
textFormat: Text.PlainText
|
||
visible: !tab.isWifi
|
||
text: tab.link && tab.link.kind === "ethernet"
|
||
? "Wired connection — no radio to report on. The local leg lives on the Latency tab."
|
||
: "Not connected."
|
||
color: tab.panel.dim
|
||
font.family: tab.panel.fontFamily
|
||
font.pixelSize: Style.font.bodySmall
|
||
wrapMode: Text.WordWrap
|
||
width: parent.width
|
||
}
|
||
|
||
// ---- signal -------------------------------------------------------------
|
||
Column {
|
||
width: parent.width
|
||
visible: tab.isWifi
|
||
spacing: Style.space(12)
|
||
|
||
Text {
|
||
textFormat: Text.PlainText
|
||
text: "THE LOCAL LEG · THIS MACHINE TO THE ROUTER"
|
||
color: tab.panel.dim
|
||
font.family: tab.panel.fontFamily
|
||
font.pixelSize: Style.font.caption
|
||
font.letterSpacing: 1
|
||
}
|
||
|
||
Row {
|
||
width: parent.width
|
||
spacing: Style.space(20)
|
||
|
||
readonly property var dbm: tab.link ? tab.link.signal_dbm : null
|
||
readonly property color tone: {
|
||
if (dbm === null || dbm === undefined) return tab.panel.dim
|
||
if (dbm >= -60) return tab.panel.okTone
|
||
if (dbm >= -70) return tab.panel.warnTone
|
||
return Color.urgent
|
||
}
|
||
|
||
Column {
|
||
spacing: Style.space(2)
|
||
anchors.bottom: parent.bottom
|
||
|
||
Text {
|
||
textFormat: Text.PlainText
|
||
text: parent.parent.dbm !== null && parent.parent.dbm !== undefined
|
||
? parent.parent.dbm + " dBm" : "--"
|
||
color: parent.parent.tone
|
||
font.family: tab.panel.fontFamily
|
||
font.pixelSize: Style.fontPx(2.0)
|
||
font.weight: Font.Bold
|
||
}
|
||
Text {
|
||
textFormat: Text.PlainText
|
||
text: "SIGNAL"
|
||
color: tab.panel.dim
|
||
font.family: tab.panel.fontFamily
|
||
font.pixelSize: Style.font.caption
|
||
font.letterSpacing: 1
|
||
}
|
||
}
|
||
|
||
Column {
|
||
width: parent.width - Style.space(130)
|
||
anchors.bottom: parent.bottom
|
||
anchors.bottomMargin: Style.space(4)
|
||
spacing: Style.space(5)
|
||
|
||
Rectangle {
|
||
width: parent.width
|
||
height: Style.space(8)
|
||
color: Qt.rgba(tab.panel.fg.r, tab.panel.fg.g, tab.panel.fg.b, 0.10)
|
||
|
||
Rectangle {
|
||
readonly property var dbm: tab.link ? tab.link.signal_dbm : null
|
||
height: parent.height
|
||
// -90 dBm is unusable, -30 is rail: map onto 0..1.
|
||
width: parent.width * (dbm === null || dbm === undefined
|
||
? 0 : Math.max(0, Math.min(1, (dbm + 90) / 60)))
|
||
color: parent.parent.parent.tone
|
||
Behavior on width { NumberAnimation { duration: 300 } }
|
||
}
|
||
// The marginal mark at -67 dBm, where video calls start to suffer.
|
||
Rectangle {
|
||
x: parent.width * ((-67 + 90) / 60)
|
||
y: -Style.space(3)
|
||
width: 1
|
||
height: parent.height + Style.space(6)
|
||
color: Qt.rgba(tab.panel.fg.r, tab.panel.fg.g, tab.panel.fg.b, 0.35)
|
||
}
|
||
}
|
||
|
||
Item {
|
||
width: parent.width
|
||
height: scaleLeft.implicitHeight
|
||
|
||
Text {
|
||
id: scaleLeft
|
||
textFormat: Text.PlainText
|
||
text: "−90 unusable"
|
||
color: tab.panel.dim
|
||
font.family: tab.panel.fontFamily
|
||
font.pixelSize: Style.font.caption
|
||
}
|
||
Text {
|
||
textFormat: Text.PlainText
|
||
x: parent.width * ((-67 + 90) / 60) - implicitWidth / 2
|
||
text: "−67 marginal"
|
||
color: tab.panel.dim
|
||
font.family: tab.panel.fontFamily
|
||
font.pixelSize: Style.font.caption
|
||
}
|
||
Text {
|
||
textFormat: Text.PlainText
|
||
anchors.right: parent.right
|
||
text: "−30 max"
|
||
color: tab.panel.dim
|
||
font.family: tab.panel.fontFamily
|
||
font.pixelSize: Style.font.caption
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// ---- signal & local lag history ---------------------------------------
|
||
Item {
|
||
width: parent.width
|
||
height: sigLabel.implicitHeight
|
||
|
||
Text {
|
||
id: sigLabel
|
||
textFormat: Text.PlainText
|
||
text: "SIGNAL & LOCAL LAG · LAST 30 MIN"
|
||
color: tab.panel.dim
|
||
font.family: tab.panel.fontFamily
|
||
font.pixelSize: Style.font.caption
|
||
font.letterSpacing: 1
|
||
}
|
||
Text {
|
||
textFormat: Text.PlainText
|
||
anchors.right: parent.right
|
||
text: {
|
||
var marks = tab.roamMarks, n = {roam: 0, kick: 0, drop: 0}
|
||
for (var i = 0; i < marks.length; i++) n[marks[i].kind] += 1
|
||
var parts = []
|
||
if (n.roam) parts.push(n.roam === 1 ? "one roam" : n.roam + " roams")
|
||
if (n.kick) parts.push(n.kick === 1 ? "kicked once" : "kicked " + n.kick + "\u00d7")
|
||
if (n.drop) parts.push(n.drop === 1 ? "one drop" : n.drop + " drops")
|
||
return parts.join(" \u00b7 ")
|
||
}
|
||
color: tab.panel.dim
|
||
font.family: tab.panel.fontFamily
|
||
font.pixelSize: Style.font.caption
|
||
}
|
||
}
|
||
|
||
Canvas {
|
||
id: sigChart
|
||
width: parent.width
|
||
height: Style.space(78)
|
||
|
||
property real hoverX: -1
|
||
onHoverXChanged: requestPaint()
|
||
|
||
HoverHandler {
|
||
onPointChanged: sigChart.hoverX = hovered ? point.position.x : -1
|
||
onHoveredChanged: if (!hovered) sigChart.hoverX = -1
|
||
}
|
||
|
||
readonly property var pts: tab.panel.recentPoints
|
||
readonly property var roams: tab.roamMarks
|
||
onPtsChanged: requestPaint()
|
||
onRoamsChanged: requestPaint()
|
||
onWidthChanged: requestPaint()
|
||
|
||
onPaint: {
|
||
var ctx = getContext("2d")
|
||
ctx.reset()
|
||
ctx.clearRect(0, 0, width, height)
|
||
var bottom = height - 1
|
||
ctx.strokeStyle = Qt.rgba(tab.panel.fg.r, tab.panel.fg.g,
|
||
tab.panel.fg.b, 0.16)
|
||
ctx.lineWidth = 1
|
||
ctx.beginPath()
|
||
ctx.moveTo(0, bottom + 0.5)
|
||
ctx.lineTo(width, bottom + 0.5)
|
||
ctx.stroke()
|
||
|
||
var p = pts
|
||
if (!p || p.length < 2) return
|
||
var t0 = p[0].t, span = Math.max(1, p[p.length - 1].t - t0)
|
||
function xAt(t) { return (t - t0) * (width - 1) / span }
|
||
|
||
// Access-point changes first, dashed, under the series: purple
|
||
// when this machine chose to move, amber when it was kicked or
|
||
// the link dropped.
|
||
ctx.setLineDash([2, 3])
|
||
for (var m = 0; m < roams.length; m++) {
|
||
var rx = xAt(roams[m].ts)
|
||
if (rx < 0 || rx > width) continue
|
||
ctx.strokeStyle = roams[m].kind === "roam"
|
||
? Qt.rgba(0.73, 0.6, 0.97, 0.55) : Qt.rgba(0.88, 0.69, 0.41, 0.7)
|
||
ctx.beginPath()
|
||
ctx.moveTo(rx, 0)
|
||
ctx.lineTo(rx, height)
|
||
ctx.stroke()
|
||
}
|
||
ctx.setLineDash([])
|
||
|
||
function runs(key, yAt) {
|
||
var out = [], cur = []
|
||
for (var i = 0; i < p.length; i++) {
|
||
var v = p[i][key]
|
||
if (v === null || v === undefined) {
|
||
if (cur.length > 1) out.push(cur)
|
||
cur = []
|
||
} else {
|
||
cur.push([xAt(p[i].t), yAt(v)])
|
||
}
|
||
}
|
||
if (cur.length > 1) out.push(cur)
|
||
return out
|
||
}
|
||
|
||
// Signal on the fixed -90..-30 dBm scale, as a filled band.
|
||
var ok = tab.panel.okTone
|
||
var sigRuns = runs("sig", function(v) {
|
||
var f = Math.max(0, Math.min(1, (v + 90) / 60))
|
||
return bottom - (bottom - 3) * f
|
||
})
|
||
for (var r = 0; r < sigRuns.length; r++) {
|
||
var run = sigRuns[r]
|
||
ctx.beginPath()
|
||
ctx.moveTo(run[0][0], bottom)
|
||
for (var j = 0; j < run.length; j++) ctx.lineTo(run[j][0], run[j][1])
|
||
ctx.lineTo(run[run.length - 1][0], bottom)
|
||
ctx.closePath()
|
||
ctx.fillStyle = Qt.rgba(ok.r, ok.g, ok.b, 0.14)
|
||
ctx.fill()
|
||
ctx.beginPath()
|
||
for (j = 0; j < run.length; j++) {
|
||
if (j === 0) ctx.moveTo(run[j][0], run[j][1])
|
||
else ctx.lineTo(run[j][0], run[j][1])
|
||
}
|
||
ctx.strokeStyle = ok
|
||
ctx.lineWidth = 1.4
|
||
ctx.stroke()
|
||
}
|
||
|
||
// Local lag on its own scale, a thin dim line.
|
||
var lagPeak = 8
|
||
for (var i = 0; i < p.length; i++) {
|
||
if (p[i].local !== null && p[i].local !== undefined)
|
||
lagPeak = Math.max(lagPeak, p[i].local)
|
||
}
|
||
lagPeak *= 1.15
|
||
var lagRuns = runs("local", function(v) {
|
||
return bottom - (bottom - 3) * Math.min(1, v / lagPeak)
|
||
})
|
||
ctx.strokeStyle = tab.panel.dim
|
||
ctx.lineWidth = 1
|
||
for (r = 0; r < lagRuns.length; r++) {
|
||
run = lagRuns[r]
|
||
ctx.beginPath()
|
||
for (j = 0; j < run.length; j++) {
|
||
if (j === 0) ctx.moveTo(run[j][0], run[j][1])
|
||
else ctx.lineTo(run[j][0], run[j][1])
|
||
}
|
||
ctx.stroke()
|
||
}
|
||
|
||
if (hoverX >= 0) {
|
||
var tAt = t0 + hoverX * span / (width - 1)
|
||
var best = null, bestD = Infinity
|
||
for (i = 0; i < p.length; i++) {
|
||
var d = Math.abs(p[i].t - tAt)
|
||
if (d < bestD) { bestD = d; best = p[i] }
|
||
}
|
||
if (best) {
|
||
var cx = xAt(best.t)
|
||
ctx.strokeStyle = Qt.rgba(tab.panel.fg.r, tab.panel.fg.g,
|
||
tab.panel.fg.b, 0.4)
|
||
ctx.lineWidth = 1
|
||
ctx.beginPath()
|
||
ctx.moveTo(cx + 0.5, 0)
|
||
ctx.lineTo(cx + 0.5, height)
|
||
ctx.stroke()
|
||
var parts = [Qt.formatTime(new Date(best.t * 1000), "HH:mm:ss")]
|
||
parts.push(best.sig !== null && best.sig !== undefined
|
||
? best.sig + " dBm" : "no signal data")
|
||
if (best.local !== null && best.local !== undefined)
|
||
parts.push("lag " + best.local.toFixed(1) + " ms")
|
||
var label = parts.join(" · ")
|
||
Readout.draw(ctx, tab.panel.fontFamily, label, cx, width,
|
||
tab.panel.fg, Color.popups.background)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
Row {
|
||
spacing: Style.space(16)
|
||
|
||
component ChartKey: Row {
|
||
property color tint: "white"
|
||
property string label: ""
|
||
property bool dashed: false
|
||
spacing: Style.space(6)
|
||
Rectangle {
|
||
width: parent.dashed ? 1 : Style.space(10)
|
||
height: parent.dashed ? Style.space(9) : 2
|
||
color: parent.tint
|
||
anchors.verticalCenter: parent.verticalCenter
|
||
}
|
||
Text {
|
||
textFormat: Text.PlainText
|
||
text: parent.label
|
||
color: tab.panel.dim
|
||
font.family: tab.panel.fontFamily
|
||
font.pixelSize: Style.font.caption
|
||
anchors.verticalCenter: parent.verticalCenter
|
||
}
|
||
}
|
||
|
||
ChartKey { tint: tab.panel.okTone; label: "signal" }
|
||
ChartKey { tint: tab.panel.dim; label: "local lag" }
|
||
ChartKey { tint: "#bb9af7"; label: "roam"; dashed: true }
|
||
ChartKey { tint: tab.panel.warnTone; label: "kicked / dropped"; dashed: true }
|
||
}
|
||
|
||
PanelSeparator { width: parent.width }
|
||
|
||
// ---- link facts -------------------------------------------------------
|
||
Grid {
|
||
width: parent.width
|
||
columns: 2
|
||
columnSpacing: Style.space(24)
|
||
rowSpacing: Style.space(6)
|
||
|
||
readonly property real cell: (width - Style.space(24)) / 2
|
||
|
||
component KvRow: Item {
|
||
property string k: ""
|
||
property string v: ""
|
||
height: kText.implicitHeight
|
||
Text {
|
||
id: kText
|
||
textFormat: Text.PlainText
|
||
text: parent.k
|
||
color: tab.panel.dim
|
||
font.family: tab.panel.fontFamily
|
||
font.pixelSize: Style.font.bodySmall
|
||
}
|
||
Text {
|
||
textFormat: Text.PlainText
|
||
anchors.right: parent.right
|
||
text: parent.v
|
||
color: tab.panel.fg
|
||
font.family: tab.panel.fontFamily
|
||
font.pixelSize: Style.font.bodySmall
|
||
elide: Text.ElideLeft
|
||
width: parent.width - kText.implicitWidth - Style.space(8)
|
||
horizontalAlignment: Text.AlignRight
|
||
}
|
||
}
|
||
|
||
KvRow { width: parent.cell; k: "Band"
|
||
v: tab.link && tab.link.band ? tab.link.band : "--" }
|
||
KvRow { width: parent.cell; k: "Channel"
|
||
v: tab.link && tab.link.channel
|
||
? tab.link.channel + (tab.link.width_mhz ? " · " + tab.link.width_mhz + " MHz" : "")
|
||
: "--" }
|
||
KvRow { width: parent.cell; k: "Tx rate"
|
||
v: tab.link && tab.link.tx_mbps ? tab.link.tx_mbps + " Mbps" : "--" }
|
||
KvRow { width: parent.cell; k: "Rx rate"
|
||
v: tab.link && tab.link.rx_mbps ? tab.link.rx_mbps + " Mbps" : "--" }
|
||
KvRow { width: parent.cell; k: "Standard"
|
||
v: tab.link && tab.link.standard ? tab.link.standard : "--" }
|
||
KvRow { width: parent.cell; k: "Interface"
|
||
v: tab.link && tab.link.iface ? tab.link.iface : "--" }
|
||
KvRow { width: parent.cell; k: "BSSID"
|
||
v: tab.link && tab.link.bssid ? tab.link.bssid : "--" }
|
||
KvRow { width: parent.cell; k: "Gateway"
|
||
v: tab.link && tab.link.gateway ? tab.link.gateway : "--" }
|
||
}
|
||
|
||
PanelSeparator { width: parent.width }
|
||
|
||
// ---- airtime health ---------------------------------------------------
|
||
Text {
|
||
textFormat: Text.PlainText
|
||
text: "AIRTIME · WHY WI-FI FEELS SLOW WHEN SIGNAL LOOKS FINE"
|
||
color: tab.panel.dim
|
||
font.family: tab.panel.fontFamily
|
||
font.pixelSize: Style.font.caption
|
||
font.letterSpacing: 1
|
||
}
|
||
|
||
Row {
|
||
width: parent.width
|
||
spacing: Style.space(14)
|
||
readonly property real cell: (width - Style.space(14) * 2) / 3
|
||
|
||
component AirStat: Column {
|
||
property string label: ""
|
||
property string display: "--"
|
||
property real frac: 0 // 0..1 meter fill
|
||
property bool bad: false
|
||
spacing: Style.space(4)
|
||
Text {
|
||
textFormat: Text.PlainText
|
||
text: parent.display
|
||
color: parent.bad ? tab.panel.warnTone : tab.panel.okTone
|
||
font.family: tab.panel.fontFamily
|
||
font.pixelSize: Style.font.heading
|
||
font.weight: Font.Bold
|
||
}
|
||
Text {
|
||
textFormat: Text.PlainText
|
||
text: parent.label
|
||
color: tab.panel.dim
|
||
font.family: tab.panel.fontFamily
|
||
font.pixelSize: Style.font.caption
|
||
font.letterSpacing: 1
|
||
}
|
||
Rectangle {
|
||
width: parent.width
|
||
height: Math.max(2, Style.space(3))
|
||
color: Qt.rgba(tab.panel.fg.r, tab.panel.fg.g, tab.panel.fg.b, 0.12)
|
||
Rectangle {
|
||
height: parent.height
|
||
width: parent.width * Math.max(0, Math.min(1, parent.parent.frac))
|
||
color: parent.parent.bad ? tab.panel.warnTone : tab.panel.okTone
|
||
}
|
||
}
|
||
}
|
||
|
||
AirStat {
|
||
width: parent.cell
|
||
label: "TX RETRIES"
|
||
display: tab.station && tab.station.retry_pct !== undefined
|
||
? tab.station.retry_pct.toFixed(1) + " %"
|
||
: (tab.station && tab.station.tx_retries !== undefined
|
||
? String(tab.station.tx_retries) : "--")
|
||
// 10% retries fills the meter; past ~5% the air is genuinely busy.
|
||
frac: tab.station && tab.station.retry_pct !== undefined
|
||
? tab.station.retry_pct / 10 : 0
|
||
bad: tab.station && tab.station.retry_pct > 5
|
||
}
|
||
AirStat {
|
||
width: parent.cell
|
||
label: "TX FAILED"
|
||
display: tab.station && tab.station.tx_failed !== undefined
|
||
? String(tab.station.tx_failed) : "--"
|
||
frac: tab.station && tab.station.tx_failed !== undefined
|
||
? tab.station.tx_failed / 50 : 0
|
||
bad: tab.station && tab.station.tx_failed > 10
|
||
}
|
||
AirStat {
|
||
width: parent.cell
|
||
label: "BEACON LOSS"
|
||
display: tab.station && tab.station.beacon_loss !== undefined
|
||
? String(tab.station.beacon_loss) : "--"
|
||
frac: tab.station && tab.station.beacon_loss !== undefined
|
||
? tab.station.beacon_loss / 10 : 0
|
||
bad: tab.station && tab.station.beacon_loss > 0
|
||
}
|
||
}
|
||
|
||
Text {
|
||
textFormat: Text.PlainText
|
||
width: parent.width
|
||
text: "Counters since association. Retries mean a noisy channel; failures mean "
|
||
+ "frames given up on; beacon loss means the router's heartbeat went missing."
|
||
color: tab.panel.dim
|
||
font.family: tab.panel.fontFamily
|
||
font.pixelSize: Style.font.caption
|
||
wrapMode: Text.WordWrap
|
||
}
|
||
|
||
PanelSeparator { width: parent.width }
|
||
|
||
// ---- link events ------------------------------------------------------
|
||
Text {
|
||
textFormat: Text.PlainText
|
||
text: "LINK EVENTS · LAST 24 H"
|
||
color: tab.panel.dim
|
||
font.family: tab.panel.fontFamily
|
||
font.pixelSize: Style.font.caption
|
||
font.letterSpacing: 1
|
||
}
|
||
|
||
Text {
|
||
textFormat: Text.PlainText
|
||
visible: tab.linkEvents.length === 0
|
||
text: "None. The link has been steady."
|
||
color: tab.panel.dim
|
||
font.family: tab.panel.fontFamily
|
||
font.pixelSize: Style.font.bodySmall
|
||
}
|
||
|
||
Column {
|
||
width: parent.width
|
||
spacing: Style.space(8)
|
||
|
||
Repeater {
|
||
model: tab.linkEvents
|
||
|
||
Row {
|
||
id: linkRow
|
||
required property var modelData
|
||
width: parent.width
|
||
spacing: Style.space(10)
|
||
|
||
readonly property color tone: {
|
||
var k = linkRow.modelData.kind
|
||
if (k === "roam") return "#bb9af7"
|
||
if (k === "kick" || k === "drop") return tab.panel.warnTone
|
||
if (k === "rate-drop") return tab.panel.warnTone
|
||
if (k === "associate") return tab.panel.okTone
|
||
return Color.accent
|
||
}
|
||
|
||
Text {
|
||
textFormat: Text.PlainText
|
||
width: Style.space(42)
|
||
text: {
|
||
var d = new Date(linkRow.modelData.ts * 1000)
|
||
return d.toLocaleString(Qt.locale(), "HH:mm")
|
||
}
|
||
color: tab.panel.dim
|
||
font.family: tab.panel.fontFamily
|
||
font.pixelSize: Style.font.caption
|
||
}
|
||
Rectangle {
|
||
width: Style.space(5)
|
||
height: Style.space(5)
|
||
color: linkRow.tone
|
||
anchors.verticalCenter: parent.verticalCenter
|
||
}
|
||
Text {
|
||
textFormat: Text.PlainText
|
||
width: parent.width - Style.space(42) - Style.space(5)
|
||
- Style.space(58) - Style.space(10) * 3
|
||
text: linkRow.modelData.detail
|
||
color: tab.panel.fg
|
||
font.family: tab.panel.fontFamily
|
||
font.pixelSize: Style.font.bodySmall
|
||
wrapMode: Text.WordWrap
|
||
}
|
||
Text {
|
||
textFormat: Text.PlainText
|
||
width: Style.space(58)
|
||
horizontalAlignment: Text.AlignRight
|
||
text: {
|
||
var e = linkRow.modelData
|
||
if (!e.ended_ts || e.ended_ts === e.ts) return ""
|
||
var s = e.ended_ts - e.ts
|
||
return s < 60 ? "for " + s + " s"
|
||
: "for " + Math.round(s / 60) + " m"
|
||
}
|
||
color: tab.panel.dim
|
||
font.family: tab.panel.fontFamily
|
||
font.pixelSize: Style.font.caption
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|