mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-06-08 04:09:15 -04:00
a34fda984d
- Resolved random DMS API errors & QML Warnings
283 lines
9.1 KiB
QML
283 lines
9.1 KiB
QML
import QtQuick
|
|
import QtQuick.Effects
|
|
import qs.Common
|
|
import qs.Services
|
|
import qs.Widgets
|
|
|
|
Item {
|
|
id: thumbnail
|
|
readonly property var log: Log.scoped("ClipboardThumbnail")
|
|
|
|
required property var entry
|
|
required property string entryType
|
|
required property var modal
|
|
required property var listView
|
|
required property int itemIndex
|
|
property bool disposed: false
|
|
|
|
Image {
|
|
id: thumbnailImage
|
|
|
|
property bool isVisible: false
|
|
property string cachedImageData: ""
|
|
property bool loadQueued: false
|
|
property bool activeLoad: false
|
|
property bool completed: false
|
|
property int loadGeneration: 0
|
|
property var activeEntryId: null
|
|
property var activeRequest: null
|
|
property var currentEntryId: entry && entry.id !== undefined ? entry.id : null
|
|
property string currentEntryType: entryType
|
|
|
|
anchors.fill: parent
|
|
source: cachedImageData ? `data:image/png;base64,${cachedImageData}` : ""
|
|
fillMode: Image.PreserveAspectCrop
|
|
smooth: true
|
|
cache: false
|
|
visible: false
|
|
asynchronous: true
|
|
sourceSize.width: 128
|
|
sourceSize.height: 128
|
|
|
|
onCurrentEntryIdChanged: {
|
|
if (thumbnailImage.completed) {
|
|
thumbnailImage.resetForEntry();
|
|
}
|
|
}
|
|
|
|
onCurrentEntryTypeChanged: {
|
|
if (thumbnailImage.completed) {
|
|
thumbnailImage.resetForEntry();
|
|
}
|
|
}
|
|
|
|
function hasValidEntryId() {
|
|
return entry && entry.id !== undefined && entry.id !== null;
|
|
}
|
|
|
|
function releaseActiveLoad() {
|
|
if (!thumbnailImage.activeLoad) {
|
|
return;
|
|
}
|
|
thumbnailImage.activeLoad = false;
|
|
if (modal && modal.activeImageLoads > 0) {
|
|
modal.activeImageLoads--;
|
|
}
|
|
}
|
|
|
|
function finishLoad(request) {
|
|
thumbnailImage.loadQueued = false;
|
|
thumbnailImage.activeEntryId = null;
|
|
if (!request || thumbnailImage.activeRequest === request) {
|
|
thumbnailImage.activeRequest = null;
|
|
}
|
|
thumbnailImage.releaseActiveLoad();
|
|
}
|
|
|
|
function cancelLoad() {
|
|
if (thumbnailImage.activeRequest) {
|
|
thumbnailImage.activeRequest.cancelled = true;
|
|
thumbnailImage.activeRequest = null;
|
|
}
|
|
retryTimer.stop();
|
|
visibilityTimer.stop();
|
|
thumbnailImage.loadQueued = false;
|
|
thumbnailImage.activeEntryId = null;
|
|
thumbnailImage.releaseActiveLoad();
|
|
}
|
|
|
|
function resetForEntry() {
|
|
thumbnailImage.loadGeneration++;
|
|
thumbnailImage.cachedImageData = "";
|
|
thumbnailImage.isVisible = false;
|
|
thumbnailImage.cancelLoad();
|
|
Qt.callLater(function () {
|
|
if (thumbnail.disposed) {
|
|
return;
|
|
}
|
|
thumbnailImage.checkVisibility();
|
|
});
|
|
}
|
|
|
|
function startLoad() {
|
|
if (!modal) {
|
|
thumbnailImage.loadQueued = false;
|
|
return;
|
|
}
|
|
modal.activeImageLoads++;
|
|
thumbnailImage.activeLoad = true;
|
|
thumbnailImage.loadImage();
|
|
}
|
|
|
|
function tryLoadImage() {
|
|
if (thumbnail.disposed || thumbnailImage.loadQueued || entryType !== "image" || thumbnailImage.cachedImageData || !thumbnailImage.hasValidEntryId()) {
|
|
return;
|
|
}
|
|
thumbnailImage.loadQueued = true;
|
|
if (modal && modal.activeImageLoads < modal.maxConcurrentLoads) {
|
|
thumbnailImage.startLoad();
|
|
} else {
|
|
retryTimer.restart();
|
|
}
|
|
}
|
|
|
|
function loadImage() {
|
|
if (!thumbnailImage.hasValidEntryId()) {
|
|
thumbnailImage.finishLoad();
|
|
return;
|
|
}
|
|
const requestedId = entry.id;
|
|
const generation = thumbnailImage.loadGeneration;
|
|
const request = {
|
|
"cancelled": false
|
|
};
|
|
thumbnailImage.activeEntryId = requestedId;
|
|
thumbnailImage.activeRequest = request;
|
|
DMSService.sendRequest("clipboard.getEntry", {
|
|
"id": requestedId
|
|
}, function (response) {
|
|
if (request.cancelled) {
|
|
return;
|
|
}
|
|
if (thumbnail.disposed || generation !== thumbnailImage.loadGeneration || thumbnailImage.activeRequest !== request || thumbnailImage.activeEntryId !== requestedId) {
|
|
return;
|
|
}
|
|
thumbnailImage.finishLoad(request);
|
|
if (!entry || entry.id !== requestedId || entryType !== "image") {
|
|
return;
|
|
}
|
|
if (response.error) {
|
|
log.warn("Failed to load image:", requestedId);
|
|
return;
|
|
}
|
|
if (!response.result) {
|
|
ClipboardService.refresh();
|
|
return;
|
|
}
|
|
const data = response.result?.data;
|
|
if (data) {
|
|
thumbnailImage.cachedImageData = data;
|
|
}
|
|
});
|
|
}
|
|
|
|
Timer {
|
|
id: retryTimer
|
|
interval: ClipboardConstants.retryInterval
|
|
onTriggered: {
|
|
if (!thumbnailImage.loadQueued) {
|
|
return;
|
|
}
|
|
if (modal && modal.activeImageLoads < modal.maxConcurrentLoads) {
|
|
thumbnailImage.startLoad();
|
|
} else {
|
|
retryTimer.restart();
|
|
}
|
|
}
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
thumbnailImage.completed = true;
|
|
if (entryType !== "image" || listView.height <= 0 || !thumbnailImage.hasValidEntryId()) {
|
|
return;
|
|
}
|
|
|
|
const itemY = itemIndex * (ClipboardConstants.itemHeight + listView.spacing);
|
|
const viewTop = listView.contentY;
|
|
const viewBottom = viewTop + listView.height;
|
|
isVisible = (itemY + ClipboardConstants.itemHeight >= viewTop && itemY <= viewBottom);
|
|
|
|
if (isVisible) {
|
|
tryLoadImage();
|
|
}
|
|
}
|
|
|
|
Component.onDestruction: {
|
|
thumbnail.disposed = true;
|
|
thumbnailImage.cancelLoad();
|
|
}
|
|
|
|
Timer {
|
|
id: visibilityTimer
|
|
interval: 100
|
|
onTriggered: thumbnailImage.checkVisibility()
|
|
}
|
|
|
|
function checkVisibility() {
|
|
if (thumbnail.disposed || entryType !== "image" || listView.height <= 0 || isVisible || !thumbnailImage.hasValidEntryId()) {
|
|
return;
|
|
}
|
|
const itemY = itemIndex * (ClipboardConstants.itemHeight + listView.spacing);
|
|
const viewTop = listView.contentY - ClipboardConstants.viewportBuffer;
|
|
const viewBottom = viewTop + listView.height + ClipboardConstants.extendedBuffer;
|
|
const nowVisible = (itemY + ClipboardConstants.itemHeight >= viewTop && itemY <= viewBottom);
|
|
if (nowVisible) {
|
|
isVisible = true;
|
|
tryLoadImage();
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: listView
|
|
|
|
function onContentYChanged() {
|
|
if (thumbnailImage.isVisible || entryType !== "image") {
|
|
return;
|
|
}
|
|
visibilityTimer.restart();
|
|
}
|
|
|
|
function onHeightChanged() {
|
|
if (thumbnailImage.isVisible || entryType !== "image") {
|
|
return;
|
|
}
|
|
visibilityTimer.restart();
|
|
}
|
|
}
|
|
}
|
|
|
|
MultiEffect {
|
|
anchors.fill: parent
|
|
anchors.margins: 2
|
|
source: thumbnailImage
|
|
maskEnabled: true
|
|
maskSource: clipboardRoundedRectangularMask
|
|
visible: entryType === "image" && thumbnailImage.status === Image.Ready && thumbnailImage.source != ""
|
|
maskThresholdMin: 0.5
|
|
maskSpreadAtMin: 1
|
|
}
|
|
|
|
Item {
|
|
id: clipboardRoundedRectangularMask
|
|
width: ClipboardConstants.thumbnailSize
|
|
height: ClipboardConstants.itemHeight - 4
|
|
layer.enabled: true
|
|
layer.smooth: true
|
|
visible: false
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
radius: Theme.cornerRadius / 2 // Thumbnail corner radius is divided by 2 so it doesnt look weird on large corner radius (eg: 32px)
|
|
color: "black"
|
|
antialiasing: true
|
|
}
|
|
}
|
|
|
|
DankIcon {
|
|
visible: !(entryType === "image" && thumbnailImage.status === Image.Ready && thumbnailImage.source != "")
|
|
name: {
|
|
switch (entryType) {
|
|
case "image":
|
|
return "image";
|
|
case "long_text":
|
|
return "subject";
|
|
default:
|
|
return "content_copy";
|
|
}
|
|
}
|
|
size: Theme.iconSize
|
|
color: Theme.primary
|
|
anchors.centerIn: parent
|
|
}
|
|
}
|