first commit

This commit is contained in:
Beyhan Oğur
2026-09-03 04:45:36 +03:00
commit d5f52cc3d3
1038 changed files with 169579 additions and 0 deletions
@@ -0,0 +1,65 @@
#region License
// Copyright (C) 2020 Reetus
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#endregion
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace ClassicAssist.Browser.Controls
{
public class ClearableComboBox : ComboBox
{
public static readonly DependencyProperty ClearCommandProperty =
DependencyProperty.Register( "ClearCommand", typeof( ICommand ), typeof( ClearableComboBox ) );
public static readonly DependencyProperty ClearCommandParameterProperty =
DependencyProperty.Register( "ClearCommandParameter", typeof( object ), typeof( ClearableComboBox ),
new UIPropertyMetadata( null ) );
public ICommand ClearCommand
{
get => (ICommand) GetValue( ClearCommandProperty );
set => SetValue( ClearCommandProperty, value );
}
public object ClearCommandParameter
{
get => GetValue( ClearCommandParameterProperty );
set => SetValue( ClearCommandParameterProperty, value );
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
Button button = (Button) GetTemplateChild( "ClearButton" );
if ( button != null )
{
button.Click += ClearButtonOnClick;
}
}
private void ClearButtonOnClick( object sender, RoutedEventArgs e )
{
SelectedItem = null;
ClearCommand?.Execute( ClearCommandParameter );
}
}
}
@@ -0,0 +1,151 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:ClassicAssist.Browser.Controls">
<SolidColorBrush x:Key="ThemeForegroundBrush" Color="#CCCCCC" />
<DrawingImage x:Key="ClearIcon">
<DrawingImage.Drawing>
<DrawingGroup ClipGeometry="M0,0 V512.001 H512.001 V0 H0 Z">
<DrawingGroup.Transform>
<TranslateTransform X="0.000250011682510376" Y="0" />
</DrawingGroup.Transform>
<DrawingGroup Opacity="1">
<DrawingGroup Opacity="1">
<GeometryDrawing Brush="{DynamicResource ThemeForegroundBrush}"
Geometry="F1 M512.001,512.001z M0,0z M284.286,256.002L506.143,34.144C513.954,26.333 513.954,13.669 506.143,5.859 498.332,-1.951 485.668,-1.952 477.858,5.859L256,227.717 34.143,5.859C26.332,-1.952 13.668,-1.952 5.858,5.859 -1.952,13.67 -1.953,26.334 5.858,34.144L227.715,256.001 5.858,477.859C-1.953,485.67 -1.953,498.334 5.858,506.144 9.763,510.049 14.882,512.001 20.001,512.001 25.12,512.001 30.238,510.049 34.144,506.144L256,284.287 477.857,506.144C481.762,510.049 486.881,512.001 492,512.001 497.119,512.001 502.237,510.049 506.143,506.144 513.954,498.333 513.954,485.669 506.143,477.859L284.286,256.002z" />
</DrawingGroup>
</DrawingGroup>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
<Style TargetType="{x:Type controls:ClearableComboBox}">
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="Background" Value="{DynamicResource TextBoxBackgroundBrush}" />
<Setter Property="Foreground" Value="{DynamicResource TextBoxForegroundBrush}" />
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto" />
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
<Setter Property="ScrollViewer.CanContentScroll" Value="True" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource TextBoxForegroundBrush}" />
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
<Setter Property="Validation.ErrorTemplate" Value="{DynamicResource ValidationErrorOverlayTemplate}" />
<Setter Property="Padding" Value="3,0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:ClearableComboBox}">
<Grid>
<TextBox x:Name="PART_EditableTextBox"
IsReadOnly="{TemplateBinding IsReadOnly}"
Focusable="True"
Style="{DynamicResource TextBoxStyle}"
Visibility="Hidden" />
<Border x:Name="NonEditableBorder"
Background="{DynamicResource ComboBoxNonEditableBackgroundBrush}"
BorderThickness="1"
BorderBrush="{DynamicResource ComboBoxNonEditableBorderBrush}">
<ContentPresenter IsHitTestVisible="False"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Content="{TemplateBinding ComboBox.SelectionBoxItem}"
ContentTemplate="{TemplateBinding ComboBox.SelectionBoxItemTemplate}"
ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}"
Margin="{TemplateBinding Padding}" DockPanel.Dock="Left" />
</Border>
<Button x:Name="ClearButton" Padding="0" Background="Transparent"
BorderBrush="Transparent" BorderThickness="0" Width="8" Height="8" Margin="0,0,20,0"
HorizontalAlignment="Right">
<Image Source="{DynamicResource ClearIcon}" Height="8" />
</Button>
<ToggleButton x:Name="ToggleButton"
ClickMode="Press"
HorizontalAlignment="Right"
Background="{DynamicResource ComboBoxButtonBackgroundBrush}"
BorderBrush="{DynamicResource ComboBoxButtonBorderBrush}"
BorderThickness="0"
Foreground="{DynamicResource ComboBoxButtonForegroundBrush}"
IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsDropDownOpen, Mode=TwoWay}">
<ToggleButton.Template>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="20" />
</Grid.ColumnDefinitions>
<Border Grid.Column="1"
x:Name="ButtonBorder"
Margin="0"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}" />
<Path x:Name="Arrow"
Grid.Column="1"
Data="M0,0 L0,2 L4,6 L8,2 L8,0 L4,4 z"
Fill="{TemplateBinding Foreground}"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="ButtonBorder" Property="Background"
Value="{DynamicResource ComboBoxButtonHoverBackgroundBrush}" />
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="ButtonBorder" Property="Background"
Value="{DynamicResource ComboBoxButtonPressBackgroundBrush}" />
</Trigger>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="ButtonBorder" Property="Background"
Value="{DynamicResource ComboBoxButtonCheckedBackgroundBrush}" />
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="ButtonBorder" Property="Background"
Value="{DynamicResource ComboBoxButtonDisabledBackgroundBrush}" />
<Setter TargetName="ButtonBorder" Property="BorderBrush"
Value="{DynamicResource ComboBoxButtonDisabledBorderBrush}" />
<Setter TargetName="Arrow" Property="Fill"
Value="{DynamicResource ComboBoxButtonDisabledForegroundBrush}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ToggleButton.Template>
</ToggleButton>
<Popup Placement="Bottom"
Focusable="False"
AllowsTransparency="True"
IsOpen="{TemplateBinding ComboBox.IsDropDownOpen}"
PopupAnimation="Slide">
<Grid MinWidth="{TemplateBinding ActualWidth}"
MaxHeight="{TemplateBinding MaxDropDownHeight}">
<Border BorderThickness="1"
Background="{DynamicResource ComboBoxListBackgroundBrush}"
BorderBrush="{DynamicResource ComboBoxListBorderBrush}"
Margin="0,1,0,0">
<ScrollViewer SnapsToDevicePixels="True">
<ItemsPresenter KeyboardNavigation.DirectionalNavigation="Contained" />
</ScrollViewer>
</Border>
</Grid>
</Popup>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="NonEditableBorder" Property="Background"
Value="{DynamicResource ComboBoxNonEditableHoverBackgroundBrush}" />
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="NonEditableBorder" Property="Background"
Value="{DynamicResource ComboBoxNonEditableDisabledBackgroundBrush}" />
<Setter TargetName="NonEditableBorder" Property="BorderBrush"
Value="{DynamicResource ComboBoxNonEditableDisabledBorderBrush}" />
</Trigger>
<Trigger Property="IsEditable" Value="True">
<Setter Property="IsTabStop" Value="False" />
<Setter TargetName="PART_EditableTextBox" Property="Visibility" Value="Visible" />
<Setter TargetName="NonEditableBorder" Property="Visibility" Value="Hidden" />
<Setter TargetName="ToggleButton" Property="BorderThickness" Value="1,0,0,0" />
<Setter TargetName="ToggleButton" Property="Focusable" Value="False" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
@@ -0,0 +1,58 @@
#region License
// Copyright (C) 2020 Reetus
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#endregion
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace ClassicAssist.Browser.Data
{
public class CategoriesConverter : JsonConverter
{
public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer )
{
throw new NotImplementedException();
}
public override object ReadJson( JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer )
{
switch ( reader.TokenType )
{
case JsonToken.StartArray:
{
JToken token = JToken.Load( reader );
return token.ToObject<string[]>();
}
case JsonToken.String:
{
JToken token = JToken.Load( reader );
return new[] { token.ToObject<string>() };
}
default:
return null;
}
}
public override bool CanConvert( Type objectType )
{
throw new NotImplementedException();
}
}
}
+318
View File
@@ -0,0 +1,318 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using ClassicAssist.Shared;
using ClassicAssist.Browser.Models;
using LiteDB;
using Newtonsoft.Json;
using Octokit;
using Repository = ClassicAssist.Browser.Models.Repository;
namespace ClassicAssist.Browser.Data
{
public class Database
{
private const string DATABASE_FILE = "macros.db";
private const string MANIFEST_URL =
"https://raw.githubusercontent.com/Reetus/ClassicAssist-Macros/master/Macros/metadata.json";
private const string MACRO_RAW_BASEURL =
"https://raw.githubusercontent.com/Reetus/ClassicAssist-Macros/master/Macros/";
private const string MACRO_BASEURL = "https://github.com/Reetus/ClassicAssist-Macros/tree/master/Macros/";
private const string REPOSITORY_OWNER = "Reetus";
private const string REPOSITORY_NAME = "ClassicAssist-Macros";
private static Database _instance;
private static readonly object _instanceLock = new object();
private readonly GitHubClient _github;
private readonly HttpClient _httpClient;
private ILiteCollection<Macro> _macros;
private ILiteCollection<Manifest> _manifest;
private ILiteCollection<Repository> _repository;
private Database()
{
_httpClient = new HttpClient();
string databasePath = Path.Combine( Engine.StartupPath ?? Environment.CurrentDirectory, DATABASE_FILE );
LiteDatabase database = new LiteDatabase( $"Filename={databasePath};Connection=Shared" );
_github = new GitHubClient( new ProductHeaderValue( "ClassicAssist" ) );
InitCollections( database );
}
private void InitCollections( ILiteDatabase database )
{
_repository = database.GetCollection<Repository>( "repository" );
_repository.EnsureIndex( x => x.Id );
_manifest = database.GetCollection<Manifest>( "manifest" );
_manifest.EnsureIndex( x => x.Id );
_macros = database.GetCollection<Macro>( "macros" );
_macros.EnsureIndex( x => x.Id );
}
public string GetCommitHash()
{
Repository latest = _repository.Find( r => true ).OrderByDescending( r => r.DateTime ).FirstOrDefault() ??
new Repository();
return latest.CommitHash;
}
public async Task<string> GetGithubHash()
{
Reference reference = await _github.Git.Reference.Get( REPOSITORY_OWNER, REPOSITORY_NAME, "heads/master" );
string hash = reference.Object.Sha;
Repository latest = _repository.Find( r => true ).OrderByDescending( r => r.DateTime ).FirstOrDefault() ??
new Repository();
latest.CommitHash = hash;
latest.DateTime = DateTime.Now;
_repository.Upsert( latest );
return hash;
}
public async Task<Manifest> GetManifest()
{
Manifest latest = _manifest.Find( r => true ).OrderByDescending( r => r.DateTime ).FirstOrDefault();
if ( latest != null && !( DateTime.Now - latest.DateTime >= TimeSpan.FromMinutes( 15 ) ) )
{
return latest;
}
try
{
if ( latest != null && GetCommitHash() == await GetGithubHash() )
{
return latest;
}
}
catch ( RateLimitExceededException )
{
// Github throttle error
}
latest = await FetchManifest();
_manifest.Upsert( latest );
return latest;
}
public async Task<string[]> GetMacros( IEnumerable<Filter> filter = null )
{
Manifest manifest = await GetManifest();
if ( filter == null )
{
return manifest.Files.Select( f => f.Name ).OrderBy( f => f ).ToArray();
}
string shardFilter = filter.FirstOrDefault( t => t.FilterType == FilterType.Shard )?.Value;
string eraFilter = filter.FirstOrDefault( t => t.FilterType == FilterType.Era )?.Value;
string authorFilter = filter.FirstOrDefault( t => t.FilterType == FilterType.Author )?.Value;
Predicate<string[]> categoryPredicate =
CategoryToPredicate( filter.FirstOrDefault( t => t.FilterType == FilterType.Category ) );
IEnumerable<string> macros = manifest.Files.Where( m =>
( string.IsNullOrEmpty( shardFilter ) || m.Shard != null && m.Shard.Equals( shardFilter ) ) &&
( string.IsNullOrEmpty( eraFilter ) || m.Era == null || m.Era.Equals( eraFilter ) ) &&
( string.IsNullOrEmpty( authorFilter ) || m.Author == null || m.Author.Equals( authorFilter ) ) &&
categoryPredicate( m.Categories ) ).Select( m => m.Name );
return macros.ToArray();
}
private Predicate<string[]> CategoryToPredicate( Filter filter )
{
if ( filter.Category == null )
{
return f => true;
}
return m =>
{
if ( m.Length < filter.Category.Values.Length )
{
return false;
}
for ( int i = 0; i < filter.Category.Values.Length; i++ )
{
string macroCategory = m[i];
string filterCategory = filter.Category.Values[i];
if ( !macroCategory.Equals( filterCategory ) )
{
return false;
}
}
return true;
};
}
public async Task<string[]> GetShards()
{
Manifest manifest = await GetManifest();
return manifest.Files.Where( f => f.Shard != null ).Select( f => f.Shard ).OrderBy( f => f ).Distinct()
.ToArray();
}
private async Task<Manifest> FetchManifest()
{
HttpResponseMessage response = await _httpClient.GetAsync( MANIFEST_URL );
string json = await response.Content.ReadAsStringAsync();
Metadata[] obj = JsonConvert.DeserializeObject<Metadata[]>( json );
if ( obj == null )
{
return null;
}
Manifest manifest = new Manifest { Files = obj };
return manifest;
}
public static Database GetInstance()
{
// ReSharper disable once InvertIf
if ( _instance == null )
{
lock ( _instanceLock )
{
if ( _instance == null )
{
_instance = new Database();
}
}
}
return _instance;
}
public async Task<string[]> GetEras()
{
Manifest manifest = await GetManifest();
return manifest.Files.Where( f => f.Era != null ).Select( f => f.Era ).OrderBy( f => f ).Distinct()
.ToArray();
}
public async Task<string[]> GetAuthors()
{
Manifest manifest = await GetManifest();
return manifest.Files.Where( f => f.Author != null ).Select( f => f.Author ).OrderBy( f => f ).Distinct()
.ToArray();
}
public async Task<Category[]> GetCategories()
{
Manifest manifest = await GetManifest();
List<Category> results = new List<Category>();
IEnumerable<string[]> categories = manifest.Files.Select( f => f.Categories ).OrderBy( f => f[0] )
.ThenBy( f => f.Length ).Distinct();
foreach ( string[] category in categories )
{
if ( category.Length == 0 )
{
continue;
}
if ( results.All( e => e.Name != category[0] ) )
{
results.Add( new Category { Name = category[0], Values = new[] { category[0] } } );
}
if ( category.Length == 1 )
{
continue;
}
string res = $"--{category[1]}";
if ( category.Length > 2 )
{
for ( int i = 2; i < category.Length; i++ )
{
res += $" ˃ {category[i]}";
}
}
if ( results.All( e => e.Name != res ) )
{
results.Add( new Category { Name = res, Values = category } );
}
}
return results.ToArray();
}
public async Task<string> GetMacroByName( string value )
{
Manifest manifest = await GetManifest();
Metadata macro = manifest.Files.FirstOrDefault( f => f.Name == value );
if ( macro == null )
{
return string.Empty;
}
Macro cached = _macros.Find( m => m.Name == macro.Name && m.SHA1 == macro.SHA1 ).FirstOrDefault();
if ( cached != null )
{
return cached.Text;
}
string url = string.Concat( MACRO_RAW_BASEURL, macro.FileName.Replace( '\\', '/' ) );
string result = await _httpClient.GetStringAsync( url );
if ( string.IsNullOrEmpty( result ) )
{
return result;
}
Macro inserted = new Macro { Name = macro.Name, SHA1 = macro.SHA1, Text = result };
_macros.Insert( inserted );
return result;
}
public async void OpenGithubMacroURL( string value )
{
Manifest manifest = await GetManifest();
Metadata macro = manifest.Files.FirstOrDefault( f => f.Name == value );
if ( macro == null )
{
return;
}
string url = string.Concat( MACRO_BASEURL, macro.FileName.Replace( '\\', '/' ) );
Process.Start( url );
}
}
}
+30
View File
@@ -0,0 +1,30 @@
#region License
// Copyright (C) 2020 Reetus
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#endregion
using ClassicAssist.Browser.Models;
namespace ClassicAssist.Browser.Data
{
public class Filter
{
public Category Category { get; set; }
public FilterType FilterType { get; set; }
public string Value { get; set; }
}
}
+29
View File
@@ -0,0 +1,29 @@
#region License
// Copyright (C) 2020 Reetus
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#endregion
namespace ClassicAssist.Browser.Data
{
public enum FilterType
{
Shard,
Era,
Author,
Category
}
}
@@ -0,0 +1,146 @@
<UserControl x:Class="ClassicAssist.Browser.MacroBrowserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:resources="clr-namespace:ClassicAssist.Shared.Resources;assembly=ClassicAssist.Shared"
xmlns:avalonedit="http://icsharpcode.net/sharpdevelop/avalonedit"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:misc="clr-namespace:ClassicAssist.UI.Misc"
xmlns:controls="clr-namespace:ClassicAssist.Browser.Controls"
xmlns:data="clr-namespace:ClassicAssist.Browser.Data"
xmlns:browser="clr-namespace:ClassicAssist.Browser"
xmlns:controls1="clr-namespace:ClassicAssist.UI.Controls"
mc:Ignorable="d"
d:DesignHeight="550" d:DesignWidth="900">
<UserControl.DataContext>
<browser:MacroBrowserViewModel />
</UserControl.DataContext>
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Resources/DarkTheme.xaml" />
<ResourceDictionary Source="../Resources/Icons.xaml" />
<ResourceDictionary
Source="../UI/Views/Debug/DebugToolBarStyle.xaml" />
<ResourceDictionary Source="Controls/ClearableComboBox.xaml" />
<ResourceDictionary Source="../Resources/LeftRightIconToggleButton.xaml" />
</ResourceDictionary.MergedDictionaries>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="5" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<controls1:StretchyWrapPanel Orientation="Horizontal" Grid.Row="0" Margin="10,0,10,10">
<controls1:StretchyWrapPanel.Resources>
<Style TargetType="{x:Type TextBlock}" BasedOn="{StaticResource {x:Type TextBlock}}">
<Setter Property="Margin" Value="10,0,10,0" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Right" />
</Style>
<Style TargetType="{x:Type controls:ClearableComboBox}"
BasedOn="{StaticResource {x:Type controls:ClearableComboBox}}">
<Setter Property="MinWidth" Value="80" />
</Style>
</controls1:StretchyWrapPanel.Resources>
<TextBlock Text="{x:Static resources:Strings.Shard_}" Margin="0,0,10,0" />
<controls:ClearableComboBox ItemsSource="{Binding Shards}" ClearCommand="{Binding ClearCommand}"
ClearCommandParameter="{x:Static data:FilterType.Shard}"
SelectedItem="{Binding ShardFilter}" />
<TextBlock Text="{x:Static resources:Strings.Era}" />
<controls:ClearableComboBox ItemsSource="{Binding Eras}" ClearCommand="{Binding ClearCommand}"
ClearCommandParameter="{x:Static data:FilterType.Era}"
SelectedItem="{Binding EraFilter}" />
<TextBlock Text="{x:Static resources:Strings.Author}" />
<controls:ClearableComboBox ItemsSource="{Binding Authors}" ClearCommand="{Binding ClearCommand}"
ClearCommandParameter="{x:Static data:FilterType.Author}"
SelectedItem="{Binding AuthorFilter}" />
<TextBlock Text="{x:Static resources:Strings.Category}" />
<controls:ClearableComboBox ItemsSource="{Binding Categories}" ClearCommand="{Binding ClearCommand}"
ClearCommandParameter="{x:Static data:FilterType.Category}"
SelectedItem="{Binding CategoryFilter}" />
</controls1:StretchyWrapPanel>
<Grid Row="1" Margin="10,0,10,0">
<Grid.ColumnDefinitions>
<ColumnDefinition>
<ColumnDefinition.Style>
<Style TargetType="{x:Type ColumnDefinition}">
<Setter Property="Width" Value="200" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, ElementName=ToggleButton}" Value="True">
<Setter Property="Width" Value="0" />
</DataTrigger>
</Style.Triggers>
</Style>
</ColumnDefinition.Style>
</ColumnDefinition>
<ColumnDefinition Width="5" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListBox x:Name="listBox" ItemsSource="{Binding Macros}" Grid.Column="0" Grid.Row="0"
SelectedItem="{Binding SelectedItem}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" ToolTip="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBlock Grid.Row="1" TextWrapping="Wrap">
<Hyperlink NavigateUri="https://github.com/Reetus/ClassicAssist-Macros/blob/master/README.md"
RequestNavigate="Hyperlink_RequestNavigate">
<Run Text="{x:Static resources:Strings.Learn_how_to_contribute_your_own_macros___}" />
</Hyperlink>
</TextBlock>
</Grid>
<GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Stretch" />
<Grid Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<avalonedit:TextEditor ShowLineNumbers="True" x:Name="CodeTextEditor" Grid.Row="0"
HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"
FontFamily="Consolas" Margin="10,0,0,0" IsReadOnly="True">
<i:Interaction.Behaviors>
<misc:AvalonEditBehaviour
TextBinding="{Binding SelectedMacro}" />
</i:Interaction.Behaviors>
</avalonedit:TextEditor>
<WrapPanel Grid.Row="1" Orientation="Horizontal" Margin="10,10,0,0">
<WrapPanel.Resources>
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Margin" Value="0,0,10,0" />
<Setter Property="Padding" Value="10,5" />
<Setter Property="FontSize" Value="11" />
</Style>
</WrapPanel.Resources>
<ToggleButton x:Name="ToggleButton" Height="15" Margin="0"
Style="{DynamicResource LeftRightIconToggleButton}" VerticalAlignment="Center" />
<Button Content="{x:Static resources:Strings.Copy_to_clipboard}"
Command="{Binding CopyToClipboardCommand}" CommandParameter="{Binding SelectedMacro}" />
<Button Content="{x:Static resources:Strings.Create_Macro}"
Command="{Binding CreateMacroCommand}" CommandParameter="{Binding SelectedItem}" />
<Button Content="{x:Static resources:Strings.Open_in_Github}"
Command="{Binding OpenGithubCommand}"
CommandParameter="{Binding SelectedItem, ElementName=listBox}" />
</WrapPanel>
</Grid>
</Grid>
</Grid>
<ProgressBar Grid.Row="1" IsIndeterminate="True"
Visibility="{Binding Loading, Converter={StaticResource BooleanToVisibilityConverter}}" />
</Grid>
</UserControl>
@@ -0,0 +1,31 @@
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Windows.Controls;
using System.Windows.Navigation;
using System.Xml;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Highlighting.Xshd;
namespace ClassicAssist.Browser
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MacroBrowserControl : UserControl
{
public MacroBrowserControl()
{
InitializeComponent();
CodeTextEditor.SyntaxHighlighting = HighlightingLoader.Load(
new XmlTextReader( Path.Combine( Path.GetDirectoryName( Assembly.GetExecutingAssembly().Location ),
"Python.Dark.xshd" ) ), HighlightingManager.Instance );
}
private void Hyperlink_RequestNavigate( object sender, RequestNavigateEventArgs e )
{
Process.Start( e.Uri.ToString() );
}
}
}
@@ -0,0 +1,351 @@
#region License
// Copyright (C) 2020 Reetus
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#endregion
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
using ClassicAssist.Browser.Data;
using ClassicAssist.Browser.Models;
using ClassicAssist.Data.Macros;
using ClassicAssist.Shared.Resources;
using ClassicAssist.UI.ViewModels;
using Microsoft.Scripting.Utils;
namespace ClassicAssist.Browser
{
public class MacroBrowserViewModel : BaseViewModel
{
private readonly Dictionary<string, Func<Task<string[]>>>
_data = new Dictionary<string, Func<Task<string[]>>>();
private readonly Database _database = Database.GetInstance();
private string _authorFilter;
private ObservableCollection<string> _authors = new ObservableCollection<string>();
private ObservableCollection<Category> _categories = new ObservableCollection<Category>();
private Category _categoryFilter;
private ICommand _clearCommand;
private ICommand _copyToClipboardCommand;
private ICommand _createMacroCommand;
private string _eraFilter;
private ObservableCollection<string> _eras = new ObservableCollection<string>();
private bool _loading = true;
private ObservableCollection<string> _macros = new ObservableCollection<string>();
private ICommand _openGithubCommand;
private string _selectedItem;
private string _selectedMacro;
private string _shardFilter;
private ObservableCollection<string> _shards = new ObservableCollection<string>();
private Dispatcher _test;
public MacroBrowserViewModel()
{
_data.Add( nameof( Macros ), async () => await _database.GetMacros() );
_data.Add( nameof( Shards ), async () => await _database.GetShards() );
_data.Add( nameof( Eras ), async () => await _database.GetEras() );
_data.Add( nameof( Authors ), async () => await _database.GetAuthors() );
_test = Dispatcher.CurrentDispatcher;
Task.Run( async () =>
{
try
{
await RefreshData();
}
catch ( Exception e )
{
MessageBox.Show( $"{Strings.Failed_to_download_manifest___}\n\n{e.Message}", Strings.Error,
MessageBoxButton.OK, MessageBoxImage.Error );
}
finally
{
Loading = false;
}
} );
}
public string AuthorFilter
{
get => _authorFilter;
set
{
SetProperty( ref _authorFilter, value );
ApplyFilter();
}
}
public ObservableCollection<string> Authors
{
get => _authors;
set => SetProperty( ref _authors, value );
}
public ObservableCollection<Category> Categories
{
get => _categories;
set => SetProperty( ref _categories, value );
}
public Category CategoryFilter
{
get => _categoryFilter;
set
{
SetProperty( ref _categoryFilter, value );
ApplyFilter();
}
}
public ICommand ClearCommand => _clearCommand ?? ( _clearCommand = new RelayCommand( ClearFilter, o => true ) );
public ICommand CopyToClipboardCommand =>
_copyToClipboardCommand ??
( _copyToClipboardCommand = new RelayCommand( CopyToClipboard, o => SelectedItem != null ) );
public ICommand CreateMacroCommand =>
_createMacroCommand ??
( _createMacroCommand = new RelayCommandAsync( CreateMacro, o => SelectedItem != null ) );
public string EraFilter
{
get => _eraFilter;
set
{
SetProperty( ref _eraFilter, value );
ApplyFilter();
}
}
public ObservableCollection<string> Eras
{
get => _eras;
set => SetProperty( ref _eras, value );
}
public bool Loading
{
get => _loading;
set => SetProperty( ref _loading, value );
}
public ObservableCollection<string> Macros
{
get => _macros;
set => SetProperty( ref _macros, value );
}
public ICommand OpenGithubCommand =>
_openGithubCommand ?? ( _openGithubCommand = new RelayCommand( OpenGithub, o => SelectedItem != null ) );
public string SelectedItem
{
get => _selectedItem;
set
{
SetProperty( ref _selectedItem, value );
GetMacro( value ).ConfigureAwait( false );
}
}
public string SelectedMacro
{
get => _selectedMacro;
set => SetProperty( ref _selectedMacro, value );
}
public string ShardFilter
{
get => _shardFilter;
set
{
SetProperty( ref _shardFilter, value );
ApplyFilter();
}
}
public ObservableCollection<string> Shards
{
get => _shards;
set => SetProperty( ref _shards, value );
}
private async Task CreateMacro( object obj )
{
if ( !( obj is string macro ) )
{
return;
}
MessageBoxResult result = MessageBox.Show( Strings.Macro_Warning, Strings.Warning, MessageBoxButton.YesNo,
MessageBoxImage.Warning );
if ( result != MessageBoxResult.Yes )
{
return;
}
MacroManager.GetInstance().NewMacro?.Invoke( macro, await _database.GetMacroByName( macro ) );
}
private static void CopyToClipboard( object obj )
{
if ( !( obj is string macro ) )
{
return;
}
try
{
Clipboard.SetText( macro );
}
catch ( Exception )
{
// ignored
}
}
private void ClearFilter( object obj )
{
if ( !( obj is FilterType filterType ) )
{
return;
}
switch ( filterType )
{
case FilterType.Shard:
ShardFilter = null;
break;
case FilterType.Era:
EraFilter = null;
break;
case FilterType.Author:
AuthorFilter = null;
break;
case FilterType.Category:
break;
default:
throw new ArgumentOutOfRangeException();
}
ApplyFilter();
}
private void ApplyFilter()
{
List<Filter> filters = new List<Filter>
{
new Filter { FilterType = FilterType.Shard, Value = ShardFilter },
new Filter { FilterType = FilterType.Era, Value = EraFilter },
new Filter { FilterType = FilterType.Author, Value = AuthorFilter },
new Filter { FilterType = FilterType.Category, Category = CategoryFilter }
};
_database.GetMacros( filters ).ContinueWith( t =>
{
_dispatcher.Invoke( () =>
{
Macros.Clear();
Macros.AddRange( t.Result );
} );
} );
}
private async Task GetMacro( string value )
{
try
{
Loading = true;
string macro = await _database.GetMacroByName( value );
SelectedMacro = macro;
}
catch ( Exception e )
{
MessageBox.Show( $"{Strings.Failed_to_download_macro___}\n\n{e.Message}", Strings.Error,
MessageBoxButton.OK, MessageBoxImage.Error );
}
finally
{
Loading = false;
}
}
private async Task RefreshData()
{
try
{
foreach ( KeyValuePair<string, Func<Task<string[]>>> kvp in _data )
{
PropertyInfo property =
GetType().GetProperty( kvp.Key, BindingFlags.Instance | BindingFlags.Public );
if ( property == null )
{
continue;
}
string[] data = await kvp.Value.Invoke();
if ( data == null )
{
continue;
}
ObservableCollection<string> obj = (ObservableCollection<string>) property.GetValue( this );
if ( obj == null )
{
continue;
}
_test.Invoke( () =>
{
obj.Clear();
obj.AddRange( data );
} );
}
await _dispatcher.InvokeAsync( async () => Categories.AddRange( await _database.GetCategories() ) );
}
catch ( Exception e )
{
MessageBox.Show( e.Message, Strings.Error );
}
}
private void OpenGithub( object obj )
{
if ( !( obj is string macro ) )
{
return;
}
_database.OpenGithubMacroURL( macro );
}
}
}
+32
View File
@@ -0,0 +1,32 @@
#region License
// Copyright (C) 2020 Reetus
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#endregion
namespace ClassicAssist.Browser.Models
{
public class Category
{
public string Name { get; set; }
public string[] Values { get; set; }
public override string ToString()
{
return Name;
}
}
}
+33
View File
@@ -0,0 +1,33 @@
#region License
// Copyright (C) 2020 Reetus
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#endregion
using LiteDB;
namespace ClassicAssist.Browser.Models
{
public class Macro
{
[BsonId]
public int Id { get; set; }
public string Name { get; set; }
public string SHA1 { get; set; }
public string Text { get; set; }
}
}
+33
View File
@@ -0,0 +1,33 @@
#region License
// Copyright (C) 2020 Reetus
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#endregion
using System;
using LiteDB;
namespace ClassicAssist.Browser.Models
{
public class Manifest
{
public DateTime DateTime { get; set; } = DateTime.Now;
public Metadata[] Files { get; set; }
[BsonId]
public int Id { get; set; }
}
}
+42
View File
@@ -0,0 +1,42 @@
#region License
// Copyright (C) 2020 Reetus
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#endregion
using System;
using ClassicAssist.Browser.Data;
using Newtonsoft.Json;
namespace ClassicAssist.Browser.Models
{
public class Metadata
{
public string Author { get; set; }
[JsonConverter( typeof( CategoriesConverter ) )]
public string[] Categories { get; set; }
public string Description { get; set; }
public string Era { get; set; }
public string FileName { get; set; }
public DateTime ModifiedDate { get; set; }
public string Name { get; set; }
public string SHA1 { get; set; }
public string Shard { get; set; }
public int Size { get; set; }
}
}
@@ -0,0 +1,33 @@
#region License
// Copyright (C) 2020 Reetus
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#endregion
using System;
using LiteDB;
namespace ClassicAssist.Browser.Models
{
public class Repository
{
public string CommitHash { get; set; }
public DateTime DateTime { get; set; } = DateTime.Now;
[BsonId]
public int Id { get; set; }
}
}