diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7e0f4c2..e0cefeb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,13 +9,6 @@ permissions: jobs: build: runs-on: windows-latest - strategy: - matrix: - framework: - - net462 - - net7.0-windows - runtime: - - win10-x64 steps: - uses: actions/checkout@v3 @@ -25,10 +18,10 @@ jobs: - name: Build run: | - dotnet publish src/LipUI/LipUI.csproj -c Release -f ${{ matrix.framework }} -o build/ -r ${{ matrix.runtime }} --sc true + dotnet publish src/LipUI.sln -c Release - uses: actions/upload-artifact@v3 with: - name: ${{ matrix.framework }}-${{ matrix.runtime }} + name: net7.0-windows-win10-x64 path: | - build/ + src/LipUI/bin/Release/net7.0-windows10.0.19041.0/win10-x64/publish/ diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ae77b06..570e313 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -9,13 +9,6 @@ permissions: jobs: build: runs-on: windows-latest - strategy: - matrix: - framework: - - net462 - - net7.0-windows - runtime: - - win10-x64 steps: - uses: actions/checkout@v3 @@ -25,31 +18,24 @@ jobs: - name: Build run: | - dotnet publish src/LipUI/LipUI.csproj -c Release -f ${{ matrix.framework }} -o build/ -r ${{ matrix.runtime }} --sc true + dotnet publish src/LipUI.sln -c Release - uses: actions/upload-artifact@v3 with: - name: ${{ matrix.framework }}-${{ matrix.runtime }} + name: net7.0-windows-win10-x64 path: | - build/ + src/LipUI/bin/Release/net7.0-windows10.0.19041.0/win10-x64/publish/ upload-to-release: needs: - build runs-on: ubuntu-latest - strategy: - matrix: - framework: - - net462 - - net7.0-windows - runtime: - - win10-x64 steps: - uses: actions/checkout@v3 - uses: actions/download-artifact@v3 with: - name: ${{ matrix.framework }}-${{ matrix.runtime }} + name: net7.0-windows-win10-x64 path: artifacts/ - name: Copy essential files @@ -59,7 +45,7 @@ jobs: - name: Pack artifacts run: | cd artifacts - zip -9r ../lipui-${{ matrix.framework }}-${{ matrix.runtime }}.zip * + zip -9r ../lipui-net7.0-windows-win10-x64.zip * cd .. - name: Upload artifacts to release @@ -67,4 +53,4 @@ jobs: with: append_body: true files: | - lipui-${{ matrix.framework }}-${{ matrix.runtime }}.zip + lipui-net7.0-windows-win10-x64.zip diff --git a/src/LipUI.sln b/src/LipUI.sln index 03c8eb6..4f3997e 100644 --- a/src/LipUI.sln +++ b/src/LipUI.sln @@ -3,36 +3,44 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.4.33213.308 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LipUI", "LipUI\LipUI.csproj", "{7AFA9B27-6BD1-4BE1-AEA3-EA560BAD4289}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LipNETWrapper", "LipNETWrapper\LipNETWrapper.csproj", "{A274FAC9-3169-4EAB-A659-4090F8D6E568}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LipNETWrapperTest", "LipNETWrapperTest\LipNETWrapperTest.csproj", "{2245E398-21FD-4F05-87A8-C6E8E29A4685}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LipWebApi", "LipWebApi\LipWebApi.csproj", "{17569CD2-D4D2-4E10-927C-BECEAECF7786}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LipUI", "LipUI\LipUI.csproj", "{ADF26F0A-8BCE-4F01-ACA8-042B9EC35064}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|ARM64 = Debug|ARM64 + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|ARM64 = Release|ARM64 + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {7AFA9B27-6BD1-4BE1-AEA3-EA560BAD4289}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7AFA9B27-6BD1-4BE1-AEA3-EA560BAD4289}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7AFA9B27-6BD1-4BE1-AEA3-EA560BAD4289}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7AFA9B27-6BD1-4BE1-AEA3-EA560BAD4289}.Release|Any CPU.Build.0 = Release|Any CPU - {A274FAC9-3169-4EAB-A659-4090F8D6E568}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A274FAC9-3169-4EAB-A659-4090F8D6E568}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A274FAC9-3169-4EAB-A659-4090F8D6E568}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A274FAC9-3169-4EAB-A659-4090F8D6E568}.Release|Any CPU.Build.0 = Release|Any CPU - {2245E398-21FD-4F05-87A8-C6E8E29A4685}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2245E398-21FD-4F05-87A8-C6E8E29A4685}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2245E398-21FD-4F05-87A8-C6E8E29A4685}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2245E398-21FD-4F05-87A8-C6E8E29A4685}.Release|Any CPU.Build.0 = Release|Any CPU - {17569CD2-D4D2-4E10-927C-BECEAECF7786}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {17569CD2-D4D2-4E10-927C-BECEAECF7786}.Debug|Any CPU.Build.0 = Debug|Any CPU - {17569CD2-D4D2-4E10-927C-BECEAECF7786}.Release|Any CPU.ActiveCfg = Release|Any CPU - {17569CD2-D4D2-4E10-927C-BECEAECF7786}.Release|Any CPU.Build.0 = Release|Any CPU + {ADF26F0A-8BCE-4F01-ACA8-042B9EC35064}.Debug|Any CPU.ActiveCfg = Debug|x64 + {ADF26F0A-8BCE-4F01-ACA8-042B9EC35064}.Debug|Any CPU.Build.0 = Debug|x64 + {ADF26F0A-8BCE-4F01-ACA8-042B9EC35064}.Debug|Any CPU.Deploy.0 = Debug|x64 + {ADF26F0A-8BCE-4F01-ACA8-042B9EC35064}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {ADF26F0A-8BCE-4F01-ACA8-042B9EC35064}.Debug|ARM64.Build.0 = Debug|ARM64 + {ADF26F0A-8BCE-4F01-ACA8-042B9EC35064}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {ADF26F0A-8BCE-4F01-ACA8-042B9EC35064}.Debug|x64.ActiveCfg = Debug|x64 + {ADF26F0A-8BCE-4F01-ACA8-042B9EC35064}.Debug|x64.Build.0 = Debug|x64 + {ADF26F0A-8BCE-4F01-ACA8-042B9EC35064}.Debug|x64.Deploy.0 = Debug|x64 + {ADF26F0A-8BCE-4F01-ACA8-042B9EC35064}.Debug|x86.ActiveCfg = Debug|x86 + {ADF26F0A-8BCE-4F01-ACA8-042B9EC35064}.Debug|x86.Build.0 = Debug|x86 + {ADF26F0A-8BCE-4F01-ACA8-042B9EC35064}.Debug|x86.Deploy.0 = Debug|x86 + {ADF26F0A-8BCE-4F01-ACA8-042B9EC35064}.Release|Any CPU.ActiveCfg = Release|x64 + {ADF26F0A-8BCE-4F01-ACA8-042B9EC35064}.Release|Any CPU.Build.0 = Release|x64 + {ADF26F0A-8BCE-4F01-ACA8-042B9EC35064}.Release|Any CPU.Deploy.0 = Release|x64 + {ADF26F0A-8BCE-4F01-ACA8-042B9EC35064}.Release|ARM64.ActiveCfg = Release|ARM64 + {ADF26F0A-8BCE-4F01-ACA8-042B9EC35064}.Release|ARM64.Build.0 = Release|ARM64 + {ADF26F0A-8BCE-4F01-ACA8-042B9EC35064}.Release|ARM64.Deploy.0 = Release|ARM64 + {ADF26F0A-8BCE-4F01-ACA8-042B9EC35064}.Release|x64.ActiveCfg = Release|x64 + {ADF26F0A-8BCE-4F01-ACA8-042B9EC35064}.Release|x64.Build.0 = Release|x64 + {ADF26F0A-8BCE-4F01-ACA8-042B9EC35064}.Release|x64.Deploy.0 = Release|x64 + {ADF26F0A-8BCE-4F01-ACA8-042B9EC35064}.Release|x86.ActiveCfg = Release|x86 + {ADF26F0A-8BCE-4F01-ACA8-042B9EC35064}.Release|x86.Build.0 = Release|x86 + {ADF26F0A-8BCE-4F01-ACA8-042B9EC35064}.Release|x86.Deploy.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/LipUI/App.xaml b/src/LipUI/App.xaml index f6f9d24..9d33aa6 100644 --- a/src/LipUI/App.xaml +++ b/src/LipUI/App.xaml @@ -1,38 +1,16 @@ - + + xmlns:local="using:LipUI"> - - + + - - - - - - - - - - - - - - + diff --git a/src/LipUI/App.xaml.cs b/src/LipUI/App.xaml.cs index 76af964..515584a 100644 --- a/src/LipUI/App.xaml.cs +++ b/src/LipUI/App.xaml.cs @@ -1,115 +1,48 @@ -using System.IO; -using System.Reflection; -using System.Windows; -using System.Windows.Threading; -using LipUI.Services; -using LipUI.ViewModels; -using LipUI.Views.Pages; -using LipUI.Views.Windows; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Wpf.Ui.Mvvm.Contracts; -using Wpf.Ui.Mvvm.Services; +using LipUI.Models; +using Microsoft.UI.Xaml; +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. namespace LipUI { /// - /// Interaction logic for App.xaml + /// Provides application-specific behavior to supplement the default Application class. /// - public partial class App + public partial class App : Application { - public App() - { - } - // The.NET Generic Host provides dependency injection, configuration, logging, and other services. - // https://docs.microsoft.com/dotnet/core/extensions/generic-host - // https://docs.microsoft.com/dotnet/core/extensions/dependency-injection - // https://docs.microsoft.com/dotnet/core/extensions/configuration - // https://docs.microsoft.com/dotnet/core/extensions/logging - private static readonly IHost _host = Host - .CreateDefaultBuilder() - .ConfigureAppConfiguration(c => { c.SetBasePath(Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location)); }) - .ConfigureServices((context, services) => - { - // App Host - services.AddHostedService(); - - // Page resolver service - services.AddSingleton(); - - // Theme manipulation - services.AddSingleton(); - - // TaskBar manipulation - services.AddSingleton(); - - // Service containing navigation, same as INavigationWindow... but without window - services.AddSingleton(); - - // Main window with navigation - services.AddScoped(); - services.AddScoped(); - - // Views - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - // ViewModels - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - // Configuration - //services.Configure(context.Configuration.GetSection(nameof(AppConfig))); - }).Build(); - /// - /// Gets registered service. + /// Initializes the singleton application object. This is the first line of authored code + /// executed, and as such is the logical equivalent of main() or WinMain(). /// - /// Type of the service to get. - /// Instance of the service or . - public static T GetService() - where T : class - { -#pragma warning disable CS8603 - return _host.Services.GetService(typeof(T)) as T; -#pragma warning restore CS8603 - } - /// - /// Occurs when the application is loading. - /// - private async void OnStartup(object sender, StartupEventArgs e) + public App() { - await Global.Init(); - await _host.StartAsync(); + InitializeComponent(); + + Current.RequestedTheme = InternalServices.ApplicationTheme = Main.Config.PersonalizationSettings.ColorTheme switch + { + ElementTheme.Dark => ApplicationTheme.Dark, + ElementTheme.Light => ApplicationTheme.Light, + ElementTheme.Default or _ => Current.RequestedTheme + }; } /// - /// Occurs when the application is closing. + /// Invoked when the application is launched. /// - private async void OnExit(object sender, ExitEventArgs e) + /// Details about the launch request and process. + protected override void OnLaunched(LaunchActivatedEventArgs args) { - await _host.StopAsync(); - _host.Dispose(); + m_window = new MainWindow(); + m_window.Activate(); + + UnhandledException += App_UnhandledException; } - /// - /// Occurs when an exception is thrown by an application but not handled. - /// - private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) + private void App_UnhandledException(object sender, UnhandledExceptionEventArgs e) { - // For more info see https://docs.microsoft.com/en-us/dotnet/api/system.windows.application.dispatcherunhandledexception?view=windowsdesktop-6.0 } + + internal Window? m_window; } -} \ No newline at end of file +} diff --git a/src/LipUI/AssemblyInfo.cs b/src/LipUI/AssemblyInfo.cs deleted file mode 100644 index 2e567e0..0000000 --- a/src/LipUI/AssemblyInfo.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Windows; - -[assembly: ThemeInfo( - ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located - //(used if a resource is not found in the page, - // or application resource dictionaries) - ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located - //(used if a resource is not found in the page, - // app, or any theme specific resource dictionaries) -)] - \ No newline at end of file diff --git a/src/LipUI/Assets/Images.Designer.cs b/src/LipUI/Assets/Images.Designer.cs new file mode 100644 index 0000000..684ed57 --- /dev/null +++ b/src/LipUI/Assets/Images.Designer.cs @@ -0,0 +1,113 @@ +//------------------------------------------------------------------------------ +// +// 此代码由工具生成。 +// 运行时版本:4.0.30319.42000 +// +// 对此文件的更改可能会导致不正确的行为,并且如果 +// 重新生成代码,这些更改将会丢失。 +// +//------------------------------------------------------------------------------ + +namespace LipUI.Assets { + using System; + + + /// + /// 一个强类型的资源类,用于查找本地化的字符串等。 + /// + // 此类是由 StronglyTypedResourceBuilder + // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 + // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen + // (以 /str 作为命令选项),或重新生成 VS 项目。 + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Images { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Images() { + } + + /// + /// 返回此类使用的缓存的 ResourceManager 实例。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("LipUI.Assets.Images", typeof(Images).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// 重写当前线程的 CurrentUICulture 属性,对 + /// 使用此强类型资源类的所有资源查找执行重写。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// 查找 System.Byte[] 类型的本地化资源。 + /// + internal static byte[] applicationIcon_1024 { + get { + object obj = ResourceManager.GetObject("applicationIcon_1024", resourceCulture); + return ((byte[])(obj)); + } + } + + /// + /// 查找 System.Byte[] 类型的本地化资源。 + /// + internal static byte[] applicationIcon_256 { + get { + object obj = ResourceManager.GetObject("applicationIcon_256", resourceCulture); + return ((byte[])(obj)); + } + } + + /// + /// 查找 System.Byte[] 类型的本地化资源。 + /// + internal static byte[] glass { + get { + object obj = ResourceManager.GetObject("glass", resourceCulture); + return ((byte[])(obj)); + } + } + + /// + /// 查找 System.Byte[] 类型的本地化资源。 + /// + internal static byte[] grass_block { + get { + object obj = ResourceManager.GetObject("grass_block", resourceCulture); + return ((byte[])(obj)); + } + } + + /// + /// 查找 System.Byte[] 类型的本地化资源。 + /// + internal static byte[] netherrack { + get { + object obj = ResourceManager.GetObject("netherrack", resourceCulture); + return ((byte[])(obj)); + } + } + } +} diff --git a/src/LipUI/Language/LanguageFiles.resx b/src/LipUI/Assets/Images.resx similarity index 83% rename from src/LipUI/Language/LanguageFiles.resx rename to src/LipUI/Assets/Images.resx index 2e54183..1c4e5d5 100644 --- a/src/LipUI/Language/LanguageFiles.resx +++ b/src/LipUI/Assets/Images.resx @@ -118,7 +118,19 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - files\en.lang;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + applicationIcon-1024.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + applicationIcon-256.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + glass.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + grass_block.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + netherrack.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 \ No newline at end of file diff --git a/src/LipUI/Assets/SplashScreen.scale-200.png b/src/LipUI/Assets/SplashScreen.scale-200.png new file mode 100644 index 0000000..32f486a Binary files /dev/null and b/src/LipUI/Assets/SplashScreen.scale-200.png differ diff --git a/src/LipUI/Assets/Square150x150Logo.scale-200.png b/src/LipUI/Assets/Square150x150Logo.scale-200.png new file mode 100644 index 0000000..53ee377 Binary files /dev/null and b/src/LipUI/Assets/Square150x150Logo.scale-200.png differ diff --git a/src/LipUI/Assets/Square44x44Logo.scale-200.png b/src/LipUI/Assets/Square44x44Logo.scale-200.png new file mode 100644 index 0000000..f713bba Binary files /dev/null and b/src/LipUI/Assets/Square44x44Logo.scale-200.png differ diff --git a/src/LipUI/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/src/LipUI/Assets/Square44x44Logo.targetsize-24_altform-unplated.png new file mode 100644 index 0000000..dc9f5be Binary files /dev/null and b/src/LipUI/Assets/Square44x44Logo.targetsize-24_altform-unplated.png differ diff --git a/src/LipUI/Assets/StoreLogo.png b/src/LipUI/Assets/StoreLogo.png new file mode 100644 index 0000000..a4586f2 Binary files /dev/null and b/src/LipUI/Assets/StoreLogo.png differ diff --git a/src/LipUI/Assets/Wide310x150Logo.scale-200.png b/src/LipUI/Assets/Wide310x150Logo.scale-200.png new file mode 100644 index 0000000..8b4a5d0 Binary files /dev/null and b/src/LipUI/Assets/Wide310x150Logo.scale-200.png differ diff --git a/src/LipUI/Assets/glass.png b/src/LipUI/Assets/glass.png new file mode 100644 index 0000000..6b7246a Binary files /dev/null and b/src/LipUI/Assets/glass.png differ diff --git a/src/LipUI/Assets/grass_block.png b/src/LipUI/Assets/grass_block.png new file mode 100644 index 0000000..effc84c Binary files /dev/null and b/src/LipUI/Assets/grass_block.png differ diff --git a/src/LipUI/Assets/netherrack.png b/src/LipUI/Assets/netherrack.png new file mode 100644 index 0000000..2fc0530 Binary files /dev/null and b/src/LipUI/Assets/netherrack.png differ diff --git a/src/LipUI/FodyWeavers.xml b/src/LipUI/FodyWeavers.xml deleted file mode 100644 index 5029e70..0000000 --- a/src/LipUI/FodyWeavers.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/LipUI/FodyWeavers.xsd b/src/LipUI/FodyWeavers.xsd deleted file mode 100644 index 05e92c1..0000000 --- a/src/LipUI/FodyWeavers.xsd +++ /dev/null @@ -1,141 +0,0 @@ - - - - - - - - - - - - A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks - - - - - A list of assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks. - - - - - A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks - - - - - A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks. - - - - - A list of unmanaged 32 bit assembly names to include, delimited with line breaks. - - - - - A list of unmanaged 64 bit assembly names to include, delimited with line breaks. - - - - - The order of preloaded assemblies, delimited with line breaks. - - - - - - This will copy embedded files to disk before loading them into memory. This is helpful for some scenarios that expected an assembly to be loaded from a physical file. - - - - - Controls if .pdbs for reference assemblies are also embedded. - - - - - Controls if runtime assemblies are also embedded. - - - - - Controls whether the runtime assemblies are embedded with their full path or only with their assembly name. - - - - - Embedded assemblies are compressed by default, and uncompressed when they are loaded. You can turn compression off with this option. - - - - - As part of Costura, embedded assemblies are no longer included as part of the build. This cleanup can be turned off. - - - - - Costura by default will load as part of the module initialization. This flag disables that behavior. Make sure you call CosturaUtility.Initialize() somewhere in your code. - - - - - Costura will by default use assemblies with a name like 'resources.dll' as a satellite resource and prepend the output path. This flag disables that behavior. - - - - - A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with | - - - - - A list of assembly names to include from the default action of "embed all Copy Local references", delimited with |. - - - - - A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with | - - - - - A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with |. - - - - - A list of unmanaged 32 bit assembly names to include, delimited with |. - - - - - A list of unmanaged 64 bit assembly names to include, delimited with |. - - - - - The order of preloaded assemblies, delimited with |. - - - - - - - - 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. - - - - - A comma-separated list of error codes that can be safely ignored in assembly verification. - - - - - 'false' to turn off automatic generation of the XML Schema file. - - - - - \ No newline at end of file diff --git a/src/LipUI/Global.cs b/src/LipUI/Global.cs deleted file mode 100644 index f8d23ef..0000000 --- a/src/LipUI/Global.cs +++ /dev/null @@ -1,639 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using CommunityToolkit.Mvvm.ComponentModel; -using LipNETWrapper; -using LipUI.Models; -using LipUI.ViewModels; -using LipUI.Views.Controls; -using LipUI.Views.Windows; -using Wpf.Ui.Common; -using Wpf.Ui.Common.Interfaces; -using Wpf.Ui.Controls; - -namespace LipUI -{ - internal static class Global - { - - /// - /// Disclaimer - /// - private const string eulaTextCN = """ - 本软件包管理器(以下简称“本软件”)是由LiteLDev(以下简称“开发者”)开发和提供的。本软件旨在帮助用户管理和安装各种软件包,但不对任何软件包的内容、质量、功能、安全性或合法性负责。用户在使用本软件时,应自行判断和承担相关风险。 - 开发者不保证本软件的稳定性、可靠性、准确性或完整性。开发者不对本软件可能存在的任何缺陥、错误、病毒或其他有害成分负责。开发者不对用户使用本软件造成的任何直接或间接损失(包括但不限于数据丢失、设备损坏、利润损失等)负责。 - 开发者保留随时修改、更新或终止本软件及其相关服务的权利,无需事先通知用户。用户应自行备份重要数据,并定期检查本软件是否有更新版本。 - 用户在使用本软件时,应遵守相关法律法规,尊重他人的知识产权和隐私权,不得利用本软件进行任何违法或侵权行为。如用户违反上述规定,造成任何第三方损害或被第三方索赔,开发者不承担任何责任。 - 如果用户对本免责条款有任何疑问或意见,请联系开发者:LiteLDev - """; - private const string eulaTextEN = """ - This software package manager (hereinafter referred to as "this software") is developed and provided by LiteLDev (hereinafter referred to as "the developer"). This software is designed to help users manage and install various software packages, but is not responsible for any content, quality, functionality, security or legality of any software package. Users should use this software at their own discretion and assume all related risks. - The developer does not guarantee the stability, reliability, accuracy or completeness of this software. The developer is not liable for any defects, errors, viruses or other harmful components that may exist in this software. The developer is not liable for any direct or indirect damages (including but not limited to data loss, device damage, profit loss etc.) caused by the use of this software. - The developer reserves the right to modify, update or terminate this software and its related services at any time without prior notice to users. Users should back up important data and check regularly for updates of this software. - Users should comply with relevant laws and regulations when using this software, respect the intellectual property rights and privacy rights of others, and not use this software for any illegal or infringing activities. If users violate the above provisions and cause any damage to any third party or are claimed by any third party, the developer does not bear any responsibility. - If you have any questions or comments about this disclaimer, please contact the developer: LiteLDev - """; - /// - /// i18n - /// - internal static Language.Model I18N => - Application.Current.FindResource("I18N") as Language.Model -#if DEBUG - ?? throw new NullReferenceException("undefined I18N"); -#else - ?? i18nFallback.Value; - private static Lazy i18nFallback = new(() => new());//资源获取失败时返回 -#endif - - /// 初始化语言 - private static async Task InitLanguage() - { -#if DEBUG - _ = Task.Run(() => - { - //调试模式自动保存语言文件 - var dir = Path.Combine(Environment.CurrentDirectory); - var index = dir.LastIndexOf("LipUI", StringComparison.Ordinal); - if (index != -1) - { - dir = Path.Combine(dir[..index], "LipUI", "Language", "Files"); - foreach (var (id, _) in Language.Utils.AvailableLanguages) - { - if (id is Language.Utils.LangId.zh_Hans or Language.Utils.LangId.Default) { continue; } - var file = Path.Combine(dir, id + ".lang"); - if (File.Exists(file)) - { - File.WriteAllText(file, - Language.Utils.SerializeToStr( - Language.Utils.SerializeToDict( - Language.Utils.DeserializeFromDict( - Language.Utils.GetLangDictionaryFromResource(id) - ) - ) - ), Encoding.UTF8);//刷入类并写入新项 - } - } - } - }).ConfigureAwait(false); -#endif - await Language.Utils.SwitchLanguage(Config.Language); - } - /// 初始化全局变量(初始化程序) - internal static async Task Init() - { - await Global.InitLanguage(); - var eulaPath = Path.Combine(ConfigFolder, "EULA.txt"); - var eulaText = (Config.Language is Language.Utils.LangId.Default - ? Language.Utils.GetSystemLanguage() - : Config.Language) switch - { - Language.Utils.LangId.zh_Hans => eulaTextCN, - //Language.Utils.LangId.en => , - _ => eulaTextEN - }; - if (File.Exists(eulaPath)) - { - if (File.ReadAllText(eulaPath, Encoding.UTF8) == eulaText)//条款已读 - { - InitNext(); - return; - } - } - //显示条款 - { - _ = ShowDialog(I18N.Eula, await DispatcherInvokeAsync(() => new TextBlock - { - Text = eulaText, - TextWrapping = TextWrapping.WrapWithOverflow - }), (I18N.EulaDeny, _ => - { - PopupSnackbarWarn(I18N.EulaDeniedTitle, I18N.EulaDeniedContent); - Task.Delay(2500).ContinueWith(_ => - { - Environment.Exit(0); - }); - } - ), (I18N.EulaAccept, hide => - { - hide(); - var directory = Path.GetDirectoryName(eulaPath); - if (directory is not null && !Directory.Exists(directory)) - Directory.CreateDirectory(directory); - File.WriteAllText(eulaPath, eulaText, Encoding.UTF8); - InitNext(); - } - ), dialog => - { - dialog.DialogHeight = 400; - dialog.DialogWidth = 400; - }); - } - } - - internal static void InitNext() - { - // 从配置获取Lip路径 - if (!TryRefreshLipPath()) - { - var vm = new LipInstallerViewModel(); - _ = ShowDialog(I18N.LipInstallerDialog, new LipInstaller(vm), (I18N.LipInstallerDialogComplete, hide => - { - Config.AutoLipPath = !vm.ManualExe; - Config.LipPath = vm.LipPath; - if (TryRefreshLipPath()) - { - hide(); - CheckWorkDir(); - } - else - PopupSnackbar(I18N.LipInstallerSnackbarLipNotFound, I18N.LipInstallerSnackbarLipNotFoundTip); - } - ), (I18N.LipInstallerDialogDownload, hide => - { - Config.AutoLipPath = !vm.ManualExe; - if (vm.GlobalExe) - { - if (vm.ShowProgressBar) - { - vm.Tip = I18N.LipInstallerLipDownloading; - } - else - { - vm.ShowProgressBar = true; - Task.Run(async () => - { - try - { - var bytes = await Utils.DownloadLipInstaller(e => - { - DispatcherInvoke(() => - { - vm.Progress = e.ProgressPercentage; - string BytesToStr(long val) => val switch - { - < 1024 => $"{val}B", - < 1024 * 1024 => $"{val / 1024}KB", - < 1024 * 1024 * 1024 => $"{val / 1024 / 1024}MB", - _ => $"{val / 1024 / 1024 / 1024}GB" - }; - vm.Tip = string.Format(I18N.LipInstallerLipDownloadingProgress, BytesToStr(e.BytesReceived), - BytesToStr(e.TotalBytesToReceive)); - }); - }); - await DispatcherInvokeAsync(() => vm.Tip = I18N.LipInstallerLipDownloadedSuccess); - var tmpDir = Path.Combine(ConfigFolder, "temp"); - if (!Directory.Exists(tmpDir)) Directory.CreateDirectory(tmpDir); - var installer = Path.Combine(tmpDir, "lip_installer.exe"); - File.WriteAllBytes(installer, bytes); - //等待安装程序退出 - Process.Start(installer) - ?.WaitForExit(); - //开启新的LipUI - Process.Start(Environment.GetCommandLineArgs()[0], - string.Join(" ", - from arg in Environment.GetCommandLineArgs().Skip(1) - select $"\"{arg}\"")); - //删除安装包 - try - { - File.Delete(installer); - } - catch - { - // ignored - } - //退出当前实例 - Environment.Exit(0); - } - catch (Exception e) - { - await DispatcherInvokeAsync(() => vm.Tip = string.Format(I18N.LipInstallerDownloadFailed, e)); -#if DEBUG - throw; -#endif - } - }); - vm.Progress = 0; - } - } - else if (vm.PortableExe) - { - if (vm.ShowProgressBar) - { - vm.Tip = I18N.LipInstallerLipDownloading; - } - else - { - vm.ShowProgressBar = true; - Task.Run(async () => - { - try - { - var bytes = await Utils.DownloadLipPortable(e => - { - DispatcherInvoke(() => - { - vm.Progress = e.ProgressPercentage; - string BytesToStr(long val) => val switch - { - < 1024 => $"{val}B", - < 1024 * 1024 => $"{val / 1024}KB", - < 1024 * 1024 * 1024 => $"{val / 1024 / 1024}MB", - _ => $"{val / 1024 / 1024 / 1024}GB" - }; - vm.Tip = string.Format(I18N.LipInstallerLipDownloadingProgress, BytesToStr(e.BytesReceived), - BytesToStr(e.TotalBytesToReceive)); - }); - }); - await DispatcherInvokeAsync(() => vm.Tip = I18N.LipInstallerLipDownloadedSuccess); - //解压缩 - // Create a memory stream from the byte array - using MemoryStream ms = new MemoryStream(bytes); - // Create a zip archive from the memory stream - using ZipArchive zip = new ZipArchive(ms, ZipArchiveMode.Read); - ZipArchiveEntry entry = zip.Entries.First(x => Path.GetFileName(x.Name) == "lip.exe") - ?? throw new Exception( - I18N.LipInstallerLipNotFound); - entry.ExtractToFile(Path.Combine(Directory.GetCurrentDirectory(), entry.Name)); - //开启新的LipUI - //Process.Start(Environment.GetCommandLineArgs()[0], - // string.Join(" ", - // from arg in Environment.GetCommandLineArgs().Skip(1) - // select $"\"{arg}\"")); - //Environment.Exit(0); - hide(); - } - catch (Exception e) - { - await DispatcherInvokeAsync(() => vm.Tip = string.Format(I18N.LipInstallerDownloadFailed, e)); -#if DEBUG - throw; -#endif - } - }); - vm.Progress = 0; - } - } - else - PopupSnackbar(I18N.LipInstallerInvalidOperation, I18N.LipInstallerInvalidOperationTip, SymbolRegular.Warning16, ControlAppearance.Danger); - } - ), modify: dialog => - { - dialog.DialogHeight = 300; - }); - } - else - { - CheckWorkDir(); - } - } - internal static void CheckWorkDir() - { - if (!Directory.Exists(Config.WorkingDirectory?.Directory ?? "")) - {//保存的WorkingDirectory不合法,需要手动选择 - _ = ShowDialog(I18N.WorkingPathSelectorInitDialog, new WorkingPathSelector(), ( - I18N.WorkingPathSelectorInitDialogComplete, hide => - { - if (Directory.Exists(Config.WorkingDirectory?.Directory ?? "")) - { - hide(); - } - else - { - PopupSnackbar(I18N.WorkingPathSelectorInitErr, Config.WorkingDirectory?.Directory ?? "", SymbolRegular.Warning16, ControlAppearance.Caution); - } - } - ), modify: dialog => - { - dialog.DialogWidth = 750; - dialog.DialogHeight = 370; - }); - } - else - { - Lip.WorkingPath = Config.WorkingDirectory?.Directory ?? ""; - } - } - static bool TryRefreshLipPath() - { - if (Config.AutoLipPath)//是否设置了自动获取 - { - //当前目录优先 - var current = Path.GetFullPath("lip.exe"); - if (File.Exists(current)) - { - Lip.ExecutablePath = current; - return true; - } - //全局Path - var (success, path) = Utils.TryGetLipFromPath(); - if (success) - { - Lip.ExecutablePath = path!; - return true; - } - } - else if (File.Exists(Config.LipPath))//使用自定义路径 - { - Lip.ExecutablePath = Config.LipPath; - return true; - } - else - { - var file = Path.Combine(Config.LipPath, "lip.exe"); - if (File.Exists(file))//使用自定义路径文件夹下的lip.exe - { - Lip.ExecutablePath = file; - return true; - } - } - return false;//最终没能找到 - } - /// 配置文件路径 - private static readonly string ConfigFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".lip", "config", "lipui"); - internal static readonly string ConfigPath = Path.Combine(ConfigFolder, "config.json"); - private static Lazy _config = //延迟加载的配置文件对象 - new(() => - { - var fp = Path.GetFullPath(ConfigPath); - var result = File.Exists(fp) - ? AppConfig.FromString(File.ReadAllText(fp)) - : new AppConfig(); - //订阅属性更改,并应用属性 - void Save() - { - try - { - var dir = Path.GetDirectoryName(fp)!; - if (!Directory.Exists(dir)) - { - Directory.CreateDirectory(dir); - } - File.WriteAllText(fp, result?.ToString()); - } - catch - { - // ignored - } - } - result.PropertyChanged += (s, e) => - { - switch (e.PropertyName)//修改后应用配置 - { - case nameof(result.WorkingDirectory) when result!.WorkingDirectory is not null: - Lip!.WorkingPath = result!.WorkingDirectory.Directory; - break; - case nameof(result.LipPath) when result!.LipPath is not null: - Lip!.ExecutablePath = result!.LipPath; - TryRefreshLipPath(); - break; - case nameof(result.AutoLipPath): - TryRefreshLipPath(); - break; - } - //保存 - Save(); - }; - //遍历所有子属性,订阅属性更改 - var props = result.GetType().GetProperties(); - - void PropertyChangedHandler(object sender, PropertyChangedEventArgs e) => Save(); - void CollectionChangeHandler(object sender, NotifyCollectionChangedEventArgs e) - { - Save(); - if (e.NewItems is not null) - foreach (var item in e.NewItems) - Subscribe(item); - } - void Subscribe(object? sub) - { - if (sub is INotifyPropertyChanged subProp) - { - subProp.PropertyChanged -= PropertyChangedHandler; - subProp.PropertyChanged += PropertyChangedHandler; - } - if (sub is INotifyCollectionChanged subCol) - { - subCol.CollectionChanged -= CollectionChangeHandler; - subCol.CollectionChanged += CollectionChangeHandler; - } - } - foreach (var prop in props) - { - if (prop.PropertyType.IsClass && prop.PropertyType != typeof(string)) - { - var sub = prop.GetValue(result); - Subscribe(sub); - } - } - return result; - }); - /// - /// 程序配置文件实例 - /// - internal static AppConfig Config => _config.Value; - /// - /// 实现Lip接口的实例 - /// - internal static ILipWrapper Lip = new LipConsoleWrapper(); - /// - /// UI线程封送消息,异步可等待 - /// - /// 方法对象 - /// 异步任务 - internal static async Task DispatcherInvokeAsync(Action act) - { - await Application.Current.Dispatcher.InvokeAsync(act); - } - /// - /// UI线程封送消息,异步可等待 - /// - /// 异步方法对象 - /// 异步任务 - internal static async Task DispatcherInvokeAsync(Func act) - { - await Application.Current.Dispatcher.InvokeAsync(act); - } - /// - /// UI线程封送消息 - /// - /// 返回类型 - /// 方法对象 - /// 返回对象 - internal static async Task DispatcherInvokeAsync(Func act) - { - return await Application.Current.Dispatcher.InvokeAsync(act); - } - /// - /// UI线程封送消息 - /// - /// 方法委托 - internal static void DispatcherInvoke(Action act) - { - Application.Current.Dispatcher.Invoke(act); - } - /// - /// 导航到页面 - /// - /// 页面的类,要求继承ui:Page - /// 页面的ViewModel,要求继承ObservableObject且实现INavigationAware - public static void Navigate() - where T : INavigableView - where TV : ObservableObject, INavigationAware - { - DispatcherInvoke(() => - { - ((MainWindow)Application.Current.MainWindow!).Navigate(typeof(T)); - }); - } - /// - /// 弹出底部提示 - /// - public static void PopupSnackbar(string title, string content, SymbolRegular icon = SymbolRegular.Info16, ControlAppearance appearance = ControlAppearance.Secondary, int timeout = 4000) - { - DispatcherInvoke(() => - { - var snackbar = ((MainWindow)Application.Current.MainWindow!).Snackbar; - snackbar.Timeout = timeout; - snackbar.Show(title, content, icon, appearance); - }); - } - public static void PopupSnackbarWarn(string title, string content) - => PopupSnackbar(title, content, SymbolRegular.Warning16, ControlAppearance.Caution, 8000); - /// - /// 弹出对话框 - /// - /// 标题 - /// 内容(如果是WPF控件需要DispatcherInvoke在UI线程创建) - /// 左按钮内容和回调 - /// 右按钮内容和回调 - /// 对dialog控件的额外修改(通常无需操作) - /// - public static async Task ShowDialog( - string title, - object content, - (string, Action)? right = null, - (string, Action)? left = null, - Action? modify = null) - { - await DispatcherInvokeAsync(async () => - { - var dialog = ((MainWindow)Application.Current.MainWindow!).Dialog; - bool successAndHide = false; - void Hide() - { - successAndHide = true; - DispatcherInvoke(() => - { - dialog.Hide(); - }); - } - RoutedEventHandler onDialogOnButtonLeftClick = (_, e) => Hide(); - RoutedEventHandler onDialogOnButtonRightClick = (_, e) => Hide(); - { - dialog.ButtonRightVisibility = Visibility.Hidden; - if (right is var (k, v)) - { - dialog.ButtonRightName = k; - dialog.ButtonRightVisibility = Visibility.Visible; - onDialogOnButtonRightClick = (_, e) => v(Hide); - } - } - { - dialog.ButtonLeftVisibility = Visibility.Hidden; - if (left is var (k, v)) - { - dialog.ButtonLeftName = k; - dialog.ButtonLeftVisibility = Visibility.Visible; - onDialogOnButtonLeftClick = (_, e) => v(() => Hide()); - } - } - dialog.DialogHeight = 250; - dialog.DialogWidth = 400; - modify?.Invoke(dialog); - //if (content is Control ct) - //{ - // //if (ct.MinHeight > 100) - // //{ - // //dialog.DialogHeight = double.NaN-400; - // //dialog.DialogWidth = double.NaN; - // //} - //} - dialog.ButtonLeftClick += onDialogOnButtonLeftClick; - dialog.ButtonRightClick += onDialogOnButtonRightClick; - //dialog.Title = title; - if (content is not UIElement) - { - content = new TextBlock - { - TextWrapping = TextWrapping.WrapWithOverflow, - Text = content.ToString() - }; - } - - var textBlock = new TextBlock { FontSize = 20, Text = title, Margin = new Thickness(0, 0, 0, 5) }; - textBlock.SetValue(DockPanel.DockProperty, Dock.Top); - dialog.Content = new DockPanel() - { - Children = - { - textBlock, - (UIElement)content - } - }; - while (!successAndHide) - { - await dialog.ShowAndWaitAsync(); - } - dialog.ButtonLeftClick -= onDialogOnButtonLeftClick; - dialog.ButtonRightClick -= onDialogOnButtonRightClick; - }); - } - #region 共享队列(页面之间传递信息) - private static readonly List _queuedItems = new(); - public static void EnqueueItem(T item) - { - if (item == null) throw new ArgumentNullException(nameof(item)); - var index = _queuedItems.FindIndex(x => x.GetType() == item.GetType()); - if (index != -1) - { - _queuedItems[index] = item; - } - else - { - _queuedItems.Add(item); - } - } - public static bool TryDequeueItem([NotNullWhen(true)] out T? val) - { - val = default; - var isFound = false; - foreach (var item in _queuedItems) - { - if (item is T v) - { - val = v; - isFound = true; - break; - } - } - if (isFound) - { - _queuedItems.Remove(val!); - } - return isFound; - } - #endregion - } -} diff --git a/src/LipUI/Helpers/BooleanToOpacityConverter.cs b/src/LipUI/Helpers/BooleanToOpacityConverter.cs deleted file mode 100644 index 240049b..0000000 --- a/src/LipUI/Helpers/BooleanToOpacityConverter.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Globalization; -using System.Windows.Data; - -namespace LipUI.Helpers; - -internal class BooleanToOpacityConverter : IValueConverter -{ - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - if (value is true) - { - if (double.TryParse(parameter.ToString(), out var pd)) - { - return pd; - } - return .7; - } - return true; - } - - public object ConvertBack(object value, Type targetType, object parameter, - CultureInfo culture) - { - return Binding.DoNothing; - } -} \ No newline at end of file diff --git a/src/LipUI/Helpers/BooleanToVisibilityConverter.cs b/src/LipUI/Helpers/BooleanToVisibilityConverter.cs deleted file mode 100644 index 52d5975..0000000 --- a/src/LipUI/Helpers/BooleanToVisibilityConverter.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Globalization; -using System.Windows; -using System.Windows.Data; - -namespace LipUI.Helpers; -internal class BooleanToVisibilityConverter : IValueConverter -{ - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - if (value is true) - { - return Visibility.Visible; - } - return Visibility.Collapsed; - } - - public object ConvertBack(object value, Type targetType, object parameter, - CultureInfo culture) - { - return Binding.DoNothing; - } -} \ No newline at end of file diff --git a/src/LipUI/Helpers/BooleanToVisibilityHiddenConverter.cs b/src/LipUI/Helpers/BooleanToVisibilityHiddenConverter.cs deleted file mode 100644 index 0b5be26..0000000 --- a/src/LipUI/Helpers/BooleanToVisibilityHiddenConverter.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Globalization; -using System.Windows; -using System.Windows.Data; - -namespace LipUI.Helpers; - -internal class BooleanToVisibilityHiddenConverter : IValueConverter -{ - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - if (value is true) - { - return Visibility.Visible; - } - return Visibility.Hidden; - } - - public object ConvertBack(object value, Type targetType, object parameter, - CultureInfo culture) - { - return Binding.DoNothing; - } -} \ No newline at end of file diff --git a/src/LipUI/Helpers/EnumToBooleanConverter.cs b/src/LipUI/Helpers/EnumToBooleanConverter.cs deleted file mode 100644 index a057e29..0000000 --- a/src/LipUI/Helpers/EnumToBooleanConverter.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Globalization; -using System.Windows.Data; -using Wpf.Ui.Appearance; - -namespace LipUI.Helpers -{ - internal class EnumToBooleanConverter : IValueConverter - { - - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - if (parameter is not string enumString) - throw new ArgumentException("ExceptionEnumToBooleanConverterParameterMustBeAnEnumName"); - - if (!Enum.IsDefined(typeof(ThemeType), value)) - throw new ArgumentException("ExceptionEnumToBooleanConverterValueMustBeAnEnum"); - - var enumValue = Enum.Parse(typeof(ThemeType), enumString); - - return enumValue.Equals(value); - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - if (parameter is not string enumString) - throw new ArgumentException("ExceptionEnumToBooleanConverterParameterMustBeAnEnumName"); - - return Enum.Parse(typeof(ThemeType), enumString); - } - } -} diff --git a/src/LipUI/Helpers/FeaturedTagConverter.cs b/src/LipUI/Helpers/FeaturedTagConverter.cs deleted file mode 100644 index 897d599..0000000 --- a/src/LipUI/Helpers/FeaturedTagConverter.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Globalization; -using System.Windows.Data; - -namespace LipUI.Helpers; - -internal class FeaturedTagConverter : IValueConverter -{ - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - return value switch - { - "featured" => Global.I18N.RegistryTagFeatured, - "integration" => Global.I18N.RegistryTagIntegration, - "module" => Global.I18N.RegistryTagModule, - "plugin" => Global.I18N.RegistryTagPlugin, - "anti-cheat" => Global.I18N.RegistryTagAntiCheat, - _ => value - }; - } - - public object ConvertBack(object value, Type targetType, object parameter, - CultureInfo culture) - { - return Binding.DoNothing; - } -} \ No newline at end of file diff --git a/src/LipUI/Helpers/HalfOriginalConverter.cs b/src/LipUI/Helpers/HalfOriginalConverter.cs deleted file mode 100644 index 9e2f3f1..0000000 --- a/src/LipUI/Helpers/HalfOriginalConverter.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Globalization; -using System.Windows.Data; - -namespace LipUI.Helpers -{ - internal class HalfOriginalConverter : IValueConverter - { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - if (value is not double original) - throw new ArgumentException("ExceptionHalfOriginalConverterValueMustBeADouble"); - return original / 2; - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - if (value is not double half) - throw new ArgumentException("ExceptionHalfOriginalConverterValueMustBeADouble"); - return half * 2; - } - } -} diff --git a/src/LipUI/Helpers/InvBooleanConverter.cs b/src/LipUI/Helpers/InvBooleanConverter.cs deleted file mode 100644 index 76526fc..0000000 --- a/src/LipUI/Helpers/InvBooleanConverter.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Globalization; -using System.Windows.Data; - -namespace LipUI.Helpers; - -internal class InvBooleanConverter : IValueConverter -{ - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - if (value is true) - { - return false; - } - return true; - } - - public object ConvertBack(object value, Type targetType, object parameter, - CultureInfo culture) - { - if (value is true) - { - return false; - } - return true; - } -} \ No newline at end of file diff --git a/src/LipUI/Helpers/InvBooleanToVisibilityConverter.cs b/src/LipUI/Helpers/InvBooleanToVisibilityConverter.cs deleted file mode 100644 index a0d5f7d..0000000 --- a/src/LipUI/Helpers/InvBooleanToVisibilityConverter.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Globalization; -using System.Windows; -using System.Windows.Data; - -namespace LipUI.Helpers; - -internal class InvBooleanToVisibilityConverter : IValueConverter -{ - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - if (value is true) - { - return Visibility.Collapsed; - } - return Visibility.Visible; - } - - public object ConvertBack(object value, Type targetType, object parameter, - CultureInfo culture) - { - return Binding.DoNothing; - } -} \ No newline at end of file diff --git a/src/LipUI/Helpers/MultiValueConverter.cs b/src/LipUI/Helpers/MultiValueConverter.cs deleted file mode 100644 index b28da48..0000000 --- a/src/LipUI/Helpers/MultiValueConverter.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Globalization; -using System.Linq; -using System.Windows.Data; - -namespace LipUI.Helpers; -internal class MultiValueConverter : IMultiValueConverter -{ - public object? Convert(object[]? values, Type targetType, object parameter, CultureInfo culture) - { - // Check if values are valid - if (values == null ) return null; - if (values.Length == 0) return null; - return values.ToArray(); - } - - public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) - { - return (object[])value; - } -} diff --git a/src/LipUI/Helpers/OpacityToVisibilityConverter.cs b/src/LipUI/Helpers/OpacityToVisibilityConverter.cs deleted file mode 100644 index bc23b0b..0000000 --- a/src/LipUI/Helpers/OpacityToVisibilityConverter.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Globalization; -using System.Windows; -using System.Windows.Data; - -namespace LipUI.Helpers -{ - internal class OpacityToVisibilityConverter : IValueConverter - { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - if (value is double opacity) - { - if (opacity > 0) - { - return Visibility.Visible; - } - } - return Visibility.Hidden; - } - - public object ConvertBack(object value, Type targetType, object parameter, - CultureInfo culture) - { - //if (value is System.Windows.Visibility visibility) - //{ - // if (visibility == System.Windows.Visibility.Visible) - // { - // return 1.0; - // } - //} - return Binding.DoNothing; - } - } -} diff --git a/src/LipUI/Helpers/StringFormatConverter.cs b/src/LipUI/Helpers/StringFormatConverter.cs deleted file mode 100644 index a1f1c8f..0000000 --- a/src/LipUI/Helpers/StringFormatConverter.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Globalization; -using System.Windows.Data; - -namespace LipUI.Helpers; - -internal class StringFormatConverter : IMultiValueConverter -{ - - public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) - { - if (values.Length == 0) - { - return string.Empty; - } - var format = values[0] as string; - if (string.IsNullOrWhiteSpace(format)) - { - return string.Empty; - } - var args = new object[values.Length - 1]; - Array.Copy(values, 1, args, 0, args.Length); - return string.Format(format, args); - } - public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) - { - return Array.Empty(); - } -} \ No newline at end of file diff --git a/src/LipUI/Helpers/StringNotEmptyToVisibilityConverter.cs b/src/LipUI/Helpers/StringNotEmptyToVisibilityConverter.cs deleted file mode 100644 index e3b5060..0000000 --- a/src/LipUI/Helpers/StringNotEmptyToVisibilityConverter.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Globalization; -using System.Windows; -using System.Windows.Data; - -namespace LipUI.Helpers; -internal class StringNotEmptyToVisibilityConverter : IValueConverter -{ - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - if (value is string str) - { - if (string.IsNullOrWhiteSpace(str)) - { - return Visibility.Collapsed; - } - } - return Visibility.Visible; - } - - public object ConvertBack(object value, Type targetType, object parameter, - CultureInfo culture) - { - return Binding.DoNothing; - } -} \ No newline at end of file diff --git a/src/LipUI/InternalServices.cs b/src/LipUI/InternalServices.cs new file mode 100644 index 0000000..372f0f9 --- /dev/null +++ b/src/LipUI/InternalServices.cs @@ -0,0 +1,120 @@ +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media.Imaging; +using Microsoft.Windows.ApplicationModel.Resources; +using System; +using System.IO; +using System.Threading.Tasks; +using Windows.UI; + +namespace LipUI; + +internal static class ResourceExtensions +{ + private static readonly ResourceLoader _resourceLoader = new(); + + public static string GetLocalized(this string resourceKey) => _resourceLoader.GetString(resourceKey); +} + +internal static class FrameExtensions +{ + public static bool TryGoBack(this Frame frame) + { + if (frame.CanGoBack) + { + frame.GoBack(); + return true; + } + return false; + } +} + +internal static class ColorExtensions +{ + public static Color Invert(this in Color color) + => Color.FromArgb(color.A, (byte)(255 - color.R), (byte)(255 - color.G), (byte)(255 - color.B)); +} + +internal static class InternalServices +{ + public static BitmapImage CreateImageFromBytes(byte[] bytes) + { + var image = new BitmapImage(); + using var stream = new MemoryStream(bytes); + image.SetSource(stream.AsRandomAccessStream()); + return image; + } + + internal static MainWindow? MainWindow { get; set; } + + public static async ValueTask ShowInfoBarAsync( + string? title, + string? message = null, + InfoBarSeverity severity = InfoBarSeverity.Informational, + TimeSpan interval = default, + UIElement? barContent = null) + { + if (interval == default) + interval = TimeSpan.FromSeconds(3); + + + + if (MainWindow is not null) + await MainWindow.ShowInfoBarAsync(title, message, severity, interval, barContent); + } + + public static async ValueTask ShowInfoBarAsync( + Exception ex, + bool containsStacktrace = false, + InfoBarSeverity severity = InfoBarSeverity.Error, + TimeSpan interval = default, + UIElement? barContent = null) + { + if (interval == default) + interval = TimeSpan.FromSeconds(5); + await ShowInfoBarAsync( + ex.GetType().Name, + containsStacktrace ? ex.ToString() : ex.Message, + severity, + interval, + barContent); + } + + public static void ShowInfoBar( + string? title, + string? message = null, + InfoBarSeverity severity = InfoBarSeverity.Informational, + TimeSpan interval = default, + UIElement? barContent = null, + Action? completed = null) + { + if (interval == default) + interval = TimeSpan.FromSeconds(3); + + MainWindow?.ShowInfoBar(title, message, severity, interval, barContent, completed); + } + + public static void ShowInfoBar( + Exception ex, + bool containsStacktrace = false, + InfoBarSeverity severity = InfoBarSeverity.Error, + TimeSpan interval = default, + UIElement? barContent = null, + Action? completed = null) + { + if (interval == default) + interval = TimeSpan.FromSeconds(5); + ShowInfoBar(ex.GetType().Name, + containsStacktrace ? ex.ToString() : ex.Message, + severity, + interval, + barContent, + completed); + } + + public static event Action? WindowClosed; + + internal static void OnWindowClosed() => WindowClosed?.Invoke(); + + public static ApplicationTheme ApplicationTheme { get; internal set; } +} diff --git a/src/LipUI/Language/Files/en.lang b/src/LipUI/Language/Files/en.lang deleted file mode 100644 index 248d4b6..0000000 --- a/src/LipUI/Language/Files/en.lang +++ /dev/null @@ -1,138 +0,0 @@ -ApplicationTitle=LipUI - Tooth Pack Manager -NavigationHome=Home -NavigationLocal=Local Tooth -NavigationInstall=Install Tooth Pack -NavigationRemove=Remove Tooth Pack -NavigationRegistry=Tooth Hub -NavigationWebUi=WebUI -NavigationDeveloper=Developer Tools -NavigationSettings=Settings & About -LocalBottomInstall= {0} Tooth Pack has been installed -LocalBottomReload=Reload -RegistryBottomInstall= {0} of total {1} Tooth Pack -UninstallBackToLocal=Back to local pack page -RegistryBottomReload=Reload -LocalButtonUpgrade=Upgrade -LocalButtonUninstall=Unistall -ToothItemDetailButton=Show Options -ToothItemAuthor=Author: -ToothItemLicense=License: -SettingsMainTitle=Main Settings -SettingsTheme=Theme -SettingsLipPath=Lip Path: -SettingsLanguage=Language -SettingsLipPathAuto=Get automatially -SettingsDeveloperTitle=Developer Options -SettingsDeveloperConfigPath=Config file location : -SettingsDeveloperConfigOpen=Open -SettingsDeveloperSkipDependency=Show "Skip dependencies" checkbox on the installation page -SettingsDeveloperExit=Exit developer mode -AboutTitle=About -AboutLipUIVersion=LipUI Version : -AboutLipVersion=Lip Version : -AboutLipUILink=LipUI on GitHub -AboutLipLink=Lip on GitHub -HomeStartTip=Click the left navigation bar to get started! -HomeWorkingDirectoryTitle=Working directory: -LipInstallerConfigMode=ConfigMode: -LipInstallerAssumedPath=Assumed Path: -LipInstallerGlobalExe=[Global] Download and install globally -LipInstallerPortableExe=[Portable] Download and place in current directory -LipInstallerManualExe=[Manually] Configure manually -RegistryTagFeatured=Featured -RegistryTagIntegration=Integration -RegistryTagModule=Module -RegistryTagPlugin=Plugin -RegistryTagAntiCheat=AntiCheat -InstallSkipDependency=Skip Dependency -InstallButton=Install {0} -InstallFetchButton=Fetch -InstallFetchCancelButton=Cancel -ToothInfoInstalled=[Installed] -ToothInfoPackName=PackName: -ToothInfoPackVersion=Version: -ToothInfoName=Name: -ToothInfoAuthor=Author: -ToothInfoLicense=License: -ToothInfoDescription=Description: -ToothInfoHomePage=HomePage: -ToothInfoHomePageOpenLink=Open -UninstallTip1=Goto -UninstallTip2= page to select target tooth pack to remove -UninstallLocalLink=Local Tooth Pack -UninstallConfirm=Confirm Delete {0} -InstallInvoking=Installing... -InstallTerminate=Terminate (Unsafe) -InstallInputToothAddress=Input tooth pack address... -RegistryItemVersionButton=Version: -RegistryItemInstallButton=Install {0} -RegistryFeaturedFilter=Filter Featured content -RegistrySearchPlaceholder=Type to search... -AppConfigWorkingDirectoryUnnamed=Unnamed -DeveloperEnterTip=Please go to the settings page and continuously tap the version number 5 times to enable developer mode -RegistryTipFetchAll=Fetching all tooth packs -RegistryTipFetchFailed=Fetch failed -RegistryTipRefreshFailed=Refresh error -RegistrySearchTagFailed=Tag query failed -DeveloperSnackbarTitle=Developer mode -DeveloperTitle=Developer Mode -DeveloperSnackbarSubtitle=Tap {0} more times to enter developer mode. -DeveloperDialog=Do you want to enter developer mode? \nSome extra options will be available on some pages in developer mode, please use with caution. -DeveloperDialogConfirm=Confirm -DeveloperDialogCancel=Cancel -DeveloperAlreadyIn=You are already in developer mode. -DeveloperEnterSuccess=Entered developer mode successfully. -InstallYNDialog=Prompt -InstallYNDialogGrant=OK -InstallYNDialogDeny=Cancel -InstallYNCanceledTitle=Cancelled -InstallYNCanceled=Installation cancelled. -InstallSuccessTitle=Installation completed -InstallSuccess=Successfully installed all tooth files. -InstallGeneratingBDSLib=Generating BDS ... -InstallErrorTitle=Error -InstallFetchFailed=Fetch failed -SettingsSuccessTitle=Settings saved -SettingsFailedTitle=Path does not exist -DeveloperDialogExit=Do you want to exit developer mode? -DeveloperDialogExited=Exited developer mode successfully. -CopyToClipboard=Copied to clipboard -WorkingPathSelectorTitle=Select working directory -WorkingPathSelectorAdded=Successfully added. -WorkingPathSelectorAlreadyAdded=Directory already added. -WorkingPathSelectorNotExist=Directory does not exist. -LocalFetchRetry=Error, please try fetching again. -LocalFetchFailed=Fetch failed. -WorkingPathSelectorAddButton=Add -WorkingPathSelectorSelectButton=Select -WorkingPathSelectorCurrent=Current Directory -WorkingPathSelectorItemCopy=Copy -WorkingPathSelectorItemOpen=Open Directory -WorkingPathSelectorItemDelete=Delete -HomeWorkingDirectoryEditToggle=Click to edit -Eula=Terms -EulaDeny=Deny -EulaAccept=Accept -EulaDeniedTitle=You deny the terms -EulaDeniedContent=Program is about to exit -LipInstallerDialog= lip.exe required to be configured -LipInstallerDialogComplete=Complete -LipInstallerSnackbarLipNotFound= lip.exe is not found -LipInstallerSnackbarLipNotFoundTip=If lip.exe is already configured, please restart LipUI -LipInstallerDialogDownload=Download -LipInstallerLipDownloading=Downloading, please wait... -LipInstallerLipDownloadingProgress=Downloading...{0}/{1} -LipInstallerLipDownloadedSuccess=Download completed. -LipInstallerDownloadFailed=Failure : {0} -LipInstallerLipNotFound=lip.exe is not found in downloaded archive -LipInstallerInvalidOperation=Invalid operation -LipInstallerInvalidOperationTip=Please select 'Global' or 'Portable' to download lip.exe -WorkingPathSelectorInitDialog=A valid working directory needs to be specified -WorkingPathSelectorInitDialogComplete=Complete -WorkingPathSelectorInitErr=Please select a valid job working directory -SettingsClearCache=Cache Purge for Lip -SettingsClearCacheTitle=Cache Purge -SettingsClearCacheContent=Confirm to clear cache for lip -SettingsClearCacheConfirm=Confirm -SettingsClearCacheCancel=Cancel -SettingsClearCacheCompleted=Cache purge completed diff --git a/src/LipUI/Language/LanguageFiles.Designer.cs b/src/LipUI/Language/LanguageFiles.Designer.cs deleted file mode 100644 index 686e1c7..0000000 --- a/src/LipUI/Language/LanguageFiles.Designer.cs +++ /dev/null @@ -1,73 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace LipUI.Language { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class LanguageFiles { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal LanguageFiles() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("LipUI.Language.LanguageFiles", typeof(LanguageFiles).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized resource of type System.Byte[]. - /// - internal static byte[] en { - get { - object obj = ResourceManager.GetObject("en", resourceCulture); - return ((byte[])(obj)); - } - } - } -} diff --git a/src/LipUI/Language/Model.cs b/src/LipUI/Language/Model.cs deleted file mode 100644 index ea1fcdd..0000000 --- a/src/LipUI/Language/Model.cs +++ /dev/null @@ -1,158 +0,0 @@ -using CommunityToolkit.Mvvm.ComponentModel; - -namespace LipUI.Language; - -/// -/// 多语言类 -/// 添加规则: -/// 1.必须添加[ObservableProperty]注解,以便自动生成属性 -/// 2.必须string类型,以便前端绑定 -/// 3.下划线开头,自动生成属性会去掉下划线并让首个字母大写 -/// 前端绑定方法: -/// 对于 string textA {get;set;} = "A"; -/// 则有 Text="{Binding TextA,Source={StaticResource I18N}}" -/// 后端获取方法: -/// string v = Global.I18N.TextA; -/// -public class Model : ObservableObject -{ - public new void OnPropertyChanged(string key) => base.OnPropertyChanged(key); - public string ApplicationTitle { get; set; } = "LipUI - Tooth包管理器"; - public string NavigationHome { get; set; } = "主页"; - public string NavigationLocal { get; set; } = "本地Tooth包"; - public string NavigationInstall { get; set; } = "安装Tooth包"; - public string NavigationRemove { get; set; } = "卸载Tooth包"; - public string NavigationRegistry { get; set; } = "包市场"; - public string NavigationWebUi { get; set; } = "WebUI"; - public string NavigationDeveloper { get; set; } = "开发者工具"; - public string NavigationSettings { get; set; } = "设置 & 关于"; - public string LocalBottomInstall { get; set; } = "已安装 {0} 个Tooth包 "; - public string LocalBottomReload { get; set; } = "重新加载"; - public string RegistryBottomInstall { get; set; } = "获取到 {0} / {1} 个Tooth包 "; - public string UninstallBackToLocal { get; set; } = "返回本地包"; - public string RegistryBottomReload { get; set; } = "重新加载"; - public string LocalButtonUpgrade { get; set; } = "升级"; - public string LocalButtonUninstall { get; set; } = "卸载"; - public string ToothItemDetailButton { get; set; } = "详细信息"; - public string ToothItemAuthor { get; set; } = "作者:"; - public string ToothItemLicense { get; set; } = "协议:"; - public string SettingsMainTitle { get; set; } = "主设置"; - public string SettingsTheme { get; set; } = "主题"; - public string SettingsLipPath { get; set; } = "Lip路径:"; - public string SettingsLanguage { get; set; } = "语言"; - public string SettingsLipPathAuto { get; set; } = "自动获取"; - public string SettingsDeveloperTitle { get; set; } = "开发者选项"; - public string SettingsDeveloperConfigPath { get; set; } = "配置文件位置:"; - public string SettingsDeveloperConfigOpen { get; set; } = "打开"; - public string SettingsDeveloperSkipDependency { get; set; } = "安装界面显示\"跳过依赖\"选项"; - public string SettingsDeveloperExit { get; set; } = "退出开发者模式"; - public string AboutTitle { get; set; } = "关于"; - public string AboutLipUIVersion { get; set; } = "LipUI版本:"; - public string AboutLipVersion { get; set; } = "Lip版本:"; - public string AboutLipUILink { get; set; } = "LipUI 的 GitHub 项目地址"; - public string AboutLipLink { get; set; } = "Lip 的 GitHub 项目地址"; - public string HomeStartTip { get; set; } = "点击左边的导航栏开始吧!"; - public string HomeWorkingDirectoryTitle { get; set; } = "工作目录:"; - public string LipInstallerConfigMode { get; set; } = "配置模式:"; - public string LipInstallerAssumedPath { get; set; } = "当前拟定位置:"; - public string LipInstallerGlobalExe { get; set; } = "下载并安装到全局"; - public string LipInstallerPortableExe { get; set; } = "下载并放置到当前目录"; - public string LipInstallerManualExe { get; set; } = "手动配置路径"; - public string RegistryTagFeatured { get; set; } = "精华"; - public string RegistryTagIntegration { get; set; } = "整合"; - public string RegistryTagModule { get; set; } = "模块"; - public string RegistryTagPlugin { get; set; } = "插件"; - public string RegistryTagAntiCheat { get; set; } = "反作弊"; - public string InstallSkipDependency { get; set; } = "跳过依赖"; - public string InstallButton { get; set; } = "安装 {0}"; - public string InstallFetchButton { get; set; } = "获取"; - public string InstallFetchCancelButton { get; set; } = "取消"; - public string ToothInfoInstalled { get; set; } = "[已安装]"; - public string ToothInfoPackName { get; set; } = "包名:"; - public string ToothInfoPackVersion { get; set; } = "版本:"; - public string ToothInfoName { get; set; } = "名称:"; - public string ToothInfoAuthor { get; set; } = "作者:"; - public string ToothInfoLicense { get; set; } = "协议:"; - public string ToothInfoDescription { get; set; } = "描述:"; - public string ToothInfoHomePage { get; set; } = "主页:"; - public string ToothInfoHomePageOpenLink { get; set; } = "打开"; - public string UninstallTip1 { get; set; } = "请在"; - public string UninstallTip2 { get; set; } = "页面点击详细信息,然后点击删除"; - public string UninstallLocalLink { get; set; } = "本地包"; - public string UninstallConfirm { get; set; } = "确认删除 {0}"; - public string InstallInvoking { get; set; } = "安装中..."; - public string InstallTerminate { get; set; } = "终止安装(危险)"; - public string InstallInputToothAddress { get; set; } = "输入Tooth包的地址..."; - public string RegistryItemVersionButton { get; set; } = "版本:"; - public string RegistryItemInstallButton { get; set; } = "安装 {0}"; - public string RegistryFeaturedFilter { get; set; } = "精华内容"; - public string RegistrySearchPlaceholder { get; set; } = "查找..."; - public string AppConfigWorkingDirectoryUnnamed { get; set; } = "未命名"; - public string DeveloperEnterTip { get; set; } = "请到设置页面连续点击5次版本号开启开发者模式"; - public string RegistryTipFetchAll { get; set; } = "正在获取包列表"; - public string RegistryTipFetchFailed { get; set; } = "获取失败"; - public string RegistryTipRefreshFailed { get; set; } = "刷新出错"; - public string RegistrySearchTagFailed { get; set; } = "查询Tag失败"; - public string DeveloperSnackbarTitle { get; set; } = "开发者模式"; - public string DeveloperTitle { get; set; } = "开发者模式"; - public string DeveloperSnackbarSubtitle { get; set; } = "再点 {0} 次进入开发者模式。"; - public string DeveloperDialog { get; set; } = "是否进入开发者模式?\n开发者模式下部分页面会有一些额外的选项,请谨慎使用。"; - public string DeveloperDialogConfirm { get; set; } = "确认"; - public string DeveloperDialogCancel { get; set; } = "取消"; - public string DeveloperAlreadyIn { get; set; } = "您已经在开发者模式了。"; - public string DeveloperEnterSuccess { get; set; } = "已进入开发者模式。"; - public string InstallYNDialog { get; set; } = "提示"; - public string InstallYNDialogGrant { get; set; } = "好的"; - public string InstallYNDialogDeny { get; set; } = "取消"; - public string InstallYNCanceledTitle { get; set; } = "取消"; - public string InstallYNCanceled { get; set; } = "安装已取消"; - public string InstallSuccessTitle { get; set; } = "安装完成"; - public string InstallSuccess { get; set; } = "Successfully installed all tooth files."; - public string InstallGeneratingBDSLib { get; set; } = "生成BDS..."; - public string InstallErrorTitle { get; set; } = "小错误"; - public string InstallFetchFailed { get; set; } = "获取失败"; - public string SettingsSuccessTitle { get; set; } = "设置成功"; - public string SettingsFailedTitle { get; set; } = "路径不存在"; - public string DeveloperDialogExit { get; set; } = "是否退出开发者模式?"; - public string DeveloperDialogExited { get; set; } = "已退出开发者模式"; - public string CopyToClipboard { get; set; } = "已复制到剪切板"; - public string WorkingPathSelectorTitle { get; set; } = "选择工作目录"; - public string WorkingPathSelectorAdded { get; set; } = "添加成功"; - public string WorkingPathSelectorAlreadyAdded { get; set; } = "目录已添加"; - public string WorkingPathSelectorNotExist { get; set; } = "目录不存在"; - public string LocalFetchRetry { get; set; } = "小错误,请尝试重新获取"; - public string LocalFetchFailed { get; set; } = "获取失败"; - public string WorkingPathSelectorAddButton { get; set; } = "添加"; - public string WorkingPathSelectorSelectButton { get; set; } = "选择"; - public string WorkingPathSelectorCurrent { get; set; } = "当前目录"; - public string WorkingPathSelectorItemCopy { get; set; } = "复制"; - public string WorkingPathSelectorItemOpen { get; set; } = "打开文件夹"; - public string WorkingPathSelectorItemDelete { get; set; } = "删除当前项"; - public string HomeWorkingDirectoryEditToggle { get; set; } = "开启编辑"; - public string Eula { get; set; } = "条款"; - public string EulaDeny { get; set; } = "不同意"; - public string EulaAccept { get; set; } = "同意"; - public string EulaDeniedTitle { get; set; } = "您拒绝了条款"; - public string EulaDeniedContent { get; set; } = "程序即将退出"; - public string LipInstallerDialog { get; set; } = "需要配置 lip.exe"; - public string LipInstallerDialogComplete { get; set; } = "完成"; - public string LipInstallerSnackbarLipNotFound { get; set; } = "未找到lip.exe"; - public string LipInstallerSnackbarLipNotFoundTip { get; set; } = "如已安装请重新启动LipUI"; - public string LipInstallerDialogDownload { get; set; } = "下载"; - public string LipInstallerLipDownloading { get; set; } = "正在下载,请等待..."; - public string LipInstallerLipDownloadingProgress { get; set; } = "下载中...{0}/{1}"; - public string LipInstallerLipDownloadedSuccess { get; set; } = "下载完成."; - public string LipInstallerDownloadFailed { get; set; } = "失败:{0}"; - public string LipInstallerLipNotFound { get; set; } = "压缩包中未找到 lip.exe"; - public string LipInstallerInvalidOperation { get; set; } = "无效操作"; - public string LipInstallerInvalidOperationTip { get; set; } = "请选中安装到'全局'或者'当前目录'以下载 lip.exe"; - public string WorkingPathSelectorInitDialog { get; set; } = "需要指定有效的工作路径"; - public string WorkingPathSelectorInitDialogComplete { get; set; } = "完成"; - public string WorkingPathSelectorInitErr { get; set; } = "请选择有效的工作路径"; - public string SettingsClearCache { get; set; } = "清除Lip的缓存"; - public string SettingsClearCacheTitle { get; set; } = "清除Lip的缓存"; - public string SettingsClearCacheContent { get; set; } = "确认清除缓存"; - public string SettingsClearCacheConfirm { get; set; } = "确定"; - public string SettingsClearCacheCancel { get; set; } = "取消"; - public string SettingsClearCacheCompleted { get; set; } = "清除缓存执行完成"; -} \ No newline at end of file diff --git a/src/LipUI/Language/Utils.cs b/src/LipUI/Language/Utils.cs deleted file mode 100644 index 1db43ae..0000000 --- a/src/LipUI/Language/Utils.cs +++ /dev/null @@ -1,220 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace LipUI.Language; -public static class Utils -{ - #region Helper - internal static void Deconstruct(this KeyValuePair dict, out string key, out string value) - { - key = dict.Key; - value = dict.Value; - } - #endregion - public record LanguageDescriptionItem(LangId Id, string Name); - public enum LangId - { - Default, - zh_Hans, - en - } - public static LanguageDescriptionItem[] AvailableLanguages = new LanguageDescriptionItem[] - { - new (LangId.Default,"Default (System)"), - new (LangId.zh_Hans,"简体中文"), - new (LangId.en,"English") - }; - public static async Task SwitchLanguage(LangId target) - { - if (target != CurrentLangId) - { - Global.Config.Language = target; - } - async Task LoadLang(LangId id) - { - if (id == LangId.zh_Hans) - {//build-in language - await PopulateLanguage(SerializeToDict(new())); - } - else - { - var dict = GetLangDictionaryFromResource(id); - await PopulateLanguage(dict); - } - } - if (target == LangId.Default) - await LoadLang(GetSystemLanguage()); - else await LoadLang(target); - } - /// - /// 获取系统语言 - /// - /// 语言ID - public static LangId GetSystemLanguage() - { - var lang = System.Globalization.CultureInfo.CurrentCulture.Name.ToLower(); - if (lang == "zh-CN") - return LangId.zh_Hans; - if (lang.StartsWith("zh")) - return LangId.zh_Hans; - return LangId.en; - } - public static Dictionary GetLangDictionaryFromResource(LangId target) - { - var res = target switch - { - LangId.en => LanguageFiles.en, - _ => throw new ArgumentOutOfRangeException(nameof(target), target, null) - }; - string str; - if (res.Length >= 3 && res[0] == 0xEF && res[1] == 0xBB && res[2] == 0xBF) - {//UTF-8 with BOM - str = Encoding.UTF8.GetString(res, 3, res.Length - 3); - } - else - {//UTF-8 without BOM - str = Encoding.UTF8.GetString(res); - } - var dict = DeserializeFromStr(str); - return dict; - } - - public static LangId CurrentLangId - { - get => Global.Config.Language; - set => _=SwitchLanguage(value); - } - - public static Model DeserializeFromDict(Dictionary dict) - { - Model i18N = new(); - foreach (var (key, value) in dict) - { - if (GetProp(key, out _, out var set)) - { - set(i18N, value); - } - else - { - Debug.WriteLine($"[Language] [DeserializeFromDict] Property {key} not found."); - } - } - return i18N; - } - public static Dictionary DeserializeFromStr(string langStr) => - ( - from x in langStr - .Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries) - where !string.IsNullOrWhiteSpace(x) - && !x.StartsWith("//") - && !x.StartsWith("#") - && x.Contains('=') - select x.Split(new[] { '=' }, 2) - ) - //split by "=" to get key and value - //remove all element that has same key - .GroupBy(x => x[0].Trim(), (key, group) => - { - var gp = group.ToArray(); -#if DEBUG - //check if only has one value ,otherwise write log - if (gp.Count() > 1) - { - //Duplicate key - Debug.WriteLine("[Language] [DeserializeFromStr] Duplicate key " + key + " in language"); - //print all value - foreach (var item in gp) - { - Debug.WriteLine("\t=" + item[1]); - } - } -#endif - //get last element - return new { key, value = gp.Last()[1] }; - }) - .ToDictionary(x => x.key, x => x.value.Replace("\\n", "\n")); - - internal static string SerializeToStr(Dictionary dict) - { - var sb = new StringBuilder(); - foreach (var (key, value) in dict) - { - sb.Append(key); - sb.Append('='); - sb.Append(value.Replace("\n", "\\n")); - sb.AppendLine(); - } - return sb.ToString(); - } - public static Dictionary SerializeToDict(Model i18n) - { - var type = typeof(Model); - var properties = type.GetProperties(); - var dict = new Dictionary(); - foreach (var property in properties) - { - var value = property.GetValue(i18n); - if (value is not string str) - { - Debug.WriteLine($"[Language] [SerializeToDict] value '{value ?? "null"}' is not string"); - continue; - } - var key = property.Name; - dict.Add(key, str); - } - return dict; - } - ////缓存模式,减少反射速度更快,但是多占用了点内存 - //private static Lazy> allProp = - // new(() => - // (from x in typeof(Model).GetProperties() - // where x.CanRead && x.CanWrite && x.PropertyType == typeof(string) - // select x) - // .ToDictionary(x => x.Name.Trim(), x => x)); - //private static bool GetProp(string key, out PropertyInfo prop) - //{ - // return allProp.Value.TryGetValue(key.Trim(), out prop); - //} - private static bool GetProp(string key, - [NotNullWhen(true)] out Func? get, - [NotNullWhen(true)] out Action? set) - { - var prop = typeof(Model).GetProperty(key.Trim()); - if (prop is null) - { - get = null; - set = null; - return false; - } - get = x => (string)prop.GetValue(x)!; - set = (x, v) => - { - prop.SetValue(x, v); - x.OnPropertyChanged(key); - }; - return true; - } - - public static async Task PopulateLanguage(Dictionary dict) - { - var i18N = Global.I18N; - foreach (var (key, value) in dict) - { - if (GetProp(key, out _, out var set)) - { - await Global.DispatcherInvokeAsync(() => - { - set(i18N, value); - }); - } - else - { - Debug.WriteLine($"[Language] [PopulateLanguage] key '{key}' not found in Language.Model"); - } - } - } -} \ No newline at end of file diff --git a/src/LipUI/Language/en-US/Resources.resw b/src/LipUI/Language/en-US/Resources.resw new file mode 100644 index 0000000..21ef2bb --- /dev/null +++ b/src/LipUI/Language/en-US/Resources.resw @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Restart + + + Need to restart application. + + + Search + + + Agree + + + Disagree + + + GitHub API + + + Github proxy + + + Lip Index API + + + Lip path + + + General settings + + + No description + + + No server selected + + + Error + + + Information + + + Warning + + + Unknown Lip path! + + + Unknown server path! + + + Cancel + + + Install + + + Installation completed! + + + Install Lip + + + LipUI + + + Uninstall + + + Update + + + Modules + + + Plugins + + + UI Plugins + + + Allowlist + + + Modules + + + Server properties + + + Home + + + Local packages + + + Module manager + + + More + + + Lip index + + + Acrylic + + + Mica + + + None + + + Transparent + + + Background + + + Background color + + + Background color secondary + + + Clear image + + + Dark + + + Default + + + Enable background image + + + Light + + + Luminosity opacity + + + Navigation view content background color + + + Navigation view content border color + + + Select image + + + Personalization + + + Unknown server path! + + + Save successful! + + + Saving… + + + README.md not found + + + Select server + + + Cancel + + + Confirm + + + Description + + + Name + + + Version + + + Working directory + + + Server path already exists! + + + Start server + + + Install + + \ No newline at end of file diff --git a/src/LipUI/Language/zh-CN/Resources.resw b/src/LipUI/Language/zh-CN/Resources.resw new file mode 100644 index 0000000..16b3d8e --- /dev/null +++ b/src/LipUI/Language/zh-CN/Resources.resw @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 立即重启 + + + 需要重启应用 + + + 搜索 + + + 同意 + + + 不同意 + + + GitHub API + + + Github代理 + + + Lip Index API + + + Lip路径 + + + 常规 + + + 无描述 + + + 未选择服务器 + + + 错误 + + + 信息 + + + 警告 + + + 未知的Lip路径! + + + 未知的服务器路径! + + + 取消 + + + 安装 + + + 安装完成! + + + 安装Lip + + + LipUI + + + 卸载 + + + 更新 + + + 组件 + + + 插件 + + + UI插件 + + + 白名单 + + + 组件 + + + 服务器配置 + + + 主页 + + + 本地包 + + + 组件管理 + + + 更多 + + + 包索引 + + + 亚克力 + + + 云母 + + + + + + 透明 + + + 背景 + + + 背景颜色 + + + 二级背景颜色 + + + 清除图片 + + + 深色 + + + 默认 + + + 启用背景图片 + + + 浅色 + + + 不透明度 + + + 导航视图内容背景颜色 + + + 导航视图内容边框颜色 + + + 选择图片 + + + 个性化 + + + 未知的服务器路径! + + + 保存成功! + + + 保存中... + + + 未找到README.md + + + 选择服务器 + + + 取消 + + + 确认 + + + 描述 + + + 名称 + + + 版本 + + + 工作目录 + + + 服务器路径已存在! + + + 启动服务器 + + + 安装 + + \ No newline at end of file diff --git a/src/LipUI/LipUI.csproj b/src/LipUI/LipUI.csproj index 731948f..86126e0 100644 --- a/src/LipUI/LipUI.csproj +++ b/src/LipUI/LipUI.csproj @@ -1,74 +1,228 @@  + - - WinExe - 0.2.7 - enable - 11.0 - true - app.manifest - applicationIcon.ico - - - net462; - - - net462;net7.0-windows - - - System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute;System.Diagnostics.CodeAnalysis.NotNullAttribute - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive; compile - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive; compile - - - - - - - - + None + true - - - - + WinExe + net7.0-windows10.0.19041.0 + 10.0.17763.0 + LipUI + app.manifest + x64 + win10-x64 + win10-$(Platform).pubxml + true + true + true + enable + False + True + 41F2155140B5369541BFDEEAC7B71FFB18F9E6DF + SHA256 + True + True + True + Never + 0 + en-US + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + + + + + - - - - - - - - True - True - LanguageFiles.resx - - - - - - ResXFileCodeGenerator - LanguageFiles.Designer.cs - - + + + + + + + + + + + + + + + + True + True + Images.resx + + + + + True + True + Images.resx + + + True + True + Resource1.resx + + + + + ResXFileCodeGenerator + Images.Designer.cs + + + + + MSBuild:Compile + Designer + + + MSBuild:Compile + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + + + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + true + diff --git a/src/LipUI/LipUI_TemporaryKey.pfx b/src/LipUI/LipUI_TemporaryKey.pfx new file mode 100644 index 0000000..04c4dee Binary files /dev/null and b/src/LipUI/LipUI_TemporaryKey.pfx differ diff --git a/src/LipUI/MainWIndowWin32Invoke.cs b/src/LipUI/MainWIndowWin32Invoke.cs new file mode 100644 index 0000000..e6c6197 --- /dev/null +++ b/src/LipUI/MainWIndowWin32Invoke.cs @@ -0,0 +1,91 @@ +using System; +using System.Runtime.InteropServices; +using WinRT; +using static PInvoke.User32; + +namespace LipUI; + +internal partial class MainWindow +{ + private readonly int MinWidth = 800; + private readonly int MinHeight = 450; + + private NativeMethods.WinProc? newWndProc = null; + private nint oldWndProc = nint.Zero; + + private void SubClassing() + { + var hwnd = this.As().WindowHandle; + if (hwnd == nint.Zero) + { + int error = Marshal.GetLastWin32Error(); + throw new InvalidOperationException($"Failed to get window handler: error code {error}"); + } + + newWndProc = new(NewWindowProc); + + // Here we use the NativeMethods class 👇 + oldWndProc = NativeMethods.SetWindowLong(hwnd, WindowLongIndexFlags.GWL_WNDPROC, newWndProc); + if (oldWndProc == nint.Zero) + { + int error = Marshal.GetLastWin32Error(); + throw new InvalidOperationException($"Failed to set GWL_WNDPROC: error code {error}"); + } + } + + private nint NewWindowProc(nint hWnd, WindowMessage Msg, nint wParam, nint lParam) + { + [DllImport("user32.dll")] + static extern nint CallWindowProc(nint lpPrevWndFunc, nint hWnd, WindowMessage Msg, nint wParam, nint lParam); + + switch (Msg) + { + case WindowMessage.WM_GETMINMAXINFO: + var dpi = GetDpiForWindow(hWnd); + float scalingFactor = (float)dpi / 96; + + MINMAXINFO minMaxInfo = Marshal.PtrToStructure(lParam); + minMaxInfo.ptMinTrackSize.x = (int)(MinWidth * scalingFactor); + minMaxInfo.ptMinTrackSize.y = (int)(MinHeight * scalingFactor); + Marshal.StructureToPtr(minMaxInfo, lParam, true); + break; + + } + return CallWindowProc(oldWndProc, hWnd, Msg, wParam, lParam); + } + + private static class NativeMethods + { + [StructLayout(LayoutKind.Sequential)] + struct MINMAXINFO + { + public PInvoke.POINT ptReserved; + public PInvoke.POINT ptMaxSize; + public PInvoke.POINT ptMaxPosition; + public PInvoke.POINT ptMinTrackSize; + public PInvoke.POINT ptMaxTrackSize; + } + + + public delegate nint WinProc(nint hWnd, WindowMessage Msg, nint wParam, nint lParam); + + // We have to handle the 32-bit and 64-bit functions separately. + // 'SetWindowLongPtr' is the 64-bit version of 'SetWindowLong', and isn't available in user32.dll for 32-bit processes. + [DllImport("user32.dll", EntryPoint = "SetWindowLong")] + private static extern nint SetWindowLong32(nint hWnd, WindowLongIndexFlags nIndex, WinProc newProc); + + [DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")] + private static extern nint SetWindowLong64(nint hWnd, WindowLongIndexFlags nIndex, WinProc newProc); + + public static nint SetWindowLong(nint hWnd, WindowLongIndexFlags nIndex, WinProc newProc) + => nint.Size is 4 ? SetWindowLong32(hWnd, nIndex, newProc) : SetWindowLong64(hWnd, nIndex, newProc); + + [ComImport] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [Guid("EECDBF0E-BAE9-4CB6-A68E-9598E1CB57BB")] + internal interface IWindowNative + { + nint WindowHandle { get; } + } + } +} diff --git a/src/LipUI/MainWindow.xaml b/src/LipUI/MainWindow.xaml new file mode 100644 index 0000000..0a582db --- /dev/null +++ b/src/LipUI/MainWindow.xaml @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/LipUI/MainWindow.xaml.cs b/src/LipUI/MainWindow.xaml.cs new file mode 100644 index 0000000..e7ce485 --- /dev/null +++ b/src/LipUI/MainWindow.xaml.cs @@ -0,0 +1,151 @@ +using LipUI.Models; +using LipUI.Models.Plugin; +using LipUI.Pages.Settings; +using Microsoft.UI.Dispatching; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using System; +using System.Threading; +using System.Threading.Tasks; +using Windows.System; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace LipUI; + +/// +/// An empty window that can be used on its own or navigated to within a Frame. +/// +internal sealed partial class MainWindow : Window +{ + public MainWindow() + { + InitializeComponent(); + + ExtendsContentIntoTitleBar = true; + SetTitleBar(AppTitleBar); + + SubClassing(); + + AppWindow.Resize(new(1600, 900)); + + InternalServices.MainWindow = this; + + PersonalizationSettingsView.Initialize(); + + enabled = PluginEnabled; + disabled = PluginDisabled; + + Task.Run(PluginSystem.LoadAsync); + } + + private async void MainWindow_Closed(object sender, WindowEventArgs args) + { + InternalServices.OnWindowClosed(); + await Main.SaveConfigAsync(); + } + + internal void ShowInfoBar( + string? title, + string? message, + InfoBarSeverity severity, + TimeSpan interval = default, + UIElement? barContent = null, + Action? completed = null) + { + Task.Run(async () => + { + var mre = new ManualResetEvent(false); + + DispatcherQueue.TryEnqueue(() => + { + GlobalInfoBar.Title = title; + GlobalInfoBar.Message = message; + GlobalInfoBar.Severity = severity; + GlobalInfoBar.IsClosable = false; + GlobalInfoBar.Content = barContent; + GlobalInfoBar.IsOpen = true; + + void task(object? sender, object e) + { + InfoBarPopInStoryboard.Completed -= task; + mre.Set(); + } + InfoBarPopInStoryboard.Completed += task; + + InfoBarPopInStoryboard.Begin(); + }); + mre.WaitOne(); + + await Task.Delay(interval); + + mre.Reset(); + DispatcherQueue.TryEnqueue(() => + { + InfoBarPopOutStoryboard.Begin(); + + void task(object? sender, object e) + { + InfoBarPopOutStoryboard.Completed -= task; + GlobalInfoBar.IsOpen = false; + mre.Set(); + } + InfoBarPopOutStoryboard.Completed += task; + }); + mre.WaitOne(); + mre.Dispose(); + completed?.Invoke(); + }); + } + + internal async ValueTask ShowInfoBarAsync(string? title, + string? message, + InfoBarSeverity severity, + TimeSpan interval = default, + UIElement? barContent = null) + { + await Task.Run(async () => + { + var mre = new ManualResetEvent(false); + + DispatcherQueue.TryEnqueue(() => + { + GlobalInfoBar.Title = title; + GlobalInfoBar.Message = message; + GlobalInfoBar.Severity = severity; + GlobalInfoBar.IsClosable = false; + GlobalInfoBar.Content = barContent; + GlobalInfoBar.IsOpen = true; + + void task(object? sender, object e) + { + InfoBarPopInStoryboard.Completed -= task; + mre.Set(); + } + InfoBarPopInStoryboard.Completed += task; + + InfoBarPopInStoryboard.Begin(); + }); + mre.WaitOne(); + + await Task.Delay(interval); + + mre.Reset(); + DispatcherQueue.TryEnqueue(() => + { + InfoBarPopOutStoryboard.Begin(); + + void task(object? sender, object e) + { + InfoBarPopOutStoryboard.Completed -= task; + GlobalInfoBar.IsOpen = false; + mre.Set(); + } + InfoBarPopOutStoryboard.Completed += task; + }); + mre.WaitOne(); + mre.Dispose(); + }); + } +} diff --git a/src/LipUI/MainWindowNavigationView.cs b/src/LipUI/MainWindowNavigationView.cs new file mode 100644 index 0000000..fabbe4e --- /dev/null +++ b/src/LipUI/MainWindowNavigationView.cs @@ -0,0 +1,170 @@ +using LipUI.Models.Plugin; +using LipUI.Pages.Home; +using LipUI.Pages.Index; +using LipUI.Pages.LocalPackage; +using LipUI.Pages.Settings; +using LipUI.Pages.ToothPack; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media.Animation; +using Microsoft.UI.Xaml.Navigation; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; + +namespace LipUI; + +internal partial class MainWindow +{ + private record NavigationPages(string HomePage, string IndexPage, string ModuleManagerPage, string LocalPackagePage, string SettingsPage); + + + private readonly NavigationPages NavigationPage = new( + typeof(HomePage).FullName!, + typeof(IndexPage).FullName!, + typeof(ModuleManagerPage).FullName!, + typeof(LocalPackagePage).FullName!, + typeof(SettingsPage).FullName!); + + private readonly HashSet NavigationPageTypes = new() + { + typeof(HomePage), + typeof(IndexPage), + typeof(ModuleManagerPage), + typeof(LocalPackagePage), + typeof(SettingsPage) + }; + + + + private void ContentFrame_NavigationFailed(object sender, NavigationFailedEventArgs e) + { + throw new Exception("Failed to load Page " + e.SourcePageType.FullName); + } + + private void NavView_Loading(FrameworkElement sender, object args) + { + sender.Resources["NavigationViewContentBackground"] + = PersonalizationSettingsView.MyRes.ApplicationNavigationViewContentBackground; + sender.Resources["NavigationViewContentGridBorderBrush"] + = PersonalizationSettingsView.MyRes.ApplicationNavigationViewContentBorder; + + PersonalizationSettingsView.MyRes.PropertyChanged += (object? _sender, PropertyChangedEventArgs e) => + { + switch (e.PropertyName) + { + case nameof(GlobalResources.ApplicationNavigationViewContentBackground): + sender.Resources["NavigationViewContentBackground"] + = PersonalizationSettingsView.MyRes.ApplicationNavigationViewContentBackground; + break; + + case nameof(GlobalResources.ApplicationNavigationViewContentBorder): + sender.Resources["NavigationViewContentGridBorderBrush"] + = PersonalizationSettingsView.MyRes.ApplicationNavigationViewContentBorder; + break; + + case nameof(GlobalResources.ApplicationBackground): + RootBorder.Background = PersonalizationSettingsView.MyRes.ApplicationBackground; + break; + } + }; + } + + private void NavView_Loaded(object sender, RoutedEventArgs e) + { + // You can also add items in code. + + // Add handler for ContentFrame navigation. + ContentFrame.Navigated += On_Navigated; + + // NavView doesn't load any page by default, so load home page. + NavView.SelectedItem = NavView.MenuItems[0]; + // If navigation occurs on SelectionChanged, this isn't needed. + // Because we use ItemInvoked to navigate, we need to call Navigate + // here to load the home page. + NavView_Navigate(typeof(HomePage), null, new EntranceNavigationTransitionInfo()); + } + + private async void NavView_ItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args) + { + + if (args.IsSettingsInvoked == true) + { + NavView_Navigate(typeof(SettingsPage), null, args.RecommendedNavigationTransitionInfo); + } + else if (args.InvokedItemContainer != null) + { + if (args.InvokedItemContainer.Tag is null) + return; + + if (args.InvokedItemContainer.Tag is ILipuiPluginUI plugin) + { + try + { + NavView_Navigate( + plugin.PageType, plugin.PageParameterRequsted(), args.RecommendedNavigationTransitionInfo); + } + catch (Exception ex) { await InternalServices.ShowInfoBarAsync(ex); } + } + else + { + NavView_Navigate( + Type.GetType(args.InvokedItemContainer.Tag.ToString()!)!, + null, + args.RecommendedNavigationTransitionInfo); + } + } + } + + private void NavView_Navigate( + Type navPageType, + object? parameter, + NavigationTransitionInfo transitionInfo) + { + Type preNavPageType = ContentFrame.CurrentSourcePageType; + + if (navPageType is not null && !Equals(preNavPageType, navPageType)) + { + ContentFrame.Navigate(navPageType, parameter, transitionInfo); + } + } + + private void NavView_BackRequested(NavigationView sender, NavigationViewBackRequestedEventArgs args) + { + TryGoBack(); + } + + private bool TryGoBack() + { + if (!ContentFrame.CanGoBack) + return false; + + if (NavView.IsPaneOpen && + (NavView.DisplayMode is NavigationViewDisplayMode.Compact || + NavView.DisplayMode is NavigationViewDisplayMode.Minimal)) + return false; + + ContentFrame.TryGoBack(); + return true; + } + + private void On_Navigated(object sender, NavigationEventArgs e) + { + NavView.IsBackEnabled = ContentFrame.CanGoBack; + + if (ContentFrame.SourcePageType == typeof(SettingsPage)) + { + NavView.SelectedItem = (NavigationViewItem)NavView.SettingsItem; + } + else if (ContentFrame.SourcePageType != null) + { + + if (NavigationPageTypes.Contains(ContentFrame.SourcePageType)) + NavView.SelectedItem = NavView.MenuItems + .OfType() + .First(i => i.Tag.Equals(ContentFrame.SourcePageType.FullName!.ToString())); + } + } + +} diff --git a/src/LipUI/MainWindowPluginsHandler.cs b/src/LipUI/MainWindowPluginsHandler.cs new file mode 100644 index 0000000..42d24bd --- /dev/null +++ b/src/LipUI/MainWindowPluginsHandler.cs @@ -0,0 +1,116 @@ +using LipUI.Models.Plugin; +using Microsoft.UI.Xaml.Controls; +using System; +using System.Collections.Generic; + +namespace LipUI; + +internal partial class MainWindow +{ + public static void InitEventHandlers() + { + PluginSystem.PluginEnabled += PluginSystem_PluginEnabled; + PluginSystem.PluginDisabled += PluginSystem_PluginDisabled; + } + + private static readonly HashSet enabledModules = new(); + private static void PluginSystem_PluginEnabled(ILipuiPlugin obj) + { + lock (enabledModules) + { + if (obj is ILipuiPluginUI uiPlugin) + { + enabledModules.Add(uiPlugin); + enabled?.Invoke(uiPlugin); + } + } + } + + private static void PluginSystem_PluginDisabled(ILipuiPlugin obj) + { + lock (enabledModules) + { + if (obj is ILipuiPluginUI uiPlugin) + { + enabledModules.Remove(uiPlugin); + disabled?.Invoke(uiPlugin); + } + } + } + + private static Action? enabled; + private static Action? disabled; + + private readonly object _lock = new(); + private uint navViewBarEnabledPluginsCount; + + private readonly Dictionary views = new(); + + private void PluginEnabled(ILipuiPluginUI plugin) + { + DispatcherQueue.TryEnqueue(async () => + { + try + { + lock (_lock) + { + IList items; + + //bool addIntoNavViewBar = false; + //if (navViewBarEnabledPluginsCount < 4) + //{ + items = NavView.MenuItems; + //addIntoNavViewBar = true; + //} + //else + // items = NavigationViewItem_More.MenuItems; + + var view = new NavigationViewItem() + { + Icon = plugin.NavigatonBarIcon, + Content = plugin.NavigationBarContent, + Tag = plugin + }; + views.Add(plugin, view); + + items.Add(view); + + //if (addIntoNavViewBar) + navViewBarEnabledPluginsCount++; + + } + } + catch (Exception ex) + { + await InternalServices.ShowInfoBarAsync(ex); + } + }); + } + + private void PluginDisabled(ILipuiPluginUI plugin) + { + DispatcherQueue.TryEnqueue(async () => + { + try + { + lock (_lock) + { + if (NavView.MenuItems.Remove(views[plugin])) + { + navViewBarEnabledPluginsCount--; + } + //else + //{ + // NavigationViewItem_More.MenuItems.Remove(views[plugin]); + //} + + views.Remove(plugin); + } + } + catch (Exception ex) + { + await InternalServices.ShowInfoBarAsync(ex); + } + }); + } +} diff --git a/src/LipUI/Models/AppConfig.cs b/src/LipUI/Models/AppConfig.cs deleted file mode 100644 index 58e3337..0000000 --- a/src/LipUI/Models/AppConfig.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Collections.ObjectModel; -using System.Linq; -using CommunityToolkit.Mvvm.ComponentModel; -using LipUI.Language; -using Newtonsoft.Json; -using Wpf.Ui.Appearance; - -namespace LipUI.Models -{ - [Serializable] - public partial class AppConfig : ObservableObject - { - public AppConfig() - { - _allWorkingDirectory.CollectionChanged += (_, _) => - { - OnPropertyChanged(nameof(WorkingDirectory)); - }; - } - public partial class AppConfigWorkingDirectory : ObservableObject, IEquatable - { - [ObservableProperty] string _directory = string.Empty; - [ObservableProperty] string _name = Global.I18N.AppConfigWorkingDirectoryUnnamed; - public bool Equals(AppConfigWorkingDirectory other) - { - return Directory == other.Directory; - } - public static implicit operator AppConfigWorkingDirectory(string v) => new() { Directory = v }; - } - [ObservableProperty] string _lipPath = "lip.exe"; - [JsonIgnore] - public AppConfigWorkingDirectory? WorkingDirectory - { - get - { - return AllWorkingDirectory.FirstOrDefault(x => x.Directory == _workingDirectoryPath) ?? new(); - } - set - { - if (value is not null) - { - _workingDirectoryPath = value.Directory; - } - OnPropertyChanged(); - } - } - [ObservableProperty] private Utils.LangId _language = Utils.LangId.Default; - [JsonProperty("WorkingDirectory")] string? _workingDirectoryPath = string.Empty; - [NotifyPropertyChangedFor(nameof(WorkingDirectory))][ObservableProperty] ObservableCollection _allWorkingDirectory = new(); - [ObservableProperty] ThemeType _theme; - [ObservableProperty] private bool _autoLipPath = true; - [ObservableProperty] private bool _developerMode = false; - partial void OnDeveloperModeChanged(bool value) - { - if (!value)//退出开发者模式时恢复所有选项到默认 - { - DevShowSkipDependency = false; - } - } - [ObservableProperty] private bool _devShowSkipDependency; - public override string ToString() - { - return JsonConvert.SerializeObject(this); - } - public static AppConfig FromString(string json) - { - return JsonConvert.DeserializeObject(json) ?? new AppConfig(); - } - } -} \ No newline at end of file diff --git a/src/LipUI/Models/Config.cs b/src/LipUI/Models/Config.cs new file mode 100644 index 0000000..fb5e42d --- /dev/null +++ b/src/LipUI/Models/Config.cs @@ -0,0 +1,138 @@ +using CommunityToolkit.WinUI.Helpers; +using LipUI.Pages.Settings; +using Microsoft.UI; +using Microsoft.UI.Xaml; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Text.Json.Serialization; + +namespace LipUI.Models; + +internal static class DefaultSettings +{ + public const string ConfigFileName = "config.json"; + + public const string PluginsDir = "plugins"; + + public const string DataDirectory = ".lipui"; + + public const string LipRepoOwner = "lippkg"; + + public const string LipRepoName = "lip"; + + public const string LipPortableUrl = "https://api.github.com/repos/lippkg/lip/releases/latest"; + + + + + public const string LipIndexApiKey = "api.lippkg.com"; + + public const string GitHubApiKey = "api.github.com"; + + public const string LipExecutableFileName = "lip.exe"; +} + +internal class Config +{ + public class GeneralSetting + { + [JsonPropertyName("lip_path")] + public string LipPath { get; set; } = string.Empty; + + [JsonPropertyName("lip_index_api")] + public string LipIndexApiKey { get; set; } = string.Empty; + + [JsonPropertyName("github_api")] + public string GithubApiKey { get; set; } = string.Empty; + + [JsonPropertyName("github_proxy")] + public string GithubProxy { get; set; } = string.Empty; + } + + public class PersonalizationSetting + { + + [JsonPropertyName("color_theme")] + public ElementTheme ColorTheme { get; set; } = ElementTheme.Default; + + [JsonPropertyName("backdrop_type")] + public PersonalizationSettingsView.BackdropControllerType BackdropType { get; set; } = PersonalizationSettingsView.BackdropControllerType.None; + + [JsonPropertyName("backdrop_luminosity")] + public double? BackdropLuminosity { get; set; } = 20; + + [JsonPropertyName("background_color")] + public string? BackgroundColor { get; set; } + + [JsonPropertyName("navigation_view_content_background_color")] + public string? NavigationViewContentBackgroundColor { get; set; } + + [JsonPropertyName("navigation_view_content_border_color")] + public string? NavigationViewContentBorderColor { get; set; } + + [JsonPropertyName("background_secondary_color")] + public string? BackgroundSecondaryColor { get; set; } + + [JsonPropertyName("enable_image_background")] + public bool EnableImageBackground { get; set; } + + [JsonPropertyName("background_image_path")] + public string? BackgroundImagePath { get; set; } + + [JsonPropertyName("reload_colors")] + public bool ResetColors { get; set; } + } + + + [JsonPropertyName("general_settings")] + public GeneralSetting GeneralSettings { get; set; } + + [JsonPropertyName("personalization_settings")] + public PersonalizationSetting PersonalizationSettings { get; set; } + + [JsonPropertyName("plugin_enable_info")] + public Dictionary PluginEanbleInfo { get; set; } + + [JsonPropertyName("servers")] + public List ServerInstances { get; set; } + + [JsonPropertyName("selected_server")] + public ServerInstance? SelectedServer { get; set; } + + public Config() + { + ServerInstances = new(); + ResetGeneralSettings(Main.WorkingDirectory); + ResetPersonalizationSettings(); + PluginEanbleInfo = new(); + } + + [MemberNotNull(nameof(GeneralSettings))] + public void ResetGeneralSettings(string workingDir) + { + GeneralSettings = new() + { + LipPath = Path.Combine(workingDir, DefaultSettings.LipExecutableFileName), + LipIndexApiKey = DefaultSettings.LipIndexApiKey, + GithubApiKey = DefaultSettings.GitHubApiKey, + }; + } + + [MemberNotNull(nameof(PersonalizationSettings))] + public void ResetPersonalizationSettings() + { + PersonalizationSettings = new() + { + BackdropType = PersonalizationSettingsView.BackdropControllerType.None, + BackdropLuminosity = 0, + BackgroundColor = Colors.Transparent.ToHex(), + NavigationViewContentBackgroundColor = Colors.Transparent.ToHex(), + NavigationViewContentBorderColor = Colors.Transparent.ToHex(), + BackgroundSecondaryColor = Colors.Transparent.ToHex(), + EnableImageBackground = false, + BackgroundImagePath = null, + ResetColors = true + }; + } +} diff --git a/src/LipUI/Models/GlobalIcons.cs b/src/LipUI/Models/GlobalIcons.cs new file mode 100644 index 0000000..ecfa91d --- /dev/null +++ b/src/LipUI/Models/GlobalIcons.cs @@ -0,0 +1,12 @@ +using LipUI.Assets; +using Microsoft.UI.Xaml.Media.Imaging; + + +namespace LipUI.Models; + +internal static class GlobalIcons +{ + public static readonly BitmapImage GrassBlock = InternalServices.CreateImageFromBytes(Images.grass_block); + public static readonly BitmapImage Netherrack = InternalServices.CreateImageFromBytes(Images.netherrack); + public static readonly BitmapImage Glass = InternalServices.CreateImageFromBytes(Images.glass); +} diff --git a/src/LipUI/Models/Lip/LipCommand.cs b/src/LipUI/Models/Lip/LipCommand.cs new file mode 100644 index 0000000..579f7fa --- /dev/null +++ b/src/LipUI/Models/Lip/LipCommand.cs @@ -0,0 +1,73 @@ +using System; + +namespace LipUI.Models.Lip; + +public class LipCommand +{ + public readonly string Command; + public readonly LipCommand? Parent; + + private LipCommand() => throw new NotSupportedException(); + + private LipCommand(string command, LipCommand? parent = null) + { + Command = command; + Parent = parent; + } + + + + public static readonly LipCommand Lip = new("lip"); + + public static readonly LipCommand AutoRemove = new("autoremove", Lip); + + public static readonly LipCommand Cache = new("cache", Lip); + + public static readonly LipCommand Purge = new("purge", Cache); + + public static readonly LipCommand Install = new("install", Lip); + + public static readonly LipCommand List = new("list", List); + + public static readonly LipCommand Show = new("show", Lip); + + public static readonly LipCommand Tooth = new("tooth", Lip); + + public static readonly LipCommand Init = new("init", Tooth); + + public static readonly LipCommand Pack = new("pack", Tooth); + + public static readonly LipCommand Uninstall = new("uninstall", Lip); + + public override string ToString() => Command; + + public static explicit operator string(LipCommand command) => command.Command; + + public static bool operator ==(LipCommand left, LipCommand right) + => left.Equals(right); + + public static bool operator !=(LipCommand left, LipCommand right) + => !left.Equals(right); + + public static LipCommandContext CreateCommand() => new(); + + public override bool Equals(object? obj) + { + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj is null) + { + return false; + } + + if (obj is LipCommand command) + return Command == command.Command; + + return false; + } + + public override int GetHashCode() => Command.GetHashCode(); +} diff --git a/src/LipUI/Models/Lip/LipCommandContext.cs b/src/LipUI/Models/Lip/LipCommandContext.cs new file mode 100644 index 0000000..d15d0e4 --- /dev/null +++ b/src/LipUI/Models/Lip/LipCommandContext.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace LipUI.Models.Lip; + +public class LipCommandSyntaxException : Exception +{ + public LipCommandSyntaxException(string? message) : base(message) + { + } +} + +public class LipCommandContext +{ + private readonly List commands = new(); + private readonly List options = new(); + + private LipCommand? current; + private string? parameter; + + public LipCommandContext() { } + + public LipCommandContext Append(LipCommand command) + { + if (current is null || (command.Parent is not null && command.Parent == current)) + { + commands.Add(command); + current = command; + return this; + } + + throw new LipCommandSyntaxException(this); + } + + public LipCommandContext Append(LipCommandOption option) + { + if (current is not null && option.AvailableCommands.First(c => c == current) is not null) + { + options.Add(option); + return this; + } + + throw new LipCommandSyntaxException(this); + } + + public LipCommandContext Commands(params LipCommand[] commands) + { + foreach (var option in commands) Append(option); + return this; + } + + public LipCommandContext Options(params LipCommandOption[] options) + { + foreach (var option in options) Append(option); + return this; + } + + public LipCommandContext Parameter(string param) + { + parameter = param; + return this; + } + + public static LipCommandContext operator +(LipCommandContext context, LipCommand command) + => context.Append(command); + + public static LipCommandContext operator +(LipCommandContext context, LipCommandOption option) + => context.Append(option); + + public static LipCommandContext operator +(LipCommandContext context, string param) + => context.Parameter(param); + + public override string ToString() + { + var bulider = new StringBuilder(); + foreach (var cmd in commands) + bulider.Append($"{cmd} "); + foreach (var option in options) + bulider.Append($"{option} "); + if (parameter is not null) + bulider.Append(parameter); + return bulider.ToString().Trim(); + } + + public static implicit operator string(LipCommandContext command) + => command.ToString(); +} diff --git a/src/LipUI/Models/Lip/LipCommandOption.cs b/src/LipUI/Models/Lip/LipCommandOption.cs new file mode 100644 index 0000000..dfe31a4 --- /dev/null +++ b/src/LipUI/Models/Lip/LipCommandOption.cs @@ -0,0 +1,66 @@ +using System; +using static LipUI.Models.Lip.LipCommand; + +namespace LipUI.Models.Lip; + +public class LipCommandOption +{ + public readonly string? Abbreviation; + public readonly string Option; + public readonly LipCommand[] AvailableCommands; + + private LipCommandOption() => throw new NotSupportedException(); + + public LipCommandOption(string? abbreviation, string option, params LipCommand[] availableCommands) + { + Abbreviation = abbreviation; + Option = option; + AvailableCommands = availableCommands; + } + + public static readonly LipCommandOption Help + = new("h", "help", LipCommand.Lip, AutoRemove, Cache, Purge, Install, List, Show, Tooth, Init, Pack, Uninstall); + + public static readonly LipCommandOption Version + = new("V", "version", LipCommand.Lip); + + public static readonly LipCommandOption Verbose + = new("v", "verbose", LipCommand.Lip); + + public static readonly LipCommandOption Quiet + = new("q", "quiet", LipCommand.Lip); + + public static readonly LipCommandOption Yes + = new("y", "yes", AutoRemove, Install, Uninstall); + + public static readonly LipCommandOption Upgrade + = new(null, "upgrade", Install); + + public static readonly LipCommandOption ForceReinstall + = new(null, "force-reinstall", Install); + + public static readonly LipCommandOption NoDependencies + = new(null, "no-dependencies", Install); + + public static readonly LipCommandOption Upgradable + = new(null, "upgradable", List); + + public static readonly LipCommandOption Json + = new(null, "json", List, Show); + + public static readonly LipCommandOption Available + = new(null, "available", Show); + + public static readonly LipCommandOption KeepPossession + = new(null, "keep-possession", Uninstall); + + public override string ToString() + { + if (Abbreviation is not null) + return $"-{Abbreviation}"; + else + return $"--{Option}"; + } + + public static explicit operator string(LipCommandOption option) => option.ToString(); +} diff --git a/src/LipUI/Models/Lip/LipConsole.cs b/src/LipUI/Models/Lip/LipConsole.cs new file mode 100644 index 0000000..b0a75aa --- /dev/null +++ b/src/LipUI/Models/Lip/LipConsole.cs @@ -0,0 +1,82 @@ +using System; +using System.Diagnostics; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace LipUI.Models.Lip; + +public class LipConsole +{ + public LipConsole(string executablePath, string workingDir) + { + ExecutablePath = executablePath; + WorkingPath = workingDir; + } + public string ExecutablePath { get; } + public string WorkingPath { get; } + + public event Action? OutputError; + public event Action? Output; + + public async ValueTask Run(string command, Action? getInstance = null, CancellationToken cancellationToken = default) + { + var ins = new LipConsoleInstance( + ExecutablePath, + WorkingPath, + command, + cancellationToken, + output => + { + Output?.Invoke(output); + }, + error => + { + OutputError?.Invoke(error); + }, + out Process? process); + getInstance?.Invoke(ins); + + while (ins.HasExited is false) await Task.Delay(100, cancellationToken); + cancellationToken.ThrowIfCancellationRequested(); + + return ins.ExitCode; + } + + public async ValueTask Run(LipCommandContext command, Action? getInstance = null, CancellationToken cancellationToken = default) + => await Run(command.ToString(), getInstance, cancellationToken); + + public async ValueTask RunAndGetString(string command, Action? getInstance = null, CancellationToken cancellationToken = default) + { + var builder = new StringBuilder(); + var ins = new LipConsoleInstance( + ExecutablePath, + WorkingPath, + command, + cancellationToken, + output => + { + Output?.Invoke(output); + builder.AppendLine(output); + }, + error => + { + OutputError?.Invoke(error); + builder.AppendLine(error); + }, + out Process? process); + getInstance?.Invoke(ins); + + while (ins.HasExited is false) + { + try { await Task.Delay(100, cancellationToken); } catch { } + ins.KillIfCanceled(); + } + cancellationToken.ThrowIfCancellationRequested(); + + return builder.ToString(); + } + + public async ValueTask RunAndGetString(LipCommandContext command, Action? getInstance = null, CancellationToken cancellationToken = default) + => await RunAndGetString(command.ToString(), getInstance, cancellationToken); +} diff --git a/src/LipUI/Models/Lip/LipConsoleInstance.cs b/src/LipUI/Models/Lip/LipConsoleInstance.cs new file mode 100644 index 0000000..9e5a89a --- /dev/null +++ b/src/LipUI/Models/Lip/LipConsoleInstance.cs @@ -0,0 +1,198 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Threading; + +namespace LipUI.Models.Lip; + +public class LipConsoleInstance : IDisposable +{ + public Process? Process { get; private set; } + public bool HasExited => Process?.HasExited ?? false; + public int ExitCode => Process?.ExitCode ?? -1; + private readonly CancellationToken _tk; + + public LipConsoleInstance(string exe, string? workingDir, string cmd, CancellationToken tk, + Action output, Action outputErr, out Process process) : this(exe, workingDir, cmd, tk, output, outputErr) + { + process = this.Process!; + } + public LipConsoleInstance(string exe, string? workingDir, string cmd, CancellationToken tk, + Action output, Action outputErr) + { + _tk = tk; + Process = new() + { + StartInfo = new(exe, cmd) + { + StandardOutputEncoding = Encoding.UTF8, + StandardErrorEncoding = Encoding.UTF8, + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true, + RedirectStandardError = true, + RedirectStandardInput = true, + WorkingDirectory = workingDir ?? Path.GetDirectoryName(exe) + } + }; + Process.OutputDataReceived += (_, args) => + { + if (!_tk.IsCancellationRequested) + output(args.Data ?? ""); + }; + Process.ErrorDataReceived += (_, args) => + { + if (!_tk.IsCancellationRequested) + outputErr(args.Data ?? ""); + }; + Process.Start(); + Process.BeginOutputReadLine(); + Process.BeginErrorReadLine(); + } + public void KillIfCanceled() + { + if (_tk.IsCancellationRequested) + { + try + { + Process?.Kill(); + Process = null; + } + catch { } + _tk.ThrowIfCancellationRequested(); + } + } + + public void Dispose() + { + try + { + if (Process is { HasExited: false }) + Process?.Kill(); + } + catch { } + Process?.Dispose(); + GC.SuppressFinalize(this); + } + + ~LipConsoleInstance() + { + Dispose(); + } +} +//public class __LipConsole +//{ +// public __LipConsole(string executablePath = "lip.exe", string? workingDir = null) +// { +// ExecutablePath = executablePath; +// WorkingPath = workingDir; +// } +// public string ExecutablePath { get; } +// public string? WorkingPath { get; } +// public async Task Run(string cmd, Action? output, CancellationToken tk = default) +// { +// var inst = new LipConsoleInstance(ExecutablePath, WorkingPath, cmd, tk, s => output?.Invoke(s), s => output?.Invoke(s)); +// while (!inst.HasExited) +// { +// await Task.Delay(100, tk); +// } +// tk.ThrowIfCancellationRequested(); +// return inst.ExitCode; +// } +// public async Task RunWithInput(string cmd, Action>? output, CancellationToken tk = default) +// { +// Process? process = default; +// var inst = new LipConsoleInstance(ExecutablePath, WorkingPath, cmd, tk, +// s => +// { +// output?.Invoke(s, s => +// { +// process?.StandardInput.WriteLine(s); +// }); +// }, s => +// { +// output?.Invoke(s, s => +// { +// process?.StandardInput.WriteLine(s); +// }); +// }, out process); +// while (!inst.HasExited) +// { +// await Task.Delay(100, tk); +// } +// tk.ThrowIfCancellationRequested(); +// return inst.ExitCode; +// } +// public async Task Run(string cmd, Action output, Action outputError, CancellationToken tk = default) +// { +// var inst = new LipConsoleInstance(ExecutablePath, WorkingPath, cmd, tk, output, outputError); +// while (!inst.HasExited) +// { +// await Task.Delay(100, tk); +// } +// tk.ThrowIfCancellationRequested(); +// return inst.ExitCode; +// } +// public async Task RunString(string cmd, Action? output = null, CancellationToken tk = default) +// { +// var sb = new StringBuilder(); +// var inst = new LipConsoleInstance(ExecutablePath, WorkingPath, cmd, tk, +// s => +// { +// sb.AppendLine(s); output?.Invoke(s); +// }, s => +// { +// sb.AppendLine(s); output?.Invoke(s); +// }); +// while (!inst.HasExited) +// { +// try +// { +// await Task.Delay(100, tk); +// } +// catch +// { +// // ignored +// } +// inst.KillIfCanceled(); +// } +// tk.ThrowIfCancellationRequested(); +// return sb.ToString(); +// } +// public async Task RunStringWithInput(string cmd, Action>? output = null, CancellationToken tk = default) +// { +// var sb = new StringBuilder(); +// Process? process = default; +// var inst = new LipConsoleInstance(ExecutablePath, WorkingPath, cmd, tk, +// s => +// { +// sb.AppendLine(s); +// output?.Invoke(s, s => +// { +// process?.StandardInput.WriteLine(s); +// }); +// }, s => +// { +// sb.AppendLine(s); +// output?.Invoke(s, s => +// { +// process?.StandardInput.WriteLine(s); +// }); +// }, out process); +// while (!inst.HasExited) +// { +// try +// { +// await Task.Delay(100, tk); +// } +// catch +// { +// // ignored +// } +// inst.KillIfCanceled(); +// } +// tk.ThrowIfCancellationRequested(); +// return sb.ToString(); +// } +//} diff --git a/src/LipUI/Models/Main.cs b/src/LipUI/Models/Main.cs new file mode 100644 index 0000000..51b58df --- /dev/null +++ b/src/LipUI/Models/Main.cs @@ -0,0 +1,121 @@ +using LipUI.Models.Lip; +using LipUI.Pages.LipExecutionPanel; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using System; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Threading.Tasks; + +namespace LipUI.Models; + +internal static class Main +{ + static Main() => Initialize(); + + + + public static Config Config { get; private set; } + + public static string WorkingDirectory { get; private set; } + + public static bool ColorsFirstInitSign = false; + + + [MemberNotNull(nameof(Config), nameof(WorkingDirectory))] + private static void Initialize() + { + InitializeWorkingDir(); + InitializeConfig(); + } + + [MemberNotNull(nameof(WorkingDirectory))] + private static void InitializeWorkingDir() + { + var currentDir = new FileInfo(Environment.ProcessPath!).Directory!.FullName; + + var path = Path.Combine(currentDir, DefaultSettings.DataDirectory); + if (Directory.Exists(path) is false) + { + Directory.CreateDirectory(path); + } + WorkingDirectory = path; + } + + [MemberNotNull(nameof(Config))] + private static void InitializeConfig() + { + var path = Path.Combine(WorkingDirectory, DefaultSettings.ConfigFileName); + if (File.Exists(path)) + { + using var config = File.OpenRead(path); + using var reader = new StreamReader(config); + Config = JsonSerializer.Deserialize(reader.ReadToEnd()) ?? throw new NullReferenceException(); + } + else + { + Config = new Config(); + Config.PersonalizationSettings.ResetColors = true; + } + } + + public static async ValueTask CreateLipConsole(XamlRoot xamlRoot) + { + var (success, path) = await TryGetLipConsolePathAsync(xamlRoot); + if (success is false) + return null; + + var server = Config.SelectedServer; + if (server is null) + return null; + + return new LipConsole(path!, server.WorkingDirectory); + } + + + private static readonly object _lock = new(); + public static async ValueTask SaveConfigAsync() + { + await Task.Run(() => + { + lock (_lock) + { + var path = Path.Combine(WorkingDirectory, DefaultSettings.ConfigFileName); + if (File.Exists(path)) File.Delete(path); + + using var file = File.Create(path); + using var writer = new StreamWriter(file); + + + writer.Write(JsonSerializer.Serialize(Config, new JsonSerializerOptions() + { + WriteIndented = true, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + })); + } + }); + } + + public static async ValueTask<(bool, string)> TryGetLipConsolePathAsync(XamlRoot xamlRoot) + { + if (File.Exists(Config.GeneralSettings.LipPath)) + return (true, Config.GeneralSettings.LipPath); + else + { + var dialog = new ContentDialog() + { + XamlRoot = xamlRoot, + Content = new LipInstallerView() + }; + + await dialog.ShowAsync(); + + + return File.Exists(Config.GeneralSettings.LipPath) ? + (true, Config.GeneralSettings.LipPath) : + (false, string.Empty); + } + } +} diff --git a/src/LipUI/Models/PackageInstaller.cs b/src/LipUI/Models/PackageInstaller.cs new file mode 100644 index 0000000..8435b12 --- /dev/null +++ b/src/LipUI/Models/PackageInstaller.cs @@ -0,0 +1,9 @@ +namespace LipUI.Models; + +internal static class PackageInstaller +{ + //public static ValueTask InstallPackage(LipIndex.LipIndexData.LipToothItem item) + //{ + + //} +} diff --git a/src/LipUI/Models/Plugin/ILipuiPlugin.cs b/src/LipUI/Models/Plugin/ILipuiPlugin.cs new file mode 100644 index 0000000..61e54a7 --- /dev/null +++ b/src/LipUI/Models/Plugin/ILipuiPlugin.cs @@ -0,0 +1,25 @@ +using System; + +namespace LipUI.Models.Plugin; + +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] +public sealed class LipUIModuleAttribute : Attribute +{ +} + +public interface ILipuiPlugin +{ + public string PluginName { get; } + + public bool DefaultEnabled { get; } + + public Guid Guid { get; } + + public void OnInitlalize(LipuiRuntimeInfo info) { } + + public void OnEnable(LipuiServices services) { } + + public void OnDisable(LipuiServices services) { } + + public void OnApplicationExit() { } +} diff --git a/src/LipUI/Models/Plugin/ILipuiPluginModule.cs b/src/LipUI/Models/Plugin/ILipuiPluginModule.cs new file mode 100644 index 0000000..96c3874 --- /dev/null +++ b/src/LipUI/Models/Plugin/ILipuiPluginModule.cs @@ -0,0 +1,19 @@ +using LipUI.Pages.Home.Modules; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Media; +using System; + +namespace LipUI.Models.Plugin; + +public interface ILipuiPluginModule : ILipuiPlugin +{ + public Type PageType { get; } + + public FrameworkElement? IconContent { get => null; } + + public Brush? IconBackground { get => null; } + + public void OnIconInitialze(ModuleIcon icon) { } + + public void OnExit() { } +} diff --git a/src/LipUI/Models/Plugin/ILipuiPluginUI.cs b/src/LipUI/Models/Plugin/ILipuiPluginUI.cs new file mode 100644 index 0000000..e6527d8 --- /dev/null +++ b/src/LipUI/Models/Plugin/ILipuiPluginUI.cs @@ -0,0 +1,15 @@ +using Microsoft.UI.Xaml.Controls; +using System; + +namespace LipUI.Models.Plugin; + +public interface ILipuiPluginUI : ILipuiPlugin +{ + public IconElement NavigatonBarIcon { get; } + + public object NavigationBarContent { get; } + + public Type PageType { get; } + + public object? PageParameterRequsted() { return null; } +} diff --git a/src/LipUI/Models/Plugin/LipuiRuntimeInfo.cs b/src/LipUI/Models/Plugin/LipuiRuntimeInfo.cs new file mode 100644 index 0000000..9d85f8b --- /dev/null +++ b/src/LipUI/Models/Plugin/LipuiRuntimeInfo.cs @@ -0,0 +1,11 @@ +using Microsoft.UI.Xaml; + +namespace LipUI.Models.Plugin; + +#nullable disable +public class LipuiRuntimeInfo +{ + public ElementTheme Theme { get; init; } + + public Window ApplicationWindow { get; init; } +} diff --git a/src/LipUI/Models/Plugin/LipuiServices.cs b/src/LipUI/Models/Plugin/LipuiServices.cs new file mode 100644 index 0000000..3785d09 --- /dev/null +++ b/src/LipUI/Models/Plugin/LipuiServices.cs @@ -0,0 +1,54 @@ +using LipUI.Models.Lip; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using System; +using System.Threading.Tasks; + +namespace LipUI.Models.Plugin; + +public class LipuiServices +{ + public async ValueTask CreateLipConsoleAsync(XamlRoot xamlRoot, string workingDir) + { + var (success, path) = await Main.TryGetLipConsolePathAsync(xamlRoot); + if (success) + { + return new(path!, workingDir); + } + return null; + } + + public static async ValueTask ShowInfoBarAsync( + string? title, + string? message = null, + InfoBarSeverity severity = InfoBarSeverity.Informational, + TimeSpan interval = default, + UIElement? barContent = null) + => await InternalServices.ShowInfoBarAsync(title, message, severity, interval, barContent); + + public static async ValueTask ShowInfoBarAsync( + Exception ex, + bool containsStacktrace = false, + InfoBarSeverity severity = InfoBarSeverity.Error, + TimeSpan interval = default, + UIElement? barContent = null) + => await InternalServices.ShowInfoBarAsync(ex, containsStacktrace, severity, interval, barContent); + + public static void ShowInfoBar( + string? title, + string? message = null, + InfoBarSeverity severity = InfoBarSeverity.Informational, + TimeSpan interval = default, + UIElement? barContent = null, + Action? completed = null) + => InternalServices.ShowInfoBar(title, message, severity, interval, barContent, completed); + + public static void ShowInfoBar( + Exception ex, + bool containsStacktrace = false, + InfoBarSeverity severity = InfoBarSeverity.Error, + TimeSpan interval = default, + UIElement? barContent = null, + Action? completed = null) + => InternalServices.ShowInfoBar(ex, containsStacktrace, severity, interval, barContent, completed); +} diff --git a/src/LipUI/Models/Plugin/PluginSystem.cs b/src/LipUI/Models/Plugin/PluginSystem.cs new file mode 100644 index 0000000..bfa8d21 --- /dev/null +++ b/src/LipUI/Models/Plugin/PluginSystem.cs @@ -0,0 +1,241 @@ +using LipUI.Pages.Home.Modules; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.Loader; +using System.Threading.Tasks; +using guid_string = System.String; + +namespace LipUI.Models.Plugin; + +internal static class PluginSystem +{ + static PluginSystem() + { + ModulesPage.InitEventHandlers(); + MainWindow.InitEventHandlers(); + } + + public static IEnumerable? Plugins { get; private set; } + + public static IEnumerable? UIPlugins { get; private set; } + + public static IEnumerable? Modules { get; private set; } + + private static readonly HashSet enabledPlugins = new(); + + private static Dictionary guidWithPlugins = new(); + + private static readonly LipuiServices services = new(); + + + /// + /// noexcept + /// + public static async ValueTask LoadAsync() + { + try + { + var types = await GetPluginTypes(); + if (types is null) + return; + + var (plugins, uiPlugins, modules) = await CreateInstances(types); + + (Plugins, UIPlugins, Modules) = (plugins, uiPlugins, modules); + + await InitializePlugins(plugins); + await EnablePlugins(plugins); + } + catch (Exception ex) { await InternalServices.ShowInfoBarAsync(ex); } + } + + private static async ValueTask?> GetPluginTypes() + { + var dir = Path.Combine(Main.WorkingDirectory, DefaultSettings.PluginsDir); + if (Directory.Exists(dir) is false) + { + Directory.CreateDirectory(dir); + return null; + } + + List pluginTypes = new(); + var ctx = AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly())!; + + var dirInfo = new DirectoryInfo(dir); + + var types = from type in Assembly.GetExecutingAssembly().GetTypes() + where type.IsAssignableTo(typeof(ILipuiPlugin)) && type.GetCustomAttribute() is not null + select type; + + pluginTypes.AddRange(types); + + foreach (var file in dirInfo.EnumerateFiles()) + { + types = await TryLoadAssemblyAndGetPluginTypes(ctx, file.FullName); + if (types is not null) + pluginTypes.AddRange(types); + } + + return pluginTypes; + } + + private static async ValueTask?> TryLoadAssemblyAndGetPluginTypes(AssemblyLoadContext ctx, string path) + { + try + { + var assembly = ctx.LoadFromAssemblyPath(path); + if (assembly is null) + return null; + + var types = from type in assembly.DefinedTypes + where type.IsAssignableTo(typeof(ILipuiPlugin)) && type.GetCustomAttribute() is not null + select type; + return types; + } + catch (Exception ex) + { + await InternalServices.ShowInfoBarAsync(ex); + return null; + } + } + + private static async ValueTask<(IEnumerable, IEnumerable, IEnumerable)> CreateInstances(IEnumerable types) + { + List instances = new(types.Count()); + List uiInstances = new(); + List modules = new(); + + foreach (var type in types) + { + try + { + var obj = Activator.CreateInstance(type); + + if (obj is ILipuiPlugin plugin) + { + var guidStr = plugin.Guid.ToString(); + if (guidWithPlugins.ContainsKey(guidStr)) + continue; + + guidWithPlugins.Add(guidStr, plugin); + + instances.Add(plugin); + } + + if (obj is ILipuiPluginUI ui) + uiInstances.Add(ui); + + if (obj is ILipuiPluginModule module) + modules.Add(module); + } + catch (Exception ex) + { + await InternalServices.ShowInfoBarAsync(ex); + continue; + } + } + return (instances, uiInstances, modules); + } + + private static async ValueTask InitializePlugins(IEnumerable plugins) + { + var info = new LipuiRuntimeInfo() + { + Theme = Main.Config.PersonalizationSettings.ColorTheme, + ApplicationWindow = InternalServices.MainWindow ?? throw new NullReferenceException() + }; + foreach (var plugin in plugins) + { + try + { + await Task.Run(() => plugin.OnInitlalize(info)); + } + catch (Exception ex) + { + await InternalServices.ShowInfoBarAsync(ex); + continue; + } + } + } + + public static string GetPluginEnabledConfigKey(ILipuiPlugin plugin) + => $"{plugin.GetType().Assembly.GetName().Name}+{plugin.PluginName}+{plugin.Guid}"; + + private static async ValueTask EnablePlugins(IEnumerable plugins) + { + var enableInfo = Main.Config.PluginEanbleInfo; + Main.Config.PluginEanbleInfo = new(); + foreach (var plugin in plugins) + { + var key = GetPluginEnabledConfigKey(plugin); + try + { + if (enableInfo.TryGetValue(key, out bool enable) is false) + { + enable = plugin.DefaultEnabled; + } + + if (enable) + { + EnablePlugin(plugin); + } + } + catch (Exception ex) + { + await InternalServices.ShowInfoBarAsync(ex); + continue; + } + } + } + + public static void EnablePlugin(ILipuiPlugin plugin) + { + try + { + if (enabledPlugins.Contains(plugin)) + return; + + Main.Config.PluginEanbleInfo[GetPluginEnabledConfigKey(plugin)] = true; + var task = Main.SaveConfigAsync(); + + enabledPlugins.Add(plugin); + + Task.Run(() => plugin.OnEnable(services)); + PluginEnabled?.Invoke(plugin); + } + catch (Exception ex) + { + Task.Run(() => InternalServices.ShowInfoBarAsync(ex)); + } + } + + public static void DisablePlugin(ILipuiPlugin plugin) + { + try + { + if (enabledPlugins.Contains(plugin) is false) + return; + + Main.Config.PluginEanbleInfo[GetPluginEnabledConfigKey(plugin)] = false; + + enabledPlugins.Remove(plugin); + var task = Main.SaveConfigAsync(); + + Task.Run(() => plugin.OnDisable(services)); + PluginDisabled?.Invoke(plugin); + } + catch (Exception ex) + { + Task.Run(() => InternalServices.ShowInfoBarAsync(ex)); + } + } + + public static bool IsPluginEnabled(ILipuiPlugin plugin) + => enabledPlugins.Contains(plugin); + + public static event Action? PluginEnabled; + public static event Action? PluginDisabled; +} diff --git a/src/LipUI/Models/ServerIcon.cs b/src/LipUI/Models/ServerIcon.cs new file mode 100644 index 0000000..d81d799 --- /dev/null +++ b/src/LipUI/Models/ServerIcon.cs @@ -0,0 +1,61 @@ +using Microsoft.UI.Xaml.Media.Imaging; +using System; +using System.IO; +using System.Threading.Tasks; + +namespace LipUI.Models; + +internal static class ServerIcon +{ + [Flags] public enum IconType { None, Empty, Preview, Default, Custom } + + public static async ValueTask GetIcon(string? version, string? iconPath) + { + IconType type; + + if (iconPath is null) + { + if (string.IsNullOrEmpty(version)) + type = IconType.Empty; + else if (version.Contains("preview")) + type = IconType.Preview; + else + type = IconType.Default; + } + else type = IconType.Custom; + + switch (type) + { + case IconType.Empty: return GlobalIcons.Glass; + case IconType.Preview: return GlobalIcons.Netherrack; + case IconType.Default: return GlobalIcons.GrassBlock; + case IconType.Custom: + { + if (File.Exists(iconPath)) + { + try + { + using var file = File.OpenRead(iconPath); + var bitmap = new BitmapImage(); + await bitmap.SetSourceAsync(file.AsRandomAccessStream()); + return bitmap; + } + catch (Exception) + { + return await GetIcon(version, null); + } + } + else return await GetIcon(version, null); + + } + + case IconType.None: + default: throw new InvalidProgramException("???"); + + } + } + + public static async ValueTask GetIcon(ServerInstance? server) + => server is null ? await GetIcon(null, null) : await GetIcon(server.Version, server.Icon); + +} diff --git a/src/LipUI/Models/ServerInstance.cs b/src/LipUI/Models/ServerInstance.cs new file mode 100644 index 0000000..70886e9 --- /dev/null +++ b/src/LipUI/Models/ServerInstance.cs @@ -0,0 +1,48 @@ +using System.Text.Json.Serialization; + +namespace LipUI.Models; + +internal class ServerInstance +{ + [JsonPropertyName("name")] + public string Name { get; set; } = "undefined"; + + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; + + [JsonPropertyName("version")] + public string Version { get; set; } = string.Empty; + + [JsonPropertyName("working_directory")] + public string WorkingDirectory { get; set; } = string.Empty; + + [JsonPropertyName("icon")] + public string? Icon { get; set; } + + public static bool operator ==(ServerInstance? left, ServerInstance? right) + => left is not null && left.Equals(right); + + public static bool operator !=(ServerInstance? left, ServerInstance? right) + => !(left == right); + + public override bool Equals(object? obj) + { + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj is null) + { + return false; + } + + if (obj is ServerInstance instance) + return WorkingDirectory == instance.WorkingDirectory; + else + return false; + } + + public override int GetHashCode() + => WorkingDirectory.GetHashCode(); +} diff --git a/src/LipUI/Package.appxmanifest b/src/LipUI/Package.appxmanifest new file mode 100644 index 0000000..5005f52 --- /dev/null +++ b/src/LipUI/Package.appxmanifest @@ -0,0 +1,52 @@ + + + + + + + + + + LipUI + minec + Assets\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/LipUI/Pages/Home/HomePage.xaml b/src/LipUI/Pages/Home/HomePage.xaml new file mode 100644 index 0000000..e1b0c4c --- /dev/null +++ b/src/LipUI/Pages/Home/HomePage.xaml @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/LipUI/Pages/Home/HomePage.xaml.cs b/src/LipUI/Pages/Home/HomePage.xaml.cs new file mode 100644 index 0000000..6d83245 --- /dev/null +++ b/src/LipUI/Pages/Home/HomePage.xaml.cs @@ -0,0 +1,107 @@ +using LipUI.Models; +using LipUI.Pages.Home.Modules; +using LipUI.Pages.ServerSelection; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Navigation; +using System; +using System.Diagnostics; +using System.IO; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace LipUI.Pages.Home; + +/// +/// An empty page that can be used on its own or navigated to within a Frame. +/// +public sealed partial class HomePage : Page +{ + public HomePage() + { + InitializeComponent(); + } + + private void ServerIconImage_Loading(FrameworkElement sender, object args) + => RefreshIcon(); + + private async void RefreshIcon() + { + ServerInstance? instance = Main.Config.SelectedServer; + var image = await ServerIcon.GetIcon(instance); + image.DecodePixelType = Microsoft.UI.Xaml.Media.Imaging.DecodePixelType.Logical; + image.DecodePixelHeight = (int)ServerIconImage.Height; + image.DecodePixelWidth = (int)ServerIconImage.Width; + + ServerIconImage.Source = image; + ServerDesc.Text = + instance is null ? + "home$nullServerPath".GetLocalized() : + string.IsNullOrWhiteSpace(instance.Description) ? + "home$emptyDesc".GetLocalized() : + instance.Description; + } + + private void SelectServerButton_Click(object sender, RoutedEventArgs e) + => Frame.Navigate(typeof(ServerSelectionPage), () => { DispatcherQueue.TryEnqueue(RefreshIcon); }); + + private void StartServerButton_Click(object sender, RoutedEventArgs e) + { + if (Main.Config.SelectedServer is null) + return; + + var dir = Main.Config.SelectedServer.WorkingDirectory; + var path = Path.Combine(dir, "bedrock_server_mod.exe"); + + if (File.Exists(path)) + { + Process.Start(path); + return; + } + + path = Path.Combine(dir, "bedrock_server.exe"); + if (File.Exists(path)) + { + Process.Start(path); + } + } + + private void ContentFrame_NavigationFailed(object sender, NavigationFailedEventArgs e) + => throw new Exception("Failed to load Page " + e.SourcePageType.FullName); + + private void ContentFrame_Loading(FrameworkElement sender, object args) + => ContentFrame.Navigate(typeof(ModulesPage)); + + private void BackButton_PointerEntered(object sender, PointerRoutedEventArgs e) + => AnimatedIcon.SetState(BackAnimatedIcon, "PointerOver"); + + private void BackButton_PointerExited(object sender, PointerRoutedEventArgs e) + => AnimatedIcon.SetState(BackAnimatedIcon, "Normal"); + + private void BackButton_Click(object sender, RoutedEventArgs e) + { + ModuleIcon.OnExit(ContentFrame.Content.GetType()); + ContentFrame.TryGoBack(); + } + + private void ContentFrame_Navigated(object sender, NavigationEventArgs e) + { + BackButton.IsEnabled = ContentFrame.CanGoBack; + var type = e.Content.GetType(); + + DispatcherQueue.TryEnqueue(async () => + { + try + { + InternalFrameTitle.Text = type != typeof(ModulesPage) ? + ModuleIcon.Modules[type].PluginName : "modules$title$modulePage".GetLocalized(); + } + catch (Exception ex) + { + await InternalServices.ShowInfoBarAsync(ex); + } + }); + } +} diff --git a/src/LipUI/Pages/Home/Modules/AllowListViewerPage.xaml b/src/LipUI/Pages/Home/Modules/AllowListViewerPage.xaml new file mode 100644 index 0000000..846fc89 --- /dev/null +++ b/src/LipUI/Pages/Home/Modules/AllowListViewerPage.xaml @@ -0,0 +1,14 @@ + + + + + + + diff --git a/src/LipUI/Pages/Home/Modules/AllowListViewerPage.xaml.cs b/src/LipUI/Pages/Home/Modules/AllowListViewerPage.xaml.cs new file mode 100644 index 0000000..8e2191a --- /dev/null +++ b/src/LipUI/Pages/Home/Modules/AllowListViewerPage.xaml.cs @@ -0,0 +1,38 @@ +using Microsoft.UI.Xaml.Controls; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace LipUI.Pages.Home.Modules; + +//[LipUIModule] +//internal class AllowListViewerPage_Module : ILipUIModules +//{ +// public string ModuleName => "modules$title$allowListViewer".GetLocalized(); + +// public Type PageType => typeof(AllowListViewerPage); + +// public FrameworkElement? IconContent +// => new SymbolIcon(Symbol.ContactInfo) { Height = 32, Width = 32 }; + +// public Brush? IconBackground +// { +// get +// { +// var color = Colors.AliceBlue; +// color.A = 200; +// return new SolidColorBrush(color); +// } +// } +//} + +/// +/// An empty page that can be used on its own or navigated to within a Frame. +/// +internal sealed partial class AllowListViewerPage : Page +{ + public AllowListViewerPage() + { + InitializeComponent(); + } +} diff --git a/src/LipUI/Pages/Home/Modules/BdsPropertiesEditorPage.xaml b/src/LipUI/Pages/Home/Modules/BdsPropertiesEditorPage.xaml new file mode 100644 index 0000000..be1135f --- /dev/null +++ b/src/LipUI/Pages/Home/Modules/BdsPropertiesEditorPage.xaml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/LipUI/Pages/Home/Modules/BdsPropertiesEditorPage.xaml.cs b/src/LipUI/Pages/Home/Modules/BdsPropertiesEditorPage.xaml.cs new file mode 100644 index 0000000..a5cc606 --- /dev/null +++ b/src/LipUI/Pages/Home/Modules/BdsPropertiesEditorPage.xaml.cs @@ -0,0 +1,359 @@ +using LipUI.Models; +using LipUI.Models.Plugin; +using Microsoft.UI; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Media; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Windows.System; +using Windows.UI; +using static LipUI.InternalServices; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace LipUI.Pages.Home.Modules; + +[LipUIModule] +internal class BdsPropertiesEditorPage_Module : ILipuiPluginModule +{ + public string PluginName => "modules$title$propertiesEditor".GetLocalized(); + + public FrameworkElement IconContent + => new SymbolIcon(Symbol.Setting) { Height = 32, Width = 32 }; + + public Brush IconBackground => new SolidColorBrush( + InternalServices.ApplicationTheme is Microsoft.UI.Xaml.ApplicationTheme.Light ? + Colors.AntiqueWhite : + Colors.DarkSlateBlue); + + public Type PageType => typeof(BdsPropertiesEditorPage); + + public bool DefaultEnabled => true; + + public Guid Guid => new("4718D1E5-7665-0ED3-9D28-9001D8FC5812"); +} + +/// +/// An empty page that can be used on its own or navigated to within a Frame. +/// +internal sealed partial class BdsPropertiesEditorPage : Page +{ + private static readonly Dictionary enums = new() + { + { "gamemode", new string[]{ "survival", "creative", "adventure" } }, + { "difficulty", new string[]{ "peaceful", "easy", "normal", "hard" } }, + { "default-player-permission-level", new string[]{ "visitor", "member", "operator" } }, + { "compression-algorithm", new string[]{ "zlib", "easy", "snappy" } }, + { "server-authoritative-movement", new string[]{ "client-auth", "server-auth", "server-auth-with-rewind" } }, + { "chat-restriction", new string[]{ "None", "Dropped", "Disabled" } }, + }; + + public ServerInstance? Server { get; private set; } + + public Dictionary BindingSettings { get; private set; } = new(); + + public BdsPropertiesEditorPage() + { + InitializeComponent(); + } + + private async void Page_Loaded(object sender, RoutedEventArgs e) + { + Server = Main.Config.SelectedServer; + + if (Server is null) + { + await Task.Delay(500); + ShowInfoBar("propertiesEditor$nullServerPath".GetLocalized(), severity: InfoBarSeverity.Error); + Frame.TryGoBack(); + return; + }; + + Viewer.Content = new ProgressRing(); + DispatcherQueue.TryEnqueue(LoadPropertiesAndCreateUI); + } + + private async ValueTask SaveAsync() + { + try + { + await ShowInfoBarAsync( + "propertiesEditor$saving".GetLocalized(), + severity: InfoBarSeverity.Informational, + barContent: new ProgressBar() + { + IsIndeterminate = true + }); + + if (BindingSettings.Count is not 0) + { + var path = Path.Combine(Server!.WorkingDirectory, "server.properties"); + var lines = await File.ReadAllLinesAsync(path); + + for (int i = 0; i < lines.Length; i++) + { + string? line = lines[i]; + if (line.StartsWith('#') || string.IsNullOrWhiteSpace(line)) + continue; + + var key = line[..line.IndexOf('=')]; + if (BindingSettings.TryGetValue(key, out var value)) + lines[i] = $"{key}={value}"; + } + + await File.WriteAllLinesAsync(path, lines); + } + + await Task.Delay(500); + await ShowInfoBarAsync("propertiesEditor$saveCompleted".GetLocalized(), severity: InfoBarSeverity.Success); + } + catch (Exception ex) + { + await ShowInfoBarAsync(ex); + } + } + + private async void LoadPropertiesAndCreateUI() + { + var path = Path.Combine(Server!.WorkingDirectory, "server.properties"); + string[] lines; + + try + { + lines = await File.ReadAllLinesAsync(path); + } + catch (Exception ex) + { + ShowInfoBar(ex); + await Task.Delay(1000); + Frame.TryGoBack(); + return; + } + + string? currentPropertiesLine, nextPropertiesLine = null; + var notes = new List(); + foreach (var line in lines) + { + if (string.IsNullOrWhiteSpace(line)) + continue; + + if (line.StartsWith('#')) + { + notes.Add(line[1..].Trim()); + continue; + } + else + { + currentPropertiesLine = nextPropertiesLine; + nextPropertiesLine = line; + } + + if (currentPropertiesLine is not null) + { + var index = currentPropertiesLine.IndexOf('='); + var key = currentPropertiesLine[..index]; + var value = currentPropertiesLine[(index + 1)..]; + + if (bool.TryParse(value, out _)) + PropertiesView.Items.Add(BoolTypeView(key, value, notes.ToArray(), BindingSettings)); + else if (enums.TryGetValue(key, out var _enums)) + PropertiesView.Items.Add(EnumTypeView(key, value, notes.ToArray(), BindingSettings, _enums)); + else + PropertiesView.Items.Add(StringTypeView(key, value, notes.ToArray(), BindingSettings)); + + notes.Clear(); + } + } + + Viewer.Content = PropertiesView; + } + + private static UIElement BoolTypeView( + string key, + string? currentValue, + string[] notes, + IDictionary bindingSettings) + { + var res = Application.Current.Resources; + + var rlt = new StackPanel() { Padding = new(4) }; + var val = currentValue is not null && bool.Parse(currentValue); + var @switch = new ToggleSwitch() + { + IsOn = val, + Height = 32 + }; + + var properties = new StackPanel() + { + Spacing = 8, + Orientation = Orientation.Horizontal, + Children = + { + new TextBlock() + { + Style = res["BodyStrongTextBlockStyle"] as Style, + Foreground = res["TextFillColorPrimaryBrush"] as Brush, + Text = key, + VerticalAlignment = VerticalAlignment.Center, + }, + @switch + } + }; + + @switch.Toggled += (sender, e) => + { + lock (bindingSettings) + bindingSettings[key] = ((ToggleSwitch)sender).IsOn.ToString().ToLower(); + }; + + var builder = new StringBuilder(); + foreach (var item in notes) + builder.AppendLine(item); + + rlt.Children.Add(properties); + rlt.Children.Add(new TextBlock() { Text = builder.ToString() }); + + return rlt; + } + + private static UIElement EnumTypeView( + string key, + string? currentValue, + string[] notes, + IDictionary bindingSettings, + string[] enums) + { + var res = Application.Current.Resources; + var rlt = new StackPanel() { Padding = new(4) }; + var flyout = new MenuFlyout(); + var button = new DropDownButton() + { + Flyout = flyout, + Content = new TextBlock { Text = currentValue }, + Height = 32 + }; + + foreach (var @enum in enums) + { + var item = new MenuFlyoutItem() { Text = @enum }; + item.Click += (sender, e) => + { + var item = sender as MenuFlyoutItem; + + lock (bindingSettings) + bindingSettings[key] = item!.Text; + + ((TextBlock)button.Content).Text = item!.Text; + }; + flyout.Items.Add(item); + } + + var properties = new StackPanel() + { + Spacing = 8, + Orientation = Orientation.Horizontal, + Children = + { + new TextBlock() + { + Style = res["BodyStrongTextBlockStyle"] as Style, + Foreground = res["TextFillColorPrimaryBrush"] as Brush, + Text = key, + VerticalAlignment = VerticalAlignment.Center, + }, + button + } + }; + + var builder = new StringBuilder(); + foreach (var item in notes) + builder.AppendLine(item); + + rlt.Children.Add(properties); + rlt.Children.Add(new TextBlock() { Text = builder.ToString() }); + + return rlt; + } + + private static UIElement StringTypeView( + string key, + string? currentValue, + string[] notes, + IDictionary bindingSettings) + { + var res = Application.Current.Resources; + + var rlt = new StackPanel() { Padding = new(4) }; + var brush = new SolidColorBrush(Color.FromArgb(0, 0, 0, 0)); + var input = new TextBox() + { + Text = currentValue, + Background = brush, + BorderBrush = brush, + Height = 32 + }; + + var properties = new StackPanel() + { + Spacing = 8, + Orientation = Orientation.Horizontal, + Children = + { + new TextBlock() + { + Style = res["BodyStrongTextBlockStyle"] as Style, + Foreground = res["TextFillColorPrimaryBrush"] as Brush, + Text = key, + VerticalAlignment = VerticalAlignment.Center, + }, + input + } + }; + + input.TextChanged += (sender, e) => + { + lock (bindingSettings) + bindingSettings[key] = ((TextBox)sender).Text; + }; + + var builder = new StringBuilder(); + foreach (var item in notes) + builder.AppendLine(item); + + rlt.Children.Add(properties); + rlt.Children.Add(new TextBlock() { Text = builder.ToString() }); + + return rlt; + } + + private async void SaveButton_Click(object sender, RoutedEventArgs e) + => await SaveAsync(); + + + + private bool ctrlPressed = false; + + private async void Page_KeyDown(object sender, KeyRoutedEventArgs e) + { + if (e.Key is VirtualKey.Control) + { + ctrlPressed = true; + return; + } + + if (ctrlPressed && e.Key is VirtualKey.S) await SaveAsync(); + } + + private void Page_KeyUp(object sender, KeyRoutedEventArgs e) + { + if (e.Key is VirtualKey.Control) + ctrlPressed = false; + } +} diff --git a/src/LipUI/Pages/Home/Modules/ModuleIcon.xaml b/src/LipUI/Pages/Home/Modules/ModuleIcon.xaml new file mode 100644 index 0000000..219fe66 --- /dev/null +++ b/src/LipUI/Pages/Home/Modules/ModuleIcon.xaml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/LipUI/Pages/Home/Modules/ModuleIcon.xaml.cs b/src/LipUI/Pages/Home/Modules/ModuleIcon.xaml.cs new file mode 100644 index 0000000..6981980 --- /dev/null +++ b/src/LipUI/Pages/Home/Modules/ModuleIcon.xaml.cs @@ -0,0 +1,65 @@ +using LipUI.Models.Plugin; +using Microsoft.UI; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace LipUI.Pages.Home.Modules; + +public sealed partial class ModuleIcon : UserControl +{ + + internal static Dictionary Modules { get; private set; } = new(); + + public Type? PageType { get; private set; } + + public ModuleIcon(ILipuiPluginModule module) + { + InitializeComponent(); + + try + { + + if (module is not null) + { + PageType = module.PageType; + + ModuleName.Text = module.PluginName; + + var icon = module.IconContent; + IconView.Child = icon; + + ElementBorder.Background = module.IconBackground; + + IconView.Child ??= (icon = new SymbolIcon(Symbol.More)); + IconView.Height = icon!.Height; + IconView.Width = icon!.Width; + + ElementBorder.Background ??= new SolidColorBrush(Colors.Transparent); + + Modules.TryAdd(PageType, module); + + module.OnIconInitialze(this); + } + + } + catch (Exception ex) + { + Task.Run(() => InternalServices.ShowInfoBarAsync(ex)); + return; + } + } + + internal static void OnExit(Type type) + { + if (Modules.TryGetValue(type, out var module)) + { + module.OnExit(); + } + } +} diff --git a/src/LipUI/Pages/Home/Modules/ModulesPage.xaml b/src/LipUI/Pages/Home/Modules/ModulesPage.xaml new file mode 100644 index 0000000..e7f84b8 --- /dev/null +++ b/src/LipUI/Pages/Home/Modules/ModulesPage.xaml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/src/LipUI/Pages/Home/Modules/ModulesPage.xaml.cs b/src/LipUI/Pages/Home/Modules/ModulesPage.xaml.cs new file mode 100644 index 0000000..f0cde3e --- /dev/null +++ b/src/LipUI/Pages/Home/Modules/ModulesPage.xaml.cs @@ -0,0 +1,81 @@ +using LipUI.Models.Plugin; +using Microsoft.UI; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace LipUI.Pages.Home.Modules; + +internal class ModulesPage_Module : ILipuiPluginModule +{ + + public FrameworkElement IconContent => new Viewbox(); + + public Brush IconBackground => new SolidColorBrush(Colors.Transparent); + + public Type PageType => typeof(ModulesPage); + + public string PluginName => "modules$title$modulePage".GetLocalized(); + + public bool DefaultEnabled => false; + + public Guid Guid => default; +} + +/// +/// An empty page that can be used on its own or navigated to within a Frame. +/// +public sealed partial class ModulesPage : Page +{ + public static void InitEventHandlers() + { + PluginSystem.PluginEnabled += PluginSystem_PluginEnabled; + PluginSystem.PluginDisabled += PluginSystem_PluginDisabled; + } + + private static readonly HashSet enabledModules = new(); + private static void PluginSystem_PluginEnabled(ILipuiPlugin obj) + { + lock (enabledModules) + { + if (obj is ILipuiPluginModule module) + enabledModules.Add(module); + } + } + + private static void PluginSystem_PluginDisabled(ILipuiPlugin obj) + { + lock (enabledModules) + { + if (obj is ILipuiPluginModule module) + enabledModules.Remove(module); + } + } + + public ModulesPage() + { + InitializeComponent(); + + DispatcherQueue.TryEnqueue(async () => + { + await Task.Delay(500); + + foreach (var module in enabledModules) + { + ModulesView.Items.Add(new ModuleIcon(module)); + } + }); + } + + private void ModulesView_ItemClick(object sender, ItemClickEventArgs e) + { + var item = e.ClickedItem as ModuleIcon; + Frame.Navigate(item!.PageType); + } +} diff --git a/src/LipUI/Pages/Index/IndexPage.xaml b/src/LipUI/Pages/Index/IndexPage.xaml new file mode 100644 index 0000000..fb0ac66 --- /dev/null +++ b/src/LipUI/Pages/Index/IndexPage.xaml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/LipUI/Pages/Index/IndexPage.xaml.cs b/src/LipUI/Pages/Index/IndexPage.xaml.cs new file mode 100644 index 0000000..a45f6ae --- /dev/null +++ b/src/LipUI/Pages/Index/IndexPage.xaml.cs @@ -0,0 +1,182 @@ +using LipUI.Models; +using LipUI.Protocol; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using static LipUI.Protocol.LipIndex.LipIndexData; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace LipUI.Pages.Index; + +/// +/// An empty page that can be used on its own or navigated to within a Frame. +/// +public sealed partial class IndexPage : Page +{ + + private LipIndex? lipIndex; + + public IndexPage() + { + InitializeComponent(); + } + + private void ReloadLipIndex(IEnumerable? items = null) + { + DispatcherQueue.TryEnqueue(async () => + { + try + { + TeethScrollView.Content = new ProgressRing(); + ToothListView.Items.Clear(); + + if (items is null) + { + lipIndex = await RequestLipIndexAsync(Main.Config.GeneralSettings.LipIndexApiKey); + items = lipIndex.Data.Items; + } + + var handler = AuthorButton_Click; + foreach (var item in items) + { + ToothListView.Items.Add(new LipIndexToothView(item, handler)); + } + TeethScrollView.Content = ToothListView; + } + catch (Exception ex) + { + TeethScrollView.Content = ToothListView; + await InternalServices.ShowInfoBarAsync(ex); + } + }); + } + + private void AuthorButton_Click(string author) + { + DispatcherQueue.TryEnqueue(() => SuggestBox.Text = author); + QuerySubmitted(author); + } + + private void IndexPage_Loaded(object sender, RoutedEventArgs e) + => ReloadLipIndex(); + + private static async ValueTask RequestLipIndexAsync(string lipApiUrl) + { + static void ThrowException(string api) => throw new NullReferenceException($"Failed to get index : {api}"); + + using var client = new HttpClient(); + + //foreach all lip index pages + + var text = await client.GetStringAsync($"https://{lipApiUrl}/search/teeth?page=1"); + if (string.IsNullOrWhiteSpace(text)) + ThrowException(lipApiUrl); + + var index = LipIndex.Deserialize(text)!; + var pageCount = index.Data.TotalPages; + for (int i = 2; i <= pageCount; ++i) + { + text = await client.GetStringAsync($"https://{lipApiUrl}/search/teeth?&page={i}"); + if (string.IsNullOrWhiteSpace(text)) + ThrowException(lipApiUrl); + + var temp = LipIndex.Deserialize(text)!; + + index.Data.Items.AddRange(temp.Data.Items); + } + + return index; + } + + private void AutoSuggestBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) + { + if (args.Reason is not AutoSuggestionBoxTextChangeReason.UserInput) + return; + + var str = sender.Text.ToLower(); + + Task.Run(async () => + { + try + { + lock (lipIndex!.Data.Items) + { + var dataset = from tooth in lipIndex.Data.Items + where tooth.Name.ToLower().Contains(str) + || tooth.Author.ToLower().Contains(str) + select tooth.Name; + + DispatcherQueue.TryEnqueue(() => sender.ItemsSource = dataset); + } + } + catch (Exception ex) + { + await InternalServices.ShowInfoBarAsync(ex); + } + }); + } + + private void AutoSuggestBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) + => QuerySubmitted(args.QueryText); + + private void QuerySubmitted(string queryText) + => Task.Run(async () => + { + try + { + var query = queryText.ToLower(); + + if (string.IsNullOrWhiteSpace(query)) + { + ReloadLipIndex(); + return; + } + lock (lipIndex!.Data.Items) + { + var selectedTeeth = from tooth in lipIndex.Data.Items + where tooth.Name.ToLower().Contains(query) + || tooth.Author.ToLower().Contains(query) + select tooth; + + ReloadLipIndex(selectedTeeth); + } + } + catch (Exception ex) + { + await InternalServices.ShowInfoBarAsync(ex); + } + }); + + private void AutoSuggestBox_SuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args) + { + + } + + private void FeaturedFilterButton_Checked(object sender, RoutedEventArgs e) + { + + } + + private void FeaturedFilterButton_Unchecked(object sender, RoutedEventArgs e) + { + + } + + private void ToothListView_ItemClick(object sender, ItemClickEventArgs e) + { + var view = e.ClickedItem as LipIndexToothView; + Frame.Navigate(typeof(ToothInfoPage), view!.Tooth); + } + + private void ReloadPackageButton_Click(object sender, RoutedEventArgs e) + { + SuggestBox.Text = null; + ReloadLipIndex(); + } +} diff --git a/src/LipUI/Pages/Index/LipIndexToothView.xaml b/src/LipUI/Pages/Index/LipIndexToothView.xaml new file mode 100644 index 0000000..a7c1d8a --- /dev/null +++ b/src/LipUI/Pages/Index/LipIndexToothView.xaml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/LipUI/Pages/Index/LipIndexToothView.xaml.cs b/src/LipUI/Pages/Index/LipIndexToothView.xaml.cs new file mode 100644 index 0000000..8c0533d --- /dev/null +++ b/src/LipUI/Pages/Index/LipIndexToothView.xaml.cs @@ -0,0 +1,32 @@ +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using System; +using static LipUI.Protocol.LipIndex.LipIndexData; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace LipUI.Pages.Index; + +public sealed partial class LipIndexToothView : UserControl +{ + + public LipToothItem Tooth { get; private set; } + + private readonly Action onClick; + + public LipIndexToothView(LipToothItem tooth, Action authorButtonClickHandler) + { + Tooth = tooth; + + InitializeComponent(); + + ToothName.Text = tooth.Name; + ToothDescription.Text = tooth.Description; + AuthorButtonText.Text = tooth.Author; + onClick = authorButtonClickHandler; + } + + private void AuthorButton_Click(object sender, RoutedEventArgs e) + => onClick(Tooth.Author); +} diff --git a/src/LipUI/Pages/LipExecutionPanel/ConsoleLineView.xaml b/src/LipUI/Pages/LipExecutionPanel/ConsoleLineView.xaml new file mode 100644 index 0000000..f6cf6f5 --- /dev/null +++ b/src/LipUI/Pages/LipExecutionPanel/ConsoleLineView.xaml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/src/LipUI/Pages/LipExecutionPanel/ConsoleLineView.xaml.cs b/src/LipUI/Pages/LipExecutionPanel/ConsoleLineView.xaml.cs new file mode 100644 index 0000000..84413cf --- /dev/null +++ b/src/LipUI/Pages/LipExecutionPanel/ConsoleLineView.xaml.cs @@ -0,0 +1,14 @@ +using Microsoft.UI.Xaml.Controls; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace LipUI.Pages.LipExecutionPanel; + +public sealed partial class ConsoleLineView : UserControl +{ + public ConsoleLineView(string line) + { + InitializeComponent(); + } +} diff --git a/src/LipUI/Pages/LipExecutionPanel/EulaAcceptView.xaml b/src/LipUI/Pages/LipExecutionPanel/EulaAcceptView.xaml new file mode 100644 index 0000000..7287a30 --- /dev/null +++ b/src/LipUI/Pages/LipExecutionPanel/EulaAcceptView.xaml @@ -0,0 +1,26 @@ + + + + + + + + + + + + diff --git a/src/LipUI/Pages/LipExecutionPanel/EulaAcceptView.xaml.cs b/src/LipUI/Pages/LipExecutionPanel/EulaAcceptView.xaml.cs new file mode 100644 index 0000000..2b1dc1d --- /dev/null +++ b/src/LipUI/Pages/LipExecutionPanel/EulaAcceptView.xaml.cs @@ -0,0 +1,15 @@ +using Microsoft.UI.Xaml.Controls; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace LipUI.VIews +{ + public sealed partial class EulaAcceptView : UserControl + { + public EulaAcceptView() + { + this.InitializeComponent(); + } + } +} diff --git a/src/LipUI/Pages/LipExecutionPanel/LipExecutionPanelPage.xaml b/src/LipUI/Pages/LipExecutionPanel/LipExecutionPanelPage.xaml new file mode 100644 index 0000000..a8c9139 --- /dev/null +++ b/src/LipUI/Pages/LipExecutionPanel/LipExecutionPanelPage.xaml @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/LipUI/Pages/LipExecutionPanel/LipExecutionPanelPage.xaml.cs b/src/LipUI/Pages/LipExecutionPanel/LipExecutionPanelPage.xaml.cs new file mode 100644 index 0000000..65923cc --- /dev/null +++ b/src/LipUI/Pages/LipExecutionPanel/LipExecutionPanelPage.xaml.cs @@ -0,0 +1,405 @@ +using LipUI.Models; +using LipUI.Models.Lip; +using LipUI.Protocol; +using Microsoft.UI.Dispatching; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Media; +using Microsoft.UI.Xaml.Navigation; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Diagnostics; +using System.Text.RegularExpressions; +using Windows.UI; +using static LipUI.Models.Lip.LipCommand; +using static LipUI.Models.Lip.LipCommandOption; +using static LipUI.Protocol.LipIndex.LipIndexData; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace LipUI.Pages.LipExecutionPanel; + +/// +/// An empty page that can be used on its own or navigated to within a Frame. +/// +internal sealed partial class LipExecutionPanelPage : Page +{ + private partial class LipConsoleHandler + { + private LipExecutionPanelPage page; + private LipConsole? lip; + private Process? process; + private readonly DispatcherQueue dispatcherQueue; + + private static Style CaptionTextBlockStyle = (Style)Application.Current.Resources["CaptionTextBlockStyle"]; + private static Brush TextFillColorPrimaryBrush = (Brush)Application.Current.Resources["TextFillColorPrimaryBrush"]; + + private readonly List<(Regex, Func)> regexsAndMethods; + + public void Output(string line) + => dispatcherQueue.TryEnqueue(() => page.output.Add(new() + { + Text = line, + Style = CaptionTextBlockStyle, + Foreground = TextFillColorPrimaryBrush + })); + + public void Input(string line, bool output = true) + { + if (output) dispatcherQueue.TryEnqueue(() => page.output.Add(new() + { + Text = line, + Style = CaptionTextBlockStyle, + Foreground = TextFillColorPrimaryBrush + })); + process?.StandardInput.WriteLine(line); + } + + private void OutputHandler(string line) + { + if (string.IsNullOrWhiteSpace(line)) + return; + + bool output = true; + + foreach (var (k, v) in regexsAndMethods) + { + var match = k.Match(line); + if (match.Success) + { + output = v(k, match, line); + break; + } + } + + if (output) Output(line); + } + + public LipConsoleHandler(LipExecutionPanelPage page) + { + this.page = page; + dispatcherQueue = page.DispatcherQueue; + regexsAndMethods = new() + { + (Y_or_N_Regex(), Y_or_N_Matched), + (BDSDownloader_ProgressBar_Regex(), BDSDownloader_ProgressBar_Matched), + (BDSDownloader_StartDownloadOrUnziping_Regex(), BDSDownloader_StartDownloadOrUnziping_Matched), + (BDSDownloader_DownloadComplete_Regex(), BDSDownloader_DownloadComplete_Matched), + (Done_Regex(), Done_Matched), + (Lip_ProgressBar_Regex(), Lip_ProgressBar_Matched), + (Lip_StartDownload_Regex(), Lip_StartDownload_Matched) + }; + } + + public void Run() + { + Action closed = null!; + closed = () => + { + process?.Kill(); + InternalServices.WindowClosed -= closed; + }; + InternalServices.WindowClosed += closed; + + dispatcherQueue.TryEnqueue(async () => + { + lip = await Main.CreateLipConsole(page.XamlRoot); + if (lip is null) + { + var bar = page.TaskProgressBar; + bar.IsIndeterminate = false; + bar.Value = 0; + + page.LipWorkingInfoText.Text = string.Empty; + page.ProgressRateText.Text = string.Empty; + + await InternalServices.ShowInfoBarAsync( + "infobar$error".GetLocalized(), + Main.Config.SelectedServer is null ? + "lipExecution$nullServerPath".GetLocalized() : + "lipExecution$nullLipPath".GetLocalized(), + InfoBarSeverity.Error); + + page.Frame.TryGoBack(); + + return; + } + + + lip.Output += OutputHandler; + lip.OutputError += OutputHandler; + + switch (page.Mode) + { + case ExecutionMode.Install: + + await lip.Run(CreateCommand() + Install + page.ToothItem!.RepoPath, ins => process = ins.Process); + + break; + + case ExecutionMode.Delete: + + await lip.Run(CreateCommand() + Uninstall + page.Package!.Tooth, ins => process = ins.Process); + + break; + + case ExecutionMode.Update: + + await lip.Run(CreateCommand() + Install + Upgrade + page.Package!.Tooth, ins => process = ins.Process); + + break; + } + InternalServices.WindowClosed -= closed; + + }); + } + + + [GeneratedRegex(@"(?.*)\[y/N\]")] + private static partial Regex Y_or_N_Regex(); + + [GeneratedRegex(@"(Downloading|Unziping)\.+\s+(?[0-9]+%)\s+\[(?[\s]+|[=>\s])+\]\s+\((?.*?)\)\s+\[(?[0-9a-z]+):(?[0-9a-z]+)\]")] + private static partial Regex BDSDownloader_ProgressBar_Regex(); + + [GeneratedRegex(@"Downloading BDS (?.*?)\.\.\.|Unziping downloaded files\.\.\.")] + private static partial Regex BDSDownloader_StartDownloadOrUnziping_Regex(); + + [GeneratedRegex("Download complete!|Unzip complete!")] + private static partial Regex BDSDownloader_DownloadComplete_Regex(); + + [GeneratedRegex(@"Done\.|ERROR: aborted")] + private static partial Regex Done_Regex(); + + [GeneratedRegex(@"\s+(?[0-9]+%)\s+\|[|\s]+\|\s+\((?.*?),(?.*?)\)\s+\[(?[0-9a-z]+):(?[0-9a-z]+)\]")] + private static partial Regex Lip_ProgressBar_Regex(); + + [GeneratedRegex(@"Downloading (.*?)\.\.\.")] + private static partial Regex Lip_StartDownload_Regex(); + + + private bool Y_or_N_Matched(Regex regex, Match match, string str) + { + dispatcherQueue.TryEnqueue(async () => + { + ContentDialog contentDialog = new() + { + XamlRoot = page.XamlRoot, + Content = new TextBlock() + { + Text = match.Groups["string"].Value + }, + CloseButtonText = "eula$deny".GetLocalized(), + PrimaryButtonText = "eula$accept".GetLocalized() + }; + + var operation = await contentDialog.ShowAsync(); + switch (operation) + { + case ContentDialogResult.None: Input("N"); break; + case ContentDialogResult.Primary: Input("y"); break; + } + }); + return true; + } + + private bool BDSDownloader_ProgressBar_Matched(Regex regex, Match match, string str) + { + dispatcherQueue.TryEnqueue(() => + { + var percent = int.Parse(match.Groups["percent"].Value[..^1]); + page.TaskProgressBar.Value = percent; + page.ProgressRateText.Text = $"{match.Groups["rate"].Value} {match.Groups["elapsed"].Value} {match.Groups["estimated"].Value}"; + }); + + return false; + } + + private bool BDSDownloader_StartDownloadOrUnziping_Matched(Regex regex, Match match, string str) + { + dispatcherQueue.TryEnqueue(() => + { + var bar = page.TaskProgressBar; + bar.IsIndeterminate = false; + bar.Minimum = 0; + bar.Maximum = 100; + bar.Value = 0; + + page.LipWorkingInfoText.Text = str; + }); + return true; + } + + private bool BDSDownloader_DownloadComplete_Matched(Regex regex, Match match, string str) + { + dispatcherQueue.TryEnqueue(() => + { + var bar = page.TaskProgressBar; + bar.IsIndeterminate = true; + bar.Value = 0; + + page.LipWorkingInfoText.Text = string.Empty; + page.ProgressRateText.Text = string.Empty; + }); + return true; + } + + private bool Done_Matched(Regex regex, Match match, string str) + { + dispatcherQueue.TryEnqueue(() => + { + var bar = page.TaskProgressBar; + bar.IsIndeterminate = false; + bar.Value = 0; + + page.LipWorkingInfoText.Text = string.Empty; + page.ProgressRateText.Text = string.Empty; + }); + return true; + } + + private bool Lip_ProgressBar_Matched(Regex regex, Match match, string str) + { + dispatcherQueue.TryEnqueue(() => + { + var percent = int.Parse(match.Groups["percent"].Value[..^1]); + page.TaskProgressBar.Value = percent; + page.ProgressRateText.Text = $"{match.Groups["progress"].Value} {match.Groups["rate"].Value} {match.Groups["elapsed"].Value} {match.Groups["estimated"].Value}"; + }); + + return false; + } + + private bool Lip_StartDownload_Matched(Regex regex, Match match, string str) + { + dispatcherQueue.TryEnqueue(() => + { + var bar = page.TaskProgressBar; + bar.IsIndeterminate = false; + bar.Minimum = 0; + bar.Maximum = 100; + bar.Value = 0; + + page.LipWorkingInfoText.Text = str; + }); + return true; + } + + } + + public enum ExecutionMode { Install, Delete, Update } + + public record InitArguments(ExecutionMode Mode, object Parameter); + + + private LipTooth? Tooth { get; set; } + + private LipToothItem? ToothItem { get; set; } + + private string? SelectedVersion { get; set; } + + private ToothPackage? Package { get; set; } + + private ExecutionMode Mode { get; set; } + + + + + private readonly ObservableCollection output = new(); + private LipConsoleHandler? handler; + + public LipExecutionPanelPage() + { + InitializeComponent(); + output.CollectionChanged += Output_CollectionChanged; + } + + protected override void OnNavigatedTo(NavigationEventArgs e) + { + var args = (InitArguments)e.Parameter; + Mode = args.Mode; + + var style = (Style)Application.Current.Resources["CaptionTextBlockStyle"]; + var brush = new SolidColorBrush((Color)Application.Current.Resources["TextFillColorSecondary"]); + + switch (args.Mode) + { + case ExecutionMode.Install: + { + var (tooth, toothItem, selectedVersion) = ((LipTooth, LipToothItem, string))args.Parameter; + Tooth = tooth; + ToothItem = toothItem; + SelectedVersion = selectedVersion; + + ToothInfoPanel.Children.Add(new TextBlock() + { + Text = ToothItem.RepoName, + Style = style, + Foreground = brush + }); + ToothInfoPanel.Children.Add(new TextBlock() + { + Text = SelectedVersion, + Style = style, + Foreground = brush + }); + } + break; + case ExecutionMode.Delete: + case ExecutionMode.Update: + { + Package = (ToothPackage)args.Parameter; + + ToothInfoPanel.Children.Add(new TextBlock() + { + Text = Package.Tooth, + Style = style, + Foreground = brush + }); + ToothInfoPanel.Children.Add(new TextBlock() + { + Text = Package.Version, + Style = style, + Foreground = brush + }); + } + break; + } + + base.OnNavigatedTo(e); + } + + private void Output_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + OutputViewer.ChangeView(default, OutputViewer.ScrollableHeight, default); + } + + private void Page_Loaded(object sender, RoutedEventArgs e) + { + handler = new(this); + Title.Text = Mode switch + { + ExecutionMode.Install => ToothItem!.RepoPath, + ExecutionMode.Delete or ExecutionMode.Update => Package!.Tooth, + _ => throw new Exception() + }; + + handler.Run(); + } + + private void TextBox_KeyDown(object sender, KeyRoutedEventArgs e) + { + if (e.Key is Windows.System.VirtualKey.Enter) + { + var box = ((TextBox)sender); + DispatcherQueue.TryEnqueue(() => + { + handler!.Input(box.Text); + box.Text = string.Empty; + }); + } + } +} diff --git a/src/LipUI/Pages/LipExecutionPanel/LipInstallerView.xaml b/src/LipUI/Pages/LipExecutionPanel/LipInstallerView.xaml new file mode 100644 index 0000000..55ee636 --- /dev/null +++ b/src/LipUI/Pages/LipExecutionPanel/LipInstallerView.xaml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/LipUI/Pages/LocalPackage/LocalPackagePage.xaml.cs b/src/LipUI/Pages/LocalPackage/LocalPackagePage.xaml.cs new file mode 100644 index 0000000..b25c144 --- /dev/null +++ b/src/LipUI/Pages/LocalPackage/LocalPackagePage.xaml.cs @@ -0,0 +1,190 @@ +using LipUI.Models; +using LipUI.Models.Lip; +using LipUI.Protocol; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace LipUI.Pages.LocalPackage; + +/// +/// An empty page that can be used on its own or navigated to within a Frame. +/// +public sealed partial class LocalPackagePage : Page +{ + public LocalPackagePage() + { + InitializeComponent(); + } + + private ToothPackage[]? teeth; + private object _lock = new(); + + private void ReloadPackage(IEnumerable? items = null) + { + DispatcherQueue.TryEnqueue(async () => + { + TeethScrollView.Content = new ProgressRing(); + ToothListView.Items.Clear(); + + var lip = await Main.CreateLipConsole(XamlRoot); + + if (lip is null) + { + ((ProgressRing)TeethScrollView.Content).IsActive = false; + return; + } + + var cmd = LipCommand.CreateCommand(); + + + + ToothPackage[]? arr = null; + + if (items is not null) + arr = items.ToArray(); + else + try + { + var json = await lip.RunAndGetString(cmd + LipCommand.List + LipCommandOption.Json); + teeth = arr = JsonSerializer.Deserialize(json); + } + catch (Exception ex) + { + await InternalServices.ShowInfoBarAsync(ex); + } + + if (arr is not null) + { + foreach (var item in arr!) + { + ToothListView.Items.Add(new LocalToothView(this, item)); + } + RefreshUpgradableTeeth(); + } + TeethScrollView.Content = ToothListView; + }); + } + + private void RefreshUpgradableTeeth() + { + DispatcherQueue.TryEnqueue(async () => + { + var lip = await Main.CreateLipConsole(XamlRoot); + if (lip is null) return; + + var cmd = LipCommand.CreateCommand(); + + ToothPackage[] arr; + try + { + var json = await lip.RunAndGetString(cmd + LipCommand.List + LipCommandOption.Upgradable + LipCommandOption.Json); + arr = JsonSerializer.Deserialize(json)!; + } + catch (Exception ex) + { + await InternalServices.ShowInfoBarAsync(ex); + return; + } + + HashSet strings = new(); + + foreach (var tooth in arr) + strings.Add(tooth.Info.Name + tooth.Info.Author + tooth.Version); + + foreach (var i in ToothListView.Items) + { + var item = (LocalToothView)i; + + var tooth = item.Tooth; + + item.RefreshUpdateButton(strings.Contains(tooth.Info.Name + tooth.Info.Author + tooth.Version)); + } + }); + } + + private void Page_Loaded(object sender, RoutedEventArgs e) + => ReloadPackage(); + + private void AutoSuggestBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) + { + if (args.Reason is not AutoSuggestionBoxTextChangeReason.UserInput) + return; + + var str = sender.Text.ToLower(); + + Task.Run(async () => + { + try + { + lock (_lock) + { + var dataset = from tooth in teeth + where tooth.Info.Name.ToLower().Contains(str) + || tooth.Info.Author.ToLower().Contains(str) + select tooth.Info.Name; + + DispatcherQueue.TryEnqueue(() => sender.ItemsSource = dataset); + } + } + catch (Exception ex) + { + await InternalServices.ShowInfoBarAsync(ex); + } + }); + } + + private void AutoSuggestBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) + { + var query = args.QueryText.ToLower(); + Task.Run(async () => + { + try + { + if (string.IsNullOrWhiteSpace(query)) + { + ReloadPackage(); + return; + } + lock (_lock) + { + var selectedTeeth = from tooth in teeth + where tooth.Info.Name.ToLower().Contains(query) + || tooth.Info.Author.ToLower().Contains(query) + select tooth; + + ReloadPackage(selectedTeeth); + } + } + catch (Exception ex) + { + await InternalServices.ShowInfoBarAsync(ex); + } + }); + } + + private void AutoSuggestBox_SuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args) + { + + } + + private void FeaturedFilterButton_Checked(object sender, RoutedEventArgs e) + { + + } + + private void FeaturedFilterButton_Unchecked(object sender, RoutedEventArgs e) + { + + } + + private void ReloadPackageButton_Click(object sender, RoutedEventArgs e) + => ReloadPackage(); +} diff --git a/src/LipUI/Pages/LocalPackage/LocalToothView.xaml b/src/LipUI/Pages/LocalPackage/LocalToothView.xaml new file mode 100644 index 0000000..79a7725 --- /dev/null +++ b/src/LipUI/Pages/LocalPackage/LocalToothView.xaml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/LipUI/Pages/LocalPackage/LocalToothView.xaml.cs b/src/LipUI/Pages/LocalPackage/LocalToothView.xaml.cs new file mode 100644 index 0000000..ca11809 --- /dev/null +++ b/src/LipUI/Pages/LocalPackage/LocalToothView.xaml.cs @@ -0,0 +1,60 @@ +using LipUI.Pages.LipExecutionPanel; +using LipUI.Protocol; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace LipUI.Pages.LocalPackage; + +internal sealed partial class LocalToothView : UserControl +{ + public ToothPackage Tooth { get; private set; } + + private LocalPackagePage page; + + public LocalToothView(LocalPackagePage page, ToothPackage tooth) + { + this.page = page; + Tooth = tooth; + + InitializeComponent(); + + ToothName.Text = tooth.Info.Name; + ToothDescription.Text = tooth.Info.Description; + UpdateButtonText.Text = "localTooth$update".GetLocalized(); + DeleteButtonText.Text = "localTooth$uninstall".GetLocalized(); + + UpdateButton.Content = new ProgressRing() + { + Height = 16, + Width = 16 + }; + } + + public void RefreshUpdateButton(bool enable) => + DispatcherQueue.TryEnqueue(() => + { + UpdateButton.IsEnabled = enable; + UpdateButton.Content = UpdateButtonText; + }); + + private void UpdateButton_Click(object sender, RoutedEventArgs e) + { + page.Frame.Navigate( + typeof(LipExecutionPanelPage), + new LipExecutionPanelPage.InitArguments( + LipExecutionPanelPage.ExecutionMode.Update, + Tooth)); + } + + private void DeleteButton_Click(object sender, RoutedEventArgs e) + { + page.Frame.Navigate( + typeof(LipExecutionPanelPage), + new LipExecutionPanelPage.InitArguments( + LipExecutionPanelPage.ExecutionMode.Delete, + Tooth)); + } +} diff --git a/src/LipUI/Pages/ModuleManager/LipuiPluginsView.xaml b/src/LipUI/Pages/ModuleManager/LipuiPluginsView.xaml new file mode 100644 index 0000000..82f5a11 --- /dev/null +++ b/src/LipUI/Pages/ModuleManager/LipuiPluginsView.xaml @@ -0,0 +1,21 @@ + + + + + + + + + + + + diff --git a/src/LipUI/Pages/ModuleManager/LipuiPluginsView.xaml.cs b/src/LipUI/Pages/ModuleManager/LipuiPluginsView.xaml.cs new file mode 100644 index 0000000..232afb0 --- /dev/null +++ b/src/LipUI/Pages/ModuleManager/LipuiPluginsView.xaml.cs @@ -0,0 +1,157 @@ +using LipUI.Models.Plugin; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media; +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Threading.Tasks; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace LipUI.Pages.ModuleManager; + +public sealed partial class LipuiPluginsView : UserControl +{ + private readonly Style CaptionTextBlockStyle = (Style)Application.Current.Resources["CaptionTextBlockStyle"]; + private readonly Brush TextFillColorPrimaryBrush = (Brush)Application.Current.Resources["TextFillColorPrimaryBrush"]; + private readonly Brush TextFillColorSecondaryBrush = (Brush)Application.Current.Resources["TextFillColorSecondaryBrush"]; + + private readonly IEnumerable plugins; + + public LipuiPluginsView(IEnumerable plugins) + { + InitializeComponent(); + + this.plugins = plugins; + } + + public async ValueTask InitializeUIAsync() + { + + Dictionary> assemblyAndPlugins = new(); + + foreach (var plugin in plugins) + { + try + { + var assembly = plugin.GetType().Assembly; + if (assemblyAndPlugins.TryGetValue(assembly, out var list) is false) + assemblyAndPlugins[assembly] = list = new(); + + list.Add(plugin); + } + catch (Exception ex) + { + await InternalServices.ShowInfoBarAsync(ex); + continue; + } + } + + foreach (var (asm, pluginList) in assemblyAndPlugins) + { + foreach (var plugin in pluginList) + { + try + { + var pluginNameTextBlock = new TextBlock + { + Style = CaptionTextBlockStyle, + Foreground = TextFillColorPrimaryBrush, + Text = plugin.PluginName, + HorizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment = VerticalAlignment.Center + }; + var assemblyNameTextBlock = new TextBlock + { + Style = CaptionTextBlockStyle, + Foreground = TextFillColorSecondaryBrush, + Text = Path.GetFileName(asm.Location), + HorizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment = VerticalAlignment.Center + }; + var assemblyPathTextBlock = new TextBlock + { + Style = CaptionTextBlockStyle, + Foreground = TextFillColorSecondaryBrush, + Text = asm.Location, + HorizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment = VerticalAlignment.Center + }; + var guidTextBlock = new TextBlock + { + Style = CaptionTextBlockStyle, + Foreground = TextFillColorSecondaryBrush, + Text = plugin.Guid.ToString(), + HorizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment = VerticalAlignment.Center + }; + var swich = new ToggleSwitch() + { + IsOn = PluginSystem.IsPluginEnabled(plugin), + HorizontalAlignment = HorizontalAlignment.Right, + VerticalAlignment = VerticalAlignment.Center + }; + swich.Toggled += (sender, e) => + { + if (((ToggleSwitch)sender).IsOn) + PluginSystem.EnablePlugin(plugin); + else + PluginSystem.DisablePlugin(plugin); + }; + Grid.SetColumn(pluginNameTextBlock, 0); + Grid.SetRow(pluginNameTextBlock, 0); + Grid.SetRowSpan(pluginNameTextBlock, 2); + + Grid.SetColumn(assemblyNameTextBlock, 1); + Grid.SetRow(assemblyNameTextBlock, 0); + Grid.SetRowSpan(assemblyNameTextBlock, 2); + + Grid.SetColumn(assemblyPathTextBlock, 2); + Grid.SetRow(assemblyNameTextBlock, 0); + + Grid.SetColumn(guidTextBlock, 2); + Grid.SetRow(guidTextBlock, 1); + + Grid.SetColumn(swich, 3); + Grid.SetRow(swich, 0); + Grid.SetRowSpan(swich, 2); + + var grid = new Grid() + { + RowSpacing = 2, + ColumnSpacing = 2, + RowDefinitions = + { + new() { Height = new(1, GridUnitType.Star) }, + new() { Height = new(1, GridUnitType.Star) } + }, + ColumnDefinitions = + { + new() { Width = new(1, GridUnitType.Star) }, + new() { Width = new(1, GridUnitType.Star) }, + new() { Width = new(4, GridUnitType.Star) }, + new() { Width = new(2, GridUnitType.Star) }, + }, + Children = + { + pluginNameTextBlock, + assemblyNameTextBlock, + assemblyPathTextBlock, + guidTextBlock, + swich + } + }; + PluginsListView.Items.Add(grid); + } + catch (Exception ex) + { + await InternalServices.ShowInfoBarAsync(ex); + continue; + } + } + } + } +} diff --git a/src/LipUI/Pages/ModuleManager/ModuleManagerPage.xaml b/src/LipUI/Pages/ModuleManager/ModuleManagerPage.xaml new file mode 100644 index 0000000..7cd50e4 --- /dev/null +++ b/src/LipUI/Pages/ModuleManager/ModuleManagerPage.xaml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/LipUI/Pages/ModuleManager/ModuleManagerPage.xaml.cs b/src/LipUI/Pages/ModuleManager/ModuleManagerPage.xaml.cs new file mode 100644 index 0000000..55f6bbc --- /dev/null +++ b/src/LipUI/Pages/ModuleManager/ModuleManagerPage.xaml.cs @@ -0,0 +1,96 @@ +using LipUI.Models.Plugin; +using LipUI.Pages.ModuleManager; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using System; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace LipUI.Pages.ToothPack; + +/// +/// An empty page that can be used on its own or navigated to within a Frame. +/// +public sealed partial class ModuleManagerPage : Page +{ + [Flags] + private enum Selection { Plugins, UIPlugins, Modules } + + private static Selection currentSelection = Selection.Plugins; + + public ModuleManagerPage() + { + InitializeComponent(); + } + + private void PluginsButton_Click(object sender, RoutedEventArgs e) + => Plugins_Selection(); + + private void Plugins_Selection() + { + currentSelection = Selection.Plugins; + + if (PluginSystem.Plugins is null) return; + + ModulesTreeView.Content = new ProgressRing(); + DispatcherQueue.TryEnqueue(async () => + { + var view = new LipuiPluginsView(PluginSystem.Plugins); + await view.InitializeUIAsync(); + ModulesTreeView.Content = view; + }); + } + + private void UIPluginsButton_Click(object sender, RoutedEventArgs e) + => UIPlugins_Selection(); + + private void UIPlugins_Selection() + { + currentSelection = Selection.UIPlugins; + + if (PluginSystem.UIPlugins is null) return; + + ModulesTreeView.Content = new ProgressRing(); + DispatcherQueue.TryEnqueue(async () => + { + var view = new LipuiPluginsView(PluginSystem.UIPlugins); + await view.InitializeUIAsync(); + ModulesTreeView.Content = view; + }); + } + + private void ModulesButton_Click(object sender, RoutedEventArgs e) + => Modules_Selection(); + + private void Modules_Selection() + { + currentSelection = Selection.Modules; + + if (PluginSystem.Modules is null) return; + + ModulesTreeView.Content = new ProgressRing(); + DispatcherQueue.TryEnqueue(async () => + { + var view = new LipuiPluginsView(PluginSystem.Modules); + await view.InitializeUIAsync(); + ModulesTreeView.Content = view; + }); + } + + private void Page_Loading(FrameworkElement sender, object args) + { + switch (currentSelection) + { + case Selection.Plugins: + Plugins_Selection(); + break; + case Selection.UIPlugins: + UIPlugins_Selection(); + break; + case Selection.Modules: + Modules_Selection(); + break; + } + } +} diff --git a/src/LipUI/Pages/ServerSelection/ServerInstanceEditView.xaml b/src/LipUI/Pages/ServerSelection/ServerInstanceEditView.xaml new file mode 100644 index 0000000..257a9f0 --- /dev/null +++ b/src/LipUI/Pages/ServerSelection/ServerInstanceEditView.xaml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/LipUI/Pages/ServerSelection/ServerInstanceEditView.xaml.cs b/src/LipUI/Pages/ServerSelection/ServerInstanceEditView.xaml.cs new file mode 100644 index 0000000..7fce9a5 --- /dev/null +++ b/src/LipUI/Pages/ServerSelection/ServerInstanceEditView.xaml.cs @@ -0,0 +1,95 @@ +using LipUI.Models; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media.Imaging; +using System; +using System.IO; +using System.Threading.Tasks; +using Windows.Storage.Pickers; +using WinRT.Interop; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace LipUI.Pages.ServerSelection; + +internal sealed partial class ServerInstanceEditView : UserControl +{ + + + public ServerInstance Server { get; private set; } + + private string? iconPath = null; + + public BitmapImage? CustomIcon { get; private set; } + + public ServerInstanceEditView(ServerInstance server) + { + this.Server = server; + InitializeComponent(); + } + + private async ValueTask RefreshIcon(string? version, string? iconPath) + { + var image = await ServerIcon.GetIcon(version, iconPath); + image.DecodePixelType = DecodePixelType.Logical; + image.DecodePixelHeight = (int)IconImage.Height; + image.DecodePixelWidth = (int)IconImage.Width; + IconImage.Source = image; + CustomIcon = image; + } + + private void UserControl_Loaded(object sender, RoutedEventArgs e) + { + NameInput.Text = Server.Name; + DescriptionInput.Text = Server.Description; + VersionInput.Text = Server.Version; + WorkingDirectoryInput.Text = Server.WorkingDirectory; + + DispatcherQueue.TryEnqueue(async () => await RefreshIcon(Server.Version, Server.Icon)); + } + + private async void VersionInput_TextChanged(object sender, TextChangedEventArgs e) + => await RefreshIcon(VersionInput.Text, iconPath); + + private async void EditIconButton_Click(object sender, RoutedEventArgs e) + { + var picker = new FileOpenPicker() + { + SuggestedStartLocation = PickerLocationId.PicturesLibrary, + FileTypeFilter = { ".jpeg", ".jpg", ".png", ".bmp", ".tiff", ".ico" } + }; + InitializeWithWindow.Initialize(picker, WindowNative.GetWindowHandle((Application.Current as App)!.m_window)); + + var file = await picker.PickSingleFileAsync(); + if (file is not null) + iconPath = file.Path; + + await RefreshIcon(VersionInput.Text, iconPath); + } + + public void CommitServerProperies() + { + Server.Name = NameInput.Text; + Server.Description = DescriptionInput.Text; + Server.Version = VersionInput.Text; + Server.WorkingDirectory = WorkingDirectoryInput.Text; + + if (iconPath is not null) + { + var dir = Path.Combine(Server.WorkingDirectory, DefaultSettings.DataDirectory); + + if (Directory.Exists(dir) is false) + Directory.CreateDirectory(dir); + + var dest = Path.Combine(dir, $"icon{new FileInfo(iconPath).Extension}"); + + File.Copy(iconPath, dest, true); + + Server.Icon = dest; + return; + } + + Server.Icon = iconPath; + } +} diff --git a/src/LipUI/Pages/ServerSelection/ServerInstanceView.xaml b/src/LipUI/Pages/ServerSelection/ServerInstanceView.xaml new file mode 100644 index 0000000..95a1404 --- /dev/null +++ b/src/LipUI/Pages/ServerSelection/ServerInstanceView.xaml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/LipUI/Pages/ServerSelection/ServerInstanceView.xaml.cs b/src/LipUI/Pages/ServerSelection/ServerInstanceView.xaml.cs new file mode 100644 index 0000000..058216b --- /dev/null +++ b/src/LipUI/Pages/ServerSelection/ServerInstanceView.xaml.cs @@ -0,0 +1,39 @@ +using LipUI.Models; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media; +using Microsoft.UI.Xaml.Media.Animation; +using System; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace LipUI.Pages.ServerSelection; + +internal sealed partial class ServerInstanceView : UserControl +{ + public ServerInstance ServerInstance { get; private set; } + + public void SetIconImageSource(ImageSource source) + => Image.Source = source; + + public ServerInstanceView(ServerInstance instance) + { + InitializeComponent(); + ServerInstance = instance; + + RefreshUI(); + + DispatcherQueue.TryEnqueue(async () => + { + Image.Source = await ServerIcon.GetIcon(instance); + }); + } + + public void RefreshUI() + { + Text.Text = $"{ServerInstance.Name}{Environment.NewLine}{ServerInstance.Description}{Environment.NewLine}{ServerInstance.Version}"; + } + + public Storyboard SelectStoryboard => _selectStoryboard; + public Storyboard UnselectStoryboard => _unselectStoryboard; +} diff --git a/src/LipUI/Pages/ServerSelection/ServerSelectionPage.xaml b/src/LipUI/Pages/ServerSelection/ServerSelectionPage.xaml new file mode 100644 index 0000000..687a13a --- /dev/null +++ b/src/LipUI/Pages/ServerSelection/ServerSelectionPage.xaml @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/LipUI/Pages/ServerSelection/ServerSelectionPage.xaml.cs b/src/LipUI/Pages/ServerSelection/ServerSelectionPage.xaml.cs new file mode 100644 index 0000000..c88c465 --- /dev/null +++ b/src/LipUI/Pages/ServerSelection/ServerSelectionPage.xaml.cs @@ -0,0 +1,329 @@ +using LipUI.Models; +using Microsoft.UI; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Media; +using Microsoft.UI.Xaml.Navigation; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using Windows.Storage.Pickers; +using WinRT.Interop; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace LipUI.Pages.ServerSelection; + +/// +/// An empty page that can be used on its own or navigated to within a Frame. +/// +public sealed partial class ServerSelectionPage : Page +{ + private ToggleButton DeleteButton; + private Button ApplyDeleteButton; + + private Action? refreshIcon; + + [Flags] private enum Mode { Select, Edit } + + private Mode ItemClickMode { get; set; } + + private readonly HashSet selectedViews = new(); + + public ServerSelectionPage() + { + InitializeComponent(); + InitButtons(); + } + + [MemberNotNull(nameof(DeleteButton), nameof(ApplyDeleteButton))] + private void InitButtons() + { + DeleteButton = new() + { + Height = 48, + Width = 48, + Background = new SolidColorBrush(Colors.Transparent), + BorderBrush = new SolidColorBrush(Colors.Transparent), + Content = new SymbolIcon() + { + Symbol = Symbol.Delete, + Height = 24, + Width = 24 + }, + }; + DeleteButton.Click += DeleteButton_Click; + + ApplyDeleteButton = new() + { + Height = 48, + Width = 48, + Background = new SolidColorBrush(Colors.Transparent), + BorderBrush = new SolidColorBrush(Colors.Transparent), + Shadow = null, + Content = new SymbolIcon() + { + Symbol = Symbol.Accept, + Height = 24, + Width = 24 + } + }; + ApplyDeleteButton.Click += DeleteApplyButton_Click; + } + + + private void ServerScrollView_Loaded(object sender, RoutedEventArgs e) + { + var progressRing = new ProgressRing() + { + IsIndeterminate = false, + Minimum = 0, + Maximum = Main.Config.ServerInstances.Count, + IsActive = true + }; + ServerScrollView.Content = progressRing; + + DispatcherQueue.TryEnqueue(async () => + { + await Task.Yield(); + + for (int i = 0; i < Main.Config.ServerInstances.Count; i++) + { + ServerGridView.Items.Add(new ServerInstanceView(Main.Config.ServerInstances[i])); + progressRing.Value = i; + } + + ServerScrollView.Content = ServerGridView; + }); + } + + + protected override void OnNavigatedTo(NavigationEventArgs e) + { + refreshIcon = (Action)e.Parameter; + base.OnNavigatedTo(e); + } + + + + private void SwitchMode(Mode clickMode) + { + lock (this) + { + switch (clickMode) + { + case Mode.Select: + if (ItemClickMode is Mode.Edit) + { + DispatcherQueue.TryEnqueue(() => + { + ButtonsPanel.Children.Remove(ApplyDeleteButton); + DeleteButton.IsChecked = false; + ButtonsPanel.Children.Remove(DeleteButton); + }); + foreach (var view in selectedViews) + { + view.UnselectStoryboard.Begin(); + } + selectedViews.Clear(); + } + break; + case Mode.Edit: + { + if (ItemClickMode is Mode.Select) + { + DispatcherQueue.TryEnqueue(() => + { + ButtonsPanel.Children.Add(DeleteButton); + }); + } + } + break; + } + ItemClickMode = clickMode; + } + } + + private async void ServerGridView_ItemClick(object sender, ItemClickEventArgs e) + { + if (e.ClickedItem is Border border && border.Tag as string is "Add") + { + await ItemClick_AddServerViewClicked(sender, e); + } + else + { + switch (ItemClickMode) + { + case Mode.Select: ItemClick_SelectMode(sender, e); return; + case Mode.Edit: ItemClick_EditMode(sender, e); return; + } + } + } + + private async ValueTask ItemClick_AddServerViewClicked(object sender, ItemClickEventArgs e) + { + if (ItemClickMode is not Mode.Select) + { + AddServerCardViewStoryboard.Begin(); + return; + } + + var picker = new FolderPicker() + { + SuggestedStartLocation = PickerLocationId.Desktop, + FileTypeFilter = { "*" } + }; + InitializeWithWindow.Initialize(picker, WindowNative.GetWindowHandle((Application.Current as App)!.m_window)); + + var folder = await picker.PickSingleFolderAsync(); + if (folder is not null) + { + var server = new ServerInstance() + { + WorkingDirectory = folder.Path + }; + + foreach (var instance in Main.Config.ServerInstances) + { + if (instance == server) + { + await InternalServices.ShowInfoBarAsync( + "infoBar$error".GetLocalized(), + "serverSelection$existed".GetLocalized(), + InfoBarSeverity.Error); + + return; + } + } + + Main.Config.ServerInstances.Add(server); + + ServerGridView.DispatcherQueue.TryEnqueue(async () => + { + var view = new ServerInstanceView(server); + ServerGridView.Items.Add(view); + + await Task.Delay(500); + + ShowEditView(view); + }); + } + } + + private async void ItemClick_SelectMode(object sender, ItemClickEventArgs e) + { + Main.Config.SelectedServer = (e.ClickedItem as ServerInstanceView)!.ServerInstance; + refreshIcon!(); + await Main.SaveConfigAsync(); + Frame.TryGoBack(); + } + + private void ItemClick_EditMode(object sender, ItemClickEventArgs e) + { + lock (selectedViews) + { + var view = (ServerInstanceView)e.ClickedItem; + if (DeleteButton.IsChecked!.Value) + { + if (selectedViews.Contains(view)) + { + selectedViews.Remove(view); + view.UnselectStoryboard.Begin(); + } + else if (selectedViews.Add(view)) + { + view.SelectStoryboard.Begin(); + } + } + else + { + ShowEditView(view); + } + } + } + + private void ShowEditView(ServerInstanceView view) + { + var editView = new ServerInstanceEditView(view.ServerInstance); + var dialog = new ContentDialog() + { + XamlRoot = XamlRoot, + Content = editView, + CloseButtonText = "server$editor$cancel".GetLocalized(), + PrimaryButtonText = "server$editor$confirm".GetLocalized() + }; + DispatcherQueue.TryEnqueue(async () => + { + var operation = await dialog.ShowAsync(); + switch (operation) + { + case ContentDialogResult.Primary: + + editView.CommitServerProperies(); + + if (editView.CustomIcon is not null) + view.SetIconImageSource(editView.CustomIcon); + + view.RefreshUI(); + await Main.SaveConfigAsync(); + + return; + case ContentDialogResult.None: + case ContentDialogResult.Secondary: + + if (ItemClickMode is not Mode.Edit) + { + lock (Main.Config) + { + ServerGridView.Items.Remove(view); + Main.Config.ServerInstances.Remove(view.ServerInstance); + if (Main.Config.SelectedServer == view.ServerInstance) + Main.Config.SelectedServer = null; + } + } + return; + } + }); + } + + private void EditButton_Click(object sender, RoutedEventArgs e) + => SwitchMode(EditButton.IsChecked ?? false ? Mode.Edit : Mode.Select); + + private void DeleteButton_Click(object sender, RoutedEventArgs e) + { + if (DeleteButton.IsChecked!.Value) + { + DispatcherQueue.TryEnqueue(() => + { + ButtonsPanel.Children.Add(ApplyDeleteButton); + }); + } + else + { + DispatcherQueue.TryEnqueue(() => + { + ButtonsPanel.Children.Remove(ApplyDeleteButton); + }); + } + } + + private async void DeleteApplyButton_Click(object sender, RoutedEventArgs e) + { + lock (Main.Config) + { + foreach (var view in selectedViews) + { + ServerGridView.Items.Remove(view); + Main.Config.ServerInstances.Remove(view.ServerInstance); + if (Main.Config.SelectedServer == view.ServerInstance) + Main.Config.SelectedServer = null; + } + + refreshIcon!(); + } + await Main.SaveConfigAsync(); + } +} diff --git a/src/LipUI/Pages/Settings/GeneralSettingsView.xaml b/src/LipUI/Pages/Settings/GeneralSettingsView.xaml new file mode 100644 index 0000000..c791d70 --- /dev/null +++ b/src/LipUI/Pages/Settings/GeneralSettingsView.xaml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/LipUI/Pages/Settings/GeneralSettingsView.xaml.cs b/src/LipUI/Pages/Settings/GeneralSettingsView.xaml.cs new file mode 100644 index 0000000..fb54f5c --- /dev/null +++ b/src/LipUI/Pages/Settings/GeneralSettingsView.xaml.cs @@ -0,0 +1,56 @@ +using LipUI.Models; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace LipUI.Pages.Settings; + +internal sealed partial class GeneralSettingsView : UserControl +{ + public GeneralSettingsView() + { + InitializeComponent(); + } + + private void GithubProxyInput_TextChanged(object sender, TextChangedEventArgs e) + { + Main.Config.GeneralSettings.GithubProxy = GithubProxyInput.Text; + } + + private void LipIndexApiInput_TextChanged(object sender, TextChangedEventArgs e) + { + Main.Config.GeneralSettings.LipIndexApiKey = LipIndexApiInput.Text; + } + + private void LipPathInput_TextChanged(object sender, TextChangedEventArgs e) + { + Main.Config.GeneralSettings.LipPath = LipPathInput.Text; + } + + private void GithubApiInput_TextChanged(object sender, TextChangedEventArgs e) + { + Main.Config.GeneralSettings.GithubApiKey = GithubApiInput.Text; + } + + private void GithubProxyInput_Loading(FrameworkElement sender, object args) + { + GithubProxyInput.Text = Main.Config.GeneralSettings.GithubProxy; + } + + private void LipIndexApiInput_Loading(FrameworkElement sender, object args) + { + LipIndexApiInput.Text = Main.Config.GeneralSettings.LipIndexApiKey; + } + + private void GithubApiInput_Loading(FrameworkElement sender, object args) + { + GithubApiInput.Text = Main.Config.GeneralSettings.GithubApiKey; + } + + private void LipPathInput_Loading(FrameworkElement sender, object args) + { + LipPathInput.Text = Main.Config.GeneralSettings.LipPath; + } +} diff --git a/src/LipUI/Pages/Settings/PersonalizationSettingsView.xaml b/src/LipUI/Pages/Settings/PersonalizationSettingsView.xaml new file mode 100644 index 0000000..5227f8b --- /dev/null +++ b/src/LipUI/Pages/Settings/PersonalizationSettingsView.xaml @@ -0,0 +1,288 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/LipUI/Pages/ToothInfoPage.xaml.cs b/src/LipUI/Pages/ToothInfoPage.xaml.cs new file mode 100644 index 0000000..f78f11d --- /dev/null +++ b/src/LipUI/Pages/ToothInfoPage.xaml.cs @@ -0,0 +1,157 @@ +#pragma warning disable CS8632 + +using CommunityToolkit.WinUI.UI.Controls; +using LipUI.Models; +using LipUI.Pages.LipExecutionPanel; +using LipUI.Protocol; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media; +using Microsoft.UI.Xaml.Navigation; +using System; +using System.Net.Http; +using Windows.ApplicationModel.DataTransfer; +using Windows.UI; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace LipUI.Pages; + +/// +/// An empty page that can be used on its own or navigated to within a Frame. +/// +public sealed partial class ToothInfoPage : Page +{ + + private LipIndex.LipIndexData.LipToothItem? toothItem; + + private LipTooth? tooth; + + private LipIndex.LipIndexData.LipToothItem ToothItem + { + get => toothItem!; + set + { + toothItem = value; + + Title.Text = toothItem.Name; + RepoPath.Text = toothItem.RepoPath; + Description.Text = toothItem.Description; + Author.Text = toothItem.Author; + LatestVersion.Text = toothItem.LatestVersion; + LatestReleaseTime.Text + = DateTimeOffset + .FromUnixTimeSeconds(toothItem.LatestVersionReleaseTime) + .LocalDateTime + .ToString(); + DownloadCount.Text = toothItem.DownloadCount.ToString(); + + } + } + + private string ToothRepoUri => $"https://{ToothItem.RepoPath}"; + + public ToothInfoPage() + { + this.InitializeComponent(); + } + + protected override void OnNavigatedTo(NavigationEventArgs e) + { + ToothItem = (e.Parameter as LipIndex.LipIndexData.LipToothItem)!; + base.OnNavigatedTo(e); + } + + private void CopyRepoPathButton_Click(object sender, RoutedEventArgs e) + { + DataPackage package = new() + { + RequestedOperation = DataPackageOperation.Copy + }; + package.SetText(ToothItem.RepoPath); + Clipboard.SetContent(package); + } + + private void MarkdownViewer_Loaded(object sender, RoutedEventArgs e) + { + MarkdownViewer.Content = new ProgressRing(); + + DispatcherQueue.TryEnqueue(async () => + { + try + { + var client = new Octokit.GitHubClient(new Octokit.ProductHeaderValue("lippkg")); + var readme = await client.Repository.Content.GetReadme(ToothItem.RepoOwner, ToothItem.RepoName); + MarkdownViewer.Content = new MarkdownTextBlock() + { + Margin = new(24, 8, 24, 8), + Background = new SolidColorBrush(Color.FromArgb(0, 255, 255, 255)), + Text = readme.Content + }; + } + catch (Octokit.NotFoundException) + { + MarkdownViewer.Content = new TextBlock() + { + Text = "readme$notfound".GetLocalized(), + Foreground = Application.Current.Resources["TextFillColorSecondary"] as SolidColorBrush, + Style = Application.Current.Resources["SubtitleTextBlockStyle"] as Style + }; + } + }); + } + + private void ToothVersionSelectButton_Loaded(object sender, RoutedEventArgs e) + { + ToothVersionSelectButton.Content = new ProgressRing() + { + Height = 16, + Width = 16 + }; + + DispatcherQueue.TryEnqueue(async () => + { + using var client = new HttpClient(); + var dataStr = await client.GetStringAsync( + $"https://{Main.Config.GeneralSettings.LipIndexApiKey}/teeth/{ToothItem.RepoOwner}/{ToothItem.RepoName}/{ToothItem.LatestVersion}"); + + tooth = LipTooth.Deserialize(dataStr); + + var flyout = new MenuFlyout(); + ToothVersionSelectButton.Flyout = flyout; + + foreach (var version in tooth.Data.Versions) + { + var item = new MenuFlyoutItem() { Text = version.Version }; + item.Click += Item_Click; + + flyout.Items.Add(item); + } + + ToothVersionSelectButton.Content = new TextBlock() + { + Text = ToothItem.LatestVersion + }; + }); + } + + private void Item_Click(object sender, RoutedEventArgs e) + { + if (ToothVersionSelectButton.Content is TextBlock textBlock) + { + var item = sender as MenuFlyoutItem; + textBlock.Text = item!.Text; + } + } + + private void InstallButton_Click(object sender, RoutedEventArgs e) + { + if (ToothVersionSelectButton.Content is TextBlock text) + Frame.Navigate( + typeof(LipExecutionPanelPage), + new LipExecutionPanelPage.InitArguments( + LipExecutionPanelPage.ExecutionMode.Install, + (tooth, toothItem, text.Text))); + } +} diff --git a/src/LipUI/Properties/PublishProfiles/win10-arm64.pubxml b/src/LipUI/Properties/PublishProfiles/win10-arm64.pubxml new file mode 100644 index 0000000..a7fdd16 --- /dev/null +++ b/src/LipUI/Properties/PublishProfiles/win10-arm64.pubxml @@ -0,0 +1,20 @@ + + + + + FileSystem + ARM64 + win10-arm64 + bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\ + true + False + False + True + + + \ No newline at end of file diff --git a/src/LipUI/Properties/PublishProfiles/win10-x64.pubxml b/src/LipUI/Properties/PublishProfiles/win10-x64.pubxml new file mode 100644 index 0000000..26ea7e5 --- /dev/null +++ b/src/LipUI/Properties/PublishProfiles/win10-x64.pubxml @@ -0,0 +1,20 @@ + + + + + FileSystem + x64 + win10-x64 + bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\ + true + False + False + True + + + \ No newline at end of file diff --git a/src/LipUI/Properties/PublishProfiles/win10-x86.pubxml b/src/LipUI/Properties/PublishProfiles/win10-x86.pubxml new file mode 100644 index 0000000..34d14d4 --- /dev/null +++ b/src/LipUI/Properties/PublishProfiles/win10-x86.pubxml @@ -0,0 +1,20 @@ + + + + + FileSystem + x86 + win10-x86 + bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\ + true + False + False + True + + + \ No newline at end of file diff --git a/src/LipUI/Properties/launchSettings.json b/src/LipUI/Properties/launchSettings.json new file mode 100644 index 0000000..f625f14 --- /dev/null +++ b/src/LipUI/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "profiles": { + "LipUI (Package)": { + "commandName": "MsixPackage" + }, + "LipUI (Unpackaged)": { + "commandName": "Project" + } + } +} \ No newline at end of file diff --git a/src/LipUI/Protocol/LipIndex.cs b/src/LipUI/Protocol/LipIndex.cs new file mode 100644 index 0000000..02aa0bd --- /dev/null +++ b/src/LipUI/Protocol/LipIndex.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace LipUI.Protocol; + +public class LipIndex +{ + [JsonPropertyName("apiVersion")] + public string ApiVersion { get; set; } = string.Empty; + + [JsonPropertyName("data")] + public LipIndexData Data { get; set; } = new(); + + public class LipIndexData + { + [JsonPropertyName("pageIndex")] + public int PageIndex { get; set; } + + [JsonPropertyName("totalPages")] + public int TotalPages { get; set; } + + [JsonPropertyName("items")] + public List Items { get; set; } = new List(); + + public class LipToothItem + { + [JsonPropertyName("toothRepoPath")] + public string RepoPath { get; set; } = string.Empty; + + [JsonPropertyName("toothRepoOwner")] + public string RepoOwner { get; set; } = string.Empty; + + [JsonPropertyName("toothRepoName")] + public string RepoName { get; set; } = string.Empty; + + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; + + [JsonPropertyName("author")] + public string Author { get; set; } = string.Empty; + + [JsonPropertyName("tags")] + public IReadOnlyList Tags { get; set; } = new List(); + + [JsonPropertyName("latestVersion")] + public string LatestVersion { get; set; } = string.Empty; + + [JsonPropertyName("latestVersionReleaseTime")] + public long LatestVersionReleaseTime { get; set; } + + [JsonPropertyName("downloadCount")] + public int DownloadCount { get; set; } + } + } + + public static LipIndex Deserialize(string json) + => JsonSerializer.Deserialize(json) ?? throw new NullReferenceException(); +} \ No newline at end of file diff --git a/src/LipUI/Protocol/LipTooth.cs b/src/LipUI/Protocol/LipTooth.cs new file mode 100644 index 0000000..d5e4c68 --- /dev/null +++ b/src/LipUI/Protocol/LipTooth.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace LipUI.Protocol; + +public class LipTooth +{ + [JsonPropertyName("apiVersion")] + public string ApiVersion { get; set; } = string.Empty; + + [JsonPropertyName("data")] + public LipToothData Data { get; set; } = new(); + + public class LipToothData + { + [JsonPropertyName("toothRepoOwner")] + public string ToothRepoOwner { get; set; } = string.Empty; + + [JsonPropertyName("toothRepoName")] + public string ToothRepoName { get; set; } = string.Empty; + + [JsonPropertyName("version")] + public string Version { get; set; } = string.Empty; + + [JsonPropertyName("releaseTime")] + public long ReleaseTime { get; set; } + + [JsonPropertyName("versions")] + public IReadOnlyList Versions { get; set; } = new List(); + + [JsonPropertyName("downloadCount")] + public int DownloadCount { get; set; } + + public struct LipToothVersion + { + [JsonPropertyName("version")] + public string Version { get; set; } + + [JsonPropertyName("releaseTime")] + public long ReleaseTime { get; set; } + } + } + + public static LipTooth Deserialize(string json) + => JsonSerializer.Deserialize(json) ?? throw new NullReferenceException(); +} diff --git a/src/LipUI/Protocol/LocalToothItem.cs b/src/LipUI/Protocol/LocalToothItem.cs new file mode 100644 index 0000000..782cf28 --- /dev/null +++ b/src/LipUI/Protocol/LocalToothItem.cs @@ -0,0 +1,94 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LipUI.Protocol; + +internal class ToothPackage +{ + public class ToothPackageInfo + { + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; + + [JsonPropertyName("author")] + public string Author { get; set; } = string.Empty; + + [JsonPropertyName("tags")] + public List Tags { get; set; } = new(); + } + + public class ToothPackageFiles + { + [JsonPropertyName("place")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public List? Place { get; set; } + + [JsonPropertyName("preserve")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public List? Preserve { get; set; } + + [JsonPropertyName("remove")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public List? Remove { get; set; } + } + + public class Place + { + [JsonPropertyName("src")] + public string Src { get; set; } = string.Empty; + + [JsonPropertyName("dest")] + public string Dest { get; set; } = string.Empty; + } + + public class ToothPackageCommands + { + [JsonPropertyName("pre_install")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public List? PreInstall { get; set; } + + [JsonPropertyName("post_install")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public List? PostInstall { get; set; } + + [JsonPropertyName("pre_uninstall")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public List? PreUninstall { get; set; } + + [JsonPropertyName("post_uninstall")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public List? PostUninstall { get; set; } + } + + + [JsonPropertyName("format_version")] + public int FormatVersion { get; set; } + + [JsonPropertyName("tooth")] + public string Tooth { get; set; } = string.Empty; + + [JsonPropertyName("version")] + public string Version { get; set; } = string.Empty; + + [JsonPropertyName("info")] + public ToothPackageInfo Info { get; set; } = new(); + + [JsonPropertyName("commands")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public ToothPackageCommands? Commands { get; set; } + + [JsonPropertyName("dependencies")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public Dictionary? Dependencies { get; set; } + + [JsonPropertyName("prerequisites")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public Dictionary? Prerequisites { get; set; } + + [JsonPropertyName("files")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public ToothPackageFiles? Files { get; set; } +} diff --git a/src/LipUI/Services/ApplicationHostService.cs b/src/LipUI/Services/ApplicationHostService.cs deleted file mode 100644 index 9d5c215..0000000 --- a/src/LipUI/Services/ApplicationHostService.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Windows; -using LipUI.Views.Pages; -using LipUI.Views.Windows; -using Microsoft.Extensions.Hosting; -using Wpf.Ui.Mvvm.Contracts; - -namespace LipUI.Services -{ - /// - /// Managed host of the application. - /// - public class ApplicationHostService : IHostedService - { - private readonly IServiceProvider _serviceProvider; - private INavigationWindow _navigationWindow; -#pragma warning disable CS8618 - public ApplicationHostService(IServiceProvider serviceProvider) -#pragma warning restore CS8618 - { - _serviceProvider = serviceProvider; - } - - /// - /// Triggered when the application host is ready to start the service. - /// - /// Indicates that the start process has been aborted. - public async Task StartAsync(CancellationToken cancellationToken) - { - await HandleActivationAsync(); - } - - /// - /// Triggered when the application host is performing a graceful shutdown. - /// - /// Indicates that the shutdown process should no longer be graceful. - public async Task StopAsync(CancellationToken cancellationToken) - { - await Task.CompletedTask; - } - - /// - /// Creates main window during activation. - /// - private async Task HandleActivationAsync() - { - await Task.CompletedTask; - - if (!Application.Current.Windows.OfType().Any()) - { - _navigationWindow = (_serviceProvider.GetService(typeof(INavigationWindow)) as INavigationWindow)!; - _navigationWindow!.ShowWindow(); - _navigationWindow.Navigate(typeof(DashboardPage)); - } - - await Task.CompletedTask; - } - } -} diff --git a/src/LipUI/Services/PageService.cs b/src/LipUI/Services/PageService.cs deleted file mode 100644 index bb0b7ab..0000000 --- a/src/LipUI/Services/PageService.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Windows; -using Wpf.Ui.Mvvm.Contracts; - -namespace LipUI.Services -{ - /// - /// Service that provides pages for navigation. - /// - public class PageService : IPageService - { - /// - /// Service which provides the instances of pages. - /// - private readonly IServiceProvider _serviceProvider; - - /// - /// Creates new instance and attaches the . - /// - public PageService(IServiceProvider serviceProvider) - { - _serviceProvider = serviceProvider; - } - - /// - public T? GetPage() where T : class - { - if (!typeof(FrameworkElement).IsAssignableFrom(typeof(T))) - throw new InvalidOperationException("The page should be a WPF control."); - - return (T?)_serviceProvider.GetService(typeof(T)); - } - - /// - public FrameworkElement? GetPage(Type pageType) - { - if (!typeof(FrameworkElement).IsAssignableFrom(pageType)) - throw new InvalidOperationException("The page should be a WPF control."); - - return _serviceProvider.GetService(pageType) as FrameworkElement; - } - } -} diff --git a/src/LipUI/ViewModels/DashboardViewModel.cs b/src/LipUI/ViewModels/DashboardViewModel.cs deleted file mode 100644 index 764c26c..0000000 --- a/src/LipUI/ViewModels/DashboardViewModel.cs +++ /dev/null @@ -1,29 +0,0 @@ -using CommunityToolkit.Mvvm.ComponentModel; -using System.ComponentModel; -using System.Diagnostics; -using Wpf.Ui.Common.Interfaces; - -namespace LipUI.ViewModels; -public partial class DashboardViewModel : ObservableObject, INavigationAware -{ - [ObservableProperty] - WorkingPathSelectorViewModel _selector = new() { NoEdit = true }; - public void OnNavigatedTo() - { - LocalRef.OnNavigatedTo(); - Global.Config.PropertyChanged += OnConfigOnPropertyChanged; - } - void OnConfigOnPropertyChanged(object s, PropertyChangedEventArgs e) - { - Debug.WriteLine(e.PropertyName); - if (e.PropertyName == nameof(Global.Config.WorkingDirectory)) - { - LocalRef.OnNavigatedTo(); - } - } - public void OnNavigatedFrom() - { - Global.Config.PropertyChanged -= OnConfigOnPropertyChanged; - } - [ObservableProperty] ToothLocalModel _localRef = new(); -} \ No newline at end of file diff --git a/src/LipUI/ViewModels/DeveloperPageViewModel.cs b/src/LipUI/ViewModels/DeveloperPageViewModel.cs deleted file mode 100644 index 7a8bf27..0000000 --- a/src/LipUI/ViewModels/DeveloperPageViewModel.cs +++ /dev/null @@ -1,23 +0,0 @@ -using CommunityToolkit.Mvvm.ComponentModel; -using LipUI.Models; -using Wpf.Ui.Common.Interfaces; - -namespace LipUI.ViewModels -{ - public partial class DeveloperPageViewModel : ObservableObject, INavigationAware - { - public void OnNavigatedTo() - { - } - public void OnNavigatedFrom() - { - } - - [ObservableProperty] - private ToothJsonViewModel _toothJson = new(new() - { - - }); - public AppConfig GlobalConfig => Global.Config; - } -} diff --git a/src/LipUI/ViewModels/InstallPageViewModel.cs b/src/LipUI/ViewModels/InstallPageViewModel.cs deleted file mode 100644 index 0b6c6d1..0000000 --- a/src/LipUI/ViewModels/InstallPageViewModel.cs +++ /dev/null @@ -1,285 +0,0 @@ -using System; -using System.Collections.ObjectModel; -using System.Diagnostics; -using System.Globalization; -using System.Linq; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using LipUI.Models; -using Wpf.Ui.Common.Interfaces; -using Wpf.Ui.Controls; - -namespace LipUI.ViewModels -{ - public record InstallInfo(string Tooth, ToothInfoPanelViewModel data, string? Version); - public partial class InstallPageViewModel : ObservableObject, INavigationAware - { - [ObservableProperty] - ObservableCollection _outPut = new(); - [ObservableProperty] - string _toothName = string.Empty; - partial void OnToothNameChanged(string value) - { - ToothInfoPanel = null; - OutPut.Clear(); - } - [NotifyCanExecuteChangedFor(nameof(InstallCommand))] - [NotifyPropertyChangedFor(nameof(InfoLoaded))] - [ObservableProperty] - ToothInfoPanelViewModel? _toothInfoPanel; - public bool InfoLoaded => ToothInfoPanel is not null; - [ObservableProperty] private bool _installing; - /// - /// 执行安装 - /// - [RelayCommand(CanExecute = nameof(InfoLoaded))] - public async Task Install() - { - OutPut.Clear(); - Ctk = new CancellationTokenSource(); - Installing = true; - try - { - var fullname = ToothName; - if (!string.IsNullOrWhiteSpace(SelectedVersion)) - fullname += "@" + SelectedVersion; - var exitCode = await Global.Lip.InstallPackageAsync(fullname, !string.IsNullOrWhiteSpace(ToothInfoPanel?.Version), IsSkipDependency && GlobalConfig.DeveloperMode, Ctk.Token, (x, input) => - { - if (!string.IsNullOrWhiteSpace(x)) - { - if (x.EndsWith("[y/N]", true, CultureInfo.InvariantCulture))//条款 - { - Task.Delay(1000).ContinueWith(async _ => - { - var fullEula = - //string.Join(Environment.NewLine, OutPut) - OutPut.Last() - .Replace("(http", Environment.NewLine + "http"); - //.Replace("http", Environment.NewLine + "http"); - if (fullEula.EndsWith("[y/N]", true, CultureInfo.InvariantCulture)) - { - //remove - fullEula = fullEula[..^5].Trim(); - } - if (fullEula.EndsWith(")", true, CultureInfo.InvariantCulture)) - { - //remove - fullEula = fullEula[..^1]; - } - ////remove str before === - //{ - // var index = fullEula.IndexOf("\r\n", StringComparison.Ordinal); - // if (index != -1) { fullEula = fullEula[(index + 8)..]; } - //} - _ = Global.ShowDialog(Global.I18N.InstallYNDialog, await Global.DispatcherInvokeAsync(() => - { - try - { - StackPanel content = new() { Margin = new(0, 20, 0, 0) }; - //foreach (var data in fullEula.Split(new[] { "https" }, StringSplitOptions.None)) - //{ - // content.Children.Add(new TextBlock() { Text = data }); - //} - //highlight all http url in eula - while (Regex.Match(fullEula, @"https?://[^\s]+") is { Success: true } match) - { - var text = match.Value; - var index = match.Index; - var length = match.Length; - var before = fullEula[..index]; - var after = fullEula[(index + length)..]; - var link = new Hyperlink { NavigateUri = text, Content = text }; - link.Click += (sender, e) => { Process.Start(text); }; - content.Children.Add(new TextBlock { Text = before }); - content.Children.Add(link); - fullEula = after; - } - content.Children.Add(new TextBlock { Text = fullEula }); - return content; - } - catch - { - return new StackPanel - { - Children = - { - new TextBlock - { - Text = fullEula, - TextWrapping = TextWrapping.WrapWithOverflow - } - } - }; - } - }), (Global.I18N.InstallYNDialogDeny, hide => - { - hide(); - Global.PopupSnackbarWarn(Global.I18N.InstallYNCanceledTitle, Global.I18N.InstallYNCanceled); - Task.Delay(1000).ContinueWith(_ => - { - input("n"); - }); - } - ), (Global.I18N.InstallYNDialogGrant, hide => - { - hide(); - input("y"); - } - ), modify: dialog => - { - Global.DispatcherInvoke(() => - { - dialog.DialogHeight = 300; - dialog.DialogWidth = 500; - }); - }); - }); - Global.DispatcherInvoke(() => OutPut.Add(x)); - } - else if (Regex.Match(x, @"(?:Downloading|Unziping).+?(\d+%.*)") is { Success: true } match) - { - var str = match.Groups[1].Value; - //去除匹配并去除"[==> ] " - Percentage = Regex.Replace(str, @"\[(=|>|\s)+?\]", ""); - } - // 79% |███████████████████████████████ | (101/126 kB, 151 kB/s) [0s:0s] - else if (Regex.Match(x, @"(\d+%)") is { Success: true }) - { - Percentage = Regex.Replace(x, @"\|(█|\s)+\|", "").Trim(); - } - else if (x.Trim().EndsWith("|")) - Percentage = x.Replace("|", "").Trim(); - else - { - Global.DispatcherInvoke(() => OutPut.Add(x)); - if (x.StartsWith("Successfully installed all tooth files")) - { - Global.PopupSnackbar(Global.I18N.InstallSuccessTitle, Global.I18N.InstallSuccess); - } - else if (x.StartsWith("[Info] Generating BDS Please wait for few minutes")) - { - Percentage = Global.I18N.InstallGeneratingBDSLib; - } - else if (x.StartsWith("Error", true, CultureInfo.InvariantCulture)) - { - Global.PopupSnackbarWarn(Global.I18N.InstallErrorTitle, x[6..]); - } - } - } - }); - OutPut.Add($"ExitCode:{exitCode}"); - } - catch (Exception ex) - { - OutPut.Add(ex.ToString()); - } - Ctk = null; - Installing = false; - Percentage = ""; - } - [RelayCommand] - public async Task FetchInfo() - { - OutPut.Clear(); - Ctk = new CancellationTokenSource(); - try - { - var (success, versions, message) = await Global.Lip.GetPackageInfoAsync(ToothName, Ctk?.Token ?? default, x => - { - if (!x.StartsWith("{")) - { - Global.DispatcherInvoke(() => OutPut.Add(x)); - } - }); - if (success) - { - if (ToothInfoPanel?.Tooth != ToothName) - { - ToothInfoPanel = new ToothInfoPanelViewModel(versions!) - { - Tooth = ToothName - }; - } - else - { - ToothInfoPanel.RefreshVersion(versions!); - if (versions?.FirstOrDefault() is not null and var v) - { - SelectedVersion = v; - } - } - } - else - { - Global.PopupSnackbarWarn(Global.I18N.InstallFetchFailed, message); - } - } - catch (Exception ex) - { - OutPut.Add(ex.ToString()); - } - Ctk = null; - } - [ObservableProperty] - [NotifyPropertyChangedFor(nameof(CanCancel))] - [NotifyCanExecuteChangedFor(nameof(CancelCommand))] - CancellationTokenSource? _ctk; - [ObservableProperty] - string? _selectedVersion; - [ObservableProperty] - string _percentage = string.Empty; - partial void OnPercentageChanged(string value) - { - try - { - if (Regex.Match(value, @"(\d+?(?:\.\d+?)?)%") is { Success: true } match - && double.TryParse(match.Groups[1].Value.Trim(), out var pn)) - { - _tmpPercentageNumber = pn; - } - else - { - _tmpPercentageNumber = 0; - } - } - catch - { - _tmpPercentageNumber = 0; - } - OnPropertyChanged(nameof(PercentageIsIndeterminate)); - OnPropertyChanged(nameof(PercentageNumber)); - } - private double _tmpPercentageNumber; - public double PercentageNumber => PercentageIsIndeterminate ? 80 : _tmpPercentageNumber; - public bool PercentageIsIndeterminate => _tmpPercentageNumber is <= 0 or >= 100; - public bool CanCancel => Ctk is not null; - public AppConfig GlobalConfig => Global.Config; - - [ObservableProperty] private bool _isSkipDependency; - [RelayCommand(CanExecute = nameof(CanCancel))] - public void Cancel() - { - Ctk?.Cancel(); - Ctk = null; - } - public async void OnNavigatedTo() - { - if (Global.TryDequeueItem(out var item)) - { - ToothName = item.Tooth; - ToothInfoPanel = item.data; - SelectedVersion = item.Version ?? item.data.Versions?.FirstOrDefault(); - await FetchInfo(); - } - } - public void OnNavigatedFrom() - { - } - - } -} diff --git a/src/LipUI/ViewModels/LanguageSelectorViewModel.cs b/src/LipUI/ViewModels/LanguageSelectorViewModel.cs deleted file mode 100644 index 36c7344..0000000 --- a/src/LipUI/ViewModels/LanguageSelectorViewModel.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Linq; -using CommunityToolkit.Mvvm.ComponentModel; -using LipUI.Language; -using LipUI.Models; - -namespace LipUI.ViewModels; -public class LanguageSelectorViewModel : ObservableObject -{ - public AppConfig Config => Global.Config; - public Utils.LanguageDescriptionItem[] AvailableLanguages => Language.Utils.AvailableLanguages; - public Utils.LanguageDescriptionItem? CurrentLanguage - { - get - { - return AvailableLanguages.FirstOrDefault(x => x.Id == Config.Language); - } - set - { - if (value is not null) - { - Utils.CurrentLangId = value.Id; - } - } - } -} diff --git a/src/LipUI/ViewModels/LipInstallerViewModel.cs b/src/LipUI/ViewModels/LipInstallerViewModel.cs deleted file mode 100644 index 1f5303a..0000000 --- a/src/LipUI/ViewModels/LipInstallerViewModel.cs +++ /dev/null @@ -1,18 +0,0 @@ -using CommunityToolkit.Mvvm.ComponentModel; - -namespace LipUI.ViewModels -{ - public partial class LipInstallerViewModel : ObservableObject - { - [ObservableProperty] bool _manualExe; - [ObservableProperty] bool _portableExe = true; - [ObservableProperty] bool _globalExe; - [ObservableProperty] string _lipPath = string.Empty; - [NotifyPropertyChangedFor(nameof(CurrentLipPath))] - [ObservableProperty] string _tip = string.Empty; - - [ObservableProperty] bool _showProgressBar; - [ObservableProperty] double _progress; - public string CurrentLipPath => Global.Lip.ExecutablePath; - } -} diff --git a/src/LipUI/ViewModels/LipRegistryPageViewModel.cs b/src/LipUI/ViewModels/LipRegistryPageViewModel.cs deleted file mode 100644 index b34634d..0000000 --- a/src/LipUI/ViewModels/LipRegistryPageViewModel.cs +++ /dev/null @@ -1,292 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using LipUI.Views.Pages; -using Wpf.Ui.Common.Interfaces; - -namespace LipUI.ViewModels; -public partial class LipRegistryPageViewModel : ObservableRecipient, INavigationAware -{ - public LipRegistryPageViewModel() - { - ToothItems.CollectionChanged += (_, _) => - { - RefreshQuery(); - //OnPropertyChanged(nameof(VisibleToothItems)); - }; - } - private bool _isInitialized; - [ObservableProperty] - ToothInfoPanelViewModel? _currentInfo; - [ObservableProperty] - ToothItemViewModel? _currentSelected; - [ObservableProperty] - bool _isShowingDetail; - [ObservableProperty] - //[NotifyPropertyChangedFor(nameof(VisibleToothItems))] - ObservableCollection _toothItems = new(); - partial void OnToothItemsChanged(ObservableCollection value) - { - RefreshQuery(); - } - private bool waitingRefreshQuery; - [ObservableProperty] bool _isSearching; - int _searchingCount; - - async void RefreshQuery() - { - IEnumerable RefreshQueryInternal() - { - if (string.IsNullOrWhiteSpace(SearchText) && !OnlyFeatured) - return ToothItems; - var searchLower = Regex.Replace(SearchText.ToLower(), @"\[[\w-_]+?\]", ""); - var tags = Array.Empty(); - try - { - tags = ( - from x in Regex.Matches(SearchText, @"\[([\w-_]+?)\]").Cast() - select x.Groups[1].Value).Concat(OnlyFeatured ? new[] { "featured" } : Array.Empty()) - .ToArray(); - } - catch (Exception e) - { - Global.PopupSnackbarWarn(Global.I18N.RegistrySearchTagFailed, e.Message); - } - - var items = ToothItems.Where(x => - x.Tooth.ToLower().Contains(searchLower) || - (x.RegistryItem != null && ( - x.RegistryItem.Description.ToLower().Contains(searchLower) || - x.RegistryItem.License.ToLower().Contains(searchLower) || - x.RegistryItem.Homepage.ToLower().Contains(searchLower) || - x.RegistryItem.Name.ToLower().Contains(searchLower) || - x.RegistryItem.Repository.ToLower().Contains(searchLower) || - x.RegistryItem.Tooth.ToLower().Contains(searchLower) || - x.RegistryItem.Author.ToLower().Contains(searchLower))) - ); - if (tags.Length > 0) - items = from x in items - where tags.All(t => x.Author.Tag == t || x.Tags.Any(y => y.Tag == t)) - select x; - var result = items.ToArray(); - return result; - } - try - { - IsSearching = true; - _searchingCount++; - do - { - await Task.Delay(5); - } while (waitingRefreshQuery); - - waitingRefreshQuery = true; - var all = RefreshQueryInternal(); - //remove the items VisibleToothItems don't have - var allItems = all as ToothItemViewModel[] ?? all.ToArray(); - var toRemove = VisibleToothItems.Except(allItems).ToArray(); - foreach (var item in toRemove) - { - item.Actived = false; - //await Task.Delay(50); - } - //add the items VisibleToothItems don't have - foreach (var item in allItems.Except(VisibleToothItems).ToArray()) - { - VisibleToothItems.Add(item); - item.Actived = true; - await Task.Delay(10); - } - if (toRemove.Any()) - { - await Task.Delay(2000); - foreach (var item in toRemove) - { - VisibleToothItems.Remove(item); - await Task.Delay(10); - } - } - } - catch (Exception e) - { - Global.PopupSnackbarWarn(Global.I18N.RegistryTipRefreshFailed, e.Message); - } - finally - { - waitingRefreshQuery = false; - _searchingCount--; - if (_searchingCount == 0) - { - IsSearching = false; - } - } - } - - [ObservableProperty] ObservableCollection _visibleToothItems = new(); - [RelayCommand] - void InvokeSearch() - { - //OnPropertyChanged(nameof(VisibleToothItems)); - RefreshQuery(); - } - [ObservableProperty] - bool _loading = true; - public void OnNavigatedTo() - { - if (!_isInitialized) - InitializeViewModel(); - } - public void OnNavigatedFrom() - { - } - //[NotifyPropertyChangedFor(nameof(VisibleToothItems))] - [ObservableProperty] string _searchText = string.Empty; - DateTime _lastSearch = DateTime.Now;//上次执行搜索 - partial void OnSearchTextChanged(string value)//输入变动 - {//1秒的时间间隔是为了避免频繁刷新导致界面卡顿 - void TrySearch()//执行搜索 - { - //do not invoke if the last search is in 1 second - var now = DateTime.Now; - if (now - _lastSearch < TimeSpan.FromSeconds(1)) - { - //delay for 1 second to invoke search - Task.Run(async () => - { - await Task.Delay(1000); - await Global.DispatcherInvokeAsync(() => - { - var now = DateTime.Now; - //这1秒内没搜索,执行一次搜索 - if (now - _lastSearch > TimeSpan.FromSeconds(1)) - { - InvokeSearch(); - _lastSearch = now; - } - }); - }); - return; - } - _lastSearch = now; - InvokeSearch(); - } - - TrySearch(); - } - - public string RegistryHub - { - get - { - var args = Environment.GetCommandLineArgs().ToList(); - //using LipUI.exe -r http://xxx.xxx.xxx/registry.json - //or --registry - var index = args.FindIndex(x => x is "-r" or "--registry"); - if (index != -1 && args.Count > index + 1) - { - return args[index + 1]; - } - return "https://registry.litebds.com/index.json"; - } - } - //[NotifyPropertyChangedFor(nameof(VisibleToothItems))] - [ObservableProperty] bool _onlyFeatured; - partial void OnOnlyFeaturedChanged(bool value) - { - //RefreshQuery(); - AddTag(new object[] { "featured", value }); - } - [RelayCommand] - protected async Task LoadAllPackages() - { - try - { - await Global.DispatcherInvokeAsync(() => - { - IsLoading = true; - LoadingOutPut.Clear(); - LoadingOutPut.Add(Global.I18N.RegistryTipFetchAll); - LoadingOutPut.Add(RegistryHub); - ToothItems.Clear(); - }); - var registry = await Global.Lip.GetLipRegistryAsync(RegistryHub); - - await Global.DispatcherInvokeAsync(() => - { - IsLoading = false; - }); - foreach (var item in registry.Index) - { - await Global.DispatcherInvokeAsync(() => - ToothItems.Add(new ToothItemViewModel(ShowInfo, item.Value))); - } - } - catch (Exception e) - { - Global.PopupSnackbarWarn(Global.I18N.RegistryTipFetchFailed, e.Message); - } - } - protected void InitializeViewModel() - { - Task.Run(LoadAllPackages); //初始化加载所有包 - _isInitialized = true; - } - protected async Task ShowInfo(ToothItemViewModel toothItem) - { - CurrentSelected = toothItem; - var (success, versions, message) = await Global.Lip.GetPackageInfoAsync(toothItem.Tooth); - if (success) - { - if (toothItem.RegistryItem is not null) - CurrentInfo = new ToothInfoPanelViewModel(versions!, toothItem.RegistryItem); - } - else - { - Global.PopupSnackbarWarn(Global.I18N.RegistryTipFetchFailed, message); - } - IsShowingDetail = true; - } - [ObservableProperty] bool _isLoading; - [ObservableProperty] ObservableCollection _loadingOutPut = new(); - [RelayCommand] - void GotoInstall() - { - if (CurrentInfo != null) - Global.EnqueueItem(new InstallInfo(CurrentInfo.Tooth, CurrentInfo, CurrentInfo.SelectedVersion)); - Global.Navigate(); - } - - [RelayCommand] - void AddTag(object[] value) - { - if (value is [string v, bool isChecked]) - { - if (v == "featured") - { - OnlyFeatured = isChecked; - } - var item = "[" + v + "]"; - if (isChecked) - { - SearchText = item + SearchText.Replace(item, ""); - foreach (var element in ToothItems) - foreach (var tag in element.Tags.Concat(new[] { element.Author })) - if (tag.Tag == v) - tag.IsSelected = true; - } - else - { - SearchText = SearchText.Replace(item, ""); - foreach (var element in ToothItems) - foreach (var tag in element.Tags.Concat(new[] { element.Author })) - if (tag.Tag == v) - tag.IsSelected = false; - } - } - } -} \ No newline at end of file diff --git a/src/LipUI/ViewModels/LipWebPageViewModel.cs b/src/LipUI/ViewModels/LipWebPageViewModel.cs deleted file mode 100644 index 798fda6..0000000 --- a/src/LipUI/ViewModels/LipWebPageViewModel.cs +++ /dev/null @@ -1,13 +0,0 @@ -using CommunityToolkit.Mvvm.ComponentModel; -using Wpf.Ui.Common.Interfaces; - -namespace LipUI.ViewModels; -public partial class LipWebPageViewModel : ObservableRecipient, INavigationAware -{ - public void OnNavigatedTo() - { - } - public void OnNavigatedFrom() - { - } -} diff --git a/src/LipUI/ViewModels/MainWindowViewModel.cs b/src/LipUI/ViewModels/MainWindowViewModel.cs deleted file mode 100644 index 92087f4..0000000 --- a/src/LipUI/ViewModels/MainWindowViewModel.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using System.Collections.ObjectModel; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Data; -using CommunityToolkit.Mvvm.ComponentModel; -using LipUI.Views.Pages; -using Microsoft.Win32; -using Wpf.Ui.Common; -using Wpf.Ui.Controls; -using Wpf.Ui.Controls.Interfaces; -using Wpf.Ui.Mvvm.Contracts; - -namespace LipUI.ViewModels -{ - public partial class MainWindowViewModel : ObservableObject - { - private bool _isInitialized; - - [ObservableProperty] - private ObservableCollection _navigationItems = new(); - - [ObservableProperty] - private ObservableCollection _navigationFooter = new(); - - //[ObservableProperty] - //private ObservableCollection _trayMenuItems = new(); - - public MainWindowViewModel(INavigationService navigationService) - { - if (!_isInitialized) - InitializeViewModel(); - } - private void InitializeViewModel() - { - NavigationItem Create(string tag, SymbolRegular icon, string i18nKey) - { - var item = new NavigationItem - { - Content = "", - PageTag = tag, - Icon = icon, - PageType = typeof(T) - }; - item.SetBinding(ContentControl.ContentProperty, new Binding - { - Path = new PropertyPath(i18nKey), - Source = Global.I18N, - Mode = BindingMode.OneWay - }); - return item; - } - NavigationItems = new ObservableCollection - { - Create("dashboard", SymbolRegular.Home24, nameof(Global.I18N.NavigationHome)), - Create("local", SymbolRegular.Box24, nameof(Global.I18N.NavigationLocal)), - Create("install", SymbolRegular.Add24, nameof(Global.I18N.NavigationInstall)), - Create("remove", SymbolRegular.BoxDismiss24, nameof(Global.I18N.NavigationRemove)), - Create("registry", SymbolRegular.BoxSearch24, nameof(Global.I18N.NavigationRegistry)), - //Create("web", SymbolRegular.WebAsset24, nameof(Global.I18N.NavigationWebUi)), - //Create("developer", SymbolRegular.DeveloperBoard24, nameof(Global.I18N.NavigationDeveloper)), - }; - - NavigationFooter = new ObservableCollection - { - Create("settings", SymbolRegular.Settings24, nameof(Global.I18N.NavigationSettings)), - }; - - //TrayMenuItems = new ObservableCollection - //{ - // new MenuItem - // { - // Header = "Home", - // Tag = "tray_home" - // } - //}; - - _isInitialized = true; - } - } -} diff --git a/src/LipUI/ViewModels/SettingsViewModel.cs b/src/LipUI/ViewModels/SettingsViewModel.cs deleted file mode 100644 index 018bfe5..0000000 --- a/src/LipUI/ViewModels/SettingsViewModel.cs +++ /dev/null @@ -1,147 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; -using System.Reflection; -using System.Threading.Tasks; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using LipUI.Models; -using Wpf.Ui.Appearance; -using Wpf.Ui.Common.Interfaces; -using static LipUI.Models.AppConfig; - -namespace LipUI.ViewModels -{ - public partial class SettingsViewModel : ObservableObject, INavigationAware - { - private bool _isInitialized; - [ObservableProperty] - private string _appVersion = string.Empty; - [ObservableProperty] - private string _lipVersion = string.Empty; - [ObservableProperty] - private ThemeType _currentTheme = Global.Config.Theme; - partial void OnCurrentThemeChanged(ThemeType value) - { - Global.Config.Theme = value; - } - [ObservableProperty] - bool _autoLipPath = Global.Config.AutoLipPath; - partial void OnAutoLipPathChanged(bool value) - { - Global.Config.AutoLipPath = value; - } - [ObservableProperty] - string _lipPath = Global.Config.LipPath; - partial void OnLipPathChanged(string value) - { - if (File.Exists(value)) - { - Global.Config.LipPath = value; - Global.PopupSnackbar(Global.I18N.SettingsSuccessTitle, value); - } - else if (File.Exists(Path.Combine(value, "lip.exe"))) - { - Global.Config.LipPath = Path.Combine(value, "lip.exe"); - Global.PopupSnackbar(Global.I18N.SettingsSuccessTitle, Global.Config.LipPath); - } - else - { - Global.PopupSnackbarWarn(Global.I18N.SettingsFailedTitle, value); - } - } - [ObservableProperty] - public AppConfigWorkingDirectory _workingDir = Global.Config.WorkingDirectory ?? new(); - - public string CurrentLipPath => Global.Lip.ExecutablePath; - - partial void OnWorkingDirChanged(AppConfigWorkingDirectory value) - { - if (Directory.Exists(value.Directory)) - { - Global.Config.WorkingDirectory = value; - } - } - public void OnNavigatedTo() - { - if (!_isInitialized) - InitializeViewModel(); - } - public void OnNavigatedFrom() - { - } - private void InitializeViewModel() - { - CurrentTheme = Theme.GetAppTheme(); - AppVersion = $"LipUI - {GetAssemblyVersion()}"; - LipVersion = "loading ... "; - Task.Run(async () => - { - var version = await Global.Lip.GetLipVersion(); - LipVersion = version; - }).ConfigureAwait(false); - _isInitialized = true; - } - [RelayCommand] - private void OnOpenLipUrl() - { - //https://github.com/LiteLDev/Lip - Process.Start("https://github.com/LiteLDev/Lip"); - } - private string GetAssemblyVersion() - { - return Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? String.Empty; - } - - [RelayCommand] - private void OnChangeTheme(string parameter) - { - switch (parameter) - { - case "theme_light": - if (CurrentTheme == ThemeType.Light) - break; - Theme.Apply(ThemeType.Light); - CurrentTheme = ThemeType.Light; - break; - default: - if (CurrentTheme == ThemeType.Dark) - break; - Theme.Apply(ThemeType.Dark); - CurrentTheme = ThemeType.Dark; - break; - } - } - public AppConfig GlobalConfig => Global.Config; - public string DMConfigPath => Global.ConfigPath; - - [RelayCommand] - private async Task ExitDeveloperMode() - { - await Global.ShowDialog(Global.I18N.DeveloperTitle, Global.I18N.DeveloperDialogExit, - (Global.I18N.DeveloperDialogCancel, hide => hide()), (Global.I18N.DeveloperDialogConfirm, hide => - { - Global.Config.DeveloperMode = false; - Global.PopupSnackbar(Global.I18N.DeveloperSnackbarTitle, Global.I18N.DeveloperDialogExited); - hide(); - } - )); - } - - [RelayCommand] - private async Task ClearCache() - { - await Global.ShowDialog(Global.I18N.SettingsClearCacheTitle, Global.I18N.SettingsClearCacheContent, - (Global.I18N.SettingsClearCacheCancel, hide => hide()), (Global.I18N.SettingsClearCacheConfirm, hide => - { - hide(); - Global.Lip.CachePurge().ContinueWith(_ => - { - Global.PopupSnackbar(Global.I18N.SettingsClearCacheTitle, - Global.I18N.SettingsClearCacheCompleted); - }); - } - )); - } - } -} diff --git a/src/LipUI/ViewModels/SourceSelectorViewModel.cs b/src/LipUI/ViewModels/SourceSelectorViewModel.cs deleted file mode 100644 index c8237c8..0000000 --- a/src/LipUI/ViewModels/SourceSelectorViewModel.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Windows; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; - -namespace LipUI.ViewModels; -public partial class SourceSelectorViewModel : ObservableObject -{//todo 换源自定义的页面 - [RelayCommand] - void Copy(string v) - { - Clipboard.SetText(v); - Global.PopupSnackbar(Global.I18N.CopyToClipboard, v); - } - [RelayCommand] - void Select(string v) - { - //Config.WorkingDirectory = v; - //Global.CheckWorkDir(); - } - [RelayCommand] - void Delete(string dir) - { - //Config.AllWorkingDirectory.Remove(dir); - //Global.CheckWorkDir(); - } -} \ No newline at end of file diff --git a/src/LipUI/ViewModels/ToothInfoPanelViewModel.cs b/src/LipUI/ViewModels/ToothInfoPanelViewModel.cs deleted file mode 100644 index 4267711..0000000 --- a/src/LipUI/ViewModels/ToothInfoPanelViewModel.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.Linq; -using System.Windows; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using LipNETWrapper.Class; - -namespace LipUI.ViewModels -{ - public partial class ToothInfoPanelViewModel : ObservableObject - { - private LipPackageVersions? _ver; - public ToothInfoPanelViewModel(LipPackage info) - { - Tooth = info.Tooth; - Author = info.Info.Author; - Description = info.Info.Description; - Homepage = "undefined"; - Name = info.Info.Name; - License = ""; - Version = info.Version; - } - public ToothInfoPanelViewModel(LipPackageVersions info, LipRegistry.LipRegistryItem item) : this(info) - { - Tooth = item.Tooth; - Author = item.Author; - Description = item.Description; - Homepage = item.Homepage; - Name = item.Name; - License = item.License; - //Version = item.Version; - Tags = item.Tags.ToArray(); - } - public ToothInfoPanelViewModel(LipPackageVersions ver) - { - _ver = ver; - if (Versions?.FirstOrDefault() is not null and var v) - { - SelectedVersion = v; - } - } - [ObservableProperty] string _author = string.Empty; - [ObservableProperty] string _description = string.Empty; - [ObservableProperty] string _homepage = string.Empty; - [ObservableProperty] string _name = string.Empty; - [ObservableProperty] string _license = string.Empty; - [ObservableProperty] string _version = string.Empty; - [ObservableProperty] string _tooth = string.Empty; - [ObservableProperty] string[] _tags = Array.Empty(); - public string[]? Versions => _ver?.ToArray(); - public void RefreshVersion(LipPackageVersions ver) - { - _ver = ver; - OnPropertyChanged(nameof(Versions)); - } - [RelayCommand] - void CopyToothButton() - { - Clipboard.SetText(Tooth); - Global.PopupSnackbar(Global.I18N.CopyToClipboard, Tooth); - } - [ObservableProperty] private string? _selectedVersion; - } -} diff --git a/src/LipUI/ViewModels/ToothItemViewModel.cs b/src/LipUI/ViewModels/ToothItemViewModel.cs deleted file mode 100644 index 36eb215..0000000 --- a/src/LipUI/ViewModels/ToothItemViewModel.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using LipNETWrapper.Class; - -namespace LipUI.ViewModels; -public partial class ToothItemViewModel : ObservableObject -{ - [ObservableProperty] bool _actived = true;//等待移除,用于淡出动画 - private Func _showInfo; - public ToothItemViewModel(Func showInfo, LipPackage package) - { - _showInfo = showInfo; - Version = package.Version; - Tooth = package.Tooth; - Information = package; - Author.Tag = Information.Info.Author; - } - public ToothItemViewModel( - Func showInfo, LipRegistry.LipRegistryItem item) - { - _showInfo = showInfo; - Detailed = true; - Tooth = item.Tooth; - RegistryItem = item; - Tags = (from x in item.Tags - select new ToothItemTagViewModel { Tag = x }) - //.Concat(new[] { new ToothItemTagViewModel() { Tag = item.Author } }) - .ToArray(); - Author.Tag = item.Author; - } - #region Detailed - [ObservableProperty] bool _detailed;//是否有具体细节 - [ObservableProperty] LipRegistry.LipRegistryItem? _registryItem; - [ObservableProperty] LipPackage? _information; - #endregion - [RelayCommand(CanExecute = nameof(ExecutingShowInfo))] - async Task ShowInfo() => await _showInfo(this); - [ObservableProperty] private bool _executingShowInfo = true; - [ObservableProperty] string _tooth = string.Empty; - [ObservableProperty] string _version = string.Empty; - [ObservableProperty] ToothItemTagViewModel[] _tags = Array.Empty(); - [ObservableProperty] ToothItemTagViewModel _author = new(); - - public partial class ToothItemTagViewModel : ObservableObject - { - [ObservableProperty] string _tag = string.Empty; - [ObservableProperty] bool _isSelected = false; - } -} diff --git a/src/LipUI/ViewModels/ToothJsonViewModel.cs b/src/LipUI/ViewModels/ToothJsonViewModel.cs deleted file mode 100644 index ebac1c6..0000000 --- a/src/LipUI/ViewModels/ToothJsonViewModel.cs +++ /dev/null @@ -1,39 +0,0 @@ -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using LipNETWrapper.Class.Tooth; - -namespace LipUI.ViewModels -{ - public partial class ToothJsonViewModel : ObservableObject - { - public ToothJsonViewModel(ToothJson data) - { - _toothJson = data; - } - [ObservableProperty] private ToothJson _toothJson; - [RelayCommand] - private void RemoveDependency(string key) - { - ToothJson.Dependencies.Remove(key); - } - [RelayCommand] - private void RemoveDependencyVersion(string key ) - { - //ToothJson.Dependencies[key].RemoveAt(key); - } - [RelayCommand] - private void AddDependencyVersion(string key) - { - if (!ToothJson.Dependencies.ContainsKey(key)) - { - ToothJson.Dependencies[key] = new(); - } - ToothJson.Dependencies[key].Add(new()); - } - [RelayCommand] - private void AddDependency() - { - ToothJson.Dependencies.Add("new_dependency", new()); - } - } -} diff --git a/src/LipUI/ViewModels/ToothLocalModel.cs b/src/LipUI/ViewModels/ToothLocalModel.cs deleted file mode 100644 index f63caa7..0000000 --- a/src/LipUI/ViewModels/ToothLocalModel.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System; -using System.Collections.ObjectModel; -using System.Diagnostics; -using System.Threading.Tasks; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using LipNETWrapper.Class; -using LipUI.Views.Pages; -using Wpf.Ui.Common.Interfaces; - -namespace LipUI.ViewModels -{ - public partial class ToothLocalModel : ObservableObject, INavigationAware - { - //protected bool _isInitialized = false; - [ObservableProperty] - ToothInfoPanelViewModel? _currentInfo; - [ObservableProperty] - ToothItemViewModel? _currentSelected; - [ObservableProperty] - bool _isShowingDetail; - [ObservableProperty] - ObservableCollection _toothItems - = new(); - [ObservableProperty] - bool _loading = true; - public void OnNavigatedTo() - { - Task.Run(LoadAllPackages);//初始化加载所以包 - //if (!_isInitialized) - // InitializeViewModel(); - } - public void OnNavigatedFrom() { } - [RelayCommand(CanExecute = nameof(Loading))] - protected async Task LoadAllPackages() - { - try - { - await Global.DispatcherInvokeAsync(() => ToothItems.Clear()); - var (packages, _) = await Global.Lip.GetAllPackagesAsync(); - //Debug.WriteLine(message); - foreach (var package in packages) - { - await Global.DispatcherInvokeAsync(() => ToothItems.Add(new ToothItemViewModel(ShowInfo, package))); - await Task.Delay(100);//100毫秒显示一个,假装很丝滑 - } - } - catch (Exception e) - { - Global.PopupSnackbarWarn(Global.I18N.LocalFetchRetry, e.Message); -#if DEBUG - throw; -#endif - } - } - - protected Task<(bool success, LipPackage? package, string message)> FetchPackageInfo(string tooth) - { - return Global.Lip.GetLocalPackageInfoAsync(tooth); - } - protected async Task ShowInfo(ToothItemViewModel toothItem) - { - CurrentSelected = toothItem; - var (success, package, message) = await FetchPackageInfo(toothItem.Tooth); - if (success) - { - CurrentInfo = new ToothInfoPanelViewModel(package!); - } - else - { - Global.PopupSnackbarWarn(Global.I18N.LocalFetchFailed, message); - } - IsShowingDetail = true; - } - - [RelayCommand] - void Uninstall(string target) - { - IsShowingDetail = false; - Global.EnqueueItem(new UninstallItem(target)); - Global.Navigate(); - } - [RelayCommand] - void Upgrade(ToothInfoPanelViewModel vm) - { - IsShowingDetail = false; - Global.EnqueueItem(new InstallInfo(vm.Tooth, vm, vm.Version)); - Global.Navigate(); - } - } -} diff --git a/src/LipUI/ViewModels/UninstallPageViewModel.cs b/src/LipUI/ViewModels/UninstallPageViewModel.cs deleted file mode 100644 index dd84ad4..0000000 --- a/src/LipUI/ViewModels/UninstallPageViewModel.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Collections.ObjectModel; -using System.Threading.Tasks; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using Wpf.Ui.Common.Interfaces; - -namespace LipUI.ViewModels; -public record UninstallItem(string Tooth); -public partial class UninstallPageViewModel : ObservableObject, INavigationAware -{ - [ObservableProperty][NotifyPropertyChangedFor(nameof(HasTooth))] string _tooth=string.Empty; - [ObservableProperty][NotifyPropertyChangedFor(nameof(HasTooth))] bool _uninstallComplete; - public bool HasTooth => !string.IsNullOrWhiteSpace(Tooth); - [ObservableProperty] ObservableCollection _outPut = new(); - public void OnNavigatedTo() - { - if (Global.TryDequeueItem(out var data)) - { - Tooth = data.Tooth; - } - } - public void OnNavigatedFrom() - { - } - [RelayCommand] - async Task DoUninstall() - { - OutPut.Clear(); - try - { - var exitCode = await Global.Lip.UninstallPackageAsync(Tooth, default, x => - { - Global.DispatcherInvoke(() => OutPut.Add(x)); - }); - OutPut.Add($"ExitCode:{exitCode}"); - UninstallComplete = true; - } - catch (Exception ex) - { - OutPut.Add(ex.ToString()); - } - } -} diff --git a/src/LipUI/ViewModels/WorkingPathSelectorViewModel.cs b/src/LipUI/ViewModels/WorkingPathSelectorViewModel.cs deleted file mode 100644 index e51fd65..0000000 --- a/src/LipUI/ViewModels/WorkingPathSelectorViewModel.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System.Diagnostics; -using System.IO; -using System.Threading.Tasks; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using LipUI.Models; -using Ookii.Dialogs.Wpf; -using Wpf.Ui.Common; -using static LipUI.Models.AppConfig; -using Clipboard = System.Windows.Clipboard; - -namespace LipUI.ViewModels; - -public partial class WorkingPathSelectorViewModel : ObservableObject -{ - [ObservableProperty] public bool _noEdit = false; - public AppConfig Config => Global.Config; - [RelayCommand] - void Copy(AppConfigWorkingDirectory v) - { - Clipboard.SetText(v.Directory); - Global.PopupSnackbar(Global.I18N.CopyToClipboard, v.Directory); - } - [RelayCommand] - internal void Select(AppConfigWorkingDirectory v) - { - Config.WorkingDirectory = v; - Global.CheckWorkDir(); - } - [RelayCommand] - internal async Task Open(AppConfigWorkingDirectory v) - { - await Task.Run(() => - { - Process.Start("explorer.exe", v.Directory)?.WaitForExit(1000); - }); - } - [RelayCommand] - void Delete(AppConfigWorkingDirectory dir) - { - Config.AllWorkingDirectory.Remove(dir); - Global.CheckWorkDir(); - } - [ObservableProperty] string _addingWorkingDir = string.Empty; - //[ObservableProperty] string _tip; - /// - /// 选择目录 - /// - [RelayCommand] - void SelectWorkingDir() - { - var dialog = new VistaFolderBrowserDialog - { - SelectedPath = AddingWorkingDir, - ShowNewFolderButton = true, - Description = Global.I18N.WorkingPathSelectorTitle - }; - if (dialog.ShowDialog() == true) - { - AddingWorkingDir = dialog.SelectedPath; - //自动添加 - AddWorkingDir(); - } - } - public bool ShowGetCurrentButton => !Global.Config.AllWorkingDirectory.Contains(Directory.GetCurrentDirectory()); - /// - /// 获取当前目录 - /// - [RelayCommand] - void GetCurrentDir() - { - AddingWorkingDir = Directory.GetCurrentDirectory(); - //自动添加 - AddWorkingDir(); - } - /// - /// 添加目录 - /// - [RelayCommand] - void AddWorkingDir() - { - if (string.IsNullOrWhiteSpace(AddingWorkingDir)) return; - AddingWorkingDir = AddingWorkingDir.Trim(); - if (Directory.Exists(AddingWorkingDir)) - { - if (Config.AllWorkingDirectory.Contains(AddingWorkingDir)) - { - Config.WorkingDirectory = AddingWorkingDir; - Global.PopupSnackbar(Global.I18N.WorkingPathSelectorAlreadyAdded, AddingWorkingDir, SymbolRegular.Warning16, ControlAppearance.Caution); - } - else - { - Config.AllWorkingDirectory.Add(AddingWorkingDir); - Config.WorkingDirectory = AddingWorkingDir; - AddingWorkingDir = ""; - Global.PopupSnackbar(Global.I18N.WorkingPathSelectorAdded, AddingWorkingDir); - } - } - else - { - Global.PopupSnackbar(Global.I18N.WorkingPathSelectorNotExist, AddingWorkingDir, SymbolRegular.Warning16, ControlAppearance.Caution); - } - } -} \ No newline at end of file diff --git a/src/LipUI/Views/Controls/LanguageSelector.xaml b/src/LipUI/Views/Controls/LanguageSelector.xaml deleted file mode 100644 index ba2f9e0..0000000 --- a/src/LipUI/Views/Controls/LanguageSelector.xaml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/LipUI/Views/Controls/LanguageSelector.xaml.cs b/src/LipUI/Views/Controls/LanguageSelector.xaml.cs deleted file mode 100644 index 74da14f..0000000 --- a/src/LipUI/Views/Controls/LanguageSelector.xaml.cs +++ /dev/null @@ -1,31 +0,0 @@ -using LipUI.ViewModels; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Navigation; -using System.Windows.Shapes; - -namespace LipUI.Views.Controls -{ - /// - /// Interaction logic for LanguageSelector.xaml - /// - public partial class LanguageSelector : UserControl - { - public LanguageSelector(LanguageSelectorViewModel vm) - { - InitializeComponent(); - DataContext = vm; - } - public LanguageSelector() : this(new()) { } - } -} diff --git a/src/LipUI/Views/Controls/LipInstaller.xaml b/src/LipUI/Views/Controls/LipInstaller.xaml deleted file mode 100644 index 283ed12..0000000 --- a/src/LipUI/Views/Controls/LipInstaller.xaml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/LipUI/Views/Controls/LipInstaller.xaml.cs b/src/LipUI/Views/Controls/LipInstaller.xaml.cs deleted file mode 100644 index b12e53b..0000000 --- a/src/LipUI/Views/Controls/LipInstaller.xaml.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Windows.Controls; -using LipUI.ViewModels; - -namespace LipUI.Views.Controls -{ - /// - /// Interaction logic for LipInstaller.xaml - /// - public partial class LipInstaller : UserControl - { - public LipInstaller(LipInstallerViewModel vm) - { - InitializeComponent(); - DataContext = vm; - } - } -} diff --git a/src/LipUI/Views/Controls/SourceSelector.xaml b/src/LipUI/Views/Controls/SourceSelector.xaml deleted file mode 100644 index 3d7c9cf..0000000 --- a/src/LipUI/Views/Controls/SourceSelector.xaml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - diff --git a/src/LipUI/Views/Controls/SourceSelector.xaml.cs b/src/LipUI/Views/Controls/SourceSelector.xaml.cs deleted file mode 100644 index cd72c9f..0000000 --- a/src/LipUI/Views/Controls/SourceSelector.xaml.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Windows.Controls; - -namespace LipUI.Views.Controls -{ - /// - /// Interaction logic for SourceSelector.xaml - /// - public partial class SourceSelector : UserControl - { - public SourceSelector() - { - InitializeComponent(); - } - } -} diff --git a/src/LipUI/Views/Controls/ToothInfoPanel.xaml b/src/LipUI/Views/Controls/ToothInfoPanel.xaml deleted file mode 100644 index ec42f7a..0000000 --- a/src/LipUI/Views/Controls/ToothInfoPanel.xaml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/LipUI/Views/Controls/ToothInfoPanel.xaml.cs b/src/LipUI/Views/Controls/ToothInfoPanel.xaml.cs deleted file mode 100644 index 49f4d43..0000000 --- a/src/LipUI/Views/Controls/ToothInfoPanel.xaml.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Windows; -using System.Windows.Controls; - -namespace LipUI.Views.Controls -{ - /// - /// Interaction logic for ToothInfoPanel.xaml - /// - public partial class ToothInfoPanel : UserControl - { - public ToothInfoPanel() - { - InitializeComponent(); - } - //content property - public new static readonly DependencyProperty ContentProperty = DependencyProperty.Register( - nameof(Content), typeof(object), typeof(ToothInfoPanel), new PropertyMetadata(default(object))); - - public new object Content - { - get => GetValue(ContentProperty); - set => SetValue(ContentProperty, value); - } - } -} diff --git a/src/LipUI/Views/Controls/ToothItem.xaml b/src/LipUI/Views/Controls/ToothItem.xaml deleted file mode 100644 index c9eed05..0000000 --- a/src/LipUI/Views/Controls/ToothItem.xaml +++ /dev/null @@ -1,164 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/LipUI/Views/Controls/ToothItem.xaml.cs b/src/LipUI/Views/Controls/ToothItem.xaml.cs deleted file mode 100644 index f4f250e..0000000 --- a/src/LipUI/Views/Controls/ToothItem.xaml.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Windows; -using System.Windows.Media.Animation; -using LipUI.ViewModels; - -namespace LipUI.Views.Controls -{ - /// - /// Interaction logic for ToothItem.xaml - /// - public partial class ToothItem - { - public ToothItemViewModel ViewModel => (ToothItemViewModel)DataContext; - public ToothItem() - { - InitializeComponent(); - } - private void Timeline_OnCompleted(object sender, EventArgs e) - { - try - { - DoubleAnimation animation = new DoubleAnimation(); - animation.From = main.ActualHeight; - animation.To = 0; - animation.Duration = TimeSpan.FromSeconds(1); - - Storyboard storyboard = new Storyboard(); - storyboard.Children.Add(animation); - - Storyboard.SetTarget(animation, main); - Storyboard.SetTargetProperty(animation, new PropertyPath(HeightProperty)); - main.Resources.Add("animation", storyboard); - storyboard.Begin(); - storyboard.Completed += (s, _) => - { - //main.Visibility = Visibility.Collapsed; - try - { - main.Resources.Remove("animation"); - - } - catch - { - // ignored - } - }; - } - catch - { - // ignored - } - - } - } -} diff --git a/src/LipUI/Views/Controls/ToothJson.xaml b/src/LipUI/Views/Controls/ToothJson.xaml deleted file mode 100644 index 2a92f36..0000000 --- a/src/LipUI/Views/Controls/ToothJson.xaml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - diff --git a/src/LipUI/Views/Controls/ToothJson.xaml.cs b/src/LipUI/Views/Controls/ToothJson.xaml.cs deleted file mode 100644 index 36d2d07..0000000 --- a/src/LipUI/Views/Controls/ToothJson.xaml.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Navigation; -using System.Windows.Shapes; - -namespace LipUI.Views.Controls -{ - /// - /// Interaction logic for ToothJson.xaml - /// - public partial class ToothJson : UserControl - { - public ToothJson() - { - InitializeComponent(); - } - } -} diff --git a/src/LipUI/Views/Controls/WorkingPathSelector.xaml b/src/LipUI/Views/Controls/WorkingPathSelector.xaml deleted file mode 100644 index 24b101d..0000000 --- a/src/LipUI/Views/Controls/WorkingPathSelector.xaml +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/LipUI/Views/Controls/WorkingPathSelector.xaml.cs b/src/LipUI/Views/Controls/WorkingPathSelector.xaml.cs deleted file mode 100644 index bb059b7..0000000 --- a/src/LipUI/Views/Controls/WorkingPathSelector.xaml.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Windows.Controls; -using LipUI.ViewModels; - -namespace LipUI.Views.Controls -{ - /// - /// Interaction logic for WorkingPathSelector.xaml - /// - public partial class WorkingPathSelector : UserControl - { - public WorkingPathSelectorViewModel ViewModel => (WorkingPathSelectorViewModel)DataContext; - public bool NoEdit - { - set => ViewModel.NoEdit = value; - } - public WorkingPathSelector() - { - InitializeComponent(); - DataContext ??= new WorkingPathSelectorViewModel(); - } - } -} diff --git a/src/LipUI/Views/Pages/DashboardPage.xaml b/src/LipUI/Views/Pages/DashboardPage.xaml deleted file mode 100644 index 62699bd..0000000 --- a/src/LipUI/Views/Pages/DashboardPage.xaml +++ /dev/null @@ -1,121 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/LipUI/Views/Pages/DashboardPage.xaml.cs b/src/LipUI/Views/Pages/DashboardPage.xaml.cs deleted file mode 100644 index 3cd1b45..0000000 --- a/src/LipUI/Views/Pages/DashboardPage.xaml.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Windows.Input; -using LipUI.ViewModels; -using Wpf.Ui.Common.Interfaces; - -namespace LipUI.Views.Pages -{ - /// - /// Interaction logic for DashboardPage.xaml - /// - public partial class DashboardPage : INavigableView - { - public DashboardViewModel ViewModel - { - get; - } - public DashboardPage(DashboardViewModel viewModel) - { - ViewModel = viewModel; - InitializeComponent(); - } - } -} \ No newline at end of file diff --git a/src/LipUI/Views/Pages/DeveloperPage.xaml b/src/LipUI/Views/Pages/DeveloperPage.xaml deleted file mode 100644 index edb871c..0000000 --- a/src/LipUI/Views/Pages/DeveloperPage.xaml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - diff --git a/src/LipUI/Views/Pages/DeveloperPage.xaml.cs b/src/LipUI/Views/Pages/DeveloperPage.xaml.cs deleted file mode 100644 index 51d425c..0000000 --- a/src/LipUI/Views/Pages/DeveloperPage.xaml.cs +++ /dev/null @@ -1,18 +0,0 @@ -using LipUI.ViewModels; -using Wpf.Ui.Common.Interfaces; - -namespace LipUI.Views.Pages -{ - /// - /// Interaction logic for DeveloperPage.xaml - /// - public partial class DeveloperPage : INavigableView - { - public DeveloperPage(DeveloperPageViewModel viewModel) - { - ViewModel = viewModel; - InitializeComponent(); - } - public DeveloperPageViewModel ViewModel { get; } - } -} diff --git a/src/LipUI/Views/Pages/InstallPage.xaml b/src/LipUI/Views/Pages/InstallPage.xaml deleted file mode 100644 index 5064727..0000000 --- a/src/LipUI/Views/Pages/InstallPage.xaml +++ /dev/null @@ -1,121 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/LipUI/Views/Pages/InstallPage.xaml.cs b/src/LipUI/Views/Pages/InstallPage.xaml.cs deleted file mode 100644 index 0ccefaa..0000000 --- a/src/LipUI/Views/Pages/InstallPage.xaml.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.Collections.Specialized; -using System.Windows.Controls; -using System.Windows.Input; -using LipUI.ViewModels; -using Wpf.Ui.Common.Interfaces; - -namespace LipUI.Views.Pages -{ - /// - /// Interaction logic for InstallPage.xaml - /// - public partial class InstallPage : INavigableView - { - public InstallPageViewModel ViewModel - { - get; - } - public InstallPage(InstallPageViewModel viewModel) - { - ViewModel = viewModel; - InitializeComponent(); - ViewModel.OutPut.CollectionChanged += OutPut_CollectionChanged!; - } - private void OutPut_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) - { - ScrollViewer.ScrollToEnd(); - } - - private void UIElement_OnPreviewMouseWheel(object sender, MouseWheelEventArgs e) - { - var s = (ComboBox)sender; - var length = s.Items.Count; - if (e.Delta > 0)//向下滚 - { - if (s.SelectedIndex == 0) - s.SelectedIndex = length - 1; - else - s.SelectedIndex--; - } - else - { - if (s.SelectedIndex == length - 1) - s.SelectedIndex = 0; - else - s.SelectedIndex++; - } - e.Handled = true; - } - } -} diff --git a/src/LipUI/Views/Pages/LipRegistryPage.xaml b/src/LipUI/Views/Pages/LipRegistryPage.xaml deleted file mode 100644 index f89aaa4..0000000 --- a/src/LipUI/Views/Pages/LipRegistryPage.xaml +++ /dev/null @@ -1,191 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/LipUI/Views/Pages/LipRegistryPage.xaml.cs b/src/LipUI/Views/Pages/LipRegistryPage.xaml.cs deleted file mode 100644 index e13c7e9..0000000 --- a/src/LipUI/Views/Pages/LipRegistryPage.xaml.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Windows.Controls; -using System.Windows.Input; -using LipUI.ViewModels; -using Wpf.Ui.Common.Interfaces; - -namespace LipUI.Views.Pages; -/// -/// Interaction logic for LipRegistryPage.xaml -/// -public partial class LipRegistryPage : INavigableView -{ - public LipRegistryPageViewModel ViewModel - { - get; - } - public LipRegistryPage(LipRegistryPageViewModel viewModel) - { - ViewModel = viewModel; - InitializeComponent(); - } - private void UIElement_OnMouseDown(object sender, MouseButtonEventArgs e) - { - ViewModel.IsShowingDetail = false; - } - private void UIElement_OnTouchDown(object sender, TouchEventArgs e) - { - ViewModel.IsShowingDetail = false; - } - private void UIElement_OnPreviewMouseWheel(object sender, MouseWheelEventArgs e) - { - var s = (ComboBox)sender; - var length = s.Items.Count; - if (e.Delta > 0)//向下滚 - { - if (s.SelectedIndex == 0) - s.SelectedIndex = length - 1; - else - s.SelectedIndex--; - } - else - { - if (s.SelectedIndex == length - 1) - s.SelectedIndex = 0; - else - s.SelectedIndex++; - } - e.Handled = true; - } -} diff --git a/src/LipUI/Views/Pages/LipWebPage.xaml b/src/LipUI/Views/Pages/LipWebPage.xaml deleted file mode 100644 index 7b36a4b..0000000 --- a/src/LipUI/Views/Pages/LipWebPage.xaml +++ /dev/null @@ -1,14 +0,0 @@ - - - LipWeb WIP - - diff --git a/src/LipUI/Views/Pages/LipWebPage.xaml.cs b/src/LipUI/Views/Pages/LipWebPage.xaml.cs deleted file mode 100644 index 80b150e..0000000 --- a/src/LipUI/Views/Pages/LipWebPage.xaml.cs +++ /dev/null @@ -1,19 +0,0 @@ -using LipUI.ViewModels; -using Wpf.Ui.Common.Interfaces; - -namespace LipUI.Views.Pages -{ - /// - /// Interaction logic for LipWebPage.xaml - /// - public partial class LipWebPage : INavigableView - { - public LipWebPage(LipWebPageViewModel viewModel) - { - ViewModel = viewModel; - InitializeComponent(); - } - - public LipWebPageViewModel ViewModel { get; } - } -} diff --git a/src/LipUI/Views/Pages/SettingsPage.xaml b/src/LipUI/Views/Pages/SettingsPage.xaml deleted file mode 100644 index a3a0719..0000000 --- a/src/LipUI/Views/Pages/SettingsPage.xaml +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/LipUI/Views/Pages/ToothLocalPage.xaml.cs b/src/LipUI/Views/Pages/ToothLocalPage.xaml.cs deleted file mode 100644 index ad22564..0000000 --- a/src/LipUI/Views/Pages/ToothLocalPage.xaml.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Windows.Input; -using LipUI.ViewModels; -using Wpf.Ui.Common.Interfaces; - -namespace LipUI.Views.Pages -{ - /// - /// Interaction logic for ToothLocalPage.xaml - /// - public partial class ToothLocalPage : INavigableView - { - public ToothLocalModel ViewModel - { - get; - } - public ToothLocalPage(ToothLocalModel viewModel) - { - ViewModel = viewModel; - - InitializeComponent(); - } - private void UIElement_OnMouseDown(object sender, MouseButtonEventArgs e) - { - ViewModel.IsShowingDetail = false; - } - private void UIElement_OnTouchDown(object sender, TouchEventArgs e) - { - ViewModel.IsShowingDetail = false; - } - } -} diff --git a/src/LipUI/Views/Pages/UninstallPage.xaml b/src/LipUI/Views/Pages/UninstallPage.xaml deleted file mode 100644 index 467e629..0000000 --- a/src/LipUI/Views/Pages/UninstallPage.xaml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/LipUI/Views/Pages/UninstallPage.xaml.cs b/src/LipUI/Views/Pages/UninstallPage.xaml.cs deleted file mode 100644 index 8744741..0000000 --- a/src/LipUI/Views/Pages/UninstallPage.xaml.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Windows; -using LipUI.ViewModels; -using Wpf.Ui.Common.Interfaces; - -namespace LipUI.Views.Pages -{ - /// - /// Interaction logic for UninstallPage.xaml - /// - public partial class UninstallPage : INavigableView - { - public UninstallPageViewModel ViewModel - { - get; - } - public UninstallPage(UninstallPageViewModel viewModel) - { - ViewModel = viewModel; - InitializeComponent(); - } - - private void Hyperlink_OnClick(object sender, RoutedEventArgs e) - { - Global.Navigate(); - } - } -} diff --git a/src/LipUI/Views/Windows/MainWindow.xaml b/src/LipUI/Views/Windows/MainWindow.xaml deleted file mode 100644 index c702968..0000000 --- a/src/LipUI/Views/Windows/MainWindow.xaml +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/LipUI/Views/Windows/MainWindow.xaml.cs b/src/LipUI/Views/Windows/MainWindow.xaml.cs deleted file mode 100644 index 088ee2c..0000000 --- a/src/LipUI/Views/Windows/MainWindow.xaml.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Data; -using LipUI.ViewModels; -using Wpf.Ui.Appearance; -using Wpf.Ui.Common; -using Wpf.Ui.Controls.Interfaces; -using Wpf.Ui.Mvvm.Contracts; - -namespace LipUI.Views.Windows -{ - /// - /// Interaction logic for MainWindow.xaml - /// - public partial class MainWindow : INavigationWindow - { - public MainWindowViewModel ViewModel - { - get; - } - - public MainWindow(MainWindowViewModel viewModel, IPageService pageService, INavigationService navigationService) - { - ViewModel = viewModel; - DataContext = this; - - InitializeComponent(); - SetPageService(pageService); - - navigationService.SetNavigationControl(RootNavigation); - - //从配置获取主题 - if (Global.Config.Theme is not ThemeType.Unknown) Theme.Apply(Global.Config.Theme); - } - - #region INavigationWindow methods - - public Frame GetFrame() - => RootFrame; - public INavigation GetNavigation() - => RootNavigation; - public bool Navigate(Type pageType) - => RootNavigation.Navigate(pageType); - public void SetPageService(IPageService pageService) - => RootNavigation.PageService = pageService; - public void ShowWindow() - => Show(); - public void CloseWindow() - => Close(); - #endregion INavigationWindow methods - - /// - /// Raises the closed event. - /// - protected override void OnClosed(EventArgs e) - { - base.OnClosed(e); - - // Make sure that closing this window will begin the process of closing the application. - Application.Current.Shutdown(); - } - - private void RootNavigation_OnNavigated(INavigation sender, RoutedNavigationEventArgs e) - { - if (RootNavigation?.Current is { } navigationItem) - { - string? text = navigationItem.Content as string; - if (!string.IsNullOrEmpty(text)) - { - Breadcrumb.SetBinding(TextBlock.TextProperty, - new Binding(nameof(navigationItem.Content)) { Source = navigationItem }); - } - } - } - } -} \ No newline at end of file diff --git a/src/LipUI/app.manifest b/src/LipUI/app.manifest index 06626c3..9d9ba22 100644 --- a/src/LipUI/app.manifest +++ b/src/LipUI/app.manifest @@ -1,75 +1,19 @@ - + - - - - - - - - - - - - - - - - - - - - - - - + - - - - + - - PerMonitor - true/PM - true - + + PerMonitorV2 + - - - - - - - - + \ No newline at end of file diff --git a/src/LipUI/applicationIcon.ico b/src/LipUI/applicationIcon.ico deleted file mode 100644 index cf0e97f..0000000 Binary files a/src/LipUI/applicationIcon.ico and /dev/null differ diff --git a/src/LipWebApi/AuthManager.cs b/src/LipWebApi/AuthManager.cs index b66978a..32ab3a4 100644 --- a/src/LipWebApi/AuthManager.cs +++ b/src/LipWebApi/AuthManager.cs @@ -1,11 +1,11 @@ using HttpServerLite; +using Newtonsoft.Json.Linq; using System; using System.Collections.Concurrent; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; -using Newtonsoft.Json.Linq; using static LipWebApi.WebApi.Main; namespace LipWebApi; internal static class AuthManager @@ -89,7 +89,7 @@ bool CheckUsernameAndPassword()//检查用户名和密码 // } //} //_userTokens.TryRemove(username, out _);//移除旧验证 - + return false; } bool isValid = CheckUsernameAndPassword(); diff --git a/src/LipWebApi/Launcher.cs b/src/LipWebApi/Launcher.cs index 19b7042..e6375fd 100644 --- a/src/LipWebApi/Launcher.cs +++ b/src/LipWebApi/Launcher.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace LipWebApi; +namespace LipWebApi; internal class Launcher { } diff --git a/src/LipWebApi/Models/AuthData.cs b/src/LipWebApi/Models/AuthData.cs index 99ad39f..0516619 100644 --- a/src/LipWebApi/Models/AuthData.cs +++ b/src/LipWebApi/Models/AuthData.cs @@ -1,7 +1,7 @@ -using System; +using Newtonsoft.Json; +using System; using System.Collections.Concurrent; using System.IO; -using Newtonsoft.Json; namespace LipWebApi.Models { diff --git a/src/LipWebApi/Program.cs b/src/LipWebApi/Program.cs index f6f9a3e..4cd5797 100644 --- a/src/LipWebApi/Program.cs +++ b/src/LipWebApi/Program.cs @@ -1,7 +1,6 @@ -using System; -using System.Collections.Generic; +using HttpServerLite; +using System; using System.Threading.Tasks; -using HttpServerLite; Webserver server = new Webserver("localhost", 9000, false, null, null, DefaultRoute); server.Settings.Headers.Host = "https://localhost:3000"; diff --git a/src/LipWebApi/WebApi/LocalTooth.cs b/src/LipWebApi/WebApi/LocalTooth.cs index 514896d..b3606b9 100644 --- a/src/LipWebApi/WebApi/LocalTooth.cs +++ b/src/LipWebApi/WebApi/LocalTooth.cs @@ -1,11 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using static LipWebApi.WebApi.Main; - -namespace LipWebApi.WebApi +namespace LipWebApi.WebApi { public class LocalTooth { diff --git a/src/LipWebApi/WebApi/Main.cs b/src/LipWebApi/WebApi/Main.cs index ebb031c..44e0660 100644 --- a/src/LipWebApi/WebApi/Main.cs +++ b/src/LipWebApi/WebApi/Main.cs @@ -1,6 +1,5 @@ -using System; +using HttpServerLite; using System.Threading.Tasks; -using HttpServerLite; namespace LipWebApi.WebApi; public class Main