First Commit

This commit is contained in:
2025-08-31 00:25:32 -05:00
commit cddf6798a6
40 changed files with 2401 additions and 0 deletions

View File

@@ -0,0 +1,65 @@
using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Platform.Storage;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using CrossLangDevStudio.Messages;
using CrossLangDevStudio.Views;
namespace CrossLangDevStudio.ViewModels;
public partial class FileEditorViewModel : ViewModelBase, ISavable
{
TabItemViewModel tab;
public string FilePath { get; }
bool _modified = false;
private bool Modified
{
get => _modified;
set
{
_modified = value;
if (value)
{
tab.Header = $"{Path.GetFileName(FilePath)}*";
}
else
{
tab.Header = Path.GetFileName(FilePath);
}
}
}
private string _text = "";
public string Text
{
get => _text;
set
{
Modified = true;
this.SetProperty(ref _text, value, nameof(Text));
}
}
public FileEditorViewModel(string path,TabItemViewModel tab)
{
this.tab = tab;
FilePath = path;
_text = File.ReadAllText(path);
}
public void Save()
{
File.WriteAllText(FilePath, Text);
Modified = false;
}
}

5
ViewModels/ISavable.cs Normal file
View File

@@ -0,0 +1,5 @@
namespace CrossLangDevStudio.ViewModels;
interface ISavable
{
void Save();
}

View File

@@ -0,0 +1,409 @@
using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using Avalonia.Controls;
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;
namespace CrossLangDevStudio.ViewModels;
public partial class MainWindowViewModel : ViewModelBase
{
public HttpClient Client { get; set; } = new();
public string CurrentProject { get; set; } = "";
public Func<object> NewItemFactory => AddItem;
public ObservableCollection<TabItemViewModel> TabItems { get; } = new();
public ObservableCollection<ProjectFileNode> ProjectFiles { get; } = new ObservableCollection<ProjectFileNode>();
public EventHandler<Tabalonia.Events.CloseLastTabEventArgs>? Closed { get; } = null;
public TabItemViewModel WelcomePage
{
get
{
return new TabItemViewModel()
{
Header = "Welcome",
Body = new WelcomeViewModel(this)
};
}
}
private ProjectFileNode? _selectedProjectFile = null;
public ProjectFileNode? SelectedProjectFile
{
get => _selectedProjectFile;
set
{
_selectedProjectFile = value;
value?.Click?.Invoke();
}
}
public MainWindowViewModel(string[] args)
{
if (args.Length == 1)
{
LoadProject(args[0]);
}
if (string.IsNullOrWhiteSpace(CurrentProject))
this.TabItems.Add(WelcomePage);
}
private object AddItem()
{
var tab = new TabItemViewModel
{
Header = "New Tab",
};
if (Directory.Exists(CurrentProject))
{
var newTabItem = new NewTabViewModel(this, tab);
tab.Body = newTabItem;
}
return tab;
}
internal void LoadProject(string obj)
{
if (!File.Exists(Path.Combine(obj, "cross.json")))
{
return;
}
CrossLangShell.EnsureRecent(obj);
CurrentProject = obj.TrimEnd(Path.DirectorySeparatorChar);
Refresh(CurrentProject);
}
[RelayCommand]
public void RefreshListing()
{
if (!File.Exists(Path.Combine(CurrentProject, "cross.json")))
{
return;
}
Refresh(CurrentProject);
}
private void Refresh(string obj)
{
for (int i = 0; i < TabItems.Count; i++)
{
if (TabItems[i].Body is WelcomeViewModel)
{
TabItems.RemoveAt(i);
i--;
}
}
ObservableCollection<ProjectFileNode> entries = new ObservableCollection<ProjectFileNode>
{
new ProjectFileNode("Project",[
new ProjectFileNode("Configuration",()=>{
OpenProjectConfig();
}),
new ProjectFileNode("Packages")
])
};
void Itterate(ObservableCollection<ProjectFileNode> nodes, string dir)
{
foreach (var item in Directory.EnumerateDirectories(dir))
{
ObservableCollection<ProjectFileNode> files = new();
Itterate(files, item);
var pfn = new ProjectFileNode(Path.GetFileName(item), files);
nodes.Add(pfn);
}
foreach (var item in Directory.EnumerateFiles(dir))
{
nodes.Add(new ProjectFileNode(Path.GetFileName(item), () =>
{
OpenFile(item);
}));
}
}
Itterate(entries, obj);
ProjectFiles.Clear();
ProjectFiles.Add(new ProjectFileNode(Path.GetFileName(obj), entries));
}
private void OpenProjectConfig()
{
if (Directory.Exists(CurrentProject))
{
var config = Path.Combine(CurrentProject, "cross.json");
foreach (var item in TabItems)
{
if (item.Body is ProjectConfigurationViewModel model && model.FilePath == config)
{
return;
}
}
TabItemViewModel vm = new TabItemViewModel();
vm.Header = "Project Configuration";
var pcm = new ProjectConfigurationViewModel(config, vm);
vm.Body = pcm;
AddTab(vm);
}
}
static string[] FILE_EXTS = new string[]{
".tcross",
".json",
".txt",
".cpp",
".hpp",
".html",
".css",
".webmanifest",
".cs",
".c",
".h",
".xml",
".xaml",
".js",
".jsx",
".ts",
".tsx",
".gitignore",
".svg"
};
public void OpenFile(string path)
{
bool isValid = false;
foreach (var item in FILE_EXTS)
{
if (Path.GetExtension(path).ToLower() == item)
isValid = true;
}
if (!isValid) return;
foreach (var item in TabItems)
{
if (item.Body is FileEditorViewModel model)
{
if (model.FilePath == path)
{
SelectedTab = item;
return;
}
}
}
var tab = new TabItemViewModel
{
Header = Path.GetFileName(path),
};
tab.Body = new FileEditorViewModel(path,tab);
AddTab(tab);
}
[ObservableProperty]
private TabItemViewModel? _selectedTab = null;
private void AddTab(TabItemViewModel item)
{
TabItems.Add(item);
SelectedTab = item;
}
[RelayCommand]
public async Task NewProjectAsync()
{
var res = await WeakReferenceMessenger.Default.Send(new NewProjectMessage());
if (!string.IsNullOrWhiteSpace(res))
{
LoadProject(res);
}
}
[RelayCommand]
public async Task OpenProjectAsync()
{
var opts = new FolderPickerOpenOptions();
opts.AllowMultiple = false;
opts.Title = "Open Project";
var window = await WeakReferenceMessenger.Default.Send(new GetWindowMessage());
opts.SuggestedStartLocation = await window.StorageProvider.TryGetFolderFromPathAsync(CrossLangShell.Settings.ProjectDirectory) ?? null;
var res = await window.StorageProvider.OpenFolderPickerAsync(opts);
if (res.Count == 1)
{
var url = res[0].TryGetLocalPath();
if (!string.IsNullOrWhiteSpace(url))
{
LoadProject(url);
}
}
}
[RelayCommand]
private void OpenProjectInFileManager()
{
if (Directory.Exists(CurrentProject))
{
CrossLangShell.OpenFolderInFileManager(CurrentProject);
}
}
[RelayCommand]
private void OpenProjectInTerminal()
{
if (Directory.Exists(CurrentProject))
{
CrossLangShell.OpenTerminalInFolder(CurrentProject);
}
}
[RelayCommand]
private void BuildAndRun()
{
SaveAll();
if (Directory.Exists(CurrentProject))
{
CrossLangShell.RunProjectInFolder(CurrentProject);
}
}
[RelayCommand]
private void Build()
{
SaveAll();
if (Directory.Exists(CurrentProject))
{
CrossLangShell.BuildProjectInFolder(CurrentProject);
}
}
[RelayCommand]
private void Save()
{
SelectedTab?.Save();
}
[RelayCommand]
private void SaveAll()
{
foreach (var tab in TabItems)
{
tab.Save();
}
}
}
public partial class NewTabViewModel : ViewModelBase
{
private MainWindowViewModel mainWindowViewModel;
private TabItemViewModel tab;
public NewTabViewModel(MainWindowViewModel mainWindowViewModel, TabItemViewModel tab)
{
this.mainWindowViewModel = mainWindowViewModel;
this.tab = tab;
}
[ObservableProperty]
private string _filePath = "src/";
[ObservableProperty]
private decimal _progress = 0;
[ObservableProperty]
private string _url = "";
[RelayCommand]
private void CreateDirectory()
{
if (!string.IsNullOrWhiteSpace(FilePath))
{
Directory.CreateDirectory(Path.Combine(mainWindowViewModel.CurrentProject, FilePath));
mainWindowViewModel.RefreshListing();
mainWindowViewModel.TabItems.Remove(tab);
}
}
[RelayCommand]
private void CreateFile()
{
if (!string.IsNullOrWhiteSpace(FilePath))
{
var path = Path.Combine(mainWindowViewModel.CurrentProject, FilePath);
File.WriteAllText(path, "");
mainWindowViewModel.RefreshListing();
mainWindowViewModel.TabItems.Remove(tab);
mainWindowViewModel.OpenFile(path);
}
}
[RelayCommand]
private async Task DownloadFileAsync()
{
try
{
using var destStrm = File.Create(Path.Combine(mainWindowViewModel.CurrentProject, FilePath));
var res = await mainWindowViewModel.Client.GetAsync(Url);
long length = 0;
long offset = 0;
Progress = 0;
if (res.IsSuccessStatusCode)
{
if (res.Content.Headers.ContentLength.HasValue)
{
length = res.Content.Headers.ContentLength.Value;
byte[] array = new byte[1024];
int read = 0;
using var src = await res.Content.ReadAsStreamAsync();
do
{
read = await src.ReadAsync(array);
await destStrm.WriteAsync(array, 0, read);
offset += read;
if (length > 0)
{
Progress = (decimal)offset / (decimal)length;
}
} while (read != 0);
}
else
{
await res.Content.CopyToAsync(destStrm);
}
Progress = 1;
mainWindowViewModel.RefreshListing();
mainWindowViewModel.TabItems.Remove(tab);
}
else
{
throw new Exception();
}
}
catch (Exception)
{
File.Delete(FilePath);
this.Url = $"FAILED: {this.Url}";
}
}
}

View File

@@ -0,0 +1,120 @@
using System;
using System.Collections.ObjectModel;
using System.IO;
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;
namespace CrossLangDevStudio.ViewModels;
public partial class NewProjectDialogViewModel : ViewModelBase
{
public ObservableCollection<CrossLangTemplate> CrossLangTemplates { get; set; } = new ObservableCollection<CrossLangTemplate>();
bool updated = false;
string _name = "";
public string Name
{
get
{
return _name;
}
set
{
updated = true;
SetProperty(ref _name, value, nameof(Name));
}
}
private void SelectProjectType(string name)
{
if (!updated)
{
var newName = name.Replace(" ", "");
int i;
for (i = 1; i < int.MaxValue; i++)
{
string name2 = $"{name}{i}";
if (!Directory.Exists(Path.Combine(ParentDirectory, name2)))
{
Name = name2;
updated = false;
break;
}
}
}
}
[ObservableProperty]
private string _parentDirectory = CrossLangShell.Settings.ProjectDirectory;
CrossLangTemplate? _template = null;
public CrossLangTemplate? SelectedTemplate
{
get
{
return _template;
}
set
{
SetProperty(ref _template, value, nameof(SelectedTemplate));
if (value is not null)
{
SelectProjectType(value.Name);
}
}
}
public NewProjectDialogViewModel()
{
foreach (var item in Directory.EnumerateFiles(Path.Combine(CrossLangShell.CrossLangConfigDirectory, "Templates"), "*.crvm"))
{
CrossLangFile file = new CrossLangFile();
try
{
file.Load(item);
CrossLangTemplates.Add(new CrossLangTemplate(Path.GetFileNameWithoutExtension(item), file));
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
}
[RelayCommand]
private void OK()
{
if (_template is not null && !string.IsNullOrWhiteSpace(Name) && !string.IsNullOrWhiteSpace(ParentDirectory))
{
var path = Path.Combine(ParentDirectory, Name);
Directory.CreateDirectory(path);
if (CrossLangShell.CreateProject(_template.TemplateName, path))
{
WeakReferenceMessenger.Default.Send(new NewProjectCloseMessage(path));
}
}
}
[RelayCommand]
private void Cancel()
{
WeakReferenceMessenger.Default.Send(new NewProjectCloseMessage(null));
}
}
public partial class CrossLangTemplate(string name,CrossLangFile file) : ObservableObject
{
[ObservableProperty]
private string _name = file.GetTemplatePrettyName();
[ObservableProperty]
private IImage _icon = file.GetIcon(64);
public string TemplateName = name;
}

View File

@@ -0,0 +1,220 @@
using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Platform.Storage;
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 ProjectConfigurationViewModel : ViewModelBase, ISavable
{
/*
<ComboBoxItem>Console Application</ComboBoxItem>
<ComboBoxItem>Library</ComboBoxItem>
<ComboBoxItem>Web Application</ComboBoxItem>
<ComboBoxItem>Template</ComboBoxItem>
<ComboBoxItem>Compile Tool</ComboBoxItem>
<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;
private bool Modified
{
get => _modified;
set
{
_modified = value;
if (value)
{
tab.Header = $"Project Configuration*";
}
else
{
tab.Header = "Project Configuration";
}
}
}
string _name;
string _version;
string _icon;
string _maintainer;
string _repository;
string _homepage;
string _license;
string _templatename;
string _templatenamepretty;
string _description;
int _type;
public string Name
{
get => _name;
set
{
Modified = true;
SetProperty(ref _name, value, nameof(Name));
}
}
public string Version
{
get => _version;
set
{
Modified = true;
SetProperty(ref _version, value, nameof(Version));
}
}
public string Icon
{
get => _icon;
set
{
Modified = true;
SetProperty(ref _icon, value, nameof(Icon));
}
}
public string Maintainer
{
get => _maintainer;
set
{
Modified = true;
SetProperty(ref _maintainer, value, nameof(Maintainer));
}
}
public string Homepage
{
get => _homepage;
set
{
Modified = true;
SetProperty(ref _homepage, value, nameof(Homepage));
}
}
public string Repository
{
get => _repository;
set
{
Modified = true;
SetProperty(ref _repository, value, nameof(Repository));
}
}
public string License
{
get => _license;
set
{
Modified = true;
SetProperty(ref _license, value, nameof(License));
}
}
public string TemplateName
{
get => _templatename;
set
{
Modified = true;
SetProperty(ref _templatename, value, nameof(TemplateName));
}
}
public string TemplateNamePretty
{
get => _templatenamepretty;
set
{
Modified = true;
SetProperty(ref _templatenamepretty, value, nameof(TemplateNamePretty));
}
}
public string Description
{
get => _description;
set
{
Modified = true;
SetProperty(ref _description, value, nameof(Description));
}
}
public int Type
{
get => _type;
set
{
Modified = true;
SetProperty(ref _type, value, nameof(Type));
}
}
public ProjectConfigurationViewModel(string configPath, TabItemViewModel tab)
{
FilePath = configPath;
this.tab = tab;
var config = JsonConvert.DeserializeObject<CrossLangConfig>(File.ReadAllText(configPath));
_name = config?.Name ?? "";
_version = config?.Version.ToString() ?? "1.0.0.0-dev";
_icon = config?.Icon ?? "";
_repository = config?.Info?.Repoository ?? "";
_homepage = config?.Info?.HomePage ?? "";
_maintainer = config?.Info?.Maintainer ?? "";
_license = config?.Info?.License ?? "";
_templatename = config?.Info?.TemplateName ?? "";
_templatenamepretty = config?.Info?.TemplateNamePretty ?? "";
_type = Array.IndexOf(types, config?.Info?.Type ?? "console");
_type = _type == -1 ? 0 : _type;
_description = config?.Info?.Description ?? "";
}
[RelayCommand]
public void Save()
{
var config = JsonConvert.DeserializeObject<CrossLangConfig>(File.ReadAllText(FilePath)) ?? new CrossLangConfig();
config.Name = Name;
if (CrossLangVersion.TryParse(Version, out var vers))
{
config.Version = vers;
}
config.Icon = string.IsNullOrWhiteSpace(Icon) ? null : Icon;
config.Info.Description = string.IsNullOrWhiteSpace(Description) ? null : Description;
config.Info.HomePage = string.IsNullOrWhiteSpace(Homepage) ? null : Homepage;
config.Info.License = string.IsNullOrWhiteSpace(License) ? null : License;
config.Info.Maintainer = string.IsNullOrWhiteSpace(Maintainer) ? null : Maintainer;
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];
File.WriteAllText(FilePath, JsonConvert.SerializeObject(config, Formatting.Indented, new JsonSerializerSettings()
{
NullValueHandling = NullValueHandling.Ignore
}));
Modified = false;
}
}

View File

@@ -0,0 +1,24 @@
using System;
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
namespace CrossLangDevStudio.ViewModels;
public partial class TabItemViewModel : ObservableObject
{
[ObservableProperty]
private string _header = "";
public ViewModelBase? Body { get; set; }
public override string ToString() => Header;
public void Save()
{
if (Body is ISavable savable)
{
savable.Save();
}
}
}

View File

@@ -0,0 +1,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace CrossLangDevStudio.ViewModels;
public class ViewModelBase : ObservableObject
{
}

View File

@@ -0,0 +1,66 @@
using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Platform.Storage;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using CrossLangDevStudio.Messages;
using CrossLangDevStudio.Views;
namespace CrossLangDevStudio.ViewModels;
public partial class WelcomeViewModel : ViewModelBase
{
public MainWindowViewModel Main { get; }
public WelcomeViewModel(MainWindowViewModel main)
{
Main = main;
foreach (var proj in CrossLangShell.GetRecentProjects())
{
RecentProjects.Add(new RecentProject(proj,Main.LoadProject));
}
}
public ObservableCollection<RecentProject> RecentProjects { get; set; } = new ObservableCollection<RecentProject>();
[RelayCommand]
private async Task NewProjectAsync()
{
await Main.NewProjectAsync();
}
[RelayCommand]
private async Task OpenProjectAsync()
{
await Main.OpenProjectAsync();
}
}
public partial class RecentProject(string path, Action<string> onProjectClicked) : ObservableObject
{
public string ProjectPath { get; set; } = path;
private string GetDir()
{
var projPath = ProjectPath;
var path = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
if (projPath.StartsWith(path))
{
projPath= projPath.Replace(path, "~");
}
return projPath;
}
public string Name => $"{Path.GetFileName(ProjectPath)} ({GetDir()})";
[RelayCommand]
private void Click()
{
onProjectClicked(ProjectPath);
}
}