UI/SpawnMenu/SaveMenu.razor
@using Sandbox;
@using Sandbox.UI;
@namespace Sandbox
@inherits Panel
@attribute [SpawnMenuHost.SpawnMenuMode<SpawnMenuHost.HostOnly>]
@attribute [Icon( "💾" )]
@attribute [Title( "#spawnmenu.tab.saves" )]
@attribute [Order( 150 )]
<root>
<div class="save-window">
<div class="save-header">💾 <label>#spawnmenu.saves.header</label></div>
<div class="body">
@if ( !SaveFiles.Any() )
{
<div class="empty-state">#spawnmenu.saves.empty</div>
}
@foreach ( var save in SaveFiles )
{
var s = save;
<div class="save-card @(!s.IsCompatible ? "outdated" : "")"
@onclick="@(() => ConfirmLoad( s ))"
@onmousedown="@((PanelEvent e) => OnCardMouseDown( s, e ))">
@if ( s.Thumbnail != null )
{
<Image class="save-thumb" Texture="@s.Thumbnail" />
}
<div class="save-info @(s.Thumbnail == null ? "save-info--padded" : "")">
<label class="save-name">@s.DisplayName</label>
@if ( !string.IsNullOrEmpty( s.Timestamp ) )
{
<label class="save-timestamp">@s.Timestamp</label>
}
@if ( !s.IsCompatible )
{
<label class="save-outdated-badge">#spawnmenu.saves.outdated_badge</label>
}
</div>
<div class="save-dots">⋮</div>
</div>
}
</div>
<div class="footer">
<TextEntry @ref="SaveNameEntry" Placeholder="#spawnmenu.saves.name_placeholder" @onsubmit="@OnSave"></TextEntry>
<button @onclick="@OnSave">💾 <label>#spawnmenu.saves.save_button</label></button>
</div>
</div>
</root>
@code
{
TextEntry SaveNameEntry;
List<SaveFileEntry> SaveFiles = new();
record SaveFileEntry( string FileName, string DisplayName, string Timestamp, Texture Thumbnail, bool IsCompatible );
static string SavesPath => "saves";
protected override void OnAfterTreeRender ( bool firstTime )
{
if ( firstTime )
{
RefreshSaveList();
}
}
void RefreshSaveList()
{
SaveFiles.Clear();
if ( !FileSystem.Data.DirectoryExists( SavesPath ) )
{
FileSystem.Data.CreateDirectory( SavesPath );
}
foreach ( var file in FileSystem.Data.FindFile( SavesPath, "*.sav" ) )
{
var filePath = $"{SavesPath}/{file}";
var displayName = file;
string timestamp = null;
Texture thumbnail = null;
var metadata = SaveSystem.GetFileMetadata( filePath );
if ( metadata != null )
{
if ( metadata.TryGetValue( "Title", out var name ) )
displayName = name;
if ( metadata.TryGetValue( "Timestamp", out var ts ) )
timestamp = ts;
}
var isCompatible = SaveSystem.GetFileSaveVersion( filePath ) == SaveSystem.SaveVersion;
var thumbPath = $"{SavesPath}/{System.IO.Path.GetFileNameWithoutExtension( file )}.thumb.png";
if ( FileSystem.Data.FileExists( thumbPath ) )
{
thumbnail = Texture.LoadFromFileSystem( thumbPath, FileSystem.Data );
}
SaveFiles.Add( new SaveFileEntry( file, displayName, timestamp, thumbnail, isCompatible ) );
}
StateHasChanged();
}
void OnCardMouseDown( SaveFileEntry save, PanelEvent e )
{
if ( e is MousePanelEvent { MouseButton: MouseButtons.Right } me )
{
me.StopPropagation();
var menu = MenuPanel.Open( this );
menu.AddOption( "download", "#spawnmenu.saves.load_context", () => ConfirmLoad( save ) );
menu.AddOption( "delete", "#spawnmenu.saves.delete_context", () => ConfirmDelete( save ) );
}
}
void ConfirmLoad( SaveFileEntry save )
{
if ( !save.IsCompatible )
{
_ = new StringQueryPopup
{
Title = "#spawnmenu.saves.incompatible_title",
Prompt = Game.Language.GetPhrase( "spawnmenu.saves.incompatible_message", new() { { "name", save.DisplayName } } ),
ConfirmLabel = "#spawnmenu.common.ok",
ShowInput = false,
Parent = FindPopupPanel()
};
return;
}
_ = new StringQueryPopup
{
Title = "#spawnmenu.saves.load_title",
Prompt = Game.Language.GetPhrase( "spawnmenu.saves.load_confirm", new() { { "name", save.DisplayName } } ),
ConfirmLabel = "#spawnmenu.saves.load_button",
ShowInput = false,
OnConfirm = _ => LoadSave( save.FileName ),
Parent = FindPopupPanel()
};
}
void ConfirmDelete( SaveFileEntry save )
{
var popup = new StringQueryPopup
{
Title = "#spawnmenu.saves.delete_title",
Prompt = Game.Language.GetPhrase( "spawnmenu.saves.delete_confirm", new() { { "name", save.DisplayName } } ),
ConfirmLabel = "#spawnmenu.saves.delete_button",
ShowInput = false,
OnConfirm = _ => DeleteSave( save.FileName ),
Parent = FindPopupPanel()
};
}
void OnSave()
{
var saveName = SaveNameEntry?.Text?.Trim();
if ( string.IsNullOrEmpty( saveName ) )
return;
var now = DateTime.Now;
var timeString = now.ToString( "yyyy-MM-dd HH:mm:ss" );
var baseName = now.ToString( "yyyy-MM-dd-HH-mm-ss" );
var fileName = $"{SavesPath}/{baseName}.sav";
// Capture a 256x144 (16:9) thumbnail from the scene camera
var camera = Game.ActiveScene?.Camera;
if ( camera.IsValid() )
{
var bitmap = new Bitmap( 256, 144 );
camera.RenderToBitmap( bitmap );
FileSystem.Data.WriteAllBytes( $"{SavesPath}/{baseName}.thumb.png", bitmap.ToPng() );
}
SaveSystem.Current.SetMetadata( "Timestamp", timeString );
SaveSystem.Current.SetMetadata( "Title", saveName );
SaveSystem.Current.Save( fileName );
SaveNameEntry.Text = "";
RefreshSaveList();
}
void LoadSave( string fileName )
{
_ = SaveSystem.Current.Load( $"{SavesPath}/{fileName}" );
}
void DeleteSave( string fileName )
{
FileSystem.Data.DeleteFile( $"{SavesPath}/{fileName}" );
var thumbPath = $"{SavesPath}/{System.IO.Path.GetFileNameWithoutExtension( fileName )}.thumb.png";
if ( FileSystem.Data.FileExists( thumbPath ) )
FileSystem.Data.DeleteFile( thumbPath );
RefreshSaveList();
}
}