mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
Settings: all tabed - screenrecorder will be a plugin soon
This commit is contained in:
@@ -995,6 +995,10 @@
|
||||
}
|
||||
},
|
||||
"support": "Support us",
|
||||
"tabs": {
|
||||
"contributors": "Contributors",
|
||||
"version": "Version"
|
||||
},
|
||||
"title": "About"
|
||||
},
|
||||
"audio": {
|
||||
@@ -1063,9 +1067,20 @@
|
||||
"tabs": {
|
||||
"devices": "Devices",
|
||||
"media": "Media",
|
||||
"visualizer": "Visualizer",
|
||||
"volumes": "Volumes"
|
||||
},
|
||||
"title": "Audio",
|
||||
"visualizer": {
|
||||
"frame-rate": {
|
||||
"description": "Higher rates are smoother but use more resources.",
|
||||
"label": "Frame rate"
|
||||
},
|
||||
"type": {
|
||||
"description": "Choose a visualization type for media playback.",
|
||||
"label": "Visualization type"
|
||||
}
|
||||
},
|
||||
"volumes": {
|
||||
"input-volume": {
|
||||
"description": "Microphone input volume level.",
|
||||
@@ -2191,9 +2206,10 @@
|
||||
}
|
||||
},
|
||||
"tabs": {
|
||||
"general": "General",
|
||||
"general": "Appearance",
|
||||
"duration": "Duration",
|
||||
"history": "History",
|
||||
"sounds": "Sounds",
|
||||
"sound": "Sound",
|
||||
"toast": "Toast"
|
||||
},
|
||||
"title": "Notifications",
|
||||
@@ -2242,12 +2258,22 @@
|
||||
"label": "Monitors display"
|
||||
}
|
||||
},
|
||||
"section": {
|
||||
"general": {
|
||||
"tabs": {
|
||||
"general": "General",
|
||||
"events": "Events"
|
||||
},
|
||||
"general": {
|
||||
"section": {
|
||||
"description": "Configure visibility and behavior of OSD.",
|
||||
"label": "General"
|
||||
}
|
||||
},
|
||||
"events": {
|
||||
"section": {
|
||||
"description": "Select which events trigger the on-screen display.",
|
||||
"label": "Events"
|
||||
}
|
||||
},
|
||||
"title": "On-Screen Display",
|
||||
"types": {
|
||||
"brightness": {
|
||||
@@ -2342,6 +2368,11 @@
|
||||
"tooltip": "Remove plugin source"
|
||||
}
|
||||
},
|
||||
"tabs": {
|
||||
"available": "Available",
|
||||
"installed": "Installed",
|
||||
"sources": "Sources"
|
||||
},
|
||||
"title": "Plugins",
|
||||
"uninstall": "Uninstall",
|
||||
"uninstall-dialog": {
|
||||
@@ -2499,6 +2530,10 @@
|
||||
"description": "Display number labels (1-2-3-4...) on buttons and enable number keybinds for quick selection.",
|
||||
"label": "Show number labels"
|
||||
},
|
||||
"tabs": {
|
||||
"general": "General",
|
||||
"actions": "Actions"
|
||||
},
|
||||
"title": "Session Menu"
|
||||
},
|
||||
"system-monitor": {
|
||||
@@ -2765,7 +2800,7 @@
|
||||
},
|
||||
"tabs": {
|
||||
"automation": "Automation",
|
||||
"look-feel": "Look & feel",
|
||||
"look": "Look",
|
||||
"general": "General"
|
||||
},
|
||||
"title": "Wallpaper"
|
||||
|
||||
@@ -4,6 +4,7 @@ import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Modules.Panels.Settings.Tabs
|
||||
import qs.Modules.Panels.Settings.Tabs.About
|
||||
import qs.Modules.Panels.Settings.Tabs.Audio
|
||||
import qs.Modules.Panels.Settings.Tabs.Bar
|
||||
import qs.Modules.Panels.Settings.Tabs.ColorScheme
|
||||
@@ -12,6 +13,8 @@ import qs.Modules.Panels.Settings.Tabs.Display
|
||||
import qs.Modules.Panels.Settings.Tabs.Dock
|
||||
import qs.Modules.Panels.Settings.Tabs.Launcher
|
||||
import qs.Modules.Panels.Settings.Tabs.Notifications
|
||||
import qs.Modules.Panels.Settings.Tabs.Osd
|
||||
import qs.Modules.Panels.Settings.Tabs.Plugins
|
||||
import qs.Modules.Panels.Settings.Tabs.Region
|
||||
import qs.Modules.Panels.Settings.Tabs.SessionMenu
|
||||
import qs.Modules.Panels.Settings.Tabs.SystemMonitor
|
||||
|
||||
@@ -10,7 +10,7 @@ import qs.Widgets
|
||||
SmartPanel {
|
||||
id: root
|
||||
|
||||
preferredWidth: Math.round(820 * Style.uiScaleRatio)
|
||||
preferredWidth: Math.round(840 * Style.uiScaleRatio)
|
||||
preferredHeight: Math.round(910 * Style.uiScaleRatio)
|
||||
|
||||
// Settings panel mode: "centered", "attached", "window"
|
||||
|
||||
@@ -11,8 +11,8 @@ FloatingWindow {
|
||||
id: root
|
||||
|
||||
title: "Noctalia"
|
||||
minimumSize: Qt.size(820 * Style.uiScaleRatio, 910 * Style.uiScaleRatio)
|
||||
implicitWidth: Math.round(820 * Style.uiScaleRatio)
|
||||
minimumSize: Qt.size(840 * Style.uiScaleRatio, 910 * Style.uiScaleRatio)
|
||||
implicitWidth: Math.round(840 * Style.uiScaleRatio)
|
||||
implicitHeight: Math.round(910 * Style.uiScaleRatio)
|
||||
color: Color.mSurface
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: 0
|
||||
|
||||
NTabBar {
|
||||
id: subTabBar
|
||||
Layout.fillWidth: true
|
||||
distributeEvenly: true
|
||||
currentIndex: tabView.currentIndex
|
||||
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.about.tabs.version")
|
||||
tabIndex: 0
|
||||
checked: subTabBar.currentIndex === 0
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.about.tabs.contributors")
|
||||
tabIndex: 1
|
||||
checked: subTabBar.currentIndex === 1
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.marginL
|
||||
}
|
||||
|
||||
NTabView {
|
||||
id: tabView
|
||||
currentIndex: subTabBar.currentIndex
|
||||
|
||||
VersionSubTab {}
|
||||
ContributorsSubTab {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.Noctalia
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
property var contributors: GitHubService.contributors
|
||||
property int avatarCacheVersion: 0
|
||||
|
||||
readonly property int topContributorsCount: 20
|
||||
|
||||
Connections {
|
||||
target: GitHubService
|
||||
function onCachedAvatarsChanged() {
|
||||
root.avatarCacheVersion++;
|
||||
}
|
||||
}
|
||||
|
||||
spacing: Style.marginL
|
||||
|
||||
NHeader {
|
||||
description: root.contributors.length === 1 ? I18n.tr("settings.about.contributors.section.description", {
|
||||
"count": root.contributors.length
|
||||
}) : I18n.tr("settings.about.contributors.section.description_plural", {
|
||||
"count": root.contributors.length
|
||||
})
|
||||
enableDescriptionRichText: true
|
||||
}
|
||||
|
||||
// Top 20 contributors with full cards (avoids GridView shader crashes on Qt 6.8)
|
||||
Flow {
|
||||
id: topContributorsFlow
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
Repeater {
|
||||
model: Math.min(root.contributors.length, root.topContributorsCount)
|
||||
|
||||
delegate: Rectangle {
|
||||
width: Math.max(Math.round(topContributorsFlow.width / 2 - Style.marginM - 1), Math.round(Style.baseWidgetSize * 4))
|
||||
height: Math.round(Style.baseWidgetSize * 2.3)
|
||||
radius: Style.radiusM
|
||||
color: contributorArea.containsMouse ? Color.mHover : "transparent"
|
||||
border.width: 1
|
||||
border.color: contributorArea.containsMouse ? Color.mPrimary : Color.mOutline
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginM
|
||||
spacing: Style.marginM
|
||||
|
||||
// Avatar container with rectangular design (modern, no shader issues)
|
||||
Item {
|
||||
id: wrapper
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredWidth: Style.baseWidgetSize * 1.8
|
||||
Layout.preferredHeight: Style.baseWidgetSize * 1.8
|
||||
|
||||
property bool isRounded: false
|
||||
|
||||
// Background and image container
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
// Simple circular image (pre-rendered, no shaders)
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
source: {
|
||||
// Depend on avatarCacheVersion to trigger re-evaluation
|
||||
var _ = root.avatarCacheVersion;
|
||||
// Try cached circular version first
|
||||
var username = root.contributors[index].login;
|
||||
var cached = GitHubService.getAvatarPath(username);
|
||||
if (cached) {
|
||||
wrapper.isRounded = true;
|
||||
return cached;
|
||||
}
|
||||
|
||||
// Fall back to original avatar URL
|
||||
return root.contributors[index].avatar_url || "";
|
||||
}
|
||||
fillMode: Image.PreserveAspectFit // Fit since image is already circular with transparency
|
||||
mipmap: true
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
visible: root.contributors[index].avatar_url !== undefined && root.contributors[index].avatar_url !== ""
|
||||
opacity: status === Image.Ready ? 1.0 : 0.0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback icon
|
||||
NIcon {
|
||||
anchors.centerIn: parent
|
||||
visible: !root.contributors[index].avatar_url || root.contributors[index].avatar_url === ""
|
||||
icon: "person"
|
||||
pointSize: Style.fontSizeL
|
||||
color: Color.mPrimary
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: wrapper.isRounded
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
radius: width * 0.5
|
||||
border.width: Style.borderM
|
||||
border.color: Color.mPrimary
|
||||
}
|
||||
}
|
||||
|
||||
// Info column
|
||||
ColumnLayout {
|
||||
spacing: 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: root.contributors[index].login || "Unknown"
|
||||
font.weight: Style.fontWeightBold
|
||||
color: contributorArea.containsMouse ? Color.mOnHover : Color.mOnSurface
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
pointSize: Style.fontSizeS
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginXS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NIcon {
|
||||
icon: "git-commit"
|
||||
pointSize: Style.fontSizeXS
|
||||
color: contributorArea.containsMouse ? Color.mOnHover : Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NText {
|
||||
text: `${(root.contributors[index].contributions || 0).toString()} commits`
|
||||
pointSize: Style.fontSizeXS
|
||||
color: contributorArea.containsMouse ? Color.mOnHover : Color.mOnSurfaceVariant
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hover indicator
|
||||
NIcon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
icon: "arrow-right"
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mPrimary
|
||||
opacity: contributorArea.containsMouse ? 1.0 : 0.0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: contributorArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (root.contributors[index].html_url)
|
||||
Quickshell.execDetached(["xdg-open", root.contributors[index].html_url]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remaining contributors (simple text links)
|
||||
Flow {
|
||||
id: remainingContributorsFlow
|
||||
visible: root.contributors.length > root.topContributorsCount
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
spacing: Style.marginS
|
||||
|
||||
Repeater {
|
||||
model: Math.max(0, root.contributors.length - root.topContributorsCount)
|
||||
|
||||
delegate: Rectangle {
|
||||
width: nameText.implicitWidth + Style.marginM * 2
|
||||
height: nameText.implicitHeight + Style.marginS * 2
|
||||
radius: Style.radiusS
|
||||
color: nameArea.containsMouse ? Color.mHover : "transparent"
|
||||
border.width: Style.borderS
|
||||
border.color: nameArea.containsMouse ? Color.mPrimary : Color.mOutline
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
id: nameText
|
||||
anchors.centerIn: parent
|
||||
text: root.contributors[index + root.topContributorsCount].login || "Unknown"
|
||||
pointSize: Style.fontSizeXS
|
||||
color: nameArea.containsMouse ? Color.mOnHover : Color.mOnSurface
|
||||
font.weight: Style.fontWeightMedium
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: nameArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (root.contributors[index + root.topContributorsCount].html_url)
|
||||
Quickshell.execDetached(["xdg-open", root.contributors[index + root.topContributorsCount].html_url]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
import qs.Services.Noctalia
|
||||
import qs.Services.System
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
property string latestVersion: GitHubService.latestVersion
|
||||
property string currentVersion: UpdateService.currentVersion
|
||||
property string commitInfo: ""
|
||||
|
||||
readonly property bool isGitVersion: root.currentVersion.endsWith("-git")
|
||||
|
||||
spacing: Style.marginL
|
||||
|
||||
Component.onCompleted: {
|
||||
Logger.d("VersionSubTab", "Current version:", root.currentVersion);
|
||||
Logger.d("VersionSubTab", "Is git version:", root.isGitVersion);
|
||||
// Only fetch commit info for -git versions
|
||||
if (root.isGitVersion) {
|
||||
// On NixOS, extract commit hash from the store path first
|
||||
if (HostService.isNixOS) {
|
||||
var shellDir = Quickshell.shellDir || "";
|
||||
Logger.d("VersionSubTab", "Component.onCompleted - NixOS detected, shellDir:", shellDir);
|
||||
if (shellDir) {
|
||||
// Extract commit hash from path like: /nix/store/...-noctalia-shell-2025-11-30_225e6d3/share/noctalia-shell
|
||||
// Pattern matches: noctalia-shell-YYYY-MM-DD_<commit_hash>
|
||||
var match = shellDir.match(/noctalia-shell-\d{4}-\d{2}-\d{2}_([0-9a-f]{7,})/i);
|
||||
if (match && match[1]) {
|
||||
// Use first 7 characters of the commit hash
|
||||
root.commitInfo = match[1].substring(0, 7);
|
||||
Logger.d("VersionSubTab", "Component.onCompleted - Extracted commit from NixOS path:", root.commitInfo);
|
||||
return;
|
||||
} else {
|
||||
Logger.d("VersionSubTab", "Component.onCompleted - Could not extract commit from NixOS path, trying fallback");
|
||||
}
|
||||
}
|
||||
fetchGitCommit();
|
||||
return;
|
||||
} else {
|
||||
// On non-NixOS systems, check for pacman first.
|
||||
whichPacmanProcess.running = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: gitFallbackTimer
|
||||
interval: 500
|
||||
running: false
|
||||
onTriggered: {
|
||||
if (!root.commitInfo) {
|
||||
fetchGitCommit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: whichPacmanProcess
|
||||
command: ["sh", "-c", "command -v pacman"]
|
||||
running: false
|
||||
onExited: function (exitCode) {
|
||||
if (exitCode === 0) {
|
||||
Logger.d("VersionSubTab", "whichPacmanProcess - pacman found, starting query");
|
||||
pacmanProcess.running = true;
|
||||
gitFallbackTimer.start();
|
||||
} else {
|
||||
Logger.d("VersionSubTab", "whichPacmanProcess - pacman not found, falling back to git");
|
||||
fetchGitCommit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: pacmanProcess
|
||||
command: ["pacman", "-Q", "noctalia-shell-git"]
|
||||
running: false
|
||||
|
||||
onStarted: {
|
||||
gitFallbackTimer.stop();
|
||||
}
|
||||
|
||||
onExited: function (exitCode) {
|
||||
gitFallbackTimer.stop();
|
||||
Logger.d("VersionSubTab", "pacmanProcess - Process exited with code:", exitCode);
|
||||
if (exitCode === 0) {
|
||||
var output = stdout.text.trim();
|
||||
Logger.d("VersionSubTab", "pacmanProcess - Output:", output);
|
||||
var match = output.match(/noctalia-shell-git\s+(.+)/);
|
||||
if (match && match[1]) {
|
||||
// For Arch packages, the version format might be like: 3.4.0.r112.g3f00bec8-1
|
||||
// Extract just the commit hash part if it exists
|
||||
var version = match[1];
|
||||
var commitMatch = version.match(/\.g([0-9a-f]{7,})/i);
|
||||
if (commitMatch && commitMatch[1]) {
|
||||
// Show short hash (first 7 characters)
|
||||
root.commitInfo = commitMatch[1].substring(0, 7);
|
||||
Logger.d("VersionSubTab", "pacmanProcess - Set commitInfo from Arch package:", root.commitInfo);
|
||||
return; // Successfully got commit hash from Arch package
|
||||
} else {
|
||||
// If no commit hash in version format, still try git repo
|
||||
Logger.d("VersionSubTab", "pacmanProcess - No commit hash in version, trying git");
|
||||
fetchGitCommit();
|
||||
}
|
||||
} else {
|
||||
// Unexpected output format, try git
|
||||
Logger.d("VersionSubTab", "pacmanProcess - Unexpected output format, trying git");
|
||||
fetchGitCommit();
|
||||
}
|
||||
} else {
|
||||
// If not on Arch, try to get git commit from repository
|
||||
Logger.d("VersionSubTab", "pacmanProcess - Package not found, trying git");
|
||||
fetchGitCommit();
|
||||
}
|
||||
}
|
||||
|
||||
stdout: StdioCollector {}
|
||||
stderr: StdioCollector {}
|
||||
}
|
||||
|
||||
function fetchGitCommit() {
|
||||
var shellDir = Quickshell.shellDir || "";
|
||||
Logger.d("VersionSubTab", "fetchGitCommit - shellDir:", shellDir);
|
||||
if (!shellDir) {
|
||||
Logger.d("VersionSubTab", "fetchGitCommit - Cannot determine shell directory, skipping git commit fetch");
|
||||
return;
|
||||
}
|
||||
|
||||
gitProcess.workingDirectory = shellDir;
|
||||
gitProcess.running = true;
|
||||
}
|
||||
|
||||
Process {
|
||||
id: gitProcess
|
||||
command: ["git", "rev-parse", "--short", "HEAD"]
|
||||
running: false
|
||||
|
||||
onExited: function (exitCode) {
|
||||
Logger.d("VersionSubTab", "gitProcess - Process exited with code:", exitCode);
|
||||
if (exitCode === 0) {
|
||||
var gitOutput = stdout.text.trim();
|
||||
Logger.d("VersionSubTab", "gitProcess - gitOutput:", gitOutput);
|
||||
if (gitOutput) {
|
||||
root.commitInfo = gitOutput;
|
||||
Logger.d("VersionSubTab", "gitProcess - Set commitInfo to:", root.commitInfo);
|
||||
}
|
||||
} else {
|
||||
Logger.d("VersionSubTab", "gitProcess - Git command failed. Exit code:", exitCode);
|
||||
}
|
||||
}
|
||||
|
||||
stdout: StdioCollector {}
|
||||
stderr: StdioCollector {}
|
||||
}
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.about.noctalia.section.label")
|
||||
description: I18n.tr("settings.about.noctalia.section.description")
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginXL
|
||||
|
||||
// Versions
|
||||
GridLayout {
|
||||
columns: 2
|
||||
rowSpacing: Style.marginXS
|
||||
columnSpacing: Style.marginS
|
||||
|
||||
NText {
|
||||
text: I18n.tr("settings.about.noctalia.latest-version")
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
|
||||
NText {
|
||||
text: root.latestVersion
|
||||
color: Color.mOnSurface
|
||||
font.weight: Style.fontWeightBold
|
||||
}
|
||||
|
||||
NText {
|
||||
text: I18n.tr("settings.about.noctalia.installed-version")
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
|
||||
NText {
|
||||
text: root.currentVersion
|
||||
color: Color.mOnSurface
|
||||
font.weight: Style.fontWeightBold
|
||||
}
|
||||
|
||||
NText {
|
||||
visible: root.isGitVersion
|
||||
text: I18n.tr("settings.about.noctalia.git-commit")
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
|
||||
NText {
|
||||
visible: root.isGitVersion
|
||||
text: root.commitInfo || I18n.tr("settings.about.noctalia.git-commit-loading")
|
||||
color: Color.mOnSurface
|
||||
font.weight: Style.fontWeightBold
|
||||
font.family: root.commitInfo ? "monospace" : ""
|
||||
pointSize: Style.fontSizeXS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Action buttons row
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: Style.marginM
|
||||
Layout.bottomMargin: Style.marginM
|
||||
spacing: Style.marginM
|
||||
|
||||
NButton {
|
||||
icon: "sparkles"
|
||||
text: I18n.tr("settings.about.changelog")
|
||||
fontSize: Style.fontSizeXS
|
||||
iconSize: Style.fontSizeS
|
||||
outlined: true
|
||||
onClicked: {
|
||||
var screen = PanelService.openedPanel?.screen || Quickshell.screens[0];
|
||||
UpdateService.viewChangelog(screen);
|
||||
}
|
||||
}
|
||||
|
||||
NButton {
|
||||
icon: "heart"
|
||||
text: I18n.tr("settings.about.support")
|
||||
fontSize: Style.fontSizeXS
|
||||
iconSize: Style.fontSizeS
|
||||
outlined: true
|
||||
onClicked: {
|
||||
Quickshell.execDetached(["xdg-open", "https://ko-fi.com/lysec"]);
|
||||
ToastService.showNotice(I18n.tr("settings.about.support"), I18n.tr("toast.kofi.opened"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,530 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
import qs.Services.Noctalia
|
||||
import qs.Services.System
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
property string latestVersion: GitHubService.latestVersion
|
||||
property string currentVersion: UpdateService.currentVersion
|
||||
property var contributors: GitHubService.contributors
|
||||
property string commitInfo: ""
|
||||
property int avatarCacheVersion: 0
|
||||
|
||||
readonly property int topContributorsCount: 20
|
||||
|
||||
Connections {
|
||||
target: GitHubService
|
||||
function onCachedAvatarsChanged() {
|
||||
root.avatarCacheVersion++;
|
||||
}
|
||||
}
|
||||
readonly property bool isGitVersion: root.currentVersion.endsWith("-git")
|
||||
|
||||
spacing: Style.marginL
|
||||
|
||||
Component.onCompleted: {
|
||||
Logger.d("AboutTab", "Current version:", root.currentVersion);
|
||||
Logger.d("AboutTab", "Is git version:", root.isGitVersion);
|
||||
// Only fetch commit info for -git versions
|
||||
if (root.isGitVersion) {
|
||||
// On NixOS, extract commit hash from the store path first
|
||||
if (HostService.isNixOS) {
|
||||
var shellDir = Quickshell.shellDir || "";
|
||||
Logger.d("AboutTab", "Component.onCompleted - NixOS detected, shellDir:", shellDir);
|
||||
if (shellDir) {
|
||||
// Extract commit hash from path like: /nix/store/...-noctalia-shell-2025-11-30_225e6d3/share/noctalia-shell
|
||||
// Pattern matches: noctalia-shell-YYYY-MM-DD_<commit_hash>
|
||||
var match = shellDir.match(/noctalia-shell-\d{4}-\d{2}-\d{2}_([0-9a-f]{7,})/i);
|
||||
if (match && match[1]) {
|
||||
// Use first 7 characters of the commit hash
|
||||
root.commitInfo = match[1].substring(0, 7);
|
||||
Logger.d("AboutTab", "Component.onCompleted - Extracted commit from NixOS path:", root.commitInfo);
|
||||
return;
|
||||
} else {
|
||||
Logger.d("AboutTab", "Component.onCompleted - Could not extract commit from NixOS path, trying fallback");
|
||||
}
|
||||
}
|
||||
fetchGitCommit();
|
||||
return;
|
||||
} else {
|
||||
// On non-NixOS systems, check for pacman first.
|
||||
whichPacmanProcess.running = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: gitFallbackTimer
|
||||
interval: 500
|
||||
running: false
|
||||
onTriggered: {
|
||||
if (!root.commitInfo) {
|
||||
fetchGitCommit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: whichPacmanProcess
|
||||
command: ["sh", "-c", "command -v pacman"]
|
||||
running: false
|
||||
onExited: function (exitCode) {
|
||||
if (exitCode === 0) {
|
||||
Logger.d("AboutTab", "whichPacmanProcess - pacman found, starting query");
|
||||
pacmanProcess.running = true;
|
||||
gitFallbackTimer.start();
|
||||
} else {
|
||||
Logger.d("AboutTab", "whichPacmanProcess - pacman not found, falling back to git");
|
||||
fetchGitCommit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: pacmanProcess
|
||||
command: ["pacman", "-Q", "noctalia-shell-git"]
|
||||
running: false
|
||||
|
||||
onStarted: {
|
||||
gitFallbackTimer.stop();
|
||||
}
|
||||
|
||||
onExited: function (exitCode) {
|
||||
gitFallbackTimer.stop();
|
||||
Logger.d("AboutTab", "pacmanProcess - Process exited with code:", exitCode);
|
||||
if (exitCode === 0) {
|
||||
var output = stdout.text.trim();
|
||||
Logger.d("AboutTab", "pacmanProcess - Output:", output);
|
||||
var match = output.match(/noctalia-shell-git\s+(.+)/);
|
||||
if (match && match[1]) {
|
||||
// For Arch packages, the version format might be like: 3.4.0.r112.g3f00bec8-1
|
||||
// Extract just the commit hash part if it exists
|
||||
var version = match[1];
|
||||
var commitMatch = version.match(/\.g([0-9a-f]{7,})/i);
|
||||
if (commitMatch && commitMatch[1]) {
|
||||
// Show short hash (first 7 characters)
|
||||
root.commitInfo = commitMatch[1].substring(0, 7);
|
||||
Logger.d("AboutTab", "pacmanProcess - Set commitInfo from Arch package:", root.commitInfo);
|
||||
return; // Successfully got commit hash from Arch package
|
||||
} else {
|
||||
// If no commit hash in version format, still try git repo
|
||||
Logger.d("AboutTab", "pacmanProcess - No commit hash in version, trying git");
|
||||
fetchGitCommit();
|
||||
}
|
||||
} else {
|
||||
// Unexpected output format, try git
|
||||
Logger.d("AboutTab", "pacmanProcess - Unexpected output format, trying git");
|
||||
fetchGitCommit();
|
||||
}
|
||||
} else {
|
||||
// If not on Arch, try to get git commit from repository
|
||||
Logger.d("AboutTab", "pacmanProcess - Package not found, trying git");
|
||||
fetchGitCommit();
|
||||
}
|
||||
}
|
||||
|
||||
stdout: StdioCollector {}
|
||||
stderr: StdioCollector {}
|
||||
}
|
||||
|
||||
function fetchGitCommit() {
|
||||
var shellDir = Quickshell.shellDir || "";
|
||||
Logger.d("AboutTab", "fetchGitCommit - shellDir:", shellDir);
|
||||
if (!shellDir) {
|
||||
Logger.d("AboutTab", "fetchGitCommit - Cannot determine shell directory, skipping git commit fetch");
|
||||
return;
|
||||
}
|
||||
|
||||
gitProcess.workingDirectory = shellDir;
|
||||
gitProcess.running = true;
|
||||
}
|
||||
|
||||
Process {
|
||||
id: gitProcess
|
||||
command: ["git", "rev-parse", "--short", "HEAD"]
|
||||
running: false
|
||||
|
||||
onExited: function (exitCode) {
|
||||
Logger.d("AboutTab", "gitProcess - Process exited with code:", exitCode);
|
||||
if (exitCode === 0) {
|
||||
var gitOutput = stdout.text.trim();
|
||||
Logger.d("AboutTab", "gitProcess - gitOutput:", gitOutput);
|
||||
if (gitOutput) {
|
||||
root.commitInfo = gitOutput;
|
||||
Logger.d("AboutTab", "gitProcess - Set commitInfo to:", root.commitInfo);
|
||||
}
|
||||
} else {
|
||||
Logger.d("AboutTab", "gitProcess - Git command failed. Exit code:", exitCode);
|
||||
}
|
||||
}
|
||||
|
||||
stdout: StdioCollector {}
|
||||
stderr: StdioCollector {}
|
||||
}
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.about.noctalia.section.label")
|
||||
description: I18n.tr("settings.about.noctalia.section.description")
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginXL
|
||||
|
||||
// Versions
|
||||
GridLayout {
|
||||
columns: 2
|
||||
rowSpacing: Style.marginXS
|
||||
columnSpacing: Style.marginS
|
||||
|
||||
NText {
|
||||
text: I18n.tr("settings.about.noctalia.latest-version")
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
|
||||
NText {
|
||||
text: root.latestVersion
|
||||
color: Color.mOnSurface
|
||||
font.weight: Style.fontWeightBold
|
||||
}
|
||||
|
||||
NText {
|
||||
text: I18n.tr("settings.about.noctalia.installed-version")
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
|
||||
NText {
|
||||
text: root.currentVersion
|
||||
color: Color.mOnSurface
|
||||
font.weight: Style.fontWeightBold
|
||||
}
|
||||
|
||||
NText {
|
||||
visible: root.isGitVersion
|
||||
text: I18n.tr("settings.about.noctalia.git-commit")
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
|
||||
NText {
|
||||
visible: root.isGitVersion
|
||||
text: root.commitInfo || I18n.tr("settings.about.noctalia.git-commit-loading")
|
||||
color: Color.mOnSurface
|
||||
font.weight: Style.fontWeightBold
|
||||
font.family: root.commitInfo ? "monospace" : ""
|
||||
pointSize: Style.fontSizeXS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Action buttons row
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: Style.marginM
|
||||
Layout.bottomMargin: Style.marginM
|
||||
spacing: Style.marginM
|
||||
|
||||
NButton {
|
||||
icon: "sparkles"
|
||||
text: I18n.tr("settings.about.changelog")
|
||||
fontSize: Style.fontSizeXS
|
||||
iconSize: Style.fontSizeS
|
||||
outlined: true
|
||||
onClicked: {
|
||||
var screen = PanelService.openedPanel?.screen || Quickshell.screens[0];
|
||||
UpdateService.viewChangelog(screen);
|
||||
}
|
||||
}
|
||||
|
||||
NButton {
|
||||
visible: !HostService.isNixOS
|
||||
icon: "wand"
|
||||
text: I18n.tr("settings.general.launch-setup-wizard")
|
||||
fontSize: Style.fontSizeXS
|
||||
iconSize: Style.fontSizeS
|
||||
outlined: true
|
||||
onClicked: {
|
||||
var targetScreen = PanelService.openedPanel ? PanelService.openedPanel.screen : (Quickshell.screens.length > 0 ? Quickshell.screens[0] : null);
|
||||
if (!targetScreen) {
|
||||
return;
|
||||
}
|
||||
var setupPanel = PanelService.getPanel("setupWizardPanel", targetScreen);
|
||||
if (setupPanel) {
|
||||
setupPanel.open();
|
||||
} else {
|
||||
Qt.callLater(() => {
|
||||
var sp = PanelService.getPanel("setupWizardPanel", targetScreen);
|
||||
if (sp)
|
||||
sp.open();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NButton {
|
||||
icon: "heart"
|
||||
text: I18n.tr("settings.about.support")
|
||||
fontSize: Style.fontSizeXS
|
||||
iconSize: Style.fontSizeS
|
||||
outlined: true
|
||||
onClicked: {
|
||||
Quickshell.execDetached(["xdg-open", "https://ko-fi.com/lysec"]);
|
||||
ToastService.showNotice(I18n.tr("settings.about.support"), I18n.tr("toast.kofi.opened"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginXXXL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
// Contributors
|
||||
NHeader {
|
||||
label: I18n.tr("settings.about.contributors.section.label")
|
||||
description: root.contributors.length === 1 ? I18n.tr("settings.about.contributors.section.description", {
|
||||
"count": root.contributors.length
|
||||
}) : I18n.tr("settings.about.contributors.section.description_plural", {
|
||||
"count": root.contributors.length
|
||||
})
|
||||
enableDescriptionRichText: true
|
||||
}
|
||||
|
||||
// Top 20 contributors with full cards (avoids GridView shader crashes on Qt 6.8)
|
||||
Flow {
|
||||
id: topContributorsFlow
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
Repeater {
|
||||
model: Math.min(root.contributors.length, root.topContributorsCount)
|
||||
|
||||
delegate: Rectangle {
|
||||
width: Math.max(Math.round(topContributorsFlow.width / 2 - Style.marginM - 1), Math.round(Style.baseWidgetSize * 4))
|
||||
height: Math.round(Style.baseWidgetSize * 2.3)
|
||||
radius: Style.radiusM
|
||||
color: contributorArea.containsMouse ? Color.mHover : "transparent"
|
||||
border.width: 1
|
||||
border.color: contributorArea.containsMouse ? Color.mPrimary : Color.mOutline
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginM
|
||||
spacing: Style.marginM
|
||||
|
||||
// Avatar container with rectangular design (modern, no shader issues)
|
||||
Item {
|
||||
id: wrapper
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredWidth: Style.baseWidgetSize * 1.8
|
||||
Layout.preferredHeight: Style.baseWidgetSize * 1.8
|
||||
|
||||
property bool isRounded: false
|
||||
|
||||
// Background and image container
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
// Simple circular image (pre-rendered, no shaders)
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
source: {
|
||||
// Depend on avatarCacheVersion to trigger re-evaluation
|
||||
var _ = root.avatarCacheVersion;
|
||||
// Try cached circular version first
|
||||
var username = root.contributors[index].login;
|
||||
var cached = GitHubService.getAvatarPath(username);
|
||||
if (cached) {
|
||||
wrapper.isRounded = true;
|
||||
return cached;
|
||||
}
|
||||
|
||||
// Fall back to original avatar URL
|
||||
return root.contributors[index].avatar_url || "";
|
||||
}
|
||||
fillMode: Image.PreserveAspectFit // Fit since image is already circular with transparency
|
||||
mipmap: true
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
visible: root.contributors[index].avatar_url !== undefined && root.contributors[index].avatar_url !== ""
|
||||
opacity: status === Image.Ready ? 1.0 : 0.0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback icon
|
||||
NIcon {
|
||||
anchors.centerIn: parent
|
||||
visible: !root.contributors[index].avatar_url || root.contributors[index].avatar_url === ""
|
||||
icon: "person"
|
||||
pointSize: Style.fontSizeL
|
||||
color: Color.mPrimary
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: wrapper.isRounded
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
radius: width * 0.5
|
||||
border.width: Style.borderM
|
||||
border.color: Color.mPrimary
|
||||
}
|
||||
}
|
||||
|
||||
// Info column
|
||||
ColumnLayout {
|
||||
spacing: 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: root.contributors[index].login || "Unknown"
|
||||
font.weight: Style.fontWeightBold
|
||||
color: contributorArea.containsMouse ? Color.mOnHover : Color.mOnSurface
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
pointSize: Style.fontSizeS
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginXS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NIcon {
|
||||
icon: "git-commit"
|
||||
pointSize: Style.fontSizeXS
|
||||
color: contributorArea.containsMouse ? Color.mOnHover : Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NText {
|
||||
text: `${(root.contributors[index].contributions || 0).toString()} commits`
|
||||
pointSize: Style.fontSizeXS
|
||||
color: contributorArea.containsMouse ? Color.mOnHover : Color.mOnSurfaceVariant
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hover indicator
|
||||
NIcon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
icon: "arrow-right"
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mPrimary
|
||||
opacity: contributorArea.containsMouse ? 1.0 : 0.0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: contributorArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (root.contributors[index].html_url)
|
||||
Quickshell.execDetached(["xdg-open", root.contributors[index].html_url]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remaining contributors (simple text links)
|
||||
Flow {
|
||||
id: remainingContributorsFlow
|
||||
visible: root.contributors.length > root.topContributorsCount
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
spacing: Style.marginS
|
||||
|
||||
Repeater {
|
||||
model: Math.max(0, root.contributors.length - root.topContributorsCount)
|
||||
|
||||
delegate: Rectangle {
|
||||
width: nameText.implicitWidth + Style.marginM * 2
|
||||
height: nameText.implicitHeight + Style.marginS * 2
|
||||
radius: Style.radiusS
|
||||
color: nameArea.containsMouse ? Color.mHover : "transparent"
|
||||
border.width: Style.borderS
|
||||
border.color: nameArea.containsMouse ? Color.mPrimary : Color.mOutline
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
id: nameText
|
||||
anchors.centerIn: parent
|
||||
text: root.contributors[index + root.topContributorsCount].login || "Unknown"
|
||||
pointSize: Style.fontSizeXS
|
||||
color: nameArea.containsMouse ? Color.mOnHover : Color.mOnSurface
|
||||
font.weight: Style.fontWeightMedium
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: nameArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (root.contributors[index + root.topContributorsCount].html_url)
|
||||
Quickshell.execDetached(["xdg-open", root.contributors[index + root.topContributorsCount].html_url]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,11 @@ ColumnLayout {
|
||||
tabIndex: 2
|
||||
checked: subTabBar.currentIndex === 2
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.audio.tabs.visualizer")
|
||||
tabIndex: 3
|
||||
checked: subTabBar.currentIndex === 3
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
@@ -43,5 +48,6 @@ ColumnLayout {
|
||||
VolumesSubTab {}
|
||||
DevicesSubTab {}
|
||||
MediaSubTab {}
|
||||
VisualizerSubTab {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,11 +11,6 @@ ColumnLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.audio.devices.section.label")
|
||||
description: I18n.tr("settings.audio.devices.section.description")
|
||||
}
|
||||
|
||||
// Output Devices
|
||||
ButtonGroup {
|
||||
id: sinks
|
||||
|
||||
@@ -10,11 +10,6 @@ ColumnLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.audio.media.section.label")
|
||||
description: I18n.tr("settings.audio.media.section.description")
|
||||
}
|
||||
|
||||
// Preferred player
|
||||
NTextInput {
|
||||
label: I18n.tr("settings.audio.media.primary-player.label")
|
||||
@@ -106,83 +101,4 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Audio Visualizer section
|
||||
NComboBox {
|
||||
label: I18n.tr("settings.audio.media.visualizer-type.label")
|
||||
description: I18n.tr("settings.audio.media.visualizer-type.description")
|
||||
model: [
|
||||
{
|
||||
"key": "none",
|
||||
"name": I18n.tr("options.visualizer-types.none")
|
||||
},
|
||||
{
|
||||
"key": "linear",
|
||||
"name": I18n.tr("options.visualizer-types.linear")
|
||||
},
|
||||
{
|
||||
"key": "mirrored",
|
||||
"name": I18n.tr("options.visualizer-types.mirrored")
|
||||
},
|
||||
{
|
||||
"key": "wave",
|
||||
"name": I18n.tr("options.visualizer-types.wave")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.audio.visualizerType
|
||||
defaultValue: Settings.getDefaultValue("audio.visualizerType")
|
||||
onSelected: key => Settings.data.audio.visualizerType = key
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("settings.audio.media.frame-rate.label")
|
||||
description: I18n.tr("settings.audio.media.frame-rate.description")
|
||||
model: [
|
||||
{
|
||||
"key": "30",
|
||||
"name": I18n.tr("options.frame-rates.fps", {
|
||||
"fps": "30"
|
||||
})
|
||||
},
|
||||
{
|
||||
"key": "60",
|
||||
"name": I18n.tr("options.frame-rates.fps", {
|
||||
"fps": "60"
|
||||
})
|
||||
},
|
||||
{
|
||||
"key": "100",
|
||||
"name": I18n.tr("options.frame-rates.fps", {
|
||||
"fps": "100"
|
||||
})
|
||||
},
|
||||
{
|
||||
"key": "120",
|
||||
"name": I18n.tr("options.frame-rates.fps", {
|
||||
"fps": "120"
|
||||
})
|
||||
},
|
||||
{
|
||||
"key": "144",
|
||||
"name": I18n.tr("options.frame-rates.fps", {
|
||||
"fps": "144"
|
||||
})
|
||||
},
|
||||
{
|
||||
"key": "165",
|
||||
"name": I18n.tr("options.frame-rates.fps", {
|
||||
"fps": "165"
|
||||
})
|
||||
},
|
||||
{
|
||||
"key": "240",
|
||||
"name": I18n.tr("options.frame-rates.fps", {
|
||||
"fps": "240"
|
||||
})
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.audio.cavaFrameRate
|
||||
defaultValue: Settings.getDefaultValue("audio.cavaFrameRate")
|
||||
onSelected: key => Settings.data.audio.cavaFrameRate = key
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("settings.audio.visualizer.type.label")
|
||||
description: I18n.tr("settings.audio.visualizer.type.description")
|
||||
model: [
|
||||
{
|
||||
"key": "none",
|
||||
"name": I18n.tr("options.visualizer-types.none")
|
||||
},
|
||||
{
|
||||
"key": "linear",
|
||||
"name": I18n.tr("options.visualizer-types.linear")
|
||||
},
|
||||
{
|
||||
"key": "mirrored",
|
||||
"name": I18n.tr("options.visualizer-types.mirrored")
|
||||
},
|
||||
{
|
||||
"key": "wave",
|
||||
"name": I18n.tr("options.visualizer-types.wave")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.audio.visualizerType
|
||||
defaultValue: Settings.getDefaultValue("audio.visualizerType")
|
||||
onSelected: key => Settings.data.audio.visualizerType = key
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("settings.audio.visualizer.frame-rate.label")
|
||||
description: I18n.tr("settings.audio.visualizer.frame-rate.description")
|
||||
model: [
|
||||
{
|
||||
"key": "30",
|
||||
"name": I18n.tr("options.frame-rates.fps", {
|
||||
"fps": "30"
|
||||
})
|
||||
},
|
||||
{
|
||||
"key": "60",
|
||||
"name": I18n.tr("options.frame-rates.fps", {
|
||||
"fps": "60"
|
||||
})
|
||||
},
|
||||
{
|
||||
"key": "100",
|
||||
"name": I18n.tr("options.frame-rates.fps", {
|
||||
"fps": "100"
|
||||
})
|
||||
},
|
||||
{
|
||||
"key": "120",
|
||||
"name": I18n.tr("options.frame-rates.fps", {
|
||||
"fps": "120"
|
||||
})
|
||||
},
|
||||
{
|
||||
"key": "144",
|
||||
"name": I18n.tr("options.frame-rates.fps", {
|
||||
"fps": "144"
|
||||
})
|
||||
},
|
||||
{
|
||||
"key": "165",
|
||||
"name": I18n.tr("options.frame-rates.fps", {
|
||||
"fps": "165"
|
||||
})
|
||||
},
|
||||
{
|
||||
"key": "240",
|
||||
"name": I18n.tr("options.frame-rates.fps", {
|
||||
"fps": "240"
|
||||
})
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.audio.cavaFrameRate
|
||||
defaultValue: Settings.getDefaultValue("audio.cavaFrameRate")
|
||||
onSelected: key => Settings.data.audio.cavaFrameRate = key
|
||||
}
|
||||
}
|
||||
@@ -29,11 +29,6 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.audio.volumes.section.label")
|
||||
description: I18n.tr("settings.audio.volumes.section.description")
|
||||
}
|
||||
|
||||
// Master Volume
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS
|
||||
@@ -147,6 +142,10 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// External mixer command
|
||||
NTextInput {
|
||||
label: I18n.tr("settings.audio.external-mixer.label")
|
||||
|
||||
@@ -262,8 +262,6 @@ ColumnLayout {
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
visible: !Settings.data.colorSchemes.useWallpaperColors
|
||||
}
|
||||
|
||||
|
||||
@@ -12,16 +12,15 @@ ColumnLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.color-scheme.templates.section.label")
|
||||
description: I18n.tr("settings.color-scheme.templates.section.description")
|
||||
NLabel {
|
||||
label: I18n.tr("settings.color-scheme.templates.section.description")
|
||||
}
|
||||
|
||||
NCollapsible {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.color-scheme.templates.ui.label")
|
||||
description: I18n.tr("settings.color-scheme.templates.ui.description")
|
||||
defaultExpanded: false
|
||||
expanded: true
|
||||
|
||||
NCheckbox {
|
||||
label: "GTK"
|
||||
@@ -64,7 +63,7 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.color-scheme.templates.compositors.label")
|
||||
description: I18n.tr("settings.color-scheme.templates.compositors.description")
|
||||
defaultExpanded: false
|
||||
expanded: true
|
||||
|
||||
NCheckbox {
|
||||
label: "Niri"
|
||||
@@ -107,7 +106,7 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.color-scheme.templates.terminal.label")
|
||||
description: I18n.tr("settings.color-scheme.templates.terminal.description")
|
||||
defaultExpanded: false
|
||||
expanded: true
|
||||
|
||||
NCheckbox {
|
||||
label: "Alacritty"
|
||||
@@ -174,7 +173,7 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.color-scheme.templates.programs.label")
|
||||
description: I18n.tr("settings.color-scheme.templates.programs.description")
|
||||
defaultExpanded: false
|
||||
expanded: true
|
||||
|
||||
NCheckbox {
|
||||
label: "Fuzzel"
|
||||
@@ -370,7 +369,7 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.color-scheme.templates.misc.label")
|
||||
description: I18n.tr("settings.color-scheme.templates.misc.description")
|
||||
defaultExpanded: false
|
||||
expanded: false
|
||||
|
||||
NCheckbox {
|
||||
label: I18n.tr("settings.color-scheme.templates.misc.user-templates.label")
|
||||
|
||||
@@ -66,7 +66,6 @@ ColumnLayout {
|
||||
NComboBox {
|
||||
id: diskPathComboBox
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginM
|
||||
label: I18n.tr("settings.control-center.system-monitor-disk-path.label")
|
||||
description: I18n.tr("settings.control-center.system-monitor-disk-path.description")
|
||||
model: {
|
||||
|
||||
@@ -2,7 +2,6 @@ import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import "ControlCenter"
|
||||
import qs.Commons
|
||||
import qs.Services.System
|
||||
import qs.Services.UI
|
||||
|
||||
@@ -12,23 +12,15 @@ ColumnLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.display.monitors.section.label")
|
||||
description: I18n.tr("settings.display.monitors.section.description")
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL
|
||||
|
||||
Repeater {
|
||||
model: Quickshell.screens || []
|
||||
delegate: Rectangle {
|
||||
delegate: NBox {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: contentCol.implicitHeight + Style.marginL * 2
|
||||
radius: Style.radiusM
|
||||
color: Color.mSurfaceVariant
|
||||
border.color: Color.mOutline
|
||||
border.width: Style.borderS
|
||||
color: Color.mSurface
|
||||
|
||||
property var brightnessMonitor: BrightnessService.getMonitorForScreen(modelData)
|
||||
|
||||
@@ -39,16 +31,33 @@ ColumnLayout {
|
||||
y: Style.marginL
|
||||
spacing: Style.marginXXS
|
||||
|
||||
NLabel {
|
||||
label: modelData.name || "Unknown"
|
||||
description: {
|
||||
const compositorScale = CompositorService.getDisplayScale(modelData.name);
|
||||
I18n.tr("system.monitor-description", {
|
||||
"model": modelData.model,
|
||||
"width": modelData.width * compositorScale,
|
||||
"height": modelData.height * compositorScale,
|
||||
"scale": compositorScale
|
||||
});
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
|
||||
NText {
|
||||
text: modelData.name || "Unknown"
|
||||
pointSize: Style.fontSizeL
|
||||
font.weight: Style.fontWeightSemiBold
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
}
|
||||
|
||||
NText {
|
||||
Layout.fillWidth: true
|
||||
text: {
|
||||
const compositorScale = CompositorService.getDisplayScale(modelData.name);
|
||||
I18n.tr("system.monitor-description", {
|
||||
"model": modelData.model,
|
||||
"width": modelData.width * compositorScale,
|
||||
"height": modelData.height * compositorScale,
|
||||
"scale": compositorScale
|
||||
});
|
||||
}
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mOnSurfaceVariant
|
||||
wrapMode: Text.WordWrap
|
||||
horizontalAlignment: Text.AlignRight
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +109,8 @@ ColumnLayout {
|
||||
Layout.fillHeight: true
|
||||
NIcon {
|
||||
icon: brightnessMonitor && brightnessMonitor.method == "internal" ? "device-laptop" : "device-desktop"
|
||||
anchors.centerIn: parent
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
opacity: brightnessMonitor && !brightnessMonitor.brightnessControlAvailable ? 0.5 : 1.0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,6 @@ ColumnLayout {
|
||||
|
||||
signal checkWlsunset
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.display.night-light.section.label")
|
||||
description: I18n.tr("settings.display.night-light.section.description")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.display.night-light.enable.label")
|
||||
description: I18n.tr("settings.display.night-light.enable.description")
|
||||
@@ -37,8 +32,8 @@ ColumnLayout {
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
visible: Settings.data.nightLight.enabled
|
||||
spacing: Style.marginM
|
||||
enabled: Settings.data.nightLight.enabled
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
@@ -146,81 +141,79 @@ ColumnLayout {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.display.night-light.auto-schedule.label")
|
||||
description: I18n.tr("settings.display.night-light.auto-schedule.description", {
|
||||
"location": LocationService.stableName
|
||||
})
|
||||
checked: Settings.data.nightLight.autoSchedule
|
||||
onToggled: checked => Settings.data.nightLight.autoSchedule = checked
|
||||
visible: Settings.data.nightLight.enabled
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS
|
||||
Layout.fillWidth: true
|
||||
visible: Settings.data.nightLight.enabled && !Settings.data.nightLight.autoSchedule && !Settings.data.nightLight.forced
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("settings.display.night-light.manual-schedule.label")
|
||||
description: I18n.tr("settings.display.night-light.manual-schedule.description")
|
||||
NToggle {
|
||||
label: I18n.tr("settings.display.night-light.auto-schedule.label")
|
||||
description: I18n.tr("settings.display.night-light.auto-schedule.description", {
|
||||
"location": LocationService.stableName
|
||||
})
|
||||
checked: Settings.data.nightLight.autoSchedule
|
||||
onToggled: checked => Settings.data.nightLight.autoSchedule = checked
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS
|
||||
|
||||
NText {
|
||||
text: I18n.tr("settings.display.night-light.manual-schedule.sunrise")
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
model: root.timeOptions
|
||||
currentKey: Settings.data.nightLight.manualSunrise
|
||||
placeholder: I18n.tr("settings.display.night-light.manual-schedule.select-start")
|
||||
onSelected: key => Settings.data.nightLight.manualSunrise = key
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginS
|
||||
visible: !Settings.data.nightLight.autoSchedule && !Settings.data.nightLight.forced
|
||||
|
||||
NText {
|
||||
text: I18n.tr("settings.display.night-light.manual-schedule.sunset")
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
NLabel {
|
||||
label: I18n.tr("settings.display.night-light.manual-schedule.label")
|
||||
description: I18n.tr("settings.display.night-light.manual-schedule.description")
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
model: root.timeOptions
|
||||
currentKey: Settings.data.nightLight.manualSunset
|
||||
placeholder: I18n.tr("settings.display.night-light.manual-schedule.select-stop")
|
||||
onSelected: key => Settings.data.nightLight.manualSunset = key
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginS
|
||||
|
||||
NText {
|
||||
text: I18n.tr("settings.display.night-light.manual-schedule.sunrise")
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
model: root.timeOptions
|
||||
currentKey: Settings.data.nightLight.manualSunrise
|
||||
placeholder: I18n.tr("settings.display.night-light.manual-schedule.select-start")
|
||||
onSelected: key => Settings.data.nightLight.manualSunrise = key
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginS
|
||||
|
||||
NText {
|
||||
text: I18n.tr("settings.display.night-light.manual-schedule.sunset")
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
model: root.timeOptions
|
||||
currentKey: Settings.data.nightLight.manualSunset
|
||||
placeholder: I18n.tr("settings.display.night-light.manual-schedule.select-stop")
|
||||
onSelected: key => Settings.data.nightLight.manualSunset = key
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.display.night-light.force-activation.label")
|
||||
description: I18n.tr("settings.display.night-light.force-activation.description")
|
||||
checked: Settings.data.nightLight.forced
|
||||
onToggled: checked => {
|
||||
Settings.data.nightLight.forced = checked;
|
||||
if (checked && !Settings.data.nightLight.enabled) {
|
||||
root.checkWlsunset();
|
||||
} else {
|
||||
NightLightService.apply();
|
||||
NToggle {
|
||||
label: I18n.tr("settings.display.night-light.force-activation.label")
|
||||
description: I18n.tr("settings.display.night-light.force-activation.description")
|
||||
checked: Settings.data.nightLight.forced
|
||||
onToggled: checked => {
|
||||
Settings.data.nightLight.forced = checked;
|
||||
if (checked && !Settings.data.nightLight.enabled) {
|
||||
root.checkWlsunset();
|
||||
} else {
|
||||
NightLightService.apply();
|
||||
}
|
||||
}
|
||||
}
|
||||
visible: Settings.data.nightLight.enabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ ColumnLayout {
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL
|
||||
visible: Settings.data.dock.enabled
|
||||
enabled: Settings.data.dock.enabled
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
|
||||
@@ -10,11 +10,6 @@ import qs.Widgets
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.general.profile.section.label")
|
||||
description: I18n.tr("settings.general.profile.section.description")
|
||||
}
|
||||
|
||||
// Profile section
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
@@ -63,8 +58,8 @@ ColumnLayout {
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
Layout.topMargin: Style.marginM
|
||||
Layout.bottomMargin: Style.marginM
|
||||
}
|
||||
|
||||
// Fonts
|
||||
@@ -72,11 +67,6 @@ ColumnLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.general.fonts.section.label")
|
||||
description: I18n.tr("settings.general.fonts.section.description")
|
||||
}
|
||||
|
||||
// Font configuration section
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL
|
||||
@@ -178,7 +168,30 @@ ColumnLayout {
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
Layout.topMargin: Style.marginM
|
||||
Layout.bottomMargin: Style.marginM
|
||||
}
|
||||
|
||||
NButton {
|
||||
visible: !HostService.isNixOS
|
||||
icon: "wand"
|
||||
text: I18n.tr("settings.general.launch-setup-wizard")
|
||||
outlined: true
|
||||
onClicked: {
|
||||
var targetScreen = PanelService.openedPanel ? PanelService.openedPanel.screen : (Quickshell.screens.length > 0 ? Quickshell.screens[0] : null);
|
||||
if (!targetScreen) {
|
||||
return;
|
||||
}
|
||||
var setupPanel = PanelService.getPanel("setupWizardPanel", targetScreen);
|
||||
if (setupPanel) {
|
||||
setupPanel.open();
|
||||
} else {
|
||||
Qt.callLater(() => {
|
||||
var sp = PanelService.getPanel("setupWizardPanel", targetScreen);
|
||||
if (sp)
|
||||
sp.open();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +184,5 @@ ColumnLayout {
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services.System
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
|
||||
@@ -44,7 +44,5 @@ ColumnLayout {
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,10 +11,6 @@ ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
|
||||
NHeader {
|
||||
description: I18n.tr("settings.network.section.description")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.network.wifi.label")
|
||||
description: I18n.tr("settings.network.wifi.description")
|
||||
@@ -43,10 +39,4 @@ ColumnLayout {
|
||||
enabled: BluetoothService.enabled
|
||||
onToggled: checked => Settings.data.network.bluetoothRssiPollingEnabled = checked
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.notifications.duration.respect-expire.label")
|
||||
description: I18n.tr("settings.notifications.duration.respect-expire.description")
|
||||
checked: Settings.data.notifications.respectExpireTimeout
|
||||
onToggled: checked => Settings.data.notifications.respectExpireTimeout = checked
|
||||
defaultValue: Settings.getDefaultValue("notifications.respectExpireTimeout")
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.notifications.duration.low-urgency.label")
|
||||
description: I18n.tr("settings.notifications.duration.low-urgency.description")
|
||||
from: 1
|
||||
to: 30
|
||||
stepSize: 1
|
||||
value: Settings.data.notifications.lowUrgencyDuration
|
||||
onMoved: value => Settings.data.notifications.lowUrgencyDuration = value
|
||||
text: Settings.data.notifications.lowUrgencyDuration + "s"
|
||||
defaultValue: Settings.getDefaultValue("notifications.lowUrgencyDuration")
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: 30 * Style.uiScaleRatio
|
||||
Layout.preferredHeight: 30 * Style.uiScaleRatio
|
||||
|
||||
NIconButton {
|
||||
icon: "restore"
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
tooltipText: I18n.tr("settings.notifications.duration.reset")
|
||||
onClicked: Settings.data.notifications.lowUrgencyDuration = 3
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.notifications.duration.normal-urgency.label")
|
||||
description: I18n.tr("settings.notifications.duration.normal-urgency.description")
|
||||
from: 1
|
||||
to: 30
|
||||
stepSize: 1
|
||||
value: Settings.data.notifications.normalUrgencyDuration
|
||||
onMoved: value => Settings.data.notifications.normalUrgencyDuration = value
|
||||
text: Settings.data.notifications.normalUrgencyDuration + "s"
|
||||
defaultValue: Settings.getDefaultValue("notifications.normalUrgencyDuration")
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: 30 * Style.uiScaleRatio
|
||||
Layout.preferredHeight: 30 * Style.uiScaleRatio
|
||||
|
||||
NIconButton {
|
||||
icon: "restore"
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
tooltipText: I18n.tr("settings.notifications.duration.reset")
|
||||
onClicked: Settings.data.notifications.normalUrgencyDuration = 8
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.notifications.duration.critical-urgency.label")
|
||||
description: I18n.tr("settings.notifications.duration.critical-urgency.description")
|
||||
from: 1
|
||||
to: 30
|
||||
stepSize: 1
|
||||
value: Settings.data.notifications.criticalUrgencyDuration
|
||||
onMoved: value => Settings.data.notifications.criticalUrgencyDuration = value
|
||||
text: Settings.data.notifications.criticalUrgencyDuration + "s"
|
||||
defaultValue: Settings.getDefaultValue("notifications.criticalUrgencyDuration")
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: 30 * Style.uiScaleRatio
|
||||
Layout.preferredHeight: 30 * Style.uiScaleRatio
|
||||
|
||||
NIconButton {
|
||||
icon: "restore"
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
tooltipText: I18n.tr("settings.notifications.duration.reset")
|
||||
onClicked: Settings.data.notifications.criticalUrgencyDuration = 15
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,11 +15,6 @@ ColumnLayout {
|
||||
property var addMonitor
|
||||
property var removeMonitor
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.notifications.settings.section.label")
|
||||
description: I18n.tr("settings.notifications.settings.section.description")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.notifications.settings.enabled.label")
|
||||
description: I18n.tr("settings.notifications.settings.enabled.description")
|
||||
@@ -92,8 +87,6 @@ ColumnLayout {
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
NHeader {
|
||||
|
||||
@@ -9,126 +9,6 @@ ColumnLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.notifications.duration.section.label")
|
||||
description: I18n.tr("settings.notifications.duration.section.description")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.notifications.duration.respect-expire.label")
|
||||
description: I18n.tr("settings.notifications.duration.respect-expire.description")
|
||||
checked: Settings.data.notifications.respectExpireTimeout
|
||||
onToggled: checked => Settings.data.notifications.respectExpireTimeout = checked
|
||||
defaultValue: Settings.getDefaultValue("notifications.respectExpireTimeout")
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.notifications.duration.low-urgency.label")
|
||||
description: I18n.tr("settings.notifications.duration.low-urgency.description")
|
||||
from: 1
|
||||
to: 30
|
||||
stepSize: 1
|
||||
value: Settings.data.notifications.lowUrgencyDuration
|
||||
onMoved: value => Settings.data.notifications.lowUrgencyDuration = value
|
||||
text: Settings.data.notifications.lowUrgencyDuration + "s"
|
||||
defaultValue: Settings.getDefaultValue("notifications.lowUrgencyDuration")
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: 30 * Style.uiScaleRatio
|
||||
Layout.preferredHeight: 30 * Style.uiScaleRatio
|
||||
|
||||
NIconButton {
|
||||
icon: "restore"
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
tooltipText: I18n.tr("settings.notifications.duration.reset")
|
||||
onClicked: Settings.data.notifications.lowUrgencyDuration = 3
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.notifications.duration.normal-urgency.label")
|
||||
description: I18n.tr("settings.notifications.duration.normal-urgency.description")
|
||||
from: 1
|
||||
to: 30
|
||||
stepSize: 1
|
||||
value: Settings.data.notifications.normalUrgencyDuration
|
||||
onMoved: value => Settings.data.notifications.normalUrgencyDuration = value
|
||||
text: Settings.data.notifications.normalUrgencyDuration + "s"
|
||||
defaultValue: Settings.getDefaultValue("notifications.normalUrgencyDuration")
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: 30 * Style.uiScaleRatio
|
||||
Layout.preferredHeight: 30 * Style.uiScaleRatio
|
||||
|
||||
NIconButton {
|
||||
icon: "restore"
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
tooltipText: I18n.tr("settings.notifications.duration.reset")
|
||||
onClicked: Settings.data.notifications.normalUrgencyDuration = 8
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.notifications.duration.critical-urgency.label")
|
||||
description: I18n.tr("settings.notifications.duration.critical-urgency.description")
|
||||
from: 1
|
||||
to: 30
|
||||
stepSize: 1
|
||||
value: Settings.data.notifications.criticalUrgencyDuration
|
||||
onMoved: value => Settings.data.notifications.criticalUrgencyDuration = value
|
||||
text: Settings.data.notifications.criticalUrgencyDuration + "s"
|
||||
defaultValue: Settings.getDefaultValue("notifications.criticalUrgencyDuration")
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: 30 * Style.uiScaleRatio
|
||||
Layout.preferredHeight: 30 * Style.uiScaleRatio
|
||||
|
||||
NIconButton {
|
||||
icon: "restore"
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
tooltipText: I18n.tr("settings.notifications.duration.reset")
|
||||
onClicked: Settings.data.notifications.criticalUrgencyDuration = 15
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.notifications.history.section.label")
|
||||
description: I18n.tr("settings.notifications.history.section.description")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.notifications.history.low-urgency.label")
|
||||
description: I18n.tr("settings.notifications.history.low-urgency.description")
|
||||
|
||||
@@ -48,7 +48,7 @@ ColumnLayout {
|
||||
checked: subTabBar.currentIndex === 0
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.notifications.tabs.sounds")
|
||||
text: I18n.tr("settings.notifications.tabs.duration")
|
||||
tabIndex: 1
|
||||
checked: subTabBar.currentIndex === 1
|
||||
}
|
||||
@@ -58,10 +58,15 @@ ColumnLayout {
|
||||
checked: subTabBar.currentIndex === 2
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.notifications.tabs.toast")
|
||||
text: I18n.tr("settings.notifications.tabs.sound")
|
||||
tabIndex: 3
|
||||
checked: subTabBar.currentIndex === 3
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.notifications.tabs.toast")
|
||||
tabIndex: 4
|
||||
checked: subTabBar.currentIndex === 4
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
@@ -77,13 +82,14 @@ ColumnLayout {
|
||||
addMonitor: root.addMonitor
|
||||
removeMonitor: root.removeMonitor
|
||||
}
|
||||
SoundsSubTab {
|
||||
DurationSubTab {}
|
||||
HistorySubTab {}
|
||||
SoundSubTab {
|
||||
onOpenUnifiedPicker: root.openUnifiedSoundPicker()
|
||||
onOpenLowPicker: root.openLowSoundPicker()
|
||||
onOpenNormalPicker: root.openNormalSoundPicker()
|
||||
onOpenCriticalPicker: root.openCriticalSoundPicker()
|
||||
}
|
||||
HistorySubTab {}
|
||||
ToastSubTab {}
|
||||
}
|
||||
|
||||
|
||||
-5
@@ -16,11 +16,6 @@ ColumnLayout {
|
||||
signal openNormalPicker
|
||||
signal openCriticalPicker
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.notifications.sounds.section.label")
|
||||
description: I18n.tr("settings.notifications.sounds.section.description")
|
||||
}
|
||||
|
||||
// QtMultimedia unavailable message
|
||||
NBox {
|
||||
Layout.fillWidth: true
|
||||
@@ -9,11 +9,6 @@ ColumnLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.notifications.toast.section.label")
|
||||
description: I18n.tr("settings.notifications.toast.section.description")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.notifications.toast.keyboard.label")
|
||||
description: I18n.tr("settings.notifications.toast.keyboard.description")
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Modules.OSD
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
property var addType
|
||||
property var removeType
|
||||
|
||||
Repeater {
|
||||
model: [
|
||||
{
|
||||
type: OSD.Type.Volume,
|
||||
key: "volume"
|
||||
},
|
||||
{
|
||||
type: OSD.Type.InputVolume,
|
||||
key: "input-volume"
|
||||
},
|
||||
{
|
||||
type: OSD.Type.Brightness,
|
||||
key: "brightness"
|
||||
},
|
||||
{
|
||||
type: OSD.Type.LockKey,
|
||||
key: "lockkey"
|
||||
},
|
||||
{
|
||||
type: OSD.Type.CustomText,
|
||||
key: "custom-text"
|
||||
}
|
||||
]
|
||||
delegate: NCheckbox {
|
||||
required property var modelData
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.osd.types." + modelData.key + ".label")
|
||||
description: I18n.tr("settings.osd.types." + modelData.key + ".description")
|
||||
checked: (Settings.data.osd.enabledTypes || []).includes(modelData.type)
|
||||
onToggled: checked => {
|
||||
if (checked) {
|
||||
Settings.data.osd.enabledTypes = root.addType(Settings.data.osd.enabledTypes, modelData.type);
|
||||
} else {
|
||||
Settings.data.osd.enabledTypes = root.removeType(Settings.data.osd.enabledTypes, modelData.type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.Compositor
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
property var addMonitor
|
||||
property var removeMonitor
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("settings.osd.location.label")
|
||||
description: I18n.tr("settings.osd.location.description")
|
||||
model: [
|
||||
{
|
||||
"key": "top",
|
||||
"name": I18n.tr("options.osd.position.top_center")
|
||||
},
|
||||
{
|
||||
"key": "top_left",
|
||||
"name": I18n.tr("options.osd.position.top_left")
|
||||
},
|
||||
{
|
||||
"key": "top_right",
|
||||
"name": I18n.tr("options.osd.position.top_right")
|
||||
},
|
||||
{
|
||||
"key": "bottom",
|
||||
"name": I18n.tr("options.osd.position.bottom_center")
|
||||
},
|
||||
{
|
||||
"key": "bottom_left",
|
||||
"name": I18n.tr("options.osd.position.bottom_left")
|
||||
},
|
||||
{
|
||||
"key": "bottom_right",
|
||||
"name": I18n.tr("options.osd.position.bottom_right")
|
||||
},
|
||||
{
|
||||
"key": "left",
|
||||
"name": I18n.tr("options.osd.position.center_left")
|
||||
},
|
||||
{
|
||||
"key": "right",
|
||||
"name": I18n.tr("options.osd.position.center_right")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.osd.location || "top_right"
|
||||
defaultValue: Settings.getDefaultValue("osd.location")
|
||||
onSelected: key => Settings.data.osd.location = key
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.osd.enabled.label")
|
||||
description: I18n.tr("settings.osd.enabled.description")
|
||||
checked: Settings.data.osd.enabled
|
||||
defaultValue: Settings.getDefaultValue("osd.enabled")
|
||||
onToggled: checked => Settings.data.osd.enabled = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.osd.always-on-top.label")
|
||||
description: I18n.tr("settings.osd.always-on-top.description")
|
||||
checked: Settings.data.osd.overlayLayer
|
||||
defaultValue: Settings.getDefaultValue("osd.overlayLayer")
|
||||
onToggled: checked => Settings.data.osd.overlayLayer = checked
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.osd.background-opacity.label")
|
||||
description: I18n.tr("settings.osd.background-opacity.description")
|
||||
from: 0
|
||||
to: 100
|
||||
stepSize: 1
|
||||
value: Settings.data.osd.backgroundOpacity * 100
|
||||
defaultValue: (Settings.getDefaultValue("osd.backgroundOpacity") || 1) * 100
|
||||
onMoved: value => Settings.data.osd.backgroundOpacity = value / 100
|
||||
text: Math.round(Settings.data.osd.backgroundOpacity * 100) + "%"
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.osd.duration.auto-hide.label")
|
||||
description: I18n.tr("settings.osd.duration.auto-hide.description")
|
||||
from: 500
|
||||
to: 5000
|
||||
stepSize: 100
|
||||
value: Settings.data.osd.autoHideMs
|
||||
defaultValue: Settings.getDefaultValue("osd.autoHideMs")
|
||||
onMoved: value => Settings.data.osd.autoHideMs = value
|
||||
text: Math.round(Settings.data.osd.autoHideMs / 1000 * 10) / 10 + "s"
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("settings.osd.monitors.section.description")
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Quickshell.screens || []
|
||||
delegate: NCheckbox {
|
||||
Layout.fillWidth: true
|
||||
label: modelData.name || I18n.tr("system.unknown")
|
||||
description: {
|
||||
const compositorScale = CompositorService.getDisplayScale(modelData.name);
|
||||
I18n.tr("system.monitor-description", {
|
||||
"model": modelData.model,
|
||||
"width": modelData.width * compositorScale,
|
||||
"height": modelData.height * compositorScale,
|
||||
"scale": compositorScale
|
||||
});
|
||||
}
|
||||
checked: (Settings.data.osd.monitors || []).indexOf(modelData.name) !== -1
|
||||
onToggled: checked => {
|
||||
if (checked) {
|
||||
Settings.data.osd.monitors = root.addMonitor(Settings.data.osd.monitors, modelData.name);
|
||||
} else {
|
||||
Settings.data.osd.monitors = root.removeMonitor(Settings.data.osd.monitors, modelData.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: 0
|
||||
|
||||
// Helper functions to update arrays immutably
|
||||
function addMonitor(list, name) {
|
||||
const arr = (list || []).slice();
|
||||
if (!arr.includes(name))
|
||||
arr.push(name);
|
||||
return arr;
|
||||
}
|
||||
function removeMonitor(list, name) {
|
||||
return (list || []).filter(function (n) {
|
||||
return n !== name;
|
||||
});
|
||||
}
|
||||
function addType(list, type) {
|
||||
const arr = (list || []).slice();
|
||||
if (!arr.includes(type))
|
||||
arr.push(type);
|
||||
return arr;
|
||||
}
|
||||
function removeType(list, type) {
|
||||
return (list || []).filter(function (t) {
|
||||
return t !== type;
|
||||
});
|
||||
}
|
||||
|
||||
NTabBar {
|
||||
id: subTabBar
|
||||
Layout.fillWidth: true
|
||||
distributeEvenly: true
|
||||
currentIndex: tabView.currentIndex
|
||||
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.osd.tabs.general")
|
||||
tabIndex: 0
|
||||
checked: subTabBar.currentIndex === 0
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.osd.tabs.events")
|
||||
tabIndex: 1
|
||||
checked: subTabBar.currentIndex === 1
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.marginL
|
||||
}
|
||||
|
||||
NTabView {
|
||||
id: tabView
|
||||
currentIndex: subTabBar.currentIndex
|
||||
|
||||
GeneralSubTab {
|
||||
addMonitor: root.addMonitor
|
||||
removeMonitor: root.removeMonitor
|
||||
}
|
||||
EventsSubTab {
|
||||
addType: root.addType
|
||||
removeType: root.removeType
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,247 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Modules.OSD
|
||||
import qs.Services.Compositor
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
// Helper functions to update arrays immutably
|
||||
function addMonitor(list, name) {
|
||||
const arr = (list || []).slice();
|
||||
if (!arr.includes(name))
|
||||
arr.push(name);
|
||||
return arr;
|
||||
}
|
||||
function removeMonitor(list, name) {
|
||||
return (list || []).filter(function (n) {
|
||||
return n !== name;
|
||||
});
|
||||
}
|
||||
function addType(list, type) {
|
||||
const arr = (list || []).slice();
|
||||
if (!arr.includes(type))
|
||||
arr.push(type);
|
||||
return arr;
|
||||
}
|
||||
function removeType(list, type) {
|
||||
return (list || []).filter(function (t) {
|
||||
return t !== type;
|
||||
});
|
||||
}
|
||||
|
||||
// Display
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("settings.osd.location.label")
|
||||
description: I18n.tr("settings.osd.location.description")
|
||||
model: [
|
||||
{
|
||||
"key": "top",
|
||||
"name": I18n.tr("options.osd.position.top_center")
|
||||
},
|
||||
{
|
||||
"key": "top_left",
|
||||
"name": I18n.tr("options.osd.position.top_left")
|
||||
},
|
||||
{
|
||||
"key": "top_right",
|
||||
"name": I18n.tr("options.osd.position.top_right")
|
||||
},
|
||||
{
|
||||
"key": "bottom",
|
||||
"name": I18n.tr("options.osd.position.bottom_center")
|
||||
},
|
||||
{
|
||||
"key": "bottom_left",
|
||||
"name": I18n.tr("options.osd.position.bottom_left")
|
||||
},
|
||||
{
|
||||
"key": "bottom_right",
|
||||
"name": I18n.tr("options.osd.position.bottom_right")
|
||||
},
|
||||
{
|
||||
"key": "left",
|
||||
"name": I18n.tr("options.osd.position.center_left")
|
||||
},
|
||||
{
|
||||
"key": "right",
|
||||
"name": I18n.tr("options.osd.position.center_right")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.osd.location || "top_right"
|
||||
defaultValue: Settings.getDefaultValue("osd.location")
|
||||
onSelected: key => Settings.data.osd.location = key
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
// General
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.osd.section.general.label")
|
||||
description: I18n.tr("settings.osd.section.general.description")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.osd.enabled.label")
|
||||
description: I18n.tr("settings.osd.enabled.description")
|
||||
checked: Settings.data.osd.enabled
|
||||
defaultValue: Settings.getDefaultValue("osd.enabled")
|
||||
onToggled: checked => Settings.data.osd.enabled = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.osd.always-on-top.label")
|
||||
description: I18n.tr("settings.osd.always-on-top.description")
|
||||
checked: Settings.data.osd.overlayLayer
|
||||
defaultValue: Settings.getDefaultValue("osd.overlayLayer")
|
||||
onToggled: checked => Settings.data.osd.overlayLayer = checked
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.osd.background-opacity.label", "Background opacity")
|
||||
description: I18n.tr("settings.osd.background-opacity.description", "Controls the transparency of the OSD background.")
|
||||
from: 0
|
||||
to: 100
|
||||
stepSize: 1
|
||||
value: Settings.data.osd.backgroundOpacity * 100
|
||||
defaultValue: (Settings.getDefaultValue("osd.backgroundOpacity") || 1) * 100
|
||||
onMoved: value => Settings.data.osd.backgroundOpacity = value / 100
|
||||
text: Math.round(Settings.data.osd.backgroundOpacity * 100) + "%"
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.osd.duration.auto-hide.label")
|
||||
description: I18n.tr("settings.osd.duration.auto-hide.description")
|
||||
from: 500
|
||||
to: 5000
|
||||
stepSize: 100
|
||||
value: Settings.data.osd.autoHideMs
|
||||
defaultValue: Settings.getDefaultValue("osd.autoHideMs")
|
||||
onMoved: value => Settings.data.osd.autoHideMs = value
|
||||
text: Math.round(Settings.data.osd.autoHideMs / 1000 * 10) / 10 + "s"
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
// OSD Types Configuration
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.osd.types.section.label")
|
||||
description: I18n.tr("settings.osd.types.section.description")
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: [
|
||||
{
|
||||
type: OSD.Type.Volume,
|
||||
key: "volume"
|
||||
},
|
||||
{
|
||||
type: OSD.Type.InputVolume,
|
||||
key: "input-volume"
|
||||
},
|
||||
{
|
||||
type: OSD.Type.Brightness,
|
||||
key: "brightness"
|
||||
},
|
||||
{
|
||||
type: OSD.Type.LockKey,
|
||||
key: "lockkey"
|
||||
},
|
||||
{
|
||||
type: OSD.Type.CustomText,
|
||||
key: "custom-text"
|
||||
}
|
||||
]
|
||||
delegate: NCheckbox {
|
||||
required property var modelData
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.osd.types." + modelData.key + ".label")
|
||||
description: I18n.tr("settings.osd.types." + modelData.key + ".description")
|
||||
checked: (Settings.data.osd.enabledTypes || []).includes(modelData.type)
|
||||
onToggled: checked => {
|
||||
if (checked) {
|
||||
Settings.data.osd.enabledTypes = addType(Settings.data.osd.enabledTypes, modelData.type);
|
||||
} else {
|
||||
Settings.data.osd.enabledTypes = removeType(Settings.data.osd.enabledTypes, modelData.type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
// Monitor Configuration
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.osd.monitors.section.label")
|
||||
description: I18n.tr("settings.osd.monitors.section.description")
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Quickshell.screens || []
|
||||
delegate: NCheckbox {
|
||||
Layout.fillWidth: true
|
||||
label: modelData.name || I18n.tr("system.unknown")
|
||||
description: {
|
||||
const compositorScale = CompositorService.getDisplayScale(modelData.name);
|
||||
I18n.tr("system.monitor-description", {
|
||||
"model": modelData.model,
|
||||
"width": modelData.width * compositorScale,
|
||||
"height": modelData.height * compositorScale,
|
||||
"scale": compositorScale
|
||||
});
|
||||
}
|
||||
checked: (Settings.data.osd.monitors || []).indexOf(modelData.name) !== -1
|
||||
onToggled: checked => {
|
||||
if (checked) {
|
||||
Settings.data.osd.monitors = addMonitor(Settings.data.osd.monitors, modelData.name);
|
||||
} else {
|
||||
Settings.data.osd.monitors = removeMonitor(Settings.data.osd.monitors, modelData.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,431 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import "../../../../../Helpers/FuzzySort.js" as Fuzzysort
|
||||
import qs.Commons
|
||||
import qs.Services.Noctalia
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
property string pluginFilter: "all"
|
||||
property string pluginSearchText: ""
|
||||
|
||||
function stripAuthorEmail(author) {
|
||||
if (!author)
|
||||
return "";
|
||||
var lastBracket = author.lastIndexOf("<");
|
||||
if (lastBracket >= 0) {
|
||||
return author.substring(0, lastBracket).trim();
|
||||
}
|
||||
return author;
|
||||
}
|
||||
|
||||
// Filter controls
|
||||
RowLayout {
|
||||
spacing: Style.marginM
|
||||
Layout.fillWidth: true
|
||||
|
||||
NTabBar {
|
||||
id: filterTabBar
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
currentIndex: 0
|
||||
onCurrentIndexChanged: {
|
||||
if (currentIndex === 0)
|
||||
root.pluginFilter = "all";
|
||||
else if (currentIndex === 1)
|
||||
root.pluginFilter = "downloaded";
|
||||
else if (currentIndex === 2)
|
||||
root.pluginFilter = "notDownloaded";
|
||||
}
|
||||
|
||||
NTabButton {
|
||||
Layout.fillWidth: true
|
||||
text: I18n.tr("settings.plugins.filter.all")
|
||||
tabIndex: 0
|
||||
checked: root.pluginFilter === "all"
|
||||
}
|
||||
|
||||
NTabButton {
|
||||
Layout.fillWidth: true
|
||||
text: I18n.tr("settings.plugins.filter.downloaded")
|
||||
tabIndex: 1
|
||||
checked: root.pluginFilter === "downloaded"
|
||||
}
|
||||
|
||||
NTabButton {
|
||||
Layout.fillWidth: true
|
||||
text: I18n.tr("settings.plugins.filter.not-downloaded")
|
||||
tabIndex: 2
|
||||
checked: root.pluginFilter === "notDownloaded"
|
||||
}
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "refresh"
|
||||
tooltipText: I18n.tr("settings.plugins.refresh.tooltip")
|
||||
baseSize: Style.baseWidgetSize * 0.9
|
||||
onClicked: {
|
||||
PluginService.refreshAvailablePlugins();
|
||||
checkUpdatesTimer.restart();
|
||||
ToastService.showNotice(I18n.tr("settings.plugins.title"), I18n.tr("settings.plugins.refresh.refreshing"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Search input
|
||||
NTextInput {
|
||||
placeholderText: I18n.tr("placeholders.search")
|
||||
inputIconName: "search"
|
||||
text: root.pluginSearchText
|
||||
onTextChanged: root.pluginSearchText = text
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Available plugins list
|
||||
ColumnLayout {
|
||||
spacing: Style.marginM
|
||||
Layout.fillWidth: true
|
||||
|
||||
Repeater {
|
||||
id: availablePluginsRepeater
|
||||
|
||||
model: {
|
||||
var all = PluginService.availablePlugins || [];
|
||||
var filtered = [];
|
||||
|
||||
// First apply download filter
|
||||
for (var i = 0; i < all.length; i++) {
|
||||
var plugin = all[i];
|
||||
var downloaded = plugin.downloaded || false;
|
||||
|
||||
if (root.pluginFilter === "all") {
|
||||
filtered.push(plugin);
|
||||
} else if (root.pluginFilter === "downloaded" && downloaded) {
|
||||
filtered.push(plugin);
|
||||
} else if (root.pluginFilter === "notDownloaded" && !downloaded) {
|
||||
filtered.push(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
// Then apply fuzzy search if there's search text
|
||||
var query = root.pluginSearchText.trim();
|
||||
if (query !== "") {
|
||||
var results = Fuzzysort.go(query, filtered, {
|
||||
"keys": ["name", "description"],
|
||||
"threshold": 0.35,
|
||||
"limit": 50
|
||||
});
|
||||
filtered = [];
|
||||
for (var j = 0; j < results.length; j++) {
|
||||
filtered.push(results[j].obj);
|
||||
}
|
||||
} else {
|
||||
// Sort by lastUpdated (most recent first) when not searching
|
||||
filtered.sort(function (a, b) {
|
||||
var dateA = a.lastUpdated ? new Date(a.lastUpdated).getTime() : 0;
|
||||
var dateB = b.lastUpdated ? new Date(b.lastUpdated).getTime() : 0;
|
||||
return dateB - dateA;
|
||||
});
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
delegate: NBox {
|
||||
id: pluginBox
|
||||
property bool isHovered: hoverHandler.hovered
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Style.borderS
|
||||
Layout.rightMargin: Style.borderS
|
||||
implicitHeight: Math.round(contentColumn.implicitHeight + Style.marginL * 2)
|
||||
color: Color.mSurface
|
||||
|
||||
Behavior on implicitHeight {
|
||||
NumberAnimation {
|
||||
duration: 150
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
id: hoverHandler
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: contentColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginL
|
||||
spacing: Style.marginS
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginM
|
||||
Layout.fillWidth: true
|
||||
|
||||
NIcon {
|
||||
icon: "plugin"
|
||||
pointSize: Style.fontSizeL
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
|
||||
NText {
|
||||
text: modelData.name
|
||||
color: Color.mOnSurface
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
// Description excerpt - visible when not hovered
|
||||
NText {
|
||||
visible: !pluginBox.isHovered && modelData.description
|
||||
text: modelData.description || ""
|
||||
font.pointSize: Style.fontSizeXS
|
||||
color: Color.mOnSurfaceVariant
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Spacer when hovered or no description
|
||||
Item {
|
||||
visible: pluginBox.isHovered || !modelData.description
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Downloaded indicator
|
||||
NIcon {
|
||||
icon: "circle-check"
|
||||
pointSize: Style.fontSizeL
|
||||
color: Color.mPrimary
|
||||
visible: modelData.downloaded === true
|
||||
}
|
||||
|
||||
// Install/Uninstall button
|
||||
NIconButton {
|
||||
icon: modelData.downloaded ? "trash" : "download"
|
||||
baseSize: Style.baseWidgetSize * 0.7
|
||||
tooltipText: modelData.downloaded ? I18n.tr("settings.plugins.uninstall") : I18n.tr("settings.plugins.install")
|
||||
onClicked: {
|
||||
if (modelData.downloaded) {
|
||||
// Construct composite key for available plugins
|
||||
var pluginData = Object.assign({}, modelData);
|
||||
pluginData.compositeKey = PluginRegistry.generateCompositeKey(modelData.id, modelData.source?.url || "");
|
||||
uninstallDialog.pluginToUninstall = pluginData;
|
||||
uninstallDialog.open();
|
||||
} else {
|
||||
installPlugin(modelData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Description - visible on hover
|
||||
NText {
|
||||
visible: pluginBox.isHovered && modelData.description
|
||||
text: modelData.description || ""
|
||||
font.pointSize: Style.fontSizeXS
|
||||
color: Color.mOnSurface
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Details row - visible on hover
|
||||
RowLayout {
|
||||
visible: pluginBox.isHovered
|
||||
spacing: Style.marginS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "v" + modelData.version
|
||||
font.pointSize: Style.fontSizeXS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "•"
|
||||
font.pointSize: Style.fontSizeXS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NText {
|
||||
text: stripAuthorEmail(modelData.author)
|
||||
font.pointSize: Style.fontSizeXS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "•"
|
||||
font.pointSize: Style.fontSizeXS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NText {
|
||||
text: modelData.source ? modelData.source.name : ""
|
||||
font.pointSize: Style.fontSizeXS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NLabel {
|
||||
visible: availablePluginsRepeater.count === 0
|
||||
label: I18n.tr("settings.plugins.available.no-plugins-label")
|
||||
description: I18n.tr("settings.plugins.available.no-plugins-description")
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
// Uninstall confirmation dialog
|
||||
Popup {
|
||||
id: uninstallDialog
|
||||
parent: Overlay.overlay
|
||||
modal: true
|
||||
dim: false
|
||||
anchors.centerIn: parent
|
||||
width: 400 * Style.uiScaleRatio
|
||||
padding: Style.marginL
|
||||
|
||||
property var pluginToUninstall: null
|
||||
|
||||
background: Rectangle {
|
||||
color: Color.mSurface
|
||||
radius: Style.radiusS
|
||||
border.color: Color.mPrimary
|
||||
border.width: Style.borderM
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
width: parent.width
|
||||
spacing: Style.marginL
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.plugins.uninstall-dialog.title")
|
||||
description: I18n.tr("settings.plugins.uninstall-dialog.description", {
|
||||
"plugin": uninstallDialog.pluginToUninstall?.name || ""
|
||||
})
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginM
|
||||
Layout.fillWidth: true
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NButton {
|
||||
text: I18n.tr("common.cancel")
|
||||
onClicked: uninstallDialog.close()
|
||||
}
|
||||
|
||||
NButton {
|
||||
text: I18n.tr("settings.plugins.uninstall")
|
||||
backgroundColor: Color.mPrimary
|
||||
textColor: Color.mOnPrimary
|
||||
onClicked: {
|
||||
if (uninstallDialog.pluginToUninstall) {
|
||||
uninstallPlugin(uninstallDialog.pluginToUninstall.compositeKey);
|
||||
uninstallDialog.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Timer to check for updates after refresh starts
|
||||
Timer {
|
||||
id: checkUpdatesTimer
|
||||
interval: 100
|
||||
onTriggered: {
|
||||
PluginService.checkForUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
function installPlugin(pluginMetadata) {
|
||||
ToastService.showNotice(I18n.tr("settings.plugins.title"), I18n.tr("settings.plugins.installing", {
|
||||
"plugin": pluginMetadata.name
|
||||
}));
|
||||
|
||||
PluginService.installPlugin(pluginMetadata, false, function (success, error, registeredKey) {
|
||||
if (success) {
|
||||
ToastService.showNotice(I18n.tr("settings.plugins.title"), I18n.tr("settings.plugins.install-success", {
|
||||
"plugin": pluginMetadata.name
|
||||
}));
|
||||
// Auto-enable the plugin after installation (use registered key which may be composite)
|
||||
PluginService.enablePlugin(registeredKey);
|
||||
} else {
|
||||
ToastService.showError(I18n.tr("settings.plugins.title"), I18n.tr("settings.plugins.install-error", {
|
||||
"error": error || "Unknown error"
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function uninstallPlugin(pluginId) {
|
||||
var manifest = PluginRegistry.getPluginManifest(pluginId);
|
||||
var pluginName = manifest?.name || pluginId;
|
||||
|
||||
ToastService.showNotice(I18n.tr("settings.plugins.title"), I18n.tr("settings.plugins.uninstalling", {
|
||||
"plugin": pluginName
|
||||
}));
|
||||
|
||||
PluginService.uninstallPlugin(pluginId, function (success, error) {
|
||||
if (success) {
|
||||
ToastService.showNotice(I18n.tr("settings.plugins.title"), I18n.tr("settings.plugins.uninstall-success", {
|
||||
"plugin": pluginName
|
||||
}));
|
||||
} else {
|
||||
ToastService.showError(I18n.tr("settings.plugins.title"), I18n.tr("settings.plugins.uninstall-error", {
|
||||
"error": error || "Unknown error"
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Listen to plugin service signals
|
||||
Connections {
|
||||
target: PluginService
|
||||
|
||||
function onAvailablePluginsUpdated() {
|
||||
// Force model refresh for available plugins
|
||||
availablePluginsRepeater.model = undefined;
|
||||
Qt.callLater(function () {
|
||||
availablePluginsRepeater.model = Qt.binding(function () {
|
||||
var all = PluginService.availablePlugins || [];
|
||||
var filtered = [];
|
||||
|
||||
for (var i = 0; i < all.length; i++) {
|
||||
var plugin = all[i];
|
||||
var downloaded = plugin.downloaded || false;
|
||||
|
||||
if (root.pluginFilter === "all") {
|
||||
filtered.push(plugin);
|
||||
} else if (root.pluginFilter === "downloaded" && downloaded) {
|
||||
filtered.push(plugin);
|
||||
} else if (root.pluginFilter === "notDownloaded" && !downloaded) {
|
||||
filtered.push(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
return filtered;
|
||||
});
|
||||
});
|
||||
|
||||
// Manually trigger update check after a small delay to ensure all registries are loaded
|
||||
Qt.callLater(function () {
|
||||
PluginService.checkForUpdates();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,416 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services.Noctalia
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
// Track which plugins are currently updating
|
||||
property var updatingPlugins: ({})
|
||||
property int installedPluginsRefreshCounter: 0
|
||||
|
||||
function stripAuthorEmail(author) {
|
||||
if (!author)
|
||||
return "";
|
||||
var lastBracket = author.lastIndexOf("<");
|
||||
if (lastBracket >= 0) {
|
||||
return author.substring(0, lastBracket).trim();
|
||||
}
|
||||
return author;
|
||||
}
|
||||
|
||||
// Check for updates when tab becomes visible
|
||||
onVisibleChanged: {
|
||||
if (visible && PluginService.pluginsFullyLoaded) {
|
||||
PluginService.checkForUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
// Update All button
|
||||
NButton {
|
||||
property int updateCount: Object.keys(PluginService.pluginUpdates).length
|
||||
property bool isUpdating: false
|
||||
|
||||
text: I18n.tr("settings.plugins.update-all", {
|
||||
"count": updateCount
|
||||
})
|
||||
icon: "download"
|
||||
visible: updateCount >= 2
|
||||
enabled: !isUpdating
|
||||
backgroundColor: Color.mPrimary
|
||||
textColor: Color.mOnPrimary
|
||||
Layout.fillWidth: true
|
||||
onClicked: {
|
||||
isUpdating = true;
|
||||
var pluginIds = Object.keys(PluginService.pluginUpdates);
|
||||
var currentIndex = 0;
|
||||
|
||||
function updateNext() {
|
||||
if (currentIndex >= pluginIds.length) {
|
||||
isUpdating = false;
|
||||
ToastService.showNotice(I18n.tr("settings.plugins.title"), I18n.tr("settings.plugins.update-all-success"));
|
||||
return;
|
||||
}
|
||||
|
||||
var pluginId = pluginIds[currentIndex];
|
||||
currentIndex++;
|
||||
|
||||
PluginService.updatePlugin(pluginId, function (success, error) {
|
||||
if (!success) {
|
||||
Logger.w("InstalledSubTab", "Failed to update", pluginId + ":", error);
|
||||
}
|
||||
Qt.callLater(updateNext);
|
||||
});
|
||||
}
|
||||
|
||||
updateNext();
|
||||
}
|
||||
}
|
||||
|
||||
// Installed plugins list
|
||||
ColumnLayout {
|
||||
spacing: Style.marginM
|
||||
Layout.fillWidth: true
|
||||
|
||||
Repeater {
|
||||
id: installedPluginsRepeater
|
||||
|
||||
model: {
|
||||
// Force refresh when counter changes
|
||||
var _ = root.installedPluginsRefreshCounter;
|
||||
|
||||
var allIds = PluginRegistry.getAllInstalledPluginIds();
|
||||
var plugins = [];
|
||||
for (var i = 0; i < allIds.length; i++) {
|
||||
var compositeKey = allIds[i];
|
||||
var manifest = PluginRegistry.getPluginManifest(compositeKey);
|
||||
if (manifest) {
|
||||
// Create a copy of manifest and include update info, enabled state, and source info
|
||||
var pluginData = JSON.parse(JSON.stringify(manifest));
|
||||
pluginData.compositeKey = compositeKey;
|
||||
pluginData.updateInfo = PluginService.pluginUpdates[compositeKey];
|
||||
pluginData.pendingUpdateInfo = PluginService.pluginUpdatesPending[compositeKey];
|
||||
pluginData.enabled = PluginRegistry.isPluginEnabled(compositeKey);
|
||||
|
||||
// Add source info
|
||||
var parsed = PluginRegistry.parseCompositeKey(compositeKey);
|
||||
pluginData.isOfficial = parsed.isOfficial;
|
||||
if (!parsed.isOfficial) {
|
||||
pluginData.sourceName = PluginRegistry.getSourceNameByHash(parsed.sourceHash);
|
||||
}
|
||||
|
||||
plugins.push(pluginData);
|
||||
}
|
||||
}
|
||||
return plugins;
|
||||
}
|
||||
|
||||
delegate: NBox {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Style.borderS
|
||||
Layout.rightMargin: Style.borderS
|
||||
implicitHeight: Math.round(rowLayout.implicitHeight) + Style.marginL * 2
|
||||
color: Color.mSurface
|
||||
|
||||
RowLayout {
|
||||
id: rowLayout
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginL
|
||||
spacing: Style.marginM
|
||||
|
||||
NIcon {
|
||||
icon: "plugin"
|
||||
pointSize: Style.fontSizeXL
|
||||
color: PluginService.hasPluginError(modelData.compositeKey) ? Color.mError : Color.mOnSurface
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 2
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: modelData.name
|
||||
color: Color.mOnSurface
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NText {
|
||||
text: modelData.description
|
||||
font.pointSize: Style.fontSizeXS
|
||||
color: Color.mOnSurfaceVariant
|
||||
wrapMode: Text.WordWrap
|
||||
maximumLineCount: 2
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginS
|
||||
|
||||
NText {
|
||||
text: {
|
||||
if (modelData.updateInfo) {
|
||||
return I18n.tr("settings.plugins.update-version", {
|
||||
"current": modelData.version,
|
||||
"new": modelData.updateInfo.availableVersion
|
||||
});
|
||||
} else if (modelData.pendingUpdateInfo) {
|
||||
return I18n.tr("settings.plugins.update-pending", {
|
||||
"current": modelData.version,
|
||||
"new": modelData.pendingUpdateInfo.availableVersion,
|
||||
"required": modelData.pendingUpdateInfo.minNoctaliaVersion
|
||||
});
|
||||
}
|
||||
return "v" + modelData.version;
|
||||
}
|
||||
font.pointSize: Style.fontSizeXXS
|
||||
color: modelData.updateInfo ? Color.mPrimary : (modelData.pendingUpdateInfo ? Color.mTertiary : Color.mOnSurfaceVariant)
|
||||
font.weight: (modelData.updateInfo || modelData.pendingUpdateInfo) ? Style.fontWeightMedium : Style.fontWeightRegular
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "•"
|
||||
font.pointSize: Style.fontSizeXXS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NText {
|
||||
text: stripAuthorEmail(modelData.author)
|
||||
font.pointSize: Style.fontSizeXXS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
// Source indicator for non-official plugins
|
||||
NText {
|
||||
visible: !modelData.isOfficial
|
||||
text: "•"
|
||||
font.pointSize: Style.fontSizeXXS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NText {
|
||||
visible: !modelData.isOfficial
|
||||
text: modelData.sourceName || I18n.tr("settings.plugins.source.custom")
|
||||
font.pointSize: Style.fontSizeXXS
|
||||
color: Color.mTertiary
|
||||
}
|
||||
}
|
||||
|
||||
// Error indicator
|
||||
RowLayout {
|
||||
spacing: Style.marginS
|
||||
visible: PluginService.hasPluginError(modelData.compositeKey)
|
||||
|
||||
NIcon {
|
||||
icon: "alert-triangle"
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mError
|
||||
}
|
||||
|
||||
NText {
|
||||
property var errorInfo: PluginService.getPluginError(modelData.compositeKey)
|
||||
text: errorInfo ? errorInfo.error : ""
|
||||
font.pointSize: Style.fontSizeXXS
|
||||
color: Color.mError
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 3
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "settings"
|
||||
tooltipText: I18n.tr("settings.plugins.settings.tooltip")
|
||||
baseSize: Style.baseWidgetSize * 0.7
|
||||
visible: modelData.entryPoints?.settings !== undefined
|
||||
onClicked: {
|
||||
pluginSettingsDialog.openPluginSettings(modelData);
|
||||
}
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "trash"
|
||||
tooltipText: I18n.tr("settings.plugins.uninstall")
|
||||
baseSize: Style.baseWidgetSize * 0.7
|
||||
onClicked: {
|
||||
uninstallDialog.pluginToUninstall = modelData;
|
||||
uninstallDialog.open();
|
||||
}
|
||||
}
|
||||
|
||||
NButton {
|
||||
id: updateButton
|
||||
property string pluginId: modelData.compositeKey
|
||||
property bool isUpdating: root.updatingPlugins[pluginId] === true
|
||||
|
||||
text: isUpdating ? I18n.tr("settings.plugins.updating") : I18n.tr("settings.plugins.update")
|
||||
icon: isUpdating ? "" : "download"
|
||||
visible: modelData.updateInfo !== undefined
|
||||
enabled: !isUpdating
|
||||
backgroundColor: Color.mPrimary
|
||||
textColor: Color.mOnPrimary
|
||||
onClicked: {
|
||||
var pid = pluginId;
|
||||
var pname = modelData.name;
|
||||
var pversion = modelData.updateInfo?.availableVersion || "";
|
||||
var rootRef = root;
|
||||
var updates = Object.assign({}, rootRef.updatingPlugins);
|
||||
updates[pid] = true;
|
||||
rootRef.updatingPlugins = updates;
|
||||
|
||||
PluginService.updatePlugin(pid, function (success, error) {
|
||||
var updates2 = Object.assign({}, rootRef.updatingPlugins);
|
||||
updates2[pid] = false;
|
||||
rootRef.updatingPlugins = updates2;
|
||||
|
||||
if (success) {
|
||||
ToastService.showNotice(I18n.tr("settings.plugins.title"), I18n.tr("settings.plugins.update-success", {
|
||||
"plugin": pname,
|
||||
"version": pversion
|
||||
}));
|
||||
} else {
|
||||
ToastService.showError(I18n.tr("settings.plugins.title"), I18n.tr("settings.plugins.update-error", {
|
||||
"plugin": pname,
|
||||
"error": error || "Unknown error"
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
checked: modelData.enabled
|
||||
baseSize: Style.baseWidgetSize * 0.7
|
||||
onToggled: checked => {
|
||||
if (checked) {
|
||||
PluginService.enablePlugin(modelData.compositeKey);
|
||||
} else {
|
||||
PluginService.disablePlugin(modelData.compositeKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NLabel {
|
||||
visible: PluginRegistry.getAllInstalledPluginIds().length === 0
|
||||
label: I18n.tr("settings.plugins.installed.no-plugins-label")
|
||||
description: I18n.tr("settings.plugins.installed.no-plugins-description")
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
// Uninstall confirmation dialog
|
||||
Popup {
|
||||
id: uninstallDialog
|
||||
parent: Overlay.overlay
|
||||
modal: true
|
||||
dim: false
|
||||
anchors.centerIn: parent
|
||||
width: 400 * Style.uiScaleRatio
|
||||
padding: Style.marginL
|
||||
|
||||
property var pluginToUninstall: null
|
||||
|
||||
background: Rectangle {
|
||||
color: Color.mSurface
|
||||
radius: Style.radiusS
|
||||
border.color: Color.mPrimary
|
||||
border.width: Style.borderM
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
width: parent.width
|
||||
spacing: Style.marginL
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.plugins.uninstall-dialog.title")
|
||||
description: I18n.tr("settings.plugins.uninstall-dialog.description", {
|
||||
"plugin": uninstallDialog.pluginToUninstall?.name || ""
|
||||
})
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginM
|
||||
Layout.fillWidth: true
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NButton {
|
||||
text: I18n.tr("common.cancel")
|
||||
onClicked: uninstallDialog.close()
|
||||
}
|
||||
|
||||
NButton {
|
||||
text: I18n.tr("settings.plugins.uninstall")
|
||||
backgroundColor: Color.mPrimary
|
||||
textColor: Color.mOnPrimary
|
||||
onClicked: {
|
||||
if (uninstallDialog.pluginToUninstall) {
|
||||
root.uninstallPlugin(uninstallDialog.pluginToUninstall.compositeKey);
|
||||
uninstallDialog.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Plugin settings popup
|
||||
NPluginSettingsPopup {
|
||||
id: pluginSettingsDialog
|
||||
parent: Overlay.overlay
|
||||
showToastOnSave: true
|
||||
}
|
||||
|
||||
function uninstallPlugin(pluginId) {
|
||||
var manifest = PluginRegistry.getPluginManifest(pluginId);
|
||||
var pluginName = manifest?.name || pluginId;
|
||||
|
||||
ToastService.showNotice(I18n.tr("settings.plugins.title"), I18n.tr("settings.plugins.uninstalling", {
|
||||
"plugin": pluginName
|
||||
}));
|
||||
|
||||
PluginService.uninstallPlugin(pluginId, function (success, error) {
|
||||
if (success) {
|
||||
ToastService.showNotice(I18n.tr("settings.plugins.title"), I18n.tr("settings.plugins.uninstall-success", {
|
||||
"plugin": pluginName
|
||||
}));
|
||||
} else {
|
||||
ToastService.showError(I18n.tr("settings.plugins.title"), I18n.tr("settings.plugins.uninstall-error", {
|
||||
"error": error || "Unknown error"
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Listen to plugin registry changes
|
||||
Connections {
|
||||
target: PluginRegistry
|
||||
|
||||
function onPluginsChanged() {
|
||||
root.installedPluginsRefreshCounter++;
|
||||
}
|
||||
}
|
||||
|
||||
// Listen to plugin service signals
|
||||
Connections {
|
||||
target: PluginService
|
||||
|
||||
function onPluginUpdatesChanged() {
|
||||
root.installedPluginsRefreshCounter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: 0
|
||||
|
||||
NTabBar {
|
||||
id: subTabBar
|
||||
Layout.fillWidth: true
|
||||
distributeEvenly: true
|
||||
currentIndex: tabView.currentIndex
|
||||
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.plugins.tabs.installed")
|
||||
tabIndex: 0
|
||||
checked: subTabBar.currentIndex === 0
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.plugins.tabs.available")
|
||||
tabIndex: 1
|
||||
checked: subTabBar.currentIndex === 1
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.plugins.tabs.sources")
|
||||
tabIndex: 2
|
||||
checked: subTabBar.currentIndex === 2
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.marginL
|
||||
}
|
||||
|
||||
NTabView {
|
||||
id: tabView
|
||||
currentIndex: subTabBar.currentIndex
|
||||
|
||||
InstalledSubTab {}
|
||||
AvailableSubTab {}
|
||||
SourcesSubTab {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services.Noctalia
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
// List of plugin sources
|
||||
ColumnLayout {
|
||||
spacing: Style.marginM
|
||||
Layout.fillWidth: true
|
||||
|
||||
Repeater {
|
||||
id: pluginSourcesRepeater
|
||||
model: PluginRegistry.pluginSources || []
|
||||
|
||||
delegate: NBox {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: sourceRow.implicitHeight + Style.marginL * 2
|
||||
color: Color.mSurface
|
||||
|
||||
RowLayout {
|
||||
id: sourceRow
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginL
|
||||
spacing: Style.marginM
|
||||
|
||||
NIcon {
|
||||
icon: "brand-github"
|
||||
pointSize: Style.fontSizeL
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 2
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: modelData.name
|
||||
color: Color.mOnSurface
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NText {
|
||||
text: modelData.url
|
||||
font.pointSize: Style.fontSizeXS
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "trash"
|
||||
tooltipText: I18n.tr("settings.plugins.sources.remove.tooltip")
|
||||
visible: index !== 0 // Cannot remove official source
|
||||
baseSize: Style.baseWidgetSize * 0.7
|
||||
onClicked: {
|
||||
PluginRegistry.removePluginSource(modelData.url);
|
||||
}
|
||||
}
|
||||
|
||||
// Enable/Disable a source
|
||||
NToggle {
|
||||
checked: modelData.enabled !== false // Default to true if not set
|
||||
baseSize: Style.baseWidgetSize * 0.7
|
||||
onToggled: checked => {
|
||||
PluginRegistry.setSourceEnabled(modelData.url, checked);
|
||||
PluginService.refreshAvailablePlugins();
|
||||
ToastService.showNotice(I18n.tr("settings.plugins.title"), I18n.tr("settings.plugins.refresh.refreshing"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add custom repository
|
||||
NButton {
|
||||
text: I18n.tr("settings.plugins.sources.add-custom")
|
||||
icon: "plus"
|
||||
onClicked: {
|
||||
addSourceDialog.open();
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Add source dialog
|
||||
Popup {
|
||||
id: addSourceDialog
|
||||
parent: Overlay.overlay
|
||||
modal: true
|
||||
dim: false
|
||||
anchors.centerIn: parent
|
||||
width: 500
|
||||
padding: Style.marginL
|
||||
|
||||
background: Rectangle {
|
||||
color: Color.mSurface
|
||||
radius: Style.radiusS
|
||||
border.color: Color.mPrimary
|
||||
border.width: Style.borderM
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
width: parent.width
|
||||
spacing: Style.marginL
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.plugins.sources.add-dialog.title")
|
||||
description: I18n.tr("settings.plugins.sources.add-dialog.description")
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
id: sourceNameInput
|
||||
label: I18n.tr("settings.plugins.sources.add-dialog.name")
|
||||
placeholderText: I18n.tr("settings.plugins.sources.add-dialog.name-placeholder")
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
id: sourceUrlInput
|
||||
label: I18n.tr("settings.plugins.sources.add-dialog.url")
|
||||
placeholderText: "https://github.com/user/repo"
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginM
|
||||
Layout.fillWidth: true
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NButton {
|
||||
text: I18n.tr("common.cancel")
|
||||
onClicked: addSourceDialog.close()
|
||||
}
|
||||
|
||||
NButton {
|
||||
text: I18n.tr("common.add")
|
||||
backgroundColor: Color.mPrimary
|
||||
textColor: Color.mOnPrimary
|
||||
enabled: sourceNameInput.text.length > 0 && sourceUrlInput.text.length > 0
|
||||
onClicked: {
|
||||
if (PluginRegistry.addPluginSource(sourceNameInput.text, sourceUrlInput.text)) {
|
||||
ToastService.showNotice(I18n.tr("settings.plugins.title"), I18n.tr("settings.plugins.sources.add-dialog.success"));
|
||||
PluginService.refreshAvailablePlugins();
|
||||
addSourceDialog.close();
|
||||
sourceNameInput.text = "";
|
||||
sourceUrlInput.text = "";
|
||||
} else {
|
||||
ToastService.showError(I18n.tr("settings.plugins.title"), I18n.tr("settings.plugins.sources.add-dialog.error"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Listen to plugin registry changes
|
||||
Connections {
|
||||
target: PluginRegistry
|
||||
|
||||
function onPluginsChanged() {
|
||||
// Force model refresh for plugin sources
|
||||
pluginSourcesRepeater.model = undefined;
|
||||
Qt.callLater(function () {
|
||||
pluginSourcesRepeater.model = Qt.binding(function () {
|
||||
return PluginRegistry.pluginSources || [];
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,957 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import "../../../../Helpers/FuzzySort.js" as Fuzzysort
|
||||
import qs.Commons
|
||||
import qs.Services.Noctalia
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
// Track which plugins are currently updating
|
||||
property var updatingPlugins: ({})
|
||||
property int installedPluginsRefreshCounter: 0
|
||||
|
||||
function stripAuthorEmail(author) {
|
||||
if (!author)
|
||||
return "";
|
||||
var lastBracket = author.lastIndexOf("<");
|
||||
if (lastBracket >= 0) {
|
||||
return author.substring(0, lastBracket).trim();
|
||||
}
|
||||
return author;
|
||||
}
|
||||
|
||||
// Check for updates when tab becomes visible
|
||||
onVisibleChanged: {
|
||||
if (visible && PluginService.pluginsFullyLoaded) {
|
||||
PluginService.checkForUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
// Installed Plugins
|
||||
// ------------------------------
|
||||
NHeader {
|
||||
label: I18n.tr("settings.plugins.installed.label")
|
||||
description: I18n.tr("settings.plugins.installed.description")
|
||||
}
|
||||
|
||||
// Update All button
|
||||
NButton {
|
||||
property int updateCount: Object.keys(PluginService.pluginUpdates).length
|
||||
property bool isUpdating: false
|
||||
|
||||
text: I18n.tr("settings.plugins.update-all", {
|
||||
"count": updateCount
|
||||
})
|
||||
icon: "download"
|
||||
visible: updateCount >= 2
|
||||
enabled: !isUpdating
|
||||
backgroundColor: Color.mPrimary
|
||||
textColor: Color.mOnPrimary
|
||||
Layout.fillWidth: true
|
||||
onClicked: {
|
||||
isUpdating = true;
|
||||
var pluginIds = Object.keys(PluginService.pluginUpdates);
|
||||
var currentIndex = 0;
|
||||
|
||||
function updateNext() {
|
||||
if (currentIndex >= pluginIds.length) {
|
||||
isUpdating = false;
|
||||
ToastService.showNotice(I18n.tr("settings.plugins.title"), I18n.tr("settings.plugins.update-all-success"));
|
||||
return;
|
||||
}
|
||||
|
||||
var pluginId = pluginIds[currentIndex];
|
||||
currentIndex++;
|
||||
|
||||
PluginService.updatePlugin(pluginId, function (success, error) {
|
||||
if (!success) {
|
||||
Logger.w("PluginsTab", "Failed to update", pluginId + ":", error);
|
||||
}
|
||||
Qt.callLater(updateNext);
|
||||
});
|
||||
}
|
||||
|
||||
updateNext();
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
// Installed plugins
|
||||
// ------------------------------
|
||||
ColumnLayout {
|
||||
spacing: Style.marginM
|
||||
Layout.fillWidth: true
|
||||
|
||||
Repeater {
|
||||
id: installedPluginsRepeater
|
||||
|
||||
model: {
|
||||
// Force refresh when counter changes
|
||||
var _ = root.installedPluginsRefreshCounter;
|
||||
|
||||
var allIds = PluginRegistry.getAllInstalledPluginIds();
|
||||
var plugins = [];
|
||||
for (var i = 0; i < allIds.length; i++) {
|
||||
var compositeKey = allIds[i];
|
||||
var manifest = PluginRegistry.getPluginManifest(compositeKey);
|
||||
if (manifest) {
|
||||
// Create a copy of manifest and include update info, enabled state, and source info
|
||||
var pluginData = JSON.parse(JSON.stringify(manifest));
|
||||
pluginData.compositeKey = compositeKey;
|
||||
pluginData.updateInfo = PluginService.pluginUpdates[compositeKey];
|
||||
pluginData.pendingUpdateInfo = PluginService.pluginUpdatesPending[compositeKey];
|
||||
pluginData.enabled = PluginRegistry.isPluginEnabled(compositeKey);
|
||||
|
||||
// Add source info
|
||||
var parsed = PluginRegistry.parseCompositeKey(compositeKey);
|
||||
pluginData.isOfficial = parsed.isOfficial;
|
||||
if (!parsed.isOfficial) {
|
||||
pluginData.sourceName = PluginRegistry.getSourceNameByHash(parsed.sourceHash);
|
||||
}
|
||||
|
||||
plugins.push(pluginData);
|
||||
}
|
||||
}
|
||||
return plugins;
|
||||
}
|
||||
|
||||
delegate: NBox {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Style.borderS
|
||||
Layout.rightMargin: Style.borderS
|
||||
implicitHeight: Math.round(rowLayout.implicitHeight) + Style.marginL * 2
|
||||
color: Color.mSurface
|
||||
|
||||
RowLayout {
|
||||
id: rowLayout
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginL
|
||||
spacing: Style.marginM
|
||||
|
||||
NIcon {
|
||||
icon: "plugin"
|
||||
pointSize: Style.fontSizeXL
|
||||
color: PluginService.hasPluginError(modelData.compositeKey) ? Color.mError : Color.mOnSurface
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 2
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: modelData.name
|
||||
color: Color.mOnSurface
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NText {
|
||||
text: modelData.description
|
||||
font.pointSize: Style.fontSizeXS
|
||||
color: Color.mOnSurfaceVariant
|
||||
wrapMode: Text.WordWrap
|
||||
maximumLineCount: 2
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginS
|
||||
|
||||
NText {
|
||||
text: {
|
||||
if (modelData.updateInfo) {
|
||||
return I18n.tr("settings.plugins.update-version", {
|
||||
"current": modelData.version,
|
||||
"new": modelData.updateInfo.availableVersion
|
||||
});
|
||||
} else if (modelData.pendingUpdateInfo) {
|
||||
return I18n.tr("settings.plugins.update-pending", {
|
||||
"current": modelData.version,
|
||||
"new": modelData.pendingUpdateInfo.availableVersion,
|
||||
"required": modelData.pendingUpdateInfo.minNoctaliaVersion
|
||||
});
|
||||
}
|
||||
return "v" + modelData.version;
|
||||
}
|
||||
font.pointSize: Style.fontSizeXXS
|
||||
color: modelData.updateInfo ? Color.mPrimary : (modelData.pendingUpdateInfo ? Color.mTertiary : Color.mOnSurfaceVariant)
|
||||
font.weight: (modelData.updateInfo || modelData.pendingUpdateInfo) ? Style.fontWeightMedium : Style.fontWeightRegular
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "•"
|
||||
font.pointSize: Style.fontSizeXXS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NText {
|
||||
text: stripAuthorEmail(modelData.author)
|
||||
font.pointSize: Style.fontSizeXXS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
// Source indicator for non-official plugins
|
||||
NText {
|
||||
visible: !modelData.isOfficial
|
||||
text: "•"
|
||||
font.pointSize: Style.fontSizeXXS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NText {
|
||||
visible: !modelData.isOfficial
|
||||
text: modelData.sourceName || I18n.tr("settings.plugins.source.custom")
|
||||
font.pointSize: Style.fontSizeXXS
|
||||
color: Color.mTertiary
|
||||
}
|
||||
}
|
||||
|
||||
// Error indicator
|
||||
RowLayout {
|
||||
spacing: Style.marginS
|
||||
visible: PluginService.hasPluginError(modelData.compositeKey)
|
||||
|
||||
NIcon {
|
||||
icon: "alert-triangle"
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mError
|
||||
}
|
||||
|
||||
NText {
|
||||
property var errorInfo: PluginService.getPluginError(modelData.compositeKey)
|
||||
text: errorInfo ? errorInfo.error : ""
|
||||
font.pointSize: Style.fontSizeXXS
|
||||
color: Color.mError
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 3
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "settings"
|
||||
tooltipText: I18n.tr("settings.plugins.settings.tooltip")
|
||||
baseSize: Style.baseWidgetSize * 0.7
|
||||
visible: modelData.entryPoints?.settings !== undefined
|
||||
onClicked: {
|
||||
pluginSettingsDialog.openPluginSettings(modelData);
|
||||
}
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "trash"
|
||||
tooltipText: I18n.tr("settings.plugins.uninstall")
|
||||
baseSize: Style.baseWidgetSize * 0.7
|
||||
onClicked: {
|
||||
uninstallDialog.pluginToUninstall = modelData;
|
||||
uninstallDialog.open();
|
||||
}
|
||||
}
|
||||
|
||||
NButton {
|
||||
id: updateButton
|
||||
property string pluginId: modelData.compositeKey
|
||||
property bool isUpdating: root.updatingPlugins[pluginId] === true
|
||||
|
||||
text: isUpdating ? I18n.tr("settings.plugins.updating") : I18n.tr("settings.plugins.update")
|
||||
icon: isUpdating ? "" : "download"
|
||||
visible: modelData.updateInfo !== undefined
|
||||
enabled: !isUpdating
|
||||
backgroundColor: Color.mPrimary
|
||||
textColor: Color.mOnPrimary
|
||||
onClicked: {
|
||||
var pid = pluginId;
|
||||
var pname = modelData.name;
|
||||
var pversion = modelData.updateInfo?.availableVersion || "";
|
||||
var rootRef = root;
|
||||
var updates = Object.assign({}, rootRef.updatingPlugins);
|
||||
updates[pid] = true;
|
||||
rootRef.updatingPlugins = updates;
|
||||
|
||||
PluginService.updatePlugin(pid, function (success, error) {
|
||||
var updates2 = Object.assign({}, rootRef.updatingPlugins);
|
||||
updates2[pid] = false;
|
||||
rootRef.updatingPlugins = updates2;
|
||||
|
||||
if (success) {
|
||||
ToastService.showNotice(I18n.tr("settings.plugins.title"), I18n.tr("settings.plugins.update-success", {
|
||||
"plugin": pname,
|
||||
"version": pversion
|
||||
}));
|
||||
} else {
|
||||
ToastService.showError(I18n.tr("settings.plugins.title"), I18n.tr("settings.plugins.update-error", {
|
||||
"plugin": pname,
|
||||
"error": error || "Unknown error"
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
checked: modelData.enabled
|
||||
baseSize: Style.baseWidgetSize * 0.7
|
||||
onToggled: checked => {
|
||||
if (checked) {
|
||||
PluginService.enablePlugin(modelData.compositeKey);
|
||||
} else {
|
||||
PluginService.disablePlugin(modelData.compositeKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NLabel {
|
||||
visible: PluginRegistry.getAllInstalledPluginIds().length === 0
|
||||
label: I18n.tr("settings.plugins.installed.no-plugins-label")
|
||||
description: I18n.tr("settings.plugins.installed.no-plugins-description")
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
// Available Plugins (Sources + Filter + List)
|
||||
// ------------------------------
|
||||
NHeader {
|
||||
label: I18n.tr("settings.plugins.available.label")
|
||||
description: I18n.tr("settings.plugins.available.description")
|
||||
}
|
||||
|
||||
// Sources
|
||||
NCollapsible {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.plugins.sources.label")
|
||||
description: I18n.tr("settings.plugins.sources.description")
|
||||
expanded: false
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginM
|
||||
Layout.fillWidth: true
|
||||
|
||||
// List of plugin sources
|
||||
Repeater {
|
||||
id: pluginSourcesRepeater
|
||||
model: PluginRegistry.pluginSources || []
|
||||
|
||||
delegate: RowLayout {
|
||||
spacing: Style.marginM
|
||||
Layout.fillWidth: true
|
||||
|
||||
NIcon {
|
||||
icon: "brand-github"
|
||||
pointSize: Style.fontSizeM
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: modelData.name
|
||||
color: Color.mOnSurface
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NText {
|
||||
text: modelData.url
|
||||
font.pointSize: Style.fontSizeS
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "trash"
|
||||
tooltipText: I18n.tr("settings.plugins.sources.remove.tooltip")
|
||||
visible: index !== 0 // Cannot remove official source
|
||||
baseSize: Style.baseWidgetSize * 0.7
|
||||
onClicked: {
|
||||
PluginRegistry.removePluginSource(modelData.url);
|
||||
}
|
||||
}
|
||||
|
||||
// Enable/Disable a source
|
||||
NToggle {
|
||||
checked: modelData.enabled !== false // Default to true if not set
|
||||
baseSize: Style.baseWidgetSize * 0.7
|
||||
onToggled: checked => {
|
||||
PluginRegistry.setSourceEnabled(modelData.url, checked);
|
||||
PluginService.refreshAvailablePlugins();
|
||||
ToastService.showNotice(I18n.tr("settings.plugins.title"), I18n.tr("settings.plugins.refresh.refreshing"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Add custom repository
|
||||
NButton {
|
||||
text: I18n.tr("settings.plugins.sources.add-custom")
|
||||
icon: "plus"
|
||||
onClicked: {
|
||||
addSourceDialog.open();
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Filter controls
|
||||
RowLayout {
|
||||
spacing: Style.marginM
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginM
|
||||
Layout.bottomMargin: Style.marginM
|
||||
|
||||
NTabBar {
|
||||
id: filterTabBar
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
currentIndex: 0
|
||||
onCurrentIndexChanged: {
|
||||
if (currentIndex === 0)
|
||||
pluginFilter = "all";
|
||||
else if (currentIndex === 1)
|
||||
pluginFilter = "downloaded";
|
||||
else if (currentIndex === 2)
|
||||
pluginFilter = "notDownloaded";
|
||||
}
|
||||
|
||||
NTabButton {
|
||||
Layout.fillWidth: true
|
||||
text: I18n.tr("settings.plugins.filter.all")
|
||||
tabIndex: 0
|
||||
checked: pluginFilter === "all"
|
||||
}
|
||||
|
||||
NTabButton {
|
||||
Layout.fillWidth: true
|
||||
text: I18n.tr("settings.plugins.filter.downloaded")
|
||||
tabIndex: 1
|
||||
checked: pluginFilter === "downloaded"
|
||||
}
|
||||
|
||||
NTabButton {
|
||||
Layout.fillWidth: true
|
||||
text: I18n.tr("settings.plugins.filter.not-downloaded")
|
||||
tabIndex: 2
|
||||
checked: pluginFilter === "notDownloaded"
|
||||
}
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "refresh"
|
||||
tooltipText: I18n.tr("settings.plugins.refresh.tooltip")
|
||||
baseSize: Style.baseWidgetSize * 0.9
|
||||
onClicked: {
|
||||
PluginService.refreshAvailablePlugins();
|
||||
checkUpdatesTimer.restart();
|
||||
ToastService.showNotice(I18n.tr("settings.plugins.title"), I18n.tr("settings.plugins.refresh.refreshing"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property string pluginFilter: "all"
|
||||
property string pluginSearchText: ""
|
||||
|
||||
// Search input
|
||||
NTextInput {
|
||||
placeholderText: I18n.tr("placeholders.search")
|
||||
inputIconName: "search"
|
||||
text: root.pluginSearchText
|
||||
onTextChanged: root.pluginSearchText = text
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
// Available plugins list
|
||||
ColumnLayout {
|
||||
spacing: Style.marginM
|
||||
Layout.fillWidth: true
|
||||
|
||||
Repeater {
|
||||
id: availablePluginsRepeater
|
||||
|
||||
model: {
|
||||
var all = PluginService.availablePlugins || [];
|
||||
var filtered = [];
|
||||
|
||||
// First apply download filter
|
||||
for (var i = 0; i < all.length; i++) {
|
||||
var plugin = all[i];
|
||||
var downloaded = plugin.downloaded || false;
|
||||
|
||||
if (pluginFilter === "all") {
|
||||
filtered.push(plugin);
|
||||
} else if (pluginFilter === "downloaded" && downloaded) {
|
||||
filtered.push(plugin);
|
||||
} else if (pluginFilter === "notDownloaded" && !downloaded) {
|
||||
filtered.push(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
// Then apply fuzzy search if there's search text
|
||||
var query = root.pluginSearchText.trim();
|
||||
if (query !== "") {
|
||||
var results = Fuzzysort.go(query, filtered, {
|
||||
"keys": ["name", "description"],
|
||||
"threshold": 0.35,
|
||||
"limit": 50
|
||||
});
|
||||
filtered = [];
|
||||
for (var j = 0; j < results.length; j++) {
|
||||
filtered.push(results[j].obj);
|
||||
}
|
||||
} else {
|
||||
// Sort by lastUpdated (most recent first) when not searching
|
||||
filtered.sort(function (a, b) {
|
||||
var dateA = a.lastUpdated ? new Date(a.lastUpdated).getTime() : 0;
|
||||
var dateB = b.lastUpdated ? new Date(b.lastUpdated).getTime() : 0;
|
||||
return dateB - dateA;
|
||||
});
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
delegate: NBox {
|
||||
id: pluginBox
|
||||
property bool isHovered: hoverHandler.hovered
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Style.borderS
|
||||
Layout.rightMargin: Style.borderS
|
||||
implicitHeight: Math.round(contentColumn.implicitHeight + Style.marginL * 2)
|
||||
color: Color.mSurface
|
||||
|
||||
Behavior on implicitHeight {
|
||||
NumberAnimation {
|
||||
duration: 150
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
id: hoverHandler
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: contentColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginL
|
||||
spacing: Style.marginS
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginM
|
||||
Layout.fillWidth: true
|
||||
|
||||
NIcon {
|
||||
icon: "plugin"
|
||||
pointSize: Style.fontSizeL
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
|
||||
NText {
|
||||
text: modelData.name
|
||||
color: Color.mOnSurface
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
// Description excerpt - visible when not hovered
|
||||
NText {
|
||||
visible: !pluginBox.isHovered && modelData.description
|
||||
text: modelData.description || ""
|
||||
font.pointSize: Style.fontSizeXS
|
||||
color: Color.mOnSurfaceVariant
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Spacer when hovered or no description
|
||||
Item {
|
||||
visible: pluginBox.isHovered || !modelData.description
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Downloaded indicator
|
||||
NIcon {
|
||||
icon: "circle-check"
|
||||
pointSize: Style.fontSizeL
|
||||
color: Color.mPrimary
|
||||
visible: modelData.downloaded === true
|
||||
}
|
||||
|
||||
// Install/Uninstall button
|
||||
NIconButton {
|
||||
icon: modelData.downloaded ? "trash" : "download"
|
||||
baseSize: Style.baseWidgetSize * 0.7
|
||||
tooltipText: modelData.downloaded ? I18n.tr("settings.plugins.uninstall") : I18n.tr("settings.plugins.install")
|
||||
onClicked: {
|
||||
if (modelData.downloaded) {
|
||||
// Construct composite key for available plugins
|
||||
var pluginData = Object.assign({}, modelData);
|
||||
pluginData.compositeKey = PluginRegistry.generateCompositeKey(modelData.id, modelData.source?.url || "");
|
||||
uninstallDialog.pluginToUninstall = pluginData;
|
||||
uninstallDialog.open();
|
||||
} else {
|
||||
installPlugin(modelData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Description - visible on hover
|
||||
NText {
|
||||
visible: pluginBox.isHovered && modelData.description
|
||||
text: modelData.description || ""
|
||||
font.pointSize: Style.fontSizeXS
|
||||
color: Color.mOnSurface
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Details row - visible on hover
|
||||
RowLayout {
|
||||
visible: pluginBox.isHovered
|
||||
spacing: Style.marginS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "v" + modelData.version
|
||||
font.pointSize: Style.fontSizeXS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "•"
|
||||
font.pointSize: Style.fontSizeXS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NText {
|
||||
text: stripAuthorEmail(modelData.author)
|
||||
font.pointSize: Style.fontSizeXS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "•"
|
||||
font.pointSize: Style.fontSizeXS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NText {
|
||||
text: modelData.source ? modelData.source.name : ""
|
||||
font.pointSize: Style.fontSizeXS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NLabel {
|
||||
visible: availablePluginsRepeater.count === 0
|
||||
label: I18n.tr("settings.plugins.available.no-plugins-label")
|
||||
description: I18n.tr("settings.plugins.available.no-plugins-description")
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
// Dialogs
|
||||
// ------------------------------
|
||||
|
||||
// Add source dialog
|
||||
Popup {
|
||||
id: addSourceDialog
|
||||
parent: Overlay.overlay
|
||||
modal: true
|
||||
dim: false
|
||||
anchors.centerIn: parent
|
||||
width: 500
|
||||
padding: Style.marginL
|
||||
|
||||
background: Rectangle {
|
||||
color: Color.mSurface
|
||||
radius: Style.radiusS
|
||||
border.color: Color.mPrimary
|
||||
border.width: Style.borderM
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
width: parent.width
|
||||
spacing: Style.marginL
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.plugins.sources.add-dialog.title")
|
||||
description: I18n.tr("settings.plugins.sources.add-dialog.description")
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
id: sourceNameInput
|
||||
label: I18n.tr("settings.plugins.sources.add-dialog.name")
|
||||
placeholderText: I18n.tr("settings.plugins.sources.add-dialog.name-placeholder")
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
id: sourceUrlInput
|
||||
label: I18n.tr("settings.plugins.sources.add-dialog.url")
|
||||
placeholderText: "https://github.com/user/repo"
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginM
|
||||
Layout.fillWidth: true
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NButton {
|
||||
text: I18n.tr("common.cancel")
|
||||
onClicked: addSourceDialog.close()
|
||||
}
|
||||
|
||||
NButton {
|
||||
text: I18n.tr("common.add")
|
||||
backgroundColor: Color.mPrimary
|
||||
textColor: Color.mOnPrimary
|
||||
enabled: sourceNameInput.text.length > 0 && sourceUrlInput.text.length > 0
|
||||
onClicked: {
|
||||
if (PluginRegistry.addPluginSource(sourceNameInput.text, sourceUrlInput.text)) {
|
||||
ToastService.showNotice(I18n.tr("settings.plugins.title"), I18n.tr("settings.plugins.sources.add-dialog.success"));
|
||||
PluginService.refreshAvailablePlugins();
|
||||
addSourceDialog.close();
|
||||
sourceNameInput.text = "";
|
||||
sourceUrlInput.text = "";
|
||||
} else {
|
||||
ToastService.showError(I18n.tr("settings.plugins.title"), I18n.tr("settings.plugins.sources.add-dialog.error"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Uninstall confirmation dialog
|
||||
Popup {
|
||||
id: uninstallDialog
|
||||
parent: Overlay.overlay
|
||||
modal: true
|
||||
dim: false
|
||||
anchors.centerIn: parent
|
||||
width: 400 * Style.uiScaleRatio
|
||||
padding: Style.marginL
|
||||
|
||||
property var pluginToUninstall: null
|
||||
|
||||
background: Rectangle {
|
||||
color: Color.mSurface
|
||||
radius: Style.radiusS
|
||||
border.color: Color.mPrimary
|
||||
border.width: Style.borderM
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
width: parent.width
|
||||
spacing: Style.marginL
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.plugins.uninstall-dialog.title")
|
||||
description: I18n.tr("settings.plugins.uninstall-dialog.description", {
|
||||
"plugin": uninstallDialog.pluginToUninstall?.name || ""
|
||||
})
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginM
|
||||
Layout.fillWidth: true
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NButton {
|
||||
text: I18n.tr("common.cancel")
|
||||
onClicked: uninstallDialog.close()
|
||||
}
|
||||
|
||||
NButton {
|
||||
text: I18n.tr("settings.plugins.uninstall")
|
||||
backgroundColor: Color.mPrimary
|
||||
textColor: Color.mOnPrimary
|
||||
onClicked: {
|
||||
if (uninstallDialog.pluginToUninstall) {
|
||||
root.uninstallPlugin(uninstallDialog.pluginToUninstall.compositeKey);
|
||||
uninstallDialog.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Plugin settings popup
|
||||
NPluginSettingsPopup {
|
||||
id: pluginSettingsDialog
|
||||
parent: Overlay.overlay
|
||||
showToastOnSave: true
|
||||
}
|
||||
|
||||
// Timer to check for updates after refresh starts
|
||||
Timer {
|
||||
id: checkUpdatesTimer
|
||||
interval: 100
|
||||
onTriggered: {
|
||||
PluginService.checkForUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
// Timer to recheck updates after available plugins are updated
|
||||
Timer {
|
||||
id: recheckUpdatesTimer
|
||||
interval: 50
|
||||
onTriggered: {
|
||||
PluginService.checkForUpdates();
|
||||
}
|
||||
}
|
||||
// ------------------------------
|
||||
// Functions
|
||||
// ------------------------------
|
||||
|
||||
function installPlugin(pluginMetadata) {
|
||||
ToastService.showNotice(I18n.tr("settings.plugins.title"), I18n.tr("settings.plugins.installing", {
|
||||
"plugin": pluginMetadata.name
|
||||
}));
|
||||
|
||||
PluginService.installPlugin(pluginMetadata, false, function (success, error, registeredKey) {
|
||||
if (success) {
|
||||
ToastService.showNotice(I18n.tr("settings.plugins.title"), I18n.tr("settings.plugins.install-success", {
|
||||
"plugin": pluginMetadata.name
|
||||
}));
|
||||
// Auto-enable the plugin after installation (use registered key which may be composite)
|
||||
PluginService.enablePlugin(registeredKey);
|
||||
} else {
|
||||
ToastService.showError(I18n.tr("settings.plugins.title"), I18n.tr("settings.plugins.install-error", {
|
||||
"error": error || "Unknown error"
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function uninstallPlugin(pluginId) {
|
||||
var manifest = PluginRegistry.getPluginManifest(pluginId);
|
||||
var pluginName = manifest?.name || pluginId;
|
||||
|
||||
ToastService.showNotice(I18n.tr("settings.plugins.title"), I18n.tr("settings.plugins.uninstalling", {
|
||||
"plugin": pluginName
|
||||
}));
|
||||
|
||||
PluginService.uninstallPlugin(pluginId, function (success, error) {
|
||||
if (success) {
|
||||
ToastService.showNotice(I18n.tr("settings.plugins.title"), I18n.tr("settings.plugins.uninstall-success", {
|
||||
"plugin": pluginName
|
||||
}));
|
||||
} else {
|
||||
ToastService.showError(I18n.tr("settings.plugins.title"), I18n.tr("settings.plugins.uninstall-error", {
|
||||
"error": error || "Unknown error"
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Listen to plugin registry changes
|
||||
Connections {
|
||||
target: PluginRegistry
|
||||
|
||||
function onPluginsChanged() {
|
||||
// Force model refresh for installed plugins by incrementing counter
|
||||
root.installedPluginsRefreshCounter++;
|
||||
|
||||
// Force model refresh for plugin sources
|
||||
pluginSourcesRepeater.model = undefined;
|
||||
Qt.callLater(function () {
|
||||
pluginSourcesRepeater.model = Qt.binding(function () {
|
||||
return PluginRegistry.pluginSources || [];
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Listen to plugin service signals
|
||||
Connections {
|
||||
target: PluginService
|
||||
|
||||
function onAvailablePluginsUpdated() {
|
||||
// Force model refresh for available plugins
|
||||
availablePluginsRepeater.model = undefined;
|
||||
Qt.callLater(function () {
|
||||
availablePluginsRepeater.model = Qt.binding(function () {
|
||||
var all = PluginService.availablePlugins || [];
|
||||
var filtered = [];
|
||||
|
||||
for (var i = 0; i < all.length; i++) {
|
||||
var plugin = all[i];
|
||||
var downloaded = plugin.downloaded || false;
|
||||
|
||||
if (root.pluginFilter === "all") {
|
||||
filtered.push(plugin);
|
||||
} else if (root.pluginFilter === "downloaded" && downloaded) {
|
||||
filtered.push(plugin);
|
||||
} else if (root.pluginFilter === "notDownloaded" && !downloaded) {
|
||||
filtered.push(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
return filtered;
|
||||
});
|
||||
});
|
||||
|
||||
// Manually trigger update check after a small delay to ensure all registries are loaded
|
||||
Qt.callLater(function () {
|
||||
PluginService.checkForUpdates();
|
||||
});
|
||||
}
|
||||
|
||||
function onPluginUpdatesChanged() {
|
||||
// Increment counter to force installed plugins model refresh
|
||||
root.installedPluginsRefreshCounter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,11 +97,6 @@ ColumnLayout {
|
||||
spacing: Style.marginM
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.region.clock-panel.section.label")
|
||||
description: I18n.tr("settings.region.clock-panel.section.description")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.location.date-time.use-analog.label")
|
||||
description: I18n.tr("settings.location.date-time.use-analog.description")
|
||||
@@ -126,8 +121,6 @@ ColumnLayout {
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
// Calendar Cards Management Section
|
||||
@@ -135,11 +128,6 @@ ColumnLayout {
|
||||
spacing: Style.marginXXS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.location.calendar.cards.section.label")
|
||||
description: I18n.tr("settings.location.calendar.cards.section.description")
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Settings.data.location
|
||||
function onWeatherEnabledChanged() {
|
||||
|
||||
@@ -9,11 +9,6 @@ ColumnLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.location.date-time.section.label")
|
||||
description: I18n.tr("settings.location.date-time.section.description")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.location.date-time.12hour-format.label")
|
||||
description: I18n.tr("settings.location.date-time.12hour-format.description")
|
||||
|
||||
@@ -9,11 +9,6 @@ ColumnLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.general.language.section.label")
|
||||
description: I18n.tr("settings.general.language.section.description")
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.general.language.select.label")
|
||||
|
||||
@@ -10,11 +10,6 @@ ColumnLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.location.location.section.label")
|
||||
description: I18n.tr("settings.location.location.section.description")
|
||||
}
|
||||
|
||||
// Location section
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
@@ -55,22 +50,10 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
// Weather section
|
||||
ColumnLayout {
|
||||
spacing: Style.marginM
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.location.weather.section.label")
|
||||
description: I18n.tr("settings.location.weather.section.description")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.location.weather.enabled.label")
|
||||
description: I18n.tr("settings.location.weather.enabled.description")
|
||||
|
||||
@@ -15,12 +15,12 @@ ColumnLayout {
|
||||
currentIndex: tabView.currentIndex
|
||||
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.region.tabs.location")
|
||||
text: I18n.tr("settings.region.tabs.language")
|
||||
tabIndex: 0
|
||||
checked: subTabBar.currentIndex === 0
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.region.tabs.language")
|
||||
text: I18n.tr("settings.region.tabs.location")
|
||||
tabIndex: 1
|
||||
checked: subTabBar.currentIndex === 1
|
||||
}
|
||||
@@ -45,8 +45,8 @@ ColumnLayout {
|
||||
id: tabView
|
||||
currentIndex: subTabBar.currentIndex
|
||||
|
||||
LocationSubTab {}
|
||||
LanguageSubTab {}
|
||||
LocationSubTab {}
|
||||
DateSubTab {}
|
||||
ClockPanelSubTab {}
|
||||
}
|
||||
|
||||
@@ -52,8 +52,6 @@ ColumnLayout {
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
// Video Settings
|
||||
@@ -250,8 +248,6 @@ ColumnLayout {
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
|
||||
// Audio Settings
|
||||
@@ -325,7 +321,5 @@ ColumnLayout {
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
property var entriesModel: []
|
||||
property var updateEntry
|
||||
property var reorderEntries
|
||||
property var openEntrySettingsDialog
|
||||
|
||||
// List of items
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: listView.contentHeight
|
||||
|
||||
ListView {
|
||||
id: listView
|
||||
anchors.fill: parent
|
||||
spacing: Style.marginS
|
||||
interactive: false
|
||||
clip: true
|
||||
model: root.entriesModel
|
||||
|
||||
delegate: Item {
|
||||
id: delegateItem
|
||||
width: listView.width
|
||||
height: contentRow.height
|
||||
|
||||
required property int index
|
||||
required property var modelData
|
||||
|
||||
property bool dragging: false
|
||||
property int dragStartY: 0
|
||||
property int dragStartIndex: -1
|
||||
property int dragTargetIndex: -1
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Style.radiusM
|
||||
color: delegateItem.dragging ? Color.mSurfaceVariant : "transparent"
|
||||
border.color: delegateItem.dragging ? Color.mOutline : "transparent"
|
||||
border.width: Style.borderS
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: contentRow
|
||||
width: parent.width
|
||||
spacing: Style.marginS
|
||||
|
||||
// Drag handle
|
||||
Rectangle {
|
||||
Layout.preferredWidth: Style.baseWidgetSize * 0.7
|
||||
Layout.preferredHeight: Style.baseWidgetSize * 0.7
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
radius: Style.radiusXS
|
||||
color: dragHandleMouseArea.containsMouse ? Color.mSurfaceVariant : "transparent"
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
spacing: 2
|
||||
|
||||
Repeater {
|
||||
model: 3
|
||||
Rectangle {
|
||||
Layout.preferredWidth: Style.baseWidgetSize * 0.28
|
||||
Layout.preferredHeight: 2
|
||||
radius: 1
|
||||
color: Color.mOutline
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: dragHandleMouseArea
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.SizeVerCursor
|
||||
hoverEnabled: true
|
||||
preventStealing: false
|
||||
z: 1000
|
||||
|
||||
onPressed: mouse => {
|
||||
delegateItem.dragStartIndex = delegateItem.index;
|
||||
delegateItem.dragTargetIndex = delegateItem.index;
|
||||
delegateItem.dragStartY = delegateItem.y;
|
||||
delegateItem.dragging = true;
|
||||
delegateItem.z = 999;
|
||||
preventStealing = true;
|
||||
}
|
||||
|
||||
onPositionChanged: mouse => {
|
||||
if (delegateItem.dragging) {
|
||||
var dy = mouse.y - height / 2;
|
||||
var newY = delegateItem.y + dy;
|
||||
newY = Math.max(0, Math.min(newY, listView.contentHeight - delegateItem.height));
|
||||
delegateItem.y = newY;
|
||||
var targetIndex = Math.floor((newY + delegateItem.height / 2) / (delegateItem.height + Style.marginS));
|
||||
targetIndex = Math.max(0, Math.min(targetIndex, listView.count - 1));
|
||||
delegateItem.dragTargetIndex = targetIndex;
|
||||
}
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
preventStealing = false;
|
||||
if (delegateItem.dragStartIndex !== -1 && delegateItem.dragTargetIndex !== -1 && delegateItem.dragStartIndex !== delegateItem.dragTargetIndex) {
|
||||
root.reorderEntries(delegateItem.dragStartIndex, delegateItem.dragTargetIndex);
|
||||
}
|
||||
delegateItem.dragging = false;
|
||||
delegateItem.dragStartIndex = -1;
|
||||
delegateItem.dragTargetIndex = -1;
|
||||
delegateItem.z = 0;
|
||||
}
|
||||
|
||||
onCanceled: {
|
||||
preventStealing = false;
|
||||
delegateItem.dragging = false;
|
||||
delegateItem.dragStartIndex = -1;
|
||||
delegateItem.dragTargetIndex = -1;
|
||||
delegateItem.z = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enable checkbox
|
||||
Rectangle {
|
||||
Layout.preferredWidth: Style.baseWidgetSize * 0.7
|
||||
Layout.preferredHeight: Style.baseWidgetSize * 0.7
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
radius: Style.radiusXS
|
||||
color: modelData.enabled ? Color.mPrimary : Color.mSurface
|
||||
border.color: Color.mOutline
|
||||
border.width: Style.borderS
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
NIcon {
|
||||
visible: modelData.enabled
|
||||
anchors.centerIn: parent
|
||||
anchors.horizontalCenterOffset: -1
|
||||
icon: "check"
|
||||
color: Color.mOnPrimary
|
||||
pointSize: Math.max(Style.fontSizeXS, Style.baseWidgetSize * 0.35)
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
root.updateEntry(index, {
|
||||
"enabled": !modelData.enabled
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Label
|
||||
NText {
|
||||
Layout.fillWidth: true
|
||||
text: modelData.text
|
||||
color: Color.mOnSurface
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
// Countdown toggle with icon (only shown when global countdown is enabled)
|
||||
RowLayout {
|
||||
visible: Settings.data.sessionMenu.enableCountdown
|
||||
spacing: Style.marginXS
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
NIcon {
|
||||
icon: "clock"
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: Style.fontSizeS
|
||||
}
|
||||
|
||||
NToggle {
|
||||
checked: modelData.countdownEnabled !== undefined ? modelData.countdownEnabled : true
|
||||
onToggled: checked => root.updateEntry(delegateItem.index, {
|
||||
"countdownEnabled": checked
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Settings button (cogwheel)
|
||||
NIconButton {
|
||||
icon: "settings"
|
||||
tooltipText: I18n.tr("settings.session-menu.entry-settings.tooltip")
|
||||
baseSize: Style.baseWidgetSize * 0.7
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
onClicked: {
|
||||
root.openEntrySettingsDialog(delegateItem.index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Position binding for non-dragging state
|
||||
y: {
|
||||
if (delegateItem.dragging) {
|
||||
return delegateItem.y;
|
||||
}
|
||||
|
||||
var draggedIndex = -1;
|
||||
var targetIndex = -1;
|
||||
for (var i = 0; i < listView.count; i++) {
|
||||
var item = listView.itemAtIndex(i);
|
||||
if (item && item.dragging) {
|
||||
draggedIndex = item.dragStartIndex;
|
||||
targetIndex = item.dragTargetIndex;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (draggedIndex !== -1 && targetIndex !== -1 && draggedIndex !== targetIndex) {
|
||||
var currentIndex = delegateItem.index;
|
||||
if (draggedIndex < targetIndex) {
|
||||
if (currentIndex > draggedIndex && currentIndex <= targetIndex) {
|
||||
return (currentIndex - 1) * (delegateItem.height + Style.marginS);
|
||||
}
|
||||
} else {
|
||||
if (currentIndex >= targetIndex && currentIndex < draggedIndex) {
|
||||
return (currentIndex + 1) * (delegateItem.height + Style.marginS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return delegateItem.index * (delegateItem.height + Style.marginS);
|
||||
}
|
||||
|
||||
Behavior on y {
|
||||
enabled: !delegateItem.dragging
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.session-menu.large-buttons-style.label")
|
||||
description: I18n.tr("settings.session-menu.large-buttons-style.description")
|
||||
checked: Settings.data.sessionMenu.largeButtonsStyle
|
||||
onToggled: checked => Settings.data.sessionMenu.largeButtonsStyle = checked
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
visible: Settings.data.sessionMenu.largeButtonsStyle
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.session-menu.large-buttons-layout.label")
|
||||
description: I18n.tr("settings.session-menu.large-buttons-layout.description")
|
||||
model: [
|
||||
{
|
||||
"key": "grid",
|
||||
"name": I18n.tr("options.session-menu-grid-layout.grid")
|
||||
},
|
||||
{
|
||||
"key": "single-row",
|
||||
"name": I18n.tr("options.session-menu-grid-layout.single-row")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.sessionMenu.largeButtonsLayout
|
||||
defaultValue: Settings.getDefaultValue("sessionMenu.largeButtonsLayout")
|
||||
onSelected: key => Settings.data.sessionMenu.largeButtonsLayout = key
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.session-menu.show-number-labels.label")
|
||||
description: I18n.tr("settings.session-menu.show-number-labels.description")
|
||||
checked: Settings.data.sessionMenu.showNumberLabels !== false
|
||||
defaultValue: Settings.getDefaultValue("sessionMenu.showNumberLabels") ?? true
|
||||
onToggled: checked => Settings.data.sessionMenu.showNumberLabels = checked
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("settings.session-menu.position.label")
|
||||
description: I18n.tr("settings.session-menu.position.description")
|
||||
Layout.fillWidth: true
|
||||
model: [
|
||||
{
|
||||
"key": "center",
|
||||
"name": I18n.tr("options.control-center.position.center")
|
||||
},
|
||||
{
|
||||
"key": "top_center",
|
||||
"name": I18n.tr("options.control-center.position.top_center")
|
||||
},
|
||||
{
|
||||
"key": "top_left",
|
||||
"name": I18n.tr("options.control-center.position.top_left")
|
||||
},
|
||||
{
|
||||
"key": "top_right",
|
||||
"name": I18n.tr("options.control-center.position.top_right")
|
||||
},
|
||||
{
|
||||
"key": "bottom_center",
|
||||
"name": I18n.tr("options.control-center.position.bottom_center")
|
||||
},
|
||||
{
|
||||
"key": "bottom_left",
|
||||
"name": I18n.tr("options.control-center.position.bottom_left")
|
||||
},
|
||||
{
|
||||
"key": "bottom_right",
|
||||
"name": I18n.tr("options.control-center.position.bottom_right")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.sessionMenu.position
|
||||
onSelected: key => Settings.data.sessionMenu.position = key
|
||||
visible: !Settings.data.sessionMenu.largeButtonsStyle
|
||||
defaultValue: Settings.getDefaultValue("sessionMenu.position")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.session-menu.show-header.label")
|
||||
description: I18n.tr("settings.session-menu.show-header.description")
|
||||
checked: Settings.data.sessionMenu.showHeader
|
||||
onToggled: checked => Settings.data.sessionMenu.showHeader = checked
|
||||
visible: !Settings.data.sessionMenu.largeButtonsStyle
|
||||
defaultValue: Settings.getDefaultValue("sessionMenu.showHeader")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.session-menu.enable-countdown.label")
|
||||
description: I18n.tr("settings.session-menu.enable-countdown.description")
|
||||
checked: Settings.data.sessionMenu.enableCountdown
|
||||
onToggled: checked => Settings.data.sessionMenu.enableCountdown = checked
|
||||
defaultValue: Settings.getDefaultValue("sessionMenu.enableCountdown")
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
visible: Settings.data.sessionMenu.enableCountdown
|
||||
spacing: Style.marginXXS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("settings.session-menu.countdown-duration.label")
|
||||
description: I18n.tr("settings.session-menu.countdown-duration.description")
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
from: 1000
|
||||
to: 30000
|
||||
stepSize: 1000
|
||||
value: Settings.data.sessionMenu.countdownDuration
|
||||
onMoved: value => Settings.data.sessionMenu.countdownDuration = value
|
||||
text: Math.round(Settings.data.sessionMenu.countdownDuration / 1000) + "s"
|
||||
defaultValue: Settings.getDefaultValue("sessionMenu.countdownDuration")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,7 @@ import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
spacing: Style.marginL
|
||||
spacing: 0
|
||||
|
||||
property list<var> entriesModel: []
|
||||
property list<var> entriesDefault: [
|
||||
@@ -165,394 +164,39 @@ ColumnLayout {
|
||||
saveEntries();
|
||||
}
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.session-menu.general.section.label")
|
||||
description: I18n.tr("settings.session-menu.general.section.description")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
NTabBar {
|
||||
id: subTabBar
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.session-menu.large-buttons-style.label")
|
||||
description: I18n.tr("settings.session-menu.large-buttons-style.description")
|
||||
checked: Settings.data.sessionMenu.largeButtonsStyle
|
||||
onToggled: checked => Settings.data.sessionMenu.largeButtonsStyle = checked
|
||||
}
|
||||
distributeEvenly: true
|
||||
currentIndex: tabView.currentIndex
|
||||
|
||||
NComboBox {
|
||||
visible: Settings.data.sessionMenu.largeButtonsStyle
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.session-menu.large-buttons-layout.label")
|
||||
description: I18n.tr("settings.session-menu.large-buttons-layout.description")
|
||||
model: [
|
||||
{
|
||||
"key": "grid",
|
||||
"name": I18n.tr("options.session-menu-grid-layout.grid")
|
||||
},
|
||||
{
|
||||
"key": "single-row",
|
||||
"name": I18n.tr("options.session-menu-grid-layout.single-row")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.sessionMenu.largeButtonsLayout
|
||||
defaultValue: Settings.getDefaultValue("sessionMenu.largeButtonsLayout")
|
||||
onSelected: key => Settings.data.sessionMenu.largeButtonsLayout = key
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.session-menu.show-number-labels.label")
|
||||
description: I18n.tr("settings.session-menu.show-number-labels.description")
|
||||
checked: Settings.data.sessionMenu.showNumberLabels !== false
|
||||
defaultValue: Settings.getDefaultValue("sessionMenu.showNumberLabels") ?? true
|
||||
onToggled: checked => Settings.data.sessionMenu.showNumberLabels = checked
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("settings.session-menu.position.label")
|
||||
description: I18n.tr("settings.session-menu.position.description")
|
||||
Layout.fillWidth: true
|
||||
model: [
|
||||
{
|
||||
"key": "center",
|
||||
"name": I18n.tr("options.control-center.position.center")
|
||||
},
|
||||
{
|
||||
"key": "top_center",
|
||||
"name": I18n.tr("options.control-center.position.top_center")
|
||||
},
|
||||
{
|
||||
"key": "top_left",
|
||||
"name": I18n.tr("options.control-center.position.top_left")
|
||||
},
|
||||
{
|
||||
"key": "top_right",
|
||||
"name": I18n.tr("options.control-center.position.top_right")
|
||||
},
|
||||
{
|
||||
"key": "bottom_center",
|
||||
"name": I18n.tr("options.control-center.position.bottom_center")
|
||||
},
|
||||
{
|
||||
"key": "bottom_left",
|
||||
"name": I18n.tr("options.control-center.position.bottom_left")
|
||||
},
|
||||
{
|
||||
"key": "bottom_right",
|
||||
"name": I18n.tr("options.control-center.position.bottom_right")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.sessionMenu.position
|
||||
onSelected: key => Settings.data.sessionMenu.position = key
|
||||
visible: !Settings.data.sessionMenu.largeButtonsStyle
|
||||
defaultValue: Settings.getDefaultValue("sessionMenu.position")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.session-menu.show-header.label")
|
||||
description: I18n.tr("settings.session-menu.show-header.description")
|
||||
checked: Settings.data.sessionMenu.showHeader
|
||||
onToggled: checked => Settings.data.sessionMenu.showHeader = checked
|
||||
visible: !Settings.data.sessionMenu.largeButtonsStyle
|
||||
defaultValue: Settings.getDefaultValue("sessionMenu.showHeader")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.session-menu.enable-countdown.label")
|
||||
description: I18n.tr("settings.session-menu.enable-countdown.description")
|
||||
checked: Settings.data.sessionMenu.enableCountdown
|
||||
onToggled: checked => Settings.data.sessionMenu.enableCountdown = checked
|
||||
defaultValue: Settings.getDefaultValue("sessionMenu.enableCountdown")
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
visible: Settings.data.sessionMenu.enableCountdown
|
||||
spacing: Style.marginXXS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("settings.session-menu.countdown-duration.label")
|
||||
description: I18n.tr("settings.session-menu.countdown-duration.description")
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.session-menu.tabs.general")
|
||||
tabIndex: 0
|
||||
checked: subTabBar.currentIndex === 0
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
from: 1000
|
||||
to: 30000
|
||||
stepSize: 1000
|
||||
value: Settings.data.sessionMenu.countdownDuration
|
||||
onMoved: value => Settings.data.sessionMenu.countdownDuration = value
|
||||
text: Math.round(Settings.data.sessionMenu.countdownDuration / 1000) + "s"
|
||||
defaultValue: Settings.getDefaultValue("sessionMenu.countdownDuration")
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.session-menu.tabs.actions")
|
||||
tabIndex: 1
|
||||
checked: subTabBar.currentIndex === 1
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
Layout.preferredHeight: Style.marginL
|
||||
}
|
||||
|
||||
// Entries Management Section
|
||||
ColumnLayout {
|
||||
spacing: Style.marginM
|
||||
Layout.fillWidth: true
|
||||
NTabView {
|
||||
id: tabView
|
||||
currentIndex: subTabBar.currentIndex
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("settings.session-menu.entries.section.label")
|
||||
description: I18n.tr("settings.session-menu.entries.section.description")
|
||||
GeneralSubTab {}
|
||||
ActionsSubTab {
|
||||
entriesModel: root.entriesModel
|
||||
updateEntry: root.updateEntry
|
||||
reorderEntries: root.reorderEntries
|
||||
openEntrySettingsDialog: root.openEntrySettingsDialog
|
||||
}
|
||||
|
||||
// List of items
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: listView.contentHeight
|
||||
|
||||
ListView {
|
||||
id: listView
|
||||
anchors.fill: parent
|
||||
spacing: Style.marginS
|
||||
interactive: false
|
||||
clip: true
|
||||
model: entriesModel
|
||||
|
||||
delegate: Item {
|
||||
id: delegateItem
|
||||
width: listView.width
|
||||
height: contentRow.height
|
||||
|
||||
required property int index
|
||||
required property var modelData
|
||||
|
||||
property bool dragging: false
|
||||
property int dragStartY: 0
|
||||
property int dragStartIndex: -1
|
||||
property int dragTargetIndex: -1
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Style.radiusM
|
||||
color: delegateItem.dragging ? Color.mSurfaceVariant : "transparent"
|
||||
border.color: delegateItem.dragging ? Color.mOutline : "transparent"
|
||||
border.width: Style.borderS
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: contentRow
|
||||
width: parent.width
|
||||
spacing: Style.marginS
|
||||
|
||||
// Drag handle
|
||||
Rectangle {
|
||||
Layout.preferredWidth: Style.baseWidgetSize * 0.7
|
||||
Layout.preferredHeight: Style.baseWidgetSize * 0.7
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
radius: Style.radiusXS
|
||||
color: dragHandleMouseArea.containsMouse ? Color.mSurfaceVariant : "transparent"
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
spacing: 2
|
||||
|
||||
Repeater {
|
||||
model: 3
|
||||
Rectangle {
|
||||
Layout.preferredWidth: Style.baseWidgetSize * 0.28
|
||||
Layout.preferredHeight: 2
|
||||
radius: 1
|
||||
color: Color.mOutline
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: dragHandleMouseArea
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.SizeVerCursor
|
||||
hoverEnabled: true
|
||||
preventStealing: false
|
||||
z: 1000
|
||||
|
||||
onPressed: mouse => {
|
||||
delegateItem.dragStartIndex = delegateItem.index;
|
||||
delegateItem.dragTargetIndex = delegateItem.index;
|
||||
delegateItem.dragStartY = delegateItem.y;
|
||||
delegateItem.dragging = true;
|
||||
delegateItem.z = 999;
|
||||
preventStealing = true;
|
||||
}
|
||||
|
||||
onPositionChanged: mouse => {
|
||||
if (delegateItem.dragging) {
|
||||
var dy = mouse.y - height / 2;
|
||||
var newY = delegateItem.y + dy;
|
||||
newY = Math.max(0, Math.min(newY, listView.contentHeight - delegateItem.height));
|
||||
delegateItem.y = newY;
|
||||
var targetIndex = Math.floor((newY + delegateItem.height / 2) / (delegateItem.height + Style.marginS));
|
||||
targetIndex = Math.max(0, Math.min(targetIndex, listView.count - 1));
|
||||
delegateItem.dragTargetIndex = targetIndex;
|
||||
}
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
preventStealing = false;
|
||||
if (delegateItem.dragStartIndex !== -1 && delegateItem.dragTargetIndex !== -1 && delegateItem.dragStartIndex !== delegateItem.dragTargetIndex) {
|
||||
root.reorderEntries(delegateItem.dragStartIndex, delegateItem.dragTargetIndex);
|
||||
}
|
||||
delegateItem.dragging = false;
|
||||
delegateItem.dragStartIndex = -1;
|
||||
delegateItem.dragTargetIndex = -1;
|
||||
delegateItem.z = 0;
|
||||
}
|
||||
|
||||
onCanceled: {
|
||||
preventStealing = false;
|
||||
delegateItem.dragging = false;
|
||||
delegateItem.dragStartIndex = -1;
|
||||
delegateItem.dragTargetIndex = -1;
|
||||
delegateItem.z = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enable checkbox
|
||||
Rectangle {
|
||||
Layout.preferredWidth: Style.baseWidgetSize * 0.7
|
||||
Layout.preferredHeight: Style.baseWidgetSize * 0.7
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
radius: Style.radiusXS
|
||||
color: modelData.enabled ? Color.mPrimary : Color.mSurface
|
||||
border.color: Color.mOutline
|
||||
border.width: Style.borderS
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
NIcon {
|
||||
visible: modelData.enabled
|
||||
anchors.centerIn: parent
|
||||
anchors.horizontalCenterOffset: -1
|
||||
icon: "check"
|
||||
color: Color.mOnPrimary
|
||||
pointSize: Math.max(Style.fontSizeXS, Style.baseWidgetSize * 0.35)
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
root.updateEntry(index, {
|
||||
"enabled": !modelData.enabled
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Label
|
||||
NText {
|
||||
Layout.fillWidth: true
|
||||
text: modelData.text
|
||||
color: Color.mOnSurface
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
// Countdown toggle with icon (only shown when global countdown is enabled)
|
||||
RowLayout {
|
||||
visible: Settings.data.sessionMenu.enableCountdown
|
||||
spacing: Style.marginXS
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
NIcon {
|
||||
icon: "clock"
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: Style.fontSizeS
|
||||
}
|
||||
|
||||
NToggle {
|
||||
checked: modelData.countdownEnabled !== undefined ? modelData.countdownEnabled : true
|
||||
onToggled: checked => root.updateEntry(delegateItem.index, {
|
||||
"countdownEnabled": checked
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Settings button (cogwheel)
|
||||
NIconButton {
|
||||
icon: "settings"
|
||||
tooltipText: I18n.tr("settings.session-menu.entry-settings.tooltip")
|
||||
baseSize: Style.baseWidgetSize * 0.7
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
onClicked: {
|
||||
openEntrySettingsDialog(delegateItem.index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Position binding for non-dragging state
|
||||
y: {
|
||||
if (delegateItem.dragging) {
|
||||
return delegateItem.y;
|
||||
}
|
||||
|
||||
var draggedIndex = -1;
|
||||
var targetIndex = -1;
|
||||
for (var i = 0; i < listView.count; i++) {
|
||||
var item = listView.itemAtIndex(i);
|
||||
if (item && item.dragging) {
|
||||
draggedIndex = item.dragStartIndex;
|
||||
targetIndex = item.dragTargetIndex;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (draggedIndex !== -1 && targetIndex !== -1 && draggedIndex !== targetIndex) {
|
||||
var currentIndex = delegateItem.index;
|
||||
if (draggedIndex < targetIndex) {
|
||||
if (currentIndex > draggedIndex && currentIndex <= targetIndex) {
|
||||
return (currentIndex - 1) * (delegateItem.height + Style.marginS);
|
||||
}
|
||||
} else {
|
||||
if (currentIndex >= targetIndex && currentIndex < draggedIndex) {
|
||||
return (currentIndex + 1) * (delegateItem.height + Style.marginS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return delegateItem.index * (delegateItem.height + Style.marginS);
|
||||
}
|
||||
|
||||
Behavior on y {
|
||||
enabled: !delegateItem.dragging
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
Layout.bottomMargin: Style.marginL
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,12 +11,6 @@ ColumnLayout {
|
||||
|
||||
property var screen
|
||||
|
||||
NHeader {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.system-monitor.general.section.label")
|
||||
description: I18n.tr("settings.system-monitor.general.section.description")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginM
|
||||
@@ -95,4 +89,17 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
label: I18n.tr("settings.system-monitor.external-monitor.label")
|
||||
description: I18n.tr("settings.system-monitor.external-monitor.description")
|
||||
placeholderText: I18n.tr("settings.system-monitor.external-monitor.placeholder")
|
||||
text: Settings.data.systemMonitor.externalMonitor
|
||||
defaultValue: Settings.getDefaultValue("systemMonitor.externalMonitor")
|
||||
onTextChanged: Settings.data.systemMonitor.externalMonitor = text
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,6 @@ ColumnLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.system-monitor.polling-section.label")
|
||||
description: I18n.tr("settings.system-monitor.polling-section.description")
|
||||
}
|
||||
|
||||
// CPU Polling
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
@@ -171,17 +165,8 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
NLabel {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginM
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
label: I18n.tr("settings.system-monitor.external-monitor.label")
|
||||
description: I18n.tr("settings.system-monitor.external-monitor.description")
|
||||
placeholderText: I18n.tr("settings.system-monitor.external-monitor.placeholder")
|
||||
text: Settings.data.systemMonitor.externalMonitor
|
||||
defaultValue: Settings.getDefaultValue("systemMonitor.externalMonitor")
|
||||
onTextChanged: Settings.data.systemMonitor.externalMonitor = text
|
||||
description: I18n.tr("settings.system-monitor.polling-section.description")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,6 @@ ColumnLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.system-monitor.thresholds-section.label")
|
||||
description: I18n.tr("settings.system-monitor.thresholds-section.description")
|
||||
}
|
||||
|
||||
// CPU Usage
|
||||
NText {
|
||||
Layout.fillWidth: true
|
||||
@@ -37,6 +31,7 @@ ColumnLayout {
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: I18n.tr("settings.system-monitor.threshold.warning")
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
@@ -65,6 +60,7 @@ ColumnLayout {
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: I18n.tr("settings.system-monitor.threshold.critical")
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
@@ -101,6 +97,7 @@ ColumnLayout {
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: I18n.tr("settings.system-monitor.threshold.warning")
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
@@ -129,6 +126,7 @@ ColumnLayout {
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: I18n.tr("settings.system-monitor.threshold.critical")
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
@@ -167,6 +165,7 @@ ColumnLayout {
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: I18n.tr("settings.system-monitor.threshold.warning")
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
@@ -195,6 +194,7 @@ ColumnLayout {
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: I18n.tr("settings.system-monitor.threshold.critical")
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
@@ -231,6 +231,7 @@ ColumnLayout {
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: I18n.tr("settings.system-monitor.threshold.warning")
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
@@ -259,6 +260,7 @@ ColumnLayout {
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: I18n.tr("settings.system-monitor.threshold.critical")
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
@@ -295,6 +297,7 @@ ColumnLayout {
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: I18n.tr("settings.system-monitor.threshold.warning")
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
@@ -323,6 +326,7 @@ ColumnLayout {
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: I18n.tr("settings.system-monitor.threshold.critical")
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
@@ -337,4 +341,8 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
}
|
||||
NLabel {
|
||||
Layout.fillWidth: true
|
||||
description: I18n.tr("settings.system-monitor.thresholds-section.description")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ ColumnLayout {
|
||||
description: I18n.tr("settings.wallpaper.settings.enable-management.description")
|
||||
checked: Settings.data.wallpaper.enabled
|
||||
onToggled: checked => Settings.data.wallpaper.enabled = checked
|
||||
Layout.bottomMargin: Style.marginL
|
||||
defaultValue: Settings.getDefaultValue("wallpaper.enabled")
|
||||
}
|
||||
|
||||
@@ -32,7 +31,6 @@ ColumnLayout {
|
||||
description: I18n.tr("settings.wallpaper.settings.enable-overview.description")
|
||||
checked: Settings.data.wallpaper.overviewEnabled
|
||||
onToggled: checked => Settings.data.wallpaper.overviewEnabled = checked
|
||||
Layout.bottomMargin: Style.marginL
|
||||
defaultValue: Settings.getDefaultValue("wallpaper.overviewEnabled")
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ ColumnLayout {
|
||||
checked: subTabBar.currentIndex === 0
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("settings.wallpaper.tabs.look-feel")
|
||||
text: I18n.tr("settings.wallpaper.tabs.look")
|
||||
tabIndex: 1
|
||||
checked: subTabBar.currentIndex === 1
|
||||
visible: Settings.data.wallpaper.enabled
|
||||
|
||||
@@ -8,8 +8,8 @@ ColumnLayout {
|
||||
property string label: ""
|
||||
property string description: ""
|
||||
property bool expanded: false
|
||||
property bool defaultExpanded: false
|
||||
property real contentSpacing: Style.marginM
|
||||
property bool _userInteracted: false
|
||||
|
||||
signal toggled(bool expanded)
|
||||
|
||||
@@ -31,6 +31,7 @@ ColumnLayout {
|
||||
|
||||
// Smooth color transitions
|
||||
Behavior on color {
|
||||
enabled: root._userInteracted
|
||||
ColorAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.OutCubic
|
||||
@@ -38,6 +39,7 @@ ColumnLayout {
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
enabled: root._userInteracted
|
||||
ColorAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.OutCubic
|
||||
@@ -51,6 +53,7 @@ ColumnLayout {
|
||||
hoverEnabled: true
|
||||
|
||||
onClicked: {
|
||||
root._userInteracted = true;
|
||||
root.expanded = !root.expanded;
|
||||
root.toggled(root.expanded);
|
||||
}
|
||||
@@ -86,6 +89,7 @@ ColumnLayout {
|
||||
|
||||
rotation: root.expanded ? 90 : 0
|
||||
Behavior on rotation {
|
||||
enabled: root._userInteracted
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.OutCubic
|
||||
@@ -93,6 +97,7 @@ ColumnLayout {
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
enabled: root._userInteracted
|
||||
ColorAnimation {
|
||||
duration: Style.animationNormal
|
||||
}
|
||||
@@ -113,6 +118,7 @@ ColumnLayout {
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
Behavior on color {
|
||||
enabled: root._userInteracted
|
||||
ColorAnimation {
|
||||
duration: Style.animationNormal
|
||||
}
|
||||
@@ -130,6 +136,7 @@ ColumnLayout {
|
||||
opacity: 0.87
|
||||
|
||||
Behavior on color {
|
||||
enabled: root._userInteracted
|
||||
ColorAnimation {
|
||||
duration: Style.animationNormal
|
||||
}
|
||||
@@ -152,10 +159,11 @@ ColumnLayout {
|
||||
border.width: Style.borderS
|
||||
|
||||
// Dynamic height based on content
|
||||
Layout.preferredHeight: visible ? contentLayout.implicitHeight + (Style.marginL * 2) : 0
|
||||
Layout.preferredHeight: expanded ? contentLayout.implicitHeight + (Style.marginL * 2) : 0
|
||||
|
||||
// Smooth height animation
|
||||
Behavior on Layout.preferredHeight {
|
||||
enabled: root._userInteracted
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.OutCubic
|
||||
@@ -173,15 +181,11 @@ ColumnLayout {
|
||||
// Fade in animation for content
|
||||
opacity: root.expanded ? 1.0 : 0.0
|
||||
Behavior on opacity {
|
||||
enabled: root._userInteracted
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize expanded state
|
||||
Component.onCompleted: {
|
||||
root.expanded = root.defaultExpanded;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ ColumnLayout {
|
||||
property bool showIndicator: false
|
||||
property string indicatorTooltip: ""
|
||||
|
||||
opacity: enabled ? 1.0 : 0.6
|
||||
|
||||
spacing: Style.marginXXS
|
||||
Layout.fillWidth: true
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ RowLayout {
|
||||
|
||||
property string label: ""
|
||||
property string description: ""
|
||||
property bool enabled: true
|
||||
property bool checked: false
|
||||
property bool hovering: false
|
||||
property int baseSize: Math.round(Style.baseWidgetSize * 0.8 * Style.uiScaleRatio)
|
||||
|
||||
Reference in New Issue
Block a user