Formatting
This commit is contained in:
parent
3f64bb1879
commit
151e2b6aaf
7 changed files with 869 additions and 840 deletions
|
|
@ -13,273 +13,270 @@ import "../../Helpers/FuzzySort.js" as Fuzzysort
|
||||||
import "../../Helpers/MathHelper.js" as MathHelper
|
import "../../Helpers/MathHelper.js" as MathHelper
|
||||||
|
|
||||||
NLoader {
|
NLoader {
|
||||||
id: appLauncher
|
id: appLauncher
|
||||||
isLoaded: false
|
isLoaded: false
|
||||||
// Clipboard state is persisted in Services/Clipboard.qml
|
// Clipboard state is persisted in Services/Clipboard.qml
|
||||||
content: Component {
|
content: Component {
|
||||||
NPanel {
|
NPanel {
|
||||||
id: appLauncherPanel
|
id: appLauncherPanel
|
||||||
|
|
||||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
|
||||||
|
|
||||||
// No local timer/processes; use persistent Clipboard service
|
|
||||||
|
|
||||||
// Removed local clipboard processes; handled by Clipboard service
|
// No local timer/processes; use persistent Clipboard service
|
||||||
|
|
||||||
|
// Removed local clipboard processes; handled by Clipboard service
|
||||||
|
|
||||||
|
// Copy helpers via simple exec; avoid keeping processes alive locally
|
||||||
|
function copyImageBase64(mime, base64) {
|
||||||
|
Quickshell.execDetached(["sh", "-lc", `printf %s ${base64} | base64 -d | wl-copy -t '${mime}'`])
|
||||||
|
}
|
||||||
|
|
||||||
// Copy helpers via simple exec; avoid keeping processes alive locally
|
function copyText(text) {
|
||||||
function copyImageBase64(mime, base64) {
|
Quickshell.execDetached(["sh", "-lc", `printf %s ${text} | wl-copy -t text/plain;charset=utf-8`])
|
||||||
Quickshell.execDetached(["sh", "-lc", `printf %s ${base64} | base64 -d | wl-copy -t '${mime}'`])
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function copyText(text) {
|
function updateClipboardHistory() {
|
||||||
Quickshell.execDetached(["sh", "-lc", `printf %s ${text} | wl-copy -t text/plain;charset=utf-8`])
|
Clipboard.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function selectNext() {
|
||||||
|
if (filteredEntries.length > 0) {
|
||||||
|
selectedIndex = Math.min(selectedIndex + 1, filteredEntries.length - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectPrev() {
|
||||||
|
if (filteredEntries.length > 0) {
|
||||||
|
selectedIndex = Math.max(selectedIndex - 1, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function updateClipboardHistory() {
|
function activateSelected() {
|
||||||
Clipboard.refresh();
|
if (filteredEntries.length === 0)
|
||||||
}
|
return
|
||||||
|
|
||||||
function selectNext() {
|
var modelData = filteredEntries[selectedIndex]
|
||||||
if (filteredEntries.length > 0) {
|
if (modelData && modelData.execute) {
|
||||||
selectedIndex = Math.min(selectedIndex + 1, filteredEntries.length - 1);
|
if (modelData.isCommand) {
|
||||||
|
modelData.execute()
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
modelData.execute()
|
||||||
|
}
|
||||||
|
appLauncherPanel.hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property var desktopEntries: DesktopEntries.applications.values
|
||||||
|
property string searchText: ""
|
||||||
|
property int selectedIndex: 0
|
||||||
|
property var filteredEntries: {
|
||||||
|
console.log("[AppLauncher] Total desktop entries:", desktopEntries ? desktopEntries.length : 0)
|
||||||
|
if (!desktopEntries || desktopEntries.length === 0) {
|
||||||
|
console.log("[AppLauncher] No desktop entries available")
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out entries that shouldn't be displayed
|
||||||
|
var visibleEntries = desktopEntries.filter(entry => {
|
||||||
|
if (!entry || entry.noDisplay) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log("[AppLauncher] Visible entries:", visibleEntries.length)
|
||||||
|
|
||||||
|
var query = searchText ? searchText.toLowerCase() : ""
|
||||||
|
var results = []
|
||||||
|
|
||||||
|
// Handle special commands
|
||||||
|
if (query === ">") {
|
||||||
|
results.push({
|
||||||
|
"isCommand": true,
|
||||||
|
"name": ">calc",
|
||||||
|
"content": "Calculator - evaluate mathematical expressions",
|
||||||
|
"icon": "calculate",
|
||||||
|
"execute": function () {
|
||||||
|
searchText = ">calc "
|
||||||
|
searchInput.cursorPosition = searchText.length
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
results.push({
|
||||||
|
"isCommand": true,
|
||||||
|
"name": ">clip",
|
||||||
|
"content": "Clipboard history - browse and restore clipboard items",
|
||||||
|
"icon": "content_paste",
|
||||||
|
"execute": function () {
|
||||||
|
searchText = ">clip "
|
||||||
|
searchInput.cursorPosition = searchText.length
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle clipboard history
|
||||||
|
if (query.startsWith(">clip")) {
|
||||||
|
if (!Clipboard.initialized) {
|
||||||
|
Clipboard.refresh()
|
||||||
|
}
|
||||||
|
const searchTerm = query.slice(5).trim()
|
||||||
|
|
||||||
|
Clipboard.history.forEach(function (clip, index) {
|
||||||
|
let searchContent = clip.type === 'image' ? clip.mimeType : clip.content || clip
|
||||||
|
|
||||||
|
if (!searchTerm || searchContent.toLowerCase().includes(searchTerm)) {
|
||||||
|
let entry
|
||||||
|
if (clip.type === 'image') {
|
||||||
|
entry = {
|
||||||
|
"isClipboard": true,
|
||||||
|
"name": "Image from " + new Date(clip.timestamp).toLocaleTimeString(),
|
||||||
|
"content": "Image: " + clip.mimeType,
|
||||||
|
"icon": "image",
|
||||||
|
"type": 'image',
|
||||||
|
"data": clip.data,
|
||||||
|
"execute": function () {
|
||||||
|
const base64Data = clip.data.split(',')[1]
|
||||||
|
copyImageBase64(clip.mimeType, base64Data)
|
||||||
|
Quickshell.execDetached(["notify-send", "Clipboard", "Image copied: " + clip.mimeType])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
const textContent = clip.content || clip
|
||||||
|
let displayContent = textContent
|
||||||
|
let previewContent = ""
|
||||||
|
|
||||||
function selectPrev() {
|
displayContent = displayContent.replace(/\s+/g, ' ').trim()
|
||||||
if (filteredEntries.length > 0) {
|
|
||||||
selectedIndex = Math.max(selectedIndex - 1, 0);
|
if (displayContent.length > 50) {
|
||||||
|
previewContent = displayContent
|
||||||
|
displayContent = displayContent.split('\n')[0].substring(0, 50) + "..."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
entry = {
|
||||||
|
"isClipboard": true,
|
||||||
|
"name": displayContent,
|
||||||
|
"content": previewContent || textContent,
|
||||||
|
"icon": "content_paste",
|
||||||
|
"execute": function () {
|
||||||
|
Quickshell.clipboardText = String(textContent)
|
||||||
|
copyText(String(textContent))
|
||||||
|
var preview = (textContent.length > 50) ? textContent.slice(0, 50) + "…" : textContent
|
||||||
|
Quickshell.execDetached(["notify-send", "Clipboard", "Text copied: " + preview])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
results.push(entry)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
function activateSelected() {
|
if (results.length === 0) {
|
||||||
if (filteredEntries.length === 0) return;
|
results.push({
|
||||||
|
"isClipboard": true,
|
||||||
|
"name": "No clipboard history",
|
||||||
|
"content": "No matching clipboard entries found",
|
||||||
|
"icon": "content_paste_off"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
var modelData = filteredEntries[selectedIndex];
|
return results
|
||||||
if (modelData && modelData.execute) {
|
}
|
||||||
if (modelData.isCommand) {
|
|
||||||
modelData.execute();
|
// Handle calculator
|
||||||
return;
|
if (query.startsWith(">calc")) {
|
||||||
} else {
|
var expr = searchText.slice(5).trim()
|
||||||
modelData.execute();
|
if (expr && isMathExpression(expr)) {
|
||||||
|
var value = safeEval(expr)
|
||||||
|
if (value !== null && value !== undefined && value !== "") {
|
||||||
|
var formattedResult = MathHelper.MathHelper.formatResult(value)
|
||||||
|
results.push({
|
||||||
|
"isCalculator": true,
|
||||||
|
"name": `Calculator: ${expr} = ${formattedResult}`,
|
||||||
|
"result": value,
|
||||||
|
"expr": expr,
|
||||||
|
"icon": "calculate",
|
||||||
|
"execute": function () {
|
||||||
|
Quickshell.clipboardText = String(formattedResult)
|
||||||
|
clipboardTextCopyProcess.copyText(String(formattedResult))
|
||||||
|
Quickshell.execDetached(
|
||||||
|
["notify-send", "Calculator Result", `${expr} = ${formattedResult} (copied to clipboard)`])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regular app search
|
||||||
|
if (!query) {
|
||||||
|
results = results.concat(visibleEntries.sort(function (a, b) {
|
||||||
|
return a.name.toLowerCase().localeCompare(b.name.toLowerCase())
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
var fuzzyResults = Fuzzysort.go(query, visibleEntries, {
|
||||||
|
"keys": ["name", "comment", "genericName"]
|
||||||
|
})
|
||||||
|
results = results.concat(fuzzyResults.map(function (r) {
|
||||||
|
return r.obj
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("[AppLauncher] Filtered entries:", results.length)
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
console.log("[AppLauncher] Component completed")
|
||||||
|
console.log("[AppLauncher] DesktopEntries available:", typeof DesktopEntries !== 'undefined')
|
||||||
|
if (typeof DesktopEntries !== 'undefined') {
|
||||||
|
console.log("[AppLauncher] DesktopEntries.entries:",
|
||||||
|
DesktopEntries.entries ? DesktopEntries.entries.length : 'undefined')
|
||||||
|
}
|
||||||
|
// Start clipboard refresh immediately on open
|
||||||
|
updateClipboardHistory()
|
||||||
|
}
|
||||||
|
|
||||||
|
function isMathExpression(str) {
|
||||||
|
// Allow more characters for enhanced math functions
|
||||||
|
return /^[-+*/().0-9\s\w]+$/.test(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
function safeEval(expr) {
|
||||||
|
return MathHelper.MathHelper.evaluate(expr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main content container
|
||||||
|
Rectangle {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: Math.min(700 * scaling, parent.width * 0.75)
|
||||||
|
height: Math.min(550 * scaling, parent.height * 0.8)
|
||||||
|
radius: 32 * scaling
|
||||||
|
color: Colors.backgroundPrimary
|
||||||
|
border.color: Colors.outline
|
||||||
|
border.width: Style.borderThin * scaling
|
||||||
|
|
||||||
|
// Subtle gradient background
|
||||||
|
gradient: Gradient {
|
||||||
|
GradientStop {
|
||||||
|
position: 0.0
|
||||||
|
color: Qt.lighter(Colors.backgroundPrimary, 1.02)
|
||||||
|
}
|
||||||
|
GradientStop {
|
||||||
|
position: 1.0
|
||||||
|
color: Qt.darker(Colors.backgroundPrimary, 1.1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
appLauncherPanel.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
property var desktopEntries: DesktopEntries.applications.values
|
|
||||||
property string searchText: ""
|
|
||||||
property int selectedIndex: 0
|
|
||||||
property var filteredEntries: {
|
|
||||||
console.log("[AppLauncher] Total desktop entries:", desktopEntries ? desktopEntries.length : 0)
|
|
||||||
if (!desktopEntries || desktopEntries.length === 0) {
|
|
||||||
console.log("[AppLauncher] No desktop entries available")
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter out entries that shouldn't be displayed
|
|
||||||
var visibleEntries = desktopEntries.filter(entry => {
|
|
||||||
if (!entry || entry.noDisplay) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log("[AppLauncher] Visible entries:", visibleEntries.length)
|
|
||||||
|
|
||||||
var query = searchText ? searchText.toLowerCase() : "";
|
|
||||||
var results = [];
|
|
||||||
|
|
||||||
// Handle special commands
|
|
||||||
if (query === ">") {
|
|
||||||
results.push({
|
|
||||||
isCommand: true,
|
|
||||||
name: ">calc",
|
|
||||||
content: "Calculator - evaluate mathematical expressions",
|
|
||||||
icon: "calculate",
|
|
||||||
execute: function() {
|
|
||||||
searchText = ">calc ";
|
|
||||||
searchInput.cursorPosition = searchText.length;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
results.push({
|
|
||||||
isCommand: true,
|
|
||||||
name: ">clip",
|
|
||||||
content: "Clipboard history - browse and restore clipboard items",
|
|
||||||
icon: "content_paste",
|
|
||||||
execute: function() {
|
|
||||||
searchText = ">clip ";
|
|
||||||
searchInput.cursorPosition = searchText.length;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle clipboard history
|
|
||||||
if (query.startsWith(">clip")) {
|
|
||||||
if (!Clipboard.initialized) {
|
|
||||||
Clipboard.refresh();
|
|
||||||
}
|
|
||||||
const searchTerm = query.slice(5).trim();
|
|
||||||
|
|
||||||
Clipboard.history.forEach(function(clip, index) {
|
|
||||||
let searchContent = clip.type === 'image' ?
|
|
||||||
clip.mimeType :
|
|
||||||
clip.content || clip;
|
|
||||||
|
|
||||||
if (!searchTerm || searchContent.toLowerCase().includes(searchTerm)) {
|
|
||||||
let entry;
|
|
||||||
if (clip.type === 'image') {
|
|
||||||
entry = {
|
|
||||||
isClipboard: true,
|
|
||||||
name: "Image from " + new Date(clip.timestamp).toLocaleTimeString(),
|
|
||||||
content: "Image: " + clip.mimeType,
|
|
||||||
icon: "image",
|
|
||||||
type: 'image',
|
|
||||||
data: clip.data,
|
|
||||||
execute: function() {
|
|
||||||
const base64Data = clip.data.split(',')[1];
|
|
||||||
copyImageBase64(clip.mimeType, base64Data);
|
|
||||||
Quickshell.execDetached(["notify-send", "Clipboard", "Image copied: " + clip.mimeType]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
const textContent = clip.content || clip;
|
|
||||||
let displayContent = textContent;
|
|
||||||
let previewContent = "";
|
|
||||||
|
|
||||||
displayContent = displayContent.replace(/\s+/g, ' ').trim();
|
|
||||||
|
|
||||||
if (displayContent.length > 50) {
|
|
||||||
previewContent = displayContent;
|
|
||||||
displayContent = displayContent.split('\n')[0].substring(0, 50) + "...";
|
|
||||||
}
|
|
||||||
|
|
||||||
entry = {
|
|
||||||
isClipboard: true,
|
|
||||||
name: displayContent,
|
|
||||||
content: previewContent || textContent,
|
|
||||||
icon: "content_paste",
|
|
||||||
execute: function() {
|
|
||||||
Quickshell.clipboardText = String(textContent);
|
|
||||||
copyText(String(textContent));
|
|
||||||
var preview = (textContent.length > 50) ? textContent.slice(0,50) + "…" : textContent;
|
|
||||||
Quickshell.execDetached(["notify-send", "Clipboard", "Text copied: " + preview]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
results.push(entry);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (results.length === 0) {
|
|
||||||
results.push({
|
|
||||||
isClipboard: true,
|
|
||||||
name: "No clipboard history",
|
|
||||||
content: "No matching clipboard entries found",
|
|
||||||
icon: "content_paste_off"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle calculator
|
|
||||||
if (query.startsWith(">calc")) {
|
|
||||||
var expr = searchText.slice(5).trim();
|
|
||||||
if (expr && isMathExpression(expr)) {
|
|
||||||
var value = safeEval(expr);
|
|
||||||
if (value !== null && value !== undefined && value !== "") {
|
|
||||||
var formattedResult = MathHelper.MathHelper.formatResult(value);
|
|
||||||
results.push({
|
|
||||||
isCalculator: true,
|
|
||||||
name: `Calculator: ${expr} = ${formattedResult}`,
|
|
||||||
result: value,
|
|
||||||
expr: expr,
|
|
||||||
icon: "calculate",
|
|
||||||
execute: function() {
|
|
||||||
Quickshell.clipboardText = String(formattedResult);
|
|
||||||
clipboardTextCopyProcess.copyText(String(formattedResult));
|
|
||||||
Quickshell.execDetached(["notify-send", "Calculator Result", `${expr} = ${formattedResult} (copied to clipboard)`]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Regular app search
|
|
||||||
if (!query) {
|
|
||||||
results = results.concat(visibleEntries.sort(function (a, b) {
|
|
||||||
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
var fuzzyResults = Fuzzysort.go(query, visibleEntries, {
|
|
||||||
keys: ["name", "comment", "genericName"]
|
|
||||||
});
|
|
||||||
results = results.concat(fuzzyResults.map(function (r) {
|
|
||||||
return r.obj;
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("[AppLauncher] Filtered entries:", results.length)
|
|
||||||
return results
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
ColumnLayout {
|
||||||
console.log("[AppLauncher] Component completed")
|
anchors.fill: parent
|
||||||
console.log("[AppLauncher] DesktopEntries available:", typeof DesktopEntries !== 'undefined')
|
anchors.margins: Style.marginLarge * scaling
|
||||||
if (typeof DesktopEntries !== 'undefined') {
|
spacing: Style.marginMedium * scaling
|
||||||
console.log("[AppLauncher] DesktopEntries.entries:", DesktopEntries.entries ? DesktopEntries.entries.length : 'undefined')
|
|
||||||
}
|
|
||||||
// Start clipboard refresh immediately on open
|
|
||||||
updateClipboardHistory();
|
|
||||||
}
|
|
||||||
|
|
||||||
function isMathExpression(str) {
|
// Search bar
|
||||||
// Allow more characters for enhanced math functions
|
Rectangle {
|
||||||
return /^[-+*/().0-9\s\w]+$/.test(str);
|
|
||||||
}
|
|
||||||
|
|
||||||
function safeEval(expr) {
|
|
||||||
return MathHelper.MathHelper.evaluate(expr);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main content container
|
|
||||||
Rectangle {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: Math.min(700 * scaling, parent.width * 0.75)
|
|
||||||
height: Math.min(550 * scaling, parent.height * 0.8)
|
|
||||||
radius: 32 * scaling
|
|
||||||
color: Colors.backgroundPrimary
|
|
||||||
border.color: Colors.outline
|
|
||||||
border.width: Style.borderThin * scaling
|
|
||||||
|
|
||||||
// Subtle gradient background
|
|
||||||
gradient: Gradient {
|
|
||||||
GradientStop { position: 0.0; color: Qt.lighter(Colors.backgroundPrimary, 1.02) }
|
|
||||||
GradientStop { position: 1.0; color: Qt.darker(Colors.backgroundPrimary, 1.1) }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Style.marginLarge * scaling
|
|
||||||
spacing: Style.marginMedium * scaling
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Search bar
|
|
||||||
Rectangle {
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: 40 * scaling
|
Layout.preferredHeight: 40 * scaling
|
||||||
Layout.bottomMargin: Style.marginMedium * scaling
|
Layout.bottomMargin: Style.marginMedium * scaling
|
||||||
|
|
@ -289,67 +286,71 @@ NLoader {
|
||||||
border.width: searchInput.activeFocus ? 2 : 1
|
border.width: searchInput.activeFocus ? 2 : 1
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 12 * scaling
|
anchors.margins: 12 * scaling
|
||||||
spacing: 10 * scaling
|
spacing: 10 * scaling
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "search"
|
text: "search"
|
||||||
font.family: "Material Symbols Outlined"
|
font.family: "Material Symbols Outlined"
|
||||||
font.pointSize: 16 * scaling
|
font.pointSize: 16 * scaling
|
||||||
color: searchInput.activeFocus ? Colors.accentPrimary : Colors.textSecondary
|
color: searchInput.activeFocus ? Colors.accentPrimary : Colors.textSecondary
|
||||||
|
}
|
||||||
|
|
||||||
|
TextField {
|
||||||
|
id: searchInput
|
||||||
|
placeholderText: "Search applications..."
|
||||||
|
color: Colors.textPrimary
|
||||||
|
placeholderTextColor: Colors.textSecondary
|
||||||
|
background: null
|
||||||
|
font.pointSize: 13 * scaling
|
||||||
|
Layout.fillWidth: true
|
||||||
|
onTextChanged: {
|
||||||
|
searchText = text
|
||||||
|
selectedIndex = 0 // Reset selection when search changes
|
||||||
}
|
}
|
||||||
|
selectedTextColor: Colors.textPrimary
|
||||||
TextField {
|
selectionColor: Colors.accentPrimary
|
||||||
id: searchInput
|
padding: 0
|
||||||
placeholderText: "Search applications..."
|
verticalAlignment: TextInput.AlignVCenter
|
||||||
color: Colors.textPrimary
|
leftPadding: 0
|
||||||
placeholderTextColor: Colors.textSecondary
|
rightPadding: 0
|
||||||
background: null
|
topPadding: 0
|
||||||
font.pointSize: 13 * scaling
|
bottomPadding: 0
|
||||||
Layout.fillWidth: true
|
font.bold: true
|
||||||
onTextChanged: {
|
Component.onCompleted: {
|
||||||
searchText = text;
|
contentItem.cursorColor = Colors.textPrimary
|
||||||
selectedIndex = 0; // Reset selection when search changes
|
contentItem.verticalAlignment = TextInput.AlignVCenter
|
||||||
}
|
// Focus the search bar by default
|
||||||
selectedTextColor: Colors.textPrimary
|
Qt.callLater(() => {
|
||||||
selectionColor: Colors.accentPrimary
|
searchInput.forceActiveFocus()
|
||||||
padding: 0
|
})
|
||||||
verticalAlignment: TextInput.AlignVCenter
|
|
||||||
leftPadding: 0
|
|
||||||
rightPadding: 0
|
|
||||||
topPadding: 0
|
|
||||||
bottomPadding: 0
|
|
||||||
font.bold: true
|
|
||||||
Component.onCompleted: {
|
|
||||||
contentItem.cursorColor = Colors.textPrimary
|
|
||||||
contentItem.verticalAlignment = TextInput.AlignVCenter
|
|
||||||
// Focus the search bar by default
|
|
||||||
Qt.callLater(() => {
|
|
||||||
searchInput.forceActiveFocus()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
onActiveFocusChanged: contentItem.cursorColor = Colors.textPrimary
|
|
||||||
|
|
||||||
Keys.onDownPressed: selectNext()
|
|
||||||
Keys.onUpPressed: selectPrev()
|
|
||||||
Keys.onEnterPressed: activateSelected()
|
|
||||||
Keys.onReturnPressed: activateSelected()
|
|
||||||
Keys.onEscapePressed: appLauncherPanel.hide()
|
|
||||||
}
|
}
|
||||||
|
onActiveFocusChanged: contentItem.cursorColor = Colors.textPrimary
|
||||||
|
|
||||||
|
Keys.onDownPressed: selectNext()
|
||||||
|
Keys.onUpPressed: selectPrev()
|
||||||
|
Keys.onEnterPressed: activateSelected()
|
||||||
|
Keys.onReturnPressed: activateSelected()
|
||||||
|
Keys.onEscapePressed: appLauncherPanel.hide()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on border.color {
|
Behavior on border.color {
|
||||||
ColorAnimation { duration: 120 }
|
ColorAnimation {
|
||||||
|
duration: 120
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on border.width {
|
Behavior on border.width {
|
||||||
NumberAnimation { duration: 120 }
|
NumberAnimation {
|
||||||
|
duration: 120
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Applications list
|
// Applications list
|
||||||
ScrollView {
|
ScrollView {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
clip: true
|
clip: true
|
||||||
|
|
@ -357,163 +358,176 @@ NLoader {
|
||||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||||
|
|
||||||
ListView {
|
ListView {
|
||||||
id: appsList
|
id: appsList
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
spacing: 4 * scaling
|
spacing: 4 * scaling
|
||||||
model: filteredEntries
|
model: filteredEntries
|
||||||
currentIndex: selectedIndex
|
currentIndex: selectedIndex
|
||||||
|
|
||||||
delegate: Rectangle {
|
delegate: Rectangle {
|
||||||
width: appsList.width - Style.marginSmall * scaling
|
width: appsList.width - Style.marginSmall * scaling
|
||||||
height: 56 * scaling
|
height: 56 * scaling
|
||||||
radius: 16 * scaling
|
radius: 16 * scaling
|
||||||
property bool isSelected: index === selectedIndex
|
property bool isSelected: index === selectedIndex
|
||||||
color: (appCardArea.containsMouse || isSelected) ? Qt.darker(Colors.accentPrimary, 1.1) : Colors.backgroundSecondary
|
color: (appCardArea.containsMouse || isSelected) ? Qt.darker(
|
||||||
border.color: (appCardArea.containsMouse || isSelected) ? Colors.accentPrimary : "transparent"
|
Colors.accentPrimary,
|
||||||
border.width: (appCardArea.containsMouse || isSelected) ? 2 : 0
|
1.1) : Colors.backgroundSecondary
|
||||||
|
border.color: (appCardArea.containsMouse
|
||||||
Behavior on color {
|
|| isSelected) ? Colors.accentPrimary : "transparent"
|
||||||
ColorAnimation { duration: 150 }
|
border.width: (appCardArea.containsMouse || isSelected) ? 2 : 0
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on border.color {
|
|
||||||
ColorAnimation { duration: 150 }
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on border.width {
|
|
||||||
NumberAnimation { duration: 150 }
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
Behavior on color {
|
||||||
anchors.fill: parent
|
ColorAnimation {
|
||||||
anchors.margins: Style.marginMedium * scaling
|
duration: 150
|
||||||
spacing: Style.marginMedium * scaling
|
}
|
||||||
|
|
||||||
// App icon with background
|
|
||||||
Rectangle {
|
|
||||||
Layout.preferredWidth: 40 * scaling
|
|
||||||
Layout.preferredHeight: 40 * scaling
|
|
||||||
radius: 14 * scaling
|
|
||||||
color: appCardArea.containsMouse ? Qt.darker(Colors.accentPrimary, 1.1) : Colors.backgroundTertiary
|
|
||||||
property bool iconLoaded: (modelData.isCalculator || modelData.isClipboard || modelData.isCommand) || (iconImg.status === Image.Ready && iconImg.source !== "" && iconImg.status !== Image.Error && iconImg.source !== "")
|
|
||||||
visible: !searchText.startsWith(">calc") // Hide icon when in calculator mode
|
|
||||||
|
|
||||||
// Clipboard image display
|
|
||||||
Image {
|
|
||||||
id: clipboardImage
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 6 * scaling
|
|
||||||
visible: modelData.type === 'image'
|
|
||||||
source: modelData.data || ""
|
|
||||||
fillMode: Image.PreserveAspectCrop
|
|
||||||
asynchronous: true
|
|
||||||
cache: true
|
|
||||||
}
|
|
||||||
|
|
||||||
IconImage {
|
|
||||||
id: iconImg
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 6 * scaling
|
|
||||||
asynchronous: true
|
|
||||||
source: modelData.isCalculator ? "calculate" :
|
|
||||||
modelData.isClipboard ? (modelData.type === 'image' ? "" : "content_paste") :
|
|
||||||
modelData.isCommand ? modelData.icon :
|
|
||||||
(modelData.icon ? Quickshell.iconPath(modelData.icon, "application-x-executable") : "")
|
|
||||||
visible: (modelData.isCalculator || modelData.isClipboard || modelData.isCommand || parent.iconLoaded) && modelData.type !== 'image'
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback icon container
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 6 * scaling
|
|
||||||
radius: 10 * scaling
|
|
||||||
color: Colors.accentPrimary
|
|
||||||
opacity: 0.3
|
|
||||||
visible: !parent.iconLoaded
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
visible: !parent.iconLoaded && !(modelData.isCalculator || modelData.isClipboard || modelData.isCommand)
|
|
||||||
text: modelData.name ? modelData.name.charAt(0).toUpperCase() : "?"
|
|
||||||
font.pointSize: 18 * scaling
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: Colors.accentPrimary
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation { duration: 150 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// App info
|
|
||||||
ColumnLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: 2 * scaling
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: modelData.name || "Unknown"
|
|
||||||
font.pointSize: 14 * scaling
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: Colors.textPrimary
|
|
||||||
elide: Text.ElideRight
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
NText {
|
|
||||||
text: modelData.isCalculator ? (modelData.expr + " = " + modelData.result) :
|
|
||||||
modelData.isClipboard ? modelData.content :
|
|
||||||
modelData.isCommand ? modelData.content :
|
|
||||||
(modelData.genericName || modelData.comment || "")
|
|
||||||
font.pointSize: 11 * scaling
|
|
||||||
color: (appCardArea.containsMouse || isSelected) ? Colors.textPrimary : Colors.textSecondary
|
|
||||||
elide: Text.ElideRight
|
|
||||||
Layout.fillWidth: true
|
|
||||||
visible: text !== ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: appCardArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
selectedIndex = index;
|
|
||||||
activateSelected();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// No results message
|
Behavior on border.color {
|
||||||
NText {
|
ColorAnimation {
|
||||||
|
duration: 150
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on border.width {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 150
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Style.marginMedium * scaling
|
||||||
|
spacing: Style.marginMedium * scaling
|
||||||
|
|
||||||
|
// App icon with background
|
||||||
|
Rectangle {
|
||||||
|
Layout.preferredWidth: 40 * scaling
|
||||||
|
Layout.preferredHeight: 40 * scaling
|
||||||
|
radius: 14 * scaling
|
||||||
|
color: appCardArea.containsMouse ? Qt.darker(Colors.accentPrimary,
|
||||||
|
1.1) : Colors.backgroundTertiary
|
||||||
|
property bool iconLoaded: (modelData.isCalculator || modelData.isClipboard
|
||||||
|
|| modelData.isCommand) || (iconImg.status === Image.Ready
|
||||||
|
&& iconImg.source !== ""
|
||||||
|
&& iconImg.status !== Image.Error
|
||||||
|
&& iconImg.source !== "")
|
||||||
|
visible: !searchText.startsWith(">calc") // Hide icon when in calculator mode
|
||||||
|
|
||||||
|
// Clipboard image display
|
||||||
|
Image {
|
||||||
|
id: clipboardImage
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 6 * scaling
|
||||||
|
visible: modelData.type === 'image'
|
||||||
|
source: modelData.data || ""
|
||||||
|
fillMode: Image.PreserveAspectCrop
|
||||||
|
asynchronous: true
|
||||||
|
cache: true
|
||||||
|
}
|
||||||
|
|
||||||
|
IconImage {
|
||||||
|
id: iconImg
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 6 * scaling
|
||||||
|
asynchronous: true
|
||||||
|
source: modelData.isCalculator ? "calculate" : modelData.isClipboard ? (modelData.type === 'image' ? "" : "content_paste") : modelData.isCommand ? modelData.icon : (modelData.icon ? Quickshell.iconPath(modelData.icon, "application-x-executable") : "")
|
||||||
|
visible: (modelData.isCalculator || modelData.isClipboard || modelData.isCommand
|
||||||
|
|| parent.iconLoaded) && modelData.type !== 'image'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback icon container
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 6 * scaling
|
||||||
|
radius: 10 * scaling
|
||||||
|
color: Colors.accentPrimary
|
||||||
|
opacity: 0.3
|
||||||
|
visible: !parent.iconLoaded
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
visible: !parent.iconLoaded && !(modelData.isCalculator || modelData.isClipboard
|
||||||
|
|| modelData.isCommand)
|
||||||
|
text: modelData.name ? modelData.name.charAt(0).toUpperCase() : "?"
|
||||||
|
font.pointSize: 18 * scaling
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Colors.accentPrimary
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: 150
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// App info
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 2 * scaling
|
||||||
|
|
||||||
|
NText {
|
||||||
|
text: modelData.name || "Unknown"
|
||||||
|
font.pointSize: 14 * scaling
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Colors.textPrimary
|
||||||
|
elide: Text.ElideRight
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
NText {
|
||||||
|
text: modelData.isCalculator ? (modelData.expr + " = " + modelData.result) : modelData.isClipboard ? modelData.content : modelData.isCommand ? modelData.content : (modelData.genericName || modelData.comment || "")
|
||||||
|
font.pointSize: 11 * scaling
|
||||||
|
color: (appCardArea.containsMouse
|
||||||
|
|| isSelected) ? Colors.textPrimary : Colors.textSecondary
|
||||||
|
elide: Text.ElideRight
|
||||||
|
Layout.fillWidth: true
|
||||||
|
visible: text !== ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: appCardArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
selectedIndex = index
|
||||||
|
activateSelected()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No results message
|
||||||
|
NText {
|
||||||
text: searchText.trim() !== "" ? "No applications found" : "No applications available"
|
text: searchText.trim() !== "" ? "No applications found" : "No applications available"
|
||||||
font.pointSize: Style.fontSizeLarge * scaling
|
font.pointSize: Style.fontSizeLarge * scaling
|
||||||
color: Colors.textSecondary
|
color: Colors.textSecondary
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
visible: filteredEntries.length === 0
|
visible: filteredEntries.length === 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Results count
|
// Results count
|
||||||
NText {
|
NText {
|
||||||
text: searchText.startsWith(">clip") ? `${filteredEntries.length} clipboard item${filteredEntries.length !== 1 ? 's' : ''}` :
|
text: searchText.startsWith(
|
||||||
searchText.startsWith(">calc") ? `${filteredEntries.length} result${filteredEntries.length !== 1 ? 's' : ''}` :
|
">clip") ? `${filteredEntries.length} clipboard item${filteredEntries.length
|
||||||
`${filteredEntries.length} application${filteredEntries.length !== 1 ? 's' : ''}`
|
!== 1 ? 's' : ''}` : searchText.startsWith(
|
||||||
|
">calc") ? `${filteredEntries.length} result${filteredEntries.length
|
||||||
|
!== 1 ? 's' : ''}` : `${filteredEntries.length} application${filteredEntries.length !== 1 ? 's' : ''}`
|
||||||
font.pointSize: Style.fontSizeSmall * scaling
|
font.pointSize: Style.fontSizeSmall * scaling
|
||||||
color: Colors.textSecondary
|
color: Colors.textSecondary
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
visible: searchText.trim() !== ""
|
visible: searchText.trim() !== ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,12 @@ import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
NLoader {
|
NLoader {
|
||||||
isLoaded: Settings.data.general.showDock
|
isLoaded: Settings.data.general.showDock
|
||||||
content: Component {
|
content: Component {
|
||||||
Variants {
|
Variants {
|
||||||
model: Quickshell.screens
|
model: Quickshell.screens
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
property var modelData
|
property var modelData
|
||||||
readonly property real scaling: Scaling.scale(modelData)
|
readonly property real scaling: Scaling.scale(modelData)
|
||||||
|
|
||||||
|
|
@ -38,308 +38,321 @@ NLoader {
|
||||||
property var contextMenuToplevel: null
|
property var contextMenuToplevel: null
|
||||||
|
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: dockWindow
|
id: dockWindow
|
||||||
visible: true
|
visible: true
|
||||||
screen: modelData
|
screen: modelData
|
||||||
|
exclusionMode: ExclusionMode.Ignore
|
||||||
|
anchors.bottom: true
|
||||||
|
anchors.left: true
|
||||||
|
anchors.right: true
|
||||||
|
focusable: false
|
||||||
|
color: "transparent"
|
||||||
|
implicitHeight: 60
|
||||||
|
|
||||||
|
// Timer for auto-hide delay
|
||||||
|
Timer {
|
||||||
|
id: hideTimer
|
||||||
|
interval: hideDelay
|
||||||
|
onTriggered: if (autoHide && !dockHovered && !anyAppHovered)
|
||||||
|
hidden = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timer for show delay
|
||||||
|
Timer {
|
||||||
|
id: showTimer
|
||||||
|
interval: showDelay
|
||||||
|
onTriggered: hidden = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Behavior for smooth hide/show animations
|
||||||
|
Behavior on margins.bottom {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: hidden ? hideAnimationDuration : showAnimationDuration
|
||||||
|
easing.type: Easing.InOutQuad
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mouse area at screen bottom to detect entry and keep dock visible
|
||||||
|
MouseArea {
|
||||||
|
id: screenEdgeMouseArea
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
height: 10
|
||||||
|
hoverEnabled: true
|
||||||
|
propagateComposedEvents: true
|
||||||
|
|
||||||
|
onEntered: if (autoHide && hidden)
|
||||||
|
showTimer.start()
|
||||||
|
onExited: if (autoHide && !hidden && !dockHovered && !anyAppHovered)
|
||||||
|
hideTimer.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
margins.bottom: hidden ? -(fullHeight - peekHeight) : 0
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
enabled: contextMenuVisible
|
||||||
|
onClicked: {
|
||||||
|
contextMenuVisible = false
|
||||||
|
contextMenuTarget = null
|
||||||
|
contextMenuToplevel = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: dockContainer
|
||||||
|
width: dock.width + 40
|
||||||
|
height: 50
|
||||||
|
color: Colors.backgroundSecondary
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
topLeftRadius: 20
|
||||||
|
topRightRadius: 20
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: dockMouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
propagateComposedEvents: true
|
||||||
|
|
||||||
|
onEntered: {
|
||||||
|
dockHovered = true
|
||||||
|
if (autoHide) {
|
||||||
|
showTimer.stop()
|
||||||
|
hideTimer.stop()
|
||||||
|
hidden = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onExited: {
|
||||||
|
dockHovered = false
|
||||||
|
if (autoHide && !anyAppHovered && !contextMenuVisible)
|
||||||
|
hideTimer.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: dock
|
||||||
|
width: runningAppsRow.width
|
||||||
|
height: parent.height - 10
|
||||||
|
anchors.centerIn: parent
|
||||||
|
|
||||||
|
NTooltip {
|
||||||
|
id: appTooltip
|
||||||
|
visible: false
|
||||||
|
positionAbove: true
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAppIcon(toplevel: Toplevel): string {
|
||||||
|
if (!toplevel)
|
||||||
|
return ""
|
||||||
|
let icon = Quickshell.iconPath(toplevel.appId?.toLowerCase(), true)
|
||||||
|
if (!icon)
|
||||||
|
icon = Quickshell.iconPath(toplevel.appId, true)
|
||||||
|
if (!icon)
|
||||||
|
icon = Quickshell.iconPath(toplevel.title?.toLowerCase(), true)
|
||||||
|
if (!icon)
|
||||||
|
icon = Quickshell.iconPath(toplevel.title, true)
|
||||||
|
return icon || Quickshell.iconPath("application-x-executable", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: runningAppsRow
|
||||||
|
spacing: 8
|
||||||
|
height: parent.height
|
||||||
|
anchors.centerIn: parent
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: ToplevelManager ? ToplevelManager.toplevels : null
|
||||||
|
|
||||||
|
delegate: Rectangle {
|
||||||
|
id: appButton
|
||||||
|
width: 36
|
||||||
|
height: 36
|
||||||
|
radius: 18
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
property bool isActive: ToplevelManager.activeToplevel
|
||||||
|
&& ToplevelManager.activeToplevel === modelData
|
||||||
|
property bool hovered: appMouseArea.containsMouse
|
||||||
|
property string appId: modelData ? modelData.appId : ""
|
||||||
|
property string appTitle: modelData ? modelData.title : ""
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: 150
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: appIcon
|
||||||
|
width: 28
|
||||||
|
height: 28
|
||||||
|
anchors.centerIn: parent
|
||||||
|
source: dock.getAppIcon(modelData)
|
||||||
|
visible: source.toString() !== ""
|
||||||
|
smooth: false
|
||||||
|
mipmap: false
|
||||||
|
antialiasing: false
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
visible: !appIcon.visible
|
||||||
|
text: appButton.appId ? appButton.appId.charAt(0).toUpperCase() : "?"
|
||||||
|
font.pixelSize: 14
|
||||||
|
font.bold: true
|
||||||
|
color: appButton.isActive ? Colors.accentPrimary : Colors.textPrimary
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: appMouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||||
|
|
||||||
|
onEntered: {
|
||||||
|
anyAppHovered = true
|
||||||
|
const appName = appButton.appTitle || appButton.appId || "Unknown"
|
||||||
|
appTooltip.text = appName.length > 40 ? appName.substring(0, 37) + "..." : appName
|
||||||
|
appTooltip.target = appButton
|
||||||
|
appTooltip.isVisible = true
|
||||||
|
if (autoHide) {
|
||||||
|
showTimer.stop()
|
||||||
|
hideTimer.stop()
|
||||||
|
hidden = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onExited: {
|
||||||
|
anyAppHovered = false
|
||||||
|
appTooltip.hide()
|
||||||
|
if (autoHide && !dockHovered && !contextMenuVisible)
|
||||||
|
hideTimer.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: function (mouse) {
|
||||||
|
if (mouse.button === Qt.MiddleButton && modelData?.close) {
|
||||||
|
modelData.close()
|
||||||
|
}
|
||||||
|
if (mouse.button === Qt.LeftButton && modelData?.activate) {
|
||||||
|
modelData.activate()
|
||||||
|
}
|
||||||
|
if (mouse.button === Qt.RightButton) {
|
||||||
|
appTooltip.hide()
|
||||||
|
contextMenuTarget = appButton
|
||||||
|
contextMenuToplevel = modelData
|
||||||
|
contextMenuVisible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
visible: isActive
|
||||||
|
width: 20
|
||||||
|
height: 3
|
||||||
|
color: Colors.accentPrimary
|
||||||
|
radius: 1.5
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.bottomMargin: 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context Menu
|
||||||
|
PanelWindow {
|
||||||
|
id: contextMenuWindow
|
||||||
|
visible: contextMenuVisible
|
||||||
|
screen: dockWindow.screen
|
||||||
exclusionMode: ExclusionMode.Ignore
|
exclusionMode: ExclusionMode.Ignore
|
||||||
anchors.bottom: true
|
anchors.bottom: true
|
||||||
anchors.left: true
|
anchors.left: true
|
||||||
anchors.right: true
|
anchors.right: true
|
||||||
focusable: false
|
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
implicitHeight: 60
|
focusable: false
|
||||||
|
|
||||||
// Timer for auto-hide delay
|
|
||||||
Timer {
|
|
||||||
id: hideTimer
|
|
||||||
interval: hideDelay
|
|
||||||
onTriggered: if (autoHide && !dockHovered && !anyAppHovered) hidden = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Timer for show delay
|
|
||||||
Timer {
|
|
||||||
id: showTimer
|
|
||||||
interval: showDelay
|
|
||||||
onTriggered: hidden = false
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Behavior for smooth hide/show animations
|
|
||||||
Behavior on margins.bottom {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: hidden ? hideAnimationDuration : showAnimationDuration
|
|
||||||
easing.type: Easing.InOutQuad
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mouse area at screen bottom to detect entry and keep dock visible
|
|
||||||
MouseArea {
|
|
||||||
id: screenEdgeMouseArea
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
height: 10
|
|
||||||
hoverEnabled: true
|
|
||||||
propagateComposedEvents: true
|
|
||||||
|
|
||||||
onEntered: if (autoHide && hidden) showTimer.start()
|
|
||||||
onExited: if (autoHide && !hidden && !dockHovered && !anyAppHovered) hideTimer.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
margins.bottom: hidden ? -(fullHeight - peekHeight) : 0
|
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
enabled: contextMenuVisible
|
onClicked: {
|
||||||
onClicked: {
|
contextMenuVisible = false
|
||||||
contextMenuVisible = false
|
contextMenuTarget = null
|
||||||
contextMenuTarget = null
|
contextMenuToplevel = null
|
||||||
contextMenuToplevel = null
|
hidden = true // Hide dock when context menu closes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: dockContainer
|
id: contextMenuContainer
|
||||||
width: dock.width + 40
|
width: 80
|
||||||
height: 50
|
height: 32
|
||||||
color: Colors.backgroundSecondary
|
radius: 8
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
color: closeMouseArea.containsMouse ? Colors.hover : Colors.backgroundPrimary
|
||||||
anchors.bottom: parent.bottom
|
border.color: Colors.outline
|
||||||
topLeftRadius: 20
|
border.width: 1
|
||||||
topRightRadius: 20
|
|
||||||
|
|
||||||
MouseArea {
|
x: {
|
||||||
id: dockMouseArea
|
if (!contextMenuTarget)
|
||||||
anchors.fill: parent
|
return 0
|
||||||
hoverEnabled: true
|
const pos = contextMenuTarget.mapToItem(null, 0, 0)
|
||||||
propagateComposedEvents: true
|
let xPos = pos.x + (contextMenuTarget.width - width) / 2
|
||||||
|
return Math.max(0, Math.min(xPos, dockWindow.width - width))
|
||||||
|
}
|
||||||
|
|
||||||
onEntered: {
|
y: {
|
||||||
dockHovered = true
|
if (!contextMenuTarget)
|
||||||
if (autoHide) {
|
return 0
|
||||||
showTimer.stop()
|
const pos = contextMenuTarget.mapToItem(null, 0, 0)
|
||||||
hideTimer.stop()
|
return pos.y - height + 32
|
||||||
hidden = false
|
}
|
||||||
}
|
|
||||||
}
|
Text {
|
||||||
onExited: {
|
anchors.centerIn: parent
|
||||||
dockHovered = false
|
text: "Close"
|
||||||
if (autoHide && !anyAppHovered && !contextMenuVisible) hideTimer.start()
|
font.pixelSize: 14
|
||||||
}
|
color: Colors.textPrimary
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: closeMouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
if (contextMenuToplevel?.close)
|
||||||
|
contextMenuToplevel.close()
|
||||||
|
contextMenuVisible = false
|
||||||
|
hidden = true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Item {
|
// Animation
|
||||||
id: dock
|
scale: contextMenuVisible ? 1 : 0.9
|
||||||
width: runningAppsRow.width
|
opacity: contextMenuVisible ? 1 : 0
|
||||||
height: parent.height - 10
|
transformOrigin: Item.Bottom
|
||||||
anchors.centerIn: parent
|
|
||||||
|
|
||||||
NTooltip {
|
|
||||||
id: appTooltip
|
|
||||||
visible: false
|
|
||||||
positionAbove: true
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAppIcon(toplevel: Toplevel): string {
|
|
||||||
if (!toplevel) return "";
|
|
||||||
let icon = Quickshell.iconPath(toplevel.appId?.toLowerCase(), true);
|
|
||||||
if (!icon) icon = Quickshell.iconPath(toplevel.appId, true);
|
|
||||||
if (!icon) icon = Quickshell.iconPath(toplevel.title?.toLowerCase(), true);
|
|
||||||
if (!icon) icon = Quickshell.iconPath(toplevel.title, true);
|
|
||||||
return icon || Quickshell.iconPath("application-x-executable", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: runningAppsRow
|
|
||||||
spacing: 8
|
|
||||||
height: parent.height
|
|
||||||
anchors.centerIn: parent
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: ToplevelManager ? ToplevelManager.toplevels : null
|
|
||||||
|
|
||||||
delegate: Rectangle {
|
|
||||||
id: appButton
|
|
||||||
width: 36
|
|
||||||
height: 36
|
|
||||||
radius: 18
|
|
||||||
color:"transparent"
|
|
||||||
|
|
||||||
property bool isActive: ToplevelManager.activeToplevel && ToplevelManager.activeToplevel === modelData
|
|
||||||
property bool hovered: appMouseArea.containsMouse
|
|
||||||
property string appId: modelData ? modelData.appId : ""
|
|
||||||
property string appTitle: modelData ? modelData.title : ""
|
|
||||||
|
|
||||||
Behavior on color { ColorAnimation { duration: 150 } }
|
|
||||||
|
|
||||||
Image {
|
|
||||||
id: appIcon
|
|
||||||
width: 28
|
|
||||||
height: 28
|
|
||||||
anchors.centerIn: parent
|
|
||||||
source: dock.getAppIcon(modelData)
|
|
||||||
visible: source.toString() !== ""
|
|
||||||
smooth: false
|
|
||||||
mipmap: false
|
|
||||||
antialiasing: false
|
|
||||||
fillMode: Image.PreserveAspectFit
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
visible: !appIcon.visible
|
|
||||||
text: appButton.appId ? appButton.appId.charAt(0).toUpperCase() : "?"
|
|
||||||
font.pixelSize: 14
|
|
||||||
font.bold: true
|
|
||||||
color: appButton.isActive ? Colors.accentPrimary : Colors.textPrimary
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: appMouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
|
||||||
|
|
||||||
onEntered: {
|
|
||||||
anyAppHovered = true
|
|
||||||
const appName = appButton.appTitle || appButton.appId || "Unknown"
|
|
||||||
appTooltip.text = appName.length > 40 ? appName.substring(0, 37) + "..." : appName
|
|
||||||
appTooltip.target = appButton
|
|
||||||
appTooltip.isVisible = true
|
|
||||||
if (autoHide) {
|
|
||||||
showTimer.stop()
|
|
||||||
hideTimer.stop()
|
|
||||||
hidden = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onExited: {
|
|
||||||
anyAppHovered = false
|
|
||||||
appTooltip.hide()
|
|
||||||
if (autoHide && !dockHovered && !contextMenuVisible) hideTimer.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
onClicked: function(mouse) {
|
|
||||||
if (mouse.button === Qt.MiddleButton && modelData?.close) {
|
|
||||||
modelData.close()
|
|
||||||
}
|
|
||||||
if (mouse.button === Qt.LeftButton && modelData?.activate) {
|
|
||||||
modelData.activate()
|
|
||||||
}
|
|
||||||
if (mouse.button === Qt.RightButton) {
|
|
||||||
appTooltip.hide()
|
|
||||||
contextMenuTarget = appButton
|
|
||||||
contextMenuToplevel = modelData
|
|
||||||
contextMenuVisible = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
visible: isActive
|
|
||||||
width: 20
|
|
||||||
height: 3
|
|
||||||
color: Colors.accentPrimary
|
|
||||||
radius: 1.5
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
anchors.bottomMargin: 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
Behavior on scale {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 150
|
||||||
|
easing.type: Easing.OutBack
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 100
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Context Menu
|
|
||||||
PanelWindow {
|
|
||||||
id: contextMenuWindow
|
|
||||||
visible: contextMenuVisible
|
|
||||||
screen: dockWindow.screen
|
|
||||||
exclusionMode: ExclusionMode.Ignore
|
|
||||||
anchors.bottom: true
|
|
||||||
anchors.left: true
|
|
||||||
anchors.right: true
|
|
||||||
color: "transparent"
|
|
||||||
focusable: false
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
onClicked: {
|
|
||||||
contextMenuVisible = false
|
|
||||||
contextMenuTarget = null
|
|
||||||
contextMenuToplevel = null
|
|
||||||
hidden = true // Hide dock when context menu closes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: contextMenuContainer
|
|
||||||
width: 80
|
|
||||||
height: 32
|
|
||||||
radius: 8
|
|
||||||
color: closeMouseArea.containsMouse ? Colors.hover : Colors.backgroundPrimary
|
|
||||||
border.color: Colors.outline
|
|
||||||
border.width: 1
|
|
||||||
|
|
||||||
x: {
|
|
||||||
if (!contextMenuTarget) return 0
|
|
||||||
const pos = contextMenuTarget.mapToItem(null, 0, 0)
|
|
||||||
let xPos = pos.x + (contextMenuTarget.width - width) / 2
|
|
||||||
return Math.max(0, Math.min(xPos, dockWindow.width - width))
|
|
||||||
}
|
|
||||||
|
|
||||||
y: {
|
|
||||||
if (!contextMenuTarget) return 0
|
|
||||||
const pos = contextMenuTarget.mapToItem(null, 0, 0)
|
|
||||||
return pos.y - height + 32
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: "Close"
|
|
||||||
font.pixelSize: 14
|
|
||||||
color: Colors.textPrimary
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: closeMouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
if (contextMenuToplevel?.close) contextMenuToplevel.close()
|
|
||||||
contextMenuVisible = false
|
|
||||||
hidden = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Animation
|
|
||||||
scale: contextMenuVisible ? 1 : 0.9
|
|
||||||
opacity: contextMenuVisible ? 1 : 0
|
|
||||||
transformOrigin: Item.Bottom
|
|
||||||
|
|
||||||
Behavior on scale {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: 150
|
|
||||||
easing.type: Easing.OutBack
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation { duration: 100 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -540,7 +540,7 @@ WlSessionLock {
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: lock.authenticating ? "EXECUTING..." : "EXECUTE"
|
text: lock.authenticating ? "EXECUTING" : "EXECUTE"
|
||||||
color: executeButtonArea.containsMouse ? Colors.onAccent : Colors.accentPrimary
|
color: executeButtonArea.containsMouse ? Colors.onAccent : Colors.accentPrimary
|
||||||
font.family: "DejaVu Sans Mono"
|
font.family: "DejaVu Sans Mono"
|
||||||
font.pointSize: Style.fontSizeMedium
|
font.pointSize: Style.fontSizeMedium
|
||||||
|
|
|
||||||
|
|
@ -236,7 +236,8 @@ ColumnLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
NText {
|
NText {
|
||||||
text: (modelData.contributions || 0) + " " + ((modelData.contributions || 0) === 1 ? "commit" : "commits")
|
text: (modelData.contributions || 0) + " " + ((modelData.contributions
|
||||||
|
|| 0) === 1 ? "commit" : "commits")
|
||||||
font.pointSize: Style.fontSizeSmall * scaling
|
font.pointSize: Style.fontSizeSmall * scaling
|
||||||
color: contributorArea.containsMouse ? Colors.backgroundPrimary : Colors.textSecondary
|
color: contributorArea.containsMouse ? Colors.backgroundPrimary : Colors.textSecondary
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,134 +6,133 @@ import Quickshell.Io
|
||||||
import qs.Services
|
import qs.Services
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property var history: []
|
property var history: []
|
||||||
property bool initialized: false
|
property bool initialized: false
|
||||||
|
|
||||||
// Internal state
|
// Internal state
|
||||||
property bool _enabled: true
|
property bool _enabled: true
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
interval: 1000
|
interval: 1000
|
||||||
repeat: true
|
repeat: true
|
||||||
running: root._enabled
|
running: root._enabled
|
||||||
onTriggered: root.refresh()
|
onTriggered: root.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect current clipboard types (text/image)
|
// Detect current clipboard types (text/image)
|
||||||
Process {
|
Process {
|
||||||
id: typeProcess
|
id: typeProcess
|
||||||
property bool isLoading: false
|
property bool isLoading: false
|
||||||
property var currentTypes: []
|
property var currentTypes: []
|
||||||
|
|
||||||
onExited: (exitCode, exitStatus) => {
|
onExited: (exitCode, exitStatus) => {
|
||||||
if (exitCode === 0) {
|
if (exitCode === 0) {
|
||||||
currentTypes = String(stdout.text).trim().split('\n').filter(t => t)
|
currentTypes = String(stdout.text).trim().split('\n').filter(t => t)
|
||||||
|
|
||||||
const imageType = currentTypes.find(t => t.startsWith('image/'))
|
const imageType = currentTypes.find(t => t.startsWith('image/'))
|
||||||
if (imageType) {
|
if (imageType) {
|
||||||
imageProcess.mimeType = imageType
|
imageProcess.mimeType = imageType
|
||||||
imageProcess.command = ["sh", "-c", `wl-paste -n -t "${imageType}" | base64 -w 0`]
|
imageProcess.command = ["sh", "-c", `wl-paste -n -t "${imageType}" | base64 -w 0`]
|
||||||
imageProcess.running = true
|
imageProcess.running = true
|
||||||
} else {
|
} else {
|
||||||
textProcess.command = ["wl-paste", "-n", "--type", "text/plain"]
|
textProcess.command = ["wl-paste", "-n", "--type", "text/plain"]
|
||||||
textProcess.running = true
|
textProcess.running = true
|
||||||
}
|
|
||||||
} else {
|
|
||||||
typeProcess.isLoading = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
stdout: StdioCollector {}
|
typeProcess.isLoading = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read image data
|
stdout: StdioCollector {}
|
||||||
Process {
|
}
|
||||||
id: imageProcess
|
|
||||||
property string mimeType: ""
|
|
||||||
|
|
||||||
onExited: (exitCode, exitStatus) => {
|
// Read image data
|
||||||
if (exitCode === 0) {
|
Process {
|
||||||
const base64 = stdout.text.trim()
|
id: imageProcess
|
||||||
if (base64) {
|
property string mimeType: ""
|
||||||
const entry = {
|
|
||||||
type: 'image',
|
|
||||||
mimeType: mimeType,
|
|
||||||
data: `data:${mimeType};base64,${base64}`,
|
|
||||||
timestamp: new Date().getTime()
|
|
||||||
}
|
|
||||||
|
|
||||||
const exists = root.history.find(item => item.type === 'image' && item.data === entry.data)
|
onExited: (exitCode, exitStatus) => {
|
||||||
if (!exists) {
|
if (exitCode === 0) {
|
||||||
root.history = [entry, ...root.history].slice(0, 20)
|
const base64 = stdout.text.trim()
|
||||||
}
|
if (base64) {
|
||||||
}
|
const entry = {
|
||||||
}
|
"type": 'image',
|
||||||
|
"mimeType": mimeType,
|
||||||
|
"data": `data:${mimeType};base64,${base64}`,
|
||||||
|
"timestamp": new Date().getTime()
|
||||||
|
}
|
||||||
|
|
||||||
if (!textProcess.isLoading) {
|
const exists = root.history.find(item => item.type === 'image' && item.data === entry.data)
|
||||||
root.initialized = true
|
if (!exists) {
|
||||||
}
|
root.history = [entry, ...root.history].slice(0, 20)
|
||||||
typeProcess.isLoading = false
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
stdout: StdioCollector {}
|
if (!textProcess.isLoading) {
|
||||||
|
root.initialized = true
|
||||||
|
}
|
||||||
|
typeProcess.isLoading = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read text data
|
stdout: StdioCollector {}
|
||||||
Process {
|
}
|
||||||
id: textProcess
|
|
||||||
property bool isLoading: false
|
|
||||||
|
|
||||||
onExited: (exitCode, exitStatus) => {
|
// Read text data
|
||||||
if (exitCode === 0) {
|
Process {
|
||||||
const content = String(stdout.text).trim()
|
id: textProcess
|
||||||
if (content) {
|
property bool isLoading: false
|
||||||
const entry = {
|
|
||||||
type: 'text',
|
|
||||||
content: content,
|
|
||||||
timestamp: new Date().getTime()
|
|
||||||
}
|
|
||||||
|
|
||||||
const exists = root.history.find(item => {
|
onExited: (exitCode, exitStatus) => {
|
||||||
if (item.type === 'text') {
|
if (exitCode === 0) {
|
||||||
return item.content === content
|
const content = String(stdout.text).trim()
|
||||||
}
|
if (content) {
|
||||||
return item === content
|
const entry = {
|
||||||
})
|
"type": 'text',
|
||||||
|
"content": content,
|
||||||
|
"timestamp": new Date().getTime()
|
||||||
|
}
|
||||||
|
|
||||||
if (!exists) {
|
const exists = root.history.find(item => {
|
||||||
const newHistory = root.history.map(item => {
|
if (item.type === 'text') {
|
||||||
if (typeof item === 'string') {
|
return item.content === content
|
||||||
return {
|
}
|
||||||
type: 'text',
|
return item === content
|
||||||
content: item,
|
})
|
||||||
timestamp: new Date().getTime()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return item
|
|
||||||
})
|
|
||||||
|
|
||||||
root.history = [entry, ...newHistory].slice(0, 20)
|
if (!exists) {
|
||||||
}
|
const newHistory = root.history.map(item => {
|
||||||
}
|
if (typeof item === 'string') {
|
||||||
} else {
|
return {
|
||||||
textProcess.isLoading = false
|
"type": 'text',
|
||||||
}
|
"content": item,
|
||||||
|
"timestamp": new Date().getTime()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return item
|
||||||
|
})
|
||||||
|
|
||||||
root.initialized = true
|
root.history = [entry, ...newHistory].slice(0, 20)
|
||||||
typeProcess.isLoading = false
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
textProcess.isLoading = false
|
||||||
|
}
|
||||||
|
|
||||||
stdout: StdioCollector {}
|
root.initialized = true
|
||||||
|
typeProcess.isLoading = false
|
||||||
}
|
}
|
||||||
|
|
||||||
function refresh() {
|
stdout: StdioCollector {}
|
||||||
if (!typeProcess.isLoading && !textProcess.isLoading) {
|
}
|
||||||
typeProcess.isLoading = true
|
|
||||||
typeProcess.command = ["wl-paste", "-l"]
|
function refresh() {
|
||||||
typeProcess.running = true
|
if (!typeProcess.isLoading && !textProcess.isLoading) {
|
||||||
}
|
typeProcess.isLoading = true
|
||||||
|
typeProcess.command = ["wl-paste", "-l"]
|
||||||
|
typeProcess.running = true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -82,10 +82,10 @@ Singleton {
|
||||||
data.timestamp = Time.timestamp
|
data.timestamp = Time.timestamp
|
||||||
console.log("[GitHub] Saving data to cache file:", githubDataFile)
|
console.log("[GitHub] Saving data to cache file:", githubDataFile)
|
||||||
console.log("[GitHub] Data to save - version:", data.version, "contributors:", data.contributors.length)
|
console.log("[GitHub] Data to save - version:", data.version, "contributors:", data.contributors.length)
|
||||||
|
|
||||||
// Ensure cache directory exists
|
// Ensure cache directory exists
|
||||||
Quickshell.execDetached(["mkdir", "-p", Settings.cacheDir])
|
Quickshell.execDetached(["mkdir", "-p", Settings.cacheDir])
|
||||||
|
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
// Use direct ID reference to the FileView
|
// Use direct ID reference to the FileView
|
||||||
githubDataFileView.writeAdapter()
|
githubDataFileView.writeAdapter()
|
||||||
|
|
@ -147,7 +147,8 @@ Singleton {
|
||||||
console.log("[GitHub] Raw contributors response length:", response ? response.length : 0)
|
console.log("[GitHub] Raw contributors response length:", response ? response.length : 0)
|
||||||
if (response && response.trim()) {
|
if (response && response.trim()) {
|
||||||
const data = JSON.parse(response)
|
const data = JSON.parse(response)
|
||||||
console.log("[GitHub] Parsed contributors data type:", typeof data, "length:", Array.isArray(data) ? data.length : "not array")
|
console.log("[GitHub] Parsed contributors data type:", typeof data, "length:",
|
||||||
|
Array.isArray(data) ? data.length : "not array")
|
||||||
root.data.contributors = data || []
|
root.data.contributors = data || []
|
||||||
root.contributors = root.data.contributors
|
root.contributors = root.data.contributors
|
||||||
console.log("[GitHub] Contributors fetched from GitHub:", root.contributors.length)
|
console.log("[GitHub] Contributors fetched from GitHub:", root.contributors.length)
|
||||||
|
|
|
||||||
|
|
@ -160,6 +160,7 @@ Singleton {
|
||||||
running: false
|
running: false
|
||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
onStreamFinished: {
|
onStreamFinished: {
|
||||||
|
|
||||||
// console.log(this.text)
|
// console.log(this.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue