mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-26 04:57:47 -04:00
firststart/greeter: add a step for plugins
This commit is contained in:
1 parent
7372b79c2d
commit
4624ceb8f0
5 files changed
+663
-361
No files matched your search
@@ -1,5 +1,4 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
@@ -10,8 +9,8 @@ DankFloatingWindow {
|
||||
readonly property var log: Log.scoped("GreeterModal")
|
||||
|
||||
property int currentPage: 0
|
||||
readonly property int totalPages: 3
|
||||
readonly property var pageComponents: [welcomePage, doctorPage, completePage]
|
||||
readonly property int totalPages: 4
|
||||
readonly property var pageComponents: [welcomePage, doctorPage, pluginsPage, completePage]
|
||||
|
||||
property var cheatsheetData: ({})
|
||||
property bool cheatsheetLoaded: false
|
||||
@@ -278,7 +277,7 @@ DankFloatingWindow {
|
||||
text: I18n.tr("Skip", "greeter skip button")
|
||||
backgroundColor: "transparent"
|
||||
textColor: Theme.surfaceVariantText
|
||||
onClicked: root.skip()
|
||||
onClicked: root.currentPage === 1 ? root.nextPage() : root.skip()
|
||||
}
|
||||
|
||||
DankButton {
|
||||
@@ -327,6 +326,11 @@ DankFloatingWindow {
|
||||
GreeterDoctorPage {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: pluginsPage
|
||||
GreeterPluginsPage {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: completePage
|
||||
GreeterCompletePage {}
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Modules.Settings
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var greeterRoot: parent ? parent.greeterRoot : null
|
||||
|
||||
readonly property var curatedPluginIds: ["quickCapture", "dankKDEConnect", "wallpaperCarousel", "calculator", "emojiLauncher", "dockerManager", "amdGpuMonitor", "bongoCat"]
|
||||
|
||||
readonly property real headerIconContainerSize: Math.round(Theme.iconSize * 2)
|
||||
|
||||
property var curatedPlugins: []
|
||||
property bool isLoading: false
|
||||
property string loadError: ""
|
||||
|
||||
function refresh() {
|
||||
isLoading = true;
|
||||
loadError = "";
|
||||
DMSService.listPlugins(response => {
|
||||
isLoading = false;
|
||||
if (response.error) {
|
||||
loadError = response.error;
|
||||
return;
|
||||
}
|
||||
curatedPlugins = curate(response.result || []);
|
||||
});
|
||||
if (DMSService.apiVersion >= 8)
|
||||
DMSService.listInstalled();
|
||||
}
|
||||
|
||||
function curate(registry) {
|
||||
const byId = {};
|
||||
for (let i = 0; i < registry.length; i++)
|
||||
byId[registry[i].id] = registry[i];
|
||||
const result = [];
|
||||
for (let i = 0; i < curatedPluginIds.length; i++) {
|
||||
const plugin = byId[curatedPluginIds[i]];
|
||||
if (plugin)
|
||||
result.push(plugin);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function markInstalled(installedList) {
|
||||
const installedKeys = {};
|
||||
for (let i = 0; i < installedList.length; i++) {
|
||||
const plugin = installedList[i];
|
||||
if (plugin.id)
|
||||
installedKeys[plugin.id] = true;
|
||||
if (plugin.name)
|
||||
installedKeys[plugin.name] = true;
|
||||
}
|
||||
curatedPlugins = curatedPlugins.map(p => Object.assign({}, p, {
|
||||
"installed": installedKeys[p.id] || installedKeys[p.name] || false
|
||||
}));
|
||||
}
|
||||
|
||||
Component.onCompleted: refresh()
|
||||
|
||||
Connections {
|
||||
target: DMSService
|
||||
|
||||
function onInstalledPluginsReceived(plugins) {
|
||||
root.markInstalled(plugins);
|
||||
}
|
||||
|
||||
function onIsConnectedChanged() {
|
||||
if (DMSService.isConnected && root.curatedPlugins.length === 0 && !root.isLoading)
|
||||
root.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
DankFlickable {
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
contentHeight: mainColumn.height + Theme.spacingL * 2
|
||||
contentWidth: width
|
||||
|
||||
Column {
|
||||
id: mainColumn
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: Math.min(640, parent.width - Theme.spacingXL * 2)
|
||||
topPadding: Theme.spacingL
|
||||
spacing: Theme.spacingL
|
||||
|
||||
Row {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Rectangle {
|
||||
width: root.headerIconContainerSize
|
||||
height: root.headerIconContainerSize
|
||||
radius: Math.round(root.headerIconContainerSize * 0.29)
|
||||
color: Theme.primaryContainer
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "extension"
|
||||
size: Theme.iconSize + 4
|
||||
color: Theme.primary
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingXXS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Popular Plugins", "greeter plugins page title")
|
||||
font.pixelSize: Theme.fontSizeXLarge
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Community favorites to get you started", "greeter plugins page subtitle")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: I18n.tr("You can extend DMS with more applets, launcher features, and widgets through the plugin system. Hundreds are available on the official DMS registry, built by the community and the DMS team. Install some now, or later through the DMS CLI or the Settings window.", "greeter plugins page intro")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: Math.round(Theme.fontSizeMedium * 8)
|
||||
visible: root.isLoading
|
||||
|
||||
DankSpinner {
|
||||
anchors.centerIn: parent
|
||||
running: root.isLoading
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
visible: !root.isLoading && root.loadError !== ""
|
||||
|
||||
DankIcon {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
name: "cloud_off"
|
||||
size: Theme.iconSize + 16
|
||||
color: Theme.outline
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: I18n.tr("Couldn't load plugins", "plugin registry fetch error")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: root.loadError
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.outline
|
||||
}
|
||||
|
||||
DankButton {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: I18n.tr("Retry", "retry failed action button")
|
||||
iconName: "refresh"
|
||||
backgroundColor: Theme.surfaceContainerHighest
|
||||
textColor: Theme.surfaceText
|
||||
onClicked: root.refresh()
|
||||
}
|
||||
}
|
||||
|
||||
Grid {
|
||||
width: parent.width
|
||||
columns: 2
|
||||
rowSpacing: Theme.spacingS
|
||||
columnSpacing: Theme.spacingS
|
||||
visible: !root.isLoading && root.curatedPlugins.length > 0
|
||||
|
||||
Repeater {
|
||||
model: root.curatedPlugins
|
||||
|
||||
PluginCard {
|
||||
required property var modelData
|
||||
|
||||
width: (parent.width - Theme.spacingS) / 2
|
||||
previewHeight: Math.round(width * 0.45)
|
||||
plugin: modelData
|
||||
installed: modelData.installed || false
|
||||
onClicked: {
|
||||
if (modelData.repo)
|
||||
Qt.openUrlExternally(modelData.repo);
|
||||
}
|
||||
onInstallRequested: PluginService.installFromRegistry(modelData.name, modelData.type === "desktop")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GreeterSettingsCard {
|
||||
width: parent.width
|
||||
iconName: "explore"
|
||||
title: I18n.tr("Browse Plugins", "plugin browser window title")
|
||||
description: I18n.tr("Browse or search plugins")
|
||||
visible: !root.isLoading && root.loadError === ""
|
||||
onClicked: PopoutService.openSettingsWithTab("plugins")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,8 +24,8 @@ DankFloatingWindow {
|
||||
property var categoryFilterOptions: []
|
||||
property var availableLetters: []
|
||||
property string detailPluginId: ""
|
||||
property string loadError: ""
|
||||
|
||||
readonly property string previewApiBase: "https://api.danklinux.com/previews/"
|
||||
readonly property var detailPlugin: resolveDetailPlugin(detailPluginId, allPlugins)
|
||||
readonly property bool activeCategorySort: normalizedSortMode(SessionData.pluginBrowserSortMode) === "category"
|
||||
readonly property bool showCategoryFilters: activeCategorySort && categoryFilterOptions.length > 1
|
||||
@@ -98,99 +98,6 @@ DankFloatingWindow {
|
||||
return (plugin.status || []).indexOf("reviewed") !== -1;
|
||||
}
|
||||
|
||||
function statusTone(status) {
|
||||
switch (status) {
|
||||
case "broken":
|
||||
return "error";
|
||||
case "unmaintained":
|
||||
return "warning";
|
||||
case "reviewed":
|
||||
return "info";
|
||||
default:
|
||||
return "outline";
|
||||
}
|
||||
}
|
||||
|
||||
function badgeTone(tone) {
|
||||
switch (tone) {
|
||||
case "secondary":
|
||||
return Theme.secondary;
|
||||
case "warning":
|
||||
return Theme.warning;
|
||||
case "error":
|
||||
return Theme.error;
|
||||
case "info":
|
||||
return Theme.info;
|
||||
case "outline":
|
||||
return Theme.outline;
|
||||
default:
|
||||
return Theme.primary;
|
||||
}
|
||||
}
|
||||
|
||||
function statusLabel(status) {
|
||||
switch (status) {
|
||||
case "broken":
|
||||
return I18n.tr("broken", "plugin status");
|
||||
case "unmaintained":
|
||||
return I18n.tr("unmaintained", "plugin status");
|
||||
case "deprecated":
|
||||
return I18n.tr("deprecated", "plugin status");
|
||||
case "reviewed":
|
||||
return I18n.tr("reviewed", "plugin status");
|
||||
default:
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
function badgeModel(plugin) {
|
||||
if (!plugin || (!plugin.id && !plugin.name))
|
||||
return [];
|
||||
var badges = [];
|
||||
if (plugin.featured)
|
||||
badges.push({
|
||||
label: I18n.tr("featured"),
|
||||
icon: "star",
|
||||
tone: "secondary"
|
||||
});
|
||||
if (plugin.firstParty)
|
||||
badges.push({
|
||||
label: I18n.tr("official"),
|
||||
icon: "verified",
|
||||
tone: "primary"
|
||||
});
|
||||
else
|
||||
badges.push({
|
||||
label: I18n.tr("3rd party"),
|
||||
icon: "",
|
||||
tone: "warning"
|
||||
});
|
||||
var status = plugin.status || [];
|
||||
for (var i = 0; i < status.length; i++)
|
||||
badges.push({
|
||||
label: statusLabel(status[i]),
|
||||
icon: "",
|
||||
tone: statusTone(status[i])
|
||||
});
|
||||
return badges;
|
||||
}
|
||||
|
||||
function previewUrl(plugin) {
|
||||
if (!plugin)
|
||||
return "";
|
||||
if (plugin.previewUrl)
|
||||
return plugin.previewUrl;
|
||||
if (plugin.id)
|
||||
return previewApiBase + plugin.id;
|
||||
return plugin.screenshot || "";
|
||||
}
|
||||
|
||||
function heroUrl(plugin) {
|
||||
if (!plugin)
|
||||
return "";
|
||||
return plugin.screenshot || previewUrl(plugin);
|
||||
}
|
||||
|
||||
function relatedPlugins(plugin) {
|
||||
if (!plugin || !plugin.similar || plugin.similar.length === 0)
|
||||
return [];
|
||||
@@ -528,32 +435,27 @@ DankFloatingWindow {
|
||||
}
|
||||
|
||||
function installPlugin(pluginName, enableAfterInstall) {
|
||||
ToastService.showInfo(I18n.tr("Installing: %1", "installation progress").arg(pluginName));
|
||||
DMSService.install(pluginName, response => {
|
||||
if (response.error) {
|
||||
ToastService.showError(I18n.tr("Install failed: %1", "installation error").arg(response.error));
|
||||
PluginService.installFromRegistry(pluginName, enableAfterInstall, success => {
|
||||
if (!success)
|
||||
return;
|
||||
}
|
||||
ToastService.showInfo(I18n.tr("Installed: %1", "installation success").arg(pluginName));
|
||||
PluginService.scanPlugins();
|
||||
refreshPlugins();
|
||||
if (enableAfterInstall) {
|
||||
Qt.callLater(() => {
|
||||
PluginService.enablePlugin(pluginName);
|
||||
const plugin = PluginService.availablePlugins[pluginName];
|
||||
if (plugin?.type === "desktop") {
|
||||
const defaultConfig = DesktopWidgetRegistry.getDefaultConfig(pluginName);
|
||||
SettingsData.createDesktopWidgetInstance(pluginName, plugin.name || pluginName, defaultConfig);
|
||||
}
|
||||
hide();
|
||||
});
|
||||
}
|
||||
if (enableAfterInstall)
|
||||
hide();
|
||||
});
|
||||
}
|
||||
|
||||
function refreshPlugins() {
|
||||
isLoading = true;
|
||||
DMSService.listPlugins();
|
||||
loadError = "";
|
||||
DMSService.listPlugins(response => {
|
||||
isLoading = false;
|
||||
if (response.error) {
|
||||
loadError = response.error;
|
||||
return;
|
||||
}
|
||||
allPlugins = response.result || [];
|
||||
updateFilteredPlugins();
|
||||
});
|
||||
if (DMSService.apiVersion >= 8)
|
||||
DMSService.listInstalled();
|
||||
}
|
||||
@@ -627,18 +529,13 @@ DankFloatingWindow {
|
||||
selectedIndex = -1;
|
||||
keyboardNavigationActive = false;
|
||||
isLoading = false;
|
||||
loadError = "";
|
||||
detailPluginId = "";
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: DMSService
|
||||
|
||||
function onPluginsListReceived(plugins) {
|
||||
root.isLoading = false;
|
||||
root.allPlugins = plugins;
|
||||
root.updateFilteredPlugins();
|
||||
}
|
||||
|
||||
function onInstalledPluginsReceived(plugins) {
|
||||
var pluginMap = {};
|
||||
for (var i = 0; i < plugins.length; i++) {
|
||||
@@ -1037,245 +934,18 @@ DankFloatingWindow {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
property bool isInstalled: modelData.installed || false
|
||||
property bool isCompatible: PluginService.checkPluginCompatibility(modelData.requires_dms)
|
||||
property bool isSelected: root.keyboardNavigationActive && index === root.selectedIndex
|
||||
|
||||
width: pluginGrid.cellWidth
|
||||
height: pluginGrid.cellHeight
|
||||
|
||||
Rectangle {
|
||||
id: card
|
||||
PluginCard {
|
||||
anchors.fill: parent
|
||||
anchors.margins: pluginGrid.cardSpacing / 2
|
||||
radius: Theme.cornerRadius
|
||||
color: cardMouseArea.containsMouse ? Theme.withAlpha(Theme.surfaceVariant, 0.5) : Theme.withAlpha(Theme.surfaceVariant, 0.3)
|
||||
border.color: cardCell.isSelected ? Theme.primary : Theme.withAlpha(Theme.outline, 0.15)
|
||||
border.width: cardCell.isSelected ? 2 : 1
|
||||
scale: cardMouseArea.containsMouse ? 1.012 : 1
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: cardMouseArea
|
||||
z: 0
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: mouse => cardRipple.trigger(mouse.x, mouse.y)
|
||||
onClicked: root.openPluginDetail(cardCell.modelData)
|
||||
}
|
||||
|
||||
DankRipple {
|
||||
id: cardRipple
|
||||
cornerRadius: card.radius
|
||||
rippleColor: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
Item {
|
||||
id: previewArea
|
||||
z: 1
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Theme.spacingS
|
||||
height: pluginGrid.previewHeight
|
||||
|
||||
ClippingRectangle {
|
||||
anchors.fill: parent
|
||||
radius: Theme.cornerRadius - 2
|
||||
color: Theme.floatingWindowNestedSurface
|
||||
|
||||
CachingImage {
|
||||
id: cardPreview
|
||||
anchors.fill: parent
|
||||
imagePath: root.previewUrl(cardCell.modelData)
|
||||
maxCacheSize: 640
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
animate: false
|
||||
visible: status === Image.Ready
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: cardCell.modelData.icon || "extension"
|
||||
size: Theme.iconSize + 12
|
||||
color: Theme.withAlpha(Theme.outline, 0.6)
|
||||
visible: cardPreview.status !== Image.Ready
|
||||
}
|
||||
|
||||
DankSpinner {
|
||||
anchors.centerIn: parent
|
||||
running: cardPreview.status === Image.Loading
|
||||
visible: running
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.margins: Theme.spacingXS
|
||||
spacing: Theme.spacingXXS
|
||||
|
||||
Repeater {
|
||||
model: root.badgeModel(cardCell.modelData)
|
||||
|
||||
PluginBadge {
|
||||
required property var modelData
|
||||
label: modelData.label
|
||||
iconName: modelData.icon
|
||||
tone: root.badgeTone(modelData.tone)
|
||||
onImage: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PluginBadge {
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Theme.spacingXS
|
||||
iconName: "thumb_up"
|
||||
label: cardCell.modelData.upvotes || 0
|
||||
tone: Theme.primary
|
||||
onImage: true
|
||||
visible: !!cardCell.modelData.issueUrl
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
z: 1
|
||||
anchors.top: previewArea.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: Theme.spacingS
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
spacing: Theme.spacingXXS
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
id: cardIcon
|
||||
name: cardCell.modelData.icon || "extension"
|
||||
size: Theme.iconSize - 4
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width - cardIcon.width - installAction.width - Theme.spacingS * 2
|
||||
text: cardCell.modelData.name || ""
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: installAction
|
||||
|
||||
property string buttonState: {
|
||||
if (cardCell.isInstalled)
|
||||
return "installed";
|
||||
if (!cardCell.isCompatible)
|
||||
return "incompatible";
|
||||
return "available";
|
||||
}
|
||||
|
||||
width: 28
|
||||
height: 28
|
||||
radius: 14
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: {
|
||||
switch (buttonState) {
|
||||
case "installed":
|
||||
return Theme.surfaceVariant;
|
||||
case "incompatible":
|
||||
return Theme.withAlpha(Theme.warning, 0.15);
|
||||
default:
|
||||
return Theme.primary;
|
||||
}
|
||||
}
|
||||
opacity: buttonState === "available" && installMouseArea.containsMouse ? 0.85 : 1
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
size: 15
|
||||
name: {
|
||||
switch (installAction.buttonState) {
|
||||
case "installed":
|
||||
return "check";
|
||||
case "incompatible":
|
||||
return "warning";
|
||||
default:
|
||||
return "download";
|
||||
}
|
||||
}
|
||||
color: {
|
||||
switch (installAction.buttonState) {
|
||||
case "installed":
|
||||
return Theme.surfaceText;
|
||||
case "incompatible":
|
||||
return Theme.warning;
|
||||
default:
|
||||
return Theme.surface;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: installMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: installAction.buttonState === "available" ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
enabled: installAction.buttonState === "available"
|
||||
onClicked: root.installPlugin(cardCell.modelData.name, cardCell.modelData.type === "desktop")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: I18n.tr("by %1", "author attribution").arg(cardCell.modelData.author || I18n.tr("Unknown", "unknown author"))
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.outline
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: cardCell.modelData.description || ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 2
|
||||
}
|
||||
}
|
||||
plugin: cardCell.modelData
|
||||
previewHeight: pluginGrid.previewHeight
|
||||
installed: cardCell.modelData.installed || false
|
||||
selected: root.keyboardNavigationActive && cardCell.index === root.selectedIndex
|
||||
onClicked: root.openPluginDetail(cardCell.modelData)
|
||||
onInstallRequested: root.installPlugin(cardCell.modelData.name, cardCell.modelData.type === "desktop")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1319,7 +989,7 @@ DankFloatingWindow {
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingS
|
||||
visible: !root.isLoading && root.filteredPlugins.length === 0
|
||||
visible: !root.isLoading && root.loadError === "" && root.filteredPlugins.length === 0
|
||||
|
||||
DankIcon {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
@@ -1335,6 +1005,42 @@ DankFloatingWindow {
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingS
|
||||
visible: !root.isLoading && root.loadError !== ""
|
||||
|
||||
DankIcon {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
name: "cloud_off"
|
||||
size: Theme.iconSize + 16
|
||||
color: Theme.outline
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: I18n.tr("Couldn't load plugins", "plugin registry fetch error")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: root.loadError
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.outline
|
||||
}
|
||||
|
||||
DankButton {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: I18n.tr("Retry", "retry failed action button")
|
||||
iconName: "refresh"
|
||||
backgroundColor: Theme.surfaceContainerHighest
|
||||
textColor: Theme.surfaceText
|
||||
onClicked: root.refreshPlugins()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -1562,7 +1268,7 @@ DankFloatingWindow {
|
||||
CachingImage {
|
||||
id: heroImage
|
||||
anchors.fill: parent
|
||||
imagePath: detailPane.heroFallback ? root.previewUrl(detailPane.plugin) : root.heroUrl(detailPane.plugin)
|
||||
imagePath: detailPane.heroFallback ? PluginService.previewUrl(detailPane.plugin) : PluginService.heroUrl(detailPane.plugin)
|
||||
maxCacheSize: 1600
|
||||
fillMode: Image.PreserveAspectFit
|
||||
visible: status === Image.Ready
|
||||
@@ -1606,13 +1312,13 @@ DankFloatingWindow {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Repeater {
|
||||
model: root.badgeModel(detailPane.plugin)
|
||||
model: PluginService.badgeModel(detailPane.plugin)
|
||||
|
||||
PluginBadge {
|
||||
required property var modelData
|
||||
label: modelData.label
|
||||
iconName: modelData.icon
|
||||
tone: root.badgeTone(modelData.tone)
|
||||
tone: PluginService.badgeTone(modelData.tone)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
import QtQuick
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property var plugin: ({})
|
||||
property bool installed: false
|
||||
property bool selected: false
|
||||
property real previewHeight: Math.round((width - Theme.spacingS * 2) * 0.52)
|
||||
readonly property int infoHeight: 100
|
||||
readonly property bool compatible: PluginService.checkPluginCompatibility(plugin.requires_dms)
|
||||
|
||||
signal clicked
|
||||
signal installRequested
|
||||
|
||||
implicitHeight: previewHeight + infoHeight + Theme.spacingS * 2 + Theme.spacingM
|
||||
radius: Theme.cornerRadius
|
||||
color: cardMouseArea.containsMouse ? Theme.withAlpha(Theme.surfaceVariant, 0.5) : Theme.withAlpha(Theme.surfaceVariant, 0.3)
|
||||
border.color: selected ? Theme.primary : Theme.withAlpha(Theme.outline, 0.15)
|
||||
border.width: selected ? 2 : 1
|
||||
scale: cardMouseArea.containsMouse ? 1.012 : 1
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: cardMouseArea
|
||||
z: 0
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: mouse => cardRipple.trigger(mouse.x, mouse.y)
|
||||
onClicked: root.clicked()
|
||||
}
|
||||
|
||||
DankRipple {
|
||||
id: cardRipple
|
||||
cornerRadius: root.radius
|
||||
rippleColor: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
Item {
|
||||
id: previewArea
|
||||
z: 1
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Theme.spacingS
|
||||
height: root.previewHeight
|
||||
|
||||
ClippingRectangle {
|
||||
anchors.fill: parent
|
||||
radius: Theme.cornerRadius - 2
|
||||
color: Theme.floatingWindowNestedSurface
|
||||
|
||||
CachingImage {
|
||||
id: cardPreview
|
||||
anchors.fill: parent
|
||||
imagePath: PluginService.previewUrl(root.plugin)
|
||||
maxCacheSize: 640
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
animate: false
|
||||
visible: status === Image.Ready
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: root.plugin.icon || "extension"
|
||||
size: Theme.iconSize + 12
|
||||
color: Theme.withAlpha(Theme.outline, 0.6)
|
||||
visible: cardPreview.status !== Image.Ready
|
||||
}
|
||||
|
||||
DankSpinner {
|
||||
anchors.centerIn: parent
|
||||
running: cardPreview.status === Image.Loading
|
||||
visible: running
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.margins: Theme.spacingXS
|
||||
spacing: Theme.spacingXXS
|
||||
|
||||
Repeater {
|
||||
model: PluginService.badgeModel(root.plugin)
|
||||
|
||||
PluginBadge {
|
||||
required property var modelData
|
||||
label: modelData.label
|
||||
iconName: modelData.icon
|
||||
tone: PluginService.badgeTone(modelData.tone)
|
||||
onImage: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PluginBadge {
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Theme.spacingXS
|
||||
iconName: "thumb_up"
|
||||
label: root.plugin.upvotes || 0
|
||||
tone: Theme.primary
|
||||
onImage: true
|
||||
visible: !!root.plugin.issueUrl
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
z: 1
|
||||
anchors.top: previewArea.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: Theme.spacingS
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
spacing: Theme.spacingXXS
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
id: cardIcon
|
||||
name: root.plugin.icon || "extension"
|
||||
size: Theme.iconSize - 4
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width - cardIcon.width - installAction.width - Theme.spacingS * 2
|
||||
text: root.plugin.name || ""
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: installAction
|
||||
|
||||
property string buttonState: {
|
||||
if (root.installed)
|
||||
return "installed";
|
||||
if (!root.compatible)
|
||||
return "incompatible";
|
||||
return "available";
|
||||
}
|
||||
|
||||
width: 28
|
||||
height: 28
|
||||
radius: 14
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: {
|
||||
switch (buttonState) {
|
||||
case "installed":
|
||||
return Theme.surfaceVariant;
|
||||
case "incompatible":
|
||||
return Theme.withAlpha(Theme.warning, 0.15);
|
||||
default:
|
||||
return Theme.primary;
|
||||
}
|
||||
}
|
||||
opacity: buttonState === "available" && installMouseArea.containsMouse ? 0.85 : 1
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
size: 15
|
||||
name: {
|
||||
switch (installAction.buttonState) {
|
||||
case "installed":
|
||||
return "check";
|
||||
case "incompatible":
|
||||
return "warning";
|
||||
default:
|
||||
return "download";
|
||||
}
|
||||
}
|
||||
color: {
|
||||
switch (installAction.buttonState) {
|
||||
case "installed":
|
||||
return Theme.surfaceText;
|
||||
case "incompatible":
|
||||
return Theme.warning;
|
||||
default:
|
||||
return Theme.surface;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: installMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: installAction.buttonState === "available" ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
enabled: installAction.buttonState === "available"
|
||||
onClicked: root.installRequested()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: I18n.tr("by %1", "author attribution").arg(root.plugin.author || I18n.tr("Unknown", "unknown author"))
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.outline
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: root.plugin.description || ""
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 2
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ Singleton {
|
||||
|
||||
property bool pluginDirectoryExists: false
|
||||
property string systemPluginDirectory: "/etc/xdg/quickshell/dms-plugins"
|
||||
readonly property string previewApiBase: "https://api.danklinux.com/previews/"
|
||||
|
||||
property var knownManifests: ({})
|
||||
property var pathToPluginId: ({})
|
||||
@@ -1183,6 +1184,128 @@ Singleton {
|
||||
globalVarChanged(pluginId, varName);
|
||||
}
|
||||
|
||||
function previewUrl(plugin) {
|
||||
if (!plugin)
|
||||
return "";
|
||||
if (plugin.previewUrl)
|
||||
return plugin.previewUrl;
|
||||
if (plugin.id)
|
||||
return previewApiBase + plugin.id;
|
||||
return plugin.screenshot || "";
|
||||
}
|
||||
|
||||
function heroUrl(plugin) {
|
||||
if (!plugin)
|
||||
return "";
|
||||
return plugin.screenshot || previewUrl(plugin);
|
||||
}
|
||||
|
||||
function statusTone(status) {
|
||||
switch (status) {
|
||||
case "broken":
|
||||
return "error";
|
||||
case "unmaintained":
|
||||
return "warning";
|
||||
case "reviewed":
|
||||
return "info";
|
||||
default:
|
||||
return "outline";
|
||||
}
|
||||
}
|
||||
|
||||
function statusLabel(status) {
|
||||
switch (status) {
|
||||
case "broken":
|
||||
return I18n.tr("broken", "plugin status");
|
||||
case "unmaintained":
|
||||
return I18n.tr("unmaintained", "plugin status");
|
||||
case "deprecated":
|
||||
return I18n.tr("deprecated", "plugin status");
|
||||
case "reviewed":
|
||||
return I18n.tr("reviewed", "plugin status");
|
||||
default:
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
function badgeTone(tone) {
|
||||
switch (tone) {
|
||||
case "secondary":
|
||||
return Theme.secondary;
|
||||
case "warning":
|
||||
return Theme.warning;
|
||||
case "error":
|
||||
return Theme.error;
|
||||
case "info":
|
||||
return Theme.info;
|
||||
case "outline":
|
||||
return Theme.outline;
|
||||
default:
|
||||
return Theme.primary;
|
||||
}
|
||||
}
|
||||
|
||||
function badgeModel(plugin) {
|
||||
if (!plugin || (!plugin.id && !plugin.name))
|
||||
return [];
|
||||
var badges = [];
|
||||
if (plugin.featured)
|
||||
badges.push({
|
||||
label: I18n.tr("featured"),
|
||||
icon: "star",
|
||||
tone: "secondary"
|
||||
});
|
||||
if (plugin.firstParty)
|
||||
badges.push({
|
||||
label: I18n.tr("official"),
|
||||
icon: "verified",
|
||||
tone: "primary"
|
||||
});
|
||||
else
|
||||
badges.push({
|
||||
label: I18n.tr("3rd party"),
|
||||
icon: "",
|
||||
tone: "warning"
|
||||
});
|
||||
var status = plugin.status || [];
|
||||
for (var i = 0; i < status.length; i++)
|
||||
badges.push({
|
||||
label: statusLabel(status[i]),
|
||||
icon: "",
|
||||
tone: statusTone(status[i])
|
||||
});
|
||||
return badges;
|
||||
}
|
||||
|
||||
function installFromRegistry(pluginName, enableAfterInstall, onDone) {
|
||||
ToastService.showInfo(I18n.tr("Installing: %1", "installation progress").arg(pluginName));
|
||||
DMSService.install(pluginName, response => {
|
||||
if (response.error) {
|
||||
ToastService.showError(I18n.tr("Install failed: %1", "installation error").arg(response.error));
|
||||
if (onDone)
|
||||
onDone(false);
|
||||
return;
|
||||
}
|
||||
ToastService.showInfo(I18n.tr("Installed: %1", "installation success").arg(pluginName));
|
||||
scanPlugins();
|
||||
if (!enableAfterInstall) {
|
||||
if (onDone)
|
||||
onDone(true);
|
||||
return;
|
||||
}
|
||||
Qt.callLater(() => {
|
||||
enablePlugin(pluginName);
|
||||
const plugin = availablePlugins[pluginName];
|
||||
if (plugin?.type === "desktop") {
|
||||
const defaultConfig = DesktopWidgetRegistry.getDefaultConfig(pluginName);
|
||||
SettingsData.createDesktopWidgetInstance(pluginName, plugin.name || pluginName, defaultConfig);
|
||||
}
|
||||
if (onDone)
|
||||
onDone(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function checkPluginCompatibility(requiresDms) {
|
||||
if (!requiresDms)
|
||||
return true;
|
||||
|
||||
Reference in new issue
Block a user