Files

687 lines
17 KiB
C#

using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Threading;
using AxCopilot.Services;
using AxCopilot.Services.Agent;
using Microsoft.Web.WebView2.Core;
using Microsoft.Web.WebView2.Wpf;
namespace AxCopilot.Views;
public class PreviewWindow : Window, IComponentConnector
{
private static PreviewWindow? _instance;
private readonly List<string> _tabs = new List<string>();
private string? _activeTab;
private bool _webViewInitialized;
private string? _selectedMood;
private static readonly string WebView2DataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "AxCopilot", "WebView2_Preview");
private static readonly HashSet<string> PreviewableExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".html", ".htm", ".md", ".csv", ".txt", ".json", ".xml", ".log" };
private const int WM_NCHITTEST = 132;
private const int HTLEFT = 10;
private const int HTRIGHT = 11;
private const int HTTOP = 12;
private const int HTTOPLEFT = 13;
private const int HTTOPRIGHT = 14;
private const int HTBOTTOM = 15;
private const int HTBOTTOMLEFT = 16;
private const int HTBOTTOMRIGHT = 17;
internal TextBlock TitleText;
internal TextBlock MaxBtnIcon;
internal StackPanel TabPanel;
internal WebView2 PreviewBrowser;
internal ScrollViewer TextScroll;
internal TextBlock TextContent;
internal DataGrid DataGridContent;
internal TextBlock EmptyMessage;
private bool _contentLoaded;
public static bool IsOpen => _instance != null && _instance.IsLoaded;
public PreviewWindow()
{
InitializeComponent();
base.Loaded += OnLoaded;
base.SourceInitialized += OnSourceInitialized;
base.KeyDown += delegate(object _, KeyEventArgs e)
{
//IL_0002: Unknown result type (might be due to invalid IL or missing references)
//IL_0009: Invalid comparison between Unknown and I4
if ((int)e.Key == 13)
{
Close();
}
};
base.StateChanged += delegate
{
MaxBtnIcon.Text = ((base.WindowState == WindowState.Maximized) ? "\ue923" : "\ue922");
};
base.Closed += delegate
{
_instance = null;
};
}
[DllImport("user32.dll")]
private static extern nint SendMessage(nint hWnd, int msg, nint wParam, nint lParam);
private void OnSourceInitialized(object? sender, EventArgs e)
{
((HwndSource)PresentationSource.FromVisual(this))?.AddHook(WndProc);
}
private nint WndProc(nint hwnd, int msg, nint wParam, nint lParam, ref bool handled)
{
//IL_0033: Unknown result type (might be due to invalid IL or missing references)
//IL_0038: Unknown result type (might be due to invalid IL or missing references)
//IL_003d: Unknown result type (might be due to invalid IL or missing references)
if (msg == 132)
{
Point val = PointFromScreen(new Point((double)(short)(((IntPtr)lParam).ToInt32() & 0xFFFF), (double)(short)((((IntPtr)lParam).ToInt32() >> 16) & 0xFFFF)));
double actualWidth = base.ActualWidth;
double actualHeight = base.ActualHeight;
if (((Point)(ref val)).X < 8.0 && ((Point)(ref val)).Y < 8.0)
{
handled = true;
return 13;
}
if (((Point)(ref val)).X > actualWidth - 8.0 && ((Point)(ref val)).Y < 8.0)
{
handled = true;
return 14;
}
if (((Point)(ref val)).X < 8.0 && ((Point)(ref val)).Y > actualHeight - 8.0)
{
handled = true;
return 16;
}
if (((Point)(ref val)).X > actualWidth - 8.0 && ((Point)(ref val)).Y > actualHeight - 8.0)
{
handled = true;
return 17;
}
if (((Point)(ref val)).X < 8.0)
{
handled = true;
return 10;
}
if (((Point)(ref val)).X > actualWidth - 8.0)
{
handled = true;
return 11;
}
if (((Point)(ref val)).Y < 8.0)
{
handled = true;
return 12;
}
if (((Point)(ref val)).Y > actualHeight - 8.0)
{
handled = true;
return 15;
}
}
return IntPtr.Zero;
}
public static void ShowPreview(string filePath, string? mood = null)
{
if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath))
{
return;
}
string item = Path.GetExtension(filePath).ToLowerInvariant();
if (!PreviewableExtensions.Contains(item))
{
return;
}
((DispatcherObject)Application.Current).Dispatcher.Invoke((Action)delegate
{
if (_instance == null || !_instance.IsLoaded)
{
_instance = new PreviewWindow();
Window mainWindow = Application.Current.MainWindow;
if (mainWindow != null)
{
foreach (ResourceDictionary mergedDictionary in mainWindow.Resources.MergedDictionaries)
{
_instance.Resources.MergedDictionaries.Add(mergedDictionary);
}
}
_instance._selectedMood = mood;
_instance.Show();
}
_instance.AddTab(filePath);
_instance.Activate();
});
}
public static void RefreshIfOpen(string filePath)
{
if (_instance == null || !_instance.IsLoaded)
{
return;
}
((DispatcherObject)Application.Current).Dispatcher.Invoke((Action)delegate
{
if (_instance._activeTab != null && string.Equals(_instance._activeTab, filePath, StringComparison.OrdinalIgnoreCase))
{
_instance.LoadContent(filePath);
}
else
{
string text = _instance._tabs.FirstOrDefault((string t) => string.Equals(t, filePath, StringComparison.OrdinalIgnoreCase));
if (text != null)
{
_instance._activeTab = text;
_instance.LoadContent(text);
_instance.RebuildTabs();
}
}
});
}
private async void OnLoaded(object sender, RoutedEventArgs e)
{
try
{
CoreWebView2Environment env = await CoreWebView2Environment.CreateAsync(null, WebView2DataFolder);
await PreviewBrowser.EnsureCoreWebView2Async(env);
_webViewInitialized = true;
if (_activeTab != null)
{
LoadContent(_activeTab);
}
}
catch (Exception ex)
{
Exception ex2 = ex;
LogService.Warn("PreviewWindow WebView2 초기화 실패: " + ex2.Message);
}
}
private void AddTab(string filePath)
{
if (!_tabs.Contains<string>(filePath, StringComparer.OrdinalIgnoreCase))
{
_tabs.Add(filePath);
}
_activeTab = filePath;
RebuildTabs();
LoadContent(filePath);
}
private void CloseTab(string filePath)
{
_tabs.RemoveAll((string t) => string.Equals(t, filePath, StringComparison.OrdinalIgnoreCase));
if (_tabs.Count == 0)
{
Close();
return;
}
if (string.Equals(_activeTab, filePath, StringComparison.OrdinalIgnoreCase))
{
List<string> tabs = _tabs;
_activeTab = tabs[tabs.Count - 1];
LoadContent(_activeTab);
}
RebuildTabs();
}
private void RebuildTabs()
{
TabPanel.Children.Clear();
Brush brush = (TryFindResource("AccentColor") as Brush) ?? Brushes.CornflowerBlue;
Brush brush2 = (TryFindResource("SecondaryText") as Brush) ?? Brushes.Gray;
Brush brush3 = (TryFindResource("PrimaryText") as Brush) ?? Brushes.White;
Brush background = (TryFindResource("BorderColor") as Brush) ?? Brushes.Gray;
foreach (string tab in _tabs)
{
string fileName = Path.GetFileName(tab);
bool flag = string.Equals(tab, _activeTab, StringComparison.OrdinalIgnoreCase);
Border border = new Border
{
Background = Brushes.Transparent,
BorderBrush = (flag ? brush : Brushes.Transparent),
BorderThickness = new Thickness(0.0, 0.0, 0.0, flag ? 2 : 0),
Padding = new Thickness(10.0, 6.0, 6.0, 6.0),
Cursor = Cursors.Hand,
MaxWidth = ((_tabs.Count <= 3) ? 220 : ((_tabs.Count <= 5) ? 160 : 110))
};
StackPanel stackPanel = new StackPanel
{
Orientation = Orientation.Horizontal
};
stackPanel.Children.Add(new TextBlock
{
Text = fileName,
FontSize = 11.5,
Foreground = (flag ? brush3 : brush2),
FontWeight = (flag ? FontWeights.SemiBold : FontWeights.Normal),
VerticalAlignment = VerticalAlignment.Center,
TextTrimming = TextTrimming.CharacterEllipsis,
MaxWidth = border.MaxWidth - 36.0,
ToolTip = tab
});
string closePath = tab;
Border border2 = new Border
{
Background = Brushes.Transparent,
CornerRadius = new CornerRadius(3.0),
Padding = new Thickness(4.0, 2.0, 4.0, 2.0),
Margin = new Thickness(6.0, 0.0, 0.0, 0.0),
Cursor = Cursors.Hand,
VerticalAlignment = VerticalAlignment.Center,
Child = new TextBlock
{
Text = "\ue711",
FontFamily = new FontFamily("Segoe MDL2 Assets"),
FontSize = 8.0,
Foreground = brush2,
VerticalAlignment = VerticalAlignment.Center
}
};
border2.MouseEnter += delegate(object s, MouseEventArgs _)
{
if (s is Border border3)
{
border3.Background = new SolidColorBrush(Color.FromArgb(48, byte.MaxValue, 80, 80));
}
};
border2.MouseLeave += delegate(object s, MouseEventArgs _)
{
if (s is Border border3)
{
border3.Background = Brushes.Transparent;
}
};
border2.MouseLeftButtonUp += delegate(object _, MouseButtonEventArgs me)
{
me.Handled = true;
CloseTab(closePath);
};
stackPanel.Children.Add(border2);
border.Child = stackPanel;
string clickPath = tab;
border.MouseLeftButtonUp += delegate(object _, MouseButtonEventArgs me)
{
if (!me.Handled)
{
me.Handled = true;
_activeTab = clickPath;
RebuildTabs();
LoadContent(clickPath);
}
};
border.MouseEnter += delegate(object s, MouseEventArgs _)
{
if (s is Border border3 && !string.Equals(clickPath, _activeTab, StringComparison.OrdinalIgnoreCase))
{
border3.Background = new SolidColorBrush(Color.FromArgb(24, byte.MaxValue, byte.MaxValue, byte.MaxValue));
}
};
border.MouseLeave += delegate(object s, MouseEventArgs _)
{
if (s is Border border3)
{
border3.Background = Brushes.Transparent;
}
};
TabPanel.Children.Add(border);
List<string> tabs = _tabs;
if (tab != tabs[tabs.Count - 1])
{
TabPanel.Children.Add(new Border
{
Width = 1.0,
Height = 14.0,
Background = background,
Margin = new Thickness(2.0, 0.0, 2.0, 0.0),
VerticalAlignment = VerticalAlignment.Center
});
}
}
if (_activeTab != null)
{
TitleText.Text = "미리보기 — " + Path.GetFileName(_activeTab);
}
}
private async void LoadContent(string filePath)
{
string ext = Path.GetExtension(filePath).ToLowerInvariant();
PreviewBrowser.Visibility = Visibility.Collapsed;
TextScroll.Visibility = Visibility.Collapsed;
DataGridContent.Visibility = Visibility.Collapsed;
EmptyMessage.Visibility = Visibility.Collapsed;
if (!File.Exists(filePath))
{
EmptyMessage.Text = "파일을 찾을 수 없습니다";
EmptyMessage.Visibility = Visibility.Visible;
return;
}
try
{
switch (ext)
{
case ".html":
case ".htm":
if (!_webViewInitialized)
{
return;
}
PreviewBrowser.Source = new Uri(filePath);
PreviewBrowser.Visibility = Visibility.Visible;
break;
case ".csv":
LoadCsvContent(filePath);
DataGridContent.Visibility = Visibility.Visible;
break;
case ".md":
{
if (!_webViewInitialized)
{
return;
}
string mdText = File.ReadAllText(filePath);
if (mdText.Length > 50000)
{
mdText = mdText.Substring(0, 50000);
}
string mdHtml = TemplateService.RenderMarkdownToHtml(mdText, _selectedMood ?? "modern");
PreviewBrowser.NavigateToString(mdHtml);
PreviewBrowser.Visibility = Visibility.Visible;
break;
}
case ".txt":
case ".json":
case ".xml":
case ".log":
{
string text = File.ReadAllText(filePath);
if (text.Length > 50000)
{
text = text.Substring(0, 50000) + "\n\n... (이후 생략)";
}
TextContent.Text = text;
TextScroll.Visibility = Visibility.Visible;
break;
}
default:
EmptyMessage.Text = "미리보기할 수 없는 파일 형식입니다";
EmptyMessage.Visibility = Visibility.Visible;
break;
}
}
catch (Exception ex)
{
Exception ex2 = ex;
TextContent.Text = "미리보기 오류: " + ex2.Message;
TextScroll.Visibility = Visibility.Visible;
}
await Task.CompletedTask;
}
private void LoadCsvContent(string filePath)
{
string[] array = File.ReadAllLines(filePath);
if (array.Length == 0)
{
return;
}
DataTable dataTable = new DataTable();
string[] array2 = ParseCsvLine(array[0]);
string[] array3 = array2;
foreach (string columnName in array3)
{
dataTable.Columns.Add(columnName);
}
int num = Math.Min(array.Length, 501);
for (int j = 1; j < num; j++)
{
string[] array4 = ParseCsvLine(array[j]);
DataRow dataRow = dataTable.NewRow();
for (int k = 0; k < Math.Min(array4.Length, array2.Length); k++)
{
dataRow[k] = array4[k];
}
dataTable.Rows.Add(dataRow);
}
DataGridContent.ItemsSource = dataTable.DefaultView;
}
private static string[] ParseCsvLine(string line)
{
List<string> list = new List<string>();
StringBuilder stringBuilder = new StringBuilder();
bool flag = false;
for (int i = 0; i < line.Length; i++)
{
char c = line[i];
if (flag)
{
if (c == '"' && i + 1 < line.Length && line[i + 1] == '"')
{
stringBuilder.Append('"');
i++;
}
else if (c == '"')
{
flag = false;
}
else
{
stringBuilder.Append(c);
}
continue;
}
switch (c)
{
case '"':
flag = true;
break;
case ',':
list.Add(stringBuilder.ToString());
stringBuilder.Clear();
break;
default:
stringBuilder.Append(c);
break;
}
}
list.Add(stringBuilder.ToString());
return list.ToArray();
}
private void TitleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.ClickCount == 2)
{
ToggleMaximize();
}
else
{
DragMove();
}
}
private void OpenExternalBtn_Click(object sender, MouseButtonEventArgs e)
{
e.Handled = true;
if (_activeTab == null || !File.Exists(_activeTab))
{
return;
}
try
{
Process.Start(new ProcessStartInfo
{
FileName = _activeTab,
UseShellExecute = true
});
}
catch (Exception ex)
{
LogService.Warn("외부 프로그램 열기 실패: " + ex.Message);
}
}
private void MinBtn_Click(object sender, MouseButtonEventArgs e)
{
e.Handled = true;
base.WindowState = WindowState.Minimized;
}
private void MaxBtn_Click(object sender, MouseButtonEventArgs e)
{
e.Handled = true;
ToggleMaximize();
}
private void CloseBtn_Click(object sender, MouseButtonEventArgs e)
{
e.Handled = true;
Close();
}
private void ToggleMaximize()
{
base.WindowState = ((base.WindowState != WindowState.Maximized) ? WindowState.Maximized : WindowState.Normal);
}
private void TitleBtn_Enter(object sender, MouseEventArgs e)
{
if (sender is Border border)
{
border.Background = (TryFindResource("ItemHoverBackground") as Brush) ?? new SolidColorBrush(Color.FromArgb(24, byte.MaxValue, byte.MaxValue, byte.MaxValue));
}
}
private void CloseBtnEnter(object sender, MouseEventArgs e)
{
if (sender is Border border)
{
border.Background = new SolidColorBrush(Color.FromArgb(68, byte.MaxValue, 64, 64));
}
}
private void TitleBtn_Leave(object sender, MouseEventArgs e)
{
if (sender is Border border)
{
border.Background = Brushes.Transparent;
}
}
[DebuggerNonUserCode]
[GeneratedCode("PresentationBuildTasks", "10.0.5.0")]
public void InitializeComponent()
{
if (!_contentLoaded)
{
_contentLoaded = true;
Uri resourceLocator = new Uri("/AxCopilot;component/views/previewwindow.xaml", UriKind.Relative);
Application.LoadComponent(this, resourceLocator);
}
}
[DebuggerNonUserCode]
[GeneratedCode("PresentationBuildTasks", "10.0.5.0")]
[EditorBrowsable(EditorBrowsableState.Never)]
void IComponentConnector.Connect(int connectionId, object target)
{
switch (connectionId)
{
case 1:
((Border)target).MouseLeftButtonDown += TitleBar_MouseLeftButtonDown;
break;
case 2:
TitleText = (TextBlock)target;
break;
case 3:
((Border)target).MouseLeftButtonDown += OpenExternalBtn_Click;
((Border)target).MouseEnter += TitleBtn_Enter;
((Border)target).MouseLeave += TitleBtn_Leave;
break;
case 4:
((Border)target).MouseLeftButtonDown += MinBtn_Click;
((Border)target).MouseEnter += TitleBtn_Enter;
((Border)target).MouseLeave += TitleBtn_Leave;
break;
case 5:
((Border)target).MouseLeftButtonDown += MaxBtn_Click;
((Border)target).MouseEnter += TitleBtn_Enter;
((Border)target).MouseLeave += TitleBtn_Leave;
break;
case 6:
MaxBtnIcon = (TextBlock)target;
break;
case 7:
((Border)target).MouseLeftButtonDown += CloseBtn_Click;
((Border)target).MouseEnter += CloseBtnEnter;
((Border)target).MouseLeave += TitleBtn_Leave;
break;
case 8:
TabPanel = (StackPanel)target;
break;
case 9:
PreviewBrowser = (WebView2)target;
break;
case 10:
TextScroll = (ScrollViewer)target;
break;
case 11:
TextContent = (TextBlock)target;
break;
case 12:
DataGridContent = (DataGrid)target;
break;
case 13:
EmptyMessage = (TextBlock)target;
break;
default:
_contentLoaded = true;
break;
}
}
}