2023-04-21 09:36:24 +00:00
|
|
|
import QtQuick 2.15
|
|
|
|
import QtQml 2.15
|
|
|
|
import QtQuick.Controls 2.15
|
|
|
|
import QtQuick.Layouts 1.15
|
|
|
|
|
|
|
|
import StatusQ.Core 0.1
|
|
|
|
import StatusQ.Components 0.1
|
|
|
|
import StatusQ.Controls 0.1
|
|
|
|
import StatusQ.Core.Theme 0.1
|
|
|
|
|
2023-05-11 07:56:55 +00:00
|
|
|
import AppLayouts.stores 1.0
|
|
|
|
|
2023-04-21 09:36:24 +00:00
|
|
|
import SortFilterProxyModel 0.2
|
|
|
|
|
|
|
|
import utils 1.0
|
|
|
|
|
|
|
|
import "../panels"
|
|
|
|
import "../popups"
|
|
|
|
import "../stores"
|
|
|
|
import "../controls"
|
|
|
|
|
|
|
|
// Temporary developer view to test the filter APIs
|
2023-05-11 07:56:55 +00:00
|
|
|
Control {
|
2023-04-21 09:36:24 +00:00
|
|
|
id: root
|
|
|
|
|
2023-05-11 07:56:55 +00:00
|
|
|
property var controller: null
|
|
|
|
property var networksModel: null
|
|
|
|
property var assetsModel: null
|
|
|
|
property bool assetsLoading: true
|
|
|
|
|
|
|
|
// Mirrors src/backend/activity.nim ActivityType
|
|
|
|
enum ActivityType {
|
|
|
|
Send,
|
|
|
|
Receive,
|
|
|
|
Buy,
|
|
|
|
Swap,
|
|
|
|
Bridge
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mirrors src/backend/activity.nim ActivityStatus
|
|
|
|
enum ActivityStatus {
|
|
|
|
Failed,
|
|
|
|
Pending,
|
|
|
|
Complete,
|
|
|
|
Finalized
|
|
|
|
}
|
|
|
|
|
|
|
|
background: Rectangle {
|
|
|
|
anchors.fill: parent
|
|
|
|
color: "white"
|
|
|
|
}
|
|
|
|
|
|
|
|
QtObject {
|
|
|
|
id: d
|
|
|
|
|
|
|
|
readonly property int millisInADay: 24 * 60 * 60 * 1000
|
|
|
|
property int start: fromSlider.value > 0
|
|
|
|
? Math.floor(new Date(new Date() - (fromSlider.value * millisInADay)).getTime() / 1000)
|
|
|
|
: 0
|
|
|
|
property int end: toSlider.value > 0
|
|
|
|
? Math.floor(new Date(new Date() - (toSlider.value * millisInADay)).getTime() / 1000)
|
|
|
|
: 0
|
|
|
|
|
|
|
|
function updateFilter() {
|
|
|
|
// Time
|
|
|
|
controller.setFilterTime(d.start, d.end)
|
|
|
|
|
|
|
|
// Activity types
|
|
|
|
var types = []
|
|
|
|
for(var i = 0; i < typeModel.count; i++) {
|
|
|
|
let item = typeModel.get(i)
|
|
|
|
if(item.checked) {
|
|
|
|
types.push(i)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
controller.setFilterType(JSON.stringify(types))
|
|
|
|
|
|
|
|
// Activity status
|
|
|
|
var statuses = []
|
|
|
|
for(var i = 0; i < statusModel.count; i++) {
|
|
|
|
let item = statusModel.get(i)
|
|
|
|
if(item.checked) {
|
|
|
|
statuses.push(i)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
controller.setFilterStatus(JSON.stringify(statuses))
|
|
|
|
|
|
|
|
// Counterparty addresses
|
|
|
|
var addresses = toAddressesInput.text.split(',')
|
|
|
|
if(addresses.length == 1 && addresses[0].trim() == "") {
|
|
|
|
addresses = []
|
|
|
|
} else {
|
|
|
|
for (var i = 0; i < addresses.length; i++) {
|
|
|
|
addresses[i] = padHexAddress(addresses[i].trim());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
controller.setFilterToAddresses(JSON.stringify(addresses))
|
|
|
|
|
|
|
|
// Involved addresses
|
|
|
|
var addresses = addressesInput.text.split(',')
|
|
|
|
if(addresses.length == 1 && addresses[0].trim() == "") {
|
|
|
|
addresses = []
|
|
|
|
} else {
|
|
|
|
for (var i = 0; i < addresses.length; i++) {
|
|
|
|
addresses[i] = padHexAddress(addresses[i].trim());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
controller.setFilterAddresses(JSON.stringify(addresses))
|
|
|
|
|
|
|
|
// Chains
|
|
|
|
var chains = []
|
|
|
|
for(var i = 0; i < clonedNetworksModel.count; i++) {
|
|
|
|
let item = clonedNetworksModel.get(i)
|
|
|
|
if(item.checked) {
|
|
|
|
chains.push(parseInt(item.chainId))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
controller.setFilterChains(JSON.stringify(chains))
|
|
|
|
|
|
|
|
// Assets
|
|
|
|
var assets = []
|
|
|
|
if(assetsLoader.status == Loader.Ready) {
|
|
|
|
for(var i = 0; i < assetsLoader.item.count; i++) {
|
|
|
|
let item = assetsLoader.item.get(i)
|
|
|
|
if(item.checked) {
|
|
|
|
assets.push(item.symbol)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
controller.setFilterAssets(JSON.stringify(assets))
|
|
|
|
|
|
|
|
// Update the model
|
|
|
|
controller.updateFilter()
|
|
|
|
}
|
|
|
|
|
|
|
|
function padHexAddress(input) {
|
|
|
|
var addressLength = 40;
|
|
|
|
var strippedInput = input.startsWith("0x") ? input.slice(2) : input;
|
|
|
|
|
|
|
|
if (strippedInput.length > addressLength) {
|
|
|
|
console.error("Input is longer than expected address");
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
var paddingLength = addressLength - strippedInput.length;
|
|
|
|
var padding = Array(paddingLength + 1).join("0");
|
|
|
|
|
|
|
|
return "0x" + padding + strippedInput;
|
|
|
|
}
|
|
|
|
}
|
2023-04-21 09:36:24 +00:00
|
|
|
|
|
|
|
ColumnLayout {
|
|
|
|
anchors.fill: parent
|
|
|
|
|
|
|
|
ColumnLayout {
|
|
|
|
id: filterLayout
|
|
|
|
|
2023-05-11 07:56:55 +00:00
|
|
|
ColumnLayout {
|
|
|
|
id: timeFilterLayout
|
|
|
|
|
|
|
|
RowLayout {
|
|
|
|
Label { text: "Past Days Span: 100" }
|
|
|
|
Slider {
|
|
|
|
id: fromSlider
|
|
|
|
|
|
|
|
Layout.preferredWidth: 200
|
|
|
|
Layout.preferredHeight: 50
|
|
|
|
|
|
|
|
from: 100
|
|
|
|
to: 0
|
|
|
|
|
|
|
|
stepSize: 1
|
|
|
|
value: 0
|
|
|
|
}
|
|
|
|
Label { text: `${fromSlider.value}d - ${toSlider.value}d` }
|
|
|
|
Slider {
|
|
|
|
id: toSlider
|
|
|
|
|
|
|
|
Layout.preferredWidth: 200
|
|
|
|
Layout.preferredHeight: 50
|
|
|
|
|
|
|
|
enabled: fromSlider.value > 1
|
|
|
|
|
|
|
|
from: fromSlider.value - 1
|
|
|
|
to: 0
|
|
|
|
|
|
|
|
stepSize: 1
|
|
|
|
value: 0
|
|
|
|
}
|
|
|
|
Label { text: "0" }
|
|
|
|
}
|
|
|
|
Label { text: `Interval: ${d.start > 0 ? root.epochToDateStr(d.start) : "all time"} - ${d.end > 0 ? root.epochToDateStr(d.end) : "now"}` }
|
|
|
|
}
|
|
|
|
RowLayout {
|
|
|
|
Label { text: "Type" }
|
|
|
|
// Models the ActivityType
|
|
|
|
ListModel {
|
|
|
|
id: typeModel
|
|
|
|
|
|
|
|
ListElement { text: qsTr("Send"); checked: false }
|
|
|
|
ListElement { text: qsTr("Receive"); checked: false }
|
|
|
|
ListElement { text: qsTr("Buy"); checked: false }
|
|
|
|
ListElement { text: qsTr("Swap"); checked: false }
|
|
|
|
ListElement { text: qsTr("Bridge"); checked: false }
|
|
|
|
}
|
|
|
|
|
|
|
|
ComboBox {
|
|
|
|
model: typeModel
|
|
|
|
|
|
|
|
displayText: qsTr("Select types")
|
|
|
|
|
|
|
|
currentIndex: -1
|
|
|
|
textRole: "text"
|
|
|
|
|
|
|
|
delegate: ItemOnOffDelegate {}
|
|
|
|
}
|
|
|
|
|
|
|
|
Label { text: "Status" }
|
|
|
|
// ActivityStatus
|
|
|
|
ListModel {
|
|
|
|
id: statusModel
|
|
|
|
ListElement { text: qsTr("Failed"); checked: false }
|
|
|
|
ListElement { text: qsTr("Pending"); checked: false }
|
|
|
|
ListElement { text: qsTr("Complete"); checked: false }
|
|
|
|
ListElement { text: qsTr("Finalized"); checked: false }
|
|
|
|
}
|
2023-04-21 09:36:24 +00:00
|
|
|
|
2023-05-11 07:56:55 +00:00
|
|
|
ComboBox {
|
|
|
|
displayText: qsTr("Select statuses")
|
2023-04-21 09:36:24 +00:00
|
|
|
|
2023-05-11 07:56:55 +00:00
|
|
|
model: statusModel
|
|
|
|
|
|
|
|
currentIndex: -1
|
|
|
|
textRole: "text"
|
|
|
|
|
|
|
|
delegate: ItemOnOffDelegate {}
|
|
|
|
}
|
|
|
|
|
|
|
|
Label { text: "To addresses" }
|
|
|
|
TextField {
|
|
|
|
id: toAddressesInput
|
|
|
|
|
|
|
|
Layout.fillWidth: true
|
|
|
|
|
|
|
|
placeholderText: qsTr("0x1234, 0x5678, ...")
|
|
|
|
}
|
|
|
|
|
|
|
|
Button {
|
|
|
|
text: qsTr("Update")
|
|
|
|
onClicked: d.updateFilter()
|
|
|
|
}
|
|
|
|
}
|
2023-04-21 09:36:24 +00:00
|
|
|
RowLayout {
|
|
|
|
|
2023-05-11 07:56:55 +00:00
|
|
|
Label { text: "Addresses" }
|
|
|
|
TextField {
|
|
|
|
id: addressesInput
|
|
|
|
|
|
|
|
Layout.fillWidth: true
|
|
|
|
|
|
|
|
placeholderText: qsTr("0x1234, 0x5678, ...")
|
|
|
|
}
|
|
|
|
|
|
|
|
Label { text: "Chains" }
|
|
|
|
ComboBox {
|
|
|
|
displayText: qsTr("Select chains")
|
2023-04-21 09:36:24 +00:00
|
|
|
|
2023-05-11 07:56:55 +00:00
|
|
|
Layout.preferredWidth: 300
|
2023-04-21 09:36:24 +00:00
|
|
|
|
2023-05-11 07:56:55 +00:00
|
|
|
model: clonedNetworksModel
|
|
|
|
currentIndex: -1
|
2023-04-21 09:36:24 +00:00
|
|
|
|
2023-05-11 07:56:55 +00:00
|
|
|
delegate: ItemOnOffDelegate {}
|
2023-04-21 09:36:24 +00:00
|
|
|
}
|
|
|
|
|
2023-05-11 07:56:55 +00:00
|
|
|
Label { text: "Assets" }
|
|
|
|
ComboBox {
|
|
|
|
displayText: assetsLoader.status != Loader.Ready ? qsTr("Loading...") : qsTr("Select an asset")
|
2023-04-21 09:36:24 +00:00
|
|
|
|
2023-05-11 07:56:55 +00:00
|
|
|
enabled: assetsLoader.status == Loader.Ready
|
2023-04-21 09:36:24 +00:00
|
|
|
|
2023-05-11 07:56:55 +00:00
|
|
|
Layout.preferredWidth: 300
|
2023-04-21 09:36:24 +00:00
|
|
|
|
2023-05-11 07:56:55 +00:00
|
|
|
model: assetsLoader.item
|
|
|
|
|
|
|
|
currentIndex: -1
|
|
|
|
|
|
|
|
delegate: ItemOnOffDelegate {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
CloneModel {
|
|
|
|
id: clonedNetworksModel
|
|
|
|
|
|
|
|
sourceModel: root.networksModel
|
|
|
|
roles: ["layer", "chainId", "chainName"]
|
|
|
|
rolesOverride: [{ role: "text", transform: (md) => `${md.chainName} [${md.chainId}] ${md.layer}` },
|
|
|
|
{ role: "checked", transform: (md) => false }]
|
|
|
|
}
|
|
|
|
|
|
|
|
// Found out the hard way that the assets are not loaded immediately after root.assetLoading is enabled so there is no data set yet
|
|
|
|
Timer {
|
|
|
|
id: delayAssetLoading
|
|
|
|
|
|
|
|
property bool loadingEnabled: false
|
|
|
|
|
|
|
|
interval: 1000; repeat: false
|
|
|
|
running: !root.assetsLoading
|
|
|
|
onTriggered: loadingEnabled = true
|
|
|
|
}
|
|
|
|
|
|
|
|
Loader {
|
|
|
|
id: assetsLoader
|
|
|
|
|
|
|
|
sourceComponent: CloneModel {
|
|
|
|
sourceModel: root.assetsModel
|
|
|
|
roles: ["name", "symbol", "address"]
|
|
|
|
rolesOverride: [{ role: "text", transform: (md) => `[${md.symbol}] ${md.name}`},
|
|
|
|
{ role: "checked", transform: (md) => false }]
|
|
|
|
}
|
|
|
|
active: delayAssetLoading.loadingEnabled
|
|
|
|
}
|
|
|
|
|
|
|
|
component ItemOnOffDelegate: Item {
|
|
|
|
width: parent ? parent.width : 0
|
|
|
|
height: itemLayout.implicitHeight
|
|
|
|
|
|
|
|
readonly property var entry: model
|
|
|
|
|
|
|
|
RowLayout {
|
|
|
|
id: itemLayout
|
|
|
|
anchors.fill: parent
|
2023-04-21 09:36:24 +00:00
|
|
|
|
2023-05-11 07:56:55 +00:00
|
|
|
CheckBox { checked: entry.checked; onCheckedChanged: entry.checked = checked }
|
|
|
|
Label { text: entry.text }
|
|
|
|
RowLayout {}
|
2023-04-21 09:36:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ListView {
|
|
|
|
id: listView
|
|
|
|
|
|
|
|
Layout.fillWidth: true
|
|
|
|
Layout.fillHeight: true
|
|
|
|
|
|
|
|
model: controller.model
|
|
|
|
|
|
|
|
delegate: Item {
|
|
|
|
width: parent ? parent.width : 0
|
|
|
|
height: itemLayout.implicitHeight
|
|
|
|
|
|
|
|
readonly property var entry: model.activityEntry
|
|
|
|
|
|
|
|
RowLayout {
|
|
|
|
id: itemLayout
|
|
|
|
anchors.fill: parent
|
|
|
|
|
|
|
|
Label { text: entry.isMultiTransaction ? "MT" : entry.isPendingTransaction ? "PT" : " T" }
|
|
|
|
Label { text: `[${root.epochToDateStr(entry.timestamp)}] ` }
|
|
|
|
Label { text: entry.isMultiTransaction ? entry.fromAmount : entry.amount }
|
|
|
|
Label { text: "from"; Layout.leftMargin: 5; Layout.rightMargin: 5 }
|
|
|
|
Label { text: entry.sender; Layout.maximumWidth: 200; elide: Text.ElideMiddle }
|
|
|
|
Label { text: "to"; Layout.leftMargin: 5; Layout.rightMargin: 5 }
|
|
|
|
Label { text: entry.recipient; Layout.maximumWidth: 200; elide: Text.ElideMiddle }
|
|
|
|
Label { text: "got"; Layout.leftMargin: 5; Layout.rightMargin: 5; visible: entry.isMultiTransaction }
|
|
|
|
Label { text: entry.toAmount; Layout.leftMargin: 5; Layout.rightMargin: 5; visible: entry.isMultiTransaction }
|
|
|
|
RowLayout {} // Spacer
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function epochToDateStr(epochTimestamp) {
|
|
|
|
var date = new Date(epochTimestamp * 1000);
|
|
|
|
return date.toLocaleString(Qt.locale(), "dd-MM-yyyy hh:mm");
|
|
|
|
}
|
|
|
|
}
|