2025-01-10 12:28:29 +03:00
|
|
|
using System;
|
2025-01-11 20:39:58 +03:00
|
|
|
using System.Collections.Generic;
|
2025-01-10 12:28:29 +03:00
|
|
|
using System.Collections.ObjectModel;
|
2025-01-12 15:15:01 +03:00
|
|
|
using System.Diagnostics;
|
2025-01-11 20:39:58 +03:00
|
|
|
using System.Diagnostics.CodeAnalysis;
|
2025-01-12 15:15:01 +03:00
|
|
|
using System.IO;
|
2025-01-11 20:39:58 +03:00
|
|
|
using System.Linq;
|
2025-06-14 22:33:03 +03:00
|
|
|
using System.Threading;
|
2025-01-11 20:39:58 +03:00
|
|
|
using System.Threading.Tasks;
|
2025-01-10 12:28:29 +03:00
|
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
2025-06-14 22:33:03 +03:00
|
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
|
using Nebula.Launcher.Models;
|
2025-01-14 22:10:16 +03:00
|
|
|
using Nebula.Launcher.Services;
|
|
|
|
|
using Nebula.Launcher.ViewModels.Popup;
|
2025-06-14 22:33:03 +03:00
|
|
|
using Nebula.Launcher.Views;
|
2025-01-10 12:28:29 +03:00
|
|
|
using Nebula.Launcher.Views.Pages;
|
2025-03-14 17:08:35 +03:00
|
|
|
using Nebula.Shared.FileApis;
|
2025-01-10 12:28:29 +03:00
|
|
|
using Nebula.Shared.Models;
|
2025-01-11 20:39:58 +03:00
|
|
|
using Nebula.Shared.Services;
|
|
|
|
|
using Nebula.Shared.Utils;
|
2025-01-10 12:28:29 +03:00
|
|
|
|
2025-01-14 22:10:16 +03:00
|
|
|
namespace Nebula.Launcher.ViewModels.Pages;
|
2025-01-10 12:28:29 +03:00
|
|
|
|
|
|
|
|
[ViewModelRegister(typeof(ContentBrowserView))]
|
2025-01-14 22:10:16 +03:00
|
|
|
[ConstructGenerator]
|
2025-06-14 22:33:03 +03:00
|
|
|
public sealed partial class ContentBrowserViewModel : ViewModelBase, IContentHolder
|
2025-01-10 12:28:29 +03:00
|
|
|
{
|
2025-06-14 22:33:03 +03:00
|
|
|
[ObservableProperty] private IContentEntry _currentEntry;
|
2025-01-14 22:10:16 +03:00
|
|
|
[ObservableProperty] private string _serverText = "";
|
2025-06-14 22:33:03 +03:00
|
|
|
[ObservableProperty] private string _searchText = "";
|
2025-01-14 22:10:16 +03:00
|
|
|
[GenerateProperty] private ContentService ContentService { get; } = default!;
|
|
|
|
|
[GenerateProperty] private CancellationService CancellationService { get; } = default!;
|
|
|
|
|
[GenerateProperty] private FileService FileService { get; } = default!;
|
|
|
|
|
[GenerateProperty] private DebugService DebugService { get; } = default!;
|
|
|
|
|
[GenerateProperty] private PopupMessageService PopupService { get; } = default!;
|
2025-06-14 22:33:03 +03:00
|
|
|
[GenerateProperty] private IServiceProvider ServiceProvider { get; }
|
2025-01-22 21:06:05 +03:00
|
|
|
[GenerateProperty, DesignConstruct] private ViewHelperService ViewHelperService { get; } = default!;
|
2025-01-14 22:10:16 +03:00
|
|
|
|
2025-01-11 20:39:58 +03:00
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
public void OnBackEnter()
|
2025-01-11 20:39:58 +03:00
|
|
|
{
|
2025-06-14 22:33:03 +03:00
|
|
|
CurrentEntry.Parent?.GoCurrent();
|
|
|
|
|
}
|
2025-01-12 15:15:01 +03:00
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
public void OnUnpack()
|
|
|
|
|
{
|
|
|
|
|
if(CurrentEntry is not ServerFolderContentEntry serverEntry)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
var myTempDir = FileService.EnsureTempDir(out var tmpDir);
|
|
|
|
|
|
|
|
|
|
var loading = ViewHelperService.GetViewModel<LoadingContextViewModel>();
|
|
|
|
|
loading.LoadingName = "Unpacking entry";
|
|
|
|
|
PopupService.Popup(loading);
|
2025-01-14 22:10:16 +03:00
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
Task.Run(() => ContentService.Unpack(serverEntry.FileApi, myTempDir, loading));
|
|
|
|
|
var startInfo = new ProcessStartInfo(){
|
|
|
|
|
FileName = "explorer.exe",
|
|
|
|
|
Arguments = tmpDir,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Process.Start(startInfo);
|
|
|
|
|
}
|
2025-01-14 22:10:16 +03:00
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
public void OnGoEnter()
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrWhiteSpace(ServerText))
|
|
|
|
|
{
|
|
|
|
|
SetHubRoot();
|
|
|
|
|
SearchText = string.Empty;
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-01-12 15:15:01 +03:00
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var cur = ServiceProvider.GetService<ServerFolderContentEntry>()!;
|
|
|
|
|
cur.Init(this, ServerText.ToRobustUrl());
|
|
|
|
|
var curContent = cur.Go(new ContentPath(SearchText));
|
|
|
|
|
if(curContent == null)
|
|
|
|
|
throw new NullReferenceException($"{SearchText} not found in {ServerText}");
|
2025-03-14 17:08:35 +03:00
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
CurrentEntry = curContent;
|
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
PopupService.Popup(e);
|
|
|
|
|
ServerText = string.Empty;
|
|
|
|
|
SearchText = string.Empty;
|
|
|
|
|
SetHubRoot();
|
2025-01-11 20:39:58 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
partial void OnCurrentEntryChanged(IContentEntry value)
|
|
|
|
|
{
|
|
|
|
|
SearchText = value.FullPath.ToString();
|
|
|
|
|
if (value.GetRoot() is ServerFolderContentEntry serverEntry)
|
|
|
|
|
{
|
|
|
|
|
ServerText = serverEntry.ServerUrl.ToString();
|
|
|
|
|
}
|
2025-03-12 14:51:47 +03:00
|
|
|
}
|
2025-06-14 22:33:03 +03:00
|
|
|
|
2025-01-14 22:10:16 +03:00
|
|
|
protected override void InitialiseInDesignMode()
|
2025-01-10 12:28:29 +03:00
|
|
|
{
|
2025-06-14 22:33:03 +03:00
|
|
|
var root = ViewHelperService.GetViewModel<FolderContentEntry>();
|
|
|
|
|
root.Init(this);
|
|
|
|
|
var child = root.AddFolder("Biba");
|
|
|
|
|
child.AddFolder("Boba");
|
|
|
|
|
child.AddFolder("Buba");
|
|
|
|
|
CurrentEntry = root;
|
2025-01-11 20:39:58 +03:00
|
|
|
}
|
|
|
|
|
|
2025-01-14 22:10:16 +03:00
|
|
|
protected override void Initialise()
|
2025-01-11 20:39:58 +03:00
|
|
|
{
|
2025-06-14 22:33:03 +03:00
|
|
|
SetHubRoot();
|
2025-01-10 12:28:29 +03:00
|
|
|
}
|
|
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
public void SetHubRoot()
|
2025-01-10 12:28:29 +03:00
|
|
|
{
|
2025-06-14 22:33:03 +03:00
|
|
|
var root = ViewHelperService.GetViewModel<ServerListContentEntry>();
|
|
|
|
|
root.InitHubList(this);
|
|
|
|
|
CurrentEntry = root;
|
2025-01-10 12:28:29 +03:00
|
|
|
}
|
|
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
public void Go(RobustUrl url, ContentPath path)
|
2025-01-11 20:39:58 +03:00
|
|
|
{
|
2025-06-14 22:33:03 +03:00
|
|
|
ServerText = url.ToString();
|
|
|
|
|
SearchText = path.ToString();
|
|
|
|
|
OnGoEnter();
|
2025-01-11 20:39:58 +03:00
|
|
|
}
|
2025-06-14 22:33:03 +03:00
|
|
|
}
|
2025-01-10 12:28:29 +03:00
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
public interface IContentHolder
|
|
|
|
|
{
|
|
|
|
|
public IContentEntry CurrentEntry { get; set; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public interface IContentEntry
|
|
|
|
|
{
|
|
|
|
|
public IContentHolder Holder { get; }
|
|
|
|
|
|
|
|
|
|
public IContentEntry? Parent { get; set; }
|
|
|
|
|
public string? Name { get; }
|
|
|
|
|
public string IconPath { get; }
|
|
|
|
|
public ContentPath FullPath => Parent?.FullPath.With(Name) ?? new ContentPath(Name);
|
|
|
|
|
|
|
|
|
|
public IContentEntry? Go(ContentPath path);
|
|
|
|
|
|
|
|
|
|
public void GoCurrent()
|
2025-01-12 22:49:32 +03:00
|
|
|
{
|
2025-06-14 22:33:03 +03:00
|
|
|
var entry = Go(ContentPath.Empty);
|
|
|
|
|
if(entry is not null) Holder.CurrentEntry = entry;
|
2025-05-02 20:06:33 +03:00
|
|
|
}
|
2025-06-14 22:33:03 +03:00
|
|
|
|
|
|
|
|
public IContentEntry GetRoot()
|
2025-05-02 20:06:33 +03:00
|
|
|
{
|
2025-06-14 22:33:03 +03:00
|
|
|
if (Parent is null) return this;
|
|
|
|
|
return Parent.GetRoot();
|
2025-01-12 22:49:32 +03:00
|
|
|
}
|
2025-06-14 22:33:03 +03:00
|
|
|
}
|
2025-01-12 22:49:32 +03:00
|
|
|
|
2025-01-14 22:10:16 +03:00
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
public sealed class LazyContentEntry : IContentEntry
|
|
|
|
|
{
|
|
|
|
|
public IContentHolder Holder { get; set; }
|
|
|
|
|
public IContentEntry? Parent { get; set; }
|
|
|
|
|
public string? Name { get; }
|
|
|
|
|
public string IconPath { get; }
|
2025-01-14 22:10:16 +03:00
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
private readonly IContentEntry _lazyEntry;
|
|
|
|
|
private readonly Action _lazyEntryInit;
|
2025-01-14 22:10:16 +03:00
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
public LazyContentEntry (IContentHolder holder,string name, IContentEntry entry, Action lazyEntryInit)
|
|
|
|
|
{
|
|
|
|
|
Holder = holder;
|
|
|
|
|
Name = name;
|
|
|
|
|
IconPath = entry.IconPath;
|
|
|
|
|
_lazyEntry = entry;
|
|
|
|
|
_lazyEntryInit = lazyEntryInit;
|
|
|
|
|
}
|
|
|
|
|
public IContentEntry? Go(ContentPath path)
|
|
|
|
|
{
|
|
|
|
|
_lazyEntryInit?.Invoke();
|
|
|
|
|
return _lazyEntry;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-01-14 22:10:16 +03:00
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
public sealed class ExtContentExecutor
|
|
|
|
|
{
|
|
|
|
|
public ServerFolderContentEntry _root;
|
|
|
|
|
private DecompilerService _decompilerService;
|
2025-01-14 22:10:16 +03:00
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
public ExtContentExecutor(ServerFolderContentEntry root, DecompilerService decompilerService)
|
|
|
|
|
{
|
|
|
|
|
_root = root;
|
|
|
|
|
_decompilerService = decompilerService;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool TryExecute(RobustManifestItem manifestItem)
|
|
|
|
|
{
|
|
|
|
|
var ext = Path.GetExtension(manifestItem.Path);
|
|
|
|
|
|
|
|
|
|
if (ext == ".dll")
|
2025-01-11 20:39:58 +03:00
|
|
|
{
|
2025-06-14 22:33:03 +03:00
|
|
|
_decompilerService.OpenServerDecompiler(_root.ServerUrl);
|
|
|
|
|
return true;
|
2025-01-11 20:39:58 +03:00
|
|
|
}
|
2025-06-14 22:33:03 +03:00
|
|
|
|
|
|
|
|
return false;
|
2025-01-11 20:39:58 +03:00
|
|
|
}
|
2025-06-14 22:33:03 +03:00
|
|
|
}
|
2025-01-11 20:39:58 +03:00
|
|
|
|
|
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
public sealed partial class ManifestContentEntry : IContentEntry
|
|
|
|
|
{
|
|
|
|
|
public IContentHolder Holder { get; set; } = default!;
|
|
|
|
|
public IContentEntry? Parent { get; set; }
|
|
|
|
|
public string? Name { get; set; }
|
|
|
|
|
public string IconPath => "/Assets/svg/file.svg";
|
|
|
|
|
|
|
|
|
|
private RobustManifestItem _manifestItem;
|
|
|
|
|
private HashApi _hashApi = default!;
|
|
|
|
|
private ExtContentExecutor _extContentExecutor = default!;
|
2025-01-14 22:10:16 +03:00
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
public void Init(IContentHolder holder, RobustManifestItem manifestItem, HashApi api, ExtContentExecutor executor)
|
2025-01-10 12:28:29 +03:00
|
|
|
{
|
2025-06-14 22:33:03 +03:00
|
|
|
Holder = holder;
|
|
|
|
|
Name = new ContentPath(manifestItem.Path).GetName();
|
|
|
|
|
_manifestItem = manifestItem;
|
|
|
|
|
_hashApi = api;
|
|
|
|
|
_extContentExecutor = executor;
|
2025-01-11 20:39:58 +03:00
|
|
|
}
|
2025-06-14 22:33:03 +03:00
|
|
|
|
|
|
|
|
public IContentEntry? Go(ContentPath path)
|
2025-04-20 15:43:57 +03:00
|
|
|
{
|
2025-06-14 22:33:03 +03:00
|
|
|
if (_extContentExecutor.TryExecute(_manifestItem))
|
|
|
|
|
return null;
|
2025-04-20 15:43:57 +03:00
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
var ext = Path.GetExtension(_manifestItem.Path);
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (!_hashApi.TryOpen(_manifestItem, out var stream))
|
|
|
|
|
return null;
|
2025-04-20 15:43:57 +03:00
|
|
|
|
|
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
var myTempFile = Path.Combine(Path.GetTempPath(), "tempie" + ext);
|
2025-03-12 14:51:47 +03:00
|
|
|
|
2025-01-11 20:39:58 +03:00
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
var sw = new FileStream(myTempFile, FileMode.Create, FileAccess.Write, FileShare.None);
|
|
|
|
|
stream.CopyTo(sw);
|
2025-01-11 20:39:58 +03:00
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
sw.Dispose();
|
|
|
|
|
stream.Dispose();
|
2025-01-14 22:10:16 +03:00
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
var startInfo = new ProcessStartInfo(myTempFile)
|
|
|
|
|
{
|
|
|
|
|
UseShellExecute = true
|
|
|
|
|
};
|
2025-01-11 20:39:58 +03:00
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
Process.Start(startInfo);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
_extContentExecutor._root.PopupService.Popup(e);
|
|
|
|
|
}
|
|
|
|
|
return null;
|
2025-01-11 20:39:58 +03:00
|
|
|
}
|
2025-06-14 22:33:03 +03:00
|
|
|
}
|
2025-01-11 20:39:58 +03:00
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
[ViewModelRegister(typeof(FileContentEntryView), false), ConstructGenerator]
|
|
|
|
|
public sealed partial class FolderContentEntry : BaseFolderContentEntry
|
|
|
|
|
{
|
|
|
|
|
[GenerateProperty, DesignConstruct] public override ViewHelperService ViewHelperService { get; } = default!;
|
|
|
|
|
|
|
|
|
|
public FolderContentEntry AddFolder(string folderName)
|
2025-01-11 20:39:58 +03:00
|
|
|
{
|
2025-06-14 22:33:03 +03:00
|
|
|
var folder = ViewHelperService.GetViewModel<FolderContentEntry>();
|
|
|
|
|
folder.Init(Holder, folderName);
|
|
|
|
|
return AddChild(folder);
|
2025-01-11 20:39:58 +03:00
|
|
|
}
|
2025-01-29 12:32:42 +03:00
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
protected override void InitialiseInDesignMode() { }
|
|
|
|
|
protected override void Initialise() { }
|
2025-01-10 12:28:29 +03:00
|
|
|
}
|
|
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
[ViewModelRegister(typeof(FileContentEntryView), false), ConstructGenerator]
|
|
|
|
|
public sealed partial class ServerFolderContentEntry : BaseFolderContentEntry
|
2025-01-10 12:28:29 +03:00
|
|
|
{
|
2025-06-14 22:33:03 +03:00
|
|
|
[GenerateProperty, DesignConstruct] public override ViewHelperService ViewHelperService { get; } = default!;
|
|
|
|
|
[GenerateProperty] public ContentService ContentService { get; } = default!;
|
|
|
|
|
[GenerateProperty] public CancellationService CancellationService { get; } = default!;
|
|
|
|
|
[GenerateProperty] public PopupMessageService PopupService { get; } = default!;
|
|
|
|
|
[GenerateProperty] public DecompilerService DecompilerService { get; } = default!;
|
2025-03-16 09:44:54 +03:00
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
public RobustUrl ServerUrl { get; private set; }
|
2025-01-14 22:10:16 +03:00
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
public HashApi FileApi { get; private set; } = default!;
|
|
|
|
|
|
|
|
|
|
private ExtContentExecutor _contentExecutor = default!;
|
|
|
|
|
|
|
|
|
|
public void Init(IContentHolder holder, RobustUrl serverUrl)
|
2025-01-14 22:10:16 +03:00
|
|
|
{
|
2025-06-14 22:33:03 +03:00
|
|
|
base.Init(holder);
|
|
|
|
|
_contentExecutor = new ExtContentExecutor(this, DecompilerService);
|
|
|
|
|
IsLoading = true;
|
|
|
|
|
var loading = ViewHelperService.GetViewModel<LoadingContextViewModel>();
|
|
|
|
|
loading.LoadingName = "Loading entry";
|
|
|
|
|
PopupService.Popup(loading);
|
|
|
|
|
ServerUrl = serverUrl;
|
2025-03-14 17:08:35 +03:00
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
Task.Run(async () =>
|
|
|
|
|
{
|
|
|
|
|
var buildInfo = await ContentService.GetBuildInfo(serverUrl, CancellationService.Token);
|
|
|
|
|
FileApi = await ContentService.EnsureItems(buildInfo.RobustManifestInfo, loading,
|
|
|
|
|
CancellationService.Token);
|
2025-03-14 17:08:35 +03:00
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
foreach (var (path, item) in FileApi.Manifest)
|
|
|
|
|
{
|
|
|
|
|
CreateContent(new ContentPath(path), item);
|
|
|
|
|
}
|
2025-03-14 17:08:35 +03:00
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
IsLoading = false;
|
|
|
|
|
loading.Dispose();
|
|
|
|
|
});
|
2025-01-11 20:39:58 +03:00
|
|
|
}
|
|
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
public ManifestContentEntry CreateContent(ContentPath path, RobustManifestItem manifestItem)
|
2025-01-11 20:39:58 +03:00
|
|
|
{
|
2025-06-14 22:33:03 +03:00
|
|
|
var pathDir = path.GetDirectory();
|
|
|
|
|
BaseFolderContentEntry parent = this;
|
|
|
|
|
|
|
|
|
|
while (pathDir.TryNext(out var dirPart))
|
2025-01-11 20:39:58 +03:00
|
|
|
{
|
2025-06-14 22:33:03 +03:00
|
|
|
if (!parent.TryGetChild(dirPart, out var folderContentEntry))
|
|
|
|
|
{
|
|
|
|
|
folderContentEntry = ViewHelperService.GetViewModel<FolderContentEntry>();
|
|
|
|
|
((FolderContentEntry)folderContentEntry).Init(Holder, dirPart);
|
|
|
|
|
parent.AddChild(folderContentEntry);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
parent = folderContentEntry as BaseFolderContentEntry ?? throw new InvalidOperationException();
|
2025-01-11 20:39:58 +03:00
|
|
|
}
|
2025-06-14 22:33:03 +03:00
|
|
|
|
|
|
|
|
var manifestContent = new ManifestContentEntry();
|
|
|
|
|
manifestContent.Init(Holder, manifestItem, FileApi, _contentExecutor);
|
|
|
|
|
|
|
|
|
|
parent.AddChild(manifestContent);
|
|
|
|
|
|
|
|
|
|
return manifestContent;
|
2025-01-11 20:39:58 +03:00
|
|
|
}
|
2025-06-14 22:33:03 +03:00
|
|
|
|
|
|
|
|
protected override void InitialiseInDesignMode() { }
|
|
|
|
|
protected override void Initialise() { }
|
|
|
|
|
}
|
2025-01-11 20:39:58 +03:00
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
[ViewModelRegister(typeof(FileContentEntryView), false), ConstructGenerator]
|
|
|
|
|
public sealed partial class ServerListContentEntry : BaseFolderContentEntry
|
|
|
|
|
{
|
|
|
|
|
[GenerateProperty, DesignConstruct] public override ViewHelperService ViewHelperService { get; } = default!;
|
|
|
|
|
[GenerateProperty] public ConfigurationService ConfigurationService { get; } = default!;
|
|
|
|
|
[GenerateProperty] public IServiceProvider ServiceProvider { get; } = default!;
|
|
|
|
|
[GenerateProperty] public RestService RestService { get; } = default!;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public void InitHubList(IContentHolder holder)
|
2025-01-11 20:39:58 +03:00
|
|
|
{
|
2025-06-14 22:33:03 +03:00
|
|
|
base.Init(holder);
|
|
|
|
|
|
|
|
|
|
var servers = ConfigurationService.GetConfigValue(LauncherConVar.Hub)!;
|
|
|
|
|
|
|
|
|
|
foreach (var server in servers)
|
2025-01-11 20:39:58 +03:00
|
|
|
{
|
2025-06-14 22:33:03 +03:00
|
|
|
var serverFolder = ServiceProvider.GetService<ServerListContentEntry>()!;
|
|
|
|
|
var serverLazy = new LazyContentEntry(Holder, server.Name , serverFolder, () => serverFolder.InitServerList(Holder, server));
|
|
|
|
|
AddChild(serverLazy);
|
2025-01-11 20:39:58 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
public async void InitServerList(IContentHolder holder, ServerHubRecord hubRecord)
|
2025-01-11 20:39:58 +03:00
|
|
|
{
|
2025-06-14 22:33:03 +03:00
|
|
|
base.Init(holder, hubRecord.Name);
|
2025-01-12 22:49:32 +03:00
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
IsLoading = true;
|
|
|
|
|
var servers =
|
|
|
|
|
await RestService.GetAsync<List<ServerHubInfo>>(new Uri(hubRecord.MainUrl), CancellationToken.None);
|
2025-01-14 22:10:16 +03:00
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
foreach (var server in servers)
|
2025-01-11 20:39:58 +03:00
|
|
|
{
|
2025-06-14 22:33:03 +03:00
|
|
|
var serverFolder = ServiceProvider.GetService<ServerFolderContentEntry>()!;
|
|
|
|
|
var serverLazy = new LazyContentEntry(Holder, server.StatusData.Name , serverFolder, () => serverFolder.Init(Holder, server.Address.ToRobustUrl()));
|
|
|
|
|
AddChild(serverLazy);
|
2025-01-11 20:39:58 +03:00
|
|
|
}
|
|
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
IsLoading = true;
|
2025-01-11 20:39:58 +03:00
|
|
|
}
|
|
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
protected override void InitialiseInDesignMode()
|
2025-01-11 20:39:58 +03:00
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
protected override void Initialise()
|
2025-01-11 20:39:58 +03:00
|
|
|
{
|
2025-06-14 22:33:03 +03:00
|
|
|
}
|
|
|
|
|
}
|
2025-01-11 20:39:58 +03:00
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
public abstract class BaseFolderContentEntry : ViewModelBase, IContentEntry
|
|
|
|
|
{
|
|
|
|
|
public bool IsLoading { get; set; } = false;
|
|
|
|
|
public abstract ViewHelperService ViewHelperService { get; }
|
|
|
|
|
|
|
|
|
|
public ObservableCollection<IContentEntry> Entries { get; } = [];
|
2025-01-14 22:10:16 +03:00
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
private Dictionary<string, IContentEntry> _childs = [];
|
2025-01-11 20:39:58 +03:00
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
public string IconPath => "/Assets/svg/folder.svg";
|
|
|
|
|
public IContentHolder Holder { get; private set; }
|
|
|
|
|
public IContentEntry? Parent { get; set; }
|
|
|
|
|
public string? Name { get; private set; }
|
|
|
|
|
|
|
|
|
|
public IContentEntry? Go(ContentPath path)
|
2025-01-11 20:39:58 +03:00
|
|
|
{
|
2025-06-14 22:33:03 +03:00
|
|
|
if (path.IsEmpty()) return this;
|
|
|
|
|
if (_childs.TryGetValue(path.GetNext(), out var child))
|
|
|
|
|
return child.Go(path);
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2025-01-12 22:49:32 +03:00
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
public void Init(IContentHolder holder, string? name = null)
|
|
|
|
|
{
|
|
|
|
|
Name = name;
|
|
|
|
|
Holder = holder;
|
|
|
|
|
}
|
2025-01-14 22:10:16 +03:00
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
public T AddChild<T>(T child) where T: IContentEntry
|
|
|
|
|
{
|
|
|
|
|
if(child.Name is null) throw new InvalidOperationException();
|
|
|
|
|
|
|
|
|
|
child.Parent = this;
|
|
|
|
|
|
|
|
|
|
_childs.Add(child.Name, child);
|
|
|
|
|
Entries.Add(child);
|
2025-01-11 20:39:58 +03:00
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
return child;
|
2025-01-11 20:39:58 +03:00
|
|
|
}
|
|
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
public bool TryGetChild(string name,[NotNullWhen(true)] out IContentEntry? child)
|
2025-01-11 20:39:58 +03:00
|
|
|
{
|
2025-06-14 22:33:03 +03:00
|
|
|
return _childs.TryGetValue(name, out child);
|
2025-01-11 20:39:58 +03:00
|
|
|
}
|
2025-01-10 12:28:29 +03:00
|
|
|
}
|
|
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
|
|
|
|
|
public struct ContentPath : IEquatable<ContentPath>
|
2025-01-10 12:28:29 +03:00
|
|
|
{
|
2025-06-14 22:33:03 +03:00
|
|
|
public static readonly ContentPath Empty = new();
|
|
|
|
|
|
2025-01-14 22:10:16 +03:00
|
|
|
public List<string> Pathes { get; }
|
2025-01-11 20:39:58 +03:00
|
|
|
|
2025-05-02 20:06:33 +03:00
|
|
|
public ContentPath()
|
|
|
|
|
{
|
|
|
|
|
Pathes = [];
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-11 20:39:58 +03:00
|
|
|
public ContentPath(List<string> pathes)
|
|
|
|
|
{
|
2025-01-14 22:10:16 +03:00
|
|
|
Pathes = pathes;
|
2025-01-11 20:39:58 +03:00
|
|
|
}
|
|
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
public ContentPath(string? path)
|
2025-01-11 20:39:58 +03:00
|
|
|
{
|
2025-01-12 22:49:32 +03:00
|
|
|
Pathes = string.IsNullOrEmpty(path)
|
|
|
|
|
? new List<string>()
|
2025-06-14 22:33:03 +03:00
|
|
|
: path.Split(['/'], StringSplitOptions.RemoveEmptyEntries).ToList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ContentPath With(string? name)
|
|
|
|
|
{
|
|
|
|
|
if (name != null) return new ContentPath([..Pathes, name]);
|
|
|
|
|
return new ContentPath(Pathes);
|
2025-01-11 20:39:58 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ContentPath GetDirectory()
|
|
|
|
|
{
|
2025-01-12 22:49:32 +03:00
|
|
|
if (Pathes.Count == 0)
|
2025-06-14 22:33:03 +03:00
|
|
|
return this;
|
2025-01-12 22:49:32 +03:00
|
|
|
|
|
|
|
|
var directoryPathes = Pathes.Take(Pathes.Count - 1).ToList();
|
|
|
|
|
return new ContentPath(directoryPathes);
|
2025-01-11 20:39:58 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public string GetName()
|
|
|
|
|
{
|
2025-01-12 22:49:32 +03:00
|
|
|
if (Pathes.Count == 0)
|
|
|
|
|
throw new InvalidOperationException("Cannot get the name of the root path.");
|
|
|
|
|
|
2025-01-11 20:39:58 +03:00
|
|
|
return Pathes.Last();
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-12 22:49:32 +03:00
|
|
|
public string GetNext()
|
|
|
|
|
{
|
|
|
|
|
if (Pathes.Count == 0)
|
|
|
|
|
throw new InvalidOperationException("No elements left to retrieve from the root.");
|
|
|
|
|
|
|
|
|
|
var nextName = Pathes[0];
|
|
|
|
|
Pathes.RemoveAt(0);
|
|
|
|
|
|
|
|
|
|
return string.IsNullOrWhiteSpace(nextName) ? GetNext() : nextName;
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
public bool TryNext([NotNullWhen(true)]out string? part)
|
|
|
|
|
{
|
|
|
|
|
part = null;
|
|
|
|
|
if (Pathes.Count == 0) return false;
|
|
|
|
|
part = GetNext();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-12 15:15:01 +03:00
|
|
|
public ContentPath Clone()
|
|
|
|
|
{
|
2025-01-12 22:49:32 +03:00
|
|
|
return new ContentPath(new List<string>(Pathes));
|
2025-01-12 15:15:01 +03:00
|
|
|
}
|
|
|
|
|
|
2025-01-12 22:49:32 +03:00
|
|
|
public string Path => Pathes.Count == 0 ? "/" : string.Join("/", Pathes);
|
|
|
|
|
|
|
|
|
|
public override string ToString()
|
|
|
|
|
{
|
|
|
|
|
return Path;
|
|
|
|
|
}
|
2025-06-14 22:33:03 +03:00
|
|
|
|
|
|
|
|
public bool IsEmpty()
|
|
|
|
|
{
|
|
|
|
|
return Pathes.Count == 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool Equals(ContentPath other)
|
|
|
|
|
{
|
|
|
|
|
return Pathes.Equals(other.Pathes);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override bool Equals(object? obj)
|
|
|
|
|
{
|
|
|
|
|
return obj is ContentPath other && Equals(other);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override int GetHashCode()
|
|
|
|
|
{
|
|
|
|
|
return Pathes.GetHashCode();
|
|
|
|
|
}
|
2025-03-16 09:44:54 +03:00
|
|
|
}
|
|
|
|
|
|
2025-06-14 22:33:03 +03:00
|
|
|
public sealed class ContentComparer : IComparer<IContentEntry>
|
2025-03-16 09:44:54 +03:00
|
|
|
{
|
2025-06-14 22:33:03 +03:00
|
|
|
public int Compare(IContentEntry? x, IContentEntry? y)
|
2025-03-16 09:44:54 +03:00
|
|
|
{
|
|
|
|
|
if (ReferenceEquals(x, y)) return 0;
|
|
|
|
|
if (y is null) return 1;
|
|
|
|
|
if (x is null) return -1;
|
2025-06-14 22:33:03 +03:00
|
|
|
return string.Compare(x.Name, y.Name, StringComparison.Ordinal);
|
2025-03-16 09:44:54 +03:00
|
|
|
}
|
2025-01-12 22:49:32 +03:00
|
|
|
}
|