Make media openable with the openLinks keybind
This involved a refactoring to move all the media handling functions (downloading, opening externally, etc) out of the Event delegates and into the EventList, which manage keybinds instead. This should also be better for performance since all these functions are no longer duplicated for every Event in view. Other user-noticable change: clicking on non-image media will always download and open them no matter if the room is encrypted or not, instead of opening non-encrypted files in browser like before. It will be possible to still do that with an "open externally" command later.
This commit is contained in:
parent
fe08014697
commit
de6d8fa59d
5
TODO.md
5
TODO.md
|
@ -1,10 +1,15 @@
|
||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
- Image viewer:
|
- Image viewer:
|
||||||
|
- fix gifs
|
||||||
|
- double click on img to open fullscreen
|
||||||
- open externally in context menu in timeline thumbnail
|
- open externally in context menu in timeline thumbnail
|
||||||
- hflickable support kinetic scrolling disabler and speed/decel settings
|
- hflickable support kinetic scrolling disabler and speed/decel settings
|
||||||
- buttons
|
- buttons
|
||||||
- keyboard controls
|
- keyboard controls
|
||||||
|
- prevent drag-scrolling timeline when image opened
|
||||||
|
- eventfile middle click
|
||||||
|
- right click in dark space of image viewer
|
||||||
|
|
||||||
- clipboard preview doesn't update when copied image changes until second time
|
- clipboard preview doesn't update when copied image changes until second time
|
||||||
- Avatar tooltip can get displayed in front of presence menu
|
- Avatar tooltip can get displayed in front of presence menu
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import QtQuick 2.12
|
import QtQuick 2.12
|
||||||
import QtQuick.Layouts 1.12
|
import QtQuick.Layouts 1.12
|
||||||
import QtAV 1.7
|
import QtAV 1.7
|
||||||
|
import "../../.."
|
||||||
import "../../../Base"
|
import "../../../Base"
|
||||||
import "../../../Base/MediaPlayer"
|
import "../../../Base/MediaPlayer"
|
||||||
|
|
||||||
|
@ -12,6 +13,6 @@ AudioPlayer {
|
||||||
HoverHandler {
|
HoverHandler {
|
||||||
onHoveredChanged:
|
onHoveredChanged:
|
||||||
eventDelegate.hoveredMediaTypeUrl =
|
eventDelegate.hoveredMediaTypeUrl =
|
||||||
hovered ? [EventDelegate.Media.Audio, audio.source] : []
|
hovered ? [Utils.Media.Audio, audio.source] : []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,6 @@ import "../../../Base"
|
||||||
HColumnLayout {
|
HColumnLayout {
|
||||||
id: eventDelegate
|
id: eventDelegate
|
||||||
|
|
||||||
enum Media { Page, File, Image, Video, Audio }
|
|
||||||
|
|
||||||
property var hoveredMediaTypeUrl: []
|
property var hoveredMediaTypeUrl: []
|
||||||
|
|
||||||
property var fetchProfilesFuture: null
|
property var fetchProfilesFuture: null
|
||||||
|
@ -171,19 +169,19 @@ HColumnLayout {
|
||||||
text:
|
text:
|
||||||
contextMenu.media.length < 1 ? "" :
|
contextMenu.media.length < 1 ? "" :
|
||||||
|
|
||||||
contextMenu.media[0] === EventDelegate.Media.Page ?
|
contextMenu.media[0] === Utils.Media.Page ?
|
||||||
qsTr("Copy page address") :
|
qsTr("Copy page address") :
|
||||||
|
|
||||||
contextMenu.media[0] === EventDelegate.Media.File ?
|
contextMenu.media[0] === Utils.Media.File ?
|
||||||
qsTr("Copy file address") :
|
qsTr("Copy file address") :
|
||||||
|
|
||||||
contextMenu.media[0] === EventDelegate.Media.Image ?
|
contextMenu.media[0] === Utils.Media.Image ?
|
||||||
qsTr("Copy image address") :
|
qsTr("Copy image address") :
|
||||||
|
|
||||||
contextMenu.media[0] === EventDelegate.Media.Video ?
|
contextMenu.media[0] === Utils.Media.Video ?
|
||||||
qsTr("Copy video address") :
|
qsTr("Copy video address") :
|
||||||
|
|
||||||
contextMenu.media[0] === EventDelegate.Media.Audio ?
|
contextMenu.media[0] === Utils.Media.Audio ?
|
||||||
qsTr("Copy audio address") :
|
qsTr("Copy audio address") :
|
||||||
|
|
||||||
qsTr("Copy media address")
|
qsTr("Copy media address")
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import QtQuick 2.12
|
import QtQuick 2.12
|
||||||
import QtQuick.Layouts 1.12
|
import QtQuick.Layouts 1.12
|
||||||
import CppUtils 0.1
|
import CppUtils 0.1
|
||||||
|
import "../../.."
|
||||||
import "../../../Base"
|
import "../../../Base"
|
||||||
import "../../../Base/HTile"
|
import "../../../Base/HTile"
|
||||||
|
|
||||||
|
@ -11,11 +12,6 @@ HTile {
|
||||||
|
|
||||||
property EventMediaLoader loader
|
property EventMediaLoader loader
|
||||||
|
|
||||||
readonly property bool cryptDict:
|
|
||||||
JSON.parse(loader.singleMediaInfo.media_crypt_dict)
|
|
||||||
|
|
||||||
readonly property bool isEncrypted: ! utils.isEmptyObject(cryptDict)
|
|
||||||
|
|
||||||
|
|
||||||
width: Math.min(
|
width: Math.min(
|
||||||
eventDelegate.width,
|
eventDelegate.width,
|
||||||
|
@ -50,7 +46,12 @@ HTile {
|
||||||
onRightClicked: eventDelegate.openContextMenu()
|
onRightClicked: eventDelegate.openContextMenu()
|
||||||
onLeftClicked:
|
onLeftClicked:
|
||||||
eventList.selectedCount ?
|
eventList.selectedCount ?
|
||||||
eventDelegate.toggleChecked() : download(Qt.openUrlExternally)
|
eventDelegate.toggleChecked() :
|
||||||
|
|
||||||
|
loader.isMedia ?
|
||||||
|
eventList.openMediaExternally(singleMediaInfo) :
|
||||||
|
|
||||||
|
Qt.openUrlExternally(loader.mediaUrl)
|
||||||
|
|
||||||
onHoveredChanged: {
|
onHoveredChanged: {
|
||||||
if (! hovered) {
|
if (! hovered) {
|
||||||
|
@ -59,8 +60,10 @@ HTile {
|
||||||
}
|
}
|
||||||
|
|
||||||
eventDelegate.hoveredMediaTypeUrl = [
|
eventDelegate.hoveredMediaTypeUrl = [
|
||||||
EventDelegate.Media.File,
|
Utils.Media.File,
|
||||||
loader.downloadedPath.replace(/^file:\/\//, "") || loader.mediaUrl
|
// XXX
|
||||||
|
// loader.downloadedPath.replace(/^file:\/\//, "") ||
|
||||||
|
loader.mediaUrl
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
import QtQuick 2.12
|
import QtQuick 2.12
|
||||||
|
import "../../.."
|
||||||
import "../../../Base"
|
import "../../../Base"
|
||||||
|
|
||||||
HMxcImage {
|
HMxcImage {
|
||||||
|
@ -8,8 +9,6 @@ HMxcImage {
|
||||||
|
|
||||||
property EventMediaLoader loader
|
property EventMediaLoader loader
|
||||||
|
|
||||||
readonly property bool isEncrypted: ! utils.isEmptyObject(cryptDict)
|
|
||||||
|
|
||||||
readonly property real maxHeight:
|
readonly property real maxHeight:
|
||||||
eventList.height * theme.chat.message.thumbnailMaxHeightRatio
|
eventList.height * theme.chat.message.thumbnailMaxHeightRatio
|
||||||
|
|
||||||
|
@ -43,69 +42,13 @@ HMxcImage {
|
||||||
Math.max(maxHeight, theme.chat.message.thumbnailMinSize.height),
|
Math.max(maxHeight, theme.chat.message.thumbnailMinSize.height),
|
||||||
)
|
)
|
||||||
|
|
||||||
function getOpenUrl(callback) {
|
|
||||||
if (image.isEncrypted && loader.mediaUrl) {
|
|
||||||
loader.download(callback)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (image.isEncrypted) {
|
|
||||||
callback(image.cachedPath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const toOpen = loader.mediaUrl || loader.thumbnailMxc
|
|
||||||
const isMxc = toOpen.startsWith("mxc://")
|
|
||||||
|
|
||||||
isMxc ?
|
|
||||||
py.callClientCoro(chat.userId, "mxc_to_http", [toOpen], callback) :
|
|
||||||
callback(toOpen)
|
|
||||||
}
|
|
||||||
|
|
||||||
function openUrlExternally() {
|
|
||||||
getOpenUrl(Qt.openUrlExternally)
|
|
||||||
}
|
|
||||||
|
|
||||||
function openImageViewer() {
|
|
||||||
utils.makePopup(
|
|
||||||
"Popups/ImageViewerPopup.qml",
|
|
||||||
{
|
|
||||||
thumbnailTitle: loader.thumbnailTitle,
|
|
||||||
thumbnailMxc: loader.thumbnailMxc,
|
|
||||||
thumbnailPath: image.cachedPath,
|
|
||||||
thumbnailCryptDict:
|
|
||||||
JSON.parse(loader.singleMediaInfo.thumbnail_crypt_dict),
|
|
||||||
|
|
||||||
fullTitle: loader.title,
|
|
||||||
// The thumbnail/cached path will be the full GIF
|
|
||||||
fullMxc: animated ? "" : loader.mediaUrl,
|
|
||||||
fullCryptDict:
|
|
||||||
JSON.parse(loader.singleMediaInfo.media_crypt_dict),
|
|
||||||
|
|
||||||
overallSize: Qt.size(
|
|
||||||
loader.singleMediaInfo.media_width ||
|
|
||||||
loader.singleMediaInfo.thumbnail_width ||
|
|
||||||
implicitWidth ||
|
|
||||||
800,
|
|
||||||
|
|
||||||
loader.singleMediaInfo.media_height ||
|
|
||||||
loader.singleMediaInfo.thumbnail_height ||
|
|
||||||
implicitHeight ||
|
|
||||||
600,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
obj => { obj.openExternallyRequested.connect(openUrlExternally) },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
width: fitSize.width
|
width: fitSize.width
|
||||||
height: fitSize.height
|
height: fitSize.height
|
||||||
horizontalAlignment: Image.AlignLeft
|
horizontalAlignment: Image.AlignLeft
|
||||||
|
|
||||||
title: thumbnail ? loader.thumbnailTitle : loader.title
|
title: thumbnail ? loader.thumbnailTitle : loader.title
|
||||||
animated: loader.singleMediaInfo.media_mime === "image/gif" ||
|
animated: eventList.isAnimated(loader.singleMediaInfo)
|
||||||
utils.urlExtension(loader.mediaUrl).toLowerCase() === "gif"
|
|
||||||
thumbnail: ! animated && loader.thumbnailMxc
|
thumbnail: ! animated && loader.thumbnailMxc
|
||||||
mxc: thumbnail ?
|
mxc: thumbnail ?
|
||||||
(loader.thumbnailMxc || loader.mediaUrl) :
|
(loader.thumbnailMxc || loader.mediaUrl) :
|
||||||
|
@ -116,30 +59,36 @@ HMxcImage {
|
||||||
loader.singleMediaInfo.media_crypt_dict
|
loader.singleMediaInfo.media_crypt_dict
|
||||||
)
|
)
|
||||||
|
|
||||||
|
onCachedPathChanged:
|
||||||
|
eventList.thumbnailCachedPaths[loader.singleMediaInfo.id] = cachedPath
|
||||||
|
|
||||||
TapHandler {
|
TapHandler {
|
||||||
acceptedButtons: Qt.LeftButton
|
acceptedButtons: Qt.LeftButton
|
||||||
acceptedModifiers: Qt.NoModifier
|
acceptedModifiers: Qt.NoModifier
|
||||||
|
gesturePolicy: TapHandler.ReleaseWithinBounds
|
||||||
onTapped:
|
onTapped:
|
||||||
eventList.selectedCount ?
|
eventList.selectedCount ?
|
||||||
eventDelegate.toggleChecked() :
|
eventDelegate.toggleChecked() :
|
||||||
openImageViewer()
|
eventList.openImageViewer(singleMediaInfo)
|
||||||
|
|
||||||
gesturePolicy: TapHandler.ReleaseWithinBounds
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TapHandler {
|
TapHandler {
|
||||||
acceptedButtons: Qt.MiddleButton
|
acceptedButtons: Qt.MiddleButton
|
||||||
acceptedModifiers: Qt.NoModifier
|
acceptedModifiers: Qt.NoModifier
|
||||||
onTapped: getOpenUrl(Qt.openUrlExternally)
|
|
||||||
gesturePolicy: TapHandler.ReleaseWithinBounds
|
gesturePolicy: TapHandler.ReleaseWithinBounds
|
||||||
|
onTapped: {
|
||||||
|
loader.isMedia ?
|
||||||
|
eventList.openMediaExternally(singleMediaInfo) :
|
||||||
|
Qt.openUrlExternally(loader.mediaUrl)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TapHandler {
|
TapHandler {
|
||||||
acceptedModifiers: Qt.ShiftModifier
|
acceptedModifiers: Qt.ShiftModifier
|
||||||
|
gesturePolicy: TapHandler.ReleaseWithinBounds
|
||||||
onTapped:
|
onTapped:
|
||||||
eventList.checkFromLastToHere(singleMediaInfo.index)
|
eventList.checkFromLastToHere(singleMediaInfo.index)
|
||||||
|
|
||||||
gesturePolicy: TapHandler.ReleaseWithinBounds
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HoverHandler {
|
HoverHandler {
|
||||||
|
@ -151,8 +100,9 @@ HMxcImage {
|
||||||
}
|
}
|
||||||
|
|
||||||
eventDelegate.hoveredMediaTypeUrl = [
|
eventDelegate.hoveredMediaTypeUrl = [
|
||||||
EventDelegate.Media.Image,
|
Utils.Media.Image,
|
||||||
loader.downloadedPath.replace(/^file:\/\//, "") ||
|
// XXX
|
||||||
|
// loader.downloadedPath.replace(/^file:\/\//, "") ||
|
||||||
loader.mediaUrl
|
loader.mediaUrl
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,29 +125,43 @@ Rectangle {
|
||||||
}
|
}
|
||||||
|
|
||||||
HShortcut {
|
HShortcut {
|
||||||
sequences: window.settings.keys.openMessagesLinks
|
sequences: window.settings.keys.openMessagesLinks // XXX: rename
|
||||||
onActivated: {
|
onActivated: {
|
||||||
let events = []
|
let indice = []
|
||||||
|
|
||||||
if (eventList.selectedCount) {
|
if (eventList.selectedCount) {
|
||||||
events = eventList.getSortedChecked()
|
indice = eventList.checkedIndice
|
||||||
} else if (eventList.currentIndex !== -1) {
|
} else if (eventList.currentIndex !== -1) {
|
||||||
events = [eventList.model.get(eventList.currentIndex)]
|
indice = [eventList.currentIndex]
|
||||||
} else {
|
} else {
|
||||||
// Find most recent event containing links
|
// Find most recent event that's a media or contains links
|
||||||
for (let i = 0; i < eventList.model.count && i <= 1000; i++) {
|
for (let i = 0; i < eventList.model.count && i <= 1000; i++) {
|
||||||
const ev = eventList.model.get(i)
|
const ev = eventList.model.get(i)
|
||||||
|
const links = JSON.parse(ev.links)
|
||||||
|
|
||||||
if (JSON.parse(ev.links).length) {
|
if (ev.media_url || ev.thumbnail_url || links.length) {
|
||||||
events = [ev]
|
indice = [i]
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const event of events) {
|
for (const i of indice.sort().reverse()) {
|
||||||
for (const link of JSON.parse(event.links))
|
const event = eventList.model.get(i)
|
||||||
Qt.openUrlExternally(link)
|
|
||||||
|
if (event.media_url || event.thumbnail_url) {
|
||||||
|
eventList.getMediaType(event) === Utils.Media.Image ?
|
||||||
|
eventList.openImageViewer(event) :
|
||||||
|
eventList.openMediaExternally(event)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const url of JSON.parse(event.links)) {
|
||||||
|
utils.getLinkType(url) === Utils.Media.Image ?
|
||||||
|
eventList.openImageViewer(event, url) :
|
||||||
|
Qt.openUrlExternally(url)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -198,6 +212,8 @@ Rectangle {
|
||||||
|
|
||||||
property alias cursorShape: cursorShapeArea.cursorShape
|
property alias cursorShape: cursorShapeArea.cursorShape
|
||||||
|
|
||||||
|
readonly property var thumbnailCachedPaths: ({}) // {event.id: path}
|
||||||
|
|
||||||
readonly property var redactableCheckedEvents:
|
readonly property var redactableCheckedEvents:
|
||||||
getSortedChecked().filter(ev => eventList.canRedact(ev))
|
getSortedChecked().filter(ev => eventList.canRedact(ev))
|
||||||
|
|
||||||
|
@ -302,6 +318,106 @@ Rectangle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getMediaType(event) {
|
||||||
|
if (event.event_type === "RoomAvatarEvent")
|
||||||
|
return Utils.Media.Image
|
||||||
|
|
||||||
|
const mainType = event.media_mime.split("/")[0].toLowerCase()
|
||||||
|
const fileEvents = ["RoomMessageFile", "RoomEncryptedFile"]
|
||||||
|
|
||||||
|
return (
|
||||||
|
mainType === "image" ? Utils.Media.Image :
|
||||||
|
mainType === "video" ? Utils.Media.Video :
|
||||||
|
mainType === "audio" ? Utils.Media.Audio :
|
||||||
|
fileEvents.includes(event.event_type) ? Utils.Media.File :
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAnimated(event) {
|
||||||
|
return (
|
||||||
|
event.media_mime === "image/gif" ||
|
||||||
|
utils.urlExtension(event.media_url).toLowerCase() === "gif"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getThumbnailTitle(event) {
|
||||||
|
return event.media_title.replace(
|
||||||
|
/\.[^\.]+$/,
|
||||||
|
event.thumbnail_mime === "image/jpeg" ? ".jpg" :
|
||||||
|
event.thumbnail_mime === "image/png" ? ".png" :
|
||||||
|
event.thumbnail_mime === "image/gif" ? ".gif" :
|
||||||
|
event.thumbnail_mime === "image/tiff" ? ".tiff" :
|
||||||
|
event.thumbnail_mime === "image/svg+xml" ? ".svg" :
|
||||||
|
event.thumbnail_mime === "image/webp" ? ".webp" :
|
||||||
|
event.thumbnail_mime === "image/bmp" ? ".bmp" :
|
||||||
|
".thumbnail"
|
||||||
|
) || utils.urlFileName(event.media_url)
|
||||||
|
}
|
||||||
|
|
||||||
|
function openImageViewer(event, forLink="") {
|
||||||
|
// if forLink is empty, this must be a media event
|
||||||
|
|
||||||
|
const title =
|
||||||
|
event.media_title || utils.urlFileName(event.media_url)
|
||||||
|
|
||||||
|
// The thumbnail/cached path will be the full GIF
|
||||||
|
const fullMxc =
|
||||||
|
forLink || (isAnimated(event) ? "" : event.media_url)
|
||||||
|
|
||||||
|
utils.makePopup(
|
||||||
|
"Popups/ImageViewerPopup.qml",
|
||||||
|
{
|
||||||
|
thumbnailTitle: getThumbnailTitle(event),
|
||||||
|
thumbnailMxc: event.thumbnail_url,
|
||||||
|
thumbnailPath: eventList.thumbnailCachedPaths[event.id],
|
||||||
|
thumbnailCryptDict: JSON.parse(event.thumbnail_crypt_dict),
|
||||||
|
|
||||||
|
fullTitle: title,
|
||||||
|
fullMxc: fullMxc,
|
||||||
|
fullCryptDict: JSON.parse(event.media_crypt_dict),
|
||||||
|
|
||||||
|
overallSize: Qt.size(
|
||||||
|
event.media_width ||
|
||||||
|
event.thumbnail_width ||
|
||||||
|
implicitWidth || // XXX
|
||||||
|
800,
|
||||||
|
|
||||||
|
event.media_height ||
|
||||||
|
event.thumbnail_height ||
|
||||||
|
implicitHeight || // XXX
|
||||||
|
600,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
obj => {
|
||||||
|
obj.openExternallyRequested.connect(() =>
|
||||||
|
forLink ?
|
||||||
|
Qt.openUrlExternally(forLink) :
|
||||||
|
eventList.openMediaExternally(event)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLocalOrDownloadMedia(event, callback) {
|
||||||
|
print("Downloading " + event.media_url + " ...")
|
||||||
|
|
||||||
|
const args = [
|
||||||
|
event.media_url,
|
||||||
|
event.media_title,
|
||||||
|
JSON.parse(event.media_crypt_dict),
|
||||||
|
]
|
||||||
|
|
||||||
|
py.callCoro("media_cache.get_media", args, path => {
|
||||||
|
print("Done: " + path)
|
||||||
|
callback(path)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function openMediaExternally(event) {
|
||||||
|
eventList.getLocalOrDownloadMedia(event, Qt.openUrlExternally)
|
||||||
|
}
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
clip: true
|
clip: true
|
||||||
keyNavigationWraps: false
|
keyNavigationWraps: false
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
import QtQuick 2.12
|
import QtQuick 2.12
|
||||||
|
import "../../.."
|
||||||
import "../../../Base"
|
import "../../../Base"
|
||||||
|
|
||||||
HLoader {
|
HLoader {
|
||||||
|
@ -12,97 +13,31 @@ HLoader {
|
||||||
property string showDate: ""
|
property string showDate: ""
|
||||||
property string showLocalEcho: ""
|
property string showLocalEcho: ""
|
||||||
|
|
||||||
property string downloadedPath: ""
|
|
||||||
|
|
||||||
readonly property string title:
|
readonly property string title:
|
||||||
singleMediaInfo.media_title || utils.urlFileName(mediaUrl)
|
singleMediaInfo.media_title || utils.urlFileName(mediaUrl)
|
||||||
|
|
||||||
readonly property string thumbnailTitle:
|
readonly property string thumbnailTitle:
|
||||||
singleMediaInfo.media_title.replace(
|
eventList.getThumbnailTitle(singleMediaInfo)
|
||||||
/\.[^\.]+$/,
|
|
||||||
singleMediaInfo.thumbnail_mime === "image/jpeg" ? ".jpg" :
|
|
||||||
singleMediaInfo.thumbnail_mime === "image/png" ? ".png" :
|
|
||||||
singleMediaInfo.thumbnail_mime === "image/gif" ? ".gif" :
|
|
||||||
singleMediaInfo.thumbnail_mime === "image/tiff" ? ".tiff" :
|
|
||||||
singleMediaInfo.thumbnail_mime === "image/svg+xml" ? ".svg" :
|
|
||||||
singleMediaInfo.thumbnail_mime === "image/webp" ? ".webp" :
|
|
||||||
singleMediaInfo.thumbnail_mime === "image/bmp" ? ".bmp" :
|
|
||||||
".thumbnail"
|
|
||||||
) || utils.urlFileName(mediaUrl)
|
|
||||||
|
|
||||||
readonly property var imageExtensions: [
|
readonly property bool isMedia:
|
||||||
"bmp", "gif", "jpg", "jpeg", "png", "pbm", "pgm", "ppm", "xbm", "xpm",
|
eventList.getMediaType(singleMediaInfo) !== null
|
||||||
"tiff", "webp", "svg",
|
|
||||||
]
|
|
||||||
|
|
||||||
readonly property var videoExtensions: [
|
readonly property int type:
|
||||||
"3gp", "avi", "flv", "m4p", "m4v", "mkv", "mov", "mp4",
|
isMedia ?
|
||||||
"mpeg", "mpg", "ogv", "qt", "vob", "webm", "wmv", "yuv",
|
eventList.getMediaType(singleMediaInfo) :
|
||||||
]
|
utils.getLinkType(mediaUrl)
|
||||||
|
|
||||||
readonly property var audioExtensions: [
|
|
||||||
"pcm", "wav", "raw", "aiff", "flac", "m4a", "tta", "aac", "mp3",
|
|
||||||
"ogg", "oga", "opus",
|
|
||||||
]
|
|
||||||
|
|
||||||
readonly property int type: {
|
|
||||||
if (singleMediaInfo.event_type === "RoomAvatarEvent")
|
|
||||||
return EventDelegate.Media.Image
|
|
||||||
|
|
||||||
const mainType = singleMediaInfo.media_mime.split("/")[0].toLowerCase()
|
|
||||||
|
|
||||||
if (mainType === "image") return EventDelegate.Media.Image
|
|
||||||
if (mainType === "video") return EventDelegate.Media.Video
|
|
||||||
if (mainType === "audio") return EventDelegate.Media.Audio
|
|
||||||
|
|
||||||
const fileEvents = ["RoomMessageFile", "RoomEncryptedFile"]
|
|
||||||
|
|
||||||
if (fileEvents.includes(singleMediaInfo.event_type))
|
|
||||||
return EventDelegate.Media.File
|
|
||||||
|
|
||||||
// If this is a preview for a link in a normal message
|
|
||||||
const ext = utils.urlExtension(mediaUrl).toLowerCase()
|
|
||||||
|
|
||||||
if (imageExtensions.includes(ext)) return EventDelegate.Media.Image
|
|
||||||
if (videoExtensions.includes(ext)) return EventDelegate.Media.Video
|
|
||||||
if (audioExtensions.includes(ext)) return EventDelegate.Media.Audio
|
|
||||||
|
|
||||||
return EventDelegate.Media.Page
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property string thumbnailMxc: singleMediaInfo.thumbnail_url
|
readonly property string thumbnailMxc: singleMediaInfo.thumbnail_url
|
||||||
|
|
||||||
function download(callback) {
|
|
||||||
if (! loader.mediaUrl.startsWith("mxc://")) {
|
|
||||||
downloadedPath = loader.mediaUrl
|
|
||||||
callback(loader.mediaUrl)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! downloadedPath) print("Downloading " + loader.mediaUrl + " ...")
|
|
||||||
|
|
||||||
const args = [
|
|
||||||
loader.mediaUrl,
|
|
||||||
loader.title,
|
|
||||||
JSON.parse(loader.singleMediaInfo.media_crypt_dict)
|
|
||||||
]
|
|
||||||
|
|
||||||
py.callCoro("media_cache.get_media", args, path => {
|
|
||||||
if (! downloadedPath) print("Done: " + path)
|
|
||||||
downloadedPath = path
|
|
||||||
callback(path)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
visible: Boolean(item)
|
visible: Boolean(item)
|
||||||
x: eventContent.spacing
|
x: eventContent.spacing
|
||||||
|
|
||||||
onTypeChanged: {
|
onTypeChanged: {
|
||||||
if (type === EventDelegate.Media.Image) {
|
if (type === Utils.Media.Image) {
|
||||||
var file = "EventImage.qml"
|
var file = "EventImage.qml"
|
||||||
|
|
||||||
} else if (type !== EventDelegate.Media.Page) {
|
} else if (type !== Utils.Media.Page) {
|
||||||
var file = "EventFile.qml"
|
var file = "EventFile.qml"
|
||||||
|
|
||||||
} else { return }
|
} else { return }
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import QtQuick 2.12
|
import QtQuick 2.12
|
||||||
import QtQuick.Layouts 1.12
|
import QtQuick.Layouts 1.12
|
||||||
import QtAV 1.7
|
import QtAV 1.7
|
||||||
|
import "../../.."
|
||||||
import "../../../Base"
|
import "../../../Base"
|
||||||
import "../../../Base/MediaPlayer"
|
import "../../../Base/MediaPlayer"
|
||||||
|
|
||||||
|
@ -11,5 +12,5 @@ VideoPlayer {
|
||||||
|
|
||||||
onHoveredChanged:
|
onHoveredChanged:
|
||||||
eventDelegate.hoveredMediaTypeUrl =
|
eventDelegate.hoveredMediaTypeUrl =
|
||||||
hovered ? [EventDelegate.Media.Video, video.source] : []
|
hovered ? [Utils.Media.Video, video.source] : []
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,23 @@ import QtQuick 2.12
|
||||||
import CppUtils 0.1
|
import CppUtils 0.1
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
|
enum Media { Page, File, Image, Video, Audio }
|
||||||
|
|
||||||
|
readonly property var imageExtensions: [
|
||||||
|
"bmp", "gif", "jpg", "jpeg", "png", "pbm", "pgm", "ppm", "xbm", "xpm",
|
||||||
|
"tiff", "webp", "svg",
|
||||||
|
]
|
||||||
|
|
||||||
|
readonly property var videoExtensions: [
|
||||||
|
"3gp", "avi", "flv", "m4p", "m4v", "mkv", "mov", "mp4",
|
||||||
|
"mpeg", "mpg", "ogv", "qt", "vob", "webm", "wmv", "yuv",
|
||||||
|
]
|
||||||
|
|
||||||
|
readonly property var audioExtensions: [
|
||||||
|
"pcm", "wav", "raw", "aiff", "flac", "m4a", "tta", "aac", "mp3",
|
||||||
|
"ogg", "oga", "opus",
|
||||||
|
]
|
||||||
|
|
||||||
function makeObject(urlComponent, parent=null, properties={},
|
function makeObject(urlComponent, parent=null, properties={},
|
||||||
callback=null) {
|
callback=null) {
|
||||||
let comp = urlComponent
|
let comp = urlComponent
|
||||||
|
@ -456,6 +473,18 @@ QtObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getLinkType(url) {
|
||||||
|
const ext = urlExtension(url).toLowerCase()
|
||||||
|
|
||||||
|
return (
|
||||||
|
imageExtensions.includes(ext) ? Utils.Media.Image :
|
||||||
|
videoExtensions.includes(ext) ? Utils.Media.Video :
|
||||||
|
audioExtensions.includes(ext) ? Utils.Media.Audio :
|
||||||
|
Utils.Media.Page
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function sendFile(userId, roomId, path, onSuccess, onError) {
|
function sendFile(userId, roomId, path, onSuccess, onError) {
|
||||||
py.callClientCoro(
|
py.callClientCoro(
|
||||||
userId, "send_file", [roomId, path], onSuccess, onError,
|
userId, "send_file", [roomId, path], onSuccess, onError,
|
||||||
|
|
Loading…
Reference in New Issue
Block a user