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 NewItemFactory => AddItem; public ObservableCollection TabItems { get; } = new(); public ObservableCollection ProjectFiles { get; } = new ObservableCollection(); public EventHandler? 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 entries = new ObservableCollection { new ProjectFileNode("Project",[ new ProjectFileNode("Configuration",()=>{ OpenProjectConfig(); }), new ProjectFileNode("Packages") ]) }; void Itterate(ObservableCollection nodes, string dir) { foreach (var item in Directory.EnumerateDirectories(dir)) { ObservableCollection 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}"; } } }