@cad0p/pi-usage 0.11.3 → 0.11.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/README.md +274 -12
- package/bin/piquota.js +49 -4
- package/extensions/quota-panel.ts +188 -31
- package/omarchy/cad0p.pi-usage/Agent.qml +33 -0
- package/omarchy/cad0p.pi-usage/Main.qml +333 -0
- package/omarchy/cad0p.pi-usage/Panel.qml +136 -0
- package/omarchy/cad0p.pi-usage/README.md +40 -0
- package/omarchy/cad0p.pi-usage/UsageContent.qml +1001 -0
- package/omarchy/cad0p.pi-usage/UsageLogic.js +603 -0
- package/omarchy/cad0p.pi-usage/assets/antigravity.svg +3 -0
- package/omarchy/cad0p.pi-usage/assets/claude.svg +1 -0
- package/omarchy/cad0p.pi-usage/assets/codex-light.svg +1 -0
- package/omarchy/cad0p.pi-usage/assets/codex.svg +1 -0
- package/omarchy/cad0p.pi-usage/assets/fireworks.svg +7 -0
- package/omarchy/cad0p.pi-usage/assets/github-actions.svg +3 -0
- package/omarchy/cad0p.pi-usage/assets/opencode-go.svg +3 -0
- package/omarchy/cad0p.pi-usage/manifest.json +56 -0
- package/package.json +2 -1
- package/src/auth/pi-auth.js +3 -0
- package/src/cli/args.js +5 -0
- package/src/cli/commands/stats.js +76 -0
- package/src/engine.js +44 -0
- package/src/local/history.js +1160 -0
- package/src/local/pi-sessions.js +1511 -0
- package/src/omarchy/publish.js +1 -65
- package/src/omarchy/record.js +82 -7
|
@@ -0,0 +1,1001 @@
|
|
|
1
|
+
import QtQuick
|
|
2
|
+
import QtQuick.Controls
|
|
3
|
+
import qs.Commons
|
|
4
|
+
import qs.Ui
|
|
5
|
+
import "UsageLogic.js" as Logic
|
|
6
|
+
|
|
7
|
+
// The scrolling body of the AI Usage panel: one collapsible section per
|
|
8
|
+
// provider, all in one list. Panel.qml mounts it inside the bar popup; the
|
|
9
|
+
// dev harness mounts it in a plain window.
|
|
10
|
+
//
|
|
11
|
+
// The rows (LimitRow, Meter, DayRow, ModelRow) and every type size, color
|
|
12
|
+
// and spacing are carried over from Omarchy's built-in Agents panel
|
|
13
|
+
// (plugins/agents/Panel.qml, MIT, by Omarchy), so an expanded section reads
|
|
14
|
+
// exactly like one of its tabs.
|
|
15
|
+
Item {
|
|
16
|
+
id: root
|
|
17
|
+
|
|
18
|
+
// Main.qml, or anything with the same providers / expandedOverrides /
|
|
19
|
+
// setExpanded / newestUpdate / refreshing surface.
|
|
20
|
+
property var usage: null
|
|
21
|
+
property color foreground: Color.foreground
|
|
22
|
+
property color urgent: Color.urgent
|
|
23
|
+
property string fontFamily: Style.font.family
|
|
24
|
+
property double nowMs: Date.now()
|
|
25
|
+
// Stand-in mark for a provider that ships no SVG: the bar glyph.
|
|
26
|
+
property string fallbackGlyph: ""
|
|
27
|
+
|
|
28
|
+
readonly property color dim: Qt.darker(foreground, 1.55)
|
|
29
|
+
readonly property color surface: Color.popups.background
|
|
30
|
+
readonly property color track: Style.selectedFillFor(foreground, Color.accent)
|
|
31
|
+
readonly property bool lightSurface: colorLuminance(surface) >= 0.5
|
|
32
|
+
|
|
33
|
+
readonly property var providers: usage ? usage.providers : []
|
|
34
|
+
readonly property var overrides: usage ? usage.expandedOverrides : ({})
|
|
35
|
+
readonly property string defaultExpanded: Logic.defaultExpandedId(providers)
|
|
36
|
+
|
|
37
|
+
// Keyboard cursor over the section headers. It follows the provider, not
|
|
38
|
+
// the slot, so a record that lands while the panel is open does not move
|
|
39
|
+
// it underneath you.
|
|
40
|
+
property bool cursorActive: false
|
|
41
|
+
property string cursorId: ""
|
|
42
|
+
readonly property int cursorIndex: {
|
|
43
|
+
for (var i = 0; i < providers.length; i++) if (providers[i].providerId === cursorId) return i
|
|
44
|
+
return 0
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
readonly property real contentImplicitHeight: column.implicitHeight
|
|
48
|
+
readonly property alias flick: panelFlick
|
|
49
|
+
|
|
50
|
+
implicitHeight: column.implicitHeight
|
|
51
|
+
|
|
52
|
+
function alpha(c, a) { return Qt.rgba(c.r, c.g, c.b, a) }
|
|
53
|
+
|
|
54
|
+
function colorChannelLuminance(value) {
|
|
55
|
+
var channel = Number(value)
|
|
56
|
+
if (!isFinite(channel)) return 0
|
|
57
|
+
return channel <= 0.03928 ? channel / 12.92 : Math.pow((channel + 0.055) / 1.055, 2.4)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function colorLuminance(color) {
|
|
61
|
+
return 0.2126 * colorChannelLuminance(color.r)
|
|
62
|
+
+ 0.7152 * colorChannelLuminance(color.g)
|
|
63
|
+
+ 0.0722 * colorChannelLuminance(color.b)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function expanded(id) { return Logic.isExpanded(id, overrides, defaultExpanded) }
|
|
67
|
+
|
|
68
|
+
function setExpanded(id, value) {
|
|
69
|
+
if (usage) usage.setExpanded(id, value)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function toggle(id) { setExpanded(id, !expanded(id)) }
|
|
73
|
+
|
|
74
|
+
function sectionItem(index) { return sectionRepeater.itemAt(index) }
|
|
75
|
+
|
|
76
|
+
function resetView() {
|
|
77
|
+
cursorActive = false
|
|
78
|
+
cursorId = providers.length > 0 ? providers[0].providerId : ""
|
|
79
|
+
panelFlick.contentY = 0
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ------------------------------------------------------------- keyboard
|
|
83
|
+
|
|
84
|
+
function maxScroll() { return Math.max(0, panelFlick.contentHeight - panelFlick.height) }
|
|
85
|
+
|
|
86
|
+
function scrollTo(y) { panelFlick.contentY = Logic.clamp(y, 0, maxScroll()) }
|
|
87
|
+
|
|
88
|
+
function ensureVisible(item) {
|
|
89
|
+
if (!item) return
|
|
90
|
+
var margin = Style.space(6)
|
|
91
|
+
var top = item.mapToItem(column, 0, 0).y
|
|
92
|
+
var bottom = top + item.height
|
|
93
|
+
if (top < panelFlick.contentY + margin) scrollTo(top - margin)
|
|
94
|
+
else if (bottom > panelFlick.contentY + panelFlick.height - margin) scrollTo(bottom + margin - panelFlick.height)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// j/k walk the headers. An open section taller than the viewport is paged
|
|
98
|
+
// through first, so nothing in it is skipped on the way to the next one.
|
|
99
|
+
function moveCursor(dy) {
|
|
100
|
+
if (providers.length === 0) return
|
|
101
|
+
if (!cursorActive) {
|
|
102
|
+
cursorActive = true
|
|
103
|
+
if (cursorId === "") cursorId = providers[0].providerId
|
|
104
|
+
ensureVisible(sectionItem(cursorIndex) ? sectionItem(cursorIndex).header : null)
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
var step = Style.space(56)
|
|
108
|
+
var current = sectionItem(cursorIndex)
|
|
109
|
+
if (current && expanded(providers[cursorIndex].providerId)) {
|
|
110
|
+
var top = current.mapToItem(column, 0, 0).y
|
|
111
|
+
var bottom = top + current.height
|
|
112
|
+
if (dy > 0 && bottom > panelFlick.contentY + panelFlick.height + 1 && panelFlick.contentY < maxScroll()) {
|
|
113
|
+
scrollTo(panelFlick.contentY + step)
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
if (dy < 0 && top < panelFlick.contentY - 1) {
|
|
117
|
+
scrollTo(panelFlick.contentY - step)
|
|
118
|
+
return
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
var next = Logic.clamp(cursorIndex + dy, 0, providers.length - 1)
|
|
122
|
+
if (next === cursorIndex) {
|
|
123
|
+
scrollTo(panelFlick.contentY + dy * step)
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
cursorId = providers[next].providerId
|
|
127
|
+
var section = sectionItem(next)
|
|
128
|
+
if (section) ensureVisible(section.header)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function toggleCursor() {
|
|
132
|
+
if (providers.length === 0) return
|
|
133
|
+
cursorActive = true
|
|
134
|
+
var id = providers[cursorIndex].providerId
|
|
135
|
+
if (cursorId === "") cursorId = id
|
|
136
|
+
toggle(id)
|
|
137
|
+
Qt.callLater(function() {
|
|
138
|
+
var section = sectionItem(cursorIndex)
|
|
139
|
+
if (section) ensureVisible(section.header)
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function setCursorExpanded(value) {
|
|
144
|
+
if (providers.length === 0) return
|
|
145
|
+
cursorActive = true
|
|
146
|
+
var id = providers[cursorIndex].providerId
|
|
147
|
+
if (cursorId === "") cursorId = id
|
|
148
|
+
setExpanded(id, value)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function footerText() {
|
|
152
|
+
if (usage && usage.refreshing) return "Refreshing…"
|
|
153
|
+
var at = usage ? String(usage.newestUpdate || "") : ""
|
|
154
|
+
if (at === "") return ""
|
|
155
|
+
var ms = new Date(at).getTime()
|
|
156
|
+
if (!isFinite(ms)) return ""
|
|
157
|
+
var age = root.nowMs - ms
|
|
158
|
+
return age < 60000 ? "Updated just now" : "Updated " + Logic.compactDuration(age) + " ago"
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ---------------------------------------------------------------- list
|
|
162
|
+
|
|
163
|
+
Flickable {
|
|
164
|
+
id: panelFlick
|
|
165
|
+
anchors.fill: parent
|
|
166
|
+
contentWidth: width
|
|
167
|
+
contentHeight: column.implicitHeight
|
|
168
|
+
clip: true
|
|
169
|
+
boundsBehavior: Flickable.StopAtBounds
|
|
170
|
+
flickableDirection: Flickable.VerticalFlick
|
|
171
|
+
interactive: contentHeight > height
|
|
172
|
+
ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded }
|
|
173
|
+
|
|
174
|
+
Column {
|
|
175
|
+
id: column
|
|
176
|
+
width: panelFlick.width
|
|
177
|
+
spacing: Style.space(8)
|
|
178
|
+
|
|
179
|
+
Text {
|
|
180
|
+
visible: root.providers.length === 0
|
|
181
|
+
width: parent.width
|
|
182
|
+
topPadding: Style.space(24)
|
|
183
|
+
bottomPadding: Style.space(24)
|
|
184
|
+
text: "No AI usage records found.\nRun piquota omarchy, or use an agent once."
|
|
185
|
+
color: root.dim
|
|
186
|
+
font.family: root.fontFamily
|
|
187
|
+
font.pixelSize: Style.font.body
|
|
188
|
+
horizontalAlignment: Text.AlignHCenter
|
|
189
|
+
wrapMode: Text.WordWrap
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
Repeater {
|
|
193
|
+
id: sectionRepeater
|
|
194
|
+
model: root.providers
|
|
195
|
+
|
|
196
|
+
ProviderSection {
|
|
197
|
+
required property var modelData
|
|
198
|
+
required property int index
|
|
199
|
+
width: column.width
|
|
200
|
+
provider: modelData
|
|
201
|
+
sectionIndex: index
|
|
202
|
+
isLast: index === root.providers.length - 1
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
Text {
|
|
207
|
+
textFormat: Text.PlainText
|
|
208
|
+
visible: text !== ""
|
|
209
|
+
width: parent.width
|
|
210
|
+
topPadding: Style.space(2)
|
|
211
|
+
text: root.footerText()
|
|
212
|
+
color: root.dim
|
|
213
|
+
font.family: root.fontFamily
|
|
214
|
+
font.pixelSize: Style.font.caption
|
|
215
|
+
horizontalAlignment: Text.AlignHCenter
|
|
216
|
+
elide: Text.ElideRight
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ------------------------------------------------------------ section
|
|
222
|
+
|
|
223
|
+
component ProviderSection: Column {
|
|
224
|
+
id: section
|
|
225
|
+
property var provider: null
|
|
226
|
+
property int sectionIndex: 0
|
|
227
|
+
property bool isLast: false
|
|
228
|
+
property alias header: headerSurface
|
|
229
|
+
|
|
230
|
+
readonly property bool open: !!provider && root.expanded(provider.providerId)
|
|
231
|
+
readonly property var head: Logic.headline(provider)
|
|
232
|
+
readonly property var limitList: provider && (!provider.accounts || provider.accounts.length === 0)
|
|
233
|
+
? Logic.limitWindows(provider.limits) : []
|
|
234
|
+
readonly property var accountList: Logic.accountRows(provider)
|
|
235
|
+
readonly property var balance: provider ? provider.balance : null
|
|
236
|
+
readonly property bool balanceAlarming: Logic.balanceAlarming(balance)
|
|
237
|
+
readonly property var models: Logic.modelRows(provider)
|
|
238
|
+
readonly property bool hasDays: Logic.hasDays(provider)
|
|
239
|
+
|
|
240
|
+
spacing: Style.space(12)
|
|
241
|
+
|
|
242
|
+
// ---------- Header: mark · name · plan · headline ----------
|
|
243
|
+
CursorSurface {
|
|
244
|
+
id: headerSurface
|
|
245
|
+
width: parent.width
|
|
246
|
+
hasCursor: root.cursorActive && root.cursorIndex === section.sectionIndex
|
|
247
|
+
foreground: root.foreground
|
|
248
|
+
implicitHeight: headerContent.implicitHeight + Style.spacing.lg * 2
|
|
249
|
+
|
|
250
|
+
MouseArea {
|
|
251
|
+
anchors.fill: parent
|
|
252
|
+
hoverEnabled: true
|
|
253
|
+
cursorShape: Qt.PointingHandCursor
|
|
254
|
+
onEntered: {
|
|
255
|
+
root.cursorActive = true
|
|
256
|
+
root.cursorId = section.provider.providerId
|
|
257
|
+
}
|
|
258
|
+
onClicked: {
|
|
259
|
+
root.cursorId = section.provider.providerId
|
|
260
|
+
root.toggle(section.provider.providerId)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
Item {
|
|
265
|
+
id: headerContent
|
|
266
|
+
anchors.left: parent.left
|
|
267
|
+
anchors.right: parent.right
|
|
268
|
+
anchors.verticalCenter: parent.verticalCenter
|
|
269
|
+
anchors.leftMargin: Style.space(8)
|
|
270
|
+
anchors.rightMargin: Style.space(8)
|
|
271
|
+
implicitHeight: Math.max(mark.height, labels.implicitHeight, trailing.implicitHeight)
|
|
272
|
+
|
|
273
|
+
Mark {
|
|
274
|
+
id: mark
|
|
275
|
+
providerId: section.provider ? section.provider.providerId : ""
|
|
276
|
+
size: Style.font.display
|
|
277
|
+
anchors.left: parent.left
|
|
278
|
+
anchors.verticalCenter: parent.verticalCenter
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
Column {
|
|
282
|
+
id: labels
|
|
283
|
+
anchors.left: mark.right
|
|
284
|
+
anchors.leftMargin: Style.space(14)
|
|
285
|
+
anchors.right: trailing.left
|
|
286
|
+
anchors.rightMargin: Style.space(10)
|
|
287
|
+
anchors.verticalCenter: parent.verticalCenter
|
|
288
|
+
spacing: Style.space(2)
|
|
289
|
+
|
|
290
|
+
Row {
|
|
291
|
+
width: parent.width
|
|
292
|
+
spacing: Style.space(8)
|
|
293
|
+
|
|
294
|
+
Text {
|
|
295
|
+
id: nameText
|
|
296
|
+
textFormat: Text.PlainText
|
|
297
|
+
text: section.provider ? section.provider.providerName : ""
|
|
298
|
+
width: Math.min(implicitWidth, parent.width - (chip.visible ? chip.width + parent.spacing : 0))
|
|
299
|
+
color: root.foreground
|
|
300
|
+
font.family: root.fontFamily
|
|
301
|
+
font.pixelSize: Style.font.title
|
|
302
|
+
font.bold: true
|
|
303
|
+
elide: Text.ElideRight
|
|
304
|
+
anchors.verticalCenter: parent.verticalCenter
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
BorderSurface {
|
|
308
|
+
id: chip
|
|
309
|
+
readonly property string label: Logic.statusChip(section.provider)
|
|
310
|
+
visible: label !== ""
|
|
311
|
+
width: chipText.implicitWidth + Style.space(10)
|
|
312
|
+
height: chipText.implicitHeight + Style.space(2)
|
|
313
|
+
anchors.verticalCenter: parent.verticalCenter
|
|
314
|
+
color: root.alpha(root.urgent, 0.10)
|
|
315
|
+
borderSpec: Border.flat(root.alpha(root.urgent, 0.35), 1)
|
|
316
|
+
radius: Style.cornerRadius
|
|
317
|
+
|
|
318
|
+
Text {
|
|
319
|
+
id: chipText
|
|
320
|
+
textFormat: Text.PlainText
|
|
321
|
+
anchors.centerIn: parent
|
|
322
|
+
text: chip.label
|
|
323
|
+
color: root.urgent
|
|
324
|
+
font.family: root.fontFamily
|
|
325
|
+
font.pixelSize: Style.font.caption
|
|
326
|
+
font.bold: true
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
Text {
|
|
332
|
+
textFormat: Text.PlainText
|
|
333
|
+
width: parent.width
|
|
334
|
+
text: Logic.headerMeta(section.provider).toUpperCase()
|
|
335
|
+
visible: text !== ""
|
|
336
|
+
color: root.dim
|
|
337
|
+
font.family: root.fontFamily
|
|
338
|
+
font.pixelSize: Style.font.caption
|
|
339
|
+
font.bold: true
|
|
340
|
+
font.letterSpacing: 1.2
|
|
341
|
+
elide: Text.ElideRight
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
Row {
|
|
346
|
+
id: trailing
|
|
347
|
+
anchors.right: parent.right
|
|
348
|
+
anchors.verticalCenter: parent.verticalCenter
|
|
349
|
+
spacing: Style.space(10)
|
|
350
|
+
|
|
351
|
+
Column {
|
|
352
|
+
visible: !!section.head
|
|
353
|
+
anchors.verticalCenter: parent.verticalCenter
|
|
354
|
+
spacing: Style.space(2)
|
|
355
|
+
|
|
356
|
+
Text {
|
|
357
|
+
textFormat: Text.PlainText
|
|
358
|
+
anchors.right: parent.right
|
|
359
|
+
text: section.head ? Math.round(section.head.percent * 100) + "%" : ""
|
|
360
|
+
color: section.head && section.head.percent >= 0.9 ? root.urgent : root.foreground
|
|
361
|
+
font.family: root.fontFamily
|
|
362
|
+
font.pixelSize: Style.font.title
|
|
363
|
+
font.bold: true
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
Text {
|
|
367
|
+
textFormat: Text.PlainText
|
|
368
|
+
anchors.right: parent.right
|
|
369
|
+
text: {
|
|
370
|
+
if (!section.head) return ""
|
|
371
|
+
var ms = Logic.resetMs(section.head, root.nowMs)
|
|
372
|
+
return section.head.title + (ms > 0 ? " · " + Logic.compactDuration(ms) : "")
|
|
373
|
+
}
|
|
374
|
+
color: root.dim
|
|
375
|
+
font.family: root.fontFamily
|
|
376
|
+
font.pixelSize: Style.font.caption
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
Text {
|
|
381
|
+
textFormat: Text.PlainText
|
|
382
|
+
anchors.verticalCenter: parent.verticalCenter
|
|
383
|
+
text: section.open ? "" : ""
|
|
384
|
+
color: root.dim
|
|
385
|
+
font.family: root.fontFamily
|
|
386
|
+
font.pixelSize: Style.font.icon
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// A folded section still shows how full its headline window is.
|
|
393
|
+
Meter {
|
|
394
|
+
visible: !section.open && !!section.head
|
|
395
|
+
width: parent.width
|
|
396
|
+
value: section.head ? section.head.percent : -1
|
|
397
|
+
alarming: !!section.head && section.head.percent >= 0.9
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// ---------- Body: the built-in tab, section by section ----------
|
|
401
|
+
Column {
|
|
402
|
+
id: body
|
|
403
|
+
visible: section.open
|
|
404
|
+
width: parent.width
|
|
405
|
+
spacing: Style.space(14)
|
|
406
|
+
|
|
407
|
+
// Status
|
|
408
|
+
BorderSurface {
|
|
409
|
+
visible: !!section.provider && section.provider.usageStatusText !== "" && section.provider.authHelpText !== ""
|
|
410
|
+
width: parent.width
|
|
411
|
+
implicitHeight: statusText.implicitHeight + Style.spacing.xl * 2
|
|
412
|
+
color: root.alpha(root.urgent, 0.10)
|
|
413
|
+
borderSpec: Border.flat(root.alpha(root.urgent, 0.35), 1)
|
|
414
|
+
radius: Style.cornerRadius
|
|
415
|
+
|
|
416
|
+
Text {
|
|
417
|
+
id: statusText
|
|
418
|
+
textFormat: Text.PlainText
|
|
419
|
+
anchors.left: parent.left
|
|
420
|
+
anchors.right: parent.right
|
|
421
|
+
anchors.verticalCenter: parent.verticalCenter
|
|
422
|
+
anchors.leftMargin: Style.space(12)
|
|
423
|
+
anchors.rightMargin: Style.space(12)
|
|
424
|
+
text: section.provider
|
|
425
|
+
? (section.provider.ready ? section.provider.usageStatusText + ": " : "") + section.provider.authHelpText
|
|
426
|
+
: ""
|
|
427
|
+
color: root.dim
|
|
428
|
+
font.family: root.fontFamily
|
|
429
|
+
font.pixelSize: Style.font.caption
|
|
430
|
+
wrapMode: Text.WordWrap
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Balance
|
|
435
|
+
Column {
|
|
436
|
+
visible: !!section.balance
|
|
437
|
+
width: parent.width
|
|
438
|
+
spacing: Style.space(10)
|
|
439
|
+
|
|
440
|
+
readonly property real ratio: section.balance && section.balance.funded > 0
|
|
441
|
+
? Logic.clamp(section.balance.remaining / section.balance.funded, 0, 1)
|
|
442
|
+
: -1
|
|
443
|
+
|
|
444
|
+
PanelSectionHeader {
|
|
445
|
+
width: parent.width
|
|
446
|
+
text: "BALANCE"
|
|
447
|
+
foreground: root.foreground
|
|
448
|
+
fontFamily: root.fontFamily
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
Item {
|
|
452
|
+
width: parent.width
|
|
453
|
+
implicitHeight: Math.max(balanceLabel.implicitHeight, balanceValue.implicitHeight)
|
|
454
|
+
|
|
455
|
+
Text {
|
|
456
|
+
id: balanceLabel
|
|
457
|
+
text: "Prepaid credits"
|
|
458
|
+
color: root.foreground
|
|
459
|
+
font.family: root.fontFamily
|
|
460
|
+
font.pixelSize: Style.font.body
|
|
461
|
+
anchors.left: parent.left
|
|
462
|
+
anchors.verticalCenter: parent.verticalCenter
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
Text {
|
|
466
|
+
id: balanceValue
|
|
467
|
+
textFormat: Text.PlainText
|
|
468
|
+
text: section.balance ? Logic.formatMoney(section.balance.remaining, section.balance.currency) : ""
|
|
469
|
+
color: section.balanceAlarming ? root.urgent : root.foreground
|
|
470
|
+
font.family: root.fontFamily
|
|
471
|
+
font.pixelSize: Style.font.caption
|
|
472
|
+
anchors.right: parent.right
|
|
473
|
+
anchors.verticalCenter: parent.verticalCenter
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
Meter {
|
|
478
|
+
visible: parent.ratio >= 0
|
|
479
|
+
width: parent.width
|
|
480
|
+
value: parent.ratio
|
|
481
|
+
alarming: section.balanceAlarming
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
Text {
|
|
485
|
+
textFormat: Text.PlainText
|
|
486
|
+
visible: text !== ""
|
|
487
|
+
width: parent.width
|
|
488
|
+
text: Logic.balanceDetailText(section.balance)
|
|
489
|
+
color: root.dim
|
|
490
|
+
font.family: root.fontFamily
|
|
491
|
+
font.pixelSize: Style.font.caption
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Limits (single account)
|
|
496
|
+
Column {
|
|
497
|
+
id: limitsSection
|
|
498
|
+
visible: section.limitList.length > 0
|
|
499
|
+
width: parent.width
|
|
500
|
+
spacing: Style.space(10)
|
|
501
|
+
|
|
502
|
+
PanelSectionHeader {
|
|
503
|
+
text: "LIMITS"
|
|
504
|
+
foreground: root.foreground
|
|
505
|
+
fontFamily: root.fontFamily
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
Repeater {
|
|
509
|
+
model: section.limitList
|
|
510
|
+
|
|
511
|
+
LimitRow {
|
|
512
|
+
required property var modelData
|
|
513
|
+
width: limitsSection.width
|
|
514
|
+
window: modelData
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Accounts (multi-account): one row per account, every window
|
|
520
|
+
Column {
|
|
521
|
+
id: accountsSection
|
|
522
|
+
visible: section.accountList.length > 0
|
|
523
|
+
width: parent.width
|
|
524
|
+
spacing: Style.space(10)
|
|
525
|
+
|
|
526
|
+
PanelSectionHeader {
|
|
527
|
+
text: "ACCOUNTS"
|
|
528
|
+
foreground: root.foreground
|
|
529
|
+
fontFamily: root.fontFamily
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
Repeater {
|
|
533
|
+
model: section.accountList
|
|
534
|
+
|
|
535
|
+
AccountRow {
|
|
536
|
+
required property var modelData
|
|
537
|
+
width: accountsSection.width
|
|
538
|
+
account: modelData
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Tokens by day
|
|
544
|
+
Column {
|
|
545
|
+
id: usageSection
|
|
546
|
+
visible: section.hasDays
|
|
547
|
+
width: parent.width
|
|
548
|
+
spacing: Style.spacing.md
|
|
549
|
+
|
|
550
|
+
readonly property var days: section.provider ? Logic.recentDayRows(section.provider) : []
|
|
551
|
+
readonly property real peak: Math.max(1, Logic.weekPeak(section.provider))
|
|
552
|
+
|
|
553
|
+
PanelSectionHeader {
|
|
554
|
+
width: parent.width
|
|
555
|
+
text: "TOKENS BY DAY"
|
|
556
|
+
foreground: root.foreground
|
|
557
|
+
fontFamily: root.fontFamily
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
Repeater {
|
|
561
|
+
model: usageSection.days
|
|
562
|
+
|
|
563
|
+
DayRow {
|
|
564
|
+
required property var modelData
|
|
565
|
+
width: usageSection.width
|
|
566
|
+
day: modelData
|
|
567
|
+
provider: section.provider
|
|
568
|
+
ratio: Number(modelData.messageCount || 0) / usageSection.peak
|
|
569
|
+
today: String(modelData.date || "") === Logic.todayDate(root.nowMs)
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// Tokens by model
|
|
575
|
+
Column {
|
|
576
|
+
id: modelSection
|
|
577
|
+
visible: section.models.length > 0
|
|
578
|
+
width: parent.width
|
|
579
|
+
spacing: Style.spacing.md
|
|
580
|
+
|
|
581
|
+
PanelSectionHeader {
|
|
582
|
+
width: parent.width
|
|
583
|
+
text: "TOKENS BY MODEL"
|
|
584
|
+
foreground: root.foreground
|
|
585
|
+
fontFamily: root.fontFamily
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
Repeater {
|
|
589
|
+
model: section.models
|
|
590
|
+
|
|
591
|
+
ModelRow {
|
|
592
|
+
required property var modelData
|
|
593
|
+
width: modelSection.width
|
|
594
|
+
row: modelData
|
|
595
|
+
share: modelData.total / Math.max(1, section.models[0].total)
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
Item { width: 1; height: Style.space(2) }
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
PanelSeparator {
|
|
604
|
+
visible: !section.isLast
|
|
605
|
+
foreground: root.foreground
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Provider mark: assets/<id>.svg, then the family without the pi- prefix,
|
|
610
|
+
// each with a -light twin first on light surfaces; the bar glyph otherwise.
|
|
611
|
+
component Mark: Item {
|
|
612
|
+
id: markItem
|
|
613
|
+
property string providerId: ""
|
|
614
|
+
property real size: Style.font.display
|
|
615
|
+
property var candidates: Logic.iconCandidates(providerId, root.lightSurface)
|
|
616
|
+
// Restart the fallback walk only when the URLs change: re-pointing source
|
|
617
|
+
// at a URL whose load already failed emits no statusChanged.
|
|
618
|
+
property string candidatesKey: candidates.join("\n")
|
|
619
|
+
property int candidateIndex: 0
|
|
620
|
+
onCandidatesKeyChanged: candidateIndex = 0
|
|
621
|
+
|
|
622
|
+
width: size
|
|
623
|
+
height: size
|
|
624
|
+
|
|
625
|
+
Image {
|
|
626
|
+
id: markImage
|
|
627
|
+
anchors.fill: parent
|
|
628
|
+
source: markItem.candidateIndex < markItem.candidates.length ? Qt.resolvedUrl(markItem.candidates[markItem.candidateIndex]) : ""
|
|
629
|
+
sourceSize.width: markItem.size * 2
|
|
630
|
+
sourceSize.height: markItem.size * 2
|
|
631
|
+
fillMode: Image.PreserveAspectFit
|
|
632
|
+
onStatusChanged: if (status === Image.Error && markItem.candidateIndex < markItem.candidates.length)
|
|
633
|
+
Qt.callLater(function() { markItem.candidateIndex++ })
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
Text {
|
|
637
|
+
textFormat: Text.PlainText
|
|
638
|
+
anchors.centerIn: parent
|
|
639
|
+
visible: markImage.status !== Image.Ready
|
|
640
|
+
text: root.fallbackGlyph
|
|
641
|
+
color: root.foreground
|
|
642
|
+
font.family: root.fontFamily
|
|
643
|
+
font.pixelSize: markItem.size
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// A limit window: label and percentage, meter, and reset countdown.
|
|
648
|
+
component LimitRow: Column {
|
|
649
|
+
id: limitRow
|
|
650
|
+
property var window: null
|
|
651
|
+
|
|
652
|
+
readonly property bool alarming: window && window.percent >= 0.9
|
|
653
|
+
|
|
654
|
+
spacing: Style.space(6)
|
|
655
|
+
|
|
656
|
+
Item {
|
|
657
|
+
width: parent.width
|
|
658
|
+
implicitHeight: Math.max(limitLabel.implicitHeight, limitValue.implicitHeight)
|
|
659
|
+
|
|
660
|
+
Text {
|
|
661
|
+
id: limitLabel
|
|
662
|
+
textFormat: Text.PlainText
|
|
663
|
+
text: limitRow.window ? limitRow.window.title : ""
|
|
664
|
+
color: root.foreground
|
|
665
|
+
font.family: root.fontFamily
|
|
666
|
+
font.pixelSize: Style.font.body
|
|
667
|
+
elide: Text.ElideRight
|
|
668
|
+
anchors.left: parent.left
|
|
669
|
+
anchors.right: limitValue.left
|
|
670
|
+
anchors.rightMargin: Style.spacing.sm
|
|
671
|
+
anchors.verticalCenter: parent.verticalCenter
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
Text {
|
|
675
|
+
id: limitValue
|
|
676
|
+
textFormat: Text.PlainText
|
|
677
|
+
text: limitRow.window && limitRow.window.percent >= 0
|
|
678
|
+
? Math.round(limitRow.window.percent * 100) + "%"
|
|
679
|
+
: "—"
|
|
680
|
+
color: limitRow.alarming ? root.urgent : root.foreground
|
|
681
|
+
font.family: root.fontFamily
|
|
682
|
+
font.pixelSize: Style.font.caption
|
|
683
|
+
anchors.right: parent.right
|
|
684
|
+
anchors.verticalCenter: parent.verticalCenter
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
Meter {
|
|
689
|
+
width: parent.width
|
|
690
|
+
value: limitRow.window ? limitRow.window.percent : -1
|
|
691
|
+
alarming: limitRow.alarming
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
Text {
|
|
695
|
+
textFormat: Text.PlainText
|
|
696
|
+
width: parent.width
|
|
697
|
+
text: {
|
|
698
|
+
var remainingMs = Logic.resetMs(limitRow.window, root.nowMs)
|
|
699
|
+
return remainingMs > 0 ? "Resets in " + Logic.formatDuration(remainingMs) : ""
|
|
700
|
+
}
|
|
701
|
+
color: root.dim
|
|
702
|
+
font.family: root.fontFamily
|
|
703
|
+
font.pixelSize: Style.font.caption
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// One account of a multi-account provider: its slot, then every window as
|
|
708
|
+
// a column of tag, percentage, reset and a mini meter, aligned across rows
|
|
709
|
+
// like a table. A failed account is dimmed and says why.
|
|
710
|
+
component AccountRow: Item {
|
|
711
|
+
id: accountRow
|
|
712
|
+
property var account: null
|
|
713
|
+
|
|
714
|
+
readonly property var windows: account ? account.windows : []
|
|
715
|
+
readonly property bool ok: !!account && account.ok
|
|
716
|
+
readonly property real columnGap: Style.space(12)
|
|
717
|
+
|
|
718
|
+
implicitHeight: Math.max(slotText.implicitHeight, ok && windows.length > 0 ? windowColumns.implicitHeight : statusLine.implicitHeight)
|
|
719
|
+
opacity: ok ? 1 : 0.55
|
|
720
|
+
|
|
721
|
+
Text {
|
|
722
|
+
id: slotText
|
|
723
|
+
textFormat: Text.PlainText
|
|
724
|
+
text: accountRow.account ? accountRow.account.id : ""
|
|
725
|
+
color: root.foreground
|
|
726
|
+
font.family: root.fontFamily
|
|
727
|
+
font.pixelSize: Style.font.bodySmall
|
|
728
|
+
font.bold: true
|
|
729
|
+
width: Style.space(30)
|
|
730
|
+
anchors.left: parent.left
|
|
731
|
+
anchors.top: parent.top
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
Row {
|
|
735
|
+
id: windowColumns
|
|
736
|
+
visible: accountRow.ok && accountRow.windows.length > 0
|
|
737
|
+
anchors.left: slotText.right
|
|
738
|
+
anchors.leftMargin: Style.space(6)
|
|
739
|
+
anchors.right: parent.right
|
|
740
|
+
anchors.top: parent.top
|
|
741
|
+
spacing: accountRow.columnGap
|
|
742
|
+
|
|
743
|
+
readonly property real cellWidth: accountRow.windows.length > 0
|
|
744
|
+
? (width - spacing * (accountRow.windows.length - 1)) / accountRow.windows.length
|
|
745
|
+
: 0
|
|
746
|
+
|
|
747
|
+
Repeater {
|
|
748
|
+
model: accountRow.windows
|
|
749
|
+
|
|
750
|
+
Column {
|
|
751
|
+
id: cell
|
|
752
|
+
required property var modelData
|
|
753
|
+
readonly property bool alarming: modelData.percent >= 0.9
|
|
754
|
+
width: windowColumns.cellWidth
|
|
755
|
+
spacing: Style.space(4)
|
|
756
|
+
|
|
757
|
+
Item {
|
|
758
|
+
width: parent.width
|
|
759
|
+
implicitHeight: cellPercent.implicitHeight
|
|
760
|
+
|
|
761
|
+
Text {
|
|
762
|
+
id: cellTag
|
|
763
|
+
textFormat: Text.PlainText
|
|
764
|
+
anchors.left: parent.left
|
|
765
|
+
anchors.verticalCenter: parent.verticalCenter
|
|
766
|
+
width: Math.min(implicitWidth, Math.max(0, cellReset.x - cellPercent.implicitWidth - Style.space(8)))
|
|
767
|
+
elide: Text.ElideRight
|
|
768
|
+
text: Logic.windowTag(cell.modelData.title)
|
|
769
|
+
color: root.dim
|
|
770
|
+
font.family: root.fontFamily
|
|
771
|
+
font.pixelSize: Style.font.caption
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
Text {
|
|
775
|
+
id: cellPercent
|
|
776
|
+
textFormat: Text.PlainText
|
|
777
|
+
anchors.left: cellTag.right
|
|
778
|
+
anchors.leftMargin: Style.space(4)
|
|
779
|
+
anchors.verticalCenter: parent.verticalCenter
|
|
780
|
+
text: Math.round(cell.modelData.percent * 100) + "%"
|
|
781
|
+
color: cell.alarming ? root.urgent : root.foreground
|
|
782
|
+
font.family: root.fontFamily
|
|
783
|
+
font.pixelSize: Style.font.caption
|
|
784
|
+
font.bold: true
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
Text {
|
|
788
|
+
id: cellReset
|
|
789
|
+
textFormat: Text.PlainText
|
|
790
|
+
anchors.right: parent.right
|
|
791
|
+
text: {
|
|
792
|
+
var ms = Logic.resetMs(cell.modelData, root.nowMs)
|
|
793
|
+
return ms > 0 ? Logic.compactDuration(ms) : ""
|
|
794
|
+
}
|
|
795
|
+
color: root.dim
|
|
796
|
+
font.family: root.fontFamily
|
|
797
|
+
font.pixelSize: Style.font.caption
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
Meter {
|
|
802
|
+
width: parent.width
|
|
803
|
+
thickness: Math.max(Style.space(3), Math.round(Style.spacing.controlHeight * 0.1))
|
|
804
|
+
value: cell.modelData.percent
|
|
805
|
+
alarming: cell.alarming
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
Text {
|
|
812
|
+
id: statusLine
|
|
813
|
+
visible: !accountRow.ok || accountRow.windows.length === 0
|
|
814
|
+
textFormat: Text.PlainText
|
|
815
|
+
anchors.left: slotText.right
|
|
816
|
+
anchors.leftMargin: Style.space(6)
|
|
817
|
+
anchors.right: parent.right
|
|
818
|
+
anchors.verticalCenter: slotText.verticalCenter
|
|
819
|
+
text: accountRow.account && accountRow.account.status !== "" ? accountRow.account.status : "No usage windows"
|
|
820
|
+
color: root.dim
|
|
821
|
+
font.family: root.fontFamily
|
|
822
|
+
font.pixelSize: Style.font.caption
|
|
823
|
+
elide: Text.ElideRight
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// Rounded track showing the percentage of the allowance used.
|
|
828
|
+
component Meter: Item {
|
|
829
|
+
id: meter
|
|
830
|
+
property real value: -1
|
|
831
|
+
property bool alarming: false
|
|
832
|
+
property real thickness: Math.max(Style.space(4), Math.round(Style.spacing.controlHeight * 0.14))
|
|
833
|
+
|
|
834
|
+
implicitHeight: thickness
|
|
835
|
+
|
|
836
|
+
Rectangle {
|
|
837
|
+
id: meterTrack
|
|
838
|
+
anchors.fill: parent
|
|
839
|
+
radius: height / 2
|
|
840
|
+
color: root.track
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
Rectangle {
|
|
844
|
+
anchors.left: meterTrack.left
|
|
845
|
+
anchors.verticalCenter: meterTrack.verticalCenter
|
|
846
|
+
height: meterTrack.height
|
|
847
|
+
radius: meterTrack.radius
|
|
848
|
+
width: meterTrack.width * Logic.clamp(meter.value, 0, 1)
|
|
849
|
+
color: meter.alarming ? root.urgent : root.foreground
|
|
850
|
+
|
|
851
|
+
Behavior on width {
|
|
852
|
+
NumberAnimation { duration: 160; easing.type: Easing.OutCubic }
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// One row per day: label, bar, tokens. Today is picked out in full
|
|
858
|
+
// foreground so the week reads as a run-up to right now.
|
|
859
|
+
component DayRow: Item {
|
|
860
|
+
id: dayRow
|
|
861
|
+
property var day: null
|
|
862
|
+
property var provider: null
|
|
863
|
+
property real ratio: 0
|
|
864
|
+
property bool today: false
|
|
865
|
+
|
|
866
|
+
implicitHeight: Math.max(dayLabel.implicitHeight, dayValue.implicitHeight) + Style.spacing.sm
|
|
867
|
+
|
|
868
|
+
Text {
|
|
869
|
+
id: dayLabel
|
|
870
|
+
textFormat: Text.PlainText
|
|
871
|
+
text: Logic.dayLabel(dayRow.day ? dayRow.day.date : "", dayRow.today)
|
|
872
|
+
color: dayRow.today ? root.foreground : root.dim
|
|
873
|
+
font.family: root.fontFamily
|
|
874
|
+
font.pixelSize: Style.font.caption
|
|
875
|
+
font.bold: dayRow.today
|
|
876
|
+
anchors.left: parent.left
|
|
877
|
+
anchors.verticalCenter: parent.verticalCenter
|
|
878
|
+
width: Style.space(52)
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
Rectangle {
|
|
882
|
+
anchors.left: dayLabel.right
|
|
883
|
+
anchors.right: dayValue.left
|
|
884
|
+
anchors.leftMargin: Style.space(8)
|
|
885
|
+
anchors.rightMargin: Style.space(10)
|
|
886
|
+
anchors.verticalCenter: parent.verticalCenter
|
|
887
|
+
height: Math.max(Style.space(4), Math.round(Style.spacing.controlHeight * 0.14))
|
|
888
|
+
radius: height / 2
|
|
889
|
+
color: root.track
|
|
890
|
+
|
|
891
|
+
Rectangle {
|
|
892
|
+
anchors.left: parent.left
|
|
893
|
+
anchors.verticalCenter: parent.verticalCenter
|
|
894
|
+
height: parent.height
|
|
895
|
+
radius: parent.radius
|
|
896
|
+
width: parent.width * Logic.clamp(dayRow.ratio, 0, 1)
|
|
897
|
+
color: dayRow.today ? root.foreground : root.alpha(root.foreground, 0.55)
|
|
898
|
+
|
|
899
|
+
Behavior on width {
|
|
900
|
+
NumberAnimation { duration: 160; easing.type: Easing.OutCubic }
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
Text {
|
|
906
|
+
id: dayValue
|
|
907
|
+
textFormat: Text.PlainText
|
|
908
|
+
text: Logic.formatTokenCount(dayRow.day ? Number(dayRow.day.messageCount || 0) : 0)
|
|
909
|
+
color: dayRow.today ? root.foreground : root.dim
|
|
910
|
+
font.family: root.fontFamily
|
|
911
|
+
font.pixelSize: Style.font.caption
|
|
912
|
+
font.bold: true
|
|
913
|
+
horizontalAlignment: Text.AlignRight
|
|
914
|
+
anchors.right: parent.right
|
|
915
|
+
anchors.verticalCenter: parent.verticalCenter
|
|
916
|
+
width: Style.space(52)
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
MouseArea {
|
|
920
|
+
id: dayHover
|
|
921
|
+
anchors.fill: parent
|
|
922
|
+
hoverEnabled: true
|
|
923
|
+
acceptedButtons: Qt.NoButton
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
PanelToolTip {
|
|
927
|
+
visible: dayHover.containsMouse
|
|
928
|
+
text: Logic.dayTooltip(dayRow.day, dayRow.today, dayRow.provider)
|
|
929
|
+
fontFamily: root.fontFamily
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// Model rows read as a table: the share bar fills the row behind the label.
|
|
934
|
+
component ModelRow: Item {
|
|
935
|
+
id: modelRow
|
|
936
|
+
property var row: null
|
|
937
|
+
property real share: 0
|
|
938
|
+
|
|
939
|
+
implicitHeight: modelName.implicitHeight + Style.spacing.lg
|
|
940
|
+
|
|
941
|
+
Rectangle {
|
|
942
|
+
anchors.fill: parent
|
|
943
|
+
radius: Style.cornerRadius
|
|
944
|
+
color: root.alpha(root.foreground, 0.05)
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
Rectangle {
|
|
948
|
+
anchors.left: parent.left
|
|
949
|
+
anchors.top: parent.top
|
|
950
|
+
anchors.bottom: parent.bottom
|
|
951
|
+
width: parent.width * Logic.clamp(modelRow.share, 0, 1)
|
|
952
|
+
radius: Style.cornerRadius
|
|
953
|
+
color: root.alpha(root.foreground, 0.14)
|
|
954
|
+
|
|
955
|
+
Behavior on width {
|
|
956
|
+
NumberAnimation { duration: 160; easing.type: Easing.OutCubic }
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
Text {
|
|
961
|
+
id: modelName
|
|
962
|
+
textFormat: Text.PlainText
|
|
963
|
+
text: modelRow.row ? modelRow.row.name : ""
|
|
964
|
+
color: root.foreground
|
|
965
|
+
font.family: root.fontFamily
|
|
966
|
+
font.pixelSize: Style.font.bodySmall
|
|
967
|
+
elide: Text.ElideRight
|
|
968
|
+
anchors.left: parent.left
|
|
969
|
+
anchors.leftMargin: Style.space(8)
|
|
970
|
+
anchors.right: modelTokens.left
|
|
971
|
+
anchors.rightMargin: Style.space(8)
|
|
972
|
+
anchors.verticalCenter: parent.verticalCenter
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
Text {
|
|
976
|
+
id: modelTokens
|
|
977
|
+
textFormat: Text.PlainText
|
|
978
|
+
text: modelRow.row ? Logic.formatTokenCount(modelRow.row.total) : ""
|
|
979
|
+
color: root.dim
|
|
980
|
+
font.family: root.fontFamily
|
|
981
|
+
font.pixelSize: Style.font.bodySmall
|
|
982
|
+
font.bold: true
|
|
983
|
+
anchors.right: parent.right
|
|
984
|
+
anchors.rightMargin: Style.space(8)
|
|
985
|
+
anchors.verticalCenter: parent.verticalCenter
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
MouseArea {
|
|
989
|
+
id: modelHover
|
|
990
|
+
anchors.fill: parent
|
|
991
|
+
hoverEnabled: true
|
|
992
|
+
acceptedButtons: Qt.NoButton
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
PanelToolTip {
|
|
996
|
+
visible: modelHover.containsMouse
|
|
997
|
+
text: Logic.modelTooltip(modelRow.row)
|
|
998
|
+
fontFamily: root.fontFamily
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
}
|