New dev build

This commit is contained in:
2025-09-06 11:14:02 -05:00
parent 700cb81f24
commit 7c4f85ec21
28 changed files with 952 additions and 17 deletions

1
.gitignore vendored
View File

@@ -3,3 +3,4 @@ bin
obj
out
build
Installing

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 66 KiB

BIN
Assets/crosslang.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@@ -6,6 +6,7 @@
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<ApplicationIcon>Assets\crosslang.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
@@ -26,6 +27,7 @@
</PackageReference>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="3.119.0" />
<PackageReference Include="Tabalonia" Version="0.10.7" />
</ItemGroup>
</Project>

22
CrossLangDevStudio.sln Normal file
View File

@@ -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

View File

@@ -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; }
}

View File

@@ -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<string> GetServers()
{
if (File.Exists(PackageServerFile))
{
var res = JsonConvert.DeserializeObject<List<string>>(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();
}
}

View File

@@ -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<CrossLangVersion?>
{
public HttpClient Client => client;
public string Name => name;
public string Server => server;
}

View File

@@ -0,0 +1,9 @@
using CrossLangDevStudio.ViewModels;
using CommunityToolkit.Mvvm.Messaging.Messages;
namespace CrossLangDevStudio.Messages;
public class InstallPackageResponseMessage(CrossLangVersion? version) : AsyncRequestMessage<CrossLangVersion?>
{
public CrossLangVersion? Version => version;
}

42
Models/IPackageManager.cs Normal file
View File

@@ -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<CrossLangDependency> 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);
}
}

View File

@@ -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<CrossLangDependency> GetInstalledPackages()
{
var cfg = JsonConvert.DeserializeObject<CrossLangConfig>(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<CrossLangConfig>(await File.ReadAllTextAsync(Path)) ?? new CrossLangConfig();
cfg.Dependencies ??= new List<CrossLangDependency>();
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<CrossLangConfig>(File.ReadAllText(Path)) ?? new CrossLangConfig();
cfg.Dependencies ??= new List<CrossLangDependency>();
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}));
}
}

View File

@@ -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/

2
Packaging/Linux/crossdev Normal file
View File

@@ -0,0 +1,2 @@
#!/bin/bash
/opt/CrossLangDevStudio/CrossLangDevStudio "$@"

View File

@@ -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

View File

@@ -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

6
Packaging/Linux/push.sh Normal file
View File

@@ -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

View File

@@ -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

View File

@@ -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<VersionObject> Versions { get; } = new ObservableCollection<VersionObject>();
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<VersionsMessage>(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<VersionMessage> 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);
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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<Package> PackageList { get; } = new ObservableCollection<Package>();
public ObservableCollection<string> Servers { get; } = new ObservableCollection<string>();
[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<SearchResult>(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<Task>? Install { get; set; }
public Func<Task>? 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<SearchPackage> Packages { get; set; } = new List<SearchPackage>();
}
}

View File

@@ -26,7 +26,6 @@ public partial class ProjectConfigurationViewModel : ViewModelBase, ISavable
<ComboBoxItem>Tool</ComboBoxItem>
<ComboBoxItem>Archive</ComboBoxItem>
*/
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

View File

@@ -0,0 +1,46 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:CrossLangDevStudio.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
Width="320"
Height="120"
WindowStartupLocation="CenterOwner"
x:Class="CrossLangDevStudio.Views.InstallPackageDialog"
x:DataType="vm:InstallPackageDialogViewModel"
Icon="/Assets/crosslang.ico"
Title="Install Package">
<Design.DataContext>
<!-- This only sets the DataContext for the previewer in an IDE,
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
<vm:InstallPackageDialogViewModel/>
</Design.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ComboBox Grid.Row="1" ItemsSource="{Binding Versions}" HorizontalAlignment="Stretch" SelectedIndex="{Binding VersionIndex}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Grid Grid.Row="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button Content="Install" Grid.Column="1" Command="{Binding InstallCommand}" />
<Button Content="Cancel" Grid.Column="2" Command="{Binding CancelCommand}" />
</Grid>
</Grid>
</Window>

View File

@@ -0,0 +1,21 @@
using System.Diagnostics;
using Avalonia.Controls;
using CommunityToolkit.Mvvm.Messaging;
using CrossLangDevStudio.Messages;
namespace CrossLangDevStudio.Views;
public partial class InstallPackageDialog : Window
{
public InstallPackageDialog()
{
InitializeComponent();
if (Design.IsDesignMode) return;
WeakReferenceMessenger.Default.Register<InstallPackageDialog, InstallPackageResponseMessage>(this, static (w, m) =>
{
w.Close(m.Version);
}
);
}
}

View File

@@ -28,6 +28,7 @@
<MenuItem Header="_Open Project" HotKey="Ctrl+O" Command="{Binding OpenProjectCommand}"/>
<MenuItem Header="_Save" HotKey="Ctrl+S" Command="{Binding SaveCommand}"/>
<MenuItem Header="Save All" HotKey="Ctrl+Shift+S" Command="{Binding SaveAllCommand}"/>
<MenuItem Header="CrossLang Settings"/>
<Separator/>
<MenuItem Header="_Exit" HotKey="Alt+F4"/>
</MenuItem>
@@ -38,6 +39,23 @@
<MenuItem Header="Open In Terminal" HotKey="CTRL+T" Command="{Binding OpenProjectInTerminalCommand}" />
<MenuItem Header="Refresh" HotKey="CTRL+R" Command="{Binding RefreshListingCommand}" />
</MenuItem>
<MenuItem Header="_CPKG">
<MenuItem Header="Add Package"/>
<MenuItem Header="Install">
<MenuItem Header="Template"/>
<MenuItem Header="Tool"/>
<MenuItem Header="Web Application"/>
<MenuItem Header="Console Application"/>
</MenuItem>
<MenuItem Header="Push Package"/>
<MenuItem Header="Manage Servers"/>
</MenuItem>
<MenuItem Header="_Help">
<MenuItem Header="Welcome"/>
<MenuItem Header="Website"/>
<MenuItem Header="Documentation"/>
</MenuItem>
</Menu>
<Grid Grid.Row="1" ColumnDefinitions="300, 4, *">
<TreeView Grid.Column="0" ItemsSource="{Binding ProjectFiles}" SelectedItem="{Binding SelectedProjectFile}">

View File

@@ -28,6 +28,15 @@ public partial class MainWindow : Window
// Show dialog window and reply with returned AlbumViewModel or null when the dialog is closed.
m.Reply(dialog.ShowDialog<string?>(w));
});
WeakReferenceMessenger.Default.Register<MainWindow, InstallPackageMessage>(this, static (w, m) =>
{
var dialog = new InstallPackageDialog
{
DataContext = new InstallPackageDialogViewModel(m)
};
m.Reply(dialog.ShowDialog<CrossLangVersion?>(w));
});
WeakReferenceMessenger.Default.Register<MainWindow, GetWindowMessage>(this, static (w, m) =>
{
m.Reply(w);

View File

@@ -0,0 +1,76 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:CrossLangDevStudio.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="CrossLangDevStudio.Views.PackageManagerView"
x:DataType="vm:PackageManagerViewModel">
<Design.DataContext>
<!-- This only sets the DataContext for the previewer in an IDE,
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
<vm:PackageManagerViewModel />
</Design.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="{Binding Search}" Watermark="Search..." />
<ComboBox Grid.Column="1" ItemsSource="{Binding Servers}" SelectedIndex="{Binding ServerIndex}" />
<Button Grid.Column="2" Content="Search" Command="{Binding SearchPackagesCommand}" />
</Grid>
<ScrollViewer Grid.Row="1" HorizontalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding PackageList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="64" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Source="{Binding Icon}" />
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding Name}"/>
<TextBlock Grid.Row="1" Text="{Binding Info}"/>
<TextBlock Grid.Row="2" Text="{Binding Description}"/>
</Grid>
<Grid Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<SplitButton Grid.Row="0" Content="{Binding MainButtonText}" Command="{Binding MainButtonCommand}">
<SplitButton.Flyout>
<MenuFlyout Placement="Bottom">
<MenuItem Header="{Binding SubButton1Text}" Command="{Binding SubButton1Command}" />
<MenuItem Header="Info" IsVisible="{Binding IsInstalled}" Command="{Binding SubButton2Command}" />
</MenuFlyout>
</SplitButton.Flyout>
</SplitButton>
</Grid>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
</UserControl>

View File

@@ -0,0 +1,11 @@
using Avalonia.Controls;
namespace CrossLangDevStudio.Views;
public partial class PackageManagerView : UserControl
{
public PackageManagerView()
{
InitializeComponent();
}
}