Real "copy URL" & "copy path" context menu entries
Replace the poorly implemented 2-in-1 "copy address" media event menu option with: - Copy <mediaType> address: visible for non-encrypted media, always copies the http URL - Copy local path: always visible for already downloaded media, even if they were downloaded before mirage was started
This commit is contained in:
parent
37579fc664
commit
30ce271ebc
5
TODO.md
5
TODO.md
|
@ -167,11 +167,6 @@
|
||||||
|
|
||||||
- Add upload keybindings (close failed upload, pause, resume)
|
- Add upload keybindings (close failed upload, pause, resume)
|
||||||
- Handle errors when setting an avatar
|
- Handle errors when setting an avatar
|
||||||
- Show proper progress ring for mxc thumbnails loading
|
|
||||||
|
|
||||||
- Sentinel function to report local file paths for already downloaded media,
|
|
||||||
without having to click and try downloading first
|
|
||||||
- EventFile "Save as..." context menu entry
|
|
||||||
|
|
||||||
- Show a reason or HTTP error code for thumbnails that fail to load
|
- Show a reason or HTTP error code for thumbnails that fail to load
|
||||||
- Support `m.file` thumbnails
|
- Support `m.file` thumbnails
|
||||||
|
|
|
@ -47,13 +47,37 @@ class MediaCache:
|
||||||
self.downloads_dir.mkdir(parents=True, exist_ok=True)
|
self.downloads_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_local_media(self, mxc: str, title: str) -> Optional[Path]:
|
||||||
|
"""Return `Media.get_local()`'s result for QML."""
|
||||||
|
|
||||||
|
media = Media(self, mxc, title, None)
|
||||||
|
|
||||||
|
try:
|
||||||
|
return await media.get_local()
|
||||||
|
except FileNotFoundError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
async def get_local_thumbnail(
|
||||||
|
self, mxc: str, title: str, width: int, height: int,
|
||||||
|
) -> Optional[Path]:
|
||||||
|
"""Return `Thumbnail.get_local()`'s result for QML."""
|
||||||
|
|
||||||
|
th = Thumbnail(self, mxc, title, None, (round(width), round(height)))
|
||||||
|
|
||||||
|
try:
|
||||||
|
return await th.get_local()
|
||||||
|
except FileNotFoundError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
async def get_media(
|
async def get_media(
|
||||||
self,
|
self,
|
||||||
mxc: str,
|
mxc: str,
|
||||||
title: str,
|
title: str,
|
||||||
crypt_dict: CryptDict = None,
|
crypt_dict: CryptDict = None,
|
||||||
) -> Path:
|
) -> Path:
|
||||||
"""Return a `Media` object. Method intended for QML convenience."""
|
"""Return `Media.get()`'s result. Intended for QML."""
|
||||||
|
|
||||||
return await Media(self, mxc, title, crypt_dict).get()
|
return await Media(self, mxc, title, crypt_dict).get()
|
||||||
|
|
||||||
|
@ -66,7 +90,7 @@ class MediaCache:
|
||||||
height: int,
|
height: int,
|
||||||
crypt_dict: CryptDict = None,
|
crypt_dict: CryptDict = None,
|
||||||
) -> Path:
|
) -> Path:
|
||||||
"""Return a `Thumbnail` object. Method intended for QML convenience."""
|
"""Return `Thumbnail.get()`'s result. Intended for QML."""
|
||||||
|
|
||||||
thumb = Thumbnail(
|
thumb = Thumbnail(
|
||||||
# QML sometimes pass float sizes, which matrix API doesn't like.
|
# QML sometimes pass float sizes, which matrix API doesn't like.
|
||||||
|
@ -116,13 +140,13 @@ class Media:
|
||||||
|
|
||||||
async with ACCESS_LOCKS[self.mxc]:
|
async with ACCESS_LOCKS[self.mxc]:
|
||||||
try:
|
try:
|
||||||
return await self._get_local_existing_file()
|
return await self.get_local()
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
return await self.create()
|
return await self.create()
|
||||||
|
|
||||||
|
|
||||||
async def _get_local_existing_file(self) -> Path:
|
async def get_local(self) -> Path:
|
||||||
"""Return the cached file's path."""
|
"""Return a cached local existing path for this media or raise."""
|
||||||
|
|
||||||
if not self.local_path.exists():
|
if not self.local_path.exists():
|
||||||
raise FileNotFoundError()
|
raise FileNotFoundError()
|
||||||
|
@ -286,7 +310,7 @@ class Thumbnail(Media):
|
||||||
return self.cache.thumbs_dir / parsed.netloc / size_dir / filename
|
return self.cache.thumbs_dir / parsed.netloc / size_dir / filename
|
||||||
|
|
||||||
|
|
||||||
async def _get_local_existing_file(self) -> Path:
|
async def get_local(self) -> Path:
|
||||||
"""Return an existing thumbnail path or raise `FileNotFoundError`.
|
"""Return an existing thumbnail path or raise `FileNotFoundError`.
|
||||||
|
|
||||||
If we have a bigger size thumbnail downloaded than the `wanted_size`
|
If we have a bigger size thumbnail downloaded than the `wanted_size`
|
||||||
|
|
|
@ -13,6 +13,6 @@ AudioPlayer {
|
||||||
HoverHandler {
|
HoverHandler {
|
||||||
onHoveredChanged:
|
onHoveredChanged:
|
||||||
eventDelegate.hoveredMediaTypeUrl =
|
eventDelegate.hoveredMediaTypeUrl =
|
||||||
hovered ? [Utils.Media.Audio, audio.source] : []
|
hovered ? [Utils.Media.Audio, audio.source, loader.title] : []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,11 +5,12 @@ import QtQuick.Layouts 1.12
|
||||||
import Clipboard 0.1
|
import Clipboard 0.1
|
||||||
import "../../.."
|
import "../../.."
|
||||||
import "../../../Base"
|
import "../../../Base"
|
||||||
|
import "../../../PythonBridge"
|
||||||
|
|
||||||
HColumnLayout {
|
HColumnLayout {
|
||||||
id: eventDelegate
|
id: eventDelegate
|
||||||
|
|
||||||
property var hoveredMediaTypeUrl: []
|
property var hoveredMediaTypeUrl: [] // [] or [mediaType, url, title]
|
||||||
|
|
||||||
property var fetchProfilesFuture: null
|
property var fetchProfilesFuture: null
|
||||||
|
|
||||||
|
@ -41,7 +42,7 @@ HColumnLayout {
|
||||||
combine
|
combine
|
||||||
|
|
||||||
readonly property int cursorShape:
|
readonly property int cursorShape:
|
||||||
eventContent.hoveredLink || hoveredMediaTypeUrl.length > 0 ?
|
eventContent.hoveredLink || hoveredMediaTypeUrl.length === 3 ?
|
||||||
Qt.PointingHandCursor :
|
Qt.PointingHandCursor :
|
||||||
|
|
||||||
eventContent.hoveredSelectable ? Qt.IBeamCursor :
|
eventContent.hoveredSelectable ? Qt.IBeamCursor :
|
||||||
|
@ -140,8 +141,25 @@ HColumnLayout {
|
||||||
|
|
||||||
property var media: []
|
property var media: []
|
||||||
property string link: ""
|
property string link: ""
|
||||||
|
property var localPath: null
|
||||||
|
property Future getLocalFuture: null
|
||||||
|
|
||||||
onClosed: { media = []; link = "" }
|
readonly property bool isEncryptedMedia:
|
||||||
|
Object.keys(JSON.parse(model.media_crypt_dict)).length > 0
|
||||||
|
|
||||||
|
onClosed: {
|
||||||
|
if (getLocalFuture) getLocalFuture.cancel()
|
||||||
|
media = []
|
||||||
|
link = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
onOpened: if (media.length === 3 && media[1].startsWith("mxc://")) {
|
||||||
|
getLocalFuture = py.callCoro(
|
||||||
|
"media_cache.get_local_media",
|
||||||
|
[media[1], media[2]],
|
||||||
|
path => { localPath = path; getLocalFuture = null },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
HMenuItem {
|
HMenuItem {
|
||||||
icon.name: "toggle-select-message"
|
icon.name: "toggle-select-message"
|
||||||
|
@ -163,14 +181,21 @@ HColumnLayout {
|
||||||
onTriggered: eventList.checkFromLastToHere(model.index)
|
onTriggered: eventList.checkFromLastToHere(model.index)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HMenuItem {
|
||||||
|
icon.name: "copy-local-path"
|
||||||
|
text: qsTr("Copy local path")
|
||||||
|
visible: Boolean(contextMenu.localPath)
|
||||||
|
onTriggered:
|
||||||
|
Clipboard.text =
|
||||||
|
contextMenu.localPath.replace(/^file:\/\//, "")
|
||||||
|
}
|
||||||
|
|
||||||
HMenuItem {
|
HMenuItem {
|
||||||
id: copyMedia
|
id: copyMedia
|
||||||
icon.name: "copy-link"
|
icon.name: "copy-link"
|
||||||
text:
|
text:
|
||||||
contextMenu.media.length < 1 ? "" :
|
contextMenu.media.length === 0 || isEncryptedMedia ?
|
||||||
|
"" :
|
||||||
contextMenu.media[0] === Utils.Media.Page ?
|
|
||||||
qsTr("Copy page address") :
|
|
||||||
|
|
||||||
contextMenu.media[0] === Utils.Media.File ?
|
contextMenu.media[0] === Utils.Media.File ?
|
||||||
qsTr("Copy file address") :
|
qsTr("Copy file address") :
|
||||||
|
@ -181,17 +206,13 @@ HColumnLayout {
|
||||||
contextMenu.media[0] === Utils.Media.Video ?
|
contextMenu.media[0] === Utils.Media.Video ?
|
||||||
qsTr("Copy video address") :
|
qsTr("Copy video address") :
|
||||||
|
|
||||||
contextMenu.media[0] === Utils.Media.Audio ?
|
qsTr("Copy audio address")
|
||||||
qsTr("Copy audio address") :
|
|
||||||
|
|
||||||
qsTr("Copy media address")
|
|
||||||
|
|
||||||
visible: Boolean(text)
|
visible: Boolean(text)
|
||||||
onTriggered: Clipboard.text = contextMenu.media[1]
|
onTriggered: Clipboard.text = contextMenu.media[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
HMenuItem {
|
HMenuItem {
|
||||||
id: copyLink
|
|
||||||
icon.name: "copy-link"
|
icon.name: "copy-link"
|
||||||
text: qsTr("Copy link address")
|
text: qsTr("Copy link address")
|
||||||
visible: Boolean(contextMenu.link)
|
visible: Boolean(contextMenu.link)
|
||||||
|
@ -201,12 +222,8 @@ HColumnLayout {
|
||||||
HMenuItem {
|
HMenuItem {
|
||||||
icon.name: "copy-text"
|
icon.name: "copy-text"
|
||||||
text:
|
text:
|
||||||
eventList.selectedCount ?
|
eventList.selectedCount ? qsTr("Copy selection") :
|
||||||
qsTr("Copy selection") :
|
contextMenu.media.length > 0 ? qsTr("Copy filename") :
|
||||||
|
|
||||||
copyMedia.visible ?
|
|
||||||
qsTr("Copy filename") :
|
|
||||||
|
|
||||||
qsTr("Copy text")
|
qsTr("Copy text")
|
||||||
|
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
|
|
|
@ -60,12 +60,8 @@ HTile {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
eventDelegate.hoveredMediaTypeUrl = [
|
eventDelegate.hoveredMediaTypeUrl =
|
||||||
Utils.Media.File,
|
[Utils.Media.File, loader.mediaUrl, loader.title]
|
||||||
// XXX
|
|
||||||
// loader.downloadedPath.replace(/^file:\/\//, "") ||
|
|
||||||
loader.mediaUrl
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Binding on backgroundColor {
|
Binding on backgroundColor {
|
||||||
|
|
|
@ -99,12 +99,8 @@ HMxcImage {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
eventDelegate.hoveredMediaTypeUrl = [
|
eventDelegate.hoveredMediaTypeUrl =
|
||||||
Utils.Media.Image,
|
[Utils.Media.Image, loader.mediaUrl, loader.title]
|
||||||
// XXX
|
|
||||||
// loader.downloadedPath.replace(/^file:\/\//, "") ||
|
|
||||||
loader.mediaUrl
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,8 @@ HLoader {
|
||||||
eventList.getMediaType(singleMediaInfo) :
|
eventList.getMediaType(singleMediaInfo) :
|
||||||
utils.getLinkType(mediaUrl)
|
utils.getLinkType(mediaUrl)
|
||||||
|
|
||||||
|
readonly property string cachedLocalPath: ""
|
||||||
|
|
||||||
readonly property string thumbnailMxc: singleMediaInfo.thumbnail_url
|
readonly property string thumbnailMxc: singleMediaInfo.thumbnail_url
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -12,5 +12,5 @@ VideoPlayer {
|
||||||
|
|
||||||
onHoveredChanged:
|
onHoveredChanged:
|
||||||
eventDelegate.hoveredMediaTypeUrl =
|
eventDelegate.hoveredMediaTypeUrl =
|
||||||
hovered ? [Utils.Media.Video, video.source] : []
|
hovered ? [Utils.Media.Video, video.source, loader.title] : []
|
||||||
}
|
}
|
||||||
|
|
15
src/icons/thin/copy-local-path.svg
Normal file
15
src/icons/thin/copy-local-path.svg
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="matrix(1.4683867 0 0 1.4683867 -6.00608 -7.949919)">
|
||||||
|
<path d="m14.089135 6.966652h1.915602v1.291619h-1.915602z" fill="none"/>
|
||||||
|
<path d="m15.046936 7.612462h2.465696v1.08719h-2.465696z" fill="none"/>
|
||||||
|
<path d="m4.090259 7.128954h16.344467v1.016837h-16.344467z"/>
|
||||||
|
<path d="m4.090259 19.162901h16.344467v.97476h-16.344467z"/>
|
||||||
|
<path d="m7.128954-5.107096h13.008707v1.016837h-13.008707z" transform="rotate(90)"/>
|
||||||
|
<path d="m7.128954-20.434727h13.008707v1.016837h-13.008707z" transform="rotate(90)"/>
|
||||||
|
<g transform="translate(.106056 -.00001)">
|
||||||
|
<path d="m12.465338 7.077473h1.539704v7.582061h-1.539704z" stroke-width=".119289" transform="matrix(.96607107 .25827638 -.35321141 .93554353 0 0)"/>
|
||||||
|
<circle cx="16.285589" cy="16.168999" r="1.162796"/>
|
||||||
|
<circle cx="12.156437" cy="16.168999" r="1.162796"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1013 B |
Loading…
Reference in New Issue
Block a user