using System.Text;
namespace Editor.LibraryManager;
class LibraryDetail : Widget
{
///
/// The package holding the description for this library
///
Package Package;
///
/// If this is installed, this will be the installed project
///
LibraryProject Installed;
///
/// List of versions
///
ComboBox VersionList;
///
/// The "install" button
///
Button ActionButton;
///
/// The "uninstall" button
///
Button UninstallButton;
///
/// The "properties" button
///
Button PropertiesButton;
///
/// The revision currently selected in the version list
///
Package.IRevision SelectedRevision;
public LibraryDetail( Package package ) : base()
{
Package = package;
Layout = Layout.Column();
Layout.Margin = 8;
Layout.Spacing = 4;
_ = FetchAndBuild();
}
async Task FetchAndBuild()
{
if ( !this.IsValid() ) return;
Layout.Clear( true );
if ( Package is null )
return;
if ( !Sandbox.Package.TryParseIdent( Package.FullIdent, out var ident ) )
return;
Installed = LibrarySystem.All.FirstOrDefault( x => x.Project.Package.Ident == Package.Ident && x.Project.Package.Org == Package.Org );
// do we have this package installed? what is the version?
var isLocalPackage = ident.org == "local";
if ( !isLocalPackage )
{
Package = await Package.FetchAsync( $"{ident.org}.{ident.package}", false ) ?? Package;
if ( !this.IsValid() ) return;
}
var membership = EditorUtility.Account.Memberships.FirstOrDefault( x => x.Ident == ident.org );
{
var header = Layout.AddRow();
header.Spacing = 4;
var icon = header.Add( new Widget() );
icon.FixedWidth = 42;
icon.FixedHeight = icon.FixedWidth;
icon.OnPaintOverride = () =>
{
Paint.SetBrushAndPen( Theme.Border.WithAlpha( 0.4f ) );
var rect = new Rect( 0, icon.Size );
if ( !string.IsNullOrWhiteSpace( Package.Thumb ) && !Package.Thumb.StartsWith( "/" ) )
{
Paint.Draw( rect, Package.Thumb );
}
else
{
Paint.DrawRect( rect, 4 );
}
return true;
};
var headerDetails = header.AddColumn();
headerDetails.Add( new Label( $"
{Package.Title}
" ) );
// org icon and title
var orgText = (Package.Org.Ident == "local") ? "Local Package" : $"From {Package.Org.Title}";
headerDetails.Add( new Label( orgText ) );
}
// Control Row - Version List, Action Button, Uninstall Button
{
var controlRow1 = Layout.AddRow();
controlRow1.Spacing = 4;
VersionList = new ComboBox( this );
controlRow1.Add( VersionList );
ActionButton = new Button( "Install", "get_app", this ) { Pressed = () => _ = OnActionPressed() };
ActionButton.FixedWidth = 100;
controlRow1.Add( ActionButton );
// If it's a local package, include the Properties & Publish buttons here since the other controls get hidden
if ( membership is not null || isLocalPackage )
{
PropertiesButton = new Button( "Open Properties", "tune" )
{
Pressed = () =>
{
var project = LibrarySystem.All.FirstOrDefault( x => x.Project.Package.Ident == Package.Ident && x.Project.Package.Org == Package.Org )?.Project;
if ( project is null ) return;
ProjectInspector.OpenForProject( project );
}
};
if ( !isLocalPackage )
{
// If this is not a local package, make it a small icon button
PropertiesButton.Text = "";
PropertiesButton.Icon = " " + PropertiesButton.Icon;
PropertiesButton.FixedWidth = 26;
}
controlRow1.Add( PropertiesButton );
}
UninstallButton = new Button( "", " delete" )
{
FixedWidth = 26,
Pressed = () => Dialog.AskConfirm( OnUninstall, $"Are you sure you want to remove {Package.Title}?", "Remove Package", "Uninstall", "Cancel" )
};
controlRow1.Add( UninstallButton );
UninstallButton.Visible = Installed is not null;
}
var pageControl = Layout.Add( new SegmentedControl( this ) );
pageControl.AddOption( "README", "description" );
pageControl.AddOption( "Package Details", "history" );
pageControl.Visible = !isLocalPackage;
// Scroll Area
var scrollArea = Layout.Add( new ScrollArea( this ) );
scrollArea.Canvas = new Widget();
scrollArea.Canvas.Layout = Layout.Column();
scrollArea.Canvas.ContentMargins = new Sandbox.UI.Margin( 0, 0, 8, 0 ); // Leave room so the scrollbar doesn't overlap
scrollArea.Canvas.VerticalSizeMode = SizeMode.Flexible;
scrollArea.Canvas.HorizontalSizeMode = SizeMode.Flexible;
scrollArea.HorizontalScrollbarMode = ScrollbarMode.Off;
scrollArea.MaximumHeight = Height;
// Description label, formatted xml
var desc = ParseDescription( Package.Description );
var descriptionLabel = scrollArea.Canvas.Layout.Add( new Label( desc ) );
descriptionLabel.WordWrap = true;
descriptionLabel.SetStyles( "figure { width: 64px; }" );
// Package Details
var packageDetails = CreatePackageDetails();
scrollArea.Canvas.Layout.Add( packageDetails );
scrollArea.Canvas.Layout.AddStretchCell();
// Setup Page Control
pageControl.OnSelectedChanged = _ =>
{
descriptionLabel.Visible = pageControl.SelectedIndex == 0;
packageDetails.Visible = pageControl.SelectedIndex == 1;
};
Layout.AddStretchCell();
if ( ident.org == "local" )
{
VersionList.Visible = false;
ActionButton.Visible = false;
return;
}
var versions = await Package.FetchVersions( $"{ident.org}.{ident.package}" );
if ( !this.IsValid() ) return;
if ( versions is not null )
{
if ( Installed is not null )
{
SelectedRevision = versions.Where( x => x.VersionId == Installed.Version.Build ).FirstOrDefault();
}
SelectedRevision ??= versions.FirstOrDefault();
UpdateVersionList( versions );
OnVersionSelected( SelectedRevision );
}
}
Widget CreatePackageDetails()
{
var parent = new Widget();
parent.Layout = Layout.Column();
var headerLabel = parent.Layout.Add( new Label( $"Summary
\n{Package.Summary}" ) );
headerLabel.WordWrap = true;
parent.Layout.AddSpacingCell( 8 );
var grid = Layout.Grid();
grid.Spacing = 4;
parent.Layout.Add( grid );
grid.AddCell( 0, 0, new Label( "Latest Version" ) );
grid.AddCell( 1, 0, new Label( $"v{Package?.Revision?.VersionId}" ) );
grid.AddCell( 0, 1, new Label( "Author" ) );
grid.AddCell( 1, 1, new Label( $"{Package?.Org?.Title}" ) );
grid.AddCell( 0, 2, new Label( "Date Published" ) );
grid.AddCell( 1, 2, new Label( $"{Package.Created.ToString( "G" )}" ) );
grid.AddCell( 0, 3, new Label( "Last Updated" ) );
grid.AddCell( 1, 3, new Label( $"{Package.Updated.ToString( "G" )}" ) );
grid.AddCell( 0, 4, new Label( "Project URL" ) );
grid.AddCell( 1, 4, new Label( $"{Package?.Url}" ) );
var tags = string.Join( ", ", Package.Tags );
if ( string.IsNullOrEmpty( tags ) ) tags = "None";
grid.AddCell( 0, 5, new Label( "Tags" ) );
grid.AddCell( 1, 5, new Label( tags ) );
// TODO: Add downloads count here?
parent.Layout.AddStretchCell();
parent.Visible = false;
return parent;
}
void UpdateVersionList( List versions )
{
VersionList.Clear();
int index = 0;
var installed = LibrarySystem.All.FirstOrDefault( x => x.Project.Package.Ident == Package.Ident && x.Project.Package.Org == Package.Org );
foreach ( var v in versions )
{
var version = v;
var name = $"{v.Created.DateTime} - {v.VersionId}";
string icon = null;
if ( installed is not null )
{
if ( installed.Version.Build == version.VersionId )
{
icon = "download_done";
}
else if ( index == 0 )
{
icon = "fiber_new";
}
}
VersionList.AddItem( name, icon, () => OnVersionSelected( v ), v.Summary, SelectedRevision == version );
if ( !string.IsNullOrWhiteSpace( icon ) )
{
VersionList.TrySelectNamed( name );
}
index++;
}
}
void OnVersionSelected( Package.IRevision revision )
{
SelectedRevision = revision;
// Is this fucker installed?
var installed = LibrarySystem.All.FirstOrDefault( x => x.Project.Package.Ident == Package.Ident && x.Project.Package.Org == Package.Org );
if ( installed is not null )
{
// is this the installed version?
if ( revision.VersionId == installed.Version.Build )
{
ActionButton.Enabled = false;
ActionButton.Icon = "folder";
ActionButton.Text = "Installed";
}
else
{
ActionButton.Enabled = true;
ActionButton.Icon = "sync";
ActionButton.Text = $"Update";
}
UninstallButton.Visible = true;
}
else
{
// it's not installed
ActionButton.Enabled = true;
ActionButton.Icon = "get_app";
ActionButton.Text = "Install";
UninstallButton.Visible = false;
}
}
string ParseDescription( string description )
{
if ( string.IsNullOrEmpty( description ) )
return "No description provided.";
if ( description.StartsWith( "[" ) )
{
try
{
var json = System.Text.Json.JsonSerializer.Deserialize>( description );
if ( json != null )
{
var html = new System.Text.StringBuilder();
foreach ( var block in json )
{
// Process block based on attributes
if ( block.attributes?.Contains( "heading1" ) == true )
{
html.Append( "" );
AppendText( html, block.text );
html.Append( "
" );
}
else if ( block.attributes?.Contains( "heading2" ) == true )
{
html.Append( "" );
AppendText( html, block.text );
html.Append( "
" );
}
else if ( block.attributes?.Contains( "heading3" ) == true )
{
html.Append( "" );
AppendText( html, block.text );
html.Append( "
" );
}
else
{
AppendText( html, block.text );
}
}
return html.ToString();
}
}
catch ( System.Exception e )
{
Log.Warning( $"Failed to parse JSON description: {e.Message}" );
}
}
return description;
}
private void AppendText( StringBuilder html, List textElements )
{
if ( textElements == null ) return;
foreach ( var element in textElements )
{
if ( element.type == "string" )
{
string text = element.@string;
// Block breaks
if ( element.attributes?.blockBreak == true )
{
if ( text == "\n" )
{
html.Append( "
" );
}
continue;
}
// Links
if ( element.attributes?.href != null )
{
html.Append( $"{text}" );
}
else
{
html.Append( text );
}
}
}
}
async Task OnActionPressed()
{
await LibrarySystem.Install( Package.FullIdent, SelectedRevision.VersionId );
// window closed
if ( !IsValid ) return;
await FetchAndBuild();
}
void OnUninstall()
{
UninstallButton.Enabled = false;
Installed.RemoveAndDelete();
_ = FetchAndBuild();
}
protected override Vector2 SizeHint()
{
return new Vector2( 300, 100 );
}
// Classes to deserialize JSON description
private class TextBlock
{
public List text { get; set; }
public List attributes { get; set; }
public Dictionary htmlAttributes { get; set; }
}
private class TextElement
{
public string type { get; set; }
public TextAttributes attributes { get; set; }
public string @string { get; set; }
public Dictionary attachment { get; set; }
}
private class TextAttributes
{
public bool blockBreak { get; set; }
public string href { get; set; }
}
}