Skip to content

Commit b925533

Browse files
committed
add navigation bar & add sample plugin
1 parent 91e979e commit b925533

File tree

59 files changed

+2336
-13
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+2336
-13
lines changed

Modulus.sln

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Modulus.PluginHost.Tests",
1717
EndProject
1818
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
1919
EndProject
20+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{D1B4CF99-7947-DD6C-81D0-5B5B3989814C}"
21+
EndProject
22+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimplePlugin", "src\samples\SimplePlugin\SimplePlugin.csproj", "{253D6931-730D-463D-A157-C5471278C785}"
23+
EndProject
2024
Global
2125
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2226
Debug|Any CPU = Debug|Any CPU
@@ -85,6 +89,18 @@ Global
8589
{C6AD3F4F-375C-DDF7-D6DF-F2132CC9F553}.Release|x64.Build.0 = Release|Any CPU
8690
{C6AD3F4F-375C-DDF7-D6DF-F2132CC9F553}.Release|x86.ActiveCfg = Release|Any CPU
8791
{C6AD3F4F-375C-DDF7-D6DF-F2132CC9F553}.Release|x86.Build.0 = Release|Any CPU
92+
{253D6931-730D-463D-A157-C5471278C785}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
93+
{253D6931-730D-463D-A157-C5471278C785}.Debug|Any CPU.Build.0 = Debug|Any CPU
94+
{253D6931-730D-463D-A157-C5471278C785}.Debug|x64.ActiveCfg = Debug|Any CPU
95+
{253D6931-730D-463D-A157-C5471278C785}.Debug|x64.Build.0 = Debug|Any CPU
96+
{253D6931-730D-463D-A157-C5471278C785}.Debug|x86.ActiveCfg = Debug|Any CPU
97+
{253D6931-730D-463D-A157-C5471278C785}.Debug|x86.Build.0 = Debug|Any CPU
98+
{253D6931-730D-463D-A157-C5471278C785}.Release|Any CPU.ActiveCfg = Release|Any CPU
99+
{253D6931-730D-463D-A157-C5471278C785}.Release|Any CPU.Build.0 = Release|Any CPU
100+
{253D6931-730D-463D-A157-C5471278C785}.Release|x64.ActiveCfg = Release|Any CPU
101+
{253D6931-730D-463D-A157-C5471278C785}.Release|x64.Build.0 = Release|Any CPU
102+
{253D6931-730D-463D-A157-C5471278C785}.Release|x86.ActiveCfg = Release|Any CPU
103+
{253D6931-730D-463D-A157-C5471278C785}.Release|x86.Build.0 = Release|Any CPU
88104
EndGlobalSection
89105
GlobalSection(SolutionProperties) = preSolution
90106
HideSolutionNode = FALSE
@@ -95,5 +111,7 @@ Global
95111
{5E969E96-57A7-B6D8-7475-5E543D716563} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
96112
{C6AD3F4F-375C-DDF7-D6DF-F2132CC9F553} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
97113
{02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
114+
{D1B4CF99-7947-DD6C-81D0-5B5B3989814C} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
115+
{253D6931-730D-463D-A157-C5471278C785} = {D1B4CF99-7947-DD6C-81D0-5B5B3989814C}
98116
EndGlobalSection
99117
EndGlobal

docs/Images/PluginManger.png

1.62 MB
Loading

docs/Images/dashboard.png

1.55 MB
Loading

docs/Images/layout.png

1.56 MB
Loading
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
# Solution Design: Plugin Contract and Foundation (Story 004)
2+
3+
## Overview
4+
This document summarizes the recommended implementation order, rationale, and testing strategies for Story 004: "Define a unified plugin contract and foundation (DI, config, localization, logging) for safe, consistent plugin integration."
5+
6+
## Recommended Task Order
7+
8+
1. **Create Modulus.Plugin.Abstractions Project**
9+
- Define interfaces:
10+
- `IPlugin`: Main plugin contract, including metadata, DI, initialization, and UI extension points.
11+
- `IPluginMeta`: Metadata contract (Name, Version, Description, Author, Dependencies).
12+
- `ILocalizer`: Localization contract (resource access, language switching).
13+
- (Optional) `IPluginSettings`: For plugin-specific configuration.
14+
- Example interface definitions:
15+
16+
```csharp
17+
public interface IPlugin
18+
{
19+
IPluginMeta Meta { get; }
20+
void ConfigureServices(IServiceCollection services, IConfiguration configuration);
21+
void Initialize(IServiceProvider provider);
22+
object? GetMainView(); // For UI plugins, e.g., Avalonia Control
23+
object? GetMenu(); // For menu extension
24+
}
25+
26+
public interface IPluginMeta
27+
{
28+
string Name { get; }
29+
string Version { get; }
30+
string Description { get; }
31+
string Author { get; }
32+
string[]? Dependencies { get; }
33+
string ContractVersion { get; } // Required: plugin contract version
34+
}
35+
36+
public interface ILocalizer
37+
{
38+
string this[string key] { get; }
39+
string CurrentLanguage { get; }
40+
void SetLanguage(string lang);
41+
IEnumerable<string> SupportedLanguages { get; }
42+
}
43+
44+
public interface IPluginSettings
45+
{
46+
IConfiguration Configuration { get; }
47+
}
48+
```
49+
50+
2. **Update Plugin Template and Main App to Reference SDK**
51+
- Add project/package reference to `Modulus.Plugin.Abstractions` in both plugin template and main app.
52+
- Ensure all plugin entry points implement `IPlugin`.
53+
54+
3. **Standardize Plugin Directory Structure and Metadata**
55+
- Each plugin in its own subdirectory:
56+
- `PluginA/PluginA.dll`
57+
- `PluginA/pluginsettings.json`
58+
- `PluginA/lang.en.json`, `PluginA/lang.zh.json`, ...
59+
- Example `pluginsettings.json`:
60+
```json
61+
{
62+
"ContractVersion": "2.0.0",
63+
"SettingA": "value",
64+
"SettingB": 123
65+
}
66+
```
67+
- Example `lang.en.json`:
68+
```json
69+
{
70+
"Hello": "Hello",
71+
"Exit": "Exit"
72+
}
73+
```
74+
75+
4. **Upgrade PluginLoader to Only Load IPlugin Implementations**
76+
- Use reflection to ensure only assemblies with `IPlugin` implementations are loaded.
77+
- Example:
78+
```csharp
79+
var asm = context.LoadFromAssemblyPath(pluginPath);
80+
var pluginType = asm.GetTypes().FirstOrDefault(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsAbstract);
81+
if (pluginType == null) throw new InvalidOperationException("No valid IPlugin implementation found.");
82+
```
83+
84+
5. **Implement DI, Config, and Logging Support**
85+
- `IPlugin.ConfigureServices` for service registration.
86+
- `IPlugin.Initialize` for runtime setup with `IServiceProvider`.
87+
- Plugins access `IConfiguration` and `ILogger<T>` via DI.
88+
- Example usage in plugin:
89+
```csharp
90+
public void ConfigureServices(IServiceCollection services, IConfiguration configuration)
91+
{
92+
services.AddSingleton<IMyService, MyService>();
93+
}
94+
public void Initialize(IServiceProvider provider)
95+
{
96+
var logger = provider.GetRequiredService<ILogger<MyPlugin>>();
97+
logger.LogInformation("Plugin initialized");
98+
}
99+
```
100+
101+
6. **Documentation and Example Plugins**
102+
- Write English interface docs and sample plugin code.
103+
- Example README snippet for plugin authors:
104+
```markdown
105+
## Plugin Contract
106+
- Implement `IPlugin` from Modulus.Plugin.Abstractions
107+
- Provide metadata, DI, and (optionally) UI extension points
108+
- Place your plugin DLL and config/localization files in a dedicated subdirectory
109+
```
110+
111+
## Version Compatibility and Error Handling
112+
113+
### Problem
114+
When the plugin contract (SDK) version changes, there are two main incompatibility scenarios:
115+
1. **Plugin is too old**: The plugin's contract version is lower than the minimum required by the host application.
116+
2. **Host is too old**: The plugin requires a newer contract version than the host supports.
117+
118+
### Solution
119+
- **Contract Version Declaration**: Both the host and each plugin declare their supported/required contract version (e.g., `ContractVersion` in `IPluginMeta` and a constant in the host).
120+
- **Compatibility Check**: When loading a plugin, the host compares its supported contract version with the plugin's declared version.
121+
- **Error Handling**:
122+
- If the plugin is too old (plugin version < host minimum):
123+
- Do not load the plugin.
124+
- Show/log a user-friendly message: `The plugin "{Name}" is not compatible with this version of Modulus. Please contact the plugin developer to update the plugin.`
125+
- If the host is too old (plugin version > host maximum):
126+
- Do not load the plugin.
127+
- Show/log a user-friendly message: `The plugin "{Name}" requires a newer version of Modulus. Please update the application to use this plugin.`
128+
- For all other contract mismatches, provide a clear error message and do not throw raw exceptions to the user.
129+
130+
#### Example Implementation
131+
```csharp
132+
const string HostContractVersion = "2.0.0";
133+
const string HostMinSupportedVersion = "1.0.0";
134+
135+
public object? RunPluginWithContractCheck(string pluginPath)
136+
{
137+
var meta = ReadMeta(pluginPath);
138+
if (meta == null)
139+
throw new InvalidOperationException("Plugin metadata not found.");
140+
141+
Version pluginVer = new Version(meta.ContractVersion);
142+
Version hostVer = new Version(HostContractVersion);
143+
Version hostMin = new Version(HostMinSupportedVersion);
144+
145+
if (pluginVer < hostMin)
146+
throw new PluginContractException($"The plugin '{meta.Name}' is too old and not compatible with this version of Modulus. Please contact the plugin developer to update the plugin.");
147+
if (pluginVer > hostVer)
148+
throw new PluginContractException($"The plugin '{meta.Name}' requires a newer version of Modulus. Please update the application to use this plugin.");
149+
150+
// ...existing plugin loading logic...
151+
}
152+
```
153+
154+
- **UI/Log Integration**: Catch `PluginContractException` and display/log the message to the user, not a raw stack trace.
155+
- **Documentation**: Clearly document the contract versioning policy for both plugin and host developers.
156+
157+
## Testing Strategy
158+
- **Contract Tests**: Use reflection/mocks to ensure only `IPlugin` implementations are loaded.
159+
- **DI/Config/Logging**: Integration tests to verify plugins can register/resolve services, read config, and log.
160+
- **Localization**: Test plugins can load and switch language resources via `ILocalizer`.
161+
- **End-to-End**: Load multiple plugins, verify isolation, config, and logging.
162+
163+
## File/Project Organization (Recommended)
164+
- `src/Modulus.Plugin.Abstractions/` (new project, all contracts/interfaces)
165+
- `src/Modulus.PluginHost/` (host, references Abstractions)
166+
- `src/Modulus.App/` (main app, references Abstractions)
167+
- `tools/modulus-plugin/` (plugin template, references Abstractions)
168+
- Each plugin: its own subdirectory with `dll`, `pluginsettings.json`, `lang.xx.json`, etc.
169+
170+
## Notes
171+
- All code comments and README files must be in English.
172+
- Story/requirement docs can remain in Chinese for team communication.
173+
- This solution is flexible: teams can adjust file/project layout as long as contract and testability are preserved.
174+
175+
---
176+
177+
**This document is intended as a reference for all team members implementing or extending the plugin system foundation.**

src/Modulus.App/App.axaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,6 @@
77
<Application.Styles>
88
<FluentTheme />
99
</Application.Styles>
10+
11+
1012
</Application>
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<UserControl xmlns="https://github.com/avaloniaui"
2+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
3+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
4+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
5+
xmlns:vm="clr-namespace:Modulus.App.Controls.ViewModels"
6+
xmlns:cv="clr-namespace:Modulus.App.Converters"
7+
mc:Ignorable="d" d:DesignWidth="80" d:DesignHeight="800"
8+
x:DataType="vm:NavigationBarViewModel"
9+
x:Class="Modulus.App.Controls.NavigationBar">
10+
<Design.DataContext>
11+
<vm:NavigationBarViewModel />
12+
</Design.DataContext>
13+
<UserControl.Resources>
14+
<cv:NavHighlightConverter x:Key="NavHighlightConverter" />
15+
</UserControl.Resources>
16+
<DockPanel VerticalAlignment="Stretch" HorizontalAlignment="Center" Width="80">
17+
<!-- Header -->
18+
<StackPanel DockPanel.Dock="Top" Spacing="8" Margin="0,16,0,0" HorizontalAlignment="Center">
19+
<Image Source="avares://Modulus.App/Assets/avalonia-logo.ico" Width="32" Height="32" Stretch="Uniform"/>
20+
<TextBlock Text="Modulus" FontSize="16" FontWeight="Bold" Foreground="#3B82F6" HorizontalAlignment="Center"/>
21+
<TextBlock Text="v1.0.0" FontSize="11" Foreground="#888" HorizontalAlignment="Center"/>
22+
<!-- Header Items -->
23+
<ItemsControl ItemsSource="{Binding HeaderItems}">
24+
<ItemsControl.ItemTemplate>
25+
<DataTemplate x:DataType="vm:NavigationMenuItemViewModel">
26+
<Button Background="{Binding IsActive, Converter={StaticResource NavHighlightConverter}}"
27+
Foreground="White"
28+
ToolTip.Tip="{Binding Tooltip}"
29+
Width="{Binding Width}"
30+
Height="{Binding Height}"
31+
CornerRadius="{Binding CornerRadius}"
32+
Command="{Binding Command}"
33+
Margin="0,4,0,4">
34+
<StackPanel>
35+
<TextBlock Text="{Binding Icon}"
36+
FontFamily="Segoe MDL2 Assets"
37+
FontSize="{Binding FontSize}"
38+
HorizontalAlignment="Center"/>
39+
</StackPanel>
40+
</Button>
41+
</DataTemplate>
42+
</ItemsControl.ItemTemplate>
43+
</ItemsControl>
44+
</StackPanel>
45+
46+
<!-- Body: Main Menu Items -->
47+
<StackPanel DockPanel.Dock="Top" Spacing="8" Margin="0,32,0,0" HorizontalAlignment="Center">
48+
<ItemsControl ItemsSource="{Binding BodyItems}">
49+
<ItemsControl.ItemTemplate>
50+
<DataTemplate x:DataType="vm:NavigationMenuItemViewModel">
51+
<Button Background="{Binding IsActive, Converter={StaticResource NavHighlightConverter}}"
52+
Foreground="White"
53+
ToolTip.Tip="{Binding Tooltip}"
54+
Width="{Binding Width}"
55+
Height="{Binding Height}"
56+
CornerRadius="{Binding CornerRadius}"
57+
Command="{Binding Command}"
58+
Margin="0,0,0,4">
59+
<StackPanel>
60+
<TextBlock Text="{Binding Icon}"
61+
FontFamily="Segoe MDL2 Assets"
62+
FontSize="{Binding FontSize}"
63+
HorizontalAlignment="Center"/>
64+
</StackPanel>
65+
</Button>
66+
</DataTemplate>
67+
</ItemsControl.ItemTemplate>
68+
</ItemsControl>
69+
</StackPanel>
70+
71+
<!-- Footer: Settings/User/Feedback etc. -->
72+
<StackPanel DockPanel.Dock="Bottom" Spacing="8" Margin="0,0,0,24" HorizontalAlignment="Center">
73+
<ItemsControl ItemsSource="{Binding FooterItems}">
74+
<ItemsControl.ItemTemplate>
75+
<DataTemplate x:DataType="vm:NavigationMenuItemViewModel">
76+
<Button Background="{Binding IsActive, Converter={StaticResource NavHighlightConverter}}"
77+
Foreground="White"
78+
ToolTip.Tip="{Binding Tooltip}"
79+
Width="{Binding Width}"
80+
Height="{Binding Height}"
81+
CornerRadius="{Binding CornerRadius}"
82+
Command="{Binding Command}"
83+
Margin="0,0,0,4">
84+
<StackPanel>
85+
<TextBlock Text="{Binding Icon}"
86+
FontFamily="Segoe MDL2 Assets"
87+
FontSize="{Binding FontSize}"
88+
HorizontalAlignment="Center"/>
89+
</StackPanel>
90+
</Button>
91+
</DataTemplate>
92+
</ItemsControl.ItemTemplate>
93+
</ItemsControl>
94+
</StackPanel>
95+
</DockPanel>
96+
</UserControl>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using Avalonia;
2+
using Avalonia.Controls;
3+
using Avalonia.Markup.Xaml;
4+
5+
namespace Modulus.App.Controls
6+
{
7+
public partial class NavigationBar : UserControl
8+
{
9+
public NavigationBar()
10+
{
11+
InitializeComponent();
12+
}
13+
14+
private void InitializeComponent()
15+
{
16+
AvaloniaXamlLoader.Load(this);
17+
}
18+
}
19+
}

0 commit comments

Comments
 (0)