1415 lines
74 KiB
QML
1415 lines
74 KiB
QML
import QtQuick
|
|
import QtQuick.Controls
|
|
import Quickshell.Wayland
|
|
import Quickshell.Io
|
|
import QtQuick.Layouts
|
|
import Quickshell
|
|
import qs.Commons
|
|
import qs.Services.Keyboard
|
|
import qs.Services.UI
|
|
import qs.Widgets
|
|
|
|
Item {
|
|
id: root
|
|
|
|
// Plugin API (injected by PluginPanelSlot)
|
|
property var pluginApi: null
|
|
|
|
// Screen context - store reference for child components
|
|
property var currentScreen: screen
|
|
|
|
// Track currently open ToDo context menu
|
|
property var activeContextMenu: null
|
|
|
|
// Refresh clipboard list and load notecards when panel becomes visible
|
|
// Save notecards when panel is closed
|
|
onVisibleChanged: {
|
|
if (visible) {
|
|
pluginApi?.mainInstance?.refreshOnPanelOpen();
|
|
pluginApi?.mainInstance?.loadNoteCards();
|
|
} else {
|
|
// Sync all local changes from notecards before saving
|
|
if (noteCardsPanel && noteCardsPanel.children[0] && noteCardsPanel.children[0].syncAllChanges) {
|
|
noteCardsPanel.children[0].syncAllChanges();
|
|
}
|
|
pluginApi?.mainInstance?.saveNoteCards();
|
|
}
|
|
}
|
|
|
|
// SmartPanel properties (required for panel behavior)
|
|
readonly property var geometryPlaceholder: mainContainer
|
|
readonly property bool allowAttach: true
|
|
|
|
// Panel dimensions - fullscreen transparent container
|
|
property real contentPreferredWidth: (pluginApi?.pluginSettings?.fullscreenMode ?? false)
|
|
? (screen?.width ?? 1920)
|
|
: (pluginApi?.pluginSettings?.panelWidth ?? 1450) * Style.uiScaleRatio
|
|
property var contentPreferredHeight: {
|
|
if (pluginApi?.pluginSettings?.fullscreenMode ?? false) return screen?.height ?? 900;
|
|
var h = pluginApi?.pluginSettings?.panelHeight ?? 0;
|
|
return h > 0 ? h * Style.uiScaleRatio : undefined;
|
|
}
|
|
|
|
// SmartPanel background color - transparent when hidePanelBackground is enabled
|
|
property color panelBackgroundColor: (pluginApi?.pluginSettings?.hidePanelBackground ?? false)
|
|
? "transparent"
|
|
: ((typeof Color !== "undefined") ? Color.mSurface : "#222222")
|
|
|
|
// Keyboard navigation
|
|
property int selectedIndex: 0
|
|
|
|
// Filtering
|
|
property string filterType: ""
|
|
property string searchText: ""
|
|
|
|
// Reset selection when filter changes
|
|
onFilterTypeChanged: selectedIndex = 0
|
|
onSearchTextChanged: selectedIndex = 0
|
|
|
|
// Filtered items (uses shared getItemType from Main.qml)
|
|
readonly property var filteredItems: {
|
|
let items = pluginApi?.mainInstance?.items || [];
|
|
if (!filterType && !searchText)
|
|
return items;
|
|
|
|
return items.filter(item => {
|
|
if (filterType) {
|
|
const itemType = pluginApi?.mainInstance?.getItemType(item) || "Text";
|
|
if (itemType !== filterType)
|
|
return false;
|
|
}
|
|
if (searchText) {
|
|
const preview = item.preview || "";
|
|
if (!preview.toLowerCase().includes(searchText.toLowerCase()))
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
|
|
Keys.onLeftPressed: {
|
|
if (listView.count > 0) {
|
|
selectedIndex = Math.max(0, selectedIndex - 1);
|
|
listView.positionViewAtIndex(selectedIndex, ListView.Contain);
|
|
}
|
|
}
|
|
|
|
Keys.onRightPressed: {
|
|
if (listView.count > 0) {
|
|
selectedIndex = Math.min(listView.count - 1, selectedIndex + 1);
|
|
listView.positionViewAtIndex(selectedIndex, ListView.Contain);
|
|
}
|
|
}
|
|
|
|
Keys.onReturnPressed: {
|
|
if (listView.count > 0 && selectedIndex >= 0 && selectedIndex < listView.count) {
|
|
const item = root.filteredItems[selectedIndex];
|
|
if (item) {
|
|
pluginApi?.mainInstance?.copyToClipboard(item.id);
|
|
if (pluginApi) {
|
|
pluginApi.closePanel(screen);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Keys.onEscapePressed: {
|
|
if (pluginApi) {
|
|
pluginApi.closePanel(screen);
|
|
}
|
|
}
|
|
|
|
Keys.onDeletePressed: {
|
|
if (listView.count > 0 && selectedIndex >= 0 && selectedIndex < listView.count) {
|
|
const item = root.filteredItems[selectedIndex];
|
|
if (item) {
|
|
pluginApi?.mainInstance?.deleteById(item.id);
|
|
if (selectedIndex >= listView.count - 1) {
|
|
selectedIndex = Math.max(0, listView.count - 2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Keys.onDigit1Pressed: filterType = ""
|
|
Keys.onDigit2Pressed: filterType = "Text"
|
|
Keys.onDigit3Pressed: filterType = "Image"
|
|
Keys.onDigit4Pressed: filterType = "Color"
|
|
Keys.onDigit5Pressed: filterType = "Link"
|
|
Keys.onDigit6Pressed: filterType = "Code"
|
|
Keys.onDigit7Pressed: filterType = "Emoji"
|
|
Keys.onDigit8Pressed: filterType = "File"
|
|
|
|
// Main container - transparent fullscreen
|
|
Item {
|
|
id: mainContainer
|
|
anchors.fill: parent
|
|
|
|
// Click-to-close backdrop — only active when panel background is hidden
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
z: -1
|
|
enabled: pluginApi?.pluginSettings?.hidePanelBackground ?? false
|
|
onClicked: {
|
|
if (root.pluginApi)
|
|
root.pluginApi.closePanel(screen);
|
|
}
|
|
}
|
|
|
|
NIconButton {
|
|
visible: pluginApi?.mainInstance?.showCloseButton ?? false
|
|
anchors.top: parent.top
|
|
anchors.right: parent.right
|
|
anchors.margins: Style.marginM
|
|
z: 10
|
|
icon: "x"
|
|
tooltipText: pluginApi?.tr("panel.close")
|
|
colorBg: (typeof Color !== "undefined") ? Color.mSurfaceVariant : "#444444"
|
|
colorBgHover: (typeof Color !== "undefined") ? Color.mError : "#CC0000"
|
|
colorFg: (typeof Color !== "undefined") ? Color.mOnSurface : "#FFFFFF"
|
|
colorFgHover: (typeof Color !== "undefined") ? Color.mOnError : "#FFFFFF"
|
|
onClicked: {
|
|
if (root.pluginApi) {
|
|
root.pluginApi.closePanel(screen);
|
|
}
|
|
}
|
|
}
|
|
|
|
// CLIPBOARD PANEL - Bottom, full width (horizontal)
|
|
Rectangle {
|
|
id: clipboardPanel
|
|
anchors.bottom: parent.bottom
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
height: Math.min(300, screen?.height * 0.3 || 300)
|
|
color: (typeof Color !== "undefined") ? Color.mSurface : "#222222"
|
|
radius: Style.radiusM
|
|
opacity: 1.0 // Override global panel opacity
|
|
|
|
Rectangle {
|
|
topLeftRadius: Style.radiusM
|
|
topRightRadius: Style.radiusM
|
|
bottomLeftRadius: 0
|
|
bottomRightRadius: 0
|
|
opacity: 1.0
|
|
}
|
|
|
|
ColumnLayout {
|
|
anchors.fill: parent
|
|
anchors.margins: Style.marginL
|
|
spacing: Style.marginM
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
spacing: Style.marginM
|
|
|
|
NText {
|
|
text: pluginApi?.tr("panel.title")
|
|
font.bold: true
|
|
font.pointSize: Style.fontSizeL
|
|
Layout.alignment: Qt.AlignVCenter
|
|
Layout.topMargin: -2 * Style.uiScaleRatio
|
|
}
|
|
|
|
Item {
|
|
Layout.fillWidth: true
|
|
}
|
|
|
|
NIconButton {
|
|
icon: "settings"
|
|
tooltipText: pluginApi?.tr("panel.settings")
|
|
Layout.alignment: Qt.AlignVCenter
|
|
colorBg: (typeof Color !== "undefined") ? Color.mSurfaceVariant : "#444444"
|
|
colorBgHover: (typeof Color !== "undefined") ? Color.mHover : "#666666"
|
|
colorFg: (typeof Color !== "undefined") ? Color.mOnSurface : "#FFFFFF"
|
|
colorFgHover: (typeof Color !== "undefined") ? Color.mOnSurface : "#FFFFFF"
|
|
onClicked: {
|
|
if (root.pluginApi) {
|
|
BarService.openPluginSettings(screen, root.pluginApi.manifest);
|
|
}
|
|
}
|
|
}
|
|
|
|
NTextInput {
|
|
id: searchInput
|
|
Layout.preferredWidth: 250
|
|
Layout.alignment: Qt.AlignVCenter
|
|
placeholderText: pluginApi?.tr("panel.search-placeholder")
|
|
text: root.searchText
|
|
onTextChanged: root.searchText = text
|
|
|
|
Keys.onEscapePressed: {
|
|
if (text !== "") {
|
|
text = "";
|
|
} else {
|
|
root.onEscapePressed();
|
|
}
|
|
}
|
|
Keys.onLeftPressed: event => {
|
|
if (searchInput.cursorPosition === 0) {
|
|
root.onLeftPressed();
|
|
event.accepted = true;
|
|
}
|
|
}
|
|
Keys.onRightPressed: event => {
|
|
if (searchInput.cursorPosition === text.length) {
|
|
root.onRightPressed();
|
|
event.accepted = true;
|
|
}
|
|
}
|
|
Keys.onReturnPressed: root.onReturnPressed()
|
|
Keys.onEnterPressed: root.onReturnPressed()
|
|
Keys.onTabPressed: event => {
|
|
root.filterType = "Text";
|
|
event.accepted = true;
|
|
}
|
|
Keys.onUpPressed: event => {
|
|
// Up = focus search (already focused, do nothing)
|
|
event.accepted = true;
|
|
}
|
|
Keys.onDownPressed: event => {
|
|
// Down = focus cards
|
|
listView.forceActiveFocus();
|
|
event.accepted = true;
|
|
}
|
|
Keys.onPressed: event => {
|
|
if (event.key === Qt.Key_Home && event.modifiers & Qt.ControlModifier) {
|
|
root.onHomePressed();
|
|
event.accepted = true;
|
|
} else if (event.key === Qt.Key_End && event.modifiers & Qt.ControlModifier) {
|
|
root.onEndPressed();
|
|
event.accepted = true;
|
|
} else if (event.key === Qt.Key_Delete) {
|
|
// Delete current card
|
|
if (listView.count > 0 && root.selectedIndex >= 0 && root.selectedIndex < listView.count) {
|
|
const item = root.filteredItems[root.selectedIndex];
|
|
if (item) {
|
|
pluginApi?.mainInstance?.deleteById(item.id);
|
|
if (root.selectedIndex >= listView.count - 1) {
|
|
root.selectedIndex = Math.max(0, listView.count - 2);
|
|
}
|
|
}
|
|
}
|
|
event.accepted = true;
|
|
} else if (event.key >= Qt.Key_0 && event.key <= Qt.Key_9) {
|
|
// Number keys for filter types
|
|
const filterMap = {
|
|
[Qt.Key_1]: "",
|
|
[Qt.Key_2]: "Text",
|
|
[Qt.Key_3]: "Image",
|
|
[Qt.Key_4]: "Color",
|
|
[Qt.Key_5]: "Link",
|
|
[Qt.Key_6]: "Code",
|
|
[Qt.Key_7]: "Emoji",
|
|
[Qt.Key_8]: "File"
|
|
};
|
|
if (filterMap.hasOwnProperty(event.key)) {
|
|
root.filterType = filterMap[event.key];
|
|
event.accepted = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
Layout.fillWidth: true
|
|
}
|
|
|
|
// Filter type -> accent color key mapping (mirrors ClipboardCard defaults)
|
|
// All/Text/Link/Emoji -> mPrimary, Image/File -> mTertiary, Color/Code -> mSecondary
|
|
RowLayout {
|
|
spacing: Style.marginXS
|
|
Layout.alignment: Qt.AlignVCenter
|
|
|
|
// --- ALL ---
|
|
Item {
|
|
readonly property string fType: ""
|
|
readonly property color accentColor: (typeof Color !== "undefined") ? Color.mPrimary : "#BB86FC"
|
|
readonly property color accentFgColor: (typeof Color !== "undefined") ? Color.mOnPrimary : "#FFFFFF"
|
|
readonly property bool isActive: root.filterType === fType
|
|
readonly property int itemCount: (pluginApi?.mainInstance?.items || []).length
|
|
// Expand slightly so the burst ring has room without clipping
|
|
width: btnAll.width + Style.fontSizeXS
|
|
height: btnAll.height + Style.fontSizeXS
|
|
|
|
NIconButton {
|
|
id: btnAll
|
|
anchors.centerIn: parent
|
|
focus: true
|
|
icon: "apps"
|
|
tooltipText: pluginApi?.tr("panel.filter-all")
|
|
colorBg: (typeof Color !== "undefined") ? (parent.isActive ? Color.mPrimary : Color.mSurfaceVariant) : "#444444"
|
|
colorBgHover: (typeof Color !== "undefined") ? (parent.isActive ? Color.mPrimary : Color.mHover) : "#666666"
|
|
colorFg: (typeof Color !== "undefined") ? (parent.isActive ? Color.mOnPrimary : Color.mOnSurface) : "#FFFFFF"
|
|
colorFgHover: (typeof Color !== "undefined") ? (parent.isActive ? Color.mOnPrimary : Color.mOnSurface) : "#FFFFFF"
|
|
onClicked: root.filterType = ""
|
|
Keys.onTabPressed: {
|
|
root.filterType = "";
|
|
event.accepted = true;
|
|
}
|
|
}
|
|
|
|
// Active burst ring - matches groupedWorkspaceNumberBurst
|
|
Rectangle {
|
|
anchors.centerIn: btnAll
|
|
width: btnAll.width + 8
|
|
height: btnAll.height + 8
|
|
radius: Math.min(width, height) / 2
|
|
color: "transparent"
|
|
border.color: (typeof Color !== "undefined") ? parent.accentColor : "#BB86FC"
|
|
border.width: 2
|
|
opacity: parent.isActive ? 0.85 : 0
|
|
scale: parent.isActive ? 1.0 : 0.7
|
|
Behavior on opacity {
|
|
NumberAnimation {
|
|
duration: Style.animationFast
|
|
easing.type: Easing.InOutCubic
|
|
}
|
|
}
|
|
Behavior on scale {
|
|
NumberAnimation {
|
|
duration: Style.animationFast
|
|
easing.type: Easing.OutBack
|
|
}
|
|
}
|
|
}
|
|
|
|
// Count badge - matches groupedWorkspaceNumberContainer pattern
|
|
Item {
|
|
visible: parent.itemCount > 0
|
|
anchors {
|
|
left: btnAll.left
|
|
top: btnAll.top
|
|
leftMargin: -Style.fontSizeXS * 0.55
|
|
topMargin: -Style.fontSizeXS * 0.25
|
|
}
|
|
width: Math.max(badgeAll.implicitWidth + Style.margin2XS, Style.fontSizeXXS * 2)
|
|
height: Math.max(badgeAll.implicitHeight + Style.marginXS, Style.fontSizeXXS * 2)
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
radius: Math.min(Style.radiusL, width / 2)
|
|
color: (typeof Color !== "undefined") ? parent.parent.accentColor : "#BB86FC"
|
|
scale: parent.parent.parent.isActive ? 1.0 : 0.85
|
|
Behavior on scale {
|
|
NumberAnimation {
|
|
duration: Style.animationNormal
|
|
easing.type: Easing.OutBack
|
|
}
|
|
}
|
|
Behavior on color {
|
|
enabled: (typeof Color !== "undefined") && !Color.isTransitioning
|
|
ColorAnimation {
|
|
duration: Style.animationFast
|
|
easing.type: Easing.InOutCubic
|
|
}
|
|
}
|
|
}
|
|
|
|
NText {
|
|
id: badgeAll
|
|
anchors.centerIn: parent
|
|
text: parent.parent.itemCount > 99 ? "99+" : parent.parent.itemCount
|
|
font.pointSize: (typeof Style !== "undefined") ? Style.fontSizeXS * 0.75 : 8
|
|
font.bold: true
|
|
color: (typeof Color !== "undefined") ? parent.parent.accentFgColor : "#FFFFFF"
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- TEXT ---
|
|
Item {
|
|
readonly property string fType: "Text"
|
|
readonly property color accentColor: (typeof Color !== "undefined") ? Color.mPrimary : "#BB86FC"
|
|
readonly property color accentFgColor: (typeof Color !== "undefined") ? Color.mOnPrimary : "#FFFFFF"
|
|
readonly property bool isActive: root.filterType === fType
|
|
readonly property int itemCount: {
|
|
const all = pluginApi?.mainInstance?.items || [];
|
|
return all.filter(i => (pluginApi?.mainInstance?.getItemType(i) || "Text") === "Text").length;
|
|
}
|
|
width: btnText.width + Style.fontSizeXS
|
|
height: btnText.height + Style.fontSizeXS
|
|
|
|
NIconButton {
|
|
id: btnText
|
|
anchors.centerIn: parent
|
|
focus: true
|
|
icon: "align-left"
|
|
tooltipText: pluginApi?.tr("panel.filter-text")
|
|
colorBg: (typeof Color !== "undefined") ? (parent.isActive ? Color.mPrimary : Color.mSurfaceVariant) : "#444444"
|
|
colorBgHover: (typeof Color !== "undefined") ? (parent.isActive ? Color.mPrimary : Color.mHover) : "#666666"
|
|
colorFg: (typeof Color !== "undefined") ? (parent.isActive ? Color.mOnPrimary : Color.mOnSurface) : "#FFFFFF"
|
|
colorFgHover: (typeof Color !== "undefined") ? (parent.isActive ? Color.mOnPrimary : Color.mOnSurface) : "#FFFFFF"
|
|
onClicked: root.filterType = "Text"
|
|
Keys.onTabPressed: {
|
|
root.filterType = "Image";
|
|
event.accepted = true;
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.centerIn: btnText
|
|
width: btnText.width + 8
|
|
height: btnText.height + 8
|
|
radius: Math.min(width, height) / 2
|
|
color: "transparent"
|
|
border.color: (typeof Color !== "undefined") ? parent.accentColor : "#BB86FC"
|
|
border.width: 2
|
|
opacity: parent.isActive ? 0.85 : 0
|
|
scale: parent.isActive ? 1.0 : 0.7
|
|
Behavior on opacity {
|
|
NumberAnimation {
|
|
duration: Style.animationFast
|
|
easing.type: Easing.InOutCubic
|
|
}
|
|
}
|
|
Behavior on scale {
|
|
NumberAnimation {
|
|
duration: Style.animationFast
|
|
easing.type: Easing.OutBack
|
|
}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
visible: parent.itemCount > 0
|
|
anchors {
|
|
left: btnText.left
|
|
top: btnText.top
|
|
leftMargin: -Style.fontSizeXS * 0.55
|
|
topMargin: -Style.fontSizeXS * 0.25
|
|
}
|
|
width: Math.max(badgeText.implicitWidth + Style.margin2XS, Style.fontSizeXXS * 2)
|
|
height: Math.max(badgeText.implicitHeight + Style.marginXS, Style.fontSizeXXS * 2)
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
radius: Math.min(Style.radiusL, width / 2)
|
|
color: (typeof Color !== "undefined") ? parent.parent.accentColor : "#BB86FC"
|
|
scale: parent.parent.parent.isActive ? 1.0 : 0.85
|
|
Behavior on scale {
|
|
NumberAnimation {
|
|
duration: Style.animationNormal
|
|
easing.type: Easing.OutBack
|
|
}
|
|
}
|
|
Behavior on color {
|
|
enabled: (typeof Color !== "undefined") && !Color.isTransitioning
|
|
ColorAnimation {
|
|
duration: Style.animationFast
|
|
easing.type: Easing.InOutCubic
|
|
}
|
|
}
|
|
}
|
|
NText {
|
|
id: badgeText
|
|
anchors.centerIn: parent
|
|
text: parent.parent.itemCount > 99 ? "99+" : parent.parent.itemCount
|
|
font.pointSize: (typeof Style !== "undefined") ? Style.fontSizeXS * 0.75 : 8
|
|
font.bold: true
|
|
color: (typeof Color !== "undefined") ? parent.parent.accentFgColor : "#FFFFFF"
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- IMAGE ---
|
|
Item {
|
|
readonly property string fType: "Image"
|
|
readonly property color accentColor: (typeof Color !== "undefined") ? Color.mTertiary : "#03DAC6"
|
|
readonly property color accentFgColor: (typeof Color !== "undefined") ? Color.mOnTertiary : "#FFFFFF"
|
|
readonly property bool isActive: root.filterType === fType
|
|
readonly property int itemCount: {
|
|
const all = pluginApi?.mainInstance?.items || [];
|
|
return all.filter(i => (pluginApi?.mainInstance?.getItemType(i) || "Text") === "Image").length;
|
|
}
|
|
width: btnImage.width + Style.fontSizeXS
|
|
height: btnImage.height + Style.fontSizeXS
|
|
|
|
NIconButton {
|
|
id: btnImage
|
|
anchors.centerIn: parent
|
|
focus: true
|
|
icon: "photo"
|
|
tooltipText: pluginApi?.tr("panel.filter-images")
|
|
colorBg: (typeof Color !== "undefined") ? (parent.isActive ? Color.mTertiary : Color.mSurfaceVariant) : "#444444"
|
|
colorBgHover: (typeof Color !== "undefined") ? (parent.isActive ? Color.mTertiary : Color.mHover) : "#666666"
|
|
colorFg: (typeof Color !== "undefined") ? (parent.isActive ? Color.mOnTertiary : Color.mOnSurface) : "#FFFFFF"
|
|
colorFgHover: (typeof Color !== "undefined") ? (parent.isActive ? Color.mOnTertiary : Color.mOnSurface) : "#FFFFFF"
|
|
onClicked: root.filterType = "Image"
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.centerIn: btnImage
|
|
width: btnImage.width + 8
|
|
height: btnImage.height + 8
|
|
radius: Math.min(width, height) / 2
|
|
color: "transparent"
|
|
border.color: (typeof Color !== "undefined") ? parent.accentColor : "#03DAC6"
|
|
border.width: 2
|
|
opacity: parent.isActive ? 0.85 : 0
|
|
scale: parent.isActive ? 1.0 : 0.7
|
|
Behavior on opacity {
|
|
NumberAnimation {
|
|
duration: Style.animationFast
|
|
easing.type: Easing.InOutCubic
|
|
}
|
|
}
|
|
Behavior on scale {
|
|
NumberAnimation {
|
|
duration: Style.animationFast
|
|
easing.type: Easing.OutBack
|
|
}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
visible: parent.itemCount > 0
|
|
anchors {
|
|
left: btnImage.left
|
|
top: btnImage.top
|
|
leftMargin: -Style.fontSizeXS * 0.55
|
|
topMargin: -Style.fontSizeXS * 0.25
|
|
}
|
|
width: Math.max(badgeImage.implicitWidth + Style.margin2XS, Style.fontSizeXXS * 2)
|
|
height: Math.max(badgeImage.implicitHeight + Style.marginXS, Style.fontSizeXXS * 2)
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
radius: Math.min(Style.radiusL, width / 2)
|
|
color: (typeof Color !== "undefined") ? parent.parent.accentColor : "#03DAC6"
|
|
scale: parent.parent.parent.isActive ? 1.0 : 0.85
|
|
Behavior on scale {
|
|
NumberAnimation {
|
|
duration: Style.animationNormal
|
|
easing.type: Easing.OutBack
|
|
}
|
|
}
|
|
Behavior on color {
|
|
enabled: (typeof Color !== "undefined") && !Color.isTransitioning
|
|
ColorAnimation {
|
|
duration: Style.animationFast
|
|
easing.type: Easing.InOutCubic
|
|
}
|
|
}
|
|
}
|
|
NText {
|
|
id: badgeImage
|
|
anchors.centerIn: parent
|
|
text: parent.parent.itemCount > 99 ? "99+" : parent.parent.itemCount
|
|
font.pointSize: (typeof Style !== "undefined") ? Style.fontSizeXS * 0.75 : 8
|
|
font.bold: true
|
|
color: (typeof Color !== "undefined") ? parent.parent.accentFgColor : "#FFFFFF"
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- COLOR ---
|
|
Item {
|
|
readonly property string fType: "Color"
|
|
readonly property color accentColor: (typeof Color !== "undefined") ? Color.mSecondary : "#CF6679"
|
|
readonly property color accentFgColor: (typeof Color !== "undefined") ? Color.mOnSecondary : "#FFFFFF"
|
|
readonly property bool isActive: root.filterType === fType
|
|
readonly property int itemCount: {
|
|
const all = pluginApi?.mainInstance?.items || [];
|
|
return all.filter(i => (pluginApi?.mainInstance?.getItemType(i) || "Text") === "Color").length;
|
|
}
|
|
width: btnColorFilter.width + Style.fontSizeXS
|
|
height: btnColorFilter.height + Style.fontSizeXS
|
|
|
|
NIconButton {
|
|
id: btnColorFilter
|
|
anchors.centerIn: parent
|
|
focus: true
|
|
icon: "palette"
|
|
tooltipText: pluginApi?.tr("panel.filter-colors")
|
|
colorBg: (typeof Color !== "undefined") ? (parent.isActive ? Color.mSecondary : Color.mSurfaceVariant) : "#444444"
|
|
colorBgHover: (typeof Color !== "undefined") ? (parent.isActive ? Color.mSecondary : Color.mHover) : "#666666"
|
|
colorFg: (typeof Color !== "undefined") ? (parent.isActive ? Color.mOnSecondary : Color.mOnSurface) : "#FFFFFF"
|
|
colorFgHover: (typeof Color !== "undefined") ? (parent.isActive ? Color.mOnSecondary : Color.mOnSurface) : "#FFFFFF"
|
|
onClicked: root.filterType = "Color"
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.centerIn: btnColorFilter
|
|
width: btnColorFilter.width + 8
|
|
height: btnColorFilter.height + 8
|
|
radius: Math.min(width, height) / 2
|
|
color: "transparent"
|
|
border.color: (typeof Color !== "undefined") ? parent.accentColor : "#CF6679"
|
|
border.width: 2
|
|
opacity: parent.isActive ? 0.85 : 0
|
|
scale: parent.isActive ? 1.0 : 0.7
|
|
Behavior on opacity {
|
|
NumberAnimation {
|
|
duration: Style.animationFast
|
|
easing.type: Easing.InOutCubic
|
|
}
|
|
}
|
|
Behavior on scale {
|
|
NumberAnimation {
|
|
duration: Style.animationFast
|
|
easing.type: Easing.OutBack
|
|
}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
visible: parent.itemCount > 0
|
|
anchors {
|
|
left: btnColorFilter.left
|
|
top: btnColorFilter.top
|
|
leftMargin: -Style.fontSizeXS * 0.55
|
|
topMargin: -Style.fontSizeXS * 0.25
|
|
}
|
|
width: Math.max(badgeColorFilter.implicitWidth + Style.margin2XS, Style.fontSizeXXS * 2)
|
|
height: Math.max(badgeColorFilter.implicitHeight + Style.marginXS, Style.fontSizeXXS * 2)
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
radius: Math.min(Style.radiusL, width / 2)
|
|
color: (typeof Color !== "undefined") ? parent.parent.accentColor : "#CF6679"
|
|
scale: parent.parent.parent.isActive ? 1.0 : 0.85
|
|
Behavior on scale {
|
|
NumberAnimation {
|
|
duration: Style.animationNormal
|
|
easing.type: Easing.OutBack
|
|
}
|
|
}
|
|
Behavior on color {
|
|
enabled: (typeof Color !== "undefined") && !Color.isTransitioning
|
|
ColorAnimation {
|
|
duration: Style.animationFast
|
|
easing.type: Easing.InOutCubic
|
|
}
|
|
}
|
|
}
|
|
NText {
|
|
id: badgeColorFilter
|
|
anchors.centerIn: parent
|
|
text: parent.parent.itemCount > 99 ? "99+" : parent.parent.itemCount
|
|
font.pointSize: (typeof Style !== "undefined") ? Style.fontSizeXS * 0.75 : 8
|
|
font.bold: true
|
|
color: (typeof Color !== "undefined") ? parent.parent.accentFgColor : "#FFFFFF"
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- LINK ---
|
|
Item {
|
|
readonly property string fType: "Link"
|
|
readonly property color accentColor: (typeof Color !== "undefined") ? Color.mPrimary : "#BB86FC"
|
|
readonly property color accentFgColor: (typeof Color !== "undefined") ? Color.mOnPrimary : "#FFFFFF"
|
|
readonly property bool isActive: root.filterType === fType
|
|
readonly property int itemCount: {
|
|
const all = pluginApi?.mainInstance?.items || [];
|
|
return all.filter(i => (pluginApi?.mainInstance?.getItemType(i) || "Text") === "Link").length;
|
|
}
|
|
width: btnLink.width + Style.fontSizeXS
|
|
height: btnLink.height + Style.fontSizeXS
|
|
|
|
NIconButton {
|
|
id: btnLink
|
|
anchors.centerIn: parent
|
|
focus: true
|
|
icon: "link"
|
|
tooltipText: pluginApi?.tr("panel.filter-links")
|
|
colorBg: (typeof Color !== "undefined") ? (parent.isActive ? Color.mPrimary : Color.mSurfaceVariant) : "#444444"
|
|
colorBgHover: (typeof Color !== "undefined") ? (parent.isActive ? Color.mPrimary : Color.mHover) : "#666666"
|
|
colorFg: (typeof Color !== "undefined") ? (parent.isActive ? Color.mOnPrimary : Color.mOnSurface) : "#FFFFFF"
|
|
colorFgHover: (typeof Color !== "undefined") ? (parent.isActive ? Color.mOnPrimary : Color.mOnSurface) : "#FFFFFF"
|
|
onClicked: root.filterType = "Link"
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.centerIn: btnLink
|
|
width: btnLink.width + 8
|
|
height: btnLink.height + 8
|
|
radius: Math.min(width, height) / 2
|
|
color: "transparent"
|
|
border.color: (typeof Color !== "undefined") ? parent.accentColor : "#BB86FC"
|
|
border.width: 2
|
|
opacity: parent.isActive ? 0.85 : 0
|
|
scale: parent.isActive ? 1.0 : 0.7
|
|
Behavior on opacity {
|
|
NumberAnimation {
|
|
duration: Style.animationFast
|
|
easing.type: Easing.InOutCubic
|
|
}
|
|
}
|
|
Behavior on scale {
|
|
NumberAnimation {
|
|
duration: Style.animationFast
|
|
easing.type: Easing.OutBack
|
|
}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
visible: parent.itemCount > 0
|
|
anchors {
|
|
left: btnLink.left
|
|
top: btnLink.top
|
|
leftMargin: -Style.fontSizeXS * 0.55
|
|
topMargin: -Style.fontSizeXS * 0.25
|
|
}
|
|
width: Math.max(badgeLink.implicitWidth + Style.margin2XS, Style.fontSizeXXS * 2)
|
|
height: Math.max(badgeLink.implicitHeight + Style.marginXS, Style.fontSizeXXS * 2)
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
radius: Math.min(Style.radiusL, width / 2)
|
|
color: (typeof Color !== "undefined") ? parent.parent.accentColor : "#BB86FC"
|
|
scale: parent.parent.parent.isActive ? 1.0 : 0.85
|
|
Behavior on scale {
|
|
NumberAnimation {
|
|
duration: Style.animationNormal
|
|
easing.type: Easing.OutBack
|
|
}
|
|
}
|
|
Behavior on color {
|
|
enabled: (typeof Color !== "undefined") && !Color.isTransitioning
|
|
ColorAnimation {
|
|
duration: Style.animationFast
|
|
easing.type: Easing.InOutCubic
|
|
}
|
|
}
|
|
}
|
|
NText {
|
|
id: badgeLink
|
|
anchors.centerIn: parent
|
|
text: parent.parent.itemCount > 99 ? "99+" : parent.parent.itemCount
|
|
font.pointSize: (typeof Style !== "undefined") ? Style.fontSizeXS * 0.75 : 8
|
|
font.bold: true
|
|
color: (typeof Color !== "undefined") ? parent.parent.accentFgColor : "#FFFFFF"
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- CODE ---
|
|
Item {
|
|
readonly property string fType: "Code"
|
|
readonly property color accentColor: (typeof Color !== "undefined") ? Color.mSecondary : "#CF6679"
|
|
readonly property color accentFgColor: (typeof Color !== "undefined") ? Color.mOnSecondary : "#FFFFFF"
|
|
readonly property bool isActive: root.filterType === fType
|
|
readonly property int itemCount: {
|
|
const all = pluginApi?.mainInstance?.items || [];
|
|
return all.filter(i => (pluginApi?.mainInstance?.getItemType(i) || "Text") === "Code").length;
|
|
}
|
|
width: btnCode.width + Style.fontSizeXS
|
|
height: btnCode.height + Style.fontSizeXS
|
|
|
|
NIconButton {
|
|
id: btnCode
|
|
anchors.centerIn: parent
|
|
focus: true
|
|
icon: "code"
|
|
tooltipText: pluginApi?.tr("panel.filter-code")
|
|
colorBg: (typeof Color !== "undefined") ? (parent.isActive ? Color.mSecondary : Color.mSurfaceVariant) : "#444444"
|
|
colorBgHover: (typeof Color !== "undefined") ? (parent.isActive ? Color.mSecondary : Color.mHover) : "#666666"
|
|
colorFg: (typeof Color !== "undefined") ? (parent.isActive ? Color.mOnSecondary : Color.mOnSurface) : "#FFFFFF"
|
|
colorFgHover: (typeof Color !== "undefined") ? (parent.isActive ? Color.mOnSecondary : Color.mOnSurface) : "#FFFFFF"
|
|
onClicked: root.filterType = "Code"
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.centerIn: btnCode
|
|
width: btnCode.width + 8
|
|
height: btnCode.height + 8
|
|
radius: Math.min(width, height) / 2
|
|
color: "transparent"
|
|
border.color: (typeof Color !== "undefined") ? parent.accentColor : "#CF6679"
|
|
border.width: 2
|
|
opacity: parent.isActive ? 0.85 : 0
|
|
scale: parent.isActive ? 1.0 : 0.7
|
|
Behavior on opacity {
|
|
NumberAnimation {
|
|
duration: Style.animationFast
|
|
easing.type: Easing.InOutCubic
|
|
}
|
|
}
|
|
Behavior on scale {
|
|
NumberAnimation {
|
|
duration: Style.animationFast
|
|
easing.type: Easing.OutBack
|
|
}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
visible: parent.itemCount > 0
|
|
anchors {
|
|
left: btnCode.left
|
|
top: btnCode.top
|
|
leftMargin: -Style.fontSizeXS * 0.55
|
|
topMargin: -Style.fontSizeXS * 0.25
|
|
}
|
|
width: Math.max(badgeCode.implicitWidth + Style.margin2XS, Style.fontSizeXXS * 2)
|
|
height: Math.max(badgeCode.implicitHeight + Style.marginXS, Style.fontSizeXXS * 2)
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
radius: Math.min(Style.radiusL, width / 2)
|
|
color: (typeof Color !== "undefined") ? parent.parent.accentColor : "#CF6679"
|
|
scale: parent.parent.parent.isActive ? 1.0 : 0.85
|
|
Behavior on scale {
|
|
NumberAnimation {
|
|
duration: Style.animationNormal
|
|
easing.type: Easing.OutBack
|
|
}
|
|
}
|
|
Behavior on color {
|
|
enabled: (typeof Color !== "undefined") && !Color.isTransitioning
|
|
ColorAnimation {
|
|
duration: Style.animationFast
|
|
easing.type: Easing.InOutCubic
|
|
}
|
|
}
|
|
}
|
|
NText {
|
|
id: badgeCode
|
|
anchors.centerIn: parent
|
|
text: parent.parent.itemCount > 99 ? "99+" : parent.parent.itemCount
|
|
font.pointSize: (typeof Style !== "undefined") ? Style.fontSizeXS * 0.75 : 8
|
|
font.bold: true
|
|
color: (typeof Color !== "undefined") ? parent.parent.accentFgColor : "#FFFFFF"
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- EMOJI ---
|
|
Item {
|
|
readonly property string fType: "Emoji"
|
|
readonly property color accentColor: (typeof Color !== "undefined") ? Color.mPrimary : "#BB86FC"
|
|
readonly property color accentFgColor: (typeof Color !== "undefined") ? Color.mOnPrimary : "#FFFFFF"
|
|
readonly property bool isActive: root.filterType === fType
|
|
readonly property int itemCount: {
|
|
const all = pluginApi?.mainInstance?.items || [];
|
|
return all.filter(i => (pluginApi?.mainInstance?.getItemType(i) || "Text") === "Emoji").length;
|
|
}
|
|
width: btnEmoji.width + Style.fontSizeXS
|
|
height: btnEmoji.height + Style.fontSizeXS
|
|
|
|
NIconButton {
|
|
id: btnEmoji
|
|
anchors.centerIn: parent
|
|
focus: true
|
|
icon: "mood-smile"
|
|
tooltipText: pluginApi?.tr("panel.filter-emoji")
|
|
colorBg: (typeof Color !== "undefined") ? (parent.isActive ? Color.mPrimary : Color.mSurfaceVariant) : "#444444"
|
|
colorBgHover: (typeof Color !== "undefined") ? (parent.isActive ? Color.mPrimary : Color.mHover) : "#666666"
|
|
colorFg: (typeof Color !== "undefined") ? (parent.isActive ? Color.mOnPrimary : Color.mOnSurface) : "#FFFFFF"
|
|
colorFgHover: (typeof Color !== "undefined") ? (parent.isActive ? Color.mOnPrimary : Color.mOnSurface) : "#FFFFFF"
|
|
onClicked: root.filterType = "Emoji"
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.centerIn: btnEmoji
|
|
width: btnEmoji.width + 8
|
|
height: btnEmoji.height + 8
|
|
radius: Math.min(width, height) / 2
|
|
color: "transparent"
|
|
border.color: (typeof Color !== "undefined") ? parent.accentColor : "#BB86FC"
|
|
border.width: 2
|
|
opacity: parent.isActive ? 0.85 : 0
|
|
scale: parent.isActive ? 1.0 : 0.7
|
|
Behavior on opacity {
|
|
NumberAnimation {
|
|
duration: Style.animationFast
|
|
easing.type: Easing.InOutCubic
|
|
}
|
|
}
|
|
Behavior on scale {
|
|
NumberAnimation {
|
|
duration: Style.animationFast
|
|
easing.type: Easing.OutBack
|
|
}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
visible: parent.itemCount > 0
|
|
anchors {
|
|
left: btnEmoji.left
|
|
top: btnEmoji.top
|
|
leftMargin: -Style.fontSizeXS * 0.55
|
|
topMargin: -Style.fontSizeXS * 0.25
|
|
}
|
|
width: Math.max(badgeEmoji.implicitWidth + Style.margin2XS, Style.fontSizeXXS * 2)
|
|
height: Math.max(badgeEmoji.implicitHeight + Style.marginXS, Style.fontSizeXXS * 2)
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
radius: Math.min(Style.radiusL, width / 2)
|
|
color: (typeof Color !== "undefined") ? parent.parent.accentColor : "#BB86FC"
|
|
scale: parent.parent.parent.isActive ? 1.0 : 0.85
|
|
Behavior on scale {
|
|
NumberAnimation {
|
|
duration: Style.animationNormal
|
|
easing.type: Easing.OutBack
|
|
}
|
|
}
|
|
Behavior on color {
|
|
enabled: (typeof Color !== "undefined") && !Color.isTransitioning
|
|
ColorAnimation {
|
|
duration: Style.animationFast
|
|
easing.type: Easing.InOutCubic
|
|
}
|
|
}
|
|
}
|
|
NText {
|
|
id: badgeEmoji
|
|
anchors.centerIn: parent
|
|
text: parent.parent.itemCount > 99 ? "99+" : parent.parent.itemCount
|
|
font.pointSize: (typeof Style !== "undefined") ? Style.fontSizeXS * 0.75 : 8
|
|
font.bold: true
|
|
color: (typeof Color !== "undefined") ? parent.parent.accentFgColor : "#FFFFFF"
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- FILE ---
|
|
Item {
|
|
readonly property string fType: "File"
|
|
readonly property color accentColor: (typeof Color !== "undefined") ? Color.mTertiary : "#03DAC6"
|
|
readonly property color accentFgColor: (typeof Color !== "undefined") ? Color.mOnTertiary : "#FFFFFF"
|
|
readonly property bool isActive: root.filterType === fType
|
|
readonly property int itemCount: {
|
|
const all = pluginApi?.mainInstance?.items || [];
|
|
return all.filter(i => (pluginApi?.mainInstance?.getItemType(i) || "Text") === "File").length;
|
|
}
|
|
width: btnFile.width + Style.fontSizeXS
|
|
height: btnFile.height + Style.fontSizeXS
|
|
|
|
NIconButton {
|
|
id: btnFile
|
|
anchors.centerIn: parent
|
|
focus: true
|
|
icon: "file"
|
|
tooltipText: pluginApi?.tr("panel.filter-files")
|
|
colorBg: (typeof Color !== "undefined") ? (parent.isActive ? Color.mTertiary : Color.mSurfaceVariant) : "#444444"
|
|
colorBgHover: (typeof Color !== "undefined") ? (parent.isActive ? Color.mTertiary : Color.mHover) : "#666666"
|
|
colorFg: (typeof Color !== "undefined") ? (parent.isActive ? Color.mOnTertiary : Color.mOnSurface) : "#FFFFFF"
|
|
colorFgHover: (typeof Color !== "undefined") ? (parent.isActive ? Color.mOnTertiary : Color.mOnSurface) : "#FFFFFF"
|
|
onClicked: root.filterType = "File"
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.centerIn: btnFile
|
|
width: btnFile.width + 8
|
|
height: btnFile.height + 8
|
|
radius: Math.min(width, height) / 2
|
|
color: "transparent"
|
|
border.color: (typeof Color !== "undefined") ? parent.accentColor : "#03DAC6"
|
|
border.width: 2
|
|
opacity: parent.isActive ? 0.85 : 0
|
|
scale: parent.isActive ? 1.0 : 0.7
|
|
Behavior on opacity {
|
|
NumberAnimation {
|
|
duration: Style.animationFast
|
|
easing.type: Easing.InOutCubic
|
|
}
|
|
}
|
|
Behavior on scale {
|
|
NumberAnimation {
|
|
duration: Style.animationFast
|
|
easing.type: Easing.OutBack
|
|
}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
visible: parent.itemCount > 0
|
|
anchors {
|
|
left: btnFile.left
|
|
top: btnFile.top
|
|
leftMargin: -Style.fontSizeXS * 0.55
|
|
topMargin: -Style.fontSizeXS * 0.25
|
|
}
|
|
width: Math.max(badgeFile.implicitWidth + Style.margin2XS, Style.fontSizeXXS * 2)
|
|
height: Math.max(badgeFile.implicitHeight + Style.marginXS, Style.fontSizeXXS * 2)
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
radius: Math.min(Style.radiusL, width / 2)
|
|
color: (typeof Color !== "undefined") ? parent.parent.accentColor : "#03DAC6"
|
|
scale: parent.parent.parent.isActive ? 1.0 : 0.85
|
|
Behavior on scale {
|
|
NumberAnimation {
|
|
duration: Style.animationNormal
|
|
easing.type: Easing.OutBack
|
|
}
|
|
}
|
|
Behavior on color {
|
|
enabled: (typeof Color !== "undefined") && !Color.isTransitioning
|
|
ColorAnimation {
|
|
duration: Style.animationFast
|
|
easing.type: Easing.InOutCubic
|
|
}
|
|
}
|
|
}
|
|
NText {
|
|
id: badgeFile
|
|
anchors.centerIn: parent
|
|
text: parent.parent.itemCount > 99 ? "99+" : parent.parent.itemCount
|
|
font.pointSize: (typeof Style !== "undefined") ? Style.fontSizeXS * 0.75 : 8
|
|
font.bold: true
|
|
color: (typeof Color !== "undefined") ? parent.parent.accentFgColor : "#FFFFFF"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
Layout.preferredWidth: 1
|
|
Layout.preferredHeight: 24
|
|
Layout.alignment: Qt.AlignVCenter
|
|
color: (typeof Color !== "undefined") ? Color.mOutline : "#888888"
|
|
opacity: 0.5
|
|
}
|
|
|
|
NButton {
|
|
focus: true
|
|
text: pluginApi?.tr("panel.clear-all")
|
|
icon: "trash"
|
|
Layout.alignment: Qt.AlignVCenter
|
|
Layout.topMargin: -2 * Style.uiScaleRatio
|
|
onClicked: pluginApi?.mainInstance?.wipeAll()
|
|
}
|
|
}
|
|
|
|
ListView {
|
|
id: listView
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
orientation: ListView.Horizontal
|
|
spacing: Style.marginM
|
|
clip: true
|
|
currentIndex: root.selectedIndex
|
|
focus: false
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
acceptedButtons: Qt.NoButton
|
|
onWheel: wheel => {
|
|
listView.flick(wheel.angleDelta.y * 12, 0);
|
|
wheel.accepted = true;
|
|
}
|
|
}
|
|
|
|
model: root.filteredItems
|
|
|
|
Keys.onUpPressed: {
|
|
searchInput.forceActiveFocus();
|
|
}
|
|
Keys.onLeftPressed: {
|
|
if (count > 0) {
|
|
root.selectedIndex = Math.max(0, root.selectedIndex - 1);
|
|
positionViewAtIndex(root.selectedIndex, ListView.Contain);
|
|
}
|
|
}
|
|
Keys.onRightPressed: {
|
|
if (count > 0) {
|
|
root.selectedIndex = Math.min(count - 1, root.selectedIndex + 1);
|
|
positionViewAtIndex(root.selectedIndex, ListView.Contain);
|
|
}
|
|
}
|
|
Keys.onReturnPressed: {
|
|
if (count > 0 && root.selectedIndex >= 0 && root.selectedIndex < count) {
|
|
const item = root.filteredItems[root.selectedIndex];
|
|
if (item) {
|
|
root.pluginApi?.mainInstance?.copyToClipboard(item.id);
|
|
if (root.pluginApi) {
|
|
root.pluginApi.closePanel(screen);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Keys.onDeletePressed: {
|
|
if (count > 0 && root.selectedIndex >= 0 && root.selectedIndex < count) {
|
|
const item = root.filteredItems[root.selectedIndex];
|
|
if (item) {
|
|
root.pluginApi?.mainInstance?.deleteById(item.id);
|
|
if (root.selectedIndex >= count - 1) {
|
|
root.selectedIndex = Math.max(0, count - 2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Keys.onEscapePressed: {
|
|
if (root.pluginApi) {
|
|
root.pluginApi.closePanel(screen);
|
|
}
|
|
}
|
|
Keys.onTabPressed: {
|
|
// Cycle through filters: All -> Text -> Image -> Color -> Link -> Code -> Emoji -> File -> All
|
|
const filters = ["", "Text", "Image", "Color", "Link", "Code", "Emoji", "File"];
|
|
const currentIdx = filters.indexOf(root.filterType);
|
|
const nextIdx = (currentIdx + 1) % filters.length;
|
|
root.filterType = filters[nextIdx];
|
|
}
|
|
Keys.onBacktabPressed: {
|
|
// Shift+Tab = cycle backwards
|
|
const filters = ["", "Text", "Image", "Color", "Link", "Code", "Emoji", "File"];
|
|
const currentIdx = filters.indexOf(root.filterType);
|
|
const prevIdx = (currentIdx - 1 + filters.length) % filters.length;
|
|
root.filterType = filters[prevIdx];
|
|
}
|
|
Keys.onPressed: event => {
|
|
if (event.key >= Qt.Key_0 && event.key <= Qt.Key_9) {
|
|
const filterMap = {
|
|
[Qt.Key_1]: "",
|
|
[Qt.Key_2]: "Text",
|
|
[Qt.Key_3]: "Image",
|
|
[Qt.Key_4]: "Color",
|
|
[Qt.Key_5]: "Link",
|
|
[Qt.Key_6]: "Code",
|
|
[Qt.Key_7]: "Emoji",
|
|
[Qt.Key_8]: "File"
|
|
};
|
|
if (filterMap.hasOwnProperty(event.key)) {
|
|
root.filterType = filterMap[event.key];
|
|
event.accepted = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
delegate: ClipboardCard {
|
|
clipboardItem: modelData
|
|
pluginApi: root.pluginApi
|
|
screen: root.currentScreen
|
|
panelRoot: root
|
|
height: listView.height
|
|
selected: index === root.selectedIndex
|
|
enableTodoIntegration: root.pluginApi?.pluginSettings?.enableTodoIntegration || false
|
|
isPinned: {
|
|
// Force re-evaluation when pinnedRevision changes
|
|
const rev = root.pluginApi?.mainInstance?.pinnedRevision || 0;
|
|
const pinnedItems = root.pluginApi?.mainInstance?.pinnedItems || [];
|
|
return pinnedItems.some(p => p.id === clipboardId);
|
|
}
|
|
|
|
onClicked: {
|
|
root.selectedIndex = index;
|
|
root.pluginApi?.mainInstance?.copyToClipboard(clipboardId);
|
|
if (root.pluginApi) {
|
|
root.pluginApi.closePanel(screen);
|
|
const autoPaste = root.pluginApi.pluginSettings?.autoPaste ?? false;
|
|
const rmbOnly = root.pluginApi.pluginSettings?.autoPasteOnRightClick ?? false;
|
|
if (autoPaste && !rmbOnly) {
|
|
root.pluginApi.mainInstance?.triggerAutoPaste();
|
|
}
|
|
}
|
|
}
|
|
|
|
onRightClicked: {
|
|
root.selectedIndex = index;
|
|
const autoPaste = root.pluginApi?.pluginSettings?.autoPaste ?? false;
|
|
const rmbOnly = root.pluginApi?.pluginSettings?.autoPasteOnRightClick ?? false;
|
|
if (autoPaste && rmbOnly) {
|
|
root.pluginApi?.mainInstance?.copyToClipboard(clipboardId);
|
|
if (root.pluginApi) {
|
|
root.pluginApi.closePanel(screen);
|
|
root.pluginApi.mainInstance?.triggerAutoPaste();
|
|
}
|
|
}
|
|
}
|
|
|
|
onDeleteClicked: {
|
|
root.pluginApi?.mainInstance?.deleteById(clipboardId);
|
|
}
|
|
|
|
onPinClicked: {
|
|
if (isPinned) {
|
|
root.pluginApi?.mainInstance?.unpinItem(clipboardId);
|
|
ToastService.showNotice(pluginApi?.tr("toast.item-unpinned"));
|
|
} else {
|
|
const pinnedItems = root.pluginApi?.mainInstance?.pinnedItems || [];
|
|
if (pinnedItems.length >= 20) {
|
|
ToastService.showWarning(pluginApi?.tr("toast.max-pinned-items").replace("{max}", "20"));
|
|
} else {
|
|
root.pluginApi?.mainInstance?.pinItem(clipboardId);
|
|
ToastService.showNotice(pluginApi?.tr("toast.item-pinned"));
|
|
}
|
|
}
|
|
}
|
|
|
|
onAddToTodoClicked: {
|
|
if (preview) {
|
|
// Direct call to Main.qml function (no internal IPC)
|
|
root.pluginApi?.mainInstance?.addTodoWithText(preview.substring(0, 200), 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
NText {
|
|
anchors.centerIn: parent
|
|
visible: listView.count === 0
|
|
text: root.filterType || root.searchText ? pluginApi?.tr("panel.no-matches") : pluginApi?.tr("panel.empty")
|
|
color: (typeof Color !== "undefined") ? Color.mOnSurfaceVariant : "#AAAAAA"
|
|
}
|
|
}
|
|
}
|
|
} // End clipboardPanel
|
|
|
|
// PINNED PANEL - Left side, vertical
|
|
Rectangle {
|
|
id: pinnedPanel
|
|
visible: pluginApi?.pluginSettings?.pincardsEnabled ?? true
|
|
anchors.left: parent.left
|
|
anchors.top: parent.top
|
|
anchors.bottom: clipboardPanel.top
|
|
anchors.bottomMargin: Style.marginM
|
|
width: Math.min(300, screen?.width * 0.2 || 300)
|
|
color: (typeof Color !== "undefined") ? Color.mSurface : "#222222"
|
|
radius: Style.radiusM
|
|
opacity: 1.0 // Override global panel opacity
|
|
|
|
ColumnLayout {
|
|
anchors.fill: parent
|
|
anchors.margins: Style.marginL
|
|
spacing: Style.marginM
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
spacing: Style.marginM
|
|
|
|
NText {
|
|
text: pluginApi?.tr("panel.pinned-title")
|
|
font.bold: true
|
|
font.pointSize: Style.fontSizeL
|
|
Layout.alignment: Qt.AlignVCenter
|
|
}
|
|
|
|
Item {
|
|
Layout.fillWidth: true
|
|
}
|
|
}
|
|
ListView {
|
|
id: pinnedListView
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
orientation: ListView.Vertical
|
|
spacing: Style.marginS
|
|
clip: true
|
|
|
|
model: root.pluginApi?.mainInstance?.pinnedItems || []
|
|
|
|
delegate: ClipboardCard {
|
|
width: pinnedListView.width
|
|
height: Math.min(150, pinnedPanel.height * 0.25)
|
|
panelRoot: root
|
|
clipboardItem: {
|
|
return {
|
|
"id": modelData.id,
|
|
"preview": modelData.isImage ? "" : modelData.preview // Don't show binary preview
|
|
,
|
|
"mime": modelData.mime || "text/plain",
|
|
"isImage": modelData.isImage || false,
|
|
"content": modelData.content || "" // For images, this is data URL
|
|
};
|
|
}
|
|
isPinned: true
|
|
pluginApi: root.pluginApi
|
|
screen: root.currentScreen
|
|
selected: false
|
|
pinnedImageDataUrl: modelData.isImage ? modelData.content : "" // Pass data URL directly
|
|
|
|
onClicked: {
|
|
root.pluginApi?.mainInstance?.copyPinnedToClipboard(modelData.id);
|
|
if (root.pluginApi) {
|
|
root.pluginApi.closePanel(screen);
|
|
const autoPaste = root.pluginApi.pluginSettings?.autoPaste ?? false;
|
|
const rmbOnly = root.pluginApi.pluginSettings?.autoPasteOnRightClick ?? false;
|
|
if (autoPaste && !rmbOnly) {
|
|
root.pluginApi.mainInstance?.triggerAutoPaste();
|
|
}
|
|
}
|
|
}
|
|
|
|
onRightClicked: {
|
|
const autoPaste = root.pluginApi?.pluginSettings?.autoPaste ?? false;
|
|
const rmbOnly = root.pluginApi?.pluginSettings?.autoPasteOnRightClick ?? false;
|
|
if (autoPaste && rmbOnly) {
|
|
root.pluginApi?.mainInstance?.copyPinnedToClipboard(modelData.id);
|
|
if (root.pluginApi) {
|
|
root.pluginApi.closePanel(screen);
|
|
root.pluginApi.mainInstance?.triggerAutoPaste();
|
|
}
|
|
}
|
|
}
|
|
|
|
onPinClicked: {
|
|
root.pluginApi?.mainInstance?.unpinItem(modelData.id);
|
|
ToastService.showNotice(pluginApi?.tr("toast.item-unpinned"));
|
|
}
|
|
|
|
onDeleteClicked: {
|
|
root.pluginApi?.mainInstance?.unpinItem(modelData.id);
|
|
}
|
|
}
|
|
|
|
NText {
|
|
anchors.centerIn: parent
|
|
visible: pinnedListView.count === 0
|
|
text: pluginApi?.tr("panel.no-pinned")
|
|
color: (typeof Color !== "undefined") ? Color.mOnSurfaceVariant : "#AAAAAA"
|
|
}
|
|
}
|
|
}
|
|
} // End pinnedPanel
|
|
|
|
// Vertical separator between pinned and notecards
|
|
Rectangle {
|
|
visible: pinnedPanel.visible && noteCardsPanel.visible
|
|
anchors.left: pinnedPanel.right
|
|
anchors.top: parent.top
|
|
anchors.bottom: clipboardPanel.top
|
|
anchors.bottomMargin: Style.marginM
|
|
width: 1
|
|
color: "transparent"
|
|
|
|
Column {
|
|
anchors.centerIn: parent
|
|
spacing: 10
|
|
Repeater {
|
|
model: 27
|
|
Rectangle {
|
|
width: 2
|
|
height: 8
|
|
color: (typeof Color !== "undefined") ? Color.mOutline : "#555555"
|
|
opacity: 0.7
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// NOTECARDS PANEL - Middle space (between pinned and clipboard)
|
|
Item {
|
|
id: noteCardsPanel
|
|
visible: pluginApi?.pluginSettings?.notecardsEnabled ?? true
|
|
anchors.left: pinnedPanel.visible ? pinnedPanel.right : parent.left
|
|
anchors.leftMargin: pinnedPanel.visible ? Style.marginM : 0
|
|
anchors.right: parent.right
|
|
anchors.top: parent.top
|
|
anchors.bottom: clipboardPanel.top
|
|
anchors.bottomMargin: Style.marginM
|
|
|
|
NoteCardsPanel {
|
|
id: notecardsPanelInstance
|
|
anchors.fill: parent
|
|
pluginApi: root.pluginApi
|
|
screen: root.currentScreen
|
|
}
|
|
} // End noteCardsPanel
|
|
} // End mainContainer
|
|
|
|
Component.onCompleted: {
|
|
selectedIndex = 0;
|
|
filterType = "";
|
|
searchText = "";
|
|
pluginApi?.mainInstance?.list(screen?.width || 100);
|
|
listView.forceActiveFocus();
|
|
WlrLayershell.keyboardFocus = WlrKeyboardFocus.OnDemand;
|
|
}
|
|
}
|