Compare commits

...

6 commits

Author SHA1 Message Date
DHR60
8fe411744b
Merge 4301415b4c into 03d5b7a05b 2025-09-26 18:25:05 +08:00
2dust
03d5b7a05b Bug fix
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
2025-09-26 17:11:48 +08:00
2dust
a652fd879b Added simple highlight function to the message view 2025-09-26 15:29:46 +08:00
2dust
326bf334e7 Optimize and improve MsgView 2025-09-26 15:07:33 +08:00
JieXu
21a773f400
Update MsgView.axaml.cs Plan C (#8035)
* Add avaloniaEdit for test

* Adjust avaloniaEdit

* Optimize and improve message function

* Update build-linux.yml

* Update MsgView.axaml

* Update MsgView.axaml.cs

---------

Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
2025-09-26 13:55:35 +08:00
DHR60
4301415b4c Add rule type selection to routing rules 2025-09-23 17:59:18 +08:00
21 changed files with 371 additions and 107 deletions

View file

@ -22,7 +22,7 @@ jobs:
matrix: matrix:
configuration: [Release] configuration: [Release]
runs-on: ubuntu-22.04 runs-on: ubuntu-24.04
steps: steps:
- name: Checkout - name: Checkout

View file

@ -5,6 +5,7 @@
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled> <CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.3.0" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.6" /> <PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.6" />
<PackageVersion Include="Avalonia.Desktop" Version="11.3.6" /> <PackageVersion Include="Avalonia.Desktop" Version="11.3.6" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.6" /> <PackageVersion Include="Avalonia.Diagnostics" Version="11.3.6" />
@ -19,6 +20,7 @@
<PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" /> <PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" />
<PackageVersion Include="ReactiveUI.WPF" Version="20.4.1" /> <PackageVersion Include="ReactiveUI.WPF" Version="20.4.1" />
<PackageVersion Include="Semi.Avalonia" Version="11.2.1.10" /> <PackageVersion Include="Semi.Avalonia" Version="11.2.1.10" />
<PackageVersion Include="Semi.Avalonia.AvaloniaEdit" Version="11.2.0.1" />
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.2.1.10" /> <PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.2.1.10" />
<PackageVersion Include="NLog" Version="6.0.4" /> <PackageVersion Include="NLog" Version="6.0.4" />
<PackageVersion Include="sqlite-net-pcl" Version="1.9.172" /> <PackageVersion Include="sqlite-net-pcl" Version="1.9.172" />
@ -27,4 +29,4 @@
<PackageVersion Include="YamlDotNet" Version="16.3.0" /> <PackageVersion Include="YamlDotNet" Version="16.3.0" />
<PackageVersion Include="ZXing.Net.Bindings.SkiaSharp" Version="0.16.14" /> <PackageVersion Include="ZXing.Net.Bindings.SkiaSharp" Version="0.16.14" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -87,6 +87,8 @@ public class Global
public const string SingboxFinalResolverTag = "final_resolver"; public const string SingboxFinalResolverTag = "final_resolver";
public const string SingboxHostsDNSTag = "hosts_dns"; public const string SingboxHostsDNSTag = "hosts_dns";
public const string SingboxFakeDNSTag = "fake_dns"; public const string SingboxFakeDNSTag = "fake_dns";
public const string RoutingRuleType = "Routing";
public const string DNSRuleType = "DNS";
public static readonly List<string> IEProxyProtocols = public static readonly List<string> IEProxyProtocols =
[ [
@ -449,6 +451,14 @@ public class Global
"none" "none"
]; ];
public static readonly Dictionary<string, string> LogLevelColors = new()
{
{ "debug", "#6C757D" },
{ "info", "#2ECC71" },
{ "warning", "#FFA500" },
{ "error", "#E74C3C" },
};
public static readonly List<string> InboundTags = public static readonly List<string> InboundTags =
[ [
"socks", "socks",
@ -471,6 +481,12 @@ public class Global
"tcp,udp" "tcp,udp"
]; ];
public static readonly List<string> RuleTypes =
[
RoutingRuleType,
DNSRuleType
];
public static readonly List<string> destOverrideProtocols = public static readonly List<string> destOverrideProtocols =
[ [
"http", "http",

View file

@ -15,4 +15,5 @@ public class RulesItem
public List<string>? Process { get; set; } public List<string>? Process { get; set; }
public bool Enabled { get; set; } = true; public bool Enabled { get; set; } = true;
public string? Remarks { get; set; } public string? Remarks { get; set; }
public List<string>? RuleTypes { get; set; }
} }

View file

@ -253,6 +253,11 @@ public partial class CoreConfigSingboxService
continue; continue;
} }
if ((item.RuleTypes?.Count ?? 0) > 0 && !item.RuleTypes.Contains(Global.DNSRuleType))
{
continue;
}
var rule = new Rule4Sbox(); var rule = new Rule4Sbox();
var validDomains = item.Domain.Count(it => ParseV2Domain(it, rule)); var validDomains = item.Domain.Count(it => ParseV2Domain(it, rule));
if (validDomains <= 0) if (validDomains <= 0)

View file

@ -136,13 +136,21 @@ public partial class CoreConfigSingboxService
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet); var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet);
foreach (var item1 in rules ?? []) foreach (var item1 in rules ?? [])
{ {
if (item1.Enabled) if (!item1.Enabled)
{ {
await GenRoutingUserRule(item1, singboxConfig); continue;
if (item1.Ip != null && item1.Ip.Count > 0) }
{
ipRules.Add(item1); if ((item1.RuleTypes?.Count ?? 0) > 0 && !item1.RuleTypes.Contains(Global.RoutingRuleType))
} {
continue;
}
await GenRoutingUserRule(item1, singboxConfig);
if (item1.Ip?.Count > 0)
{
ipRules.Add(item1);
} }
} }
} }

View file

@ -142,6 +142,11 @@ public partial class CoreConfigV2rayService
continue; continue;
} }
if ((item.RuleTypes?.Count ?? 0) > 0 && !item.RuleTypes.Contains(Global.DNSRuleType))
{
continue;
}
foreach (var domain in item.Domain) foreach (var domain in item.Domain)
{ {
if (domain.StartsWith('#')) if (domain.StartsWith('#'))

View file

@ -20,11 +20,18 @@ public partial class CoreConfigV2rayService
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet); var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet);
foreach (var item in rules) foreach (var item in rules)
{ {
if (item.Enabled) if (!item.Enabled)
{ {
var item2 = JsonUtils.Deserialize<RulesItem4Ray>(JsonUtils.Serialize(item)); continue;
await GenRoutingUserRule(item2, v2rayConfig);
} }
if ((item.RuleTypes?.Count ?? 0) > 0 && !item.RuleTypes.Contains(Global.RoutingRuleType))
{
continue;
}
var item2 = JsonUtils.Deserialize<RulesItem4Ray>(JsonUtils.Serialize(item));
await GenRoutingUserRule(item2, v2rayConfig);
} }
} }
} }

View file

@ -1,5 +1,6 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using ReactiveUI; using ReactiveUI;
using ReactiveUI.Fody.Helpers; using ReactiveUI.Fody.Helpers;
@ -9,9 +10,9 @@ namespace ServiceLib.ViewModels;
public class MsgViewModel : MyReactiveObject public class MsgViewModel : MyReactiveObject
{ {
private readonly ConcurrentQueue<string> _queueMsg = new(); private readonly ConcurrentQueue<string> _queueMsg = new();
private readonly int _numMaxMsg = 500; private volatile bool _lastMsgFilterNotAvailable;
private bool _lastMsgFilterNotAvailable; private int _showLock = 0; // 0 = unlocked, 1 = locked
private bool _blLockShow = false; public int NumMaxMsg { get; } = 500;
[Reactive] [Reactive]
public string MsgFilter { get; set; } public string MsgFilter { get; set; }
@ -33,46 +34,52 @@ public class MsgViewModel : MyReactiveObject
this.WhenAnyValue( this.WhenAnyValue(
x => x.AutoRefresh, x => x.AutoRefresh,
y => y == true) y => y == true)
.Subscribe(c => { _config.MsgUIItem.AutoRefresh = AutoRefresh; }); .Subscribe(c => _config.MsgUIItem.AutoRefresh = AutoRefresh);
AppEvents.SendMsgViewRequested AppEvents.SendMsgViewRequested
.AsObservable() .AsObservable()
//.ObserveOn(RxApp.MainThreadScheduler) //.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(async content => await AppendQueueMsg(content)); .Subscribe(content => _ = AppendQueueMsg(content));
} }
private async Task AppendQueueMsg(string msg) private async Task AppendQueueMsg(string msg)
{ {
//if (msg == Global.CommandClearMsg)
//{
// ClearMsg();
// return;
//}
if (AutoRefresh == false) if (AutoRefresh == false)
{ {
return; return;
} }
_ = EnqueueQueueMsg(msg);
if (_blLockShow) EnqueueQueueMsg(msg);
{
return;
}
if (!_config.UiItem.ShowInTaskbar) if (!_config.UiItem.ShowInTaskbar)
{ {
return; return;
} }
_blLockShow = true; if (Interlocked.CompareExchange(ref _showLock, 1, 0) != 0)
{
return;
}
await Task.Delay(500); try
var txt = string.Join("", _queueMsg.ToArray()); {
await _updateView?.Invoke(EViewAction.DispatcherShowMsg, txt); await Task.Delay(500).ConfigureAwait(false);
_blLockShow = false; var sb = new StringBuilder();
while (_queueMsg.TryDequeue(out var line))
{
sb.Append(line);
}
await _updateView?.Invoke(EViewAction.DispatcherShowMsg, sb.ToString());
}
finally
{
Interlocked.Exchange(ref _showLock, 0);
}
} }
private async Task EnqueueQueueMsg(string msg) private void EnqueueQueueMsg(string msg)
{ {
//filter msg //filter msg
if (MsgFilter.IsNotEmpty() && !_lastMsgFilterNotAvailable) if (MsgFilter.IsNotEmpty() && !_lastMsgFilterNotAvailable)
@ -91,26 +98,17 @@ public class MsgViewModel : MyReactiveObject
} }
} }
//Enqueue
if (_queueMsg.Count > _numMaxMsg)
{
for (int k = 0; k < _queueMsg.Count - _numMaxMsg; k++)
{
_queueMsg.TryDequeue(out _);
}
}
_queueMsg.Enqueue(msg); _queueMsg.Enqueue(msg);
if (!msg.EndsWith(Environment.NewLine)) if (!msg.EndsWith(Environment.NewLine))
{ {
_queueMsg.Enqueue(Environment.NewLine); _queueMsg.Enqueue(Environment.NewLine);
} }
await Task.CompletedTask;
} }
public void ClearMsg() //public void ClearMsg()
{ //{
_queueMsg.Clear(); // _queueMsg.Clear();
} //}
private void DoMsgFilter() private void DoMsgFilter()
{ {

View file

@ -21,6 +21,8 @@ public class RoutingRuleDetailsViewModel : MyReactiveObject
[Reactive] [Reactive]
public string Process { get; set; } public string Process { get; set; }
public IList<string> Types { get; set; }
[Reactive] [Reactive]
public bool AutoSort { get; set; } public bool AutoSort { get; set; }
@ -51,6 +53,11 @@ public class RoutingRuleDetailsViewModel : MyReactiveObject
Domain = Utils.List2String(SelectedSource.Domain, true); Domain = Utils.List2String(SelectedSource.Domain, true);
IP = Utils.List2String(SelectedSource.Ip, true); IP = Utils.List2String(SelectedSource.Ip, true);
Process = Utils.List2String(SelectedSource.Process, true); Process = Utils.List2String(SelectedSource.Process, true);
Types = SelectedSource.RuleTypes?.ToList();
if (Types == null || Types.Count == 0)
{
Types = Global.RuleTypes;
}
} }
private async Task SaveRulesAsync() private async Task SaveRulesAsync()
@ -73,6 +80,7 @@ public class RoutingRuleDetailsViewModel : MyReactiveObject
} }
SelectedSource.Protocol = ProtocolItems?.ToList(); SelectedSource.Protocol = ProtocolItems?.ToList();
SelectedSource.InboundTag = InboundTagItems?.ToList(); SelectedSource.InboundTag = InboundTagItems?.ToList();
SelectedSource.RuleTypes = Types?.ToList();
var hasRule = SelectedSource.Domain?.Count > 0 var hasRule = SelectedSource.Domain?.Count > 0
|| SelectedSource.Ip?.Count > 0 || SelectedSource.Ip?.Count > 0

View file

@ -11,6 +11,7 @@
RequestedThemeVariant="Default"> RequestedThemeVariant="Default">
<Application.Styles> <Application.Styles>
<semi:SemiTheme /> <semi:SemiTheme />
<semi:AvaloniaEditSemiTheme />
<StyleInclude Source="Assets/GlobalStyles.axaml" /> <StyleInclude Source="Assets/GlobalStyles.axaml" />
<StyleInclude Source="avares://Semi.Avalonia.DataGrid/Index.axaml" /> <StyleInclude Source="avares://Semi.Avalonia.DataGrid/Index.axaml" />
<dialogHost:DialogHostStyles /> <dialogHost:DialogHostStyles />

View file

@ -0,0 +1,129 @@
using Avalonia.Media;
using AvaloniaEdit;
using AvaloniaEdit.Document;
using AvaloniaEdit.Rendering;
namespace v2rayN.Desktop.Common;
public class KeywordColorizer : DocumentColorizingTransformer
{
private readonly string[] _keywords;
private readonly Dictionary<string, IBrush> _brushMap;
public KeywordColorizer(IDictionary<string, IBrush> keywordBrushMap)
{
if (keywordBrushMap == null || keywordBrushMap.Count == 0)
{
throw new ArgumentException("keywordBrushMap must not be null or empty", nameof(keywordBrushMap));
}
_brushMap = new Dictionary<string, IBrush>(StringComparer.OrdinalIgnoreCase);
foreach (var kvp in keywordBrushMap)
{
if (string.IsNullOrEmpty(kvp.Key) || kvp.Value == null)
{
continue;
}
if (!_brushMap.ContainsKey(kvp.Key))
{
_brushMap[kvp.Key] = kvp.Value;
}
}
if (_brushMap.Count == 0)
{
throw new ArgumentException("keywordBrushMap must contain at least one non-empty key with a non-null brush", nameof(keywordBrushMap));
}
_keywords = _brushMap.Keys.ToArray();
}
protected override void ColorizeLine(DocumentLine line)
{
var text = CurrentContext.Document.GetText(line);
if (string.IsNullOrEmpty(text))
{
return;
}
foreach (var kw in _keywords)
{
if (string.IsNullOrEmpty(kw))
{
continue;
}
var searchStart = 0;
while (true)
{
var idx = text.IndexOf(kw, searchStart, StringComparison.OrdinalIgnoreCase);
if (idx < 0)
{
break;
}
var kwEndIndex = idx + kw.Length;
if (IsWordCharBefore(text, idx) || IsWordCharAfter(text, kwEndIndex))
{
searchStart = idx + Math.Max(1, kw.Length);
continue;
}
var start = line.Offset + idx;
var end = start + kw.Length;
if (_brushMap.TryGetValue(kw, out var brush) && brush != null)
{
ChangeLinePart(start, end, element => element.TextRunProperties.SetForegroundBrush(brush));
}
searchStart = idx + Math.Max(1, kw.Length);
}
}
}
private static bool IsWordCharBefore(string text, int idx)
{
if (idx <= 0)
{
return false;
}
var c = text[idx - 1];
return char.IsLetterOrDigit(c) || c == '_';
}
private static bool IsWordCharAfter(string text, int idx)
{
if (idx >= text.Length)
{
return false;
}
var c = text[idx];
return char.IsLetterOrDigit(c) || c == '_';
}
}
public static class TextEditorKeywordHighlighter
{
public static void Attach(TextEditor editor, IDictionary<string, IBrush> keywordBrushMap)
{
ArgumentNullException.ThrowIfNull(editor);
if (keywordBrushMap == null || keywordBrushMap.Count == 0)
{
return;
}
if (editor.TextArea?.TextView?.LineTransformers?.OfType<KeywordColorizer>().Any() == true)
{
return;
}
var colorizer = new KeywordColorizer(keywordBrushMap);
editor.TextArea.TextView.LineTransformers.Add(colorizer);
editor.TextArea.TextView.InvalidateVisual();
}
}

View file

@ -5,6 +5,7 @@ using Avalonia.Controls.Notifications;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Styling; using Avalonia.Styling;
using AvaloniaEdit;
using ReactiveUI; using ReactiveUI;
using ReactiveUI.Fody.Helpers; using ReactiveUI.Fody.Helpers;
using Semi.Avalonia; using Semi.Avalonia;
@ -112,7 +113,8 @@ public class ThemeSettingViewModel : MyReactiveObject
x.OfType<ContextMenu>(), x.OfType<ContextMenu>(),
x.OfType<DataGridRow>(), x.OfType<DataGridRow>(),
x.OfType<ListBoxItem>(), x.OfType<ListBoxItem>(),
x.OfType<HeaderedContentControl>() x.OfType<HeaderedContentControl>(),
x.OfType<TextEditor>()
)); ));
style.Add(new Setter() style.Add(new Setter()
{ {
@ -153,7 +155,8 @@ public class ThemeSettingViewModel : MyReactiveObject
x.OfType<DataGridRow>(), x.OfType<DataGridRow>(),
x.OfType<ListBoxItem>(), x.OfType<ListBoxItem>(),
x.OfType<HeaderedContentControl>(), x.OfType<HeaderedContentControl>(),
x.OfType<WindowNotificationManager>() x.OfType<WindowNotificationManager>(),
x.OfType<TextEditor>()
)); ));
style.Add(new Setter() style.Add(new Setter()
{ {

View file

@ -2,6 +2,7 @@
x:Class="v2rayN.Desktop.Views.MsgView" x:Class="v2rayN.Desktop.Views.MsgView"
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:avaloniaEdit="clr-namespace:AvaloniaEdit;assembly=AvaloniaEdit"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib" xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
@ -70,35 +71,36 @@
Theme="{DynamicResource SimpleToggleSwitch}" /> Theme="{DynamicResource SimpleToggleSwitch}" />
</WrapPanel> </WrapPanel>
<ScrollViewer x:Name="msgScrollViewer" VerticalScrollBarVisibility="Auto"> <avaloniaEdit:TextEditor
<SelectableTextBlock Name="txtMsg"
Name="txtMsg" Margin="{StaticResource Margin8}"
Margin="{StaticResource Margin8}" IsReadOnly="True"
VerticalAlignment="Stretch" VerticalScrollBarVisibility="Auto"
Classes="TextArea" WordWrap="True">
TextAlignment="Left" <avaloniaEdit:TextEditor.Options>
TextWrapping="Wrap"> <avaloniaEdit:TextEditorOptions AllowScrollBelowDocument="False"/>
<SelectableTextBlock.ContextMenu> </avaloniaEdit:TextEditor.Options>
<ContextMenu> <avaloniaEdit:TextEditor.ContextFlyout>
<MenuItem <MenuFlyout>
x:Name="menuMsgViewSelectAll" <MenuItem
Click="menuMsgViewSelectAll_Click" x:Name="menuMsgViewSelectAll"
Header="{x:Static resx:ResUI.menuMsgViewSelectAll}" /> Click="menuMsgViewSelectAll_Click"
<MenuItem Header="{x:Static resx:ResUI.menuMsgViewSelectAll}" />
x:Name="menuMsgViewCopy" <MenuItem
Click="menuMsgViewCopy_Click" x:Name="menuMsgViewCopy"
Header="{x:Static resx:ResUI.menuMsgViewCopy}" /> Click="menuMsgViewCopy_Click"
<MenuItem Header="{x:Static resx:ResUI.menuMsgViewCopy}" />
x:Name="menuMsgViewCopyAll" <MenuItem
Click="menuMsgViewCopyAll_Click" x:Name="menuMsgViewCopyAll"
Header="{x:Static resx:ResUI.menuMsgViewCopyAll}" /> Click="menuMsgViewCopyAll_Click"
<MenuItem Header="{x:Static resx:ResUI.menuMsgViewCopyAll}" />
x:Name="menuMsgViewClear" <MenuItem
Click="menuMsgViewClear_Click" x:Name="menuMsgViewClear"
Header="{x:Static resx:ResUI.menuMsgViewClear}" /> Click="menuMsgViewClear_Click"
</ContextMenu> Header="{x:Static resx:ResUI.menuMsgViewClear}" />
</SelectableTextBlock.ContextMenu> </MenuFlyout>
</SelectableTextBlock> </avaloniaEdit:TextEditor.ContextFlyout>
</ScrollViewer> </avaloniaEdit:TextEditor>
</DockPanel> </DockPanel>
</UserControl> </UserControl>

View file

@ -1,6 +1,6 @@
using System.Reactive.Disposables; using System.Reactive.Disposables;
using Avalonia.Controls;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
using Avalonia.Threading; using Avalonia.Threading;
using ReactiveUI; using ReactiveUI;
@ -10,13 +10,11 @@ namespace v2rayN.Desktop.Views;
public partial class MsgView : ReactiveUserControl<MsgViewModel> public partial class MsgView : ReactiveUserControl<MsgViewModel>
{ {
private readonly ScrollViewer _scrollViewer; //private const int KeepLines = 30;
public MsgView() public MsgView()
{ {
InitializeComponent(); InitializeComponent();
_scrollViewer = this.FindControl<ScrollViewer>("msgScrollViewer");
ViewModel = new MsgViewModel(UpdateViewHandler); ViewModel = new MsgViewModel(UpdateViewHandler);
this.WhenActivated(disposables => this.WhenActivated(disposables =>
@ -24,6 +22,11 @@ public partial class MsgView : ReactiveUserControl<MsgViewModel>
this.Bind(ViewModel, vm => vm.MsgFilter, v => v.cmbMsgFilter.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.MsgFilter, v => v.cmbMsgFilter.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.AutoRefresh, v => v.togAutoRefresh.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.AutoRefresh, v => v.togAutoRefresh.IsChecked).DisposeWith(disposables);
}); });
TextEditorKeywordHighlighter.Attach(txtMsg, Global.LogLevelColors.ToDictionary(
kv => kv.Key,
kv => (IBrush)new SolidColorBrush(Color.Parse(kv.Value))
));
} }
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj) private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
@ -34,9 +37,8 @@ public partial class MsgView : ReactiveUserControl<MsgViewModel>
if (obj is null) if (obj is null)
return false; return false;
Dispatcher.UIThread.Post(() => Dispatcher.UIThread.Post(() => ShowMsg(obj),
ShowMsg(obj), DispatcherPriority.ApplicationIdle);
DispatcherPriority.ApplicationIdle);
break; break;
} }
return await Task.FromResult(true); return await Task.FromResult(true);
@ -44,23 +46,37 @@ public partial class MsgView : ReactiveUserControl<MsgViewModel>
private void ShowMsg(object msg) private void ShowMsg(object msg)
{ {
txtMsg.Text = msg.ToString(); //var lineCount = txtMsg.LineCount;
//if (lineCount > ViewModel?.NumMaxMsg)
//{
// var cutLine = txtMsg.Document.GetLineByNumber(lineCount - KeepLines);
// txtMsg.Document.Remove(0, cutLine.Offset);
//}
if (txtMsg.LineCount > ViewModel?.NumMaxMsg)
{
ClearMsg();
}
txtMsg.AppendText(msg.ToString());
if (togScrollToEnd.IsChecked ?? true) if (togScrollToEnd.IsChecked ?? true)
{ {
_scrollViewer?.ScrollToEnd(); txtMsg.ScrollToEnd();
} }
} }
public void ClearMsg() public void ClearMsg()
{ {
ViewModel?.ClearMsg(); txtMsg.Clear();
txtMsg.Text = ""; txtMsg.AppendText("----- Message cleared -----\n");
} }
private void menuMsgViewSelectAll_Click(object? sender, RoutedEventArgs e) private void menuMsgViewSelectAll_Click(object? sender, RoutedEventArgs e)
{ {
txtMsg.Focus(); Dispatcher.UIThread.Post(() =>
txtMsg.SelectAll(); {
txtMsg.TextArea.Focus();
txtMsg.SelectAll();
}, DispatcherPriority.Render);
} }
private async void menuMsgViewCopy_Click(object? sender, RoutedEventArgs e) private async void menuMsgViewCopy_Click(object? sender, RoutedEventArgs e)

View file

@ -33,13 +33,25 @@
Width="300" Width="300"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" /> HorizontalAlignment="Left" />
<ToggleSwitch <StackPanel
x:Name="togEnabled"
Grid.Row="0" Grid.Row="0"
Grid.Column="2" Grid.Column="2"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Center" /> VerticalAlignment="Center"
Orientation="Horizontal">
<ToggleSwitch
x:Name="togEnabled"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
VerticalAlignment="Center" />
<ListBox
x:Name="clbRuleTypes"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
SelectionMode="Multiple,Toggle"
Theme="{DynamicResource CardCheckGroupListBox}" />
</StackPanel>
<TextBlock <TextBlock
Grid.Row="1" Grid.Row="1"
@ -58,17 +70,15 @@
Grid.Row="1" Grid.Row="1"
Grid.Column="2" Grid.Column="2"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
Orientation="Horizontal"
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Center"> VerticalAlignment="Center"
Orientation="Horizontal">
<Button <Button
x:Name="btnSelectProfile" x:Name="btnSelectProfile"
Margin="0,0,8,0" Margin="0,0,8,0"
Content="{x:Static resx:ResUI.TbSelectProfile}" Click="BtnSelectProfile_Click"
Click="BtnSelectProfile_Click" /> Content="{x:Static resx:ResUI.TbSelectProfile}" />
<TextBlock <TextBlock VerticalAlignment="Center" Text="{x:Static resx:ResUI.TbRuleOutboundTagTip}" />
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbRuleOutboundTagTip}" />
</StackPanel> </StackPanel>
<TextBlock <TextBlock

View file

@ -29,6 +29,16 @@ public partial class RoutingRuleDetailsWindow : WindowBase<RoutingRuleDetailsVie
clbInboundTag.ItemsSource = Global.InboundTags; clbInboundTag.ItemsSource = Global.InboundTags;
cmbNetwork.ItemsSource = Global.RuleNetworks; cmbNetwork.ItemsSource = Global.RuleNetworks;
clbRuleTypes.SelectionChanged += ClbRuleTypes_SelectionChanged;
clbRuleTypes.ItemsSource = Global.RuleTypes;
if (ViewModel.Types != null)
{
foreach (var it in ViewModel.Types)
{
clbRuleTypes.SelectedItems.Add(it);
}
}
if (!rulesItem.Id.IsNullOrEmpty()) if (!rulesItem.Id.IsNullOrEmpty())
{ {
rulesItem.Protocol?.ForEach(it => rulesItem.Protocol?.ForEach(it =>
@ -108,4 +118,12 @@ public partial class RoutingRuleDetailsWindow : WindowBase<RoutingRuleDetailsVie
} }
} }
} }
private void ClbRuleTypes_SelectionChanged(object? sender, SelectionChangedEventArgs e)
{
if (ViewModel != null)
{
ViewModel.Types = clbRuleTypes.SelectedItems.Cast<string>().ToList();
}
}
} }

View file

@ -9,6 +9,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia.AvaloniaEdit" />
<PackageReference Include="Avalonia.Controls.DataGrid"> <PackageReference Include="Avalonia.Controls.DataGrid">
<TreatAsUsed>true</TreatAsUsed> <TreatAsUsed>true</TreatAsUsed>
</PackageReference> </PackageReference>
@ -17,6 +18,7 @@
<PackageReference Include="Avalonia.ReactiveUI" /> <PackageReference Include="Avalonia.ReactiveUI" />
<PackageReference Include="MessageBox.Avalonia" /> <PackageReference Include="MessageBox.Avalonia" />
<PackageReference Include="Semi.Avalonia" /> <PackageReference Include="Semi.Avalonia" />
<PackageReference Include="Semi.Avalonia.AvaloniaEdit" />
<PackageReference Include="Semi.Avalonia.DataGrid"> <PackageReference Include="Semi.Avalonia.DataGrid">
<TreatAsUsed>true</TreatAsUsed> <TreatAsUsed>true</TreatAsUsed>
</PackageReference> </PackageReference>

View file

@ -47,19 +47,22 @@ public partial class MsgView
private void ShowMsg(object msg) private void ShowMsg(object msg)
{ {
txtMsg.BeginChange(); if (txtMsg.LineCount > ViewModel?.NumMaxMsg)
txtMsg.Text = msg.ToString(); {
ClearMsg();
}
txtMsg.AppendText(msg.ToString());
if (togScrollToEnd.IsChecked ?? true) if (togScrollToEnd.IsChecked ?? true)
{ {
txtMsg.ScrollToEnd(); txtMsg.ScrollToEnd();
} }
txtMsg.EndChange();
} }
public void ClearMsg() public void ClearMsg()
{ {
ViewModel?.ClearMsg();
txtMsg.Clear(); txtMsg.Clear();
txtMsg.AppendText("----- Message cleared -----\n");
} }
private void menuMsgViewSelectAll_Click(object sender, System.Windows.RoutedEventArgs e) private void menuMsgViewSelectAll_Click(object sender, System.Windows.RoutedEventArgs e)

View file

@ -48,13 +48,25 @@
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" HorizontalAlignment="Left"
Style="{StaticResource DefTextBox}" /> Style="{StaticResource DefTextBox}" />
<ToggleButton <StackPanel
x:Name="togEnabled"
Grid.Row="0" Grid.Row="0"
Grid.Column="2" Grid.Column="2"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Center" /> VerticalAlignment="Center"
Orientation="Horizontal">
<ToggleButton
x:Name="togEnabled"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
VerticalAlignment="Center" />
<ListBox
x:Name="clbRuleTypes"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
FontSize="{DynamicResource StdFontSize}"
Style="{StaticResource MaterialDesignFilterChipPrimaryListBox}" />
</StackPanel>
<TextBlock <TextBlock
Grid.Row="1" Grid.Row="1"

View file

@ -22,6 +22,16 @@ public partial class RoutingRuleDetailsWindow
clbInboundTag.ItemsSource = Global.InboundTags; clbInboundTag.ItemsSource = Global.InboundTags;
cmbNetwork.ItemsSource = Global.RuleNetworks; cmbNetwork.ItemsSource = Global.RuleNetworks;
clbRuleTypes.SelectionChanged += ClbRuleTypes_SelectionChanged;
clbRuleTypes.ItemsSource = Global.RuleTypes;
if (ViewModel.Types != null)
{
foreach (var it in ViewModel.Types)
{
clbRuleTypes.SelectedItems.Add(it);
}
}
if (!rulesItem.Id.IsNullOrEmpty()) if (!rulesItem.Id.IsNullOrEmpty())
{ {
rulesItem.Protocol?.ForEach(it => rulesItem.Protocol?.ForEach(it =>
@ -101,4 +111,12 @@ public partial class RoutingRuleDetailsWindow
} }
} }
} }
private void ClbRuleTypes_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
if (ViewModel != null)
{
ViewModel.Types = clbRuleTypes.SelectedItems.Cast<string>().ToList();
}
}
} }