diff --git a/.gitignore b/.gitignore index 1d0ef60..de79242 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ bin obj out -build \ No newline at end of file +build +Installing \ No newline at end of file diff --git a/Assets/crosslang.ico b/Assets/crosslang.ico index 3fd66ae..6cdeb85 100644 Binary files a/Assets/crosslang.ico and b/Assets/crosslang.ico differ diff --git a/Assets/crosslang.png b/Assets/crosslang.png new file mode 100644 index 0000000..0291e4e Binary files /dev/null and b/Assets/crosslang.png differ diff --git a/CrossLangDevStudio.csproj b/CrossLangDevStudio.csproj index da5e124..dcf008b 100644 --- a/CrossLangDevStudio.csproj +++ b/CrossLangDevStudio.csproj @@ -6,6 +6,7 @@ true app.manifest true + Assets\crosslang.ico @@ -26,6 +27,7 @@ + diff --git a/CrossLangDevStudio.sln b/CrossLangDevStudio.sln new file mode 100644 index 0000000..67a9c16 --- /dev/null +++ b/CrossLangDevStudio.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossLangDevStudio", "CrossLangDevStudio.csproj", "{07F003F4-ED31-462B-8711-5BE508A4CD35}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {07F003F4-ED31-462B-8711-5BE508A4CD35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {07F003F4-ED31-462B-8711-5BE508A4CD35}.Debug|Any CPU.Build.0 = Debug|Any CPU + {07F003F4-ED31-462B-8711-5BE508A4CD35}.Release|Any CPU.ActiveCfg = Release|Any CPU + {07F003F4-ED31-462B-8711-5BE508A4CD35}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/CrossLangFile.cs b/CrossLangFile.cs index ddcaf9d..18463ee 100644 --- a/CrossLangFile.cs +++ b/CrossLangFile.cs @@ -127,7 +127,7 @@ public class CrossLangFile public Bitmap GetIcon(int height=128) { - using (Stream strm = (Icon > -1 && Icon < Resources.Count) ? new MemoryStream(Resources[Icon], false) : AssetLoader.Open(new Uri("avares://CrossLangDevStudio/Assets/crosslang.ico"))) + using (Stream strm = (Icon > -1 && Icon < Resources.Count) ? new MemoryStream(Resources[Icon], false) : AssetLoader.Open(new Uri("avares://CrossLangDevStudio/Assets/crosslang.png"))) { return Bitmap.DecodeToHeight(strm, height); } @@ -210,7 +210,7 @@ public class CrossLangDependency { [JsonProperty("name")] public string Name { get; set; } = ""; - [JsonProperty("verson")] + [JsonProperty("version")] public CrossLangVersion Version { get; set; } } diff --git a/CrossLangShell.cs b/CrossLangShell.cs index 9ed29c6..1ff5aaa 100644 --- a/CrossLangShell.cs +++ b/CrossLangShell.cs @@ -3,8 +3,10 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; +using System.Text; using System.Text.Json.Serialization; using System.Threading.Tasks; using Newtonsoft.Json; @@ -14,6 +16,69 @@ namespace CrossLangDevStudio; class CrossLangShell { + public static string PackageServerFile = Path.Combine(CrossLangConfigDirectory, "package_servers.json"); + public static List GetServers() + { + if (File.Exists(PackageServerFile)) + { + var res = JsonConvert.DeserializeObject>(File.ReadAllText(PackageServerFile)); + if (res is not null) return res; + } + return ["https://cpkg.tesseslanguage.com/"]; + } + public static string[] ProjectTypes { get; } = ["console", "lib", "webapp", "template", "compile_tool", "tool", "archive"]; + public static string[] ProjectProperTypes { get; } = ["Console Application","Library","Web Application","Template","Compile Tool","Tool","Archive"]; + private static void EscapeBashElement(StringBuilder builder, string val) + { + builder.Append("\""); + foreach (var item in val) + { + switch (item) + { + case '$': + case '`': + case '\\': + case '\"': + case '!': + builder.Append('\\'); + builder.Append(item); + break; + default: + builder.Append(item); + break; + } + } + builder.Append("\""); + } + private static string EscapeBashCommand(string name, string[] args) + { + StringBuilder builder = new StringBuilder(); + EscapeBashElement(builder,name); + foreach (var item in args) + { + builder.Append(' '); + EscapeBashElement(builder,item); + } + builder.Append(" ; read -p \"Press Enter to close.\""); + return builder.ToString(); + } + + private static string EscapeMacBashCommand(string workDir, bool keepOpen,string name, string[] args) + { + StringBuilder builder = new StringBuilder(); + builder.Append("cd "); + EscapeBashElement(builder, workDir); + builder.Append(" ; "); + EscapeBashElement(builder,name); + foreach (var item in args) + { + builder.Append(' '); + EscapeBashElement(builder,item); + } + if (keepOpen) + builder.Append(" ; read -p \"Press Enter to close.\""); + return builder.ToString(); + } private static CrossLangSettings? settings = null; public static CrossLangSettings Settings { @@ -88,7 +153,15 @@ class CrossLangShell } public static string GetRealPath(string exec) { - foreach (var item in (Environment.GetEnvironmentVariable("PATH") ?? "").Split(Path.PathSeparator)) + var paths = (Environment.GetEnvironmentVariable("PATH") ?? "").Split(Path.PathSeparator).ToList(); + var execDir = Environment.ProcessPath; + if (execDir is not null) + { + var dir = Path.GetDirectoryName(execDir); + if(dir is not null) + paths.Add(dir); + } + foreach (var item in paths) { var path = Path.Combine(item, exec); if (File.Exists(path)) return path; @@ -154,6 +227,10 @@ class CrossLangShell { string konsole = GetRealPath("konsole"); string gnome_terminal = GetRealPath("gnome-terminal"); + string mate_terminal = GetRealPath("mate-terminal"); + string lxterminal = GetRealPath("lxterminal"); + string deepin_terminal = GetRealPath("deepin-terminal"); + string xfce_terminal = GetRealPath("xfce4-terminal"); string xterm = GetRealPath("xterm"); if (File.Exists(konsole)) @@ -175,28 +252,185 @@ class CrossLangShell } process.Start(); } + if (File.Exists(xfce_terminal)) + { + using Process process = new Process(); + process.StartInfo.WorkingDirectory = workingDirectory; + process.StartInfo.FileName = xfce_terminal; + process.StartInfo.UseShellExecute = false; + if (commandName.Length != 0) + { + if (keepOpen) + process.StartInfo.ArgumentList.Add("--hold"); + process.StartInfo.ArgumentList.Add("-e"); + process.StartInfo.ArgumentList.Add(commandName); + foreach (var arg in args) + { + process.StartInfo.ArgumentList.Add(arg); + } + } + process.Start(); + } else if (File.Exists(gnome_terminal)) { + using Process process = new Process(); + process.StartInfo.WorkingDirectory = workingDirectory; + process.StartInfo.FileName = gnome_terminal; + process.StartInfo.UseShellExecute = false; + if (commandName.Length != 0) + { + process.StartInfo.ArgumentList.Add("--"); + if (keepOpen) + { + process.StartInfo.ArgumentList.Add("bash"); + process.StartInfo.ArgumentList.Add("-c"); + process.StartInfo.ArgumentList.Add(EscapeBashCommand(commandName, args)); + } + else + { + process.StartInfo.ArgumentList.Add(commandName); + foreach (var arg in args) + { + process.StartInfo.ArgumentList.Add(arg); + } + } + } + process.Start(); + } + else if (File.Exists(mate_terminal)) + { + using Process process = new Process(); + process.StartInfo.WorkingDirectory = workingDirectory; + process.StartInfo.FileName = mate_terminal; + process.StartInfo.UseShellExecute = false; + if (commandName.Length != 0) + { + process.StartInfo.ArgumentList.Add("--"); + if (keepOpen) + { + process.StartInfo.ArgumentList.Add("bash"); + process.StartInfo.ArgumentList.Add("-c"); + process.StartInfo.ArgumentList.Add(EscapeBashCommand(commandName, args)); + } + else + { + process.StartInfo.ArgumentList.Add(commandName); + foreach (var arg in args) + { + process.StartInfo.ArgumentList.Add(arg); + } + } + } + process.Start(); + } + else if (File.Exists(lxterminal)) + { + using Process process = new Process(); + process.StartInfo.WorkingDirectory = workingDirectory; + process.StartInfo.FileName = lxterminal; + process.StartInfo.UseShellExecute = false; + if (commandName.Length != 0) + { + process.StartInfo.ArgumentList.Add("-e"); + if (keepOpen) + { + process.StartInfo.ArgumentList.Add("bash"); + process.StartInfo.ArgumentList.Add("-c"); + process.StartInfo.ArgumentList.Add(EscapeBashCommand(commandName, args)); + } + else + { + process.StartInfo.ArgumentList.Add(commandName); + foreach (var arg in args) + { + process.StartInfo.ArgumentList.Add(arg); + } + } + } + process.Start(); + } + + else if (File.Exists(deepin_terminal)) + { + using Process process = new Process(); + process.StartInfo.WorkingDirectory = workingDirectory; + process.StartInfo.FileName = deepin_terminal; + process.StartInfo.UseShellExecute = false; + if (commandName.Length != 0) + { + if (keepOpen) + process.StartInfo.ArgumentList.Add("--keep-open"); + process.StartInfo.ArgumentList.Add("-e"); + process.StartInfo.ArgumentList.Add(commandName); + foreach (var arg in args) + { + process.StartInfo.ArgumentList.Add(arg); + } + } + process.Start(); } else if (File.Exists(xterm)) { - + using Process process = new Process(); + process.StartInfo.WorkingDirectory = workingDirectory; + process.StartInfo.FileName = xterm; + process.StartInfo.UseShellExecute = false; + if (commandName.Length != 0) + { + if (keepOpen) + process.StartInfo.ArgumentList.Add("-hold"); + process.StartInfo.ArgumentList.Add("-e"); + process.StartInfo.ArgumentList.Add(commandName); + foreach (var arg in args) + { + process.StartInfo.ArgumentList.Add(arg); + } + } + process.Start(); } } else if (OperatingSystem.IsWindows()) { using Process process = new Process(); process.StartInfo.FileName = GetRealPath("cmd.exe"); - if(commandName.Length > 0) - process.StartInfo.ArgumentList.Add(keepOpen ? "/K" : "/C"); + if (commandName.Length > 0) + process.StartInfo.ArgumentList.Add(keepOpen ? "/K" : "/C"); process.StartInfo.CreateNoWindow = false; process.StartInfo.UseShellExecute = false; process.StartInfo.WorkingDirectory = workingDirectory; - if(commandName.Length > 0) - process.StartInfo.ArgumentList.Add(commandName); - foreach (var item in args) - process.StartInfo.ArgumentList.Add(item); + if (commandName.Length > 0) + process.StartInfo.ArgumentList.Add(commandName); + if (commandName.Length > 0) + foreach (var item in args) + process.StartInfo.ArgumentList.Add(item); + process.Start(); + } + else if (OperatingSystem.IsMacOS()) + { + using Process process = new Process(); + process.StartInfo.WorkingDirectory = workingDirectory; + process.StartInfo.FileName = GetRealPath("open"); + process.StartInfo.UseShellExecute = false; + if (commandName.Length != 0) + { + process.StartInfo.ArgumentList.Add("-b"); + process.StartInfo.ArgumentList.Add("com.apple.terminal"); + if (keepOpen) + { + process.StartInfo.ArgumentList.Add("bash"); + process.StartInfo.ArgumentList.Add("-c"); + process.StartInfo.ArgumentList.Add(EscapeMacBashCommand(workingDirectory, keepOpen, commandName, args)); + } + else + { + process.StartInfo.ArgumentList.Add(commandName); + foreach (var arg in args) + { + process.StartInfo.ArgumentList.Add(arg); + } + } + } process.Start(); } } diff --git a/Messages/InstallPackageMessage.cs b/Messages/InstallPackageMessage.cs new file mode 100644 index 0000000..6931bf4 --- /dev/null +++ b/Messages/InstallPackageMessage.cs @@ -0,0 +1,12 @@ +using CrossLangDevStudio.ViewModels; +using CommunityToolkit.Mvvm.Messaging.Messages; +using System.Net.Http; + +namespace CrossLangDevStudio.Messages; + +public class InstallPackageMessage(HttpClient client,string name, string server) : AsyncRequestMessage +{ + public HttpClient Client => client; + public string Name => name; + public string Server => server; +} \ No newline at end of file diff --git a/Messages/InstallPackageResponseMessage.cs b/Messages/InstallPackageResponseMessage.cs new file mode 100644 index 0000000..66ed5d0 --- /dev/null +++ b/Messages/InstallPackageResponseMessage.cs @@ -0,0 +1,9 @@ +using CrossLangDevStudio.ViewModels; +using CommunityToolkit.Mvvm.Messaging.Messages; + +namespace CrossLangDevStudio.Messages; + +public class InstallPackageResponseMessage(CrossLangVersion? version) : AsyncRequestMessage +{ + public CrossLangVersion? Version => version; +} \ No newline at end of file diff --git a/Models/IPackageManager.cs b/Models/IPackageManager.cs new file mode 100644 index 0000000..829fa5c --- /dev/null +++ b/Models/IPackageManager.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Threading.Tasks; +using Avalonia.Controls; + +namespace CrossLangDevStudio.Models; + +public interface IPackageManager +{ + Task InstallPackageAsync(string name, CrossLangVersion version); + string Filter { get; } + void UninstallPackage(string name); + + IEnumerable GetInstalledPackages(); +} + +public static class PackageManagerExtensions +{ + public static string Summarize(this string txt) + { + var lines = txt.Replace("\r", "").Split('\n'); + bool needsthreedots = false; + if (lines.Length > 1) needsthreedots = true; + if (lines.Length == 0) return ""; + if (lines[0].Length > 120) { needsthreedots = true; lines[0] = lines[0].Substring(0, 120); } + if (needsthreedots) return $"{lines[0]}..."; + return lines[0]; + } + public static bool IsPackageInstalled(this IPackageManager pm, string name) + { + foreach (var item in pm.GetInstalledPackages()) + { + if (item.Name == name) return true; + } + return false; + } + public static async Task InstallPackageAsync(this IPackageManager pm, CrossLangDependency dep) + { + await pm.InstallPackageAsync(dep.Name, dep.Version); + } +} \ No newline at end of file diff --git a/Models/ProjectPackageManager.cs b/Models/ProjectPackageManager.cs new file mode 100644 index 0000000..d55a095 --- /dev/null +++ b/Models/ProjectPackageManager.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Threading.Tasks; +using Avalonia.Controls; +using Newtonsoft.Json; + +namespace CrossLangDevStudio.Models; + +public class ProjectPackageManager : IPackageManager +{ + public ProjectPackageManager(string path) + { + Path = path; + } + public string Path { get; init; } + public string Filter => "lib,compile_tool"; + + public IEnumerable GetInstalledPackages() + { + var cfg = JsonConvert.DeserializeObject(File.ReadAllText(Path)); + if (cfg is not null) + { + foreach (var item in cfg.Dependencies) + { + yield return item; + } + } + } + + public async Task InstallPackageAsync(string name, CrossLangVersion version) + { + var cfg = JsonConvert.DeserializeObject(await File.ReadAllTextAsync(Path)) ?? new CrossLangConfig(); + cfg.Dependencies ??= new List(); + bool has = false; + foreach (var item in cfg.Dependencies) + { + if (item.Name == name) + { + has = true; + if(version.CompareTo(item.Version) > 0) + item.Version = version; + break; + } + } + if (!has) cfg.Dependencies.Add(new CrossLangDependency { Name = name, Version = version }); + await File.WriteAllTextAsync(Path, JsonConvert.SerializeObject(cfg, Formatting.Indented, new JsonSerializerSettings(){NullValueHandling= NullValueHandling.Ignore})); + + } + + + + public void UninstallPackage(string name) + { + var cfg = JsonConvert.DeserializeObject(File.ReadAllText(Path)) ?? new CrossLangConfig(); + cfg.Dependencies ??= new List(); + + foreach (var item in cfg.Dependencies) + { + if (item.Name == name) + { + cfg.Dependencies.Remove(item); + + break; + } + } + File.WriteAllText(Path, JsonConvert.SerializeObject(cfg, Formatting.Indented,new JsonSerializerSettings(){NullValueHandling= NullValueHandling.Ignore})); + } +} \ No newline at end of file diff --git a/Packaging/Linux/build.sh b/Packaging/Linux/build.sh index e69de29..bd63f93 100644 --- a/Packaging/Linux/build.sh +++ b/Packaging/Linux/build.sh @@ -0,0 +1,15 @@ +#!/bin/bash +mkdir build +cd build +mkdir crosslang-devstudio_1.0.0_amd64 +cd crosslang-devstudio_1.0.0_amd64 +dotnet publish -c Release -r linux-x64 -o opt/CrossLangDevStudio -p:PublishReadyToRun=true -p:PublishSingleFile=true --self-contained ../../../../CrossLangDevStudio.csproj +mkdir DEBIAN +mkdir -p usr/share/applications +mkdir -p usr/bin +cp ../../debian/control-amd64 DEBIAN/control +cp ../../crosslang-devstudio.desktop usr/share/applications/ +cp ../../crossdev usr/bin/crossdev +chmod 755 usr/bin/crossdev +cd .. +dpkg-deb --build crosslang-devstudio_1.0.0_amd64/ diff --git a/Packaging/Linux/crossdev b/Packaging/Linux/crossdev new file mode 100644 index 0000000..60c7e1d --- /dev/null +++ b/Packaging/Linux/crossdev @@ -0,0 +1,2 @@ +#!/bin/bash +/opt/CrossLangDevStudio/CrossLangDevStudio "$@" \ No newline at end of file diff --git a/Packaging/Linux/crosslang-devstudio.desktop b/Packaging/Linux/crosslang-devstudio.desktop new file mode 100644 index 0000000..951c3e0 --- /dev/null +++ b/Packaging/Linux/crosslang-devstudio.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Type=Application +Version=1.0 +Name=CrossLang DevStudio +Exec=crossdev +Icon=crosslang +Categories=Education;Languages;Programming +Comment=IDE For CrossLang \ No newline at end of file diff --git a/Packaging/Linux/debian/control-amd64 b/Packaging/Linux/debian/control-amd64 new file mode 100644 index 0000000..542c9eb --- /dev/null +++ b/Packaging/Linux/debian/control-amd64 @@ -0,0 +1,8 @@ +Package: crosslang-devstudio +Version: 1.0.0 +Architecture: amd64 +Essential: no +Priority: optional +Depends: crosslang +Maintainer: Mike Nolan +Description: IDE for CrossLang diff --git a/Packaging/Linux/push.sh b/Packaging/Linux/push.sh new file mode 100644 index 0000000..2cbe2f8 --- /dev/null +++ b/Packaging/Linux/push.sh @@ -0,0 +1,6 @@ +#!/bin/bash +curl --user tesses50:$GITEA_AUTH -X DELETE \ + https://git.tesseslanguage.com/api/packages/tesses50/debian/pool/jammy/main/crosslang-devstudio/1.0.0/amd64 +curl --user tesses50:$GITEA_AUTH \ + --upload-file build/crosslang-devstudio_1.0.0_amd64.deb \ + https://git.tesseslanguage.com/api/packages/tesses50/debian/pool/jammy/main/upload diff --git a/Packaging/Windows/build.sh b/Packaging/Windows/build.sh index f664233..a972c2a 100644 --- a/Packaging/Windows/build.sh +++ b/Packaging/Windows/build.sh @@ -5,7 +5,7 @@ git clone https://onedev.site.tesses.net/crosslang cd crosslang mkdir build cd build -cmake -S .. -B . -DCMAKE_TOOLCHAIN_FILE=../../../Toolchain.cmake -DTESSESFRAMEWORK_ENABLE_SHARED=OFF -DTESSESFRAMEWORK_FETCHCONTENT=ON +cmake -S .. -B . -DCMAKE_TOOLCHAIN_FILE=../../../Toolchain.cmake -DTESSESFRAMEWORK_ENABLE_SHARED=OFF -DTESSESFRAMEWORK_ENABLE_STATIC=ON -DTESSESFRAMEWORK_ENABLE_APPS=OFF -DTESSESFRAMEWORK_ENABLE_EXAMPLES=OFF -DTESSESFRAMEWORK_FETCHCONTENT=ON make -j`nproc` mkdir -p ../../package/bin mkdir -p ../../package/share/Tesses/CrossLang diff --git a/ViewModels/InstallPackageDialogViewModel.cs b/ViewModels/InstallPackageDialogViewModel.cs new file mode 100644 index 0000000..6f73790 --- /dev/null +++ b/ViewModels/InstallPackageDialogViewModel.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Threading.Tasks; +using System.Web; +using Avalonia.Markup.Xaml.Templates; +using Avalonia.Media; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using CommunityToolkit.Mvvm.Messaging; +using CrossLangDevStudio.Messages; +using CrossLangDevStudio.Views; +using Newtonsoft.Json; + +namespace CrossLangDevStudio.ViewModels; + +public partial class InstallPackageDialogViewModel : ViewModelBase +{ + public InstallPackageMessage Message { get; } + [ObservableProperty] + private int _versionIndex = 0; + public ObservableCollection Versions { get; } = new ObservableCollection(); + + public InstallPackageDialogViewModel(InstallPackageMessage msg) + { + Message = msg; + + Task.Run(async () => + { + var res = await Message.Client.GetStringAsync($"{msg.Server.TrimEnd('/')}/api/v1/versions?name={HttpUtility.UrlEncode(msg.Name)}"); + if (!string.IsNullOrWhiteSpace(res)) + { + var res2 = JsonConvert.DeserializeObject(res); + if (res2 is not null && res2.Success) + { + foreach (var res3 in res2.Versions) + { + Versions.Add(res3.ToObject()); + } + VersionIndex = 0; + } + } + }); + } + [RelayCommand] + private void Install() + { + if(VersionIndex < Versions.Count) + WeakReferenceMessenger.Default.Send(new InstallPackageResponseMessage(Versions[VersionIndex].Version)); + } + [RelayCommand] + private void Cancel() + { + WeakReferenceMessenger.Default.Send(new InstallPackageResponseMessage(null)); + } + public partial class VersionObject(CrossLangVersion version, DateTime date) : ObservableObject + { + public CrossLangVersion Version => version; + [ObservableProperty] + private string _text = $"{version} at {date.ToShortDateString()}"; + } + + public class VersionsMessage + { + [JsonProperty("success")] + public bool Success { get; set; } + [JsonProperty("versions")] + public List Versions { get; set; } = new(); + + public class VersionMessage + { + [JsonProperty("version")] + public CrossLangVersion Version { get; set; } + + [JsonProperty("uploadTime")] + public long UploadTime { get; set; } = 0; + + public VersionObject ToObject() + { + + return new VersionObject(Version, DateTimeOffset.FromUnixTimeSeconds(UploadTime).LocalDateTime); + } + + + } + } +} \ No newline at end of file diff --git a/ViewModels/MainWindowViewModel.cs b/ViewModels/MainWindowViewModel.cs index 714480b..e1b5e55 100644 --- a/ViewModels/MainWindowViewModel.cs +++ b/ViewModels/MainWindowViewModel.cs @@ -122,7 +122,12 @@ public partial class MainWindowViewModel : ViewModelBase OpenProjectConfig(); }), - new ProjectFileNode("Packages") + new ProjectFileNode("Packages", ()=>{ + OpenProjectPackages(); + }), + new ProjectFileNode("Project Dependencies",()=>{ + + }) ]) }; @@ -151,6 +156,33 @@ public partial class MainWindowViewModel : ViewModelBase ProjectFiles.Add(new ProjectFileNode(Path.GetFileName(obj), entries)); } + private void OpenProjectPackages() + { + if (Directory.Exists(CurrentProject)) + { + var config = Path.Combine(CurrentProject, "cross.json"); + if (File.Exists(config)) + { + foreach (var item in TabItems) + { + if (item.Body is PackageManagerViewModel vm) + { + if (vm.Packages is ProjectPackageManager) + { + SelectedTab = item; + return; + } + } + } + AddTab(new TabItemViewModel() + { + Header = "Project Packages", + Body = new PackageManagerViewModel(this, new ProjectPackageManager(config)) + }); + } + } + } + private void OpenProjectConfig() { @@ -161,6 +193,7 @@ public partial class MainWindowViewModel : ViewModelBase { if (item.Body is ProjectConfigurationViewModel model && model.FilePath == config) { + SelectedTab = item; return; } } diff --git a/ViewModels/NewProjectDialogViewModel.cs b/ViewModels/NewProjectDialogViewModel.cs index 1d1a076..6abf493 100644 --- a/ViewModels/NewProjectDialogViewModel.cs +++ b/ViewModels/NewProjectDialogViewModel.cs @@ -39,7 +39,7 @@ public partial class NewProjectDialogViewModel : ViewModelBase int i; for (i = 1; i < int.MaxValue; i++) { - string name2 = $"{name}{i}"; + string name2 = $"{newName}{i}"; if (!Directory.Exists(Path.Combine(ParentDirectory, name2))) { Name = name2; diff --git a/ViewModels/PackageManagerViewModel.cs b/ViewModels/PackageManagerViewModel.cs new file mode 100644 index 0000000..f58f10d --- /dev/null +++ b/ViewModels/PackageManagerViewModel.cs @@ -0,0 +1,203 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Threading.Tasks; +using System.Web; +using Avalonia; +using Avalonia.Animation; +using Avalonia.Controls; +using Avalonia.Media; +using Avalonia.Media.Imaging; +using Avalonia.Platform.Storage; + +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using CommunityToolkit.Mvvm.Messaging; +using CrossLangDevStudio.Messages; +using CrossLangDevStudio.Models; +using CrossLangDevStudio.Views; +using Newtonsoft.Json; + +namespace CrossLangDevStudio.ViewModels; + + +public partial class PackageManagerViewModel : ViewModelBase +{ + public IPackageManager Packages { get; } + + public ObservableCollection PackageList { get; } = new ObservableCollection(); + + public ObservableCollection Servers { get; } = new ObservableCollection(); + + [ObservableProperty] + private int _serverIndex = 0; + + [ObservableProperty] + private string _search = ""; + + [ObservableProperty] + private int _page = 1; + + public MainWindowViewModel Main { get; } + + + public PackageManagerViewModel(MainWindowViewModel mvm, IPackageManager pm) + { + Main = mvm; + Packages = pm; + foreach(var svr in CrossLangShell.GetServers()) + this.Servers.Add(svr); + } + + [RelayCommand] + private async Task SearchPackagesAsync() + { + var svr = Servers[ServerIndex].TrimEnd('/'); + var body = await Main.Client.GetStringAsync($"{svr}/api/v1/search?q={HttpUtility.UrlEncode(Search)}&filter={HttpUtility.UrlEncode(Packages.Filter)}&offset={Page-1}&limit=20"); + var res = JsonConvert.DeserializeObject(body); + PackageList.Clear(); + if (res is not null) + { + + foreach (var item in res.Packages) + { + Package pkg = new Package(); + var date = DateTimeOffset.FromUnixTimeSeconds(item.UploadTime); + var date2 = date.ToLocalTime().DateTime; + + pkg.Info = $"Version: {item.Version}\tAccount: {item.AccountName}\tUpdated: {date2.ToShortDateString()}\tLicense: {item.License}\tType: {item.Type}"; + + pkg.Version = item.Version; + pkg.Name = item.Name; + pkg.Description = item.Description.Summarize(); + pkg.IsInstalled = Packages.IsPackageInstalled(item.Name); + pkg.Install = async() => + { + var resp = await WeakReferenceMessenger.Default.Send(new InstallPackageMessage(Main.Client, item.Name, svr)); + if (resp.HasValue) + { + await Packages.InstallPackageAsync(item.Name, resp.Value); + pkg.IsInstalled = true; + } + }; + pkg.Uninstall = () => + { + Packages.UninstallPackage(item.Name); + pkg.IsInstalled = false; + }; + + + byte[] data = await Main.Client.GetByteArrayAsync($"{svr}/api/v1/package_icon.png?name={HttpUtility.UrlEncode(item.Name)}&version={item.Version.ToString()}"); + using var ms = new MemoryStream(data, false); + pkg.Icon = Bitmap.DecodeToHeight(ms, 64); + PackageList.Add(pkg); + } + } + } + + public class SearchPackage + { + [JsonProperty("packageName")] + public string Name { get; set; } = ""; + + [JsonProperty("version")] + public CrossLangVersion Version { get; set; } + + [JsonProperty("uploadTime")] + public long UploadTime { get; set; } + + [JsonProperty("license")] + public string License { get; set; } = ""; + + [JsonProperty("type")] + public string Type { get; set; } = ""; + [JsonProperty("accountName")] + public string AccountName { get; set; } = ""; + [JsonProperty("description")] + public string Description { get; set; } = ""; + } + + public partial class Package : ObservableObject + { + [ObservableProperty] + private string _name = ""; + + public CrossLangVersion Version { get; set; } + public Func? Install { get; set; } + public Func? GetInfo { get; set; } + + [ObservableProperty] + private string _info = ""; + [ObservableProperty] + private string _description = ""; + + [ObservableProperty] + private IImage? _icon = null; + + [ObservableProperty] + private string _mainButtonText = ""; + + [ObservableProperty] + private string _subButton1Text = ""; + private bool _isInstalled = false; + public bool IsInstalled + { + get => _isInstalled; + set + { + SetProperty(ref _isInstalled, value, nameof(IsInstalled)); + + MainButtonText = value ? "Uninstall" : "Install"; + SubButton1Text = value ? "Change Version" : "Info"; + } + } + + public Action? Uninstall { get; set; } + + [RelayCommand] + private async Task MainButtonAsync() + { + /*var i = Install; + if (i is not null) await i();*/ + + if (_isInstalled) + { + Uninstall?.Invoke(); + } + else + { + var i = Install; + if (i is not null) await i(); + } + } + + [RelayCommand] + private async Task SubButton1Async() + { + if (_isInstalled) + { + var i = Install; + if (i is not null) await i(); + } + else + { + var i = GetInfo; + if (i is not null) await i(); + } + } + + [RelayCommand] + private async Task SubButton2Async() + { + var i = GetInfo; + if (i is not null) await i(); + } + } + + public class SearchResult + { + [JsonProperty("packages")] + public List Packages { get; set; } = new List(); + } +} diff --git a/ViewModels/ProjectConfigurationViewModel.cs b/ViewModels/ProjectConfigurationViewModel.cs index 8f75f5b..b295207 100644 --- a/ViewModels/ProjectConfigurationViewModel.cs +++ b/ViewModels/ProjectConfigurationViewModel.cs @@ -26,7 +26,6 @@ public partial class ProjectConfigurationViewModel : ViewModelBase, ISavable Tool Archive */ - static readonly string[] types = ["console","lib","app","template","compile_tool","tool","archive"]; TabItemViewModel tab; public string FilePath { get; } bool _modified = false; @@ -186,7 +185,7 @@ public partial class ProjectConfigurationViewModel : ViewModelBase, ISavable _license = config?.Info?.License ?? ""; _templatename = config?.Info?.TemplateName ?? ""; _templatenamepretty = config?.Info?.TemplateNamePretty ?? ""; - _type = Array.IndexOf(types, config?.Info?.Type ?? "console"); + _type = Array.IndexOf(CrossLangShell.ProjectTypes, config?.Info?.Type ?? "console"); _type = _type == -1 ? 0 : _type; _description = config?.Info?.Description ?? ""; @@ -210,7 +209,7 @@ public partial class ProjectConfigurationViewModel : ViewModelBase, ISavable config.Info.Repoository = string.IsNullOrWhiteSpace(Repository) ? null : Repository; config.Info.TemplateName = string.IsNullOrWhiteSpace(TemplateName) ? null : TemplateName; config.Info.TemplateNamePretty = string.IsNullOrWhiteSpace(TemplateNamePretty) ? null : TemplateNamePretty; - config.Info.Type = types[Type]; + config.Info.Type = CrossLangShell.ProjectTypes[Type]; File.WriteAllText(FilePath, JsonConvert.SerializeObject(config, Formatting.Indented, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore diff --git a/Views/InstallPackageDialog.axaml b/Views/InstallPackageDialog.axaml new file mode 100644 index 0000000..138e948 --- /dev/null +++ b/Views/InstallPackageDialog.axaml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +