feat(Monitoring): Backend's properties and models visualization
Closes: #8787 Closes: #8790
This commit is contained in:
parent
b7c1250115
commit
22da265d78
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}`)
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
singleton MonitorUtils 1.0 MonitorUtils.qml
|
|
@ -1 +1 @@
|
|||
Subproject commit 7f2dd438325925ae3db073e19eec63a49253e735
|
||||
Subproject commit 06a00bc8c3dd2c75cab7c084da84f213ffe1d70c
|
Loading…
Reference in New Issue