Begin yet another model refactor
Use native ListModel which require a lot of changes, but should be much faster than the old way which exponentially slowed down to a crawl. Also fix some popup bugs (leave/forget). Not working yet: side pane keyboard controls, proper highlight, room & member filtering, local echo replacement
This commit is contained in:
81
src/gui/Base/HAccordionView.qml
Normal file
81
src/gui/Base/HAccordionView.qml
Normal file
@@ -0,0 +1,81 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
|
||||
HListView {
|
||||
id: accordion
|
||||
|
||||
|
||||
property Component category
|
||||
property Component content
|
||||
property Component expander: HButton {
|
||||
id: expanderItem
|
||||
iconItem.small: true
|
||||
icon.name: "expand"
|
||||
backgroundColor: "transparent"
|
||||
toolTip.text: expand ? qsTr("Collapse") : qsTr("Expand")
|
||||
onClicked: expand = ! expand
|
||||
|
||||
leftPadding: theme.spacing / 2
|
||||
rightPadding: leftPadding
|
||||
|
||||
iconItem.transform: Rotation {
|
||||
origin.x: expanderItem.iconItem.width / 2
|
||||
origin.y: expanderItem.iconItem.height / 2
|
||||
angle: expanderItem.loading ? 0 : expand ? 90 : 180
|
||||
|
||||
Behavior on angle { HNumberAnimation {} }
|
||||
}
|
||||
|
||||
Behavior on opacity { HNumberAnimation {} }
|
||||
}
|
||||
|
||||
|
||||
delegate: HColumnLayout {
|
||||
id: categoryContentColumn
|
||||
width: accordion.width
|
||||
|
||||
property bool expand: true
|
||||
readonly property QtObject categoryModel: model
|
||||
|
||||
HRowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
HLoader {
|
||||
id: categoryLoader
|
||||
sourceComponent: category
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
readonly property QtObject model: categoryModel
|
||||
}
|
||||
HLoader {
|
||||
sourceComponent: expander
|
||||
|
||||
readonly property QtObject model: categoryModel
|
||||
property alias expand: categoryContentColumn.expand
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
opacity: expand ? 1 : 0
|
||||
visible: opacity > 0
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: contentLoader.implicitHeight * opacity
|
||||
|
||||
Behavior on opacity { HNumberAnimation {} }
|
||||
|
||||
HLoader {
|
||||
id: contentLoader
|
||||
width: parent.width
|
||||
active: categoryLoader.status === Loader.Ready
|
||||
sourceComponent: content
|
||||
|
||||
readonly property QtObject xcategoryModel: categoryModel
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,30 +0,0 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.12
|
||||
import QSyncable 1.0
|
||||
|
||||
JsonListModel {
|
||||
id: model
|
||||
source: []
|
||||
Component.onCompleted: if (! keyField) { throw "keyField not set" }
|
||||
|
||||
function toObject(itemList=listModel) {
|
||||
let objList = []
|
||||
|
||||
for (let item of itemList) {
|
||||
let obj = JSON.parse(JSON.stringify(item))
|
||||
|
||||
for (let role in obj) {
|
||||
if (obj[role]["objectName"] !== undefined) {
|
||||
obj[role] = toObject(item[role])
|
||||
}
|
||||
}
|
||||
objList.push(obj)
|
||||
}
|
||||
return objList
|
||||
}
|
||||
|
||||
function toJson() {
|
||||
return JSON.stringify(toObject(), null, 4)
|
||||
}
|
||||
}
|
@@ -26,10 +26,12 @@ ListView {
|
||||
visible: listView.interactive || ! listView.allowDragging
|
||||
}
|
||||
|
||||
// property bool debug: false
|
||||
|
||||
// Make sure to handle when a previous transition gets interrupted
|
||||
add: Transition {
|
||||
ParallelAnimation {
|
||||
// ScriptAction { script: print("add") }
|
||||
// ScriptAction { script: if (listView.debug) print("add") }
|
||||
HNumberAnimation { property: "opacity"; from: 0; to: 1 }
|
||||
HNumberAnimation { property: "scale"; from: 0; to: 1 }
|
||||
}
|
||||
@@ -37,7 +39,7 @@ ListView {
|
||||
|
||||
move: Transition {
|
||||
ParallelAnimation {
|
||||
// ScriptAction { script: print("move") }
|
||||
// ScriptAction { script: if (listView.debug) print("move") }
|
||||
HNumberAnimation { property: "opacity"; to: 1 }
|
||||
HNumberAnimation { property: "scale"; to: 1 }
|
||||
HNumberAnimation { properties: "x,y" }
|
||||
@@ -46,16 +48,15 @@ ListView {
|
||||
|
||||
remove: Transition {
|
||||
ParallelAnimation {
|
||||
// ScriptAction { script: print("remove") }
|
||||
// ScriptAction { script: if (listView.debug) print("remove") }
|
||||
HNumberAnimation { property: "opacity"; to: 0 }
|
||||
HNumberAnimation { property: "scale"; to: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
// displaced: move
|
||||
displaced: Transition {
|
||||
ParallelAnimation {
|
||||
// ScriptAction { script: print("displaced") }
|
||||
// ScriptAction { script: if (listView.debug) print("displaced") }
|
||||
HNumberAnimation { property: "opacity"; to: 1 }
|
||||
HNumberAnimation { property: "scale"; to: 1 }
|
||||
HNumberAnimation { properties: "x,y" }
|
||||
|
10
src/gui/Base/HSortFilterProxy.qml
Normal file
10
src/gui/Base/HSortFilterProxy.qml
Normal file
@@ -0,0 +1,10 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.12
|
||||
import com.cutehacks.gel 1.0
|
||||
|
||||
Collection {
|
||||
caseSensitiveSort: false
|
||||
localeAwareSort: true
|
||||
Component.onCompleted: reSort()
|
||||
}
|
@@ -18,19 +18,4 @@ HTile {
|
||||
signal activated()
|
||||
|
||||
property HListView view: ListView.view
|
||||
property bool shouldBeCurrent: false
|
||||
|
||||
readonly property QtObject delegateModel: model
|
||||
|
||||
readonly property alias setCurrentTimer: setCurrentTimer
|
||||
|
||||
|
||||
Timer {
|
||||
id: setCurrentTimer
|
||||
interval: 100
|
||||
repeat: true
|
||||
running: true
|
||||
// Component.onCompleted won't work for this
|
||||
onTriggered: if (shouldBeCurrent) view.currentIndex = model.index
|
||||
}
|
||||
}
|
||||
|
@@ -6,54 +6,61 @@ import Clipboard 0.1
|
||||
import "../Base"
|
||||
|
||||
HTileDelegate {
|
||||
id: accountDelegate
|
||||
id: account
|
||||
spacing: 0
|
||||
topPadding: model.index > 0 ? theme.spacing / 2 : 0
|
||||
bottomPadding: topPadding
|
||||
|
||||
backgroundColor: theme.mainPane.account.background
|
||||
opacity: collapsed && ! forceExpand ?
|
||||
opacity: collapsed && ! anyFilter ?
|
||||
theme.mainPane.account.collapsedOpacity : 1
|
||||
|
||||
shouldBeCurrent:
|
||||
window.uiState.page === "Pages/AccountSettings/AccountSettings.qml" &&
|
||||
window.uiState.pageProperties.userId === model.data.user_id
|
||||
title.color: theme.mainPane.account.name
|
||||
title.text: model.display_name || model.id
|
||||
title.font.pixelSize: theme.fontSize.big
|
||||
title.leftPadding: theme.spacing
|
||||
|
||||
setCurrentTimer.running:
|
||||
! mainPaneList.activateLimiter.running && ! mainPane.hasFocus
|
||||
image: HUserAvatar {
|
||||
userId: model.id
|
||||
displayName: model.display_name
|
||||
mxc: model.avatar_url
|
||||
}
|
||||
|
||||
contextMenu: HMenu {
|
||||
HMenuItem {
|
||||
icon.name: "copy-user-id"
|
||||
text: qsTr("Copy user ID")
|
||||
onTriggered: Clipboard.text = model.id
|
||||
}
|
||||
|
||||
Behavior on opacity { HNumberAnimation {} }
|
||||
|
||||
|
||||
readonly property bool forceExpand: Boolean(mainPaneList.filter)
|
||||
|
||||
// Hide harmless error when a filter matches nothing
|
||||
readonly property bool collapsed: try {
|
||||
return mainPaneList.collapseAccounts[model.data.user_id] || false
|
||||
} catch (err) {}
|
||||
HMenuItemPopupSpawner {
|
||||
icon.name: "sign-out"
|
||||
icon.color: theme.colors.negativeBackground
|
||||
text: qsTr("Sign out")
|
||||
|
||||
popup: "Popups/SignOutPopup.qml"
|
||||
properties: { "userId": model.id }
|
||||
}
|
||||
}
|
||||
|
||||
onActivated: pageLoader.showPage(
|
||||
"AccountSettings/AccountSettings", { "userId": model.data.user_id }
|
||||
"AccountSettings/AccountSettings", { "userId": model.id }
|
||||
)
|
||||
|
||||
|
||||
readonly property bool collapsed:
|
||||
window.uiState.collapseAccounts[model.id] || false
|
||||
|
||||
readonly property bool anyFilter: Boolean(mainPaneList.filter)
|
||||
|
||||
|
||||
function toggleCollapse() {
|
||||
window.uiState.collapseAccounts[model.data.user_id] = ! collapsed
|
||||
window.uiState.collapseAccounts[model.id] = ! collapsed
|
||||
window.uiStateChanged()
|
||||
}
|
||||
|
||||
|
||||
image: HUserAvatar {
|
||||
userId: model.data.user_id
|
||||
displayName: model.data.display_name
|
||||
mxc: model.data.avatar_url
|
||||
}
|
||||
|
||||
title.color: theme.mainPane.account.name
|
||||
title.text: model.data.display_name || model.data.user_id
|
||||
title.font.pixelSize: theme.fontSize.big
|
||||
title.leftPadding: theme.spacing
|
||||
Behavior on opacity { HNumberAnimation {} }
|
||||
|
||||
HButton {
|
||||
id: addChat
|
||||
@@ -62,7 +69,7 @@ HTileDelegate {
|
||||
backgroundColor: "transparent"
|
||||
toolTip.text: qsTr("Add new chat")
|
||||
onClicked: pageLoader.showPage(
|
||||
"AddChat/AddChat", {userId: model.data.user_id},
|
||||
"AddChat/AddChat", {userId: model.id},
|
||||
)
|
||||
|
||||
leftPadding: theme.spacing / 2
|
||||
@@ -73,7 +80,7 @@ HTileDelegate {
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.maximumWidth:
|
||||
accountDelegate.width >= 100 * theme.uiScale ? implicitWidth : 0
|
||||
account.width >= 100 * theme.uiScale ? implicitWidth : 0
|
||||
|
||||
Behavior on Layout.maximumWidth { HNumberAnimation {} }
|
||||
Behavior on opacity { HNumberAnimation {} }
|
||||
@@ -81,22 +88,23 @@ HTileDelegate {
|
||||
|
||||
HButton {
|
||||
id: expand
|
||||
loading: ! model.data.first_sync_done || ! model.data.profile_updated
|
||||
loading:
|
||||
! model.first_sync_done || model.profile_updated < new Date(1)
|
||||
iconItem.small: true
|
||||
icon.name: "expand"
|
||||
backgroundColor: "transparent"
|
||||
toolTip.text: collapsed ? qsTr("Expand") : qsTr("Collapse")
|
||||
onClicked: accountDelegate.toggleCollapse()
|
||||
onClicked: account.toggleCollapse()
|
||||
|
||||
leftPadding: theme.spacing / 2
|
||||
rightPadding: leftPadding
|
||||
|
||||
opacity: ! loading && accountDelegate.forceExpand ? 0 : 1
|
||||
opacity: ! loading && account.anyFilter ? 0 : 1
|
||||
visible: opacity > 0 && Layout.maximumWidth > 0
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.maximumWidth:
|
||||
accountDelegate.width >= 120 * theme.uiScale ? implicitWidth : 0
|
||||
account.width >= 120 * theme.uiScale ? implicitWidth : 0
|
||||
|
||||
|
||||
iconItem.transform: Rotation {
|
||||
@@ -110,21 +118,4 @@ HTileDelegate {
|
||||
Behavior on Layout.maximumWidth { HNumberAnimation {} }
|
||||
Behavior on opacity { HNumberAnimation {} }
|
||||
}
|
||||
|
||||
contextMenu: HMenu {
|
||||
HMenuItem {
|
||||
icon.name: "copy-user-id"
|
||||
text: qsTr("Copy user ID")
|
||||
onTriggered: Clipboard.text = model.data.user_id
|
||||
}
|
||||
|
||||
HMenuItemPopupSpawner {
|
||||
icon.name: "sign-out"
|
||||
icon.color: theme.colors.negativeBackground
|
||||
text: qsTr("Sign out")
|
||||
|
||||
popup: "Popups/SignOutPopup.qml"
|
||||
properties: { "userId": model.data.user_id }
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,143 +0,0 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import "../Base"
|
||||
|
||||
HListView {
|
||||
id: mainPaneList
|
||||
|
||||
|
||||
readonly property var originSource: window.mainPaneModelSource
|
||||
readonly property var collapseAccounts: window.uiState.collapseAccounts
|
||||
readonly property string filter: toolBar.roomFilter
|
||||
readonly property alias activateLimiter: activateLimiter
|
||||
|
||||
onOriginSourceChanged: filterLimiter.restart()
|
||||
onFilterChanged: filterLimiter.restart()
|
||||
onCollapseAccountsChanged: filterLimiter.restart()
|
||||
|
||||
|
||||
function filterSource() {
|
||||
let show = []
|
||||
|
||||
// Hide a harmless error when activating a RoomDelegate
|
||||
try { window.mainPaneModelSource } catch (err) { return }
|
||||
|
||||
for (let i = 0; i < window.mainPaneModelSource.length; i++) {
|
||||
let item = window.mainPaneModelSource[i]
|
||||
|
||||
if (item.type === "Account" ||
|
||||
(filter ?
|
||||
utils.filterMatches(filter, item.data.filter_string) :
|
||||
! window.uiState.collapseAccounts[item.user_id]))
|
||||
{
|
||||
if (filter && show.length && item.type === "Account" &&
|
||||
show[show.length - 1].type === "Account" &&
|
||||
! utils.filterMatches(
|
||||
filter, show[show.length - 1].data.filter_string)
|
||||
) {
|
||||
// If filter active, current and previous items are
|
||||
// both accounts and previous account doesn't match filter,
|
||||
// that means the previous account had no matching rooms.
|
||||
show.pop()
|
||||
}
|
||||
|
||||
show.push(item)
|
||||
}
|
||||
}
|
||||
|
||||
let last = show[show.length - 1]
|
||||
if (show.length && filter && last.type === "Account" &&
|
||||
! utils.filterMatches(filter, last.data.filter_string))
|
||||
{
|
||||
// If filter active, last item is an account and last item
|
||||
// doesn't match filter, that account had no matching rooms.
|
||||
show.pop()
|
||||
}
|
||||
|
||||
model.source = show
|
||||
}
|
||||
|
||||
function previous(activate=true) {
|
||||
decrementCurrentIndex()
|
||||
if (activate) activateLimiter.restart()
|
||||
}
|
||||
|
||||
function next(activate=true) {
|
||||
incrementCurrentIndex()
|
||||
if (activate) activateLimiter.restart()
|
||||
}
|
||||
|
||||
function activate() {
|
||||
currentItem.item.activated()
|
||||
}
|
||||
|
||||
function accountSettings() {
|
||||
if (! currentItem) incrementCurrentIndex()
|
||||
|
||||
pageLoader.showPage(
|
||||
"AccountSettings/AccountSettings",
|
||||
{userId: currentItem.item.delegateModel.user_id},
|
||||
)
|
||||
}
|
||||
|
||||
function addNewChat() {
|
||||
if (! currentItem) incrementCurrentIndex()
|
||||
|
||||
pageLoader.showPage(
|
||||
"AddChat/AddChat",
|
||||
{userId: currentItem.item.delegateModel.user_id},
|
||||
)
|
||||
}
|
||||
|
||||
function toggleCollapseAccount() {
|
||||
if (filter) return
|
||||
|
||||
if (! currentItem) incrementCurrentIndex()
|
||||
|
||||
if (currentItem.item.delegateModel.type === "Account") {
|
||||
currentItem.item.toggleCollapse()
|
||||
return
|
||||
}
|
||||
|
||||
for (let i = 0; i < model.source.length; i++) {
|
||||
let item = model.source[i]
|
||||
|
||||
if (item.type === "Account" && item.user_id ==
|
||||
currentItem.item.delegateModel.user_id)
|
||||
{
|
||||
currentIndex = i
|
||||
currentItem.item.toggleCollapse()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
model: HListModel {
|
||||
keyField: "id"
|
||||
source: originSource
|
||||
}
|
||||
|
||||
delegate: Loader {
|
||||
width: mainPaneList.width
|
||||
Component.onCompleted: setSource(
|
||||
model.type === "Account" ?
|
||||
"AccountDelegate.qml" : "RoomDelegate.qml",
|
||||
{view: mainPaneList}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Timer {
|
||||
id: filterLimiter
|
||||
interval: 16
|
||||
onTriggered: filterSource()
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: activateLimiter
|
||||
interval: 300
|
||||
onTriggered: activate()
|
||||
}
|
||||
}
|
66
src/gui/MainPane/AccountRoomsDelegate.qml
Normal file
66
src/gui/MainPane/AccountRoomsDelegate.qml
Normal file
@@ -0,0 +1,66 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.12
|
||||
import ".."
|
||||
import "../Base"
|
||||
|
||||
Column {
|
||||
id: delegate
|
||||
|
||||
|
||||
property string userId: model.id
|
||||
readonly property HListView view: ListView.view
|
||||
|
||||
|
||||
Account {
|
||||
id: account
|
||||
width: parent.width
|
||||
view: delegate.view
|
||||
}
|
||||
|
||||
HListView {
|
||||
id: roomList
|
||||
width: parent.width
|
||||
height: contentHeight * opacity
|
||||
opacity: account.collapsed ? 0 : 1
|
||||
visible: opacity > 0
|
||||
interactive: false
|
||||
|
||||
model: ModelStore.get(delegate.userId, "rooms")
|
||||
// model: HSortFilterProxy {
|
||||
// model: ModelStore.get(delegate.userId, "rooms")
|
||||
// comparator: (a, b) =>
|
||||
// // Sort by membership, then last event date (most recent first)
|
||||
// // then room display name or ID.
|
||||
// // Invited rooms are first, then joined rooms, then left rooms.
|
||||
|
||||
// // Left rooms may still have an inviter_id, so check left first
|
||||
// [
|
||||
// a.left,
|
||||
// b.inviter_id,
|
||||
|
||||
// b.last_event && b.last_event.date ?
|
||||
// b.last_event.date.getTime() : 0,
|
||||
|
||||
// (a.display_name || a.id).toLocaleLowerCase(),
|
||||
// ] < [
|
||||
// b.left,
|
||||
// a.inviter_id,
|
||||
|
||||
// a.last_event && a.last_event.date ?
|
||||
// a.last_event.date.getTime() : 0,
|
||||
|
||||
// (b.display_name || b.id).toLocaleLowerCase(),
|
||||
// ]
|
||||
// }
|
||||
|
||||
delegate: Room {
|
||||
width: roomList.width
|
||||
userId: delegate.userId
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
HNumberAnimation { easing.type: Easing.InOutCirc }
|
||||
}
|
||||
}
|
||||
}
|
88
src/gui/MainPane/AccountRoomsList.qml
Normal file
88
src/gui/MainPane/AccountRoomsList.qml
Normal file
@@ -0,0 +1,88 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.12
|
||||
import ".."
|
||||
import "../Base"
|
||||
|
||||
HListView {
|
||||
id: mainPaneList
|
||||
|
||||
model: ModelStore.get("accounts")
|
||||
// model: HSortFilterProxy {
|
||||
// model: ModelStore.get("accounts")
|
||||
// comparator: (a, b) =>
|
||||
// // Sort by display name or user ID
|
||||
// (a.display_name || a.id).toLocaleLowerCase() <
|
||||
// (b.display_name || b.id).toLocaleLowerCase()
|
||||
// }
|
||||
|
||||
delegate: AccountRoomsDelegate {
|
||||
width: mainPaneList.width
|
||||
height: childrenRect.height
|
||||
}
|
||||
|
||||
|
||||
readonly property string filter: toolBar.roomFilter
|
||||
|
||||
|
||||
function previous(activate=true) {
|
||||
decrementCurrentIndex()
|
||||
if (activate) activateLimiter.restart()
|
||||
}
|
||||
|
||||
function next(activate=true) {
|
||||
incrementCurrentIndex()
|
||||
if (activate) activateLimiter.restart()
|
||||
}
|
||||
|
||||
function activate() {
|
||||
currentItem.item.activated()
|
||||
}
|
||||
|
||||
function accountSettings() {
|
||||
if (! currentItem) incrementCurrentIndex()
|
||||
|
||||
pageLoader.showPage(
|
||||
"AccountSettings/AccountSettings",
|
||||
{userId: currentItem.item.delegateModel.user_id},
|
||||
)
|
||||
}
|
||||
|
||||
function addNewChat() {
|
||||
if (! currentItem) incrementCurrentIndex()
|
||||
|
||||
pageLoader.showPage(
|
||||
"AddChat/AddChat",
|
||||
{userId: currentItem.item.delegateModel.user_id},
|
||||
)
|
||||
}
|
||||
|
||||
function toggleCollapseAccount() {
|
||||
if (filter) return
|
||||
|
||||
if (! currentItem) incrementCurrentIndex()
|
||||
|
||||
if (currentItem.item.delegateModel.type === "Account") {
|
||||
currentItem.item.toggleCollapse()
|
||||
return
|
||||
}
|
||||
|
||||
for (let i = 0; i < model.source.length; i++) {
|
||||
let item = model.source[i]
|
||||
|
||||
if (item.type === "Account" && item.user_id ==
|
||||
currentItem.item.delegateModel.user_id)
|
||||
{
|
||||
currentIndex = i
|
||||
currentItem.item.toggleCollapse()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Timer {
|
||||
id: activateLimiter
|
||||
interval: 300
|
||||
onTriggered: activate()
|
||||
}
|
||||
}
|
@@ -37,7 +37,7 @@ HDrawer {
|
||||
HColumnLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
AccountRoomList {
|
||||
AccountRoomsList {
|
||||
id: mainPaneList
|
||||
clip: true
|
||||
|
||||
|
@@ -9,7 +9,7 @@ HRowLayout {
|
||||
// Hide filter field overflowing for a sec on size changes
|
||||
clip: true
|
||||
|
||||
property AccountRoomList mainPaneList
|
||||
property AccountRoomsList mainPaneList
|
||||
readonly property alias addAccountButton: addAccountButton
|
||||
readonly property alias filterField: filterField
|
||||
property alias roomFilter: filterField.text
|
||||
|
@@ -3,85 +3,48 @@
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import Clipboard 0.1
|
||||
import ".."
|
||||
import "../Base"
|
||||
|
||||
HTileDelegate {
|
||||
id: roomDelegate
|
||||
spacing: theme.spacing
|
||||
backgroundColor: theme.mainPane.room.background
|
||||
opacity: model.data.left ? theme.mainPane.room.leftRoomOpacity : 1
|
||||
|
||||
shouldBeCurrent:
|
||||
window.uiState.page === "Pages/Chat/Chat.qml" &&
|
||||
window.uiState.pageProperties.userId === model.user_id &&
|
||||
window.uiState.pageProperties.roomId === model.data.room_id
|
||||
|
||||
setCurrentTimer.running:
|
||||
! mainPaneList.activateLimiter.running && ! mainPane.hasFocus
|
||||
|
||||
|
||||
Behavior on opacity { HNumberAnimation {} }
|
||||
|
||||
|
||||
readonly property bool joined: ! invited && ! parted
|
||||
readonly property bool invited: model.data.inviter_id && ! parted
|
||||
readonly property bool parted: model.data.left
|
||||
readonly property var lastEvent: model.data.last_event
|
||||
|
||||
|
||||
onActivated: pageLoader.showRoom(model.user_id, model.data.room_id)
|
||||
|
||||
opacity: model.left ? theme.mainPane.room.leftRoomOpacity : 1
|
||||
|
||||
image: HRoomAvatar {
|
||||
displayName: model.data.display_name
|
||||
mxc: model.data.avatar_url
|
||||
displayName: model.display_name
|
||||
mxc: model.avatar_url
|
||||
}
|
||||
|
||||
title.color: theme.mainPane.room.name
|
||||
title.text: model.data.display_name || qsTr("Empty room")
|
||||
title.text: model.display_name || qsTr("Empty room")
|
||||
|
||||
additionalInfo.children: HIcon {
|
||||
svgName: "invite-received"
|
||||
colorize: theme.colors.alertBackground
|
||||
|
||||
visible: invited
|
||||
Layout.maximumWidth: invited ? implicitWidth : 0
|
||||
|
||||
Behavior on Layout.maximumWidth { HNumberAnimation {} }
|
||||
}
|
||||
|
||||
rightInfo.color: theme.mainPane.room.lastEventDate
|
||||
rightInfo.text: {
|
||||
! lastEvent || ! lastEvent.date ?
|
||||
"" :
|
||||
|
||||
utils.dateIsToday(lastEvent.date) ?
|
||||
utils.formatTime(lastEvent.date, false) : // no seconds
|
||||
|
||||
lastEvent.date.getFullYear() === new Date().getFullYear() ?
|
||||
Qt.formatDate(lastEvent.date, "d MMM") : // e.g. "5 Dec"
|
||||
|
||||
lastEvent.date.getFullYear()
|
||||
}
|
||||
|
||||
subtitle.color: theme.mainPane.room.subtitle
|
||||
subtitle.font.italic:
|
||||
Boolean(lastEvent && lastEvent.event_type === "RoomMessageEmote")
|
||||
subtitle.textFormat: Text.StyledText
|
||||
subtitle.font.italic:
|
||||
lastEvent && lastEvent.event_type === "RoomMessageEmote"
|
||||
subtitle.text: {
|
||||
if (! lastEvent) return ""
|
||||
|
||||
let isEmote = lastEvent.event_type === "RoomMessageEmote"
|
||||
let isMsg = lastEvent.event_type.startsWith("RoomMessage")
|
||||
let isUnknownMsg = lastEvent.event_type === "RoomMessageUnknown"
|
||||
let isCryptMedia = lastEvent.event_type.startsWith("RoomEncrypted")
|
||||
const isEmote = lastEvent.event_type === "RoomMessageEmote"
|
||||
const isMsg = lastEvent.event_type.startsWith("RoomMessage")
|
||||
const isUnknownMsg = lastEvent.event_type === "RoomMessageUnknown"
|
||||
const isCryptMedia = lastEvent.event_type.startsWith("RoomEncrypted")
|
||||
|
||||
// If it's a general event
|
||||
if (isEmote || isUnknownMsg || (! isMsg && ! isCryptMedia)) {
|
||||
if (isEmote || isUnknownMsg || (! isMsg && ! isCryptMedia))
|
||||
return utils.processedEventText(lastEvent)
|
||||
}
|
||||
|
||||
let text = utils.coloredNameHtml(
|
||||
const text = utils.coloredNameHtml(
|
||||
lastEvent.sender_name, lastEvent.sender_id
|
||||
) + ": " + lastEvent.inline_content
|
||||
|
||||
@@ -91,26 +54,40 @@ HTileDelegate {
|
||||
)
|
||||
}
|
||||
|
||||
rightInfo.color: theme.mainPane.room.lastEventDate
|
||||
rightInfo.text: {
|
||||
model.last_event_date < new Date(1) ?
|
||||
"" :
|
||||
|
||||
utils.dateIsToday(model.last_event_date) ?
|
||||
utils.formatTime(model.last_event_date, false) : // no seconds
|
||||
|
||||
model.last_event_date.getFullYear() === new Date().getFullYear() ?
|
||||
Qt.formatDate(model.last_event_date, "d MMM") : // e.g. "5 Dec"
|
||||
|
||||
model.last_event_date.getFullYear()
|
||||
}
|
||||
|
||||
contextMenu: HMenu {
|
||||
HMenuItemPopupSpawner {
|
||||
visible: joined
|
||||
enabled: model.data.can_invite
|
||||
enabled: model.can_invite
|
||||
icon.name: "room-send-invite"
|
||||
text: qsTr("Invite members")
|
||||
|
||||
popup: "Popups/InviteToRoomPopup.qml"
|
||||
properties: ({
|
||||
userId: model.user_id,
|
||||
roomId: model.data.room_id,
|
||||
roomName: model.data.display_name,
|
||||
invitingAllowed: Qt.binding(() => model.data.can_invite)
|
||||
userId: userId,
|
||||
roomId: model.id,
|
||||
roomName: model.display_name,
|
||||
invitingAllowed: Qt.binding(() => model.can_invite)
|
||||
})
|
||||
}
|
||||
|
||||
HMenuItem {
|
||||
icon.name: "copy-room-id"
|
||||
text: qsTr("Copy room ID")
|
||||
onTriggered: Clipboard.text = model.data.room_id
|
||||
onTriggered: Clipboard.text = model.id
|
||||
}
|
||||
|
||||
HMenuItem {
|
||||
@@ -118,12 +95,12 @@ HTileDelegate {
|
||||
icon.name: "invite-accept"
|
||||
icon.color: theme.colors.positiveBackground
|
||||
text: qsTr("Accept %1's invite").arg(utils.coloredNameHtml(
|
||||
model.data.inviter_name, model.data.inviter_id
|
||||
model.inviter_name, model.inviter_id
|
||||
))
|
||||
label.textFormat: Text.StyledText
|
||||
|
||||
onTriggered: py.callClientCoro(
|
||||
model.user_id, "join", [model.data.room_id]
|
||||
userId, "join", [model.id]
|
||||
)
|
||||
}
|
||||
|
||||
@@ -135,9 +112,9 @@ HTileDelegate {
|
||||
|
||||
popup: "Popups/LeaveRoomPopup.qml"
|
||||
properties: ({
|
||||
userId: model.user_id,
|
||||
roomId: model.data.room_id,
|
||||
roomName: model.data.display_name,
|
||||
userId: userId,
|
||||
roomId: model.id,
|
||||
roomName: model.display_name,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -149,10 +126,27 @@ HTileDelegate {
|
||||
popup: "Popups/ForgetRoomPopup.qml"
|
||||
autoDestruct: false
|
||||
properties: ({
|
||||
userId: model.user_id,
|
||||
roomId: model.data.room_id,
|
||||
roomName: model.data.display_name,
|
||||
userId: userId,
|
||||
roomId: model.id,
|
||||
roomName: model.display_name,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onActivated: pageLoader.showRoom(userId, model.id)
|
||||
|
||||
|
||||
property string userId
|
||||
readonly property bool joined: ! invited && ! parted
|
||||
readonly property bool invited: model.inviter_id && ! parted
|
||||
readonly property bool parted: model.left
|
||||
|
||||
readonly property ListModel eventModel:
|
||||
ModelStore.get(userId, model.id, "events")
|
||||
|
||||
readonly property QtObject lastEvent:
|
||||
eventModel.count > 0 ? eventModel.get(0) : null
|
||||
|
||||
|
||||
Behavior on opacity { HNumberAnimation {} }
|
||||
}
|
37
src/gui/ModelStore.qml
Normal file
37
src/gui/ModelStore.qml
Normal file
@@ -0,0 +1,37 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
pragma Singleton
|
||||
import QtQuick 2.12
|
||||
import "PythonBridge"
|
||||
|
||||
QtObject {
|
||||
property QtObject privates: QtObject {
|
||||
readonly property var store: ({})
|
||||
|
||||
readonly property PythonBridge py: PythonBridge {}
|
||||
|
||||
readonly property Component model: Component {
|
||||
ListModel {
|
||||
property var modelId
|
||||
|
||||
function find(id) {
|
||||
for (let i = 0; i < count; i++)
|
||||
if (get(i).id === id) return get(i)
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function get(...modelId) {
|
||||
if (modelId.length === 1) modelId = modelId[0]
|
||||
|
||||
if (! privates.store[modelId])
|
||||
privates.store[modelId] =
|
||||
privates.model.createObject(this, {modelId})
|
||||
|
||||
return privates.store[modelId]
|
||||
}
|
||||
}
|
@@ -3,29 +3,29 @@
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import "../.."
|
||||
import "../../Base"
|
||||
|
||||
HPage {
|
||||
id: accountSettings
|
||||
hideHeaderUnderHeight: avatarPreferredSize
|
||||
headerLabel.text: qsTr("Account settings for %1").arg(
|
||||
utils.coloredNameHtml(headerName, userId)
|
||||
)
|
||||
|
||||
|
||||
property int avatarPreferredSize: 256 * theme.uiScale
|
||||
|
||||
property string userId: ""
|
||||
|
||||
readonly property bool ready:
|
||||
accountInfo !== "waiting" && Boolean(accountInfo.profile_updated)
|
||||
accountInfo !== null && accountInfo.profile_updated > new Date(1)
|
||||
|
||||
readonly property var accountInfo: utils.getItem(
|
||||
modelSources["Account"] || [], "user_id", userId
|
||||
) || "waiting"
|
||||
readonly property QtObject accountInfo:
|
||||
ModelStore.get("accounts").find(userId)
|
||||
|
||||
property string headerName: ready ? accountInfo.display_name : userId
|
||||
|
||||
hideHeaderUnderHeight: avatarPreferredSize
|
||||
headerLabel.text: qsTr("Account settings for %1").arg(
|
||||
utils.coloredNameHtml(headerName, userId)
|
||||
)
|
||||
|
||||
|
||||
HSpacer {}
|
||||
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import "../.."
|
||||
import "../../Base"
|
||||
|
||||
HPage {
|
||||
@@ -10,8 +11,7 @@ HPage {
|
||||
|
||||
property string userId
|
||||
|
||||
readonly property var account:
|
||||
utils.getItem(modelSources["Account"] || [], "user_id", userId)
|
||||
readonly property QtObject account: ModelStore.get("accounts").find(userId)
|
||||
|
||||
|
||||
HTabContainer {
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import "../.."
|
||||
import "../../Base"
|
||||
import "RoomPane"
|
||||
|
||||
@@ -13,16 +14,11 @@ Item {
|
||||
property string userId: ""
|
||||
property string roomId: ""
|
||||
|
||||
property QtObject userInfo: ModelStore.get("accounts").find(userId)
|
||||
property QtObject roomInfo: ModelStore.get(userId, "rooms").find(roomId)
|
||||
|
||||
property bool loadingMessages: false
|
||||
property bool ready: userInfo !== "waiting" && roomInfo !== "waiting"
|
||||
|
||||
readonly property var userInfo:
|
||||
utils.getItem(modelSources["Account"] || [], "user_id", userId) ||
|
||||
"waiting"
|
||||
|
||||
readonly property var roomInfo: utils.getItem(
|
||||
modelSources[["Room", userId]] || [], "room_id", roomId
|
||||
) || "waiting"
|
||||
property bool ready: Boolean(userInfo && roomInfo)
|
||||
|
||||
readonly property alias loader: loader
|
||||
readonly property alias roomPane: roomPaneLoader.item
|
||||
|
@@ -3,18 +3,28 @@
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import Clipboard 0.1
|
||||
import "../.."
|
||||
import "../../Base"
|
||||
import "../../Dialogs"
|
||||
|
||||
Rectangle {
|
||||
id: composer
|
||||
color: theme.chat.composer.background
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: theme.baseElementsHeight
|
||||
Layout.preferredHeight: areaScrollView.implicitHeight
|
||||
Layout.maximumHeight: pageLoader.height / 2
|
||||
|
||||
|
||||
property string indent: " "
|
||||
|
||||
property var aliases: window.settings.writeAliases
|
||||
property string toSend: ""
|
||||
|
||||
property string writingUserId: chat.userId
|
||||
readonly property var writingUserInfo:
|
||||
utils.getItem(modelSources["Account"] || [], "user_id", writingUserId)
|
||||
property QtObject writingUserInfo:
|
||||
ModelStore.get("accounts").find(writingUserId)
|
||||
|
||||
property bool textChangedSinceLostFocus: false
|
||||
|
||||
@@ -40,20 +50,9 @@ Rectangle {
|
||||
lineTextUntilCursor.match(/ {1,4}/g).slice(-1)[0].length :
|
||||
1
|
||||
|
||||
|
||||
function takeFocus() { areaScrollView.forceActiveFocus() }
|
||||
|
||||
// property var pr: lineTextUntilCursor
|
||||
// onPrChanged: print(
|
||||
// "y", cursorY, "x", cursorX,
|
||||
// "ltuc <" + lineTextUntilCursor + ">", "dob",
|
||||
// deleteCharsOnBackspace, "m", lineTextUntilCursor.match(/^ +$/))
|
||||
|
||||
id: composer
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: theme.baseElementsHeight
|
||||
Layout.preferredHeight: areaScrollView.implicitHeight
|
||||
Layout.maximumHeight: pageLoader.height / 2
|
||||
color: theme.chat.composer.background
|
||||
|
||||
HRowLayout {
|
||||
anchors.fill: parent
|
||||
@@ -61,8 +60,8 @@ Rectangle {
|
||||
HUserAvatar {
|
||||
id: avatar
|
||||
userId: writingUserId
|
||||
displayName: writingUserInfo.display_name
|
||||
mxc: writingUserInfo.avatar_url
|
||||
displayName: writingUserInfo ? writingUserInfo.display_name : ""
|
||||
mxc: writingUserInfo ? writingUserInfo.avatar_url : ""
|
||||
}
|
||||
|
||||
HScrollableTextArea {
|
||||
|
@@ -1,6 +1,7 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.12
|
||||
import "../../.."
|
||||
import "../../../Base"
|
||||
|
||||
Rectangle {
|
||||
@@ -25,11 +26,7 @@ Rectangle {
|
||||
id: transferList
|
||||
anchors.fill: parent
|
||||
|
||||
model: HListModel {
|
||||
keyField: "uuid"
|
||||
source: modelSources[["Upload", chat.roomId]] || []
|
||||
}
|
||||
|
||||
model: ModelStore.get(chat.roomId, "uploads")
|
||||
delegate: Transfer { width: transferList.width }
|
||||
}
|
||||
}
|
||||
|
@@ -11,7 +11,7 @@ HTileDelegate {
|
||||
model.invited ? theme.chat.roomPane.member.invitedOpacity : 1
|
||||
|
||||
image: HUserAvatar {
|
||||
userId: model.user_id
|
||||
userId: model.id
|
||||
displayName: model.display_name
|
||||
mxc: model.avatar_url
|
||||
powerLevel: model.power_level
|
||||
@@ -19,20 +19,20 @@ HTileDelegate {
|
||||
invited: model.invited
|
||||
}
|
||||
|
||||
title.text: model.display_name || model.user_id
|
||||
title.text: model.display_name || model.id
|
||||
title.color:
|
||||
memberDelegate.hovered ?
|
||||
utils.nameColor(model.display_name || model.user_id.substring(1)) :
|
||||
utils.nameColor(model.display_name || model.id.substring(1)) :
|
||||
theme.chat.roomPane.member.name
|
||||
|
||||
subtitle.text: model.display_name ? model.user_id : ""
|
||||
subtitle.text: model.display_name ? model.id : ""
|
||||
subtitle.color: theme.chat.roomPane.member.subtitle
|
||||
|
||||
contextMenu: HMenu {
|
||||
HMenuItem {
|
||||
icon.name: "copy-user-id"
|
||||
text: qsTr("Copy user ID")
|
||||
onTriggered: Clipboard.text = model.user_id
|
||||
onTriggered: Clipboard.text = model.id
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import "../../.."
|
||||
import "../../../Base"
|
||||
|
||||
HColumnLayout {
|
||||
@@ -9,37 +10,33 @@ HColumnLayout {
|
||||
id: memberList
|
||||
clip: true
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
model: ModelStore.get(chat.userId, chat.roomId, "members")
|
||||
// model: HSortFilterProxy {
|
||||
// model: ModelStore.get(chat.userId, chat.roomId, "members")
|
||||
|
||||
// comparator: (a, b) =>
|
||||
// // Sort by power level, then by display name or user ID (no @)
|
||||
// [
|
||||
// a.invited,
|
||||
// b.power_level,
|
||||
// (a.display_name || a.id.substring(1)).toLocaleLowerCase(),
|
||||
// ] < [
|
||||
// b.invited,
|
||||
// a.power_level,
|
||||
// (b.display_name || b.id.substring(1)).toLocaleLowerCase(),
|
||||
// ]
|
||||
|
||||
readonly property var originSource:
|
||||
modelSources[["Member", chat.userId, chat.roomId]] || []
|
||||
|
||||
|
||||
onOriginSourceChanged: filterLimiter.restart()
|
||||
|
||||
|
||||
function filterSource() {
|
||||
model.source =
|
||||
utils.filterModelSource(originSource, filterField.text)
|
||||
}
|
||||
|
||||
|
||||
model: HListModel {
|
||||
keyField: "user_id"
|
||||
source: memberList.originSource
|
||||
}
|
||||
// filter: (item, index) => utils.filterMatchesAny(
|
||||
// filterField.text, item.display_name, item.id,
|
||||
// )
|
||||
// }
|
||||
|
||||
delegate: MemberDelegate {
|
||||
width: memberList.width
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: filterLimiter
|
||||
interval: 16
|
||||
onTriggered: memberList.filterSource()
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
HRowLayout {
|
||||
@@ -56,7 +53,7 @@ HColumnLayout {
|
||||
bordered: false
|
||||
opacity: width >= 16 * theme.uiScale ? 1 : 0
|
||||
|
||||
onTextChanged: filterLimiter.restart()
|
||||
onTextChanged: memberList.model.reFilter()
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
@@ -172,7 +172,7 @@ HRowLayout {
|
||||
|
||||
HRepeater {
|
||||
id: linksRepeater
|
||||
model: eventDelegate.currentModel.links
|
||||
model: JSON.parse(eventDelegate.currentModel.links)
|
||||
|
||||
EventMediaLoader {
|
||||
singleMediaInfo: eventDelegate.currentModel
|
||||
|
@@ -3,6 +3,7 @@
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import Clipboard 0.1
|
||||
import "../../.."
|
||||
import "../../../Base"
|
||||
|
||||
HColumnLayout {
|
||||
@@ -65,13 +66,8 @@ HColumnLayout {
|
||||
function json() {
|
||||
return JSON.stringify(
|
||||
{
|
||||
"model": utils.getItem(
|
||||
modelSources[[
|
||||
"Event", chat.userId, chat.roomId
|
||||
]],
|
||||
"client_id",
|
||||
model.client_id
|
||||
),
|
||||
"model": ModelStore.get(chat.userId, chat.roomId, "events")
|
||||
.get(model.id),
|
||||
"source": py.getattr(model.source, "__dict__"),
|
||||
},
|
||||
null, 4)
|
||||
|
@@ -1,6 +1,7 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.12
|
||||
import "../../.."
|
||||
import "../../../Base"
|
||||
|
||||
Rectangle {
|
||||
@@ -157,12 +158,12 @@ Rectangle {
|
||||
}
|
||||
|
||||
|
||||
model: HListModel {
|
||||
keyField: "client_id"
|
||||
source: modelSources[[
|
||||
"Event", chat.userId, chat.roomId
|
||||
]] || []
|
||||
}
|
||||
model: ModelStore.get(chat.userId, chat.roomId, "events")
|
||||
// model: HSortFilterProxy {
|
||||
// model: ModelStore.get(chat.userId, chat.roomId, "events")
|
||||
// comparator: "date"
|
||||
// descendingSort: true
|
||||
// }
|
||||
|
||||
delegate: EventDelegate {}
|
||||
}
|
||||
|
@@ -32,7 +32,7 @@ Rectangle {
|
||||
textFormat: Text.StyledText
|
||||
elide: Text.ElideRight
|
||||
text: {
|
||||
let tm = chat.roomInfo.typing_members
|
||||
const tm = JSON.parse(chat.roomInfo.typing_members)
|
||||
|
||||
if (tm.length === 0) return ""
|
||||
if (tm.length === 1) return qsTr("%1 is typing...").arg(tm[0])
|
||||
|
@@ -21,7 +21,9 @@ BoxPopup {
|
||||
window.uiState.pageProperties.userId === userId &&
|
||||
window.uiState.pageProperties.roomId === roomId)
|
||||
{
|
||||
pageLoader.showPage("Default")
|
||||
window.mainUI.pageLoader.showPrevious() ||
|
||||
window.mainUI.pageLoader.showPage("Default")
|
||||
|
||||
Qt.callLater(popup.destroy)
|
||||
}
|
||||
})
|
||||
|
@@ -1,6 +1,7 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.12
|
||||
import ".."
|
||||
|
||||
BoxPopup {
|
||||
id: popup
|
||||
@@ -28,7 +29,7 @@ BoxPopup {
|
||||
ok: button => {
|
||||
utils.makeObject(
|
||||
"Dialogs/ExportKeys.qml",
|
||||
mainUI,
|
||||
window.mainUI,
|
||||
{ userId },
|
||||
obj => {
|
||||
button.loading = Qt.binding(() => obj.exporting)
|
||||
@@ -44,10 +45,9 @@ BoxPopup {
|
||||
okClicked = true
|
||||
popup.ok()
|
||||
|
||||
if ((modelSources["Account"] || []).length < 2) {
|
||||
pageLoader.showPage("AddAccount/AddAccount")
|
||||
} else if (window.uiState.pageProperties.userId === userId) {
|
||||
pageLoader.showPage("Default")
|
||||
if (ModelStore.get("accounts").count < 2 ||
|
||||
window.uiState.pageProperties.userId === userId) {
|
||||
window.mainUI.pageLoader.showPage("AddAccount/AddAccount")
|
||||
}
|
||||
|
||||
py.callCoro("logout_client", [userId])
|
||||
|
@@ -1,6 +1,8 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.12
|
||||
import ".."
|
||||
import "../.."
|
||||
|
||||
QtObject {
|
||||
function onExitRequested(exitCode) {
|
||||
@@ -16,10 +18,10 @@ QtObject {
|
||||
|
||||
|
||||
function onCoroutineDone(uuid, result, error, traceback) {
|
||||
let onSuccess = py.privates.pendingCoroutines[uuid].onSuccess
|
||||
let onError = py.privates.pendingCoroutines[uuid].onError
|
||||
let onSuccess = Globals.pendingCoroutines[uuid].onSuccess
|
||||
let onError = Globals.pendingCoroutines[uuid].onError
|
||||
|
||||
delete py.privates.pendingCoroutines[uuid]
|
||||
delete Globals.pendingCoroutines[uuid]
|
||||
|
||||
if (error) {
|
||||
const type = py.getattr(py.getattr(error, "__class__"), "__name__")
|
||||
@@ -74,14 +76,29 @@ QtObject {
|
||||
}
|
||||
|
||||
|
||||
function onModelUpdated(syncId, data, serializedSyncId) {
|
||||
if (serializedSyncId === "Account" || serializedSyncId[0] === "Room") {
|
||||
py.callCoro("get_flat_mainpane_data", [], data => {
|
||||
window.mainPaneModelSource = data
|
||||
})
|
||||
}
|
||||
function onModelItemInserted(syncId, index, item) {
|
||||
// print("insert", syncId, index, item)
|
||||
ModelStore.get(syncId).insert(index, item)
|
||||
}
|
||||
|
||||
window.modelSources[serializedSyncId] = data
|
||||
window.modelSourcesChanged()
|
||||
|
||||
function onModelItemFieldChanged(syncId, oldIndex, newIndex, field, value){
|
||||
// print("change", syncId, oldIndex, newIndex, field, value)
|
||||
const model = ModelStore.get(syncId)
|
||||
model.setProperty(oldIndex, field, value)
|
||||
|
||||
if (oldIndex !== newIndex) model.move(oldIndex, newIndex, 1)
|
||||
}
|
||||
|
||||
|
||||
function onModelItemDeleted(syncId, index) {
|
||||
// print("del", syncId, index)
|
||||
ModelStore.get(syncId).remove(index)
|
||||
}
|
||||
|
||||
|
||||
function onModelCleared(syncId) {
|
||||
// print("clear", syncId)
|
||||
ModelStore.get(syncId).clear()
|
||||
}
|
||||
}
|
8
src/gui/PythonBridge/Privates/Globals.qml
Normal file
8
src/gui/PythonBridge/Privates/Globals.qml
Normal file
@@ -0,0 +1,8 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
pragma Singleton
|
||||
import QtQuick 2.12
|
||||
|
||||
QtObject {
|
||||
readonly property var pendingCoroutines: ({})
|
||||
}
|
1
src/gui/PythonBridge/Privates/qmldir
Normal file
1
src/gui/PythonBridge/Privates/qmldir
Normal file
@@ -0,0 +1 @@
|
||||
singleton Globals 0.1 Globals.qml
|
@@ -3,41 +3,16 @@
|
||||
import QtQuick 2.12
|
||||
import io.thp.pyotherside 1.5
|
||||
import CppUtils 0.1
|
||||
import "Privates"
|
||||
|
||||
Python {
|
||||
id: py
|
||||
Component.onCompleted: {
|
||||
for (var func in privates.eventHandlers) {
|
||||
if (! privates.eventHandlers.hasOwnProperty(func)) continue
|
||||
setHandler(func.replace(/^on/, ""), privates.eventHandlers[func])
|
||||
}
|
||||
|
||||
addImportPath("src")
|
||||
addImportPath("qrc:/src")
|
||||
|
||||
importNames("backend.qml_bridge", ["BRIDGE"], () => {
|
||||
loadSettings(() => {
|
||||
callCoro("saved_accounts.any_saved", [], any => {
|
||||
if (any) { py.callCoro("load_saved_accounts", []) }
|
||||
|
||||
py.startupAnyAccountsSaved = any
|
||||
py.ready = true
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
property bool ready: false
|
||||
property bool startupAnyAccountsSaved: false
|
||||
|
||||
readonly property QtObject privates: QtObject {
|
||||
readonly property var pendingCoroutines: ({})
|
||||
readonly property EventHandlers eventHandlers: EventHandlers {}
|
||||
|
||||
function makeFuture(callback) {
|
||||
return Qt.createComponent("Future.qml")
|
||||
.createObject(py, {bridge: py})
|
||||
.createObject(py, { bridge: py })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,15 +22,10 @@ Python {
|
||||
}
|
||||
|
||||
|
||||
function callSync(name, args=[]) {
|
||||
return call_sync("BRIDGE.backend." + name, args)
|
||||
}
|
||||
|
||||
|
||||
function callCoro(name, args=[], onSuccess=null, onError=null) {
|
||||
let uuid = name + "." + CppUtils.uuid()
|
||||
|
||||
privates.pendingCoroutines[uuid] = {onSuccess, onError}
|
||||
Globals.pendingCoroutines[uuid] = {onSuccess, onError}
|
||||
|
||||
let future = privates.makeFuture()
|
||||
|
||||
@@ -75,7 +45,7 @@ Python {
|
||||
callCoro("get_client", [accountId], () => {
|
||||
let uuid = accountId + "." + name + "." + CppUtils.uuid()
|
||||
|
||||
privates.pendingCoroutines[uuid] = {onSuccess, onError}
|
||||
Globals.pendingCoroutines[uuid] = {onSuccess, onError}
|
||||
|
||||
let call_args = [accountId, name, uuid, args]
|
||||
|
||||
|
33
src/gui/PythonBridge/PythonRootBridge.qml
Normal file
33
src/gui/PythonBridge/PythonRootBridge.qml
Normal file
@@ -0,0 +1,33 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.12
|
||||
import "Privates"
|
||||
|
||||
PythonBridge {
|
||||
Component.onCompleted: {
|
||||
for (var func in eventHandlers) {
|
||||
if (! eventHandlers.hasOwnProperty(func)) continue
|
||||
setHandler(func.replace(/^on/, ""), eventHandlers[func])
|
||||
}
|
||||
|
||||
addImportPath("src")
|
||||
addImportPath("qrc:/src")
|
||||
|
||||
importNames("backend.qml_bridge", ["BRIDGE"], () => {
|
||||
loadSettings(() => {
|
||||
callCoro("saved_accounts.any_saved", [], any => {
|
||||
if (any) { callCoro("load_saved_accounts", []) }
|
||||
|
||||
startupAnyAccountsSaved = any
|
||||
ready = true
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
property bool ready: false
|
||||
property bool startupAnyAccountsSaved: false
|
||||
|
||||
readonly property EventHandlers eventHandlers: EventHandlers {}
|
||||
}
|
@@ -16,8 +16,7 @@ Item {
|
||||
|
||||
|
||||
property bool accountsPresent:
|
||||
(modelSources["Account"] || []).length > 0 ||
|
||||
py.startupAnyAccountsSaved
|
||||
ModelStore.get("accounts").count > 0 || py.startupAnyAccountsSaved
|
||||
|
||||
readonly property alias shortcuts: shortcuts
|
||||
readonly property alias mainPane: mainPane
|
||||
|
@@ -135,30 +135,29 @@ QtObject {
|
||||
|
||||
|
||||
function processedEventText(ev) {
|
||||
if (ev.event_type === "RoomMessageEmote")
|
||||
return coloredNameHtml(ev.sender_name, ev.sender_id) + " " +
|
||||
ev.content
|
||||
const type = ev.event_type
|
||||
const unknownMsg = type === "RoomMessageUnknown"
|
||||
const sender = coloredNameHtml(ev.sender_name, ev.sender_id)
|
||||
|
||||
let unknown = ev.event_type === "RoomMessageUnknown"
|
||||
if (type === "RoomMessageEmote")
|
||||
return qsTr("%1 %2").arg(sender).arg(ev.content)
|
||||
|
||||
if (ev.event_type.startsWith("RoomMessage") && ! unknown)
|
||||
if (type.startsWith("RoomMessage") && ! unknownMsg)
|
||||
return ev.content
|
||||
|
||||
if (ev.event_type.startsWith("RoomEncrypted")) return ev.content
|
||||
if (type.startsWith("RoomEncrypted"))
|
||||
return ev.content
|
||||
|
||||
let text = qsTr(ev.content).arg(
|
||||
coloredNameHtml(ev.sender_name, ev.sender_id)
|
||||
)
|
||||
if (ev.content.includes("%2")) {
|
||||
const target = coloredNameHtml(ev.target_name, ev.target_id)
|
||||
return qsTr(ev.content).arg(sender).arg(target)
|
||||
}
|
||||
|
||||
if (text.includes("%2") && ev.target_id)
|
||||
text = text.arg(coloredNameHtml(ev.target_name, ev.target_id))
|
||||
|
||||
return text
|
||||
return qsTr(ev.content).arg(sender)
|
||||
}
|
||||
|
||||
|
||||
function filterMatches(filter, text) {
|
||||
let filter_lower = filter.toLowerCase()
|
||||
const filter_lower = filter.toLowerCase()
|
||||
|
||||
if (filter_lower === filter) {
|
||||
// Consider case only if filter isn't all lowercase (smart case)
|
||||
@@ -175,17 +174,11 @@ QtObject {
|
||||
}
|
||||
|
||||
|
||||
function filterModelSource(source, filter_text, property="filter_string") {
|
||||
if (! filter_text) return source
|
||||
let results = []
|
||||
|
||||
for (let i = 0; i < source.length; i++) {
|
||||
if (filterMatches(filter_text, source[i][property])) {
|
||||
results.push(source[i])
|
||||
}
|
||||
function filterMatchesAny(filter, ...texts) {
|
||||
for (let text of texts) {
|
||||
if (filterMatches(filter, text)) return true
|
||||
}
|
||||
|
||||
return results
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -257,14 +250,6 @@ QtObject {
|
||||
}
|
||||
|
||||
|
||||
function getItem(array, mainKey, value) {
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
if (array[i][mainKey] === value) { return array[i] }
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
|
||||
function flickPages(flickable, pages) {
|
||||
// Adapt velocity and deceleration for the number of pages to flick.
|
||||
// If this is a repeated flicking, flick faster than a single flick.
|
||||
|
@@ -28,7 +28,6 @@ ApplicationWindow {
|
||||
|
||||
// NOTE: For JS object variables, the corresponding method to notify
|
||||
// key/value changes must be called manually, e.g. settingsChanged().
|
||||
property var modelSources: ({})
|
||||
property var mainPaneModelSource: []
|
||||
|
||||
property var mainUI: null
|
||||
@@ -46,8 +45,6 @@ ApplicationWindow {
|
||||
|
||||
property var hideErrorTypes: new Set()
|
||||
|
||||
readonly property alias py: py
|
||||
|
||||
|
||||
function saveState(obj) {
|
||||
if (! obj.saveName || ! obj.saveProperties ||
|
||||
@@ -75,7 +72,7 @@ ApplicationWindow {
|
||||
}
|
||||
|
||||
|
||||
PythonBridge { id: py }
|
||||
PythonRootBridge { id: py }
|
||||
|
||||
Utils { id: utils }
|
||||
|
||||
|
1
src/gui/qmldir
Normal file
1
src/gui/qmldir
Normal file
@@ -0,0 +1 @@
|
||||
singleton ModelStore 0.1 ModelStore.qml
|
Reference in New Issue
Block a user