mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-20 23:54:26 +08:00
0.5.7
示例插件
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
|
||||
目录结构:
|
||||
- `docs/`:插件开发文档、打包文档
|
||||
- `releases/`:已经打包完成、可直接分享与安装的 `.laapp` 插件包
|
||||
- `samples/`:示例插件,其中 `LanMountainDesktop.SamplePlugin` 是示例开发插件
|
||||
- `standards/`:插件标准文件与模板
|
||||
- `tools/`:插件打包与构建工具
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\LanMountainDesktop.PluginSdk\LanMountainDesktop.PluginSdk.csproj" Private="false" />
|
||||
<None Include="plugin.json" CopyToOutputDirectory="PreserveNewest" />
|
||||
<None Include="Localization\*.json" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="CreateLaappPackage" AfterTargets="Build">
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"settings.page_title": "Plugin Status",
|
||||
"plugin.name": "LanMountain Sample Plugin",
|
||||
"plugin.description": "Example plugin used to validate PluginSdk loading, services, communication, and localization.",
|
||||
"widget.display_name": "Sample Plugin Status Clock",
|
||||
"widget.category": "Plugins",
|
||||
"settings.header.title": "Sample Plugin Capability Inspector",
|
||||
"settings.section.info": "Plugin Info",
|
||||
"settings.section.capabilities": "Accessible Capabilities",
|
||||
"settings.section.status": "Live Runtime Status",
|
||||
"settings.info.plugin_name": "Plugin Name",
|
||||
"settings.info.plugin_id": "Plugin Id",
|
||||
"settings.info.version": "Version",
|
||||
"settings.info.author": "Author",
|
||||
"settings.info.description": "Description",
|
||||
"settings.info.plugin_directory": "Plugin Directory",
|
||||
"settings.info.data_directory": "Data Directory",
|
||||
"settings.info.host_application": "Host Application",
|
||||
"settings.info.host_version": "Host Version",
|
||||
"settings.info.sdk_api_version": "SDK API Version",
|
||||
"settings.info.state_service_resolved": "State Service Resolved",
|
||||
"settings.info.clock_service_resolved": "Clock Service Resolved",
|
||||
"settings.info.message_bus_resolved": "Message Bus Resolved",
|
||||
"settings.info.component_placed": "Component Placed",
|
||||
"settings.info.placed_count": "Placed Count",
|
||||
"settings.info.preview_count": "Preview Count",
|
||||
"settings.info.placement_ids": "Placement Ids",
|
||||
"settings.info.last_component_id": "Last Component Id",
|
||||
"settings.info.last_cell_size": "Last Cell Size",
|
||||
"settings.info.clock_service_time": "Clock Service Time",
|
||||
"settings.status.updated_at": "Updated: {0}",
|
||||
"status.frontend.title": "Frontend Status",
|
||||
"status.component.title": "Component Status",
|
||||
"status.backend.title": "Backend Status",
|
||||
"status.service.title": "Clock Service",
|
||||
"status.summary.pending": "Pending",
|
||||
"status.summary.attached": "Attached",
|
||||
"status.summary.healthy": "Healthy",
|
||||
"status.summary.faulted": "Faulted",
|
||||
"status.summary.placed": "Placed",
|
||||
"status.summary.preview": "Preview",
|
||||
"status.frontend.detail.pending": "Waiting for a plugin UI surface to connect.",
|
||||
"status.frontend.detail.settings_connected": "Settings page is connected to plugin services and communication.",
|
||||
"status.frontend.detail.widget_connected": "Widget surface is connected to plugin services and communication.",
|
||||
"status.component.detail.pending": "No component instance has been created yet.",
|
||||
"status.component.detail.none": "No component instance is active.",
|
||||
"status.component.detail.preview": "Preview instances: {0}; no placed desktop instance is active yet.",
|
||||
"status.component.detail.placed": "Placed count: {0}; preview count: {1}; placements: {2}",
|
||||
"status.backend.detail.pending": "Plugin initialization is in progress.",
|
||||
"status.backend.detail.log_written": "Initialization log written to: {0}",
|
||||
"status.backend.detail.log_write_failed": "Initialization log write failed: {0}",
|
||||
"status.service.detail.pending": "Clock service is not attached yet.",
|
||||
"status.service.detail.attached": "Clock service was attached and is waiting for the first tick.",
|
||||
"status.service.detail.running": "Clock service is running. Current service time: {0}",
|
||||
"status.service.detail.write_failed": "Clock state write failed: {0}",
|
||||
"capability.manifest.title": "IPluginContext.Manifest",
|
||||
"capability.manifest.detail": "Readable. Current plugin id: {0}; version: {1}.",
|
||||
"capability.directories.title": "IPluginContext.PluginDirectory / DataDirectory",
|
||||
"capability.directories.detail": "Readable. Plugin directory: {0}; data directory: {1}.",
|
||||
"capability.properties.title": "IPluginContext.Properties",
|
||||
"capability.properties.detail": "Readable. Host properties currently exposed: {0}.",
|
||||
"capability.get_service.title": "IPluginContext.GetService<T>()",
|
||||
"capability.get_service.detail": "Callable. State service resolved: {0}; clock service resolved: {1}; message bus resolved: {2}.",
|
||||
"capability.register_service.title": "IPluginContext.RegisterService<TService>()",
|
||||
"capability.register_service.detail": "Callable during plugin initialization. This sample plugin registers SamplePluginRuntimeStateService and SamplePluginClockService into the plugin service container.",
|
||||
"capability.message_bus.title": "Plugin Communication Bus",
|
||||
"capability.message_bus.detail": "This sample plugin uses IPluginMessageBus to push clock ticks and state change notifications into plugin UI surfaces.",
|
||||
"capability.widget_context.title": "PluginDesktopComponentContext",
|
||||
"capability.widget_context.detail": "Widgets can read ComponentId, PlacementId, CellSize, and call GetService<T>() against the same plugin service container.",
|
||||
"widget.subtitle.preview": "Preview surface | placed: {0}",
|
||||
"widget.subtitle.placement": "Placement {0} | placed: {1}",
|
||||
"common.dev": "dev",
|
||||
"common.none": "(none)",
|
||||
"common.unknown": "(unknown)",
|
||||
"common.true": "true",
|
||||
"common.false": "false",
|
||||
"common.yes": "Yes",
|
||||
"common.no": "No"
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"settings.page_title": "插件状态",
|
||||
"plugin.name": "阑山示例插件",
|
||||
"plugin.description": "用于验证 PluginSdk 加载、服务、通信与本地化能力的示例插件。",
|
||||
"widget.display_name": "示例插件状态时钟",
|
||||
"widget.category": "插件",
|
||||
"settings.header.title": "示例插件能力检查器",
|
||||
"settings.section.info": "插件信息",
|
||||
"settings.section.capabilities": "可访问能力",
|
||||
"settings.section.status": "实时运行状态",
|
||||
"settings.info.plugin_name": "插件名称",
|
||||
"settings.info.plugin_id": "插件 Id",
|
||||
"settings.info.version": "版本",
|
||||
"settings.info.author": "作者",
|
||||
"settings.info.description": "描述",
|
||||
"settings.info.plugin_directory": "插件目录",
|
||||
"settings.info.data_directory": "数据目录",
|
||||
"settings.info.host_application": "宿主应用",
|
||||
"settings.info.host_version": "宿主版本",
|
||||
"settings.info.sdk_api_version": "SDK API 版本",
|
||||
"settings.info.state_service_resolved": "状态服务已解析",
|
||||
"settings.info.clock_service_resolved": "时钟服务已解析",
|
||||
"settings.info.message_bus_resolved": "消息总线已解析",
|
||||
"settings.info.component_placed": "组件是否已放置",
|
||||
"settings.info.placed_count": "已放置数量",
|
||||
"settings.info.preview_count": "预览数量",
|
||||
"settings.info.placement_ids": "放置位置 Id",
|
||||
"settings.info.last_component_id": "最近组件 Id",
|
||||
"settings.info.last_cell_size": "最近单元尺寸",
|
||||
"settings.info.clock_service_time": "时钟服务时间",
|
||||
"settings.status.updated_at": "更新时间:{0}",
|
||||
"status.frontend.title": "前端状态",
|
||||
"status.component.title": "组件状态",
|
||||
"status.backend.title": "后端状态",
|
||||
"status.service.title": "时钟服务",
|
||||
"status.summary.pending": "等待中",
|
||||
"status.summary.attached": "已挂接",
|
||||
"status.summary.healthy": "正常",
|
||||
"status.summary.faulted": "异常",
|
||||
"status.summary.placed": "已放置",
|
||||
"status.summary.preview": "预览中",
|
||||
"status.frontend.detail.pending": "等待插件界面接入。",
|
||||
"status.frontend.detail.settings_connected": "设置页已接入插件服务与通信。",
|
||||
"status.frontend.detail.widget_connected": "组件界面已接入插件服务与通信。",
|
||||
"status.component.detail.pending": "当前还没有创建组件实例。",
|
||||
"status.component.detail.none": "当前没有活动中的组件实例。",
|
||||
"status.component.detail.preview": "当前预览实例数量:{0};尚未有已放置的桌面实例。",
|
||||
"status.component.detail.placed": "已放置数量:{0};预览数量:{1};放置位置:{2}",
|
||||
"status.backend.detail.pending": "插件初始化进行中。",
|
||||
"status.backend.detail.log_written": "初始化日志已写入:{0}",
|
||||
"status.backend.detail.log_write_failed": "初始化日志写入失败:{0}",
|
||||
"status.service.detail.pending": "时钟服务尚未挂接。",
|
||||
"status.service.detail.attached": "时钟服务已挂接,正在等待第一次心跳。",
|
||||
"status.service.detail.running": "时钟服务运行中,当前服务时间:{0}",
|
||||
"status.service.detail.write_failed": "时钟状态写入失败:{0}",
|
||||
"capability.manifest.title": "IPluginContext.Manifest",
|
||||
"capability.manifest.detail": "可读取。当前插件 id:{0};版本:{1}。",
|
||||
"capability.directories.title": "IPluginContext.PluginDirectory / DataDirectory",
|
||||
"capability.directories.detail": "可读取。插件目录:{0};数据目录:{1}。",
|
||||
"capability.properties.title": "IPluginContext.Properties",
|
||||
"capability.properties.detail": "可读取。宿主当前暴露的属性:{0}。",
|
||||
"capability.get_service.title": "IPluginContext.GetService<T>()",
|
||||
"capability.get_service.detail": "可调用。状态服务已解析:{0};时钟服务已解析:{1};消息总线已解析:{2}。",
|
||||
"capability.register_service.title": "IPluginContext.RegisterService<TService>()",
|
||||
"capability.register_service.detail": "可在插件初始化阶段调用。这个示例插件会把 SamplePluginRuntimeStateService 和 SamplePluginClockService 注册进插件服务容器。",
|
||||
"capability.message_bus.title": "插件通信总线",
|
||||
"capability.message_bus.detail": "这个示例插件通过 IPluginMessageBus 向插件 UI 推送时钟心跳和状态变化通知。",
|
||||
"capability.widget_context.title": "PluginDesktopComponentContext",
|
||||
"capability.widget_context.detail": "组件可以读取 ComponentId、PlacementId、CellSize,并能在同一个插件服务容器上调用 GetService<T>()。",
|
||||
"widget.subtitle.preview": "预览界面 | 已放置:{0}",
|
||||
"widget.subtitle.placement": "位置 {0} | 已放置:{1}",
|
||||
"common.dev": "开发版",
|
||||
"common.none": "(无)",
|
||||
"common.unknown": "(未知)",
|
||||
"common.true": "是",
|
||||
"common.false": "否",
|
||||
"common.yes": "是",
|
||||
"common.no": "否"
|
||||
}
|
||||
@@ -11,10 +11,11 @@ public sealed class SamplePlugin : PluginBase, IDisposable
|
||||
public override void Initialize(IPluginContext context)
|
||||
{
|
||||
Directory.CreateDirectory(context.DataDirectory);
|
||||
var localizer = PluginLocalizer.Create(context);
|
||||
|
||||
var hostName = GetHostProperty(context, "HostApplicationName", "UnknownHost");
|
||||
var hostVersion = GetHostProperty(context, "HostVersion", "UnknownVersion");
|
||||
var sdkApiVersion = GetHostProperty(context, "PluginSdkApiVersion", "UnknownApiVersion");
|
||||
var hostName = GetHostProperty(context, PluginHostPropertyKeys.HostApplicationName, "UnknownHost");
|
||||
var hostVersion = GetHostProperty(context, PluginHostPropertyKeys.HostVersion, "UnknownVersion");
|
||||
var sdkApiVersion = GetHostProperty(context, PluginHostPropertyKeys.PluginSdkApiVersion, "UnknownApiVersion");
|
||||
var messageBus = context.GetService<IPluginMessageBus>()
|
||||
?? throw new InvalidOperationException("Plugin message bus is not available.");
|
||||
|
||||
@@ -25,10 +26,11 @@ public sealed class SamplePlugin : PluginBase, IDisposable
|
||||
hostName,
|
||||
hostVersion,
|
||||
sdkApiVersion,
|
||||
messageBus);
|
||||
messageBus,
|
||||
localizer);
|
||||
context.RegisterService(_stateService);
|
||||
|
||||
_clockService = new SamplePluginClockService(context.DataDirectory, _stateService, messageBus);
|
||||
_clockService = new SamplePluginClockService(context.DataDirectory, _stateService, messageBus, localizer);
|
||||
context.RegisterService(_clockService);
|
||||
_stateService.AttachClockService(_clockService);
|
||||
|
||||
@@ -39,11 +41,17 @@ public sealed class SamplePlugin : PluginBase, IDisposable
|
||||
try
|
||||
{
|
||||
File.AppendAllText(logPath, initMessage + Environment.NewLine);
|
||||
_stateService.MarkBackendReady($"Initialization log written to {logPath}.");
|
||||
_stateService.MarkBackendReady(localizer.Format(
|
||||
"status.backend.detail.log_written",
|
||||
"初始化日志已写入:{0}",
|
||||
logPath));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_stateService.MarkBackendFaulted($"Initialization log write failed: {ex.Message}");
|
||||
_stateService.MarkBackendFaulted(localizer.Format(
|
||||
"status.backend.detail.log_write_failed",
|
||||
"初始化日志写入失败:{0}",
|
||||
ex.Message));
|
||||
throw;
|
||||
}
|
||||
|
||||
@@ -51,15 +59,15 @@ public sealed class SamplePlugin : PluginBase, IDisposable
|
||||
|
||||
context.RegisterSettingsPage(new PluginSettingsPageRegistration(
|
||||
"status",
|
||||
"Plugin Status",
|
||||
localizer.GetString("settings.page_title", "插件状态"),
|
||||
() => new SamplePluginSettingsView(context)));
|
||||
|
||||
context.RegisterDesktopComponent(new PluginDesktopComponentRegistration(
|
||||
"LanMountainDesktop.SamplePlugin.StatusClock",
|
||||
"Sample Plugin Status Clock",
|
||||
localizer.GetString("widget.display_name", "示例插件状态时钟"),
|
||||
widgetContext => new SamplePluginStatusClockWidget(widgetContext),
|
||||
iconKey: "PuzzlePiece",
|
||||
category: "Plugins",
|
||||
category: localizer.GetString("widget.category", "插件"),
|
||||
minWidthCells: 4,
|
||||
minHeightCells: 4,
|
||||
allowDesktopPlacement: true,
|
||||
|
||||
@@ -66,6 +66,7 @@ internal sealed class SamplePluginRuntimeStateService
|
||||
private readonly string _hostApplicationName;
|
||||
private readonly string _hostVersion;
|
||||
private readonly string _sdkApiVersion;
|
||||
private readonly PluginLocalizer _localizer;
|
||||
|
||||
private SamplePluginStatusEntry _frontend;
|
||||
private SamplePluginStatusEntry _component;
|
||||
@@ -82,7 +83,8 @@ internal sealed class SamplePluginRuntimeStateService
|
||||
string hostApplicationName,
|
||||
string hostVersion,
|
||||
string sdkApiVersion,
|
||||
IPluginMessageBus messageBus)
|
||||
IPluginMessageBus messageBus,
|
||||
PluginLocalizer localizer)
|
||||
{
|
||||
_manifest = manifest;
|
||||
_pluginDirectory = pluginDirectory;
|
||||
@@ -91,34 +93,35 @@ internal sealed class SamplePluginRuntimeStateService
|
||||
_hostVersion = hostVersion;
|
||||
_sdkApiVersion = sdkApiVersion;
|
||||
_messageBus = messageBus;
|
||||
_localizer = localizer;
|
||||
|
||||
_frontend = CreateEntry(
|
||||
"frontend",
|
||||
"Frontend",
|
||||
T("status.frontend.title", "前端状态"),
|
||||
SamplePluginHealthState.Pending,
|
||||
"Pending",
|
||||
"Waiting for a plugin UI surface to connect.");
|
||||
T("status.summary.pending", "等待中"),
|
||||
T("status.frontend.detail.pending", "等待插件界面接入。"));
|
||||
|
||||
_component = CreateEntry(
|
||||
"component",
|
||||
"Component",
|
||||
T("status.component.title", "组件状态"),
|
||||
SamplePluginHealthState.Pending,
|
||||
"Pending",
|
||||
"No component instance has been created yet.");
|
||||
T("status.summary.pending", "等待中"),
|
||||
T("status.component.detail.pending", "当前还没有创建组件实例。"));
|
||||
|
||||
_backend = CreateEntry(
|
||||
"backend",
|
||||
"Backend",
|
||||
T("status.backend.title", "后端状态"),
|
||||
SamplePluginHealthState.Pending,
|
||||
"Pending",
|
||||
"Plugin initialization is in progress.");
|
||||
T("status.summary.pending", "等待中"),
|
||||
T("status.backend.detail.pending", "插件初始化进行中。"));
|
||||
|
||||
_service = CreateEntry(
|
||||
"service",
|
||||
"Clock Service",
|
||||
T("status.service.title", "时钟服务"),
|
||||
SamplePluginHealthState.Pending,
|
||||
"Pending",
|
||||
"Clock service is not attached yet.");
|
||||
T("status.summary.pending", "等待中"),
|
||||
T("status.service.detail.pending", "时钟服务尚未挂接。"));
|
||||
}
|
||||
|
||||
public void AttachClockService(SamplePluginClockService clockService)
|
||||
@@ -130,10 +133,10 @@ internal sealed class SamplePluginRuntimeStateService
|
||||
_serviceClockTime = clockService.CurrentTime;
|
||||
_service = CreateEntry(
|
||||
"service",
|
||||
"Clock Service",
|
||||
T("status.service.title", "时钟服务"),
|
||||
SamplePluginHealthState.Pending,
|
||||
"Attached",
|
||||
"Clock service was attached and is waiting for the first tick.");
|
||||
T("status.summary.attached", "已挂接"),
|
||||
T("status.service.detail.attached", "时钟服务已挂接,正在等待第一次心跳。"));
|
||||
}
|
||||
|
||||
PublishStateChanged("Clock service attached");
|
||||
@@ -145,9 +148,9 @@ internal sealed class SamplePluginRuntimeStateService
|
||||
{
|
||||
_frontend = CreateEntry(
|
||||
"frontend",
|
||||
"Frontend",
|
||||
T("status.frontend.title", "前端状态"),
|
||||
SamplePluginHealthState.Healthy,
|
||||
"Healthy",
|
||||
T("status.summary.healthy", "正常"),
|
||||
detail);
|
||||
}
|
||||
|
||||
@@ -160,9 +163,9 @@ internal sealed class SamplePluginRuntimeStateService
|
||||
{
|
||||
_backend = CreateEntry(
|
||||
"backend",
|
||||
"Backend",
|
||||
T("status.backend.title", "后端状态"),
|
||||
SamplePluginHealthState.Healthy,
|
||||
"Healthy",
|
||||
T("status.summary.healthy", "正常"),
|
||||
detail);
|
||||
}
|
||||
|
||||
@@ -175,9 +178,9 @@ internal sealed class SamplePluginRuntimeStateService
|
||||
{
|
||||
_backend = CreateEntry(
|
||||
"backend",
|
||||
"Backend",
|
||||
T("status.backend.title", "后端状态"),
|
||||
SamplePluginHealthState.Faulted,
|
||||
"Faulted",
|
||||
T("status.summary.faulted", "异常"),
|
||||
detail);
|
||||
}
|
||||
|
||||
@@ -191,10 +194,13 @@ internal sealed class SamplePluginRuntimeStateService
|
||||
_serviceClockTime = currentTime;
|
||||
_service = CreateEntry(
|
||||
"service",
|
||||
"Clock Service",
|
||||
T("status.service.title", "时钟服务"),
|
||||
SamplePluginHealthState.Healthy,
|
||||
"Healthy",
|
||||
$"Clock service is running. Current service time: {currentTime.LocalDateTime:HH:mm:ss}");
|
||||
T("status.summary.healthy", "正常"),
|
||||
Tf(
|
||||
"status.service.detail.running",
|
||||
"时钟服务运行中,当前服务时间:{0}",
|
||||
currentTime.LocalDateTime.ToString("HH:mm:ss")));
|
||||
}
|
||||
|
||||
PublishStateChanged("Clock service tick");
|
||||
@@ -206,9 +212,9 @@ internal sealed class SamplePluginRuntimeStateService
|
||||
{
|
||||
_service = CreateEntry(
|
||||
"service",
|
||||
"Clock Service",
|
||||
T("status.service.title", "时钟服务"),
|
||||
SamplePluginHealthState.Faulted,
|
||||
"Faulted",
|
||||
T("status.summary.faulted", "异常"),
|
||||
detail);
|
||||
}
|
||||
|
||||
@@ -291,32 +297,54 @@ internal sealed class SamplePluginRuntimeStateService
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
|
||||
var propertyNames = context.Properties.Count == 0
|
||||
? "(none)"
|
||||
? T("common.none", "(无)")
|
||||
: string.Join(", ", context.Properties.Keys.OrderBy(key => key, StringComparer.OrdinalIgnoreCase));
|
||||
|
||||
return
|
||||
[
|
||||
new SamplePluginCapabilityItem(
|
||||
"IPluginContext.Manifest",
|
||||
$"Readable. Current plugin id: {context.Manifest.Id}; version: {context.Manifest.Version ?? "dev"}."),
|
||||
T("capability.manifest.title", "IPluginContext.Manifest"),
|
||||
Tf(
|
||||
"capability.manifest.detail",
|
||||
"可读取。当前插件 id:{0};版本:{1}。",
|
||||
context.Manifest.Id,
|
||||
context.Manifest.Version ?? T("common.dev", "开发版"))),
|
||||
new SamplePluginCapabilityItem(
|
||||
"IPluginContext.PluginDirectory / DataDirectory",
|
||||
$"Readable. Plugin directory: {context.PluginDirectory}; data directory: {context.DataDirectory}."),
|
||||
T("capability.directories.title", "IPluginContext.PluginDirectory / DataDirectory"),
|
||||
Tf(
|
||||
"capability.directories.detail",
|
||||
"可读取。插件目录:{0};数据目录:{1}。",
|
||||
context.PluginDirectory,
|
||||
context.DataDirectory)),
|
||||
new SamplePluginCapabilityItem(
|
||||
"IPluginContext.Properties",
|
||||
$"Readable. Host properties currently exposed: {propertyNames}."),
|
||||
T("capability.properties.title", "IPluginContext.Properties"),
|
||||
Tf(
|
||||
"capability.properties.detail",
|
||||
"可读取。宿主当前暴露的属性:{0}。",
|
||||
propertyNames)),
|
||||
new SamplePluginCapabilityItem(
|
||||
"IPluginContext.GetService<T>()",
|
||||
$"Callable. State service resolved: {hasStateService}; clock service resolved: {hasClockService}; message bus resolved: {hasMessageBus}."),
|
||||
T("capability.get_service.title", "IPluginContext.GetService<T>()"),
|
||||
Tf(
|
||||
"capability.get_service.detail",
|
||||
"可调用。状态服务已解析:{0};时钟服务已解析:{1};消息总线已解析:{2}。",
|
||||
FormatBoolean(hasStateService),
|
||||
FormatBoolean(hasClockService),
|
||||
FormatBoolean(hasMessageBus))),
|
||||
new SamplePluginCapabilityItem(
|
||||
"IPluginContext.RegisterService<TService>()",
|
||||
"Callable during plugin initialization. This plugin registers SamplePluginRuntimeStateService and SamplePluginClockService into the plugin service container."),
|
||||
T("capability.register_service.title", "IPluginContext.RegisterService<TService>()"),
|
||||
T(
|
||||
"capability.register_service.detail",
|
||||
"可在插件初始化阶段调用。这个示例插件会把 SamplePluginRuntimeStateService 和 SamplePluginClockService 注册进插件服务容器。")),
|
||||
new SamplePluginCapabilityItem(
|
||||
"Plugin communication bus",
|
||||
"This plugin uses IPluginMessageBus to push clock ticks and state change notifications into plugin UI surfaces."),
|
||||
T("capability.message_bus.title", "插件通信总线"),
|
||||
T(
|
||||
"capability.message_bus.detail",
|
||||
"这个示例插件通过 IPluginMessageBus 向插件 UI 推送时钟心跳和状态变化通知。")),
|
||||
new SamplePluginCapabilityItem(
|
||||
"PluginDesktopComponentContext",
|
||||
"Widgets can read ComponentId, PlacementId, CellSize, and call GetService<T>() against the same plugin service container.")
|
||||
T("capability.widget_context.title", "PluginDesktopComponentContext"),
|
||||
T(
|
||||
"capability.widget_context.detail",
|
||||
"组件可以读取 ComponentId、PlacementId、CellSize,并能在同一个插件服务容器上调用 GetService<T>()。"))
|
||||
];
|
||||
}
|
||||
|
||||
@@ -335,10 +363,15 @@ internal sealed class SamplePluginRuntimeStateService
|
||||
{
|
||||
_component = CreateEntry(
|
||||
"component",
|
||||
"Component",
|
||||
T("status.component.title", "组件状态"),
|
||||
SamplePluginHealthState.Healthy,
|
||||
"Placed",
|
||||
$"Placed count: {placementIds.Length}; preview count: {previewCount}; placements: {string.Join(", ", placementIds)}");
|
||||
T("status.summary.placed", "已放置"),
|
||||
Tf(
|
||||
"status.component.detail.placed",
|
||||
"已放置数量:{0};预览数量:{1};放置位置:{2}",
|
||||
placementIds.Length,
|
||||
previewCount,
|
||||
string.Join(", ", placementIds)));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -346,19 +379,22 @@ internal sealed class SamplePluginRuntimeStateService
|
||||
{
|
||||
_component = CreateEntry(
|
||||
"component",
|
||||
"Component",
|
||||
T("status.component.title", "组件状态"),
|
||||
SamplePluginHealthState.Healthy,
|
||||
"Preview",
|
||||
$"Preview instances: {previewCount}; no placed desktop instance is active yet.");
|
||||
T("status.summary.preview", "预览中"),
|
||||
Tf(
|
||||
"status.component.detail.preview",
|
||||
"当前预览实例数量:{0};尚未有已放置的桌面实例。",
|
||||
previewCount));
|
||||
return;
|
||||
}
|
||||
|
||||
_component = CreateEntry(
|
||||
"component",
|
||||
"Component",
|
||||
T("status.component.title", "组件状态"),
|
||||
SamplePluginHealthState.Pending,
|
||||
"Pending",
|
||||
"No component instance is active.");
|
||||
T("status.summary.pending", "等待中"),
|
||||
T("status.component.detail.none", "当前没有活动中的组件实例。"));
|
||||
}
|
||||
|
||||
private void PublishStateChanged(string reason)
|
||||
@@ -381,6 +417,23 @@ internal sealed class SamplePluginRuntimeStateService
|
||||
detail,
|
||||
DateTimeOffset.Now);
|
||||
}
|
||||
|
||||
private string T(string key, string fallback)
|
||||
{
|
||||
return _localizer.GetString(key, fallback);
|
||||
}
|
||||
|
||||
private string Tf(string key, string fallback, params object[] args)
|
||||
{
|
||||
return _localizer.Format(key, fallback, args);
|
||||
}
|
||||
|
||||
private string FormatBoolean(bool value)
|
||||
{
|
||||
return value
|
||||
? T("common.true", "是")
|
||||
: T("common.false", "否");
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class SamplePluginClockService : IDisposable
|
||||
@@ -389,6 +442,7 @@ internal sealed class SamplePluginClockService : IDisposable
|
||||
private readonly string _clockStateFilePath;
|
||||
private readonly SamplePluginRuntimeStateService _stateService;
|
||||
private readonly IPluginMessageBus _messageBus;
|
||||
private readonly PluginLocalizer _localizer;
|
||||
private readonly Timer _timer;
|
||||
private DateTimeOffset _currentTime = DateTimeOffset.Now;
|
||||
private int _disposed;
|
||||
@@ -396,11 +450,13 @@ internal sealed class SamplePluginClockService : IDisposable
|
||||
public SamplePluginClockService(
|
||||
string dataDirectory,
|
||||
SamplePluginRuntimeStateService stateService,
|
||||
IPluginMessageBus messageBus)
|
||||
IPluginMessageBus messageBus,
|
||||
PluginLocalizer localizer)
|
||||
{
|
||||
_clockStateFilePath = Path.Combine(dataDirectory, "clock-service.txt");
|
||||
_stateService = stateService;
|
||||
_messageBus = messageBus;
|
||||
_localizer = localizer;
|
||||
_timer = new Timer(OnTimerTick);
|
||||
}
|
||||
|
||||
@@ -459,7 +515,10 @@ internal sealed class SamplePluginClockService : IDisposable
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_stateService.MarkClockServiceFaulted($"Clock state write failed: {ex.Message}");
|
||||
_stateService.MarkClockServiceFaulted(_localizer.Format(
|
||||
"status.service.detail.write_failed",
|
||||
"时钟状态写入失败:{0}",
|
||||
ex.Message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace LanMountainDesktop.SamplePlugin;
|
||||
internal sealed class SamplePluginSettingsView : UserControl
|
||||
{
|
||||
private readonly IPluginContext _context;
|
||||
private readonly PluginLocalizer _localizer;
|
||||
private readonly SamplePluginRuntimeStateService _stateService;
|
||||
private readonly SamplePluginClockService _clockService;
|
||||
private readonly IPluginMessageBus _messageBus;
|
||||
@@ -21,6 +22,7 @@ internal sealed class SamplePluginSettingsView : UserControl
|
||||
public SamplePluginSettingsView(IPluginContext context)
|
||||
{
|
||||
_context = context;
|
||||
_localizer = PluginLocalizer.Create(context);
|
||||
_stateService = context.GetService<SamplePluginRuntimeStateService>()
|
||||
?? throw new InvalidOperationException("SamplePluginRuntimeStateService is not available.");
|
||||
_clockService = context.GetService<SamplePluginClockService>()
|
||||
@@ -28,7 +30,9 @@ internal sealed class SamplePluginSettingsView : UserControl
|
||||
_messageBus = context.GetService<IPluginMessageBus>()
|
||||
?? throw new InvalidOperationException("IPluginMessageBus is not available.");
|
||||
|
||||
_stateService.MarkFrontendReady("Settings page is connected to plugin services and communication.");
|
||||
_stateService.MarkFrontendReady(T(
|
||||
"status.frontend.detail.settings_connected",
|
||||
"设置页已接入插件服务与通信。"));
|
||||
|
||||
AttachedToVisualTree += OnAttachedToVisualTree;
|
||||
DetachedFromVisualTree += OnDetachedFromVisualTree;
|
||||
@@ -56,14 +60,14 @@ internal sealed class SamplePluginSettingsView : UserControl
|
||||
{
|
||||
new TextBlock
|
||||
{
|
||||
Text = "Sample Plugin Capability Inspector",
|
||||
Text = T("settings.header.title", "示例插件能力检查器"),
|
||||
FontSize = 22,
|
||||
FontWeight = FontWeight.SemiBold,
|
||||
Foreground = Brushes.White
|
||||
},
|
||||
CreateSection("Plugin Info", _pluginInfoPanel),
|
||||
CreateSection("Accessible Capabilities", _capabilityPanel),
|
||||
CreateSection("Live Runtime Status", _statusPanel)
|
||||
CreateSection(T("settings.section.info", "插件信息"), _pluginInfoPanel),
|
||||
CreateSection(T("settings.section.capabilities", "可访问能力"), _capabilityPanel),
|
||||
CreateSection(T("settings.section.status", "实时运行状态"), _statusPanel)
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -112,31 +116,45 @@ internal sealed class SamplePluginSettingsView : UserControl
|
||||
private void RefreshPluginInfo(SamplePluginRuntimeSnapshot snapshot)
|
||||
{
|
||||
_pluginInfoPanel.Children.Clear();
|
||||
_pluginInfoPanel.Children.Add(CreateInfoLine("Plugin Name", snapshot.Manifest.Name));
|
||||
_pluginInfoPanel.Children.Add(CreateInfoLine("Plugin Id", snapshot.Manifest.Id));
|
||||
_pluginInfoPanel.Children.Add(CreateInfoLine("Version", snapshot.Manifest.Version ?? "dev"));
|
||||
_pluginInfoPanel.Children.Add(CreateInfoLine("Author", snapshot.Manifest.Author ?? "(none)"));
|
||||
_pluginInfoPanel.Children.Add(CreateInfoLine("Description", snapshot.Manifest.Description ?? "(none)"));
|
||||
_pluginInfoPanel.Children.Add(CreateInfoLine("Plugin Directory", snapshot.PluginDirectory));
|
||||
_pluginInfoPanel.Children.Add(CreateInfoLine("Data Directory", snapshot.DataDirectory));
|
||||
_pluginInfoPanel.Children.Add(CreateInfoLine("Host Application", snapshot.HostApplicationName));
|
||||
_pluginInfoPanel.Children.Add(CreateInfoLine("Host Version", snapshot.HostVersion));
|
||||
_pluginInfoPanel.Children.Add(CreateInfoLine("SDK API Version", snapshot.SdkApiVersion));
|
||||
_pluginInfoPanel.Children.Add(CreateInfoLine("State Service Resolved", (_context.GetService<SamplePluginRuntimeStateService>() is not null).ToString()));
|
||||
_pluginInfoPanel.Children.Add(CreateInfoLine("Clock Service Resolved", (_context.GetService<SamplePluginClockService>() is not null).ToString()));
|
||||
_pluginInfoPanel.Children.Add(CreateInfoLine("Message Bus Resolved", (_context.GetService<IPluginMessageBus>() is not null).ToString()));
|
||||
_pluginInfoPanel.Children.Add(CreateInfoLine("Component Placed", snapshot.HasPlacedComponent ? "Yes" : "No"));
|
||||
_pluginInfoPanel.Children.Add(CreateInfoLine("Placed Count", snapshot.PlacedCount.ToString()));
|
||||
_pluginInfoPanel.Children.Add(CreateInfoLine("Preview Count", snapshot.PreviewCount.ToString()));
|
||||
_pluginInfoPanel.Children.Add(CreateInfoLine(
|
||||
"Placement Ids",
|
||||
snapshot.PlacementIds.Count == 0 ? "(none)" : string.Join(", ", snapshot.PlacementIds)));
|
||||
_pluginInfoPanel.Children.Add(CreateInfoLine("Last Component Id", snapshot.LastComponentId ?? "(none)"));
|
||||
T("settings.info.plugin_name", "插件名称"),
|
||||
T("plugin.name", snapshot.Manifest.Name)));
|
||||
_pluginInfoPanel.Children.Add(CreateInfoLine(T("settings.info.plugin_id", "插件 Id"), snapshot.Manifest.Id));
|
||||
_pluginInfoPanel.Children.Add(CreateInfoLine(T("settings.info.version", "版本"), snapshot.Manifest.Version ?? T("common.dev", "开发版")));
|
||||
_pluginInfoPanel.Children.Add(CreateInfoLine(T("settings.info.author", "作者"), snapshot.Manifest.Author ?? T("common.none", "(无)")));
|
||||
_pluginInfoPanel.Children.Add(CreateInfoLine(
|
||||
"Last Cell Size",
|
||||
snapshot.LastCellSize > 0 ? $"{snapshot.LastCellSize:F0}px" : "(unknown)"));
|
||||
T("settings.info.description", "描述"),
|
||||
T("plugin.description", snapshot.Manifest.Description ?? T("common.none", "(无)"))));
|
||||
_pluginInfoPanel.Children.Add(CreateInfoLine(T("settings.info.plugin_directory", "插件目录"), snapshot.PluginDirectory));
|
||||
_pluginInfoPanel.Children.Add(CreateInfoLine(T("settings.info.data_directory", "数据目录"), snapshot.DataDirectory));
|
||||
_pluginInfoPanel.Children.Add(CreateInfoLine(T("settings.info.host_application", "宿主应用"), snapshot.HostApplicationName));
|
||||
_pluginInfoPanel.Children.Add(CreateInfoLine(T("settings.info.host_version", "宿主版本"), snapshot.HostVersion));
|
||||
_pluginInfoPanel.Children.Add(CreateInfoLine(T("settings.info.sdk_api_version", "SDK API 版本"), snapshot.SdkApiVersion));
|
||||
_pluginInfoPanel.Children.Add(CreateInfoLine(
|
||||
"Clock Service Time",
|
||||
T("settings.info.state_service_resolved", "状态服务已解析"),
|
||||
FormatBoolean(_context.GetService<SamplePluginRuntimeStateService>() is not null)));
|
||||
_pluginInfoPanel.Children.Add(CreateInfoLine(
|
||||
T("settings.info.clock_service_resolved", "时钟服务已解析"),
|
||||
FormatBoolean(_context.GetService<SamplePluginClockService>() is not null)));
|
||||
_pluginInfoPanel.Children.Add(CreateInfoLine(
|
||||
T("settings.info.message_bus_resolved", "消息总线已解析"),
|
||||
FormatBoolean(_context.GetService<IPluginMessageBus>() is not null)));
|
||||
_pluginInfoPanel.Children.Add(CreateInfoLine(
|
||||
T("settings.info.component_placed", "组件是否已放置"),
|
||||
snapshot.HasPlacedComponent ? T("common.yes", "是") : T("common.no", "否")));
|
||||
_pluginInfoPanel.Children.Add(CreateInfoLine(T("settings.info.placed_count", "已放置数量"), snapshot.PlacedCount.ToString()));
|
||||
_pluginInfoPanel.Children.Add(CreateInfoLine(T("settings.info.preview_count", "预览数量"), snapshot.PreviewCount.ToString()));
|
||||
_pluginInfoPanel.Children.Add(CreateInfoLine(
|
||||
T("settings.info.placement_ids", "放置位置 Id"),
|
||||
snapshot.PlacementIds.Count == 0 ? T("common.none", "(无)") : string.Join(", ", snapshot.PlacementIds)));
|
||||
_pluginInfoPanel.Children.Add(CreateInfoLine(
|
||||
T("settings.info.last_component_id", "最近组件 Id"),
|
||||
snapshot.LastComponentId ?? T("common.none", "(无)")));
|
||||
_pluginInfoPanel.Children.Add(CreateInfoLine(
|
||||
T("settings.info.last_cell_size", "最近单元尺寸"),
|
||||
snapshot.LastCellSize > 0 ? $"{snapshot.LastCellSize:F0}px" : T("common.unknown", "(未知)")));
|
||||
_pluginInfoPanel.Children.Add(CreateInfoLine(
|
||||
T("settings.info.clock_service_time", "时钟服务时间"),
|
||||
_clockService.CurrentTime.LocalDateTime.ToString("HH:mm:ss")));
|
||||
}
|
||||
|
||||
@@ -183,7 +201,7 @@ internal sealed class SamplePluginSettingsView : UserControl
|
||||
},
|
||||
new TextBlock
|
||||
{
|
||||
Text = $"Updated: {entry.UpdatedAt.LocalDateTime:HH:mm:ss}",
|
||||
Text = Tf("settings.status.updated_at", "更新时间:{0}", entry.UpdatedAt.LocalDateTime.ToString("HH:mm:ss")),
|
||||
Foreground = new SolidColorBrush(Color.Parse("#FF93C5FD"))
|
||||
}
|
||||
}
|
||||
@@ -336,4 +354,21 @@ internal sealed class SamplePluginSettingsView : UserControl
|
||||
Color.Parse("#FBBF24"))
|
||||
};
|
||||
}
|
||||
|
||||
private string T(string key, string fallback)
|
||||
{
|
||||
return _localizer.GetString(key, fallback);
|
||||
}
|
||||
|
||||
private string Tf(string key, string fallback, params object[] args)
|
||||
{
|
||||
return _localizer.Format(key, fallback, args);
|
||||
}
|
||||
|
||||
private string FormatBoolean(bool value)
|
||||
{
|
||||
return value
|
||||
? T("common.true", "是")
|
||||
: T("common.false", "否");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace LanMountainDesktop.SamplePlugin;
|
||||
internal sealed class SamplePluginStatusClockWidget : Border
|
||||
{
|
||||
private readonly PluginDesktopComponentContext _context;
|
||||
private readonly PluginLocalizer _localizer;
|
||||
private readonly SamplePluginRuntimeStateService _stateService;
|
||||
private readonly SamplePluginClockService _clockService;
|
||||
private readonly IPluginMessageBus _messageBus;
|
||||
@@ -23,6 +24,7 @@ internal sealed class SamplePluginStatusClockWidget : Border
|
||||
public SamplePluginStatusClockWidget(PluginDesktopComponentContext context)
|
||||
{
|
||||
_context = context;
|
||||
_localizer = PluginLocalizer.Create(context);
|
||||
_stateService = context.GetService<SamplePluginRuntimeStateService>()
|
||||
?? throw new InvalidOperationException("SamplePluginRuntimeStateService is not available.");
|
||||
_clockService = context.GetService<SamplePluginClockService>()
|
||||
@@ -111,7 +113,9 @@ internal sealed class SamplePluginStatusClockWidget : Border
|
||||
_context.CellSize);
|
||||
}
|
||||
|
||||
_stateService.MarkFrontendReady("Widget surface is connected to plugin services and communication.");
|
||||
_stateService.MarkFrontendReady(T(
|
||||
"status.frontend.detail.widget_connected",
|
||||
"组件界面已接入插件服务与通信。"));
|
||||
SubscribeToPluginBus();
|
||||
|
||||
RefreshClock(_clockService.CurrentTime);
|
||||
@@ -170,8 +174,8 @@ internal sealed class SamplePluginStatusClockWidget : Border
|
||||
{
|
||||
var snapshot = _stateService.GetSnapshot();
|
||||
_subtitleTextBlock.Text = string.IsNullOrWhiteSpace(_context.PlacementId)
|
||||
? $"Preview surface | placed: {snapshot.PlacedCount}"
|
||||
: $"Placement {_context.PlacementId} | placed: {snapshot.PlacedCount}";
|
||||
? Tf("widget.subtitle.preview", "预览界面 | 已放置:{0}", snapshot.PlacedCount)
|
||||
: Tf("widget.subtitle.placement", "位置 {0} | 已放置:{1}", _context.PlacementId!, snapshot.PlacedCount);
|
||||
}
|
||||
|
||||
private void RefreshStatusPanel()
|
||||
@@ -281,4 +285,14 @@ internal sealed class SamplePluginStatusClockWidget : Border
|
||||
Color.Parse("#FDBA74"))
|
||||
};
|
||||
}
|
||||
|
||||
private string T(string key, string fallback)
|
||||
{
|
||||
return _localizer.GetString(key, fallback);
|
||||
}
|
||||
|
||||
private string Tf(string key, string fallback, params object[] args)
|
||||
{
|
||||
return _localizer.Format(key, fallback, args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,11 +48,14 @@ public sealed class PluginRuntimeService : IDisposable
|
||||
UnloadInstalledPlugins();
|
||||
|
||||
var disabledPluginIds = GetDisabledPluginIds();
|
||||
var settingsSnapshot = _appSettingsService.Load();
|
||||
var hostLanguageCode = PluginLocalizer.NormalizeLanguageCode(settingsSnapshot.LanguageCode);
|
||||
var hostProperties = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["HostApplicationName"] = "LanMountainDesktop",
|
||||
["HostVersion"] = typeof(App).Assembly.GetName().Version?.ToString(),
|
||||
["PluginSdkApiVersion"] = PluginSdkInfo.ApiVersion
|
||||
[PluginHostPropertyKeys.HostApplicationName] = "LanMountainDesktop",
|
||||
[PluginHostPropertyKeys.HostVersion] = typeof(App).Assembly.GetName().Version?.ToString(),
|
||||
[PluginHostPropertyKeys.PluginSdkApiVersion] = PluginSdkInfo.ApiVersion,
|
||||
[PluginHostPropertyKeys.HostLanguageCode] = hostLanguageCode
|
||||
};
|
||||
|
||||
var discoveryFailures = new List<PluginLoadResult>();
|
||||
|
||||
Reference in New Issue
Block a user