- add: ContentView think
This commit is contained in:
BIN
Nebula.Launcher/Assets/back.png
Normal file
BIN
Nebula.Launcher/Assets/back.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
BIN
Nebula.Launcher/Assets/dir.png
Normal file
BIN
Nebula.Launcher/Assets/dir.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
BIN
Nebula.Launcher/Assets/file.png
Normal file
BIN
Nebula.Launcher/Assets/file.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
BIN
Nebula.Launcher/Assets/go.png
Normal file
BIN
Nebula.Launcher/Assets/go.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
@@ -1,48 +1,351 @@
|
||||
using System;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Platform;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Nebula.Launcher.ViewHelper;
|
||||
using Nebula.Launcher.Views.Pages;
|
||||
using Nebula.Shared;
|
||||
using Nebula.Shared.Models;
|
||||
using Nebula.Shared.Services;
|
||||
using Nebula.Shared.Utils;
|
||||
|
||||
namespace Nebula.Launcher.ViewModels;
|
||||
|
||||
[ViewModelRegister(typeof(ContentBrowserView))]
|
||||
public sealed partial class ContentBrowserViewModel : ViewModelBase
|
||||
{
|
||||
public ObservableCollection<ContentEntry> Entries = new();
|
||||
private readonly IServiceProvider _provider;
|
||||
private readonly ContentService _contentService;
|
||||
private readonly CancellationService _cancellationService;
|
||||
private readonly DebugService _debugService;
|
||||
private readonly PopupMessageService _popupService;
|
||||
public ObservableCollection<ContentEntry> Entries { get; } = new();
|
||||
private readonly List<ContentEntry> _root = new();
|
||||
|
||||
private List<string> _history = new();
|
||||
|
||||
[ObservableProperty] private string _message = "";
|
||||
[ObservableProperty] private string _searchText = "";
|
||||
|
||||
private ContentEntry? _selectedEntry;
|
||||
|
||||
public ContentEntry? SelectedEntry
|
||||
{
|
||||
get => _selectedEntry;
|
||||
set
|
||||
{
|
||||
_selectedEntry = value;
|
||||
Entries.Clear();
|
||||
|
||||
Console.WriteLine("Entries clear!");
|
||||
|
||||
if(value == null) return;
|
||||
|
||||
foreach (var (_,entryCh) in value.Childs)
|
||||
{
|
||||
Entries.Add(entryCh);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public ContentBrowserViewModel() : base()
|
||||
{
|
||||
|
||||
var a = new ContentEntry(this, "A:", "");
|
||||
var b = new ContentEntry(this, "B", "");
|
||||
a.TryAddChild(b);
|
||||
Entries.Add(a);
|
||||
}
|
||||
|
||||
public ContentBrowserViewModel(IServiceProvider provider) : base(provider)
|
||||
public ContentBrowserViewModel(IServiceProvider provider, ContentService contentService, CancellationService cancellationService,
|
||||
FileService fileService, HubService hubService, DebugService debugService, PopupMessageService popupService) : base(provider)
|
||||
{
|
||||
_provider = provider;
|
||||
_contentService = contentService;
|
||||
_cancellationService = cancellationService;
|
||||
_debugService = debugService;
|
||||
_popupService = popupService;
|
||||
|
||||
hubService.HubServerChangedEventArgs += HubServerChangedEventArgs;
|
||||
hubService.HubServerLoaded += GoHome;
|
||||
}
|
||||
|
||||
private void GoHome()
|
||||
{
|
||||
SelectedEntry = null;
|
||||
foreach (var entry in _root)
|
||||
{
|
||||
Entries.Add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
private void HubServerChangedEventArgs(HubServerChangedEventArgs obj)
|
||||
{
|
||||
if(obj.Action == HubServerChangeAction.Clear) _root.Clear();
|
||||
if (obj.Action == HubServerChangeAction.Add)
|
||||
{
|
||||
foreach (var info in obj.Items)
|
||||
{
|
||||
_root.Add(new ContentEntry(this, ToContentUrl(info.Address.ToRobustUrl()),info.Address));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public async void Go(ContentPath path)
|
||||
{
|
||||
if (path.Pathes.Count == 0)
|
||||
{
|
||||
SearchText = "";
|
||||
GoHome();
|
||||
return;
|
||||
}
|
||||
|
||||
if (path.Pathes[0] != "content:" || path.Pathes.Count < 3)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SearchText = path.Path;
|
||||
|
||||
path.Pathes.RemoveAt(0);
|
||||
path.Pathes.RemoveAt(0);
|
||||
|
||||
var serverUrl = path.Pathes[0];
|
||||
path.Pathes.RemoveAt(0);
|
||||
|
||||
_debugService.Debug(path.Path + " " + serverUrl +" "+ SelectedEntry?.ServerName);
|
||||
|
||||
try
|
||||
{
|
||||
ContentEntry entry;
|
||||
if (serverUrl == SelectedEntry?.ServerName)
|
||||
entry = SelectedEntry.GetRoot();
|
||||
else
|
||||
entry = await CreateEntry(serverUrl);
|
||||
|
||||
if (!entry.TryGetEntry(path, out var centry))
|
||||
{
|
||||
throw new Exception("Not found!");
|
||||
}
|
||||
|
||||
SelectedEntry = centry;
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
_popupService.Popup(e);
|
||||
//throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void OnBackEnter()
|
||||
{
|
||||
|
||||
Go(new ContentPath(GetHistory()));
|
||||
}
|
||||
|
||||
public void OnGoEnter()
|
||||
{
|
||||
Go(new ContentPath(SearchText));
|
||||
}
|
||||
|
||||
private async Task<ContentEntry> CreateEntry(string serverUrl)
|
||||
{
|
||||
var rurl = serverUrl.ToRobustUrl();
|
||||
var info = await _contentService.GetBuildInfo(rurl, _cancellationService.Token);
|
||||
var loading = _provider.GetService<LoadingContextViewModel>()!;
|
||||
loading.LoadingName = "Loading entry";
|
||||
_popupService.Popup(loading);
|
||||
var items = await _contentService.EnsureItems(info.RobustManifestInfo, loading,
|
||||
_cancellationService.Token);
|
||||
|
||||
var rootEntry = new ContentEntry(this,ToContentUrl(rurl), serverUrl);
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
var path = new ContentPath(item.Path);
|
||||
rootEntry.CreateItem(path, item);
|
||||
}
|
||||
|
||||
loading.Dispose();
|
||||
|
||||
return rootEntry;
|
||||
}
|
||||
|
||||
private void AppendHistory(string str)
|
||||
{
|
||||
if(_history.Count >= 10) _history.RemoveAt(9);
|
||||
_history.Insert(0, str);
|
||||
}
|
||||
|
||||
private string GetHistory()
|
||||
{
|
||||
if (_history.Count == 0) return "";
|
||||
var h = _history[0];
|
||||
_history.RemoveAt(0);
|
||||
return h;
|
||||
}
|
||||
|
||||
private string ToContentUrl(RobustUrl serverUrl)
|
||||
{
|
||||
var port = serverUrl.Uri.Port != -1 ? (":"+serverUrl.Uri.Port) : "";
|
||||
return "content://" + serverUrl.Uri.Host + port;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ContentEntry
|
||||
public class ContentEntry
|
||||
{
|
||||
private readonly ContentBrowserViewModel _viewModel;
|
||||
|
||||
public static IImage DirImage = new Bitmap(AssetLoader.Open(new Uri("avares://Nebula.Launcher/Assets/dir.png")));
|
||||
public static IImage IconImage = new Bitmap(AssetLoader.Open(new Uri("avares://Nebula.Launcher/Assets/file.png")));
|
||||
|
||||
public RobustManifestItem? Item;
|
||||
public bool IsDirectory => Item == null;
|
||||
|
||||
public string Name { get; private set; }
|
||||
public string ServerName { get; private set; }
|
||||
public IImage IconPath { get; set; } = DirImage;
|
||||
|
||||
public ContentEntry? Parent { get; private set; }
|
||||
public bool IsRoot => Parent == null;
|
||||
|
||||
private readonly Dictionary<string, ContentEntry> _childs = new();
|
||||
|
||||
public IReadOnlyDictionary<string, ContentEntry> Childs => _childs.ToFrozenDictionary();
|
||||
|
||||
public bool TryGetChild(string name,[NotNullWhen(true)] out ContentEntry? child)
|
||||
{
|
||||
return _childs.TryGetValue(name, out child);
|
||||
}
|
||||
|
||||
public bool TryAddChild(ContentEntry contentEntry)
|
||||
{
|
||||
if(_childs.TryAdd(contentEntry.Name, contentEntry))
|
||||
{
|
||||
contentEntry.Parent = this;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal ContentEntry(ContentBrowserViewModel viewModel, string name, string serverName)
|
||||
{
|
||||
Name = name;
|
||||
ServerName = serverName;
|
||||
_viewModel = viewModel;
|
||||
}
|
||||
|
||||
public ContentPath GetPath()
|
||||
{
|
||||
if (Parent != null)
|
||||
{
|
||||
var path = Parent.GetPath();
|
||||
path.Pathes.Add(Name);
|
||||
return path;
|
||||
}
|
||||
return new ContentPath(Name);
|
||||
}
|
||||
|
||||
public ContentEntry GetOrCreateDirectory(ContentPath rootPath)
|
||||
{
|
||||
if (rootPath.Pathes.Count == 0) return this;
|
||||
|
||||
var fName = rootPath.Pathes[0];
|
||||
rootPath.Pathes.RemoveAt(0);
|
||||
|
||||
if(!TryGetChild(fName, out var child))
|
||||
{
|
||||
child = new ContentEntry(_viewModel, fName, ServerName);
|
||||
TryAddChild(child);
|
||||
}
|
||||
|
||||
return child.GetOrCreateDirectory(rootPath);
|
||||
}
|
||||
|
||||
public ContentEntry GetRoot()
|
||||
{
|
||||
if (Parent == null) return this;
|
||||
return Parent.GetRoot();
|
||||
}
|
||||
|
||||
public ContentEntry CreateItem(ContentPath path, RobustManifestItem item)
|
||||
{
|
||||
var dir = path.GetDirectory();
|
||||
var dirEntry = GetOrCreateDirectory(dir);
|
||||
|
||||
var entry = new ContentEntry(_viewModel, path.GetName(), ServerName)
|
||||
{
|
||||
Item = item
|
||||
};
|
||||
|
||||
dirEntry.TryAddChild(entry);
|
||||
entry.IconPath = IconImage;
|
||||
return entry;
|
||||
}
|
||||
|
||||
public bool TryGetEntry(ContentPath path, out ContentEntry? entry)
|
||||
{
|
||||
entry = null;
|
||||
|
||||
if (path.Pathes.Count == 0)
|
||||
{
|
||||
entry = this;
|
||||
return true;
|
||||
}
|
||||
|
||||
var fName = path.Pathes[0];
|
||||
path.Pathes.RemoveAt(0);
|
||||
|
||||
if(!TryGetChild(fName, out var child))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return child.TryGetEntry(path, out entry);
|
||||
}
|
||||
|
||||
public void OnPathGo()
|
||||
{
|
||||
_viewModel.Go(GetPath());
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ContentPath
|
||||
public struct ContentPath
|
||||
{
|
||||
public RobustUrl ServerUrl;
|
||||
public List<string> Pathes;
|
||||
|
||||
public ContentPath(List<string> pathes)
|
||||
{
|
||||
Pathes = pathes;
|
||||
}
|
||||
|
||||
public ContentPath(string path)
|
||||
{
|
||||
Pathes = path.Split("/").ToList();
|
||||
}
|
||||
|
||||
public ContentPath GetDirectory()
|
||||
{
|
||||
var p = Pathes.ToList();
|
||||
p.RemoveAt(Pathes.Count - 1);
|
||||
return new ContentPath(p);
|
||||
}
|
||||
|
||||
public string GetName()
|
||||
{
|
||||
return Pathes.Last();
|
||||
}
|
||||
|
||||
public string Path => string.Join("/", Pathes);
|
||||
}
|
||||
@@ -45,7 +45,8 @@ public partial class MainViewModel : ViewModelBase
|
||||
private readonly List<ListItemTemplate> _templates =
|
||||
[
|
||||
new ListItemTemplate(typeof(AccountInfoViewModel), "Account", "Account"),
|
||||
new ListItemTemplate(typeof(ServerListViewModel), "HomeRegular", "Servers")
|
||||
new ListItemTemplate(typeof(ServerListViewModel), "HomeRegular", "Servers"),
|
||||
new ListItemTemplate(typeof(ContentBrowserViewModel), "HomeRegular", "Content")
|
||||
];
|
||||
|
||||
[ObservableProperty]
|
||||
|
||||
@@ -34,9 +34,15 @@ public partial class ServerListViewModel : ViewModelBase
|
||||
_serviceProvider = serviceProvider;
|
||||
_hubService = hubService;
|
||||
hubService.HubServerChangedEventArgs += HubServerChangedEventArgs;
|
||||
hubService.HubServerLoaded += HubServerLoaded;
|
||||
OnSearchChange += OnChangeSearch;
|
||||
}
|
||||
|
||||
private void HubServerLoaded()
|
||||
{
|
||||
SortServers();
|
||||
}
|
||||
|
||||
private void OnChangeSearch()
|
||||
{
|
||||
SortServers();
|
||||
@@ -62,8 +68,6 @@ public partial class ServerListViewModel : ViewModelBase
|
||||
{
|
||||
UnsortedServers.Clear();
|
||||
}
|
||||
|
||||
SortServers();
|
||||
}
|
||||
|
||||
private void SortServers()
|
||||
|
||||
@@ -1,39 +1,69 @@
|
||||
<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:viewModels="clr-namespace:Nebula.Launcher.ViewModels"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Nebula.Launcher.Views.Pages.ContentBrowserView"
|
||||
x:DataType="viewModels:ContentBrowserViewModel">
|
||||
<UserControl
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
mc:Ignorable="d"
|
||||
x:Class="Nebula.Launcher.Views.Pages.ContentBrowserView"
|
||||
x:DataType="viewModels:ContentBrowserViewModel"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:viewModels="clr-namespace:Nebula.Launcher.ViewModels"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Design.DataContext>
|
||||
<viewModels:ContentBrowserViewModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<StackPanel>
|
||||
<Border
|
||||
BorderThickness="2,0,0,0"
|
||||
CornerRadius="10"
|
||||
Grid.Row="1">
|
||||
<StackPanel Margin="5">
|
||||
<Border BorderThickness="2,0,0,0" CornerRadius="10">
|
||||
<Grid ColumnDefinitions="*,40,40" RowDefinitions="*">
|
||||
<TextBox
|
||||
Margin="0"
|
||||
Text="{Binding SearchText}"
|
||||
VerticalAlignment="Center"
|
||||
Watermark="Server name..." />
|
||||
Watermark="Path..." />
|
||||
<Button
|
||||
Command="{Binding OnBackEnter}"
|
||||
Grid.Column="1"
|
||||
Padding="10">
|
||||
<Image Source="/Assets/filter.png" />
|
||||
<Image Source="/Assets/back.png" />
|
||||
</Button>
|
||||
<Button
|
||||
Command="{Binding OnGoEnter}"
|
||||
Grid.Column="2"
|
||||
Padding="10">
|
||||
<Image Source="/Assets/refresh.png" />
|
||||
<Image Source="/Assets/go.png" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</Border>
|
||||
<ScrollViewer Margin="0,5,10,5">
|
||||
<ItemsControl
|
||||
Background="#00000000"
|
||||
ItemsSource="{Binding Entries}"
|
||||
Padding="0">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type viewModels:ContentEntry}">
|
||||
<Button
|
||||
Command="{Binding OnPathGo}"
|
||||
CornerRadius="0"
|
||||
HorizontalAlignment="Stretch">
|
||||
<StackPanel Orientation="Horizontal" Spacing="15">
|
||||
<Border
|
||||
Background="#00000000"
|
||||
BorderThickness="0,0,2,0"
|
||||
CornerRadius="0">
|
||||
<Image
|
||||
Height="15"
|
||||
Margin="10,0,10,0"
|
||||
Source="{Binding IconPath}" />
|
||||
</Border>
|
||||
<Label>
|
||||
<TextBlock Text="{Binding Name}" />
|
||||
</Label>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
|
||||
@@ -15,7 +15,10 @@
|
||||
<viewModels:ServerListViewModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<Grid ColumnDefinitions="*" RowDefinitions="*,40" Margin="15">
|
||||
<Grid
|
||||
ColumnDefinitions="*"
|
||||
Margin="5"
|
||||
RowDefinitions="*,40">
|
||||
<ScrollViewer Margin="0,0,0,10" Padding="0,0,8,0">
|
||||
<ItemsControl
|
||||
Background="#00000000"
|
||||
@@ -122,29 +125,32 @@
|
||||
<Panel Grid.Column="1" Grid.Row="1">
|
||||
<Button
|
||||
Command="{Binding RunInstance}"
|
||||
IsVisible="{Binding RunVisible}"
|
||||
HorizontalAlignment="Stretch"
|
||||
CornerRadius="10,0,10,10"
|
||||
HorizontalAlignment="Stretch"
|
||||
IsVisible="{Binding RunVisible}"
|
||||
VerticalAlignment="Stretch">
|
||||
<Label HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
Play
|
||||
</Label>
|
||||
</Button>
|
||||
<Grid Grid.ColumnDefinitions="*,*" IsVisible="{Binding !RunVisible}">
|
||||
<Button Command="{Binding ReadLog}"
|
||||
CornerRadius="10,0,0,10"
|
||||
Margin="0,0,1,0"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalAlignment="Stretch">
|
||||
<Button
|
||||
Command="{Binding ReadLog}"
|
||||
CornerRadius="10,0,0,10"
|
||||
HorizontalAlignment="Stretch"
|
||||
Margin="0,0,1,0"
|
||||
VerticalAlignment="Stretch">
|
||||
<Label HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
Log
|
||||
</Label>
|
||||
</Button>
|
||||
<Button Grid.Column="1" HorizontalAlignment="Stretch"
|
||||
CornerRadius="0,0,10,0"
|
||||
Margin="1,0,0,0"
|
||||
VerticalAlignment="Stretch"
|
||||
Command="{Binding StopInstance}">
|
||||
<Button
|
||||
Command="{Binding StopInstance}"
|
||||
CornerRadius="0,0,10,0"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
Margin="1,0,0,0"
|
||||
VerticalAlignment="Stretch">
|
||||
<Label HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
Stop
|
||||
</Label>
|
||||
|
||||
@@ -9,6 +9,7 @@ public class HubService
|
||||
private readonly RestService _restService;
|
||||
|
||||
public Action<HubServerChangedEventArgs>? HubServerChangedEventArgs;
|
||||
public Action? HubServerLoaded;
|
||||
|
||||
private bool _isUpdating = false;
|
||||
public HubService(ConfigurationService configurationService, RestService restService)
|
||||
@@ -35,6 +36,7 @@ public class HubService
|
||||
}
|
||||
|
||||
_isUpdating = false;
|
||||
HubServerLoaded?.Invoke();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user