mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-24 21:42:51 -05:00
clipboard: introduce native clipboard, clip-persist, clip-storage functionality
This commit is contained in:
@@ -1,14 +1,13 @@
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modals.Clipboard
|
||||
|
||||
Item {
|
||||
id: thumbnail
|
||||
|
||||
required property string entryData
|
||||
required property var entry
|
||||
required property string entryType
|
||||
required property var modal
|
||||
required property var listView
|
||||
@@ -17,13 +16,12 @@ Item {
|
||||
Image {
|
||||
id: thumbnailImage
|
||||
|
||||
property string entryId: entryData.split('\t')[0]
|
||||
property bool isVisible: false
|
||||
property string cachedImageData: ""
|
||||
property bool loadQueued: false
|
||||
|
||||
anchors.fill: parent
|
||||
source: ""
|
||||
source: cachedImageData ? `data:image/png;base64,${cachedImageData}` : ""
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
smooth: true
|
||||
cache: false
|
||||
@@ -32,53 +30,66 @@ Item {
|
||||
sourceSize.width: 128
|
||||
sourceSize.height: 128
|
||||
|
||||
onCachedImageDataChanged: {
|
||||
if (cachedImageData) {
|
||||
source = ""
|
||||
source = `data:image/png;base64,${cachedImageData}`
|
||||
function tryLoadImage() {
|
||||
if (loadQueued || entryType !== "image" || cachedImageData) {
|
||||
return;
|
||||
}
|
||||
loadQueued = true;
|
||||
if (modal.activeImageLoads < modal.maxConcurrentLoads) {
|
||||
modal.activeImageLoads++;
|
||||
loadImage();
|
||||
} else {
|
||||
retryTimer.restart();
|
||||
}
|
||||
}
|
||||
|
||||
function tryLoadImage() {
|
||||
if (!loadQueued && entryType === "image" && !cachedImageData) {
|
||||
loadQueued = true
|
||||
if (modal.activeImageLoads < modal.maxConcurrentLoads) {
|
||||
modal.activeImageLoads++
|
||||
imageLoader.running = true
|
||||
} else {
|
||||
retryTimer.restart()
|
||||
function loadImage() {
|
||||
DMSService.sendRequest("clipboard.getEntry", {
|
||||
"id": entry.id
|
||||
}, function (response) {
|
||||
loadQueued = false;
|
||||
if (modal.activeImageLoads > 0) {
|
||||
modal.activeImageLoads--;
|
||||
}
|
||||
}
|
||||
if (response.error) {
|
||||
console.warn("ClipboardThumbnail: Failed to load image:", entry.id);
|
||||
return;
|
||||
}
|
||||
const data = response.result?.data;
|
||||
if (data) {
|
||||
cachedImageData = data;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: retryTimer
|
||||
interval: ClipboardConstants.retryInterval
|
||||
onTriggered: {
|
||||
if (thumbnailImage.loadQueued && !imageLoader.running) {
|
||||
if (modal.activeImageLoads < modal.maxConcurrentLoads) {
|
||||
modal.activeImageLoads++
|
||||
imageLoader.running = true
|
||||
} else {
|
||||
retryTimer.restart()
|
||||
}
|
||||
if (!thumbnailImage.loadQueued) {
|
||||
return;
|
||||
}
|
||||
if (modal.activeImageLoads < modal.maxConcurrentLoads) {
|
||||
modal.activeImageLoads++;
|
||||
thumbnailImage.loadImage();
|
||||
} else {
|
||||
retryTimer.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (entryType !== "image") {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if item is visible on screen initially
|
||||
const itemY = itemIndex * (ClipboardConstants.itemHeight + listView.spacing)
|
||||
const viewTop = listView.contentY
|
||||
const viewBottom = viewTop + listView.height
|
||||
isVisible = (itemY + ClipboardConstants.itemHeight >= viewTop && itemY <= viewBottom)
|
||||
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()
|
||||
tryLoadImage();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,48 +97,22 @@ Item {
|
||||
target: listView
|
||||
function onContentYChanged() {
|
||||
if (entryType !== "image") {
|
||||
return
|
||||
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)
|
||||
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 && !thumbnailImage.isVisible) {
|
||||
thumbnailImage.isVisible = true
|
||||
thumbnailImage.tryLoadImage()
|
||||
thumbnailImage.isVisible = true;
|
||||
thumbnailImage.tryLoadImage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: imageLoader
|
||||
running: false
|
||||
command: ["sh", "-c", `cliphist decode ${thumbnailImage.entryId} | base64 -w 0`]
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const imageData = text.trim()
|
||||
if (imageData && imageData.length > 0) {
|
||||
thumbnailImage.cachedImageData = imageData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onExited: exitCode => {
|
||||
thumbnailImage.loadQueued = false
|
||||
if (modal.activeImageLoads > 0) {
|
||||
modal.activeImageLoads--
|
||||
}
|
||||
if (exitCode !== 0) {
|
||||
console.warn("Failed to load clipboard image:", thumbnailImage.entryId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rounded mask effect for images
|
||||
MultiEffect {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 2
|
||||
@@ -155,17 +140,17 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback icon
|
||||
DankIcon {
|
||||
visible: !(entryType === "image" && thumbnailImage.status === Image.Ready && thumbnailImage.source != "")
|
||||
name: {
|
||||
if (entryType === "image") {
|
||||
return "image"
|
||||
switch (entryType) {
|
||||
case "image":
|
||||
return "image";
|
||||
case "long_text":
|
||||
return "subject";
|
||||
default:
|
||||
return "content_copy";
|
||||
}
|
||||
if (entryType === "long_text") {
|
||||
return "subject"
|
||||
}
|
||||
return "content_copy"
|
||||
}
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
|
||||
Reference in New Issue
Block a user