BASE changed interface
This commit is contained in:
commit
256bb084c5
30 files changed
+2836
No files matched your search
@@ -0,0 +1,10 @@
|
||||
<Window x:Class="FoxBrowser.UI.ExtensionPopupWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
WindowStyle="None" ResizeMode="NoResize" ShowInTaskbar="False"
|
||||
Width="360" Height="520" MaxHeight="600"
|
||||
Background="#202328" AllowsTransparency="False">
|
||||
<Border BorderBrush="#59616D" BorderThickness="1" Background="#202328">
|
||||
<Grid x:Name="PopupHost" />
|
||||
</Border>
|
||||
</Window>
|
||||
@@ -0,0 +1,39 @@
|
||||
using System.Windows;
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using Microsoft.Web.WebView2.Wpf;
|
||||
|
||||
namespace FoxBrowser.UI;
|
||||
|
||||
public partial class ExtensionPopupWindow : Window
|
||||
{
|
||||
private readonly CoreWebView2Environment _environment;
|
||||
private readonly string _extensionId;
|
||||
private readonly string _popupPage;
|
||||
|
||||
public ExtensionPopupWindow(CoreWebView2Environment environment, string extensionId, string popupPage, System.Drawing.Point screenPosition)
|
||||
{
|
||||
InitializeComponent();
|
||||
_environment = environment;
|
||||
_extensionId = extensionId;
|
||||
_popupPage = popupPage;
|
||||
Left = screenPosition.X;
|
||||
Top = screenPosition.Y;
|
||||
Deactivated += (_, _) => Close();
|
||||
Loaded += async (_, _) => await InitializeAsync();
|
||||
}
|
||||
|
||||
private async Task InitializeAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var view = new WebView2();
|
||||
PopupHost.Children.Add(view);
|
||||
await view.EnsureCoreWebView2Async(_environment);
|
||||
view.CoreWebView2.Navigate($"chrome-extension://{_extensionId}/{_popupPage}");
|
||||
}
|
||||
catch
|
||||
{
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<Window x:Class="FoxBrowser.UI.ExtensionsSettingsWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="Розширення" Width="860" Height="620" MinWidth="560" MinHeight="360"
|
||||
Background="#15171A" Foreground="#F2F4F7">
|
||||
<DockPanel Margin="14">
|
||||
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,12,0,0">
|
||||
<Button Content="Магазин розширень" ToolTip="Відкрити Chrome Web Store; встановлення з магазину не підтримується API WebView2" Click="Store_Click" Padding="12,6" />
|
||||
<Button Content="Імпортувати розширення…" Click="Add_Click" Padding="12,6" Margin="8,0,0,0" />
|
||||
<Button Content="Закрити" Click="Close_Click" Padding="12,6" Margin="8,0,0,0" />
|
||||
</StackPanel>
|
||||
<TextBlock DockPanel.Dock="Top" Text="Chrome-сумісні unpacked extensions" FontSize="16" FontWeight="SemiBold" Margin="0,0,0,4" />
|
||||
<TextBlock DockPanel.Dock="Top" Text="Кнопка «Налаштування» відкриває сторінку, вказану в manifest.json: options_ui.page або options_page." Foreground="#AEB4BE" TextWrapping="Wrap" Margin="0,0,0,4" />
|
||||
<TextBlock DockPanel.Dock="Top" Text="Профіль WebView2: папка Profile поруч із FoxBrowser.exe (у захищеній папці Windows використовується стандартний профіль %LOCALAPPDATA%\FoxBrowser\WebView2). WebView2 зберігає реєстрацію розширення, а файли unpacked залишаються у вибраній тобою папці." Foreground="#AEB4BE" TextWrapping="Wrap" Margin="0,0,0,10" />
|
||||
<TextBlock x:Name="ExtensionsStatus" DockPanel.Dock="Top" Foreground="#FFCC66" TextWrapping="Wrap" Margin="0,0,0,8" />
|
||||
<Grid>
|
||||
<ListBox x:Name="ExtensionList" Background="#202328" BorderBrush="#454C57" Foreground="#F2F4F7">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid Margin="8">
|
||||
<Grid.ColumnDefinitions><ColumnDefinition Width="*" /><ColumnDefinition Width="Auto" /><ColumnDefinition Width="Auto" /><ColumnDefinition Width="Auto" /><ColumnDefinition Width="Auto" /></Grid.ColumnDefinitions>
|
||||
<StackPanel>
|
||||
<TextBlock Text="{Binding Name}" FontWeight="SemiBold" />
|
||||
<TextBlock Text="{Binding Id}" FontSize="11" Foreground="#AEB4BE" />
|
||||
</StackPanel>
|
||||
<CheckBox Grid.Column="1" IsChecked="{Binding IsEnabled, Mode=OneWay}" Tag="{Binding}" Click="Toggle_Click" Content="Увімкнено" VerticalAlignment="Center" Margin="8,0" />
|
||||
<CheckBox x:Name="PinCheckBox" Grid.Column="2" Content="Закріпити" Tag="{Binding}" Click="Pin_Click" VerticalAlignment="Center" Margin="8,0" />
|
||||
<Button Grid.Column="3" Content="Налаштування" Tag="{Binding}" Click="Options_Click" Padding="8,4" Margin="0,0,6,0" />
|
||||
<Button Grid.Column="4" Content="Видалити" Tag="{Binding}" Click="Remove_Click" Padding="8,4" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
<Grid x:Name="OptionsHost" Visibility="Collapsed" Background="#111317">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Button Grid.Row="0" Content="← Назад до списку розширень" Click="BackToList_Click" HorizontalAlignment="Left" Padding="10,6" Margin="8" Panel.ZIndex="2" />
|
||||
<Grid x:Name="OptionsWebViewHost" Grid.Row="1" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
</Window>
|
||||
@@ -0,0 +1,155 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using FoxBrowser.Core;
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using Microsoft.Web.WebView2.Wpf;
|
||||
using Forms = System.Windows.Forms;
|
||||
using WpfButton = System.Windows.Controls.Button;
|
||||
using WpfCheckBox = System.Windows.Controls.CheckBox;
|
||||
|
||||
namespace FoxBrowser.UI;
|
||||
|
||||
public partial class ExtensionsSettingsWindow : Window
|
||||
{
|
||||
private readonly ExtensionManager _manager;
|
||||
private readonly CoreWebView2Environment _environment;
|
||||
private readonly Func<Task> _openStore;
|
||||
private WebView2? _optionsView;
|
||||
|
||||
public ExtensionsSettingsWindow(ExtensionManager manager, CoreWebView2Environment environment, Func<Task> openStore, bool extensionsAvailable)
|
||||
{
|
||||
InitializeComponent();
|
||||
_manager = manager;
|
||||
_environment = environment;
|
||||
_openStore = openStore;
|
||||
ExtensionList.ItemsSource = _manager.Extensions;
|
||||
if (!extensionsAvailable)
|
||||
{
|
||||
ExtensionsStatus.Text = "Розширення недоступні в цьому сеансі: FoxBrowser запустився у режимі відновлення профілю. Перезапусти FoxBrowser для повернення до звичайного профілю.";
|
||||
ExtensionList.IsEnabled = false;
|
||||
}
|
||||
Loaded += async (_, _) =>
|
||||
{
|
||||
await _manager.RefreshAsync();
|
||||
Dispatcher.BeginInvoke(RefreshPinCheckboxes);
|
||||
};
|
||||
}
|
||||
|
||||
private async void Add_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
using var dialog = new Forms.FolderBrowserDialog { Description = "Виберіть розпаковану папку extension з manifest.json" };
|
||||
var owner = new WindowInteropHelper(this).Handle;
|
||||
if (dialog.ShowDialog(new NativeWindowWrapper(owner)) != Forms.DialogResult.OK) return;
|
||||
try
|
||||
{
|
||||
await _manager.AddExtensionAsync(dialog.SelectedPath);
|
||||
Dispatcher.BeginInvoke(RefreshPinCheckboxes);
|
||||
}
|
||||
catch (Exception ex) { ShowError(ex.Message); }
|
||||
}
|
||||
|
||||
private async void Toggle_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if ((sender as WpfCheckBox)?.Tag is not CoreWebView2BrowserExtension extension) return;
|
||||
var checkBox = (WpfCheckBox)sender;
|
||||
try { await _manager.SetEnabledAsync(extension, checkBox.IsChecked == true); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
checkBox.IsChecked = extension.IsEnabled;
|
||||
ShowError(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void Pin_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if ((sender as WpfCheckBox)?.Tag is CoreWebView2BrowserExtension extension && sender is WpfCheckBox checkBox)
|
||||
_manager.SetPinned(extension, checkBox.IsChecked == true);
|
||||
}
|
||||
|
||||
private async void Options_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if ((sender as WpfButton)?.Tag is not CoreWebView2BrowserExtension extension) return;
|
||||
var optionsPage = _manager.GetOptionsPage(extension.Id);
|
||||
if (string.IsNullOrWhiteSpace(optionsPage))
|
||||
{
|
||||
ShowError("Для цього extension не знайдено options_ui.page або options_page. Видали його та імпортуй папку ще раз, щоб FoxBrowser прочитав manifest.json.");
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
_optionsView ??= await CreateOptionsViewAsync();
|
||||
ExtensionList.Visibility = Visibility.Collapsed;
|
||||
OptionsHost.Visibility = Visibility.Visible;
|
||||
_optionsView.CoreWebView2.Navigate($"chrome-extension://{extension.Id}/{optionsPage}");
|
||||
}
|
||||
catch (Exception ex) { ShowError($"Не вдалося відкрити налаштування extension: {ex.Message}"); }
|
||||
}
|
||||
|
||||
private void BackToList_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
OptionsHost.Visibility = Visibility.Collapsed;
|
||||
ExtensionList.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
private async Task<WebView2> CreateOptionsViewAsync()
|
||||
{
|
||||
var view = new WebView2 { Visibility = Visibility.Visible };
|
||||
OptionsWebViewHost.Children.Add(view);
|
||||
await view.EnsureCoreWebView2Async(_environment);
|
||||
return view;
|
||||
}
|
||||
|
||||
private async void Store_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Close();
|
||||
await _openStore();
|
||||
}
|
||||
|
||||
private async void Remove_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if ((sender as WpfButton)?.Tag is CoreWebView2BrowserExtension extension)
|
||||
{
|
||||
try { await _manager.RemoveAsync(extension); }
|
||||
catch (Exception ex) { ShowError(ex.Message); }
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshPinCheckboxes()
|
||||
{
|
||||
foreach (var extension in _manager.Extensions)
|
||||
{
|
||||
if (ExtensionList.ItemContainerGenerator.ContainerFromItem(extension) is not ListBoxItem item) continue;
|
||||
var pin = FindVisualChild<WpfCheckBox>(item, "PinCheckBox");
|
||||
if (pin is not null) pin.IsChecked = _manager.IsPinned(extension);
|
||||
}
|
||||
}
|
||||
|
||||
private static T? FindVisualChild<T>(DependencyObject parent, string name) where T : FrameworkElement
|
||||
{
|
||||
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
|
||||
{
|
||||
var child = VisualTreeHelper.GetChild(parent, i);
|
||||
if (child is T match && match.Name == name) return match;
|
||||
var nested = FindVisualChild<T>(child, name);
|
||||
if (nested is not null) return nested;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void ShowError(string message) => System.Windows.MessageBox.Show(this, message, "Розширення", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
private void Close_Click(object sender, RoutedEventArgs e) => Close();
|
||||
|
||||
protected override void OnClosed(EventArgs e)
|
||||
{
|
||||
_optionsView?.Dispose();
|
||||
base.OnClosed(e);
|
||||
}
|
||||
|
||||
private sealed class NativeWindowWrapper : System.Windows.Forms.IWin32Window
|
||||
{
|
||||
public NativeWindowWrapper(nint handle) => Handle = handle;
|
||||
public nint Handle { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
using System.Drawing;
|
||||
using System.Windows;
|
||||
using Forms = System.Windows.Forms;
|
||||
|
||||
namespace FoxBrowser.UI;
|
||||
|
||||
public sealed class TrayIconManager : IDisposable
|
||||
{
|
||||
private readonly Forms.NotifyIcon _icon;
|
||||
private readonly Window _window;
|
||||
private readonly Action _exit;
|
||||
|
||||
public TrayIconManager(Window window, Action exit)
|
||||
{
|
||||
_window = window;
|
||||
_exit = exit;
|
||||
|
||||
Icon? icon = null;
|
||||
try
|
||||
{
|
||||
var icoPath = System.IO.Path.Combine(AppContext.BaseDirectory, "FoxBrowser.ico");
|
||||
if (System.IO.File.Exists(icoPath)) icon = new Icon(icoPath);
|
||||
}
|
||||
catch { }
|
||||
|
||||
_icon = new Forms.NotifyIcon
|
||||
{
|
||||
Icon = icon ?? SystemIcons.Application,
|
||||
Text = "FoxBrowser",
|
||||
Visible = true,
|
||||
ContextMenuStrip = BuildMenu()
|
||||
};
|
||||
_icon.MouseClick += OnMouseClick;
|
||||
}
|
||||
|
||||
public void MinimizeToTray()
|
||||
{
|
||||
_window.ShowInTaskbar = false;
|
||||
_window.Hide();
|
||||
}
|
||||
|
||||
public void ToggleWindow()
|
||||
{
|
||||
if (_window.IsVisible && _window.WindowState != WindowState.Minimized)
|
||||
{
|
||||
MinimizeToTray();
|
||||
return;
|
||||
}
|
||||
|
||||
_window.ShowInTaskbar = true;
|
||||
_window.Show();
|
||||
_window.WindowState = WindowState.Normal;
|
||||
_window.Activate();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_icon.MouseClick -= OnMouseClick;
|
||||
_icon.Visible = false;
|
||||
_icon.Dispose();
|
||||
}
|
||||
|
||||
private void OnMouseClick(object? sender, Forms.MouseEventArgs e)
|
||||
{
|
||||
if (e.Button == Forms.MouseButtons.Left) ToggleWindow();
|
||||
}
|
||||
|
||||
private Forms.ContextMenuStrip BuildMenu()
|
||||
{
|
||||
var menu = new Forms.ContextMenuStrip();
|
||||
menu.Items.Add("Показати / сховати", null, (_, _) => ToggleWindow());
|
||||
menu.Items.Add("Вийти", null, (_, _) => _exit());
|
||||
return menu;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<Window x:Class="FoxBrowser.UI.VpnSettingsWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="VPN — WireGuard for Windows" Width="620" Height="430" MinWidth="500" MinHeight="320"
|
||||
Background="#15171A" Foreground="#F2F4F7">
|
||||
<DockPanel Margin="14">
|
||||
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,12,0,0">
|
||||
<Button Content="Імпортувати .conf…" Click="Import_Click" Padding="12,6" />
|
||||
<Button Content="Connect" Click="Connect_Click" Padding="12,6" Margin="8,0,0,0" />
|
||||
<Button Content="Disconnect" Click="Disconnect_Click" Padding="12,6" Margin="8,0,0,0" />
|
||||
<Button Content="Закрити" Click="Close_Click" Padding="12,6" Margin="8,0,0,0" />
|
||||
</StackPanel>
|
||||
<TextBlock DockPanel.Dock="Top" Text="VPN через зовнішній WireGuard for Windows" FontSize="16" FontWeight="SemiBold" Margin="0,0,0,5" />
|
||||
<TextBlock DockPanel.Dock="Top" Text="FoxBrowser не вбудовує WireGuard: потрібна окрема офіційна інсталяція WireGuard for Windows." Foreground="#AEB4BE" TextWrapping="Wrap" Margin="0,0,0,12" />
|
||||
<StackPanel DockPanel.Dock="Bottom" Margin="0,12,0,0">
|
||||
<TextBlock Text="Статус" Foreground="#AEB4BE" />
|
||||
<TextBlock x:Name="StatusText" Text="Disconnected" FontSize="18" FontWeight="SemiBold" Margin="0,2,0,0" />
|
||||
<TextBlock x:Name="ErrorText" Foreground="#FF8C8C" TextWrapping="Wrap" Margin="0,6,0,0" />
|
||||
</StackPanel>
|
||||
<ListBox x:Name="ProfileList" Background="#202328" BorderBrush="#454C57" Foreground="#F2F4F7" DisplayMemberPath="Name" />
|
||||
</DockPanel>
|
||||
</Window>
|
||||
@@ -0,0 +1,72 @@
|
||||
using System.Windows;
|
||||
using FoxBrowser.Core;
|
||||
using Forms = System.Windows.Forms;
|
||||
|
||||
namespace FoxBrowser.UI;
|
||||
|
||||
public partial class VpnSettingsWindow : Window
|
||||
{
|
||||
private readonly VpnManager _vpn;
|
||||
|
||||
public VpnSettingsWindow(VpnManager vpn)
|
||||
{
|
||||
InitializeComponent();
|
||||
_vpn = vpn;
|
||||
ProfileList.ItemsSource = _vpn.Profiles;
|
||||
_vpn.StateChanged += OnStateChanged;
|
||||
RefreshState();
|
||||
Closed += (_, _) => _vpn.StateChanged -= OnStateChanged;
|
||||
}
|
||||
|
||||
private void Import_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
using var dialog = new Forms.OpenFileDialog
|
||||
{
|
||||
Filter = "WireGuard config (*.conf)|*.conf|All files (*.*)|*.*",
|
||||
Title = "Імпортувати WireGuard конфігурацію"
|
||||
};
|
||||
if (dialog.ShowDialog() != Forms.DialogResult.OK) return;
|
||||
try
|
||||
{
|
||||
var profile = _vpn.ImportTunnelConfig(dialog.FileName);
|
||||
ProfileList.SelectedItem = profile;
|
||||
RefreshState();
|
||||
}
|
||||
catch (Exception ex) { ShowError(ex.Message); }
|
||||
}
|
||||
|
||||
private async void Connect_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (ProfileList.SelectedItem is not VpnProfile profile)
|
||||
{
|
||||
ShowError("Спочатку імпортуйте або виберіть профіль .conf.");
|
||||
return;
|
||||
}
|
||||
try { await _vpn.ConnectAsync(profile); }
|
||||
catch (Exception ex) { ShowError(ex.Message); }
|
||||
finally { RefreshState(); }
|
||||
}
|
||||
|
||||
private async void Disconnect_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try { await _vpn.DisconnectAsync(); }
|
||||
catch (Exception ex) { ShowError(ex.Message); }
|
||||
finally { RefreshState(); }
|
||||
}
|
||||
|
||||
private void OnStateChanged(object? sender, EventArgs e) => Dispatcher.Invoke(RefreshState);
|
||||
|
||||
private void RefreshState()
|
||||
{
|
||||
StatusText.Text = _vpn.State.ToString();
|
||||
ErrorText.Text = _vpn.LastError ?? string.Empty;
|
||||
}
|
||||
|
||||
private void ShowError(string message)
|
||||
{
|
||||
ErrorText.Text = message;
|
||||
System.Windows.MessageBox.Show(this, message, "VPN", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
|
||||
private void Close_Click(object sender, RoutedEventArgs e) => Close();
|
||||
}
|
||||
Reference in new issue
Block a user