moment/src/gui/Pages/Chat/Timeline/EventDelegate.qml
miruka 63af4be1e2 Defer fetching user profiles for events
Previously, events for which the sender, target (state_key) or remover
was missing from the room members would have their profile fetched
from network when registering the event into models.

This could cause very slow past events loading times for rooms, since
the event registering function (which contained the profile retrieval
directives) is run sequentially event-by-event.

Missing profiles are now lazy-loaded when events come into the
user's view in the QML timeline.
2020-05-20 03:42:40 -04:00

276 lines
7.9 KiB
QML

// SPDX-License-Identifier: LGPL-3.0-or-later
import QtQuick 2.12
import QtQuick.Layouts 1.12
import Clipboard 0.1
import "../../.."
import "../../../Base"
HColumnLayout {
id: eventDelegate
width: eventList.width
ListView.onRemove: eventList.uncheck(model.id)
enum Media { Page, File, Image, Video, Audio }
property var hoveredMediaTypeUrl: []
property var fetchProfilesFuture: null
// Remember timeline goes from newest message at index 0 to oldest
readonly property var previousModel: eventList.model.get(model.index + 1)
readonly property var nextModel: eventList.model.get(model.index - 1)
readonly property QtObject currentModel: model
property bool checked: model.id in eventList.checked
property bool compact: window.settings.compactMode
property bool isOwn: chat.userId === model.sender_id
property bool onRight: ! eventList.ownEventsOnLeft && isOwn
property bool combine: eventList.canCombine(previousModel, model)
property bool talkBreak: eventList.canTalkBreak(previousModel, model)
property bool dayBreak: eventList.canDayBreak(previousModel, model)
readonly property bool smallAvatar: compact
readonly property bool collapseAvatar: combine
readonly property bool hideAvatar: onRight
readonly property bool isRedacted: model.event_type === "RedactedEvent"
readonly property bool hideNameLine:
model.event_type === "RoomMessageEmote" ||
! (
model.event_type.startsWith("RoomMessage") ||
model.event_type.startsWith("RoomEncrypted")
) ||
onRight ||
combine
readonly property int cursorShape:
eventContent.hoveredLink || hoveredMediaTypeUrl.length > 0 ?
Qt.PointingHandCursor :
eventContent.hoveredSelectable ? Qt.IBeamCursor :
Qt.ArrowCursor
readonly property int separationSpacing:
dayBreak ? theme.spacing * 4 :
talkBreak ? theme.spacing * 6 :
combine ? theme.spacing / (compact ? 4 : 2) :
theme.spacing * (compact ? 1 : 2)
readonly property alias eventContent: eventContent
// Needed because of eventList's MouseArea which steals the
// HSelectableLabel's MouseArea hover events
onCursorShapeChanged: eventList.cursorShape = cursorShape
Component.onCompleted: if (model.fetch_profile) py.callClientCoro(
chat.userId, "get_event_profiles", [chat.roomId, model.id],
)
Component.onDestruction:
if (fetchProfilesFuture) fetchProfilesFuture.cancel()
function json() {
let event = ModelStore.get(chat.userId, chat.roomId, "events")
.get(model.index)
event = JSON.parse(JSON.stringify(event))
event.source = JSON.parse(event.source)
return JSON.stringify(event, null, 4)
}
function openContextMenu() {
contextMenu.media = eventDelegate.hoveredMediaTypeUrl
contextMenu.link = eventContent.hoveredLink
contextMenu.popup()
}
function toggleChecked() {
eventList.toggleCheck(model.index)
}
Item {
Layout.fillWidth: true
Layout.preferredHeight:
model.event_type === "RoomCreateEvent" ? 0 : separationSpacing
}
Daybreak {
visible: dayBreak
Layout.fillWidth: true
Layout.minimumWidth: parent.width
}
Item {
visible: dayBreak
Layout.fillWidth: true
Layout.preferredHeight: separationSpacing
}
EventContent {
id: eventContent
Layout.fillWidth: true
Behavior on x { HNumberAnimation {} }
}
TapHandler {
acceptedButtons: Qt.LeftButton
acceptedModifiers: Qt.NoModifier
onTapped: toggleChecked()
}
TapHandler {
acceptedButtons: Qt.LeftButton
acceptedModifiers: Qt.ShiftModifier
onTapped: eventList.checkFromLastToHere(model.index)
}
TapHandler {
acceptedButtons: Qt.RightButton
acceptedPointerTypes: PointerDevice.GenericPointer | PointerDevice.Pen
onTapped: openContextMenu()
}
TapHandler {
acceptedPointerTypes: PointerDevice.Finger | PointerDevice.Pen
onLongPressed: openContextMenu()
}
HMenu {
id: contextMenu
property var media: []
property string link: ""
onClosed: { media = []; link = "" }
HMenuItem {
icon.name: "toggle-select-message"
text: eventDelegate.checked ? qsTr("Deselect") : qsTr("Select")
onTriggered: eventDelegate.toggleChecked()
}
HMenuItem {
visible: eventList.selectedCount >= 2
icon.name: "deselect-all-messages"
text: qsTr("Deselect all")
onTriggered: eventList.checked = {}
}
HMenuItem {
visible: model.index !== 0
icon.name: "select-until-here"
text: qsTr("Select until here")
onTriggered: eventList.checkFromLastToHere(model.index)
}
HMenuItem {
id: copyMedia
icon.name: "copy-link"
text:
contextMenu.media.length < 1 ? "" :
contextMenu.media[0] === EventDelegate.Media.Page ?
qsTr("Copy page address") :
contextMenu.media[0] === EventDelegate.Media.File ?
qsTr("Copy file address") :
contextMenu.media[0] === EventDelegate.Media.Image ?
qsTr("Copy image address") :
contextMenu.media[0] === EventDelegate.Media.Video ?
qsTr("Copy video address") :
contextMenu.media[0] === EventDelegate.Media.Audio ?
qsTr("Copy audio address") :
qsTr("Copy media address")
visible: Boolean(text)
onTriggered: Clipboard.text = contextMenu.media[1]
}
HMenuItem {
id: copyLink
icon.name: "copy-link"
text: qsTr("Copy link address")
visible: Boolean(contextMenu.link)
onTriggered: Clipboard.text = contextMenu.link
}
HMenuItem {
icon.name: "copy-text"
text:
eventList.selectedCount ?
qsTr("Copy selection") :
copyMedia.visible ?
qsTr("Copy filename") :
qsTr("Copy text")
onTriggered: {
if (! eventList.selectedCount && ! eventList.currentItem){
Clipboard.text = JSON.parse(model.source).body
return
}
eventList.copySelectedDelegates()
}
}
HMenuItemPopupSpawner {
icon.name: "remove-message"
text: qsTr("Remove")
enabled: properties.eventSenderAndIds.length
popup: "Popups/RedactPopup.qml"
popupParent: chat
properties: ({
preferUserId: chat.userId,
roomId: chat.roomId,
eventSenderAndIds: events.map(ev => [ev.sender_id, ev.id]),
onlyOwnMessageWarning:
! chat.roomInfo.can_redact_all &&
events.length < eventList.selectedCount
})
readonly property var events: {
eventList.selectedCount ?
eventList.redactableCheckedEvents :
eventList.canRedact(currentModel) ?
[model] :
[]
}
}
HMenuItem {
icon.name: "debug"
text: qsTr("Debug this event")
onTriggered:
mainUI.debugConsole.toggle(eventContent, "t.parent.json()")
}
HMenuItemPopupSpawner {
icon.name: "clear-messages"
text: qsTr("Clear messages")
popup: "Popups/ClearMessagesPopup.qml"
popupParent: chat
properties: ({userId: chat.userId, roomId: chat.roomId})
}
}
}