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)
|
||||
- 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
|
||||
- Support `m.file` thumbnails
|
||||
|
|
|
@ -47,13 +47,37 @@ class MediaCache:
|
|||
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(
|
||||
self,
|
||||
mxc: str,
|
||||
title: str,
|
||||
crypt_dict: CryptDict = None,
|
||||
) -> 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()
|
||||
|
||||
|
@ -66,7 +90,7 @@ class MediaCache:
|
|||
height: int,
|
||||
crypt_dict: CryptDict = None,
|
||||
) -> Path:
|
||||
"""Return a `Thumbnail` object. Method intended for QML convenience."""
|
||||
"""Return `Thumbnail.get()`'s result. Intended for QML."""
|
||||
|
||||
thumb = Thumbnail(
|
||||
# QML sometimes pass float sizes, which matrix API doesn't like.
|
||||
|
@ -116,13 +140,13 @@ class Media:
|
|||
|
||||
async with ACCESS_LOCKS[self.mxc]:
|
||||
try:
|
||||
return await self._get_local_existing_file()
|
||||
return await self.get_local()
|
||||
except FileNotFoundError:
|
||||
return await self.create()
|
||||
|
||||
|
||||
async def _get_local_existing_file(self) -> Path:
|
||||
"""Return the cached file's path."""
|
||||
async def get_local(self) -> Path:
|
||||
"""Return a cached local existing path for this media or raise."""
|
||||
|
||||
if not self.local_path.exists():
|
||||
raise FileNotFoundError()
|
||||
|
@ -286,7 +310,7 @@ class Thumbnail(Media):
|
|||
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`.
|
||||
|
||||
If we have a bigger size thumbnail downloaded than the `wanted_size`
|
||||
|
|
|
@ -13,6 +13,6 @@ AudioPlayer {
|
|||
HoverHandler {
|
||||
onHoveredChanged:
|
||||
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 "../../.."
|
||||
import "../../../Base"
|
||||
import "../../../PythonBridge"
|
||||
|
||||
HColumnLayout {
|
||||
id: eventDelegate
|
||||
|
||||
property var hoveredMediaTypeUrl: []
|
||||
property var hoveredMediaTypeUrl: [] // [] or [mediaType, url, title]
|
||||
|
||||
property var fetchProfilesFuture: null
|
||||
|
||||
|
@ -41,7 +42,7 @@ HColumnLayout {
|
|||
combine
|
||||
|
||||
readonly property int cursorShape:
|
||||
eventContent.hoveredLink || hoveredMediaTypeUrl.length > 0 ?
|
||||
eventContent.hoveredLink || hoveredMediaTypeUrl.length === 3 ?
|
||||
Qt.PointingHandCursor :
|
||||
|
||||
eventContent.hoveredSelectable ? Qt.IBeamCursor :
|
||||
|
@ -140,8 +141,25 @@ HColumnLayout {
|
|||
|
||||
property var media: []
|
||||
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 {
|
||||
icon.name: "toggle-select-message"
|
||||
|
@ -163,14 +181,21 @@ HColumnLayout {
|
|||
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 {
|
||||
id: copyMedia
|
||||
icon.name: "copy-link"
|
||||
text:
|
||||
contextMenu.media.length < 1 ? "" :
|
||||
|
||||
contextMenu.media[0] === Utils.Media.Page ?
|
||||
qsTr("Copy page address") :
|
||||
contextMenu.media.length === 0 || isEncryptedMedia ?
|
||||
"" :
|
||||
|
||||
contextMenu.media[0] === Utils.Media.File ?
|
||||
qsTr("Copy file address") :
|
||||
|
@ -181,17 +206,13 @@ HColumnLayout {
|
|||
contextMenu.media[0] === Utils.Media.Video ?
|
||||
qsTr("Copy video address") :
|
||||
|
||||
contextMenu.media[0] === Utils.Media.Audio ?
|
||||
qsTr("Copy audio address") :
|
||||
|
||||
qsTr("Copy media address")
|
||||
qsTr("Copy audio 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)
|
||||
|
@ -201,12 +222,8 @@ HColumnLayout {
|
|||
HMenuItem {
|
||||
icon.name: "copy-text"
|
||||
text:
|
||||
eventList.selectedCount ?
|
||||
qsTr("Copy selection") :
|
||||
|
||||
copyMedia.visible ?
|
||||
qsTr("Copy filename") :
|
||||
|
||||
eventList.selectedCount ? qsTr("Copy selection") :
|
||||
contextMenu.media.length > 0 ? qsTr("Copy filename") :
|
||||
qsTr("Copy text")
|
||||
|
||||
onTriggered: {
|
||||
|
|
|
@ -60,12 +60,8 @@ HTile {
|
|||
return
|
||||
}
|
||||
|
||||
eventDelegate.hoveredMediaTypeUrl = [
|
||||
Utils.Media.File,
|
||||
// XXX
|
||||
// loader.downloadedPath.replace(/^file:\/\//, "") ||
|
||||
loader.mediaUrl
|
||||
]
|
||||
eventDelegate.hoveredMediaTypeUrl =
|
||||
[Utils.Media.File, loader.mediaUrl, loader.title]
|
||||
}
|
||||
|
||||
Binding on backgroundColor {
|
||||
|
|
|
@ -99,12 +99,8 @@ HMxcImage {
|
|||
return
|
||||
}
|
||||
|
||||
eventDelegate.hoveredMediaTypeUrl = [
|
||||
Utils.Media.Image,
|
||||
// XXX
|
||||
// loader.downloadedPath.replace(/^file:\/\//, "") ||
|
||||
loader.mediaUrl
|
||||
]
|
||||
eventDelegate.hoveredMediaTypeUrl =
|
||||
[Utils.Media.Image, loader.mediaUrl, loader.title]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,8 @@ HLoader {
|
|||
eventList.getMediaType(singleMediaInfo) :
|
||||
utils.getLinkType(mediaUrl)
|
||||
|
||||
readonly property string cachedLocalPath: ""
|
||||
|
||||
readonly property string thumbnailMxc: singleMediaInfo.thumbnail_url
|
||||
|
||||
|
||||
|
|
|
@ -12,5 +12,5 @@ VideoPlayer {
|
|||
|
||||
onHoveredChanged:
|
||||
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