LibraryStore.cs

The LibraryStore manages the collection of game assets, including projects, sprites, tilesets, and stages, within the editor. It handles fetching asset metadata from storage, saving current editor state to disk, and loading assets into the main workspace.

File Access
namespace PixelPusher;

public sealed class LibraryItem
{
	public string Id { get; set; }
	public string Title { get; set; }
	public string Kind { get; set; }
	public string Type { get; set; }
	public string When { get; set; }
	public bool Local { get; set; } = true;
	public string File { get; set; }
}

public static class LibraryStore
{
	public const string TypeProject = "pxproject";
	public const string TypeSprite = "pxsprite";
	public const string TypeTileset = "pxtileset";
	public const string TypeStage = "pxstage";
	public const string KindProject = "pixel.project";
	public const string KindSprite = "pixel.sprite";
	public const string KindTileset = "pixel.tileset";
	public const string KindStage = "pixel.stage";

	public static List<LibraryItem> Items { get; } = new();
	public static int Version;

	public static void Refresh()
	{
		Items.Clear();
		try
		{
			foreach ( var entry in Storage.GetAll( TypeProject ) )
				Items.Add( FromEntry( entry, TypeProject ) );
			foreach ( var entry in Storage.GetAll( TypeSprite ) )
				Items.Add( FromEntry( entry, TypeSprite ) );
			foreach ( var entry in Storage.GetAll( TypeTileset ) )
				Items.Add( FromEntry( entry, TypeTileset ) );
			foreach ( var entry in Storage.GetAll( TypeStage ) )
				Items.Add( FromEntry( entry, TypeStage ) );
		}
		catch ( Exception e )
		{
			Log.Info( $"[PIXEL PUSHER] Storage list: {e.Message}" );
		}

		TryAddLocal( "pxproject.json", TypeProject, KindProject, "local project" );
		TryAddLocal( "pxsprite.json", TypeSprite, KindSprite, "local sprite" );
		TryAddLocal( "pxtileset.json", TypeTileset, KindTileset, "local tileset" );
		TryAddLocal( "pxstage.json", TypeStage, KindStage, "local stage" );

		for ( var i = StarterPack.Shelf.Length - 1; i >= 0; i-- )
		{
			var s = StarterPack.Shelf[i];
			if ( Items.Exists( x => x.Id == s.Id ) )
				continue;
			Items.Insert( 0, new LibraryItem
			{
				Id = s.Id,
				Title = s.Title,
				Kind = s.Kind,
				Type = TypeOfKind( s.Kind ),
				When = s.When,
				Local = true
			} );
		}

		Version++;
		GameFlow.Bump();
	}

	public static void SaveCurrent( string kind )
	{
		Desk.Autosave( quiet: true );
		kind ??= TypeProject;
		var title = Desk.Name ?? "untitled";

		try
		{
			var entry = Storage.CreateEntry( kind );
			switch ( kind )
			{
				case TypeSprite:
					entry.Files.WriteJson( "sprite.json", Desk.Sprite.ToSave() );
					break;
				case TypeTileset:
					entry.Files.WriteJson( "tileset.json", Desk.Tileset.ToSave() );
					break;
				case TypeStage:
					entry.Files.WriteJson( "stage.json", Desk.Stage.ToSave() );
					break;
				default:
					kind = TypeProject;
					entry.Files.WriteJson( "project.json", Desk.ToSave() );
					break;
			}

			entry.SetMeta( "title", title );
			entry.SetMeta( "kind", KindOf( kind ) );
			GameFlow.ShowToast( $"saved to desk · {title}" );
		}
		catch ( Exception e )
		{
			try
			{
				var file = kind.Replace( '.', '_' ) + ".json";
				FileSystem.Data.WriteJson( file, Desk.ToSave() );
				GameFlow.ShowToast( $"saved locally · {title}" );
			}
			catch
			{
				GameFlow.ShowToast( "save failed" );
				Log.Warning( $"[PIXEL PUSHER] save: {e.Message}" );
			}
		}

		Refresh();
	}

	public static void PublishCurrent()
	{
		Desk.Autosave( quiet: true );
		try
		{
			var entry = Storage.CreateEntry( TypeProject );
			entry.Files.WriteJson( "project.json", Desk.ToSave() );
			entry.SetMeta( "title", Desk.Name ?? GameInfo.Title );
			entry.SetMeta( "kind", KindProject );
			entry.SetMeta( "rank", GameInfo.Rank );
			entry.Publish();
			GameFlow.ShowToast( "publish dialog opened" );
		}
		catch ( Exception e )
		{
			SaveCurrent( TypeProject );
			GameFlow.ShowToast( "published locally — workshop later" );
			Log.Info( $"[PIXEL PUSHER] publish: {e.Message}" );
		}
		Refresh();
	}

	public static void LoadStarter()
	{
		LoadShelf( "starter" );
	}

	public static bool LoadShelf( string id )
	{
		if ( !StarterPack.LoadShelf( id ) )
			return false;
		GameFlow.ShowToast( $"opened  {Desk.Name}" );
		GameFlow.Bump();
		return true;
	}

	public static void Open( LibraryItem item )
	{
		if ( item is null )
			return;

		if ( StarterPack.LoadShelf( item.Id ) )
		{
			GameFlow.ShowToast( $"opened  {Desk.Name}" );
			if ( item.Kind == KindSprite )
				GameFlow.OpenStudio( GameScreen.Pixel );
			else
				GameFlow.OpenStudio( GameScreen.Stage );
			Refresh();
			return;
		}

		try
		{
			if ( !string.IsNullOrWhiteSpace( item.File ) && FileSystem.Data.FileExists( item.File ) )
			{
				ApplyFile( item.Type, item.File );
				return;
			}

			foreach ( var entry in Storage.GetAll( item.Type ) )
			{
				if ( entry.Id.ToString() != item.Id )
					continue;
				ApplyEntry( entry, item.Type );
				return;
			}
		}
		catch ( Exception e )
		{
			Log.Warning( $"[PIXEL PUSHER] open: {e.Message}" );
		}

		GameFlow.ShowToast( "could not open" );
	}

	static void ApplyEntry( Storage.Entry entry, string type )
	{
		switch ( type )
		{
			case TypeSprite:
				Desk.Sprite = PixelDoc.FromSave( entry.Files.ReadJson<PixelSave>( "sprite.json" ) );
				GameFlow.OpenStudio( GameScreen.Pixel );
				break;
			case TypeTileset:
				Desk.Tileset = TilesetDoc.FromSave( entry.Files.ReadJson<TilesetSave>( "tileset.json" ) );
				GameFlow.OpenStudio( GameScreen.Stage );
				break;
			case TypeStage:
				Desk.Stage = StageDoc.FromSave( entry.Files.ReadJson<StageSave>( "stage.json" ) );
				GameFlow.OpenStudio( GameScreen.Stage );
				break;
			default:
				Desk.Apply( entry.Files.ReadJson<ProjectSave>( "project.json" ) );
				GameFlow.OpenStudio( GameScreen.Stage );
				break;
		}

		Desk.Touch();
		GameFlow.ShowToast( $"opened · {Desk.Name}" );
		Refresh();
	}

	static void ApplyFile( string type, string file )
	{
		switch ( type )
		{
			case TypeSprite:
				Desk.Sprite = PixelDoc.FromSave( FileSystem.Data.ReadJsonOrDefault<PixelSave>( file, null ) );
				GameFlow.OpenStudio( GameScreen.Pixel );
				break;
			case TypeTileset:
				Desk.Tileset = TilesetDoc.FromSave( FileSystem.Data.ReadJsonOrDefault<TilesetSave>( file, null ) );
				GameFlow.OpenStudio( GameScreen.Stage );
				break;
			case TypeStage:
				Desk.Stage = StageDoc.FromSave( FileSystem.Data.ReadJsonOrDefault<StageSave>( file, null ) );
				GameFlow.OpenStudio( GameScreen.Stage );
				break;
			default:
				Desk.Apply( FileSystem.Data.ReadJsonOrDefault<ProjectSave>( file, null ) );
				GameFlow.OpenStudio( GameScreen.Stage );
				break;
		}

		Desk.Touch();
		GameFlow.ShowToast( $"opened · {Desk.Name}" );
		Refresh();
	}

	static void TryAddLocal( string file, string type, string kind, string when )
	{
		try
		{
			if ( !FileSystem.Data.FileExists( file ) )
				return;
			if ( Items.Exists( i => i.File == file ) )
				return;
			Items.Add( new LibraryItem
			{
				Id = file,
				Title = file,
				Kind = kind,
				Type = type,
				When = when,
				Local = true,
				File = file
			} );
		}
		catch
		{
			// ignore
		}
	}

	static LibraryItem FromEntry( Storage.Entry entry, string kind )
	{
		var title = "untitled";
		try
		{
			title = entry.GetMeta( "title", title );
		}
		catch
		{
			// ignore
		}

		return new LibraryItem
		{
			Id = entry.Id.ToString(),
			Title = title,
			Kind = KindOf( kind ),
			Type = kind,
			When = entry.Created.ToString( "yyyy-MM-dd HH:mm" ),
			Local = true
		};
	}

	static string KindOf( string type ) => type switch
	{
		TypeSprite => KindSprite,
		TypeTileset => KindTileset,
		TypeStage => KindStage,
		_ => KindProject
	};

	static string TypeOfKind( string kind ) => kind switch
	{
		KindSprite => TypeSprite,
		KindTileset => TypeTileset,
		KindStage => TypeStage,
		_ => TypeProject
	};
}