UI/SpawnMenu/Dupes/DupesWorkshop.razor

A Razor UI component for the spawn menu that lists workshop 'dupe' items. It queries Facepunch storage for items, supports filtering, sorting and infinite scroll, and creates WorkshopIcon entries that call DuplicatorTool.FromWorkshop when clicked.

Networking
@using Sandbox;
@using Sandbox.UI;
@using Sandbox.Mounting;
@inherits Panel
@namespace Sandbox

<SpawnMenuContent>

    <Header>
        <SpawnMenuToolbar>
            <Left>
                <TextEntry Placeholder="#spawnmenu.common.search" class="filter menu-input" Value:bind=@Filter />
            </Left>
            <Right>
                <DropDown Value:bind=@SortOrder />
            </Right>
        </SpawnMenuToolbar>
    </Header>

    <Body>
		
        <VirtualGrid Items=@Items ItemSize=@(200) OnLastCell="@(() => { _ = QueryNext(); })">
            <Item Context="context">

				@if (context is Storage.QueryItem item)
                {
					<WorkshopIcon Item="@item" @onclick=@( () => _ = DuplicatorTool.FromWorkshop( item ) )></WorkshopIcon>
                }

            </Item>
        </VirtualGrid>
    </Body>

</SpawnMenuContent>

@code
{
    string _filter;
    public string Filter
    {
        get => _filter;
        set
        {
            if ( _filter == value ) return;
            _filter = value;
            Rebuild();
        }
    }

    WorkshopSortMode _sortOrder = WorkshopSortMode.Popular;
    public WorkshopSortMode SortOrder
    {
        get => _sortOrder;
        set
        {
            if ( _sortOrder == value ) return;
            _sortOrder = value;
            Rebuild();
        }
    }

    public string Category { get; set; }

    protected override async Task OnParametersSetAsync()
    {
        Items.Clear();
        await QueryNext();
    }

    List<Storage.QueryItem> Items = new();
    Storage.QueryResult LastResult;

    async Task QueryNext()
    {
        if ( LastResult != null )
        {
            // No more items to load
            if (!LastResult.HasMoreResults())
                return;

            LastResult = await LastResult.GetNextResults();
            if (LastResult.Items == null) return;

            Items.AddRange(LastResult.Items);
            StateHasChanged();
            return;
        }

		var query = new Storage.Query();
		query.KeyValues["package"] = "facepunch.sandbox";
		query.KeyValues["type"] = "dupe";
		query.SortOrder = SortOrder.ToSortOrder();

        if ( !string.IsNullOrWhiteSpace( Filter ) ) query.SearchText = Filter;
        if (Category != null) query.KeyValues["Category"] = Category;

		LastResult = await query.Run();
		if ( LastResult.Items == null ) return;

		Items.AddRange( LastResult.Items );
		StateHasChanged();
	}

    async void Rebuild()
    {
        Items.Clear();
        LastResult = null;
        await QueryNext();
    }
}