Добавлен Studio
This commit is contained in:
17
Lattice.Core/Abstractions/INotificationService.cs
Normal file
17
Lattice.Core/Abstractions/INotificationService.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using Lattice.Core.Models;
|
||||||
|
using Lattice.Core.Models.Enums;
|
||||||
|
|
||||||
|
namespace Lattice.Core.Abstractions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Описывает сервис для рассылки уведомлений внутри системы Lattice.
|
||||||
|
/// </summary>
|
||||||
|
public interface INotificationService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Событие, возникающее при отправке нового сообщения.
|
||||||
|
/// </summary>
|
||||||
|
event EventHandler<NotificationEventArgs> NotificationReceived;
|
||||||
|
|
||||||
|
void Show(string message, NotificationSeverity severity = NotificationSeverity.Info, int durationSeconds = 5);
|
||||||
|
}
|
||||||
@@ -1,28 +1,25 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!-- Поддержка LTS версий и актуальной на 2026 год .NET 10 -->
|
|
||||||
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
|
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<AssemblyName>Lattice.Core</AssemblyName>
|
<AssemblyName>Lattice.Core</AssemblyName>
|
||||||
<RootNamespace>Lattice.Core</RootNamespace>
|
<RootNamespace>Lattice.Core</RootNamespace>
|
||||||
|
|
||||||
<!-- Метаданные разработчика -->
|
|
||||||
<Authors>FrigaT</Authors>
|
<Authors>FrigaT</Authors>
|
||||||
<Company>FrigaT</Company>
|
<Company>FrigaT</Company>
|
||||||
<RepositoryUrl>https://git.frigat.duckdns.org/FrigaT/Lattice</RepositoryUrl>
|
<RepositoryUrl>https://git.frigat.duckdns.org/FrigaT/Lattice</RepositoryUrl>
|
||||||
<PackageProjectUrl>https://git.frigat.duckdns.org/FrigaT/Lattice</PackageProjectUrl>
|
<PackageProjectUrl>https://git.frigat.duckdns.org/FrigaT/Lattice</PackageProjectUrl>
|
||||||
<Description>Core docking and layout engine for Lattice UI (WinUI 3 / Uno Platform).</Description>
|
<Description>Core docking and layout engine for Lattice UI (WinUI 3 / Uno Platform).</Description>
|
||||||
|
|
||||||
<!-- Совместимость с Uno Platform (Trimming и AOT) -->
|
|
||||||
<IsTrimmable Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">true</IsTrimmable>
|
<IsTrimmable Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">true</IsTrimmable>
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.1" />
|
||||||
<PackageReference Include="System.Text.Json" Version="8.0.0" />
|
<PackageReference Include="System.Text.Json" Version="10.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
8
Lattice.Core/Models/Enums/NotificationSeverity.cs
Normal file
8
Lattice.Core/Models/Enums/NotificationSeverity.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Lattice.Core.Models.Enums;
|
||||||
|
|
||||||
|
public enum NotificationSeverity {
|
||||||
|
Info,
|
||||||
|
Success,
|
||||||
|
Warning,
|
||||||
|
Error,
|
||||||
|
}
|
||||||
5
Lattice.Core/Models/NotificationEventArgs.cs
Normal file
5
Lattice.Core/Models/NotificationEventArgs.cs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
using Lattice.Core.Models.Enums;
|
||||||
|
|
||||||
|
namespace Lattice.Core.Models;
|
||||||
|
|
||||||
|
public record NotificationEventArgs(string Message, NotificationSeverity Severity, int DurationSeconds);
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
using Lattice.Core.Abstractions;
|
using Lattice.Core.Abstractions;
|
||||||
|
|
||||||
namespace Lattice.Core.Context;
|
namespace Lattice.Core.Services;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Реализация сервиса управления контекстом приложения.
|
/// Реализация сервиса управления контекстом приложения.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ContextManager : IContextService
|
public class ContextService : IContextService
|
||||||
{
|
{
|
||||||
private string _currentContext = "Common";
|
private string _currentContext = "Common";
|
||||||
|
|
||||||
@@ -6,12 +6,12 @@ using Microsoft.Extensions.Logging;
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Lattice.Core.Engine;
|
namespace Lattice.Core.Services;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Реализация сервиса управления макетом.
|
/// Реализация сервиса управления макетом.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class LayoutManager : ILayoutService
|
public class LayoutService : ILayoutService
|
||||||
{
|
{
|
||||||
private readonly ILogger? _logger;
|
private readonly ILogger? _logger;
|
||||||
private LayoutNode? _root;
|
private LayoutNode? _root;
|
||||||
@@ -22,7 +22,7 @@ public class LayoutManager : ILayoutService
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public event EventHandler? LayoutUpdated;
|
public event EventHandler? LayoutUpdated;
|
||||||
|
|
||||||
public LayoutManager(ILogger<LayoutManager>? logger = null)
|
public LayoutService(ILogger<LayoutService>? logger = null)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
19
Lattice.Core/Services/NotificationService.cs
Normal file
19
Lattice.Core/Services/NotificationService.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using Lattice.Core.Abstractions;
|
||||||
|
using Lattice.Core.Models;
|
||||||
|
using Lattice.Core.Models.Enums;
|
||||||
|
|
||||||
|
namespace Lattice.Core.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Простая реализация сервиса уведомлений.
|
||||||
|
/// Хранит только событие и вызывает его при Show().
|
||||||
|
/// </summary>
|
||||||
|
public sealed class NotificationService : INotificationService
|
||||||
|
{
|
||||||
|
public event EventHandler<NotificationEventArgs>? NotificationReceived;
|
||||||
|
|
||||||
|
public void Show(string message, NotificationSeverity severity = NotificationSeverity.Info, int durationSeconds = 5)
|
||||||
|
{
|
||||||
|
NotificationReceived?.Invoke(this, new NotificationEventArgs(message, severity, durationSeconds));
|
||||||
|
}
|
||||||
|
}
|
||||||
64
Lattice.Studio/Controls/LatticeStudioShell.xaml
Normal file
64
Lattice.Studio/Controls/LatticeStudioShell.xaml
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<UserControl
|
||||||
|
x:Class="Lattice.Studio.Controls.LatticeStudioShell"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="using:Lattice.Studio.Controls"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:lui="using:Lattice.UI.Controls"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
|
||||||
|
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<!-- TitleBar с MenuBar -->
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<!-- Тулбар -->
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<!-- СЛОТ ДЛЯ УВЕДОМЛЕНИЙ -->
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<!-- Докинг -->
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<!-- Статус-бар -->
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- Нативный TitleBar Windows 11 -->
|
||||||
|
<TitleBar x:Name="AppTitleBar"
|
||||||
|
Title="{x:Bind Title, Mode=OneWay}"
|
||||||
|
Subtitle="{x:Bind Subtitle, Mode=OneWay}"
|
||||||
|
IsBackButtonVisible="False"
|
||||||
|
IconSource="{x:Bind TitleBarIcon, Mode=OneWay}"
|
||||||
|
IsPaneToggleButtonVisible="False">
|
||||||
|
|
||||||
|
<!-- Вставляем меню прямо в заголовок -->
|
||||||
|
<TitleBar.Content>
|
||||||
|
<Grid x:Name="TitleBarContentGrid" Margin="8,0,0,0">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<!-- Меню -->
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<!-- Зона перетаскивания -->
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<!-- Слот для MenuBar -->
|
||||||
|
<ContentPresenter x:Name="MenuSlot"
|
||||||
|
Content="{x:Bind MenuContent, Mode=OneWay}"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
|
||||||
|
<!-- Пустая область для захвата мышью (Drag Region) -->
|
||||||
|
<Canvas Grid.Column="1" Background="Transparent" />
|
||||||
|
</Grid>
|
||||||
|
</TitleBar.Content>
|
||||||
|
</TitleBar>
|
||||||
|
|
||||||
|
<!-- Тулбар -->
|
||||||
|
<lui:LatticeContextualToolbar Grid.Row="1" x:Name="StudioToolbar" />
|
||||||
|
<!-- Контейнер для уведомлений (StackPanel для нескольких сообщений) -->
|
||||||
|
<StackPanel Grid.Row="2" x:Name="NotificationArea" VerticalAlignment="Top" Canvas.ZIndex="100" />
|
||||||
|
<!-- Докинг -->
|
||||||
|
<lui:LatticeDockHost Grid.Row="3" x:Name="MainDockHost" />
|
||||||
|
<!-- Статус-бар -->
|
||||||
|
<ContentPresenter Grid.Row="4" Content="{x:Bind StatusContent, Mode=OneWay}" />
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
144
Lattice.Studio/Controls/LatticeStudioShell.xaml.cs
Normal file
144
Lattice.Studio/Controls/LatticeStudioShell.xaml.cs
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
using Lattice.Core.Abstractions;
|
||||||
|
using Lattice.Core.Models;
|
||||||
|
using Lattice.Core.Models.Enums;
|
||||||
|
using Microsoft.UI.Composition.SystemBackdrops;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
using Microsoft.UI.Xaml.Media;
|
||||||
|
|
||||||
|
namespace Lattice.Studio.Controls;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Îñíîâíàÿ îáîëî÷êà ïðèëîæåíèÿ â ñòèëå Visual Studio 2026.
|
||||||
|
/// Îáåñïå÷èâàåò èíòåãðàöèþ íàòèâíîãî çàãîëîâêà, ñèñòåìû äîêèíãà è êîíòåêñòíûõ êîìàíä.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class LatticeStudioShell : UserControl
|
||||||
|
{
|
||||||
|
private ILayoutService? _layoutManager;
|
||||||
|
private IContextService? _contextService;
|
||||||
|
private IEnumerable<ActionDefinition>? _allActions;
|
||||||
|
|
||||||
|
#region Dependency Properties (Ñëîòû êàñòîìèçàöèè)
|
||||||
|
|
||||||
|
public string Title { get => (string)GetValue(TitleProperty); set => SetValue(TitleProperty, value); }
|
||||||
|
public static readonly DependencyProperty TitleProperty =
|
||||||
|
DependencyProperty.Register(nameof(Title), typeof(string), typeof(LatticeStudioShell), new PropertyMetadata("Lattice IDE"));
|
||||||
|
|
||||||
|
public string Subtitle { get => (string)GetValue(SubtitleProperty); set => SetValue(SubtitleProperty, value); }
|
||||||
|
public static readonly DependencyProperty SubtitleProperty =
|
||||||
|
DependencyProperty.Register(nameof(Subtitle), typeof(string), typeof(LatticeStudioShell), new PropertyMetadata(string.Empty));
|
||||||
|
|
||||||
|
public IconSource TitleBarIcon { get => (IconSource)GetValue(TitleBarIconProperty); set => SetValue(TitleBarIconProperty, value); }
|
||||||
|
public static readonly DependencyProperty TitleBarIconProperty =
|
||||||
|
DependencyProperty.Register(nameof(TitleBarIcon), typeof(IconSource), typeof(LatticeStudioShell), new PropertyMetadata(null));
|
||||||
|
|
||||||
|
public object MenuContent { get => GetValue(MenuContentProperty); set => SetValue(MenuContentProperty, value); }
|
||||||
|
public static readonly DependencyProperty MenuContentProperty =
|
||||||
|
DependencyProperty.Register(nameof(MenuContent), typeof(object), typeof(LatticeStudioShell), new PropertyMetadata(null));
|
||||||
|
|
||||||
|
public object StatusContent { get => GetValue(StatusContentProperty); set => SetValue(StatusContentProperty, value); }
|
||||||
|
public static readonly DependencyProperty StatusContentProperty =
|
||||||
|
DependencyProperty.Register(nameof(StatusContent), typeof(object), typeof(LatticeStudioShell), new PropertyMetadata(null));
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public LatticeStudioShell()
|
||||||
|
{
|
||||||
|
this.InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Èíèöèàëèçèðóåò îáîëî÷êó Studio è ñâÿçûâàåò å¸ ñ ñåðâèñàìè Lattice.Core.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="layoutManager">Ýêçåìïëÿð ILayoutService äëÿ óïðàâëåíèÿ îêíàìè.</param>
|
||||||
|
/// <param name="contextService">Ýêçåìïëÿð IContextService äëÿ óïðàâëåíèÿ êíîïêàìè òóëáàðà.</param>
|
||||||
|
/// <param name="actions">Ïîëíûé ñïèñîê îïðåäåëåíèé êîìàíä (ActionDefinition).</param>
|
||||||
|
public void Initialize(ILayoutService layoutManager, IContextService contextService, IEnumerable<ActionDefinition> actions)
|
||||||
|
{
|
||||||
|
_layoutManager = layoutManager;
|
||||||
|
_contextService = contextService;
|
||||||
|
_allActions = actions;
|
||||||
|
|
||||||
|
// Ñâÿçûâàåì âèçóàëüíûé õîñò äîêèíãà ñ ëîãè÷åñêèì äâèæêîì
|
||||||
|
MainDockHost.Manager = _layoutManager;
|
||||||
|
|
||||||
|
// Ïîäïèñûâàåìñÿ íà ñìåíó êîíòåêñòà (âûçûâàåòñÿ ïðè ïåðåêëþ÷åíèè âêëàäîê â Lattice.UI)
|
||||||
|
_contextService.ContextChanged += (s, newContext) =>
|
||||||
|
{
|
||||||
|
// Îáíîâëÿåì òóëáàð â ïîòîêå ïîëüçîâàòåëüñêîãî èíòåðôåéñà
|
||||||
|
this.DispatcherQueue.TryEnqueue(() =>
|
||||||
|
{
|
||||||
|
StudioToolbar.UpdateItems(_allActions, newContext);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Íà÷àëüíàÿ èíèöèàëèçàöèÿ òóëáàðà òåêóùèì êîíòåêñòîì
|
||||||
|
StudioToolbar.UpdateItems(_allActions, _contextService.CurrentContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Íàñòðàèâàåò èíòåãðàöèþ ñ íàòèâíûì îêíîì Windows 11 (TitleBar è Backdrop).
|
||||||
|
/// Âûçûâàåòñÿ èç MainWindow ïðèëîæåíèÿ.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="window">Òåêóùåå îêíî WinUI 3.</param>
|
||||||
|
public void SetupWindow(Window window)
|
||||||
|
{
|
||||||
|
// 1. Ðàñøèðÿåì êîíòåíò â îáëàñòü çàãîëîâêà (Windowing SDK 1.8)
|
||||||
|
window.ExtendsContentIntoTitleBar = true;
|
||||||
|
|
||||||
|
// 2. Óêàçûâàåì íàòèâíûé TitleBar äëÿ óïðàâëåíèÿ ïåðåòàñêèâàíèåì îêíà
|
||||||
|
if (AppTitleBar != null)
|
||||||
|
{
|
||||||
|
window.SetTitleBar(AppTitleBar);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Ïðèìåíÿåì Mica Alt äëÿ ôîíà â ñòèëå Visual Studio 2026
|
||||||
|
window.SystemBackdrop = new MicaBackdrop()
|
||||||
|
{
|
||||||
|
Kind = MicaKind.BaseAlt
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ïîäêëþ÷àåò ñåðâèñ óâåäîìëåíèé ê îáîëî÷êå.
|
||||||
|
/// </summary>
|
||||||
|
public void InitializeNotifications(INotificationService notificationService)
|
||||||
|
{
|
||||||
|
notificationService.NotificationReceived += (s, e) =>
|
||||||
|
{
|
||||||
|
this.DispatcherQueue.TryEnqueue(() =>
|
||||||
|
{
|
||||||
|
var infoBar = new InfoBar
|
||||||
|
{
|
||||||
|
Message = e.Message,
|
||||||
|
Severity = MapSeverity(e.Severity),
|
||||||
|
IsOpen = true,
|
||||||
|
Style = (Style)Application.Current.Resources["LatticeNotificationStyle"]
|
||||||
|
};
|
||||||
|
|
||||||
|
// Àâòîìàòè÷åñêîå çàêðûòèå
|
||||||
|
if (e.DurationSeconds > 0)
|
||||||
|
{
|
||||||
|
var timer = new DispatcherTimer
|
||||||
|
{
|
||||||
|
Interval = TimeSpan.FromSeconds(e.DurationSeconds)
|
||||||
|
};
|
||||||
|
timer.Tick += (st, et) => { infoBar.IsOpen = false; timer.Stop(); };
|
||||||
|
timer.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Óäàëåíèå èç ïàìÿòè ïîñëå çàêðûòèÿ
|
||||||
|
infoBar.CloseButtonClick += (sender, args) => NotificationArea.Children.Remove(infoBar);
|
||||||
|
|
||||||
|
NotificationArea.Children.Insert(0, infoBar); // Íîâûå ñâåðõó
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
private InfoBarSeverity MapSeverity(NotificationSeverity severity) => severity switch
|
||||||
|
{
|
||||||
|
NotificationSeverity.Error => InfoBarSeverity.Error,
|
||||||
|
NotificationSeverity.Warning => InfoBarSeverity.Warning,
|
||||||
|
NotificationSeverity.Success => InfoBarSeverity.Success,
|
||||||
|
_ => InfoBarSeverity.Informational
|
||||||
|
};
|
||||||
|
}
|
||||||
18
Lattice.Studio/Controls/LatticeStudioWindow.xaml
Normal file
18
Lattice.Studio/Controls/LatticeStudioWindow.xaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Window
|
||||||
|
x:Class="Lattice.Studio.Controls.LatticeStudioWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="using:Lattice.Studio.Controls"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Title="LatticeStudioWindow">
|
||||||
|
|
||||||
|
<Window.SystemBackdrop>
|
||||||
|
<MicaBackdrop />
|
||||||
|
</Window.SystemBackdrop>
|
||||||
|
|
||||||
|
<local:LatticeStudioShell x:Name="ShellRoot" />
|
||||||
|
|
||||||
|
</Window>
|
||||||
116
Lattice.Studio/Controls/LatticeStudioWindow.xaml.cs
Normal file
116
Lattice.Studio/Controls/LatticeStudioWindow.xaml.cs
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
using Lattice.Core.Abstractions;
|
||||||
|
using Lattice.Core.Models;
|
||||||
|
using Lattice.Core.Services;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
|
||||||
|
namespace Lattice.Studio.Controls;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Îêíî âåðõíåãî óðîâíÿ, ñîäåðæàùåå LatticeStudioShell.
|
||||||
|
/// Îò íåãî íàñëåäóåòñÿ ðåàëüíîå ïðèëîæåíèå.
|
||||||
|
/// </summary>
|
||||||
|
public partial class LatticeStudioWindow : Window
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Âíóòðåííèé âèçóàëüíûé Shell.
|
||||||
|
/// </summary>
|
||||||
|
public LatticeStudioShell Shell => ShellRoot;
|
||||||
|
|
||||||
|
public LatticeStudioWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
// Ñòàðòîâûé çàãîëîâîê
|
||||||
|
Title = "Lattice IDE";
|
||||||
|
Shell.Title = Title;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ïåðåîïðåäåëÿåì Title, ÷òîáû ñèíõðîíèçèðîâàòü åãî ñ Shell.
|
||||||
|
/// </summary>
|
||||||
|
public new string Title
|
||||||
|
{
|
||||||
|
get => base.Title;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
base.Title = value;
|
||||||
|
if (ShellRoot is not null)
|
||||||
|
{
|
||||||
|
ShellRoot.Title = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------
|
||||||
|
// Ïðîêñèðóåì ñâîéñòâà Shell íàðóæó
|
||||||
|
// --------------------------------------------------------------------
|
||||||
|
|
||||||
|
public string Subtitle
|
||||||
|
{
|
||||||
|
get => Shell.Subtitle;
|
||||||
|
set => Shell.Subtitle = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IconSource TitleBarIcon
|
||||||
|
{
|
||||||
|
get => Shell.TitleBarIcon;
|
||||||
|
set => Shell.TitleBarIcon = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object MenuContent
|
||||||
|
{
|
||||||
|
get => Shell.MenuContent;
|
||||||
|
set => Shell.MenuContent = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object StatusContent
|
||||||
|
{
|
||||||
|
get => Shell.StatusContent;
|
||||||
|
set => Shell.StatusContent = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------
|
||||||
|
// Èíèöèàëèçàöèÿ ñåðâèñîâ Lattice.Core
|
||||||
|
// --------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Åäèíàÿ òî÷êà èíèöèàëèçàöèè îêíà.
|
||||||
|
/// Âûçûâàåòñÿ èç êîíñòðóêòîðà íàñëåäíèêà (MainWindow).
|
||||||
|
/// </summary>
|
||||||
|
public void Initialize(
|
||||||
|
ILayoutService layoutService,
|
||||||
|
IContextService contextService,
|
||||||
|
IEnumerable<ActionDefinition> actions,
|
||||||
|
INotificationService notificationService)
|
||||||
|
{
|
||||||
|
Shell.Initialize(layoutService, contextService, actions);
|
||||||
|
Shell.InitializeNotifications(notificationService);
|
||||||
|
Shell.SetupWindow(this);
|
||||||
|
|
||||||
|
OnShellInitialized();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Èíèöèàëèçèðóåò îêíî ñòàíäàðòíûìè ðåàëèçàöèÿìè ñåðâèñîâ Lattice.Core.
|
||||||
|
/// Ïîäõîäèò äëÿ áûñòðûõ ïðîòîòèïîâ è ïðîñòûõ ïðèëîæåíèé.
|
||||||
|
/// </summary>
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
var layout = new LayoutService();
|
||||||
|
var context = new ContextService();
|
||||||
|
var notifications = new NotificationService();
|
||||||
|
|
||||||
|
// Ïóñòîé íàáîð êîìàíä ïî óìîë÷àíèþ
|
||||||
|
var actions = Enumerable.Empty<ActionDefinition>();
|
||||||
|
|
||||||
|
Initialize(layout, context, actions, notifications);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Õóê äëÿ íàñëåäíèêîâ — âûçûâàåòñÿ ïîñëå ïîëíîé èíèöèàëèçàöèè Shell.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void OnShellInitialized()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
45
Lattice.Studio/Lattice.Studio.csproj
Normal file
45
Lattice.Studio/Lattice.Studio.csproj
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFrameworks>net8.0-windows10.0.19041.0;net9.0-windows10.0.19041.0;net10.0-windows10.0.19041.0</TargetFrameworks>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<AssemblyName>Lattice.Studio</AssemblyName>
|
||||||
|
<RootNamespace>Lattice.Studio</RootNamespace>
|
||||||
|
|
||||||
|
<Authors>FrigaT</Authors>
|
||||||
|
<Company>FrigaT</Company>
|
||||||
|
<RepositoryUrl>https://git.frigat.duckdns.org/FrigaT/Lattice</RepositoryUrl>
|
||||||
|
<PackageProjectUrl>https://git.frigat.duckdns.org/FrigaT/Lattice</PackageProjectUrl>
|
||||||
|
|
||||||
|
<UseWinUI>true</UseWinUI>
|
||||||
|
<IsTrimmable>false</IsTrimmable>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="Controls\LatticeStudioWindow.xaml" />
|
||||||
|
<None Remove="Themes\StudioThemes.xaml" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.7175" />
|
||||||
|
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.8.251106002" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Lattice.Core\Lattice.Core.csproj" />
|
||||||
|
<ProjectReference Include="..\Lattice.UI\Lattice.UI.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="Controls\LatticeStudioWindow.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="Themes\StudioThemes.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
182
Lattice.Studio/README.md
Normal file
182
Lattice.Studio/README.md
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
# **Lattice.Studio**
|
||||||
|
|
||||||
|
`Lattice.Studio` — это модуль, предоставляющий готовую визуальную оболочку для приложений на WinUI 3, оформленную в стиле Visual Studio 2026.
|
||||||
|
Он включает:
|
||||||
|
|
||||||
|
- нативный Windows 11 TitleBar,
|
||||||
|
- слот для меню,
|
||||||
|
- контекстный тулбар,
|
||||||
|
- область уведомлений,
|
||||||
|
- хост для докинга,
|
||||||
|
- статус‑бар,
|
||||||
|
- тему оформления (Dark/Light),
|
||||||
|
- и главное — **готовое окно верхнего уровня `LatticeStudioWindow`**.
|
||||||
|
|
||||||
|
`LatticeStudioShell` служит визуальным контейнером, а `LatticeStudioWindow` — удобной точкой входа для реальных приложений.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 Состав модуля
|
||||||
|
|
||||||
|
### **1. LatticeStudioShell.xaml**
|
||||||
|
Определяет визуальную структуру IDE‑оболочки:
|
||||||
|
|
||||||
|
- **TitleBar**:
|
||||||
|
- заголовок,
|
||||||
|
- иконка (`IconSource`),
|
||||||
|
- слот для меню (`MenuContent`),
|
||||||
|
- drag‑region.
|
||||||
|
- **LatticeContextualToolbar** — контекстный тулбар.
|
||||||
|
- **NotificationArea** — стек уведомлений.
|
||||||
|
- **LatticeDockHost** — хост для системы докинга.
|
||||||
|
- **StatusContent** — слот для статус‑бара.
|
||||||
|
|
||||||
|
### **2. LatticeStudioShell.xaml.cs**
|
||||||
|
Реализует логику оболочки:
|
||||||
|
|
||||||
|
- интеграция с сервисами:
|
||||||
|
- `ILayoutService`,
|
||||||
|
- `IContextService`,
|
||||||
|
- `INotificationService`;
|
||||||
|
- обновление тулбара при смене контекста;
|
||||||
|
- отображение уведомлений через `InfoBar`;
|
||||||
|
- настройка окна:
|
||||||
|
- `ExtendsContentIntoTitleBar`,
|
||||||
|
- кастомный TitleBar,
|
||||||
|
- Mica Alt backdrop.
|
||||||
|
|
||||||
|
### **3. LatticeStudioWindow.xaml / .cs**
|
||||||
|
Готовое окно верхнего уровня:
|
||||||
|
|
||||||
|
- содержит `LatticeStudioShell`,
|
||||||
|
- проксирует его свойства наружу:
|
||||||
|
- `Title`,
|
||||||
|
- `Subtitle`,
|
||||||
|
- `MenuContent`,
|
||||||
|
- `StatusContent`,
|
||||||
|
- `TitleBarIcon`,
|
||||||
|
- предоставляет удобный метод инициализации:
|
||||||
|
- `Initialize(...)` — с пользовательскими сервисами,
|
||||||
|
- `Initialize()` — со стандартными реализациями.
|
||||||
|
|
||||||
|
Используется как базовый класс для `MainWindow`.
|
||||||
|
|
||||||
|
### **4. Themes/StudioThemes.xaml**
|
||||||
|
Содержит тему оформления:
|
||||||
|
|
||||||
|
- цвета панелей,
|
||||||
|
- цвета заголовков,
|
||||||
|
- акцентные цвета,
|
||||||
|
- поддержка Light/Dark.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✨ Возможности
|
||||||
|
|
||||||
|
- Готовая IDE‑оболочка для WinUI 3.
|
||||||
|
- Интеграция с Lattice.Core:
|
||||||
|
- докинг,
|
||||||
|
- контексты,
|
||||||
|
- команды.
|
||||||
|
- Нативный Windows 11 TitleBar.
|
||||||
|
- Встроенная система уведомлений.
|
||||||
|
- Слоты для меню и статус‑бара.
|
||||||
|
- Тема в стиле Visual Studio 2026.
|
||||||
|
- Готовое окно `LatticeStudioWindow` для быстрого старта.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Быстрый старт
|
||||||
|
|
||||||
|
## 🅰 Вариант 1 — минимальный XAML (всё в C#)
|
||||||
|
|
||||||
|
### **MainWindow.xaml**
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<studio:LatticeStudioWindow
|
||||||
|
x:Class="MyApp.MainWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:studio="using:Lattice.Studio.Controls">
|
||||||
|
</studio:LatticeStudioWindow>
|
||||||
|
```
|
||||||
|
|
||||||
|
### **MainWindow.xaml.cs**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public sealed partial class MainWindow : LatticeStudioWindow
|
||||||
|
{
|
||||||
|
public MainWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
Title = "My App";
|
||||||
|
|
||||||
|
MenuContent = BuildMenu();
|
||||||
|
StatusContent = new TextBlock { Text = "Ready" };
|
||||||
|
|
||||||
|
Initialize(); // стандартные сервисы
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🅱 Вариант 2 — декларативный XAML (меню/статус‑бар в XAML)
|
||||||
|
|
||||||
|
### **MainWindow.xaml**
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<studio:LatticeStudioWindow
|
||||||
|
x:Class="MyApp.MainWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:studio="using:Lattice.Studio.Controls">
|
||||||
|
|
||||||
|
<studio:LatticeStudioWindow.MenuContent>
|
||||||
|
<MenuBar>
|
||||||
|
<MenuBarItem Title="File">
|
||||||
|
<MenuFlyoutItem Text="New" />
|
||||||
|
<MenuFlyoutItem Text="Open" />
|
||||||
|
<MenuFlyoutSeparator />
|
||||||
|
<MenuFlyoutItem Text="Exit" />
|
||||||
|
</MenuBarItem>
|
||||||
|
</MenuBar>
|
||||||
|
</studio:LatticeStudioWindow.MenuContent>
|
||||||
|
|
||||||
|
<studio:LatticeStudioWindow.StatusContent>
|
||||||
|
<TextBlock Text="Ready" Margin="8,0"/>
|
||||||
|
</studio:LatticeStudioWindow.StatusContent>
|
||||||
|
|
||||||
|
</studio:LatticeStudioWindow>
|
||||||
|
```
|
||||||
|
|
||||||
|
### **MainWindow.xaml.cs**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public sealed partial class MainWindow : LatticeStudioWindow
|
||||||
|
{
|
||||||
|
public MainWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
Title = "My App";
|
||||||
|
Initialize(); // стандартные сервисы
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧩 Инициализация вручную
|
||||||
|
|
||||||
|
Если нужны свои сервисы:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
Initialize(layoutService, contextService, actions, notificationService);
|
||||||
|
```
|
||||||
|
|
||||||
|
Если нужен быстрый старт:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
Initialize();
|
||||||
|
```
|
||||||
22
Lattice.Studio/Themes/StudioThemes.xaml
Normal file
22
Lattice.Studio/Themes/StudioThemes.xaml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ResourceDictionary
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
>
|
||||||
|
|
||||||
|
<!-- Цвета Visual Studio 2026 (Dark) -->
|
||||||
|
<ResourceDictionary.ThemeDictionaries>
|
||||||
|
<ResourceDictionary x:Key="Dark">
|
||||||
|
<SolidColorBrush x:Key="LatticeStatusBarBackground" Color="#FF007ACC"/>
|
||||||
|
<SolidColorBrush x:Key="LatticePaneHeaderBackground" Color="#FF2D2D30"/>
|
||||||
|
<SolidColorBrush x:Key="LatticePaneBackground" Color="#FF1E1E1E"/>
|
||||||
|
<SolidColorBrush x:Key="LatticeActiveHeaderBrush" Color="#FF007ACC"/>
|
||||||
|
</ResourceDictionary>
|
||||||
|
<ResourceDictionary x:Key="Light">
|
||||||
|
<SolidColorBrush x:Key="LatticeStatusBarBackground" Color="#FF007ACC"/>
|
||||||
|
<SolidColorBrush x:Key="LatticePaneHeaderBackground" Color="#FFEEEEF2"/>
|
||||||
|
<SolidColorBrush x:Key="LatticePaneBackground" Color="#FFFFFFFF"/>
|
||||||
|
<SolidColorBrush x:Key="LatticeActiveHeaderBrush" Color="#FF007ACC"/>
|
||||||
|
</ResourceDictionary>
|
||||||
|
</ResourceDictionary.ThemeDictionaries>
|
||||||
|
</ResourceDictionary>
|
||||||
@@ -16,12 +16,7 @@
|
|||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Remove="Themes\Generic.xaml" />
|
<None Remove="Themes\Styles\LatticeNotification.xaml" />
|
||||||
<None Remove="Themes\Styles\DockAnchorOverlay.xaml" />
|
|
||||||
<None Remove="Themes\Styles\LatticeDockHost.xaml" />
|
|
||||||
<None Remove="Themes\Styles\LatticePane.xaml" />
|
|
||||||
<None Remove="Themes\Styles\LatticeSplitter.xaml" />
|
|
||||||
<None Remove="Themes\Styles\SharedResources.xaml" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -34,40 +29,8 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Page Update="Themes\Styles\SharedResources.xaml">
|
<Page Update="Themes\Styles\LatticeNotification.xaml">
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
</Page>
|
</Page>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Page Update="Themes\Styles\DockAnchorOverlay.xaml">
|
|
||||||
<Generator>MSBuild:Compile</Generator>
|
|
||||||
</Page>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Page Update="Themes\Styles\LatticeSplitter.xaml">
|
|
||||||
<Generator>MSBuild:Compile</Generator>
|
|
||||||
</Page>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Page Update="Themes\Styles\LatticeDockHost.xaml">
|
|
||||||
<Generator>MSBuild:Compile</Generator>
|
|
||||||
</Page>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Page Update="Themes\Generic.xaml">
|
|
||||||
<Generator>MSBuild:Compile</Generator>
|
|
||||||
</Page>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Page Update="Themes\Styles\LatticePane.xaml">
|
|
||||||
<Generator>MSBuild:Compile</Generator>
|
|
||||||
</Page>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
<ResourceDictionary Source="ms-appx:///Lattice.UI/Themes/Styles/LatticePane.xaml" />
|
<ResourceDictionary Source="ms-appx:///Lattice.UI/Themes/Styles/LatticePane.xaml" />
|
||||||
<ResourceDictionary Source="ms-appx:///Lattice.UI/Themes/Styles/LatticeSplitter.xaml" />
|
<ResourceDictionary Source="ms-appx:///Lattice.UI/Themes/Styles/LatticeSplitter.xaml" />
|
||||||
<ResourceDictionary Source="ms-appx:///Lattice.UI/Themes/Styles/DockAnchorOverlay.xaml" />
|
<ResourceDictionary Source="ms-appx:///Lattice.UI/Themes/Styles/DockAnchorOverlay.xaml" />
|
||||||
|
<ResourceDictionary Source="ms-appx:///Lattice.UI/Themes/Styles/LatticeNotification.xaml" />
|
||||||
</ResourceDictionary.MergedDictionaries>
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|||||||
15
Lattice.UI/Themes/Styles/LatticeNotification.xaml
Normal file
15
Lattice.UI/Themes/Styles/LatticeNotification.xaml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ResourceDictionary
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
>
|
||||||
|
|
||||||
|
<Style x:Key="LatticeNotificationStyle" TargetType="InfoBar">
|
||||||
|
<Setter Property="Margin" Value="12,4" />
|
||||||
|
<Setter Property="CornerRadius" Value="8" />
|
||||||
|
<Setter Property="IsIconVisible" Value="True" />
|
||||||
|
<!-- В 2026 году используем полупрозрачность Acrylic для уведомлений -->
|
||||||
|
<Setter Property="Background" Value="{ThemeResource SystemControlAcrylicElementBrush}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
</ResourceDictionary>
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
<Solution>
|
<Solution>
|
||||||
<Project Path="Lattice.Core/Lattice.Core.csproj" />
|
<Project Path="Lattice.Core/Lattice.Core.csproj" />
|
||||||
|
<Project Path="Lattice.Studio/Lattice.Studio.csproj" Id="c866e999-c303-47ae-848c-5a911894ec1d" />
|
||||||
<Project Path="Lattice.UI/Lattice.UI.csproj" Id="931bdfd9-8cf2-479a-895e-65facdb0a7ce" />
|
<Project Path="Lattice.UI/Lattice.UI.csproj" Id="931bdfd9-8cf2-479a-895e-65facdb0a7ce" />
|
||||||
</Solution>
|
</Solution>
|
||||||
|
|||||||
Reference in New Issue
Block a user