Files
qbz/crates/qbz-qt/qml/views/local/LocalScanProgress.qml
T
vicrodh c7a3012955 feat(local-library): complete catalog and metadata workflows
Add bounded native library models, complete grouping rails and scan progress. Bring source-aware metadata editing, media information and collection offline preflight to Qt, and repair inherited Plex album artwork without overriding healthy tokens.

cx
2026-08-28 18:50:05 -06:00

172 lines
6.2 KiB
QML

// Compact, read-only progress for the Local Library header. It combines the
// authoritative folder scanner with the derived catalog projection, but
// never reads rows from either model: only bounded counters cross into QML,
// preserving native paging and first-paint performance.
import QtQuick
import com.blitzfc.qbz
import "../../theme"
Item {
id: root
QbzTheme { id: theme }
readonly property var settingsDoc: {
try { return JSON.parse(QbzBridge.settingsJson || "{}") }
catch (e) { return ({}) }
}
readonly property var localScan: settingsDoc.library || ({})
readonly property var catalog: {
try { return JSON.parse(QbzLocal.localCatalogProgressJson || "{}") }
catch (e) { return ({}) }
}
readonly property bool folderActive: localScan.scanning === true
readonly property bool catalogActive: catalog.active === true
readonly property bool active: folderActive || catalogActive
readonly property real sourceDone: folderActive
? Number(localScan.sourceProcessed || 0) : Number(catalog.sourceDone || 0)
readonly property real sourceTotal: folderActive
? Number(localScan.sourceTotal || 0) : Number(catalog.sourceTotal || 0)
readonly property real overallDone: folderActive
? Number(localScan.processed || 0) : Number(catalog.overallDone || 0)
readonly property real overallTotal: folderActive
? Number(localScan.total || 0) : Number(catalog.overallTotal || 0)
readonly property int sourceIndex: folderActive
? Number(localScan.sourceIndex || 0) : Number(catalog.sourceIndex || 0)
readonly property int sourceCount: folderActive
? Number(localScan.sourceCount || 0) : Number(catalog.sourceCount || 0)
function boundedFraction(done, total) {
return total > 0 ? Math.max(0, Math.min(1, done / total)) : 0
}
function folderName() {
var id = Number(localScan.currentRootId || 0)
var folders = localScan.folders || []
for (var i = 0; i < folders.length; i++) {
if (Number(folders[i].id || 0) === id)
return folders[i].displayName || folders[i].path || ""
}
return QbzSession.tr("Local folders", QbzSession.trRev)
}
function catalogSourceName() {
var source = String(catalog.source || "")
if (source === "local")
return QbzSession.tr("Local folders", QbzSession.trRev)
if (source === "offline")
return QbzSession.tr("Offline", QbzSession.trRev)
if (source === "plex") return "Plex"
if (source === "jellyfin") return "Jellyfin"
if (source === "subsonic") return "Subsonic"
return String(catalog.sourceInstance || source)
}
readonly property string sourceName: folderActive ? folderName() : catalogSourceName()
readonly property string phaseLabel: folderActive
? QbzSession.tr("Scanning library", QbzSession.trRev)
: (catalog.phase === "bootstrap"
? QbzSession.tr("Building library index", QbzSession.trRev)
: QbzSession.tr("Updating library index", QbzSession.trRev))
readonly property string sourceOrdinal: sourceCount > 1 && sourceIndex > 0
? " · " + sourceIndex + "/" + sourceCount : ""
visible: active
height: 42
Text {
id: statusText
width: parent.width
height: 15
text: root.phaseLabel + " · " + root.sourceName + root.sourceOrdinal
color: theme.textSecondary
font.pixelSize: 11
font.weight: theme.weightMedium
elide: Text.ElideMiddle
verticalAlignment: Text.AlignVCenter
}
Item {
id: sourceLine
y: 17
width: parent.width
height: 10
Text {
id: sourceLabel
width: 42
anchors.verticalCenter: parent.verticalCenter
text: QbzSession.tr("Source", QbzSession.trRev)
color: theme.textMuted
font.pixelSize: 9
}
Rectangle {
anchors.left: sourceLabel.right
anchors.right: sourceCountText.left
anchors.leftMargin: 6
anchors.rightMargin: 8
anchors.verticalCenter: parent.verticalCenter
height: 3
radius: 2
color: theme.surfaceElevated
clip: true
Rectangle {
width: parent.width * root.boundedFraction(root.sourceDone, root.sourceTotal)
height: parent.height
radius: parent.radius
color: theme.accent
}
}
Text {
id: sourceCountText
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
width: 78
horizontalAlignment: Text.AlignRight
text: Math.round(root.sourceDone) + " / " + Math.round(root.sourceTotal)
color: theme.textMuted
font.pixelSize: 9
}
}
Item {
y: 29
width: parent.width
height: 10
Text {
id: overallLabel
width: 42
anchors.verticalCenter: parent.verticalCenter
text: QbzSession.tr("Overall", QbzSession.trRev)
color: theme.textMuted
font.pixelSize: 9
}
Rectangle {
anchors.left: overallLabel.right
anchors.right: overallCountText.left
anchors.leftMargin: 6
anchors.rightMargin: 8
anchors.verticalCenter: parent.verticalCenter
height: 3
radius: 2
color: theme.surfaceElevated
clip: true
Rectangle {
width: parent.width * root.boundedFraction(root.overallDone, root.overallTotal)
height: parent.height
radius: parent.radius
color: theme.accent
opacity: 0.72
}
}
Text {
id: overallCountText
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
width: 78
horizontalAlignment: Text.AlignRight
text: Math.round(root.overallDone) + " / " + Math.round(root.overallTotal)
color: theme.textMuted
font.pixelSize: 9
}
}
}