status-desktop/storybook/pages/SubmodelProxyModelPage.qml

346 lines
9.7 KiB
QML
Raw Normal View History

import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import StatusQ 0.1
import Storybook 1.0
import SortFilterProxyModel 0.2
import StatusQ.Core.Utils 0.1
Item {
id: root
readonly property string intro:
"The example uses two source models. The first model contains networks"
+ " (id and metadata such as name and color), visible on the left. The"
+ " second model contains tokens metadata and their balances per"
+ " network in the submodel (network id, balance).\n"
+ "The SubmodelProxyModel wrapping the tokens model joins the submodels"
+ " to the network model. It also provides filtering and sorting via"
+ " SFPM (slider and checkbox below). Additionally, SubmodelProxyModel"
+ " calculates the summary balance and issues it as a role in the"
+ " top-level model (via SumAggregator). This sum is then used to"
+ " dynamically sort the tokens model.\nClick on balances to increase"
+ " the amount."
readonly property int numberOfTokens: 2000
readonly property var colors: [
"purple", "lightgreen", "red", "blue", "darkgreen"
]
function getRandomInt(max) {
return Math.floor(Math.random() * max);
}
ListModel {
id: networksModel
ListElement {
chainId: "1"
name: "Mainnet"
color: "purple"
}
ListElement {
chainId: "2"
name: "Optimism"
color: "lightgreen"
}
ListElement {
chainId: "3"
name: "Status"
color: "red"
}
ListElement {
chainId: "4"
name: "Abitrum"
color: "blue"
}
ListElement {
chainId: "5"
name: "Sepolia"
color: "darkgreen"
}
}
ListModel {
id: tokensModel
Component.onCompleted: {
// Populate model with given number of tokens containing random
// balances
const numberOfTokens = root.numberOfTokens
const tokens = []
const chainIds = []
for (let n = 0; n < networksModel.count; n++)
chainIds.push(networksModel.get(n).chainId)
for (let i = 0; i < numberOfTokens; i++) {
const balances = []
const numberOfBalances = 1 + getRandomInt(networksModel.count)
const chainIdsCpy = [...chainIds]
for (let i = 0; i < numberOfBalances; i++) {
const chainId = chainIdsCpy.splice(
getRandomInt(chainIdsCpy.length), 1)[0]
balances.push({
chainId: chainId,
balance: 1 + getRandomInt(200)
})
}
tokens.push({ name: `Token ${i + 1}`, balances })
}
append(tokens)
}
}
// Proxy model joining networksModel to submodels under "balances" role.
// Additionally submodel is filtered and sorted via SFPM. Submodel is
// accessible via "submodel" context property.
SubmodelProxyModel {
id: submodelProxyModel
sourceModel: tokensModel
delegateModel: SortFilterProxyModel {
id: delegateRoot
// properties exposed as roles to the top-level model
readonly property var balancesCountRole: submodel.count
readonly property int sumRole: aggregator.value
sourceModel: joinModel
filters: FastExpressionFilter {
expression: balance >= thresholdSlider.value
expectedRoles: "balance"
}
sorters: RoleSorter {
roleName: "name"
enabled: sortCheckBox.checked
}
readonly property LeftJoinModel joinModel: LeftJoinModel {
leftModel: submodel
rightModel: networksModel
joinRole: "chainId"
}
readonly property SumAggregator aggregator: SumAggregator {
id: aggregator
model: delegateRoot
roleName: "balance"
}
}
submodelRoleName: "balances"
}
SortFilterProxyModel {
id: sortBySumProxy
sourceModel: submodelProxyModel
sorters: RoleSorter {
roleName: "sum"
ascendingOrder: false
}
}
ColumnLayout {
anchors.fill: parent
anchors.margins: 10
Label {
Layout.fillWidth: true
wrapMode: Text.Wrap
lineHeight: 1.2
text: root.intro
}
MenuSeparator {
Layout.fillWidth: true
}
RowLayout {
Layout.fillWidth: true
Layout.fillHeight: true
ListView {
Layout.preferredWidth: 110
Layout.leftMargin: 10
Layout.fillHeight: true
spacing: 20
model: networksModel
delegate: ColumnLayout {
width: ListView.view.width
Label {
Layout.fillWidth: true
text: model.name
font.bold: true
}
Rectangle {
Layout.preferredWidth: changeColorButton.width
Layout.preferredHeight: 10
color: model.color
}
Button {
id: changeColorButton
text: "Change color"
onClicked: {
const currentIdx = root.colors.indexOf(model.color)
const numberOfColors = root.colors.length
const nextIdx = (currentIdx + 1) % numberOfColors
networksModel.setProperty(model.index, "color",
root.colors[nextIdx])
}
}
}
}
Rectangle {
Layout.preferredWidth: 1
Layout.fillHeight: true
Layout.rightMargin: 20
color: "lightgray"
}
// ListView consuming model don't have to do any transformation
// of the submodels internally because it's handled externally via
// SubmodelProxyModel.
ListView {
id: listView
Layout.fillWidth: true
Layout.fillHeight: true
reuseItems: true
ScrollBar.vertical: ScrollBar {}
clip: true
spacing: 18
model: sortBySumProxy
delegate: ColumnLayout {
id: delegateRoot
width: ListView.view.width
height: 46
spacing: 0
readonly property var balances: model.balances
Label {
id: tokenLabel
Layout.fillWidth: true
text: model.name
font.bold: true
}
RowLayout {
spacing: 14
Layout.fillWidth: true
Repeater {
model: delegateRoot.balances
Rectangle {
width: label.implicitWidth * 1.5
height: label.implicitHeight * 2
color: "transparent"
border.width: 2
border.color: model.color
Label {
id: label
anchors.centerIn: parent
text: `${model.name} (${model.balance})`
font.pixelSize: 10
}
MouseArea {
anchors.fill: parent
onClicked: {
const item = ModelUtils.getByKey(
tokensModel, "name", tokenLabel.text)
const index = ModelUtils.indexOf(
item.balances, "chainId", model.chainId)
item.balances.setProperty(
index, "balance",
item.balances.get(index).balance + 1)
}
}
}
}
Label {
text: model.balancesCount + " / " + model.sum
}
}
}
}
}
MenuSeparator {
Layout.fillWidth: true
}
RowLayout {
Label {
text: `Number of tokens: ${listView.count}, minimum balance:`
}
Slider {
id: thresholdSlider
from: 0
to: 201
stepSize: 1
}
Label {
text: thresholdSlider.value
}
CheckBox {
id: sortCheckBox
text: "sort networks by name"
}
}
}
}
// category: Models