feat(Monitoring): Backend's properties and models visualization

Closes: #8787
Closes: #8790
This commit is contained in:
Michał Cieślak 2023-01-13 14:36:12 +01:00 committed by Michał
parent b7c1250115
commit 22da265d78
4 changed files with 473 additions and 25 deletions

View File

@ -6,35 +6,437 @@ import Monitoring 1.0
Component {
ColumnLayout {
anchors.fill: parent
anchors.margins: 10
SplitView {
id: root
spacing: 15
Label {
Layout.fillWidth: true
text: "Context properties:"
font.bold: true
}
ListView {
id: lv
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
model: Monitor.contexPropertiesNames
ColumnLayout {
SplitView.fillHeight: true
SplitView.preferredWidth: 450
spacing: 5
delegate: Text {
text: modelData
Label {
Layout.fillWidth: true
Layout.margins: 5
text: "Context properties:"
font.bold: true
}
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.margins: 5
model: Monitor.contexPropertiesModel
clip: true
spacing: 5
delegate: Item {
implicitWidth: delegateRow.implicitWidth
implicitHeight: delegateRow.implicitHeight
readonly property var contextPropertyValue:
MonitorUtils.contextPropertyBindingHelper(name, root).value
Row {
id: delegateRow
Label {
text: name
}
Label {
text: ` [${MonitorUtils.typeName(contextPropertyValue)}]`
color: "darkgreen"
}
Label {
text: ` (${MonitorUtils.valueToString(contextPropertyValue)})`
color: "darkred"
}
}
MouseArea {
anchors.fill: parent
onClicked: {
inspectionStackView.clear()
const props = {
name: name,
objectForInspection: contextPropertyValue
}
inspectionStackView.push(inspectionList, props)
}
}
}
}
}
Component {
id: modelInspectionComponent
Pane {
property string name
property var model
readonly property var rootModel: model
readonly property var roles: Monitor.modelRoles(model)
readonly property var rolesModelContent: roles.map(role => ({
visible: true,
name: role.name,
width: Math.ceil(fontMetrics.advanceWidth(` ${role.name} `))
}))
property int columnsTotalWidth:
rolesModelContent.reduce((a, x) => a + x.width, 0)
ListModel {
id: rolesModel
Component.onCompleted: append(rolesModelContent)
}
Control {
id: helperControl
font.bold: true
}
FontMetrics {
id: fontMetrics
font.bold: true
}
ColumnLayout {
anchors.fill: parent
RowLayout {
Layout.fillWidth: true
RoundButton {
text: "⬅️"
onClicked: {
inspectionStackView.pop(StackView.Immediate)
}
}
Label {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
text: name
font.pixelSize: 20
font.bold: true
}
}
MenuSeparator {
Layout.fillWidth: true
}
Label {
text: "Hint: use right/left button click on a column " +
"header to ajust width, press cell content to " +
"see full value"
}
Label {
text: `rows count: ${model.rowCount()}`
font.bold: true
}
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
contentWidth: columnsTotalWidth
flickableDirection: Flickable.AutoFlickDirection
model: rootModel
delegate: Rectangle {
implicitWidth: flow.implicitWidth
implicitHeight: flow.implicitHeight
readonly property var topModel: model
Row {
id: flow
Repeater {
model: rolesModel
Label {
id: label
width: model.width
height: implicitHeight * 1.2
text: topModel[model.name].toString()
elide: Text.ElideRight
maximumLineCount: 1
verticalAlignment: Text.AlignVCenter
leftPadding: 2
rightPadding: 1
Rectangle {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
height: 1
color: "gray"
}
Rectangle {
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
width: 1
color: "gray"
}
MouseArea {
id: labelMouseArea
anchors.fill: parent
}
ToolTip.visible: labelMouseArea.pressed
ToolTip.text: label.text
}
}
}
}
headerPositioning: ListView.OverlayHeader
header: Item {
implicitWidth: headerFlow.implicitWidth
implicitHeight: headerFlow.implicitHeight * 1.5
z: 2
Rectangle {
color: "whitesmoke"
anchors.fill: parent
border.color: "gray"
}
Row {
id: headerFlow
anchors.verticalCenter: parent.verticalCenter
Repeater {
model: rolesModel
Label {
text: ` ${model.name} `
font.bold: true
width: model.width
elide: Text.ElideRight
maximumLineCount: 1
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
const factor = 1.5
const oldWidth = model.width
const leftBtn = mouse.button === Qt.LeftButton
const newWidth
= Math.ceil(leftBtn ? oldWidth * factor : oldWidth / factor)
model.width = newWidth
columnsTotalWidth += newWidth - oldWidth
}
}
}
}
}
}
}
}
}
}
Component {
id: inspectionList
Pane {
id: inspectionPanel
property var objectForInspection
property string name
onObjectForInspectionChanged: {
inspectionModel.clear()
if (!objectForInspection)
return
const items = []
for (const property in objectForInspection) {
const type = typeof objectForInspection[property]
if (type === "function") {
items.push({
name: property,
category: "functions",
isModel: false,
type: type
})
} else {
const value = objectForInspection[property]
const detailedType = MonitorUtils.typeName(value)
const isModel = Monitor.isModel(value)
items.push({
name: property,
type: detailedType,
category: isModel? "models" : "properties",
isModel: isModel
})
}
}
items.sort((a, b) => {
const nameA = a.category
const nameB = b.category
if (nameA === nameB)
return 0
if (nameA === "models")
return -1
if (nameB === "models")
return 1
if (nameA < nameB)
return -1
if (nameA > nameB)
return 1
})
inspectionModel.append(items)
}
ColumnLayout {
anchors.fill: parent
anchors.margins: 5
Label {
text: name
font.pixelSize: 20
font.bold: true
}
MenuSeparator {
Layout.fillWidth: true
}
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 5
clip: true
model: ListModel {
id: inspectionModel
}
delegate: Item {
implicitWidth: delegateRow.implicitWidth
implicitHeight: delegateRow.implicitHeight
Row {
id: delegateRow
readonly property var object: objectForInspection[name]
Label {
text: name
}
Loader {
active: type !== "function"
sourceComponent: Label {
text: ` [${type}]`
color: "darkgreen"
}
}
Loader {
active: type !== "function"
sourceComponent: Label {
text: ` (${MonitorUtils.valueToString(delegateRow.object)})`
color: "darkred"
}
}
Loader {
active: isModel
sourceComponent: Label {
text: `, ${delegateRow.object.rowCount()} items`
color: "darkred"
font.bold: true
}
}
}
MouseArea {
anchors.fill: parent
onClicked: {
if (!isModel)
return
const props = {
name: name,
model: objectForInspection[name]
}
inspectionStackView.push(modelInspectionComponent,
props, StackView.Immediate)
}
}
}
section.property: "category"
section.delegate: Pane {
leftPadding: 0
Label {
text: section
font.bold: true
}
}
}
}
}
}
StackView {
id: inspectionStackView
SplitView.fillHeight: true
SplitView.minimumWidth: 100
}
}
}

View File

@ -0,0 +1,45 @@
pragma Singleton
import QtQml 2.14
import Monitoring 1.0
QtObject {
function typeName(obj) {
const type = Monitor.typeName(obj)
if (type === "QJSValue")
return typeof obj
return type
}
function valueToString(val) {
if (val === undefined)
return "undefined"
if (val === null)
return "null"
const str = val.toString()
if (typeof val === "string")
return `"${str}"`
if (typeof val !== "object")
return str
const bracketPos = str.indexOf("(")
if (bracketPos === -1)
return str
return str.substring(bracketPos + 1, str.length - 1)
}
function contextPropertyBindingHelper(name, parent) {
return Qt.createQmlObject(
`import QtQml 2.14; QtObject { readonly property var value: ${name} }`,
parent, `ctxPropHelperSnippet_${name}`)
}
}

1
monitoring/qmldir Normal file
View File

@ -0,0 +1 @@
singleton MonitorUtils 1.0 MonitorUtils.qml

2
vendor/DOtherSide vendored

@ -1 +1 @@
Subproject commit 7f2dd438325925ae3db073e19eec63a49253e735
Subproject commit 06a00bc8c3dd2c75cab7c084da84f213ffe1d70c