mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-29 06:04:25 +08:00
setting_re2
设置架构革新中
This commit is contained in:
@@ -21,7 +21,7 @@ public sealed class LoadedPlugin : IDisposable, IAsyncDisposable
|
||||
IPlugin plugin,
|
||||
IPluginRuntimeContext runtimeContext,
|
||||
IServiceProvider services,
|
||||
IReadOnlyList<PluginSettingsPageRegistration> settingsPages,
|
||||
IReadOnlyList<PluginSettingsSectionRegistration> settingsSections,
|
||||
IReadOnlyList<PluginDesktopComponentRegistration> desktopComponents,
|
||||
IReadOnlyList<PluginServiceExportDescriptor> exportedServices,
|
||||
IReadOnlyList<IHostedService> hostedServices,
|
||||
@@ -34,7 +34,7 @@ public sealed class LoadedPlugin : IDisposable, IAsyncDisposable
|
||||
Plugin = plugin;
|
||||
RuntimeContext = runtimeContext;
|
||||
Services = services;
|
||||
SettingsPages = settingsPages;
|
||||
SettingsSections = settingsSections;
|
||||
DesktopComponents = desktopComponents;
|
||||
ExportedServices = exportedServices;
|
||||
HostedServices = hostedServices;
|
||||
@@ -57,7 +57,7 @@ public sealed class LoadedPlugin : IDisposable, IAsyncDisposable
|
||||
|
||||
public IServiceProvider Services { get; }
|
||||
|
||||
public IReadOnlyList<PluginSettingsPageRegistration> SettingsPages { get; }
|
||||
public IReadOnlyList<PluginSettingsSectionRegistration> SettingsSections { get; }
|
||||
|
||||
public IReadOnlyList<PluginDesktopComponentRegistration> DesktopComponents { get; }
|
||||
|
||||
|
||||
@@ -1,166 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using FluentIcons.Avalonia.Fluent;
|
||||
using FluentIcons.Common;
|
||||
using LanMountainDesktop.Models;
|
||||
using LanMountainDesktop.Services;
|
||||
|
||||
namespace LanMountainDesktop.Views;
|
||||
|
||||
public partial class MainWindow
|
||||
{
|
||||
private readonly Dictionary<string, Control> _pluginSettingsPageHosts = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private void InitializePluginSettingsNavigation()
|
||||
{
|
||||
if (_pluginSettingsPageHosts.Count > 0 || SettingsNavView?.MenuItems is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var runtime = (Application.Current as App)?.PluginRuntimeService;
|
||||
var contributions = runtime?.SettingsPages
|
||||
.OrderBy(contribution => contribution.Registration.SortOrder)
|
||||
.ThenBy(contribution => contribution.Plugin.Manifest.Name, StringComparer.OrdinalIgnoreCase)
|
||||
.ThenBy(contribution => contribution.Registration.Title, StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
|
||||
if (contributions is not { Length: > 0 })
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var pageCountsByPluginId = contributions
|
||||
.GroupBy(contribution => contribution.Plugin.Manifest.Id, StringComparer.OrdinalIgnoreCase)
|
||||
.ToDictionary(group => group.Key, group => group.Count(), StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var insertIndex = SettingsNavView.MenuItems.IndexOf(SettingsNavPluginMarketItem) + 1;
|
||||
foreach (var contribution in contributions)
|
||||
{
|
||||
var tag = BuildPluginSettingsTag(contribution);
|
||||
var navigationTitle = BuildPluginSettingsNavigationTitle(contribution, pageCountsByPluginId);
|
||||
var navItem = new NavigationViewItem
|
||||
{
|
||||
Content = navigationTitle,
|
||||
Tag = tag,
|
||||
IconSource = new FluentIcons.Avalonia.Fluent.SymbolIconSource
|
||||
{
|
||||
Symbol = FluentIcons.Common.Symbol.PuzzlePiece,
|
||||
IconVariant = FluentIcons.Common.IconVariant.Regular
|
||||
}
|
||||
};
|
||||
|
||||
ToolTip.SetTip(navItem, $"{contribution.Plugin.Manifest.Name} - {contribution.Registration.Title}");
|
||||
|
||||
SettingsNavView.MenuItems.Insert(insertIndex++, navItem);
|
||||
|
||||
var pageHost = CreatePluginSettingsPageHost(contribution);
|
||||
pageHost.IsVisible = false;
|
||||
SettingsContentPagesHost.Children.Add(pageHost);
|
||||
_pluginSettingsPageHosts[tag] = pageHost;
|
||||
}
|
||||
}
|
||||
|
||||
private static string BuildPluginSettingsTag(PluginSettingsPageContribution contribution)
|
||||
{
|
||||
return $"PluginPage:{contribution.Plugin.Manifest.Id}:{contribution.Registration.Id}";
|
||||
}
|
||||
|
||||
private static string BuildPluginSettingsNavigationTitle(
|
||||
PluginSettingsPageContribution contribution,
|
||||
IReadOnlyDictionary<string, int> pageCountsByPluginId)
|
||||
{
|
||||
return pageCountsByPluginId.TryGetValue(contribution.Plugin.Manifest.Id, out var pageCount) && pageCount > 1
|
||||
? $"{contribution.Plugin.Manifest.Name} - {contribution.Registration.Title}"
|
||||
: contribution.Plugin.Manifest.Name;
|
||||
}
|
||||
|
||||
private Control CreatePluginSettingsPageHost(PluginSettingsPageContribution contribution)
|
||||
{
|
||||
Control content;
|
||||
try
|
||||
{
|
||||
content = contribution.Registration.ContentFactory(contribution.Plugin.Services);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
content = CreatePluginPageErrorContent(ex);
|
||||
}
|
||||
|
||||
return new StackPanel
|
||||
{
|
||||
Spacing = 16,
|
||||
Children =
|
||||
{
|
||||
new TextBlock
|
||||
{
|
||||
Text = contribution.Registration.Title,
|
||||
FontSize = 24,
|
||||
FontWeight = FontWeight.SemiBold,
|
||||
Foreground = GetThemeBrush("AdaptiveTextPrimaryBrush")
|
||||
},
|
||||
new TextBlock
|
||||
{
|
||||
Text = contribution.Plugin.Manifest.Name,
|
||||
Foreground = GetThemeBrush("AdaptiveTextSecondaryBrush")
|
||||
},
|
||||
content
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Control CreatePluginPageErrorContent(Exception exception)
|
||||
{
|
||||
return new Border
|
||||
{
|
||||
Background = new SolidColorBrush(Color.Parse("#332B0F16")),
|
||||
BorderBrush = new SolidColorBrush(Color.Parse("#66F97316")),
|
||||
BorderThickness = new Thickness(1),
|
||||
CornerRadius = new CornerRadius(16),
|
||||
Padding = new Thickness(16),
|
||||
Child = new TextBlock
|
||||
{
|
||||
Text = exception.Message,
|
||||
TextWrapping = TextWrapping.Wrap
|
||||
}
|
||||
};
|
||||
// Legacy plugin settings pages are removed in API-only settings mode.
|
||||
}
|
||||
|
||||
private void UpdatePluginSettingsPageVisibility(string? selectedTag)
|
||||
{
|
||||
foreach (var pair in _pluginSettingsPageHosts)
|
||||
{
|
||||
pair.Value.IsVisible = string.Equals(pair.Key, selectedTag, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
_ = selectedTag;
|
||||
}
|
||||
|
||||
internal void RefreshPluginSettingsNavigation()
|
||||
{
|
||||
if (SettingsNavView?.MenuItems is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var pair in _pluginSettingsPageHosts.ToArray())
|
||||
{
|
||||
var navItem = SettingsNavView.MenuItems
|
||||
.OfType<NavigationViewItem>()
|
||||
.FirstOrDefault(item => string.Equals(item.Tag?.ToString(), pair.Key, StringComparison.OrdinalIgnoreCase));
|
||||
if (navItem is not null)
|
||||
{
|
||||
SettingsNavView.MenuItems.Remove(navItem);
|
||||
}
|
||||
|
||||
SettingsContentPagesHost.Children.Remove(pair.Value);
|
||||
}
|
||||
|
||||
_pluginSettingsPageHosts.Clear();
|
||||
InitializePluginSettingsNavigation();
|
||||
// Legacy plugin settings pages are removed in API-only settings mode.
|
||||
}
|
||||
|
||||
private string? GetSelectedSettingsTabTag()
|
||||
@@ -212,6 +72,3 @@ public partial class MainWindow
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@ using LanMountainDesktop.PluginSdk;
|
||||
|
||||
namespace LanMountainDesktop.Services;
|
||||
|
||||
public sealed record PluginSettingsPageContribution(
|
||||
public sealed record PluginSettingsSectionContribution(
|
||||
LoadedPlugin Plugin,
|
||||
PluginSettingsPageRegistration Registration);
|
||||
PluginSettingsSectionRegistration Registration);
|
||||
|
||||
public sealed record PluginDesktopComponentContribution(
|
||||
LoadedPlugin Plugin,
|
||||
|
||||
@@ -170,10 +170,10 @@ public sealed class PluginLoader
|
||||
AppLogger.Info("PluginLoader", $"Service provider built. PluginId='{manifest.Id}'.");
|
||||
runtimeContext.SetServices(pluginServices);
|
||||
|
||||
var settingsPages = pluginServices
|
||||
.GetServices<PluginSettingsPageRegistration>()
|
||||
.OrderBy(page => page.SortOrder)
|
||||
.ThenBy(page => page.Title, StringComparer.OrdinalIgnoreCase)
|
||||
var settingsSections = pluginServices
|
||||
.GetServices<PluginSettingsSectionRegistration>()
|
||||
.OrderBy(section => section.SortOrder)
|
||||
.ThenBy(section => section.TitleLocalizationKey, StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
var desktopComponents = pluginServices
|
||||
.GetServices<PluginDesktopComponentRegistration>()
|
||||
@@ -183,7 +183,7 @@ public sealed class PluginLoader
|
||||
var exportedServices = ResolveExports(manifest, pluginServices);
|
||||
AppLogger.Info(
|
||||
"PluginLoader",
|
||||
$"Plugin contributions resolved. PluginId='{manifest.Id}'; SettingsPages={settingsPages.Length}; Widgets={desktopComponents.Length}; Exports={exportedServices.Count}.");
|
||||
$"Plugin contributions resolved. PluginId='{manifest.Id}'; SettingsSections={settingsSections.Length}; Widgets={desktopComponents.Length}; Exports={exportedServices.Count}.");
|
||||
hostedServices = pluginServices.GetServices<IHostedService>().ToArray();
|
||||
StartHostedServices(hostedServices);
|
||||
AppLogger.Info("PluginLoader", $"Hosted services started. PluginId='{manifest.Id}'; HostedServices={hostedServices.Count}.");
|
||||
@@ -196,7 +196,7 @@ public sealed class PluginLoader
|
||||
plugin,
|
||||
runtimeContext,
|
||||
pluginServices,
|
||||
settingsPages,
|
||||
settingsSections,
|
||||
desktopComponents,
|
||||
exportedServices,
|
||||
hostedServices,
|
||||
@@ -314,6 +314,8 @@ public sealed class PluginLoader
|
||||
RegisterHostService<IPluginPackageManager>(services, hostServices);
|
||||
RegisterHostService<IHostApplicationLifecycle>(services, hostServices);
|
||||
RegisterHostService<IPluginExportRegistry>(services, hostServices);
|
||||
RegisterHostService<ISettingsService>(services, hostServices);
|
||||
RegisterHostService<ISettingsCatalog>(services, hostServices);
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace LanMountainDesktop.Views.SettingsPages;
|
||||
namespace LanMountainDesktop.Services.PluginMarket;
|
||||
|
||||
internal sealed class AirAppMarketCacheService
|
||||
{
|
||||
|
||||
@@ -16,7 +16,7 @@ using Avalonia.Media.Imaging;
|
||||
using LanMountainDesktop.PluginSdk;
|
||||
using LanMountainDesktop.Services;
|
||||
|
||||
namespace LanMountainDesktop.Views.SettingsPages;
|
||||
namespace LanMountainDesktop.Services.PluginMarket;
|
||||
|
||||
internal sealed class PluginMarketEmbeddedView : UserControl, IDisposable
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Media.Imaging;
|
||||
|
||||
namespace LanMountainDesktop.Views.SettingsPages;
|
||||
namespace LanMountainDesktop.Services.PluginMarket;
|
||||
|
||||
internal sealed class AirAppMarketIconService : IDisposable
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@ using System.Net.Http.Headers;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LanMountainDesktop.Views.SettingsPages;
|
||||
namespace LanMountainDesktop.Services.PluginMarket;
|
||||
|
||||
internal sealed class AirAppMarketIndexService : IDisposable
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@ using System.Security.Cryptography;
|
||||
using LanMountainDesktop.PluginSdk;
|
||||
using LanMountainDesktop.Services;
|
||||
|
||||
namespace LanMountainDesktop.Views.SettingsPages;
|
||||
namespace LanMountainDesktop.Services.PluginMarket;
|
||||
|
||||
internal sealed class AirAppMarketInstallService : IDisposable
|
||||
{
|
||||
|
||||
@@ -6,7 +6,7 @@ using System.Linq;
|
||||
using System.Text.Json;
|
||||
using LanMountainDesktop.PluginSdk;
|
||||
|
||||
namespace LanMountainDesktop.Views.SettingsPages;
|
||||
namespace LanMountainDesktop.Services.PluginMarket;
|
||||
|
||||
internal static class AirAppMarketDefaults
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@ using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LanMountainDesktop.Views.SettingsPages;
|
||||
namespace LanMountainDesktop.Services.PluginMarket;
|
||||
|
||||
internal sealed class AirAppMarketReadmeService : IDisposable
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LanMountainDesktop.Services;
|
||||
|
||||
namespace LanMountainDesktop.Views.SettingsPages;
|
||||
namespace LanMountainDesktop.Services.PluginMarket;
|
||||
|
||||
internal sealed class AirAppMarketReleaseResolverService
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@ using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using LanMountainDesktop.Services;
|
||||
using LanMountainDesktop.Services.PluginMarket;
|
||||
|
||||
namespace LanMountainDesktop.Views.SettingsPages;
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ using Avalonia.Markup.Xaml;
|
||||
using LanMountainDesktop.Models;
|
||||
using LanMountainDesktop.Plugins;
|
||||
using LanMountainDesktop.PluginSdk;
|
||||
using LanMountainDesktop.Services.Settings;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
@@ -29,10 +30,12 @@ public sealed class PluginRuntimeService : IDisposable
|
||||
private readonly PluginSharedContractManager _sharedContractManager;
|
||||
private readonly IServiceProvider _hostServices;
|
||||
private readonly IPluginPackageManager _packageManager;
|
||||
private readonly SettingsFacadeService _settingsFacade;
|
||||
private readonly SettingsCatalogService _settingsCatalogService;
|
||||
private readonly List<LoadedPlugin> _loadedPlugins = [];
|
||||
private readonly List<PluginLoadResult> _loadResults = [];
|
||||
private readonly List<PluginCatalogEntry> _catalog = [];
|
||||
private readonly List<PluginSettingsPageContribution> _settingsPages = [];
|
||||
private readonly List<PluginSettingsSectionContribution> _settingsSections = [];
|
||||
private readonly List<PluginDesktopComponentContribution> _desktopComponents = [];
|
||||
private readonly object _packageMutationGate = new();
|
||||
|
||||
@@ -42,7 +45,14 @@ public sealed class PluginRuntimeService : IDisposable
|
||||
_sharedContractManager = new PluginSharedContractManager(
|
||||
Path.Combine(GetUserDataRootDirectory(), "PluginMarket"));
|
||||
_packageManager = new PluginRuntimePackageManager(this);
|
||||
_hostServices = new PluginHostServiceProvider(_packageManager, _applicationLifecycle, _exportRegistry);
|
||||
_settingsFacade = new SettingsFacadeService(this);
|
||||
_settingsCatalogService = (SettingsCatalogService)_settingsFacade.Catalog;
|
||||
_hostServices = new PluginHostServiceProvider(
|
||||
_packageManager,
|
||||
_applicationLifecycle,
|
||||
_exportRegistry,
|
||||
_settingsFacade.Settings,
|
||||
_settingsFacade.Catalog);
|
||||
_loaderOptions = CreateOptions();
|
||||
_loader = new PluginLoader(_loaderOptions);
|
||||
}
|
||||
@@ -55,12 +65,14 @@ public sealed class PluginRuntimeService : IDisposable
|
||||
|
||||
public IReadOnlyList<PluginCatalogEntry> Catalog => _catalog;
|
||||
|
||||
public IReadOnlyList<PluginSettingsPageContribution> SettingsPages => _settingsPages;
|
||||
public IReadOnlyList<PluginSettingsSectionContribution> SettingsSections => _settingsSections;
|
||||
|
||||
public IReadOnlyList<PluginDesktopComponentContribution> DesktopComponents => _desktopComponents;
|
||||
|
||||
public IPluginExportRegistry ExportRegistry => _exportRegistry;
|
||||
|
||||
public ISettingsFacadeService SettingsFacade => _settingsFacade;
|
||||
|
||||
public void LoadInstalledPlugins()
|
||||
{
|
||||
Directory.CreateDirectory(PluginsDirectory);
|
||||
@@ -172,11 +184,11 @@ public sealed class PluginRuntimeService : IDisposable
|
||||
true,
|
||||
true,
|
||||
null,
|
||||
loadResult.LoadedPlugin.SettingsPages.Count,
|
||||
loadResult.LoadedPlugin.SettingsSections.Count,
|
||||
loadResult.LoadedPlugin.DesktopComponents.Count));
|
||||
AppLogger.Info(
|
||||
"PluginRuntime",
|
||||
$"Plugin loaded. PluginId='{loadResult.LoadedPlugin.Manifest.Id}'; SourcePath='{loadResult.SourcePath}'; ManifestVersion='{loadResult.LoadedPlugin.Manifest.Version ?? "<unknown>"}'; ApiVersion='{loadResult.LoadedPlugin.Manifest.ApiVersion ?? "<unknown>"}'; SourceKind='{candidate.SourceKind}'; SettingsPages={loadResult.LoadedPlugin.SettingsPages.Count}; Widgets={loadResult.LoadedPlugin.DesktopComponents.Count}.");
|
||||
$"Plugin loaded. PluginId='{loadResult.LoadedPlugin.Manifest.Id}'; SourcePath='{loadResult.SourcePath}'; ManifestVersion='{loadResult.LoadedPlugin.Manifest.Version ?? "<unknown>"}'; ApiVersion='{loadResult.LoadedPlugin.Manifest.ApiVersion ?? "<unknown>"}'; SourceKind='{candidate.SourceKind}'; SettingsSections={loadResult.LoadedPlugin.SettingsSections.Count}; Widgets={loadResult.LoadedPlugin.DesktopComponents.Count}.");
|
||||
Debug.WriteLine($"[PluginRuntime] Loaded '{loadResult.Manifest?.Id}' from '{loadResult.SourcePath}'.");
|
||||
continue;
|
||||
}
|
||||
@@ -374,13 +386,16 @@ public sealed class PluginRuntimeService : IDisposable
|
||||
{
|
||||
UnloadInstalledPlugins();
|
||||
_sharedContractManager.Dispose();
|
||||
_settingsFacade.Dispose();
|
||||
}
|
||||
|
||||
private void UnloadInstalledPlugins()
|
||||
{
|
||||
for (var i = _loadedPlugins.Count - 1; i >= 0; i--)
|
||||
{
|
||||
_exportRegistry.RemoveExports(_loadedPlugins[i].Manifest.Id);
|
||||
var pluginId = _loadedPlugins[i].Manifest.Id;
|
||||
_exportRegistry.RemoveExports(pluginId);
|
||||
_settingsCatalogService.RemovePluginSections(pluginId);
|
||||
_loadedPlugins[i].Dispose();
|
||||
}
|
||||
|
||||
@@ -388,7 +403,7 @@ public sealed class PluginRuntimeService : IDisposable
|
||||
_exportRegistry.Clear();
|
||||
_loadResults.Clear();
|
||||
_catalog.Clear();
|
||||
_settingsPages.Clear();
|
||||
_settingsSections.Clear();
|
||||
_desktopComponents.Clear();
|
||||
}
|
||||
|
||||
@@ -593,9 +608,16 @@ public sealed class PluginRuntimeService : IDisposable
|
||||
{
|
||||
_exportRegistry.ReplaceExports(loadedPlugin.Manifest.Id, loadedPlugin.ExportedServices);
|
||||
|
||||
foreach (var settingsPage in loadedPlugin.SettingsPages)
|
||||
_settingsCatalogService.RegisterPluginSections(loadedPlugin.Manifest.Id, loadedPlugin.SettingsSections);
|
||||
|
||||
_settingsSections.RemoveAll(entry => string.Equals(
|
||||
entry.Plugin.Manifest.Id,
|
||||
loadedPlugin.Manifest.Id,
|
||||
StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
foreach (var settingsSection in loadedPlugin.SettingsSections)
|
||||
{
|
||||
_settingsPages.Add(new PluginSettingsPageContribution(loadedPlugin, settingsPage));
|
||||
_settingsSections.Add(new PluginSettingsSectionContribution(loadedPlugin, settingsSection));
|
||||
}
|
||||
|
||||
foreach (var desktopComponent in loadedPlugin.DesktopComponents)
|
||||
@@ -769,9 +791,10 @@ public sealed class PluginRuntimeService : IDisposable
|
||||
private void RemovePluginFromCatalog(string pluginId)
|
||||
{
|
||||
_catalog.RemoveAll(entry => string.Equals(entry.Manifest.Id, pluginId, StringComparison.OrdinalIgnoreCase));
|
||||
_settingsPages.RemoveAll(entry => string.Equals(entry.Plugin.Manifest.Id, pluginId, StringComparison.OrdinalIgnoreCase));
|
||||
_settingsSections.RemoveAll(entry => string.Equals(entry.Plugin.Manifest.Id, pluginId, StringComparison.OrdinalIgnoreCase));
|
||||
_desktopComponents.RemoveAll(entry => string.Equals(entry.Plugin.Manifest.Id, pluginId, StringComparison.OrdinalIgnoreCase));
|
||||
_loadResults.RemoveAll(entry => string.Equals(entry.Manifest?.Id, pluginId, StringComparison.OrdinalIgnoreCase));
|
||||
_settingsCatalogService.RemovePluginSections(pluginId);
|
||||
}
|
||||
|
||||
private sealed record PluginCandidate(
|
||||
@@ -784,15 +807,21 @@ public sealed class PluginRuntimeService : IDisposable
|
||||
private readonly IPluginPackageManager _packageManager;
|
||||
private readonly IHostApplicationLifecycle _applicationLifecycle;
|
||||
private readonly IPluginExportRegistry _exportRegistry;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly ISettingsCatalog _settingsCatalog;
|
||||
|
||||
public PluginHostServiceProvider(
|
||||
IPluginPackageManager packageManager,
|
||||
IHostApplicationLifecycle applicationLifecycle,
|
||||
IPluginExportRegistry exportRegistry)
|
||||
IPluginExportRegistry exportRegistry,
|
||||
ISettingsService settingsService,
|
||||
ISettingsCatalog settingsCatalog)
|
||||
{
|
||||
_packageManager = packageManager;
|
||||
_applicationLifecycle = applicationLifecycle;
|
||||
_exportRegistry = exportRegistry;
|
||||
_settingsService = settingsService;
|
||||
_settingsCatalog = settingsCatalog;
|
||||
}
|
||||
|
||||
public object? GetService(Type serviceType)
|
||||
@@ -812,6 +841,16 @@ public sealed class PluginRuntimeService : IDisposable
|
||||
return _exportRegistry;
|
||||
}
|
||||
|
||||
if (serviceType == typeof(ISettingsService))
|
||||
{
|
||||
return _settingsService;
|
||||
}
|
||||
|
||||
if (serviceType == typeof(ISettingsCatalog))
|
||||
{
|
||||
return _settingsCatalog;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,11 +75,11 @@ public partial class PluginSettingsPage : UserControl
|
||||
var enabledCount = runtime.Catalog.Count(entry => entry.IsEnabled);
|
||||
PluginSystemStatusTextBlock.Text = F(
|
||||
"settings.plugins.summary_format",
|
||||
"Detected {0} plugin(s); enabled {1}; loaded {2}; settings pages {3}; widgets {4}; failures {5}.",
|
||||
"Detected {0} plugin(s); enabled {1}; loaded {2}; settings sections {3}; widgets {4}; failures {5}.",
|
||||
runtime.Catalog.Count,
|
||||
enabledCount,
|
||||
runtime.Catalog.Count(entry => entry.IsLoaded),
|
||||
runtime.SettingsPages.Count,
|
||||
runtime.SettingsSections.Count,
|
||||
runtime.DesktopComponents.Count,
|
||||
failures.Length);
|
||||
|
||||
@@ -316,9 +316,6 @@ public partial class PluginSettingsPage : UserControl
|
||||
case MainWindow mainWindow:
|
||||
mainWindow.RefreshPluginSettingsNavigation();
|
||||
break;
|
||||
case SettingsWindow settingsWindow:
|
||||
settingsWindow.RefreshPluginSettingsNavigation();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using LanMountainDesktop.PluginSdk;
|
||||
using LanMountainDesktop.Services;
|
||||
using LanMountainDesktop.Views.SettingsPages;
|
||||
using LanMountainDesktop.Services.PluginMarket;
|
||||
|
||||
namespace LanMountainDesktop.Plugins;
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace LanMountainDesktop.Views;
|
||||
|
||||
public partial class SettingsWindow
|
||||
{
|
||||
private void ApplyPluginMarketSettingsLocalization()
|
||||
{
|
||||
PluginMarketSettingsPanel.RefreshFromRuntime();
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace LanMountainDesktop.Views;
|
||||
|
||||
public partial class SettingsWindow
|
||||
{
|
||||
internal TextBlock PluginSettingsPanelTitleTextBlock => PluginSettingsPanel.FindControl<TextBlock>("PluginSettingsPanelTitleTextBlock")!;
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander PluginSystemSettingsExpander => PluginSettingsPanel.FindControl<FluentAvalonia.UI.Controls.SettingsExpander>("PluginSystemSettingsExpander")!;
|
||||
internal TextBlock PluginSystemDescriptionTextBlock => PluginSettingsPanel.FindControl<TextBlock>("PluginSystemDescriptionTextBlock")!;
|
||||
internal TextBlock PluginSystemStatusTextBlock => PluginSettingsPanel.FindControl<TextBlock>("PluginSystemStatusTextBlock")!;
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander InstalledPluginsSettingsExpander => PluginSettingsPanel.FindControl<FluentAvalonia.UI.Controls.SettingsExpander>("InstalledPluginsSettingsExpander")!;
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander ImportPluginPackageSettingsExpander => PluginSettingsPanel.FindControl<FluentAvalonia.UI.Controls.SettingsExpander>("ImportPluginPackageSettingsExpander")!;
|
||||
internal TextBlock PluginRestartHintTextBlock => PluginSettingsPanel.FindControl<TextBlock>("PluginRestartHintTextBlock")!;
|
||||
internal TextBlock PluginCatalogEmptyTextBlock => PluginSettingsPanel.FindControl<TextBlock>("PluginCatalogEmptyTextBlock")!;
|
||||
}
|
||||
@@ -1,198 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using FluentIcons.Common;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using LanMountainDesktop.Models;
|
||||
using LanMountainDesktop.Services;
|
||||
|
||||
namespace LanMountainDesktop.Views;
|
||||
|
||||
public partial class SettingsWindow
|
||||
{
|
||||
private readonly Dictionary<string, Control> _pluginSettingsPageHosts = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private void InitializePluginSettingsNavigation()
|
||||
{
|
||||
_pluginSettingsPageHosts.Clear();
|
||||
_pluginSettingsNavItems.Clear();
|
||||
}
|
||||
|
||||
private void RegisterPluginSettingsDefinitions()
|
||||
{
|
||||
var runtime = (Application.Current as App)?.PluginRuntimeService;
|
||||
var contributions = runtime?.SettingsPages
|
||||
.OrderBy(contribution => contribution.Registration.SortOrder)
|
||||
.ThenBy(contribution => contribution.Plugin.Manifest.Name, StringComparer.OrdinalIgnoreCase)
|
||||
.ThenBy(contribution => contribution.Registration.Title, StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
|
||||
if (contributions is not { Length: > 0 })
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var pageCountsByPluginId = contributions
|
||||
.GroupBy(contribution => contribution.Plugin.Manifest.Id, StringComparer.OrdinalIgnoreCase)
|
||||
.ToDictionary(group => group.Key, group => group.Count(), StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
for (var i = 0; i < contributions.Length; i++)
|
||||
{
|
||||
var contribution = contributions[i];
|
||||
var tag = BuildPluginSettingsTag(contribution);
|
||||
_pluginSettingsPageHosts[tag] = CreatePluginSettingsPageHost(contribution);
|
||||
|
||||
RegisterSettingsPageDefinition(new IndependentSettingsPageDefinition(
|
||||
tag,
|
||||
BuildPluginSettingsNavigationTitle(contribution, pageCountsByPluginId),
|
||||
BuildPluginSettingsPageDescription(contribution),
|
||||
FluentIcons.Common.Symbol.PuzzlePiece,
|
||||
IndependentSettingsPageCategory.External,
|
||||
200 + i,
|
||||
$"{contribution.Plugin.Manifest.Name} - {contribution.Registration.Title}"));
|
||||
}
|
||||
}
|
||||
|
||||
private static string BuildPluginSettingsTag(PluginSettingsPageContribution contribution)
|
||||
{
|
||||
return $"PluginPage:{contribution.Plugin.Manifest.Id}:{contribution.Registration.Id}";
|
||||
}
|
||||
|
||||
private static string BuildPluginSettingsNavigationTitle(
|
||||
PluginSettingsPageContribution contribution,
|
||||
IReadOnlyDictionary<string, int> pageCountsByPluginId)
|
||||
{
|
||||
return pageCountsByPluginId.TryGetValue(contribution.Plugin.Manifest.Id, out var pageCount) && pageCount > 1
|
||||
? $"{contribution.Plugin.Manifest.Name} - {contribution.Registration.Title}"
|
||||
: contribution.Plugin.Manifest.Name;
|
||||
}
|
||||
|
||||
private string BuildPluginSettingsPageDescription(PluginSettingsPageContribution contribution)
|
||||
{
|
||||
return Lf(
|
||||
"settings.page_desc.plugin_contributed_format",
|
||||
"Settings page '{0}' is provided by plugin '{1}'.",
|
||||
contribution.Registration.Title,
|
||||
contribution.Plugin.Manifest.Name);
|
||||
}
|
||||
|
||||
private Control CreatePluginSettingsPageHost(PluginSettingsPageContribution contribution)
|
||||
{
|
||||
Control content;
|
||||
try
|
||||
{
|
||||
content = contribution.Registration.ContentFactory(contribution.Plugin.Services);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
content = CreatePluginPageErrorContent(ex);
|
||||
}
|
||||
|
||||
return new StackPanel
|
||||
{
|
||||
Spacing = 16,
|
||||
MaxWidth = 920,
|
||||
Children =
|
||||
{
|
||||
new TextBlock
|
||||
{
|
||||
Text = contribution.Registration.Title,
|
||||
FontSize = 24,
|
||||
FontWeight = FontWeight.SemiBold,
|
||||
Foreground = GetThemeBrush("TextFillColorPrimaryBrush")
|
||||
},
|
||||
new TextBlock
|
||||
{
|
||||
Text = contribution.Plugin.Manifest.Name,
|
||||
Foreground = GetThemeBrush("TextFillColorSecondaryBrush")
|
||||
},
|
||||
content
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static Control CreatePluginPageErrorContent(Exception exception)
|
||||
{
|
||||
return new Border
|
||||
{
|
||||
Background = new SolidColorBrush(Color.Parse("#332B0F16")),
|
||||
BorderBrush = new SolidColorBrush(Color.Parse("#66F97316")),
|
||||
BorderThickness = new Thickness(1),
|
||||
CornerRadius = new CornerRadius(16),
|
||||
Padding = new Thickness(16),
|
||||
Child = new TextBlock
|
||||
{
|
||||
Text = exception.Message,
|
||||
TextWrapping = TextWrapping.Wrap
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
internal void RefreshPluginSettingsNavigation()
|
||||
{
|
||||
var preferredTag = NormalizeSettingsPageTag(_selectedSettingsTabTag);
|
||||
InitializeSettingsNavigation();
|
||||
SelectSettingsTab(
|
||||
_settingsPageDefinitions.ContainsKey(preferredTag) ? preferredTag : "Plugins",
|
||||
persistSelection: false);
|
||||
PluginSettingsPanel?.RefreshFromRuntime();
|
||||
}
|
||||
|
||||
private string? GetSelectedSettingsTabTag()
|
||||
{
|
||||
return NormalizeSettingsPageTag(_selectedSettingsTabTag);
|
||||
}
|
||||
|
||||
private int ResolveSelectedSettingsTabIndex()
|
||||
{
|
||||
if (SettingsNavView?.MenuItems is null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var items = SettingsNavView.MenuItems.OfType<NavigationViewItem>().ToList();
|
||||
for (var i = 0; i < items.Count; i++)
|
||||
{
|
||||
if (string.Equals(items[i].Tag?.ToString(), NormalizeSettingsPageTag(_selectedSettingsTabTag), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void RestoreSettingsTabSelection(AppSettingsSnapshot snapshot)
|
||||
{
|
||||
if (SettingsNavView?.MenuItems is null || SettingsNavView.MenuItems.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var items = SettingsNavView.MenuItems.OfType<NavigationViewItem>().ToList();
|
||||
if (items.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(snapshot.SettingsTabTag))
|
||||
{
|
||||
var normalizedTag = NormalizeSettingsPageTag(snapshot.SettingsTabTag);
|
||||
var taggedItem = items
|
||||
.FirstOrDefault(item => string.Equals(item.Tag?.ToString(), normalizedTag, StringComparison.OrdinalIgnoreCase));
|
||||
if (taggedItem is not null)
|
||||
{
|
||||
_selectedSettingsTabTag = normalizedTag;
|
||||
SettingsNavView.SelectedItem = taggedItem;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var safeIndex = Math.Clamp(snapshot.SettingsTabIndex, 0, Math.Max(0, items.Count - 1));
|
||||
_selectedSettingsTabTag = items[safeIndex].Tag?.ToString() ?? _selectedSettingsTabTag;
|
||||
SettingsNavView.SelectedItem = items[safeIndex];
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
namespace LanMountainDesktop.Views;
|
||||
|
||||
public partial class SettingsWindow
|
||||
{
|
||||
private void ApplyPluginSettingsLocalization()
|
||||
{
|
||||
PluginSettingsPanelTitleTextBlock.Text = L("settings.plugins.title", "Plugins");
|
||||
PluginSystemSettingsExpander.Header = L("settings.plugins.runtime_header", "Plugin Runtime");
|
||||
PluginSystemSettingsExpander.Description = L("settings.plugins.runtime_desc", "Review plugin runtime state and load results.");
|
||||
PluginSystemDescriptionTextBlock.Text = L("settings.plugins.runtime_hint", "This page shows discovery status, load results, and runtime diagnostics for installed plugins.");
|
||||
PluginSystemStatusTextBlock.Text = L("settings.plugins.runtime_status", "Plugin runtime status will appear here after plugin discovery completes.");
|
||||
InstalledPluginsSettingsExpander.Header = L("settings.plugins.installed_header", "Installed Plugins");
|
||||
InstalledPluginsSettingsExpander.Description = L("settings.plugins.installed_desc", "Review installed plugins and remove them here.");
|
||||
ImportPluginPackageSettingsExpander.Header = L("settings.plugins.import_header", "Install From Package");
|
||||
ImportPluginPackageSettingsExpander.Description = L("settings.plugins.import_desc", "Open a .laapp package and stage it into the local plugin directory.");
|
||||
PluginRestartHintTextBlock.Text = L("settings.plugins.restart_hint", "Plugin installation and deletion changes take effect after restarting the app.");
|
||||
PluginCatalogEmptyTextBlock.Text = L("settings.plugins.empty", "No plugins found.");
|
||||
PluginSettingsPanel.RefreshFromRuntime();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user