mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-31 08:52:49 -05:00
clipboard: add popout variant
This commit is contained in:
@@ -2,8 +2,8 @@ pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
import qs.Modals.Clipboard
|
||||
import qs.Modals.Common
|
||||
import qs.Services
|
||||
|
||||
@@ -17,86 +17,35 @@ DankModal {
|
||||
active: clipboardHistoryModal.useHyprlandFocusGrab && clipboardHistoryModal.shouldHaveFocus
|
||||
}
|
||||
|
||||
property int totalCount: 0
|
||||
property var clipboardEntries: []
|
||||
property var pinnedEntries: []
|
||||
property int pinnedCount: 0
|
||||
property string searchText: ""
|
||||
property int selectedIndex: 0
|
||||
property bool keyboardNavigationActive: false
|
||||
property string activeTab: "recents"
|
||||
property bool showKeyboardHints: false
|
||||
property Component clipboardContent
|
||||
property int activeImageLoads: 0
|
||||
readonly property int maxConcurrentLoads: 3
|
||||
readonly property bool clipboardAvailable: DMSService.isConnected && (DMSService.capabilities.length === 0 || DMSService.capabilities.includes("clipboard"))
|
||||
readonly property bool wtypeAvailable: SessionService.wtypeAvailable
|
||||
|
||||
Process {
|
||||
id: wtypeProcess
|
||||
command: ["wtype", "-M", "ctrl", "-P", "v", "-p", "v", "-m", "ctrl"]
|
||||
running: false
|
||||
}
|
||||
readonly property bool clipboardAvailable: ClipboardService.clipboardAvailable
|
||||
readonly property bool wtypeAvailable: ClipboardService.wtypeAvailable
|
||||
readonly property int totalCount: ClipboardService.totalCount
|
||||
readonly property var clipboardEntries: ClipboardService.clipboardEntries
|
||||
readonly property var pinnedEntries: ClipboardService.pinnedEntries
|
||||
readonly property int pinnedCount: ClipboardService.pinnedCount
|
||||
readonly property var unpinnedEntries: ClipboardService.unpinnedEntries
|
||||
readonly property int selectedIndex: ClipboardService.selectedIndex
|
||||
readonly property bool keyboardNavigationActive: ClipboardService.keyboardNavigationActive
|
||||
property string searchText: ClipboardService.searchText
|
||||
onSearchTextChanged: ClipboardService.searchText = searchText
|
||||
|
||||
Timer {
|
||||
id: pasteTimer
|
||||
interval: 200
|
||||
repeat: false
|
||||
onTriggered: wtypeProcess.running = true
|
||||
}
|
||||
|
||||
function pasteSelected() {
|
||||
if (!keyboardNavigationActive || clipboardEntries.length === 0 || selectedIndex < 0 || selectedIndex >= clipboardEntries.length) {
|
||||
return;
|
||||
}
|
||||
if (!wtypeAvailable) {
|
||||
ToastService.showError(I18n.tr("wtype not available - install wtype for paste support"));
|
||||
return;
|
||||
}
|
||||
const entry = clipboardEntries[selectedIndex];
|
||||
DMSService.sendRequest("clipboard.copyEntry", {
|
||||
"id": entry.id
|
||||
}, function (response) {
|
||||
if (response.error) {
|
||||
ToastService.showError(I18n.tr("Failed to copy entry"));
|
||||
return;
|
||||
}
|
||||
instantClose();
|
||||
pasteTimer.start();
|
||||
});
|
||||
Ref {
|
||||
service: ClipboardService
|
||||
}
|
||||
|
||||
function updateFilteredModel() {
|
||||
const query = searchText.trim();
|
||||
let filtered = [];
|
||||
|
||||
if (query.length === 0) {
|
||||
filtered = internalEntries;
|
||||
} else {
|
||||
const lowerQuery = query.toLowerCase();
|
||||
filtered = internalEntries.filter(entry => entry.preview.toLowerCase().includes(lowerQuery));
|
||||
}
|
||||
|
||||
// Sort: pinned first, then by ID descending
|
||||
filtered.sort((a, b) => {
|
||||
if (a.pinned !== b.pinned)
|
||||
return b.pinned ? 1 : -1;
|
||||
return b.id - a.id;
|
||||
});
|
||||
|
||||
clipboardEntries = filtered;
|
||||
unpinnedEntries = filtered.filter(e => !e.pinned);
|
||||
totalCount = clipboardEntries.length;
|
||||
if (unpinnedEntries.length === 0) {
|
||||
keyboardNavigationActive = false;
|
||||
selectedIndex = 0;
|
||||
} else if (selectedIndex >= unpinnedEntries.length) {
|
||||
selectedIndex = unpinnedEntries.length - 1;
|
||||
}
|
||||
ClipboardService.updateFilteredModel();
|
||||
}
|
||||
|
||||
property var internalEntries: []
|
||||
property var unpinnedEntries: []
|
||||
property string activeTab: "recents"
|
||||
function pasteSelected() {
|
||||
ClipboardService.pasteSelected(instantClose);
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if (shouldBeVisible) {
|
||||
@@ -112,10 +61,10 @@ DankModal {
|
||||
return;
|
||||
}
|
||||
open();
|
||||
searchText = "";
|
||||
activeImageLoads = 0;
|
||||
shouldHaveFocus = true;
|
||||
refreshClipboard();
|
||||
ClipboardService.reset();
|
||||
ClipboardService.refresh();
|
||||
keyboardController.reset();
|
||||
|
||||
Qt.callLater(function () {
|
||||
@@ -128,141 +77,48 @@ DankModal {
|
||||
|
||||
function hide() {
|
||||
close();
|
||||
searchText = "";
|
||||
}
|
||||
|
||||
onDialogClosed: {
|
||||
activeImageLoads = 0;
|
||||
internalEntries = [];
|
||||
clipboardEntries = [];
|
||||
ClipboardService.reset();
|
||||
keyboardController.reset();
|
||||
}
|
||||
|
||||
function refreshClipboard() {
|
||||
DMSService.sendRequest("clipboard.getHistory", null, function (response) {
|
||||
if (response.error) {
|
||||
console.warn("ClipboardHistoryModal: Failed to get history:", response.error);
|
||||
return;
|
||||
}
|
||||
internalEntries = response.result || [];
|
||||
|
||||
pinnedEntries = internalEntries.filter(e => e.pinned);
|
||||
pinnedCount = pinnedEntries.length;
|
||||
|
||||
updateFilteredModel();
|
||||
});
|
||||
ClipboardService.refresh();
|
||||
}
|
||||
|
||||
function copyEntry(entry) {
|
||||
DMSService.sendRequest("clipboard.copyEntry", {
|
||||
"id": entry.id
|
||||
}, function (response) {
|
||||
if (response.error) {
|
||||
ToastService.showError(I18n.tr("Failed to copy entry"));
|
||||
return;
|
||||
}
|
||||
ToastService.showInfo(entry.isImage ? I18n.tr("Image copied to clipboard") : I18n.tr("Copied to clipboard"));
|
||||
hide();
|
||||
});
|
||||
ClipboardService.copyEntry(entry, hide);
|
||||
}
|
||||
|
||||
function deleteEntry(entry) {
|
||||
DMSService.sendRequest("clipboard.deleteEntry", {
|
||||
"id": entry.id
|
||||
}, function (response) {
|
||||
if (response.error) {
|
||||
console.warn("ClipboardHistoryModal: Failed to delete entry:", response.error);
|
||||
return;
|
||||
}
|
||||
internalEntries = internalEntries.filter(e => e.id !== entry.id);
|
||||
updateFilteredModel();
|
||||
if (clipboardEntries.length === 0) {
|
||||
keyboardNavigationActive = false;
|
||||
selectedIndex = 0;
|
||||
} else if (selectedIndex >= clipboardEntries.length) {
|
||||
selectedIndex = clipboardEntries.length - 1;
|
||||
}
|
||||
});
|
||||
ClipboardService.deleteEntry(entry);
|
||||
}
|
||||
|
||||
function deletePinnedEntry(entry) {
|
||||
clearConfirmDialog.show(I18n.tr("Delete Saved Item?"), I18n.tr("This will permanently remove this saved clipboard item. This action cannot be undone."), function () {
|
||||
DMSService.sendRequest("clipboard.deleteEntry", {
|
||||
"id": entry.id
|
||||
}, function (response) {
|
||||
if (response.error) {
|
||||
console.warn("ClipboardHistoryModal: Failed to delete entry:", response.error);
|
||||
return;
|
||||
}
|
||||
internalEntries = internalEntries.filter(e => e.id !== entry.id);
|
||||
updateFilteredModel();
|
||||
ToastService.showInfo(I18n.tr("Saved item deleted"));
|
||||
});
|
||||
}, function () {});
|
||||
ClipboardService.deletePinnedEntry(entry, clearConfirmDialog);
|
||||
}
|
||||
|
||||
function pinEntry(entry) {
|
||||
DMSService.sendRequest("clipboard.getPinnedCount", null, function (countResponse) {
|
||||
if (countResponse.error) {
|
||||
ToastService.showError(I18n.tr("Failed to check pin limit"));
|
||||
return;
|
||||
}
|
||||
|
||||
const maxPinned = 25; // TODO: Get from config
|
||||
if (countResponse.result.count >= maxPinned) {
|
||||
ToastService.showError(I18n.tr("Maximum pinned entries reached") + " (" + maxPinned + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
DMSService.sendRequest("clipboard.pinEntry", {
|
||||
"id": entry.id
|
||||
}, function (response) {
|
||||
if (response.error) {
|
||||
ToastService.showError(I18n.tr("Failed to pin entry"));
|
||||
return;
|
||||
}
|
||||
ToastService.showInfo(I18n.tr("Entry pinned"));
|
||||
refreshClipboard();
|
||||
});
|
||||
});
|
||||
ClipboardService.pinEntry(entry);
|
||||
}
|
||||
|
||||
function unpinEntry(entry) {
|
||||
DMSService.sendRequest("clipboard.unpinEntry", {
|
||||
"id": entry.id
|
||||
}, function (response) {
|
||||
if (response.error) {
|
||||
ToastService.showError(I18n.tr("Failed to unpin entry"));
|
||||
return;
|
||||
}
|
||||
ToastService.showInfo(I18n.tr("Entry unpinned"));
|
||||
refreshClipboard();
|
||||
});
|
||||
ClipboardService.unpinEntry(entry);
|
||||
}
|
||||
|
||||
function clearAll() {
|
||||
const hasPinned = pinnedCount > 0;
|
||||
DMSService.sendRequest("clipboard.clearHistory", null, function (response) {
|
||||
if (response.error) {
|
||||
console.warn("ClipboardHistoryModal: Failed to clear history:", response.error);
|
||||
return;
|
||||
}
|
||||
refreshClipboard();
|
||||
if (hasPinned) {
|
||||
ToastService.showInfo(I18n.tr("History cleared. %1 pinned entries kept.").arg(pinnedCount));
|
||||
}
|
||||
});
|
||||
ClipboardService.clearAll();
|
||||
}
|
||||
|
||||
function getEntryPreview(entry) {
|
||||
return entry.preview || "";
|
||||
return ClipboardService.getEntryPreview(entry);
|
||||
}
|
||||
|
||||
function getEntryType(entry) {
|
||||
if (entry.isImage) {
|
||||
return "image";
|
||||
}
|
||||
if (entry.size > ClipboardConstants.longTextThreshold) {
|
||||
return "long_text";
|
||||
}
|
||||
return "text";
|
||||
return ClipboardService.getEntryType(entry);
|
||||
}
|
||||
|
||||
visible: false
|
||||
@@ -284,20 +140,6 @@ DankModal {
|
||||
modal: clipboardHistoryModal
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: DMSService
|
||||
function onClipboardStateUpdate(data) {
|
||||
if (!clipboardHistoryModal.shouldBeVisible) {
|
||||
return;
|
||||
}
|
||||
const newHistory = data.history || [];
|
||||
internalEntries = newHistory;
|
||||
pinnedEntries = newHistory.filter(e => e.pinned);
|
||||
pinnedCount = pinnedEntries.length;
|
||||
updateFilteredModel();
|
||||
}
|
||||
}
|
||||
|
||||
ConfirmModal {
|
||||
id: clearConfirmDialog
|
||||
confirmButtonText: I18n.tr("Clear All")
|
||||
|
||||
Reference in New Issue
Block a user