mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-08-21 04:21:21 +08:00
0.7.3.1
This commit is contained in:
@@ -12,12 +12,7 @@ namespace LanMountainDesktop.Views.SettingsPages;
|
||||
public partial class GeneratedPluginSettingsPage : SettingsPageBase
|
||||
{
|
||||
public GeneratedPluginSettingsPage()
|
||||
: this(
|
||||
new PluginGeneratedSettingsPageViewModel(
|
||||
HostSettingsFacadeProvider.GetOrCreate().Settings,
|
||||
string.Empty,
|
||||
new PluginSettingsSectionRegistration("_preview", "preview", []),
|
||||
new PluginLocalizer(AppContext.BaseDirectory, "en-US")))
|
||||
: this(Design.IsDesignMode ? CreateDesignTimeViewModel() : CreateDefaultViewModel())
|
||||
{
|
||||
}
|
||||
|
||||
@@ -223,4 +218,272 @@ public partial class GeneratedPluginSettingsPage : SettingsPageBase
|
||||
|
||||
return textBox;
|
||||
}
|
||||
|
||||
private static PluginGeneratedSettingsPageViewModel CreateDefaultViewModel()
|
||||
{
|
||||
return new PluginGeneratedSettingsPageViewModel(
|
||||
HostSettingsFacadeProvider.GetOrCreate().Settings,
|
||||
string.Empty,
|
||||
new PluginSettingsSectionRegistration("_preview", "preview", []),
|
||||
new PluginLocalizer(AppContext.BaseDirectory, "en-US"));
|
||||
}
|
||||
|
||||
private static PluginGeneratedSettingsPageViewModel CreateDesignTimeViewModel()
|
||||
{
|
||||
const string pluginId = "preview.plugin";
|
||||
var settingsService = new DesignTimeSettingsService();
|
||||
var section = new PluginSettingsSectionRegistration(
|
||||
"desktop_preview",
|
||||
"Preview Widget Settings",
|
||||
[
|
||||
new SettingsOptionDefinition(
|
||||
"enable_glow",
|
||||
SettingsOptionType.Toggle,
|
||||
"Enable glow",
|
||||
"Adds a soft highlight around the preview widget.",
|
||||
true),
|
||||
new SettingsOptionDefinition(
|
||||
"refresh_minutes",
|
||||
SettingsOptionType.Number,
|
||||
"Refresh interval",
|
||||
"How often the plugin refreshes its cached content.",
|
||||
30d,
|
||||
minimum: 5d,
|
||||
maximum: 120d),
|
||||
new SettingsOptionDefinition(
|
||||
"layout_density",
|
||||
SettingsOptionType.Select,
|
||||
"Layout density",
|
||||
"Choose how compact the widget layout should feel.",
|
||||
"balanced",
|
||||
[
|
||||
new SettingsOptionChoice("compact", "Compact"),
|
||||
new SettingsOptionChoice("balanced", "Balanced"),
|
||||
new SettingsOptionChoice("comfortable", "Comfortable")
|
||||
]),
|
||||
new SettingsOptionDefinition(
|
||||
"content_path",
|
||||
SettingsOptionType.Path,
|
||||
"Content folder",
|
||||
"Local folder used by the plugin for mock assets.",
|
||||
@"C:\Preview\PluginAssets"),
|
||||
new SettingsOptionDefinition(
|
||||
"keywords",
|
||||
SettingsOptionType.List,
|
||||
"Pinned keywords",
|
||||
"Comma-separated topics that will be emphasized in the widget.",
|
||||
new[] { "avalonia", "preview", "design-time" })
|
||||
],
|
||||
"Mock plugin settings shown only in Avalonia design mode.");
|
||||
|
||||
settingsService.SetValue(
|
||||
SettingsScope.Plugin,
|
||||
"enable_glow",
|
||||
true,
|
||||
pluginId,
|
||||
sectionId: section.Id);
|
||||
settingsService.SetValue(
|
||||
SettingsScope.Plugin,
|
||||
"refresh_minutes",
|
||||
30d,
|
||||
pluginId,
|
||||
sectionId: section.Id);
|
||||
settingsService.SetValue(
|
||||
SettingsScope.Plugin,
|
||||
"layout_density",
|
||||
"balanced",
|
||||
pluginId,
|
||||
sectionId: section.Id);
|
||||
settingsService.SetValue(
|
||||
SettingsScope.Plugin,
|
||||
"content_path",
|
||||
@"C:\Preview\PluginAssets",
|
||||
pluginId,
|
||||
sectionId: section.Id);
|
||||
settingsService.SetValue(
|
||||
SettingsScope.Plugin,
|
||||
"keywords",
|
||||
new[] { "avalonia", "preview", "design-time" },
|
||||
pluginId,
|
||||
sectionId: section.Id);
|
||||
|
||||
return new PluginGeneratedSettingsPageViewModel(
|
||||
settingsService,
|
||||
pluginId,
|
||||
section,
|
||||
new PluginLocalizer(AppContext.BaseDirectory, "en-US"));
|
||||
}
|
||||
|
||||
private sealed class DesignTimeSettingsService : ISettingsService
|
||||
{
|
||||
private readonly Dictionary<string, object?> _values = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public event EventHandler<SettingsChangedEvent>? Changed;
|
||||
|
||||
public T LoadSnapshot<T>(SettingsScope scope, string? subjectId = null, string? placementId = null) where T : new()
|
||||
=> new();
|
||||
|
||||
public void SaveSnapshot<T>(
|
||||
SettingsScope scope,
|
||||
T snapshot,
|
||||
string? subjectId = null,
|
||||
string? placementId = null,
|
||||
string? sectionId = null,
|
||||
IReadOnlyCollection<string>? changedKeys = null)
|
||||
{
|
||||
RaiseChanged(scope, subjectId, placementId, sectionId, changedKeys);
|
||||
}
|
||||
|
||||
public T LoadSection<T>(
|
||||
SettingsScope scope,
|
||||
string subjectId,
|
||||
string sectionId,
|
||||
string? placementId = null) where T : new()
|
||||
=> new();
|
||||
|
||||
public void SaveSection<T>(
|
||||
SettingsScope scope,
|
||||
string subjectId,
|
||||
string sectionId,
|
||||
T section,
|
||||
string? placementId = null,
|
||||
IReadOnlyCollection<string>? changedKeys = null)
|
||||
{
|
||||
RaiseChanged(scope, subjectId, placementId, sectionId, changedKeys);
|
||||
}
|
||||
|
||||
public void DeleteSection(
|
||||
SettingsScope scope,
|
||||
string subjectId,
|
||||
string sectionId,
|
||||
string? placementId = null)
|
||||
{
|
||||
var prefix = BuildStorageKey(scope, subjectId, placementId, sectionId, key: null);
|
||||
foreach (var existingKey in _values.Keys.Where(key => key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)).ToArray())
|
||||
{
|
||||
_values.Remove(existingKey);
|
||||
}
|
||||
|
||||
RaiseChanged(scope, subjectId, placementId, sectionId, changedKeys: null);
|
||||
}
|
||||
|
||||
public T? GetValue<T>(
|
||||
SettingsScope scope,
|
||||
string key,
|
||||
string? subjectId = null,
|
||||
string? placementId = null,
|
||||
string? sectionId = null)
|
||||
{
|
||||
return _values.TryGetValue(BuildStorageKey(scope, subjectId, placementId, sectionId, key), out var value)
|
||||
? ConvertValue<T>(value)
|
||||
: default;
|
||||
}
|
||||
|
||||
public void SetValue<T>(
|
||||
SettingsScope scope,
|
||||
string key,
|
||||
T value,
|
||||
string? subjectId = null,
|
||||
string? placementId = null,
|
||||
string? sectionId = null,
|
||||
IReadOnlyCollection<string>? changedKeys = null)
|
||||
{
|
||||
_values[BuildStorageKey(scope, subjectId, placementId, sectionId, key)] = value;
|
||||
RaiseChanged(scope, subjectId, placementId, sectionId, changedKeys ?? [key]);
|
||||
}
|
||||
|
||||
public IComponentSettingsAccessor GetComponentAccessor(string componentId, string? placementId)
|
||||
{
|
||||
return new DesignTimeComponentSettingsAccessor(this, componentId, placementId);
|
||||
}
|
||||
|
||||
private static T? ConvertValue<T>(object? value)
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
if (value is T typedValue)
|
||||
{
|
||||
return typedValue;
|
||||
}
|
||||
|
||||
var targetType = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T);
|
||||
try
|
||||
{
|
||||
return (T?)Convert.ChangeType(value, targetType);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
private static string BuildStorageKey(
|
||||
SettingsScope scope,
|
||||
string? subjectId,
|
||||
string? placementId,
|
||||
string? sectionId,
|
||||
string? key)
|
||||
{
|
||||
return string.Join(
|
||||
"|",
|
||||
scope,
|
||||
subjectId ?? string.Empty,
|
||||
placementId ?? string.Empty,
|
||||
sectionId ?? string.Empty,
|
||||
key ?? string.Empty);
|
||||
}
|
||||
|
||||
private void RaiseChanged(
|
||||
SettingsScope scope,
|
||||
string? subjectId,
|
||||
string? placementId,
|
||||
string? sectionId,
|
||||
IReadOnlyCollection<string>? changedKeys)
|
||||
{
|
||||
Changed?.Invoke(this, new SettingsChangedEvent(scope, subjectId, placementId, sectionId, changedKeys));
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class DesignTimeComponentSettingsAccessor : IComponentSettingsAccessor
|
||||
{
|
||||
private readonly DesignTimeSettingsService _settingsService;
|
||||
|
||||
public DesignTimeComponentSettingsAccessor(
|
||||
DesignTimeSettingsService settingsService,
|
||||
string componentId,
|
||||
string? placementId)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
ComponentId = componentId;
|
||||
PlacementId = placementId;
|
||||
}
|
||||
|
||||
public string ComponentId { get; }
|
||||
|
||||
public string? PlacementId { get; }
|
||||
|
||||
public T LoadSnapshot<T>() where T : new()
|
||||
=> _settingsService.LoadSnapshot<T>(SettingsScope.ComponentInstance, ComponentId, PlacementId);
|
||||
|
||||
public void SaveSnapshot<T>(T snapshot, IReadOnlyCollection<string>? changedKeys = null)
|
||||
=> _settingsService.SaveSnapshot(SettingsScope.ComponentInstance, snapshot, ComponentId, PlacementId, changedKeys: changedKeys);
|
||||
|
||||
public T LoadSection<T>(string sectionId) where T : new()
|
||||
=> _settingsService.LoadSection<T>(SettingsScope.ComponentInstance, ComponentId, sectionId, PlacementId);
|
||||
|
||||
public void SaveSection<T>(string sectionId, T section, IReadOnlyCollection<string>? changedKeys = null)
|
||||
=> _settingsService.SaveSection(SettingsScope.ComponentInstance, ComponentId, sectionId, section, PlacementId, changedKeys);
|
||||
|
||||
public void DeleteSection(string sectionId)
|
||||
=> _settingsService.DeleteSection(SettingsScope.ComponentInstance, ComponentId, sectionId, PlacementId);
|
||||
|
||||
public T? GetValue<T>(string key)
|
||||
=> _settingsService.GetValue<T>(SettingsScope.ComponentInstance, key, ComponentId, PlacementId);
|
||||
|
||||
public void SetValue<T>(string key, T value, IReadOnlyCollection<string>? changedKeys = null)
|
||||
=> _settingsService.SetValue(SettingsScope.ComponentInstance, key, value, ComponentId, PlacementId, changedKeys: changedKeys);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System;
|
||||
using Avalonia.Controls;
|
||||
using LanMountainDesktop.PluginSdk;
|
||||
using LanMountainDesktop.Services;
|
||||
using LanMountainDesktop.Services.PluginMarket;
|
||||
@@ -17,7 +19,7 @@ namespace LanMountainDesktop.Views.SettingsPages;
|
||||
public partial class PluginMarketSettingsPage : SettingsPageBase
|
||||
{
|
||||
public PluginMarketSettingsPage()
|
||||
: this(CreateDefaultViewModel())
|
||||
: this(Design.IsDesignMode ? CreateDesignTimeViewModel() : CreateDefaultViewModel())
|
||||
{
|
||||
}
|
||||
|
||||
@@ -34,6 +36,11 @@ public partial class PluginMarketSettingsPage : SettingsPageBase
|
||||
|
||||
public override async void OnNavigatedTo(object? parameter)
|
||||
{
|
||||
if (Design.IsDesignMode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await ViewModel.InitializeAsync();
|
||||
}
|
||||
|
||||
@@ -48,6 +55,113 @@ public partial class PluginMarketSettingsPage : SettingsPageBase
|
||||
new AirAppMarketReadmeService());
|
||||
}
|
||||
|
||||
private static PluginMarketSettingsPageViewModel CreateDesignTimeViewModel()
|
||||
{
|
||||
var settingsFacade = HostSettingsFacadeProvider.GetOrCreate();
|
||||
var localizationService = new LocalizationService();
|
||||
var viewModel = new PluginMarketSettingsPageViewModel(
|
||||
settingsFacade,
|
||||
localizationService,
|
||||
new AirAppMarketIconService(),
|
||||
new AirAppMarketReadmeService());
|
||||
|
||||
var previewHostVersion = new Version(1, 2, 0);
|
||||
var items = new[]
|
||||
{
|
||||
CreateMarketItem(
|
||||
new PluginMarketPluginInfo(
|
||||
"news-tiles",
|
||||
"News Tiles",
|
||||
"Brings editorial news cards and ticker rows to the desktop.",
|
||||
"LanMountain Labs",
|
||||
"1.2.0",
|
||||
"1.0.0",
|
||||
"1.0.0",
|
||||
"https://example.com/news-tiles.zip",
|
||||
"v1.2.0",
|
||||
"news-tiles.zip",
|
||||
string.Empty,
|
||||
"https://example.com/news-tiles/readme",
|
||||
"https://example.com/news-tiles",
|
||||
"https://example.com/news-tiles/repo",
|
||||
["news", "widgets"],
|
||||
[],
|
||||
DateTimeOffset.Now.AddDays(-8),
|
||||
DateTimeOffset.Now.AddDays(-2)),
|
||||
localizationService,
|
||||
installedPlugin: null,
|
||||
previewHostVersion),
|
||||
CreateMarketItem(
|
||||
new PluginMarketPluginInfo(
|
||||
"workspace-pulse",
|
||||
"Workspace Pulse",
|
||||
"Tracks active projects and shows a compact productivity summary.",
|
||||
"Studio North",
|
||||
"2.4.0",
|
||||
"1.0.0",
|
||||
"1.0.0",
|
||||
"https://example.com/workspace-pulse.zip",
|
||||
"v2.4.0",
|
||||
"workspace-pulse.zip",
|
||||
string.Empty,
|
||||
"https://example.com/workspace-pulse/readme",
|
||||
"https://example.com/workspace-pulse",
|
||||
"https://example.com/workspace-pulse/repo",
|
||||
["dashboard", "productivity"],
|
||||
[],
|
||||
DateTimeOffset.Now.AddDays(-30),
|
||||
DateTimeOffset.Now.AddDays(-1)),
|
||||
localizationService,
|
||||
new InstalledPluginInfo(
|
||||
new PluginManifest(
|
||||
"workspace-pulse",
|
||||
"Workspace Pulse",
|
||||
"WorkspacePulse.dll",
|
||||
"Tracks active projects and shows a compact productivity summary.",
|
||||
"Studio North",
|
||||
"2.1.0"),
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
null),
|
||||
previewHostVersion),
|
||||
CreateMarketItem(
|
||||
new PluginMarketPluginInfo(
|
||||
"glass-panels",
|
||||
"Glass Panels",
|
||||
"Adds experimental acrylic surfaces for plugin-powered widgets.",
|
||||
"Aster Team",
|
||||
"0.8.0",
|
||||
"1.0.0",
|
||||
"9.0.0",
|
||||
"https://example.com/glass-panels.zip",
|
||||
"v0.8.0",
|
||||
"glass-panels.zip",
|
||||
string.Empty,
|
||||
"https://example.com/glass-panels/readme",
|
||||
"https://example.com/glass-panels",
|
||||
"https://example.com/glass-panels/repo",
|
||||
["theme", "experimental"],
|
||||
[],
|
||||
DateTimeOffset.Now.AddDays(-12),
|
||||
DateTimeOffset.Now.AddDays(-3)),
|
||||
localizationService,
|
||||
installedPlugin: null,
|
||||
previewHostVersion)
|
||||
};
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
viewModel.MarketPlugins.Add(item);
|
||||
viewModel.FilteredPlugins.Add(item);
|
||||
}
|
||||
|
||||
viewModel.ShowEmptyState = false;
|
||||
viewModel.EmptyStateText = string.Empty;
|
||||
viewModel.StatusMessage = "Showing 3 mocked marketplace plugins for Avalonia design mode.";
|
||||
return viewModel;
|
||||
}
|
||||
|
||||
private void OnRestartRequested(string? reason)
|
||||
{
|
||||
RequestRestart(reason ?? ViewModel.RestartRequiredMessage);
|
||||
@@ -60,4 +174,17 @@ public partial class PluginMarketSettingsPage : SettingsPageBase
|
||||
OpenDrawer(drawer, detailViewModel.DrawerTitle);
|
||||
await detailViewModel.InitializeAsync();
|
||||
}
|
||||
|
||||
private static PluginMarketItemViewModel CreateMarketItem(
|
||||
PluginMarketPluginInfo plugin,
|
||||
LocalizationService localizationService,
|
||||
InstalledPluginInfo? installedPlugin,
|
||||
Version hostVersion)
|
||||
{
|
||||
var languageCode = localizationService.NormalizeLanguageCode(
|
||||
HostSettingsFacadeProvider.GetOrCreate().Region.Get().LanguageCode);
|
||||
var item = new PluginMarketItemViewModel(plugin, localizationService, languageCode);
|
||||
item.ApplyInstallState(installedPlugin, hostVersion);
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Avalonia.Controls;
|
||||
using LanMountainDesktop.PluginSdk;
|
||||
using LanMountainDesktop.Services.Settings;
|
||||
using LanMountainDesktop.ViewModels;
|
||||
@@ -15,7 +16,7 @@ namespace LanMountainDesktop.Views.SettingsPages;
|
||||
public partial class PluginsSettingsPage : SettingsPageBase
|
||||
{
|
||||
public PluginsSettingsPage()
|
||||
: this(new PluginsSettingsPageViewModel(HostSettingsFacadeProvider.GetOrCreate()))
|
||||
: this(Design.IsDesignMode ? CreateDesignTimeViewModel() : new PluginsSettingsPageViewModel(HostSettingsFacadeProvider.GetOrCreate()))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -31,6 +32,11 @@ public partial class PluginsSettingsPage : SettingsPageBase
|
||||
|
||||
public override async void OnNavigatedTo(object? parameter)
|
||||
{
|
||||
if (Design.IsDesignMode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await ViewModel.InitializeAsync();
|
||||
}
|
||||
|
||||
@@ -38,4 +44,47 @@ public partial class PluginsSettingsPage : SettingsPageBase
|
||||
{
|
||||
RequestRestart(ViewModel.RestartRequiredMessage);
|
||||
}
|
||||
|
||||
private static PluginsSettingsPageViewModel CreateDesignTimeViewModel()
|
||||
{
|
||||
var viewModel = new PluginsSettingsPageViewModel(HostSettingsFacadeProvider.GetOrCreate());
|
||||
viewModel.InstalledPlugins.Add(new InstalledPluginItemViewModel(new InstalledPluginInfo(
|
||||
new PluginManifest(
|
||||
"calendar-plus",
|
||||
"Calendar Plus",
|
||||
"CalendarPlus.dll",
|
||||
"Adds a compact agenda widget and richer date cards.",
|
||||
"LanMountain Labs",
|
||||
"1.4.0"),
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
null)));
|
||||
viewModel.InstalledPlugins.Add(new InstalledPluginItemViewModel(new InstalledPluginInfo(
|
||||
new PluginManifest(
|
||||
"focus-mode",
|
||||
"Focus Mode",
|
||||
"FocusMode.dll",
|
||||
"Provides a distraction-free overlay and quick toggles.",
|
||||
"Studio North",
|
||||
"0.9.2"),
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
null)));
|
||||
viewModel.InstalledPlugins.Add(new InstalledPluginItemViewModel(new InstalledPluginInfo(
|
||||
new PluginManifest(
|
||||
"notes-dock",
|
||||
"Notes Dock",
|
||||
"NotesDock.dll",
|
||||
"Pins short markdown notes directly on the desktop.",
|
||||
"Aster Team",
|
||||
"2.1.0"),
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
null)));
|
||||
viewModel.StatusMessage = "Loaded 3 mocked plugins for Avalonia design mode.";
|
||||
return viewModel;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Avalonia.Controls;
|
||||
using LanMountainDesktop.PluginSdk;
|
||||
using LanMountainDesktop.Services;
|
||||
using LanMountainDesktop.Services.Settings;
|
||||
@@ -16,7 +17,7 @@ namespace LanMountainDesktop.Views.SettingsPages;
|
||||
public partial class WeatherSettingsPage : SettingsPageBase
|
||||
{
|
||||
public WeatherSettingsPage()
|
||||
: this(CreateDefaultViewModel())
|
||||
: this(Design.IsDesignMode ? CreateDesignTimeViewModel() : CreateDefaultViewModel())
|
||||
{
|
||||
}
|
||||
|
||||
@@ -29,7 +30,7 @@ public partial class WeatherSettingsPage : SettingsPageBase
|
||||
|
||||
public WeatherSettingsPageViewModel ViewModel { get; }
|
||||
|
||||
private static WeatherSettingsPageViewModel CreateDefaultViewModel()
|
||||
private static WeatherSettingsPageViewModel CreateDefaultViewModel(bool enableStartupPreviewRefresh = true)
|
||||
{
|
||||
var settingsFacade = HostSettingsFacadeProvider.GetOrCreate();
|
||||
var localizationService = new LocalizationService();
|
||||
@@ -42,6 +43,14 @@ public partial class WeatherSettingsPage : SettingsPageBase
|
||||
settingsFacade,
|
||||
localizationService,
|
||||
locationService,
|
||||
weatherLocationRefreshService);
|
||||
weatherLocationRefreshService,
|
||||
enableStartupPreviewRefresh);
|
||||
}
|
||||
|
||||
private static WeatherSettingsPageViewModel CreateDesignTimeViewModel()
|
||||
{
|
||||
var viewModel = CreateDefaultViewModel(enableStartupPreviewRefresh: false);
|
||||
viewModel.ApplyDesignTimePreview();
|
||||
return viewModel;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user