repositories / dotfiles
dotfiles
bugabingas dorkfiles
owned by admin
quickshell/nuguland/ClipboardPanel.qml
Rawpragma ComponentBehavior: Bound
import QtQuick
import "Util.js" as Util
FocusScope {
id: root
required property Theme theme
required property Clipboard clipboardService
property var sessionState: ({
"query": "",
"selectedId": "",
"scrollY": 0
})
readonly property string query: String(sessionState.query || "")
property bool stateReady: false
readonly property var panelState: clipboardService ? Util.parseJson(clipboardService.search(query), {
"rows": [],
"privateMode": false,
"lastAction": {}
}) : ({
"rows": [],
"privateMode": false,
"lastAction": {}
})
readonly property var rows: panelState.rows || []
readonly property int selectedIndex: selectionIndex()
readonly property var selectedRow: selectedIndex >= 0 ? rows[selectedIndex] : ({})
readonly property int rowHeight: 56
readonly property int rowSpacing: 6
readonly property int rowStride: rowHeight + rowSpacing
signal pasteRequested(string id)
function updateState(values) {
Object.assign(sessionState, values);
// Notify bindings when the owner supplies a plain JS object.
sessionStateChanged();
}
function selectionIndex() {
const index = rows.findIndex(row => String(row.id) === String(sessionState.selectedId));
return index >= 0 ? index : (rows.length > 0 ? 0 : -1);
}
function reconcileSelection() {
const id = selectedIndex >= 0 ? String(rows[selectedIndex].id) : "";
if (sessionState.selectedId !== id)
updateState({
"selectedId": id
});
if (stateReady)
ensureSelectedVisible();
}
function search(text) {
updateState({
"query": String(text)
});
}
function select(index) {
if (rows.length === 0)
return;
const i = Math.max(0, Math.min(rows.length - 1, Number(index)));
updateState({
"selectedId": String(rows[i].id)
});
ensureSelectedVisible();
}
function rememberScroll() {
if (stateReady && sessionState.scrollY !== list.contentY)
updateState({
"scrollY": list.contentY
});
}
function ensureSelectedVisible() {
if (selectedIndex < 0)
return;
const top = selectedIndex * rowStride;
const bottom = top + rowHeight;
if (top < list.contentY)
updateState({
"scrollY": top
});
else if (bottom > list.contentY + list.height)
updateState({
"scrollY": bottom - list.height
});
}
function copy(id) {
if (clipboardService)
clipboardService.copy(id);
}
function paste(id) {
if (clipboardService && rows.some(row => String(row.id) === String(id)))
pasteRequested(String(id));
}
function togglePin(id) {
if (clipboardService)
clipboardService.togglePin(id);
}
function deleteRow(id) {
if (clipboardService)
clipboardService.deleteRow(id);
}
function setPrivateMode(enabled) {
if (clipboardService)
clipboardService.setPrivateMode(enabled);
}
width: parent ? parent.width : 0
height: parent ? parent.height : implicitHeight
implicitHeight: 510
focus: true
onRowsChanged: selectionTimer.restart()
Timer {
id: selectionTimer
interval: 0
running: true
onTriggered: {
root.reconcileSelection();
if (!root.stateReady) {
root.stateReady = true;
searchInput.input.forceActiveFocus();
}
}
}
Keys.onUpPressed: event => {
event.accepted = true;
select(selectedIndex - 1);
}
Keys.onDownPressed: event => {
event.accepted = true;
select(selectedIndex + 1);
}
Keys.onReturnPressed: event => {
event.accepted = true;
if (!event.isAutoRepeat)
paste(selectedRow.id);
}
Keys.onEnterPressed: event => {
event.accepted = true;
if (!event.isAutoRepeat)
paste(selectedRow.id);
}
Shortcut {
sequences: ["Return", "Enter"]
enabled: searchInput.input.activeFocus
autoRepeat: false
onActivated: root.paste(root.selectedRow.id)
}
StableListModel {
id: clipboardRows
rows: root.rows
}
Row {
id: searchRow
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
spacing: 8
height: 38
PopupTextField {
id: searchInput
objectName: "searchField"
theme: root.theme
width: searchRow.width - privateButton.width - searchRow.spacing
height: searchRow.height
icon: ""
placeholderText: "search clipboard"
text: root.query
onEdited: text => root.search(text)
}
PopupButton {
id: privateButton
objectName: "privateModeChip"
theme: root.theme
width: 38
height: searchRow.height
icon: root.panelState.privateMode ? "" : ""
text: root.panelState.privateMode ? "private" : "normal"
showText: false
role: "ghost"
accessibleName: "private clipboard mode"
tooltip: root.panelState.privateMode ? "resume clipboard capture" : "pause clipboard capture"
checked: root.panelState.privateMode
enabled: !!root.clipboardService
onClicked: root.setPrivateMode(!root.panelState.privateMode)
}
}
Item {
objectName: "clipboardHistoryPanel"
anchors.left: parent.left
anchors.right: parent.right
anchors.top: searchRow.bottom
anchors.bottom: pasteFeedback.top
anchors.topMargin: 10
anchors.bottomMargin: 10
Flickable {
id: list
objectName: "clipboardResultList"
anchors.fill: parent
contentHeight: rowColumn.implicitHeight
contentY: Math.max(0, Math.min(Number(root.sessionState.scrollY || 0), contentHeight - height))
onContentYChanged: root.rememberScroll()
clip: true
boundsBehavior: Flickable.StopAtBounds
Column {
id: rowColumn
width: list.width
spacing: root.rowSpacing
Repeater {
model: clipboardRows
delegate: PopupCard {
id: clipRow
required property int index
required property var rowData
readonly property var modelData: rowData
readonly property bool isSelected: index === root.selectedIndex
objectName: "clipRow"
theme: root.theme
padding: 0
active: isSelected
hoverable: true
externallyPressed: rowArea.pressed
activeFocusOnTab: isSelected
Accessible.role: Accessible.ListItem
Accessible.name: String(modelData.title || "clipboard item")
Accessible.selected: isSelected
onActiveFocusChanged: if (activeFocus)
root.select(index)
width: rowColumn.width
height: root.rowHeight
MouseArea {
id: rowArea
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
root.select(clipRow.index);
clipRow.forceActiveFocus();
}
onDoubleClicked: root.paste(clipRow.modelData.id)
}
Item {
id: leadingSlot
anchors.left: parent.left
anchors.leftMargin: 9
anchors.verticalCenter: parent.verticalCenter
width: 36
height: 36
Image {
id: thumb
anchors.fill: parent
visible: clipRow.modelData.isImage && thumb.status !== Image.Error
source: clipRow.modelData.isImage ? "file://" + encodeURI(clipRow.modelData.imagePath || "") : ""
sourceSize.width: 36
sourceSize.height: 36
fillMode: Image.PreserveAspectCrop
asynchronous: true
clip: true
smooth: true
}
Rectangle {
id: swatch
anchors.fill: parent
visible: !clipRow.modelData.isImage && !!clipRow.modelData.swatch
radius: 9
color: clipRow.modelData.swatch ? clipRow.modelData.swatch : "transparent"
border.width: 1
border.color: root.theme.withAlpha(root.theme.ui.normal, 0.25)
}
Text {
anchors.centerIn: parent
visible: !swatch.visible && !thumb.visible
text: clipRow.modelData.icon || ""
color: root.theme.ui.accent
font.family: "Symbols Nerd Font Mono"
font.pixelSize: 18
}
}
Column {
anchors.left: leadingSlot.right
anchors.right: pinBadge.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: 11
anchors.rightMargin: 8
spacing: 3
NuguText {
theme: root.theme
selected: clipRow.isSelected
text: clipRow.modelData.title
width: parent.width
elide: Text.ElideRight
}
NuguText {
theme: root.theme
variant: "meta"
selected: clipRow.isSelected
text: clipRow.modelData.kind + " · " + clipRow.modelData.age
width: parent.width
elide: Text.ElideRight
}
}
Text {
id: pinBadge
anchors.right: parent.right
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
width: 16
text: clipRow.modelData.pinned ? "" : ""
Accessible.name: clipRow.modelData.pinned ? "pinned" : ""
color: root.theme.ui.focus
font.family: "Symbols Nerd Font Mono"
font.pixelSize: 12
}
}
}
}
}
NuguText {
anchors.centerIn: parent
width: parent.width
horizontalAlignment: Text.AlignHCenter
theme: root.theme
variant: "meta"
visible: root.rows.length === 0
text: !root.clipboardService ? "clipboard unavailable" : (root.query.length > 0 ? "no matches" : "clipboard is empty")
}
}
NuguText {
id: pasteFeedback
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: actionRail.top
anchors.bottomMargin: text.length > 0 ? 8 : 0
theme: root.theme
text: root.panelState.lastAction.error || ""
color: root.theme.error
height: text.length > 0 ? implicitHeight : 0
wrapMode: Text.Wrap
}
Row {
id: actionRail
objectName: "clipboardActionRail"
anchors.left: parent.left
anchors.bottom: parent.bottom
height: 38
spacing: 8
enabled: root.selectedIndex >= 0 && !!root.clipboardService
PopupButton {
objectName: "clipboardPaste"
theme: root.theme
height: actionRail.height
text: "paste"
icon: ""
role: "primary"
tooltip: "paste selected item into the previous window"
enabled: !root.panelState.lastAction.pending
onClicked: root.paste(root.selectedRow.id)
}
PopupButton {
objectName: "clipboardCopy"
theme: root.theme
width: 38
height: actionRail.height
icon: ""
showText: false
accessibleName: "copy selected item"
tooltip: "copy selected item to the clipboard"
onClicked: root.copy(root.selectedRow.id)
}
PopupButton {
objectName: "clipboardPin"
theme: root.theme
width: 38
height: actionRail.height
icon: ""
showText: false
checked: !!root.selectedRow.pinned
accessibleName: root.selectedRow.pinned ? "unpin selected item" : "pin selected item"
tooltip: root.selectedRow.pinned ? "unpin selected item" : "keep selected item at the top"
onClicked: root.togglePin(root.selectedRow.id)
}
PopupButton {
objectName: "clipboardDelete"
theme: root.theme
width: 38
height: actionRail.height
icon: ""
showText: false
role: "destructive"
accessibleName: "delete selected item"
tooltip: "remove selected item from clipboard history"
onClicked: root.deleteRow(root.selectedRow.id)
}
}
}