UI/SpawnMenu/Spawnlists/SpawnlistView.razor
@using Sandbox;
@using Sandbox.UI;
@inherits Panel
@namespace Sandbox
@{
var data = GetData();
var workshopId = Entry.GetMeta( "_workshopId", 0ul );
var isPublished = workshopId > 0;
var isInstalled = Entry.Files.IsReadOnly;
}
<SpawnMenuContent>
<Header>
<div class="spawnlist-header">
<SpawnMenuToolbar>
<Left>
<div class="spawnlist-identity">
<div class="folder-mark">
<div class="folder-tab"></div>
<IconPanel Text="collections_bookmark" />
</div>
<div class="identity-copy">
<h2>@data.Name</h2>
<div class="meta">
<span><IconPanel Text="widgets" /> @data.Items.Count( item => item.IsContent ) @Game.Language.GetPhrase( "spawnmenu.spawnlist.items" )</span>
<span class="dot"></span>
<span><IconPanel Text="@( isInstalled ? "cloud_done" : isPublished ? "public" : "lock" )" /> @Game.Language.GetPhrase( isInstalled ? "spawnmenu.spawnlist.installed" : isPublished ? "spawnmenu.spawnlist.published" : "spawnmenu.spawnlist.local" )</span>
</div>
</div>
</div>
</Left>
<Right>
@if ( isPublished )
{
<Button Icon="link" Tooltip="#spawnmenu.spawnlist.copy_url" class="icon-button" @onclick=@( () => Clipboard.SetText( $"https://steamcommunity.com/sharedfiles/filedetails/?id={workshopId}" ) ) />
@if ( !Entry.Files.IsReadOnly )
{
<Button Icon="sync" Tooltip="#spawnmenu.spawnlist.sync" class="icon-button" @onclick=@OnPublish />
}
}
else if ( !Entry.Files.IsReadOnly )
{
<Button Icon="cloud_upload" Text="#spawnmenu.spawnlist.publish" Tooltip="#spawnmenu.spawnlist.publish" class="menu-button" @onclick=@OnPublish />
}
@if ( !Entry.Files.IsReadOnly )
{
@if ( _editMode && CanRename )
{
<Button Icon="drive_file_rename_outline" Text="#spawnmenu.spawnlist.rename" class="menu-button" @onclick=@OnRename />
}
<Button Icon="edit" Tooltip="#spawnmenu.spawnlist.edit" class="icon-button" Active=@_editMode @onclick=@ToggleEditMode />
<Button Icon="delete" Tooltip="#spawnmenu.spawnlist.delete" class="icon-button" @onclick=@OnDelete />
}
else
{
<Button Icon="delete" Tooltip="#spawnmenu.spawnlist.remove" class="icon-button" @onclick=@OnUninstall />
}
</Right>
</SpawnMenuToolbar>
@if ( _editMode )
{
<div class="edit-toolbar">
<Button class="menu-action" Icon="label" Text="#spawnmenu.spawnlist.add_title" @onclick=@AddTitle />
<Button class="menu-action" Icon="horizontal_rule" Text="#spawnmenu.spawnlist.add_divider" @onclick=@AddDivider />
<div class="edit-instructions">
<IconPanel Text="drag_indicator" />
<label>@Game.Language.GetPhrase( "spawnmenu.spawnlist.edit_instructions" )</label>
</div>
</div>
}
</div>
</Header>
<Body>
@if ( data.Items.Count == 0 )
{
<SpawnMenuEmptyState Icon="format_list_bulleted" Title="#spawnmenu.spawnlist.empty_title" Subtitle="#spawnmenu.spawnlist.empty_instructions" />
}
else
{
<MixedVirtualGrid class="@( _editMode ? "editing" : "" )" [email protected] ItemSize=@(new Vector2( 160, 120 )) CanAcceptDrop=@CanAcceptDrop OnDropIndex=@OnDropIndex>
<Item Context="item">
@if ( item is SpawnlistItem spawnItem )
{
@if ( spawnItem.IsContent )
{
<SpawnMenuIcon Ident="@spawnItem.Ident" Title="@spawnItem.Title" Icon="@spawnItem.Icon" [email protected] />
}
else
{
<SpawnlistLayoutItem Item=@spawnItem OnRename=@RenameItem OnRemove=@RemoveItem />
}
}
</Item>
</MixedVirtualGrid>
}
</Body>
</SpawnMenuContent>
@code
{
public Storage.Entry Entry { get; set; }
SpawnlistData _cachedData;
RealTimeSince _lastRefresh;
bool _editMode;
public bool CanReorder => _editMode && Entry?.Files.IsReadOnly == false;
bool CanRename => Entry?.Files.IsReadOnly == false && Entry.GetMeta( "_workshopId", 0ul ) == 0;
SpawnlistData GetData()
{
if ( _cachedData == null )
{
_cachedData = SpawnlistData.Load( Entry );
_lastRefresh = 0;
}
return _cachedData;
}
public void RefreshCache()
{
_cachedData = SpawnlistData.Load( Entry );
_lastRefresh = 0;
StateHasChanged();
}
public override void Tick()
{
base.Tick();
// Periodically check for changes from other tabs
if ( _lastRefresh > 1f )
{
var fresh = SpawnlistData.Load( Entry );
if ( fresh.Name != _cachedData?.Name || !ItemsMatch( fresh.Items, _cachedData?.Items, !Entry.Files.IsReadOnly ) )
{
_cachedData = fresh;
StateHasChanged();
}
_lastRefresh = 0;
}
}
protected override int BuildHash() => HashCode.Combine( _cachedData?.Items?.Count, _cachedData?.Name, _editMode, CanRename );
void OnRename()
{
if ( !_editMode || !CanRename ) return;
_ = new StringQueryPopup
{
Title = "#spawnmenu.spawnlist.rename_title",
Prompt = "#spawnmenu.spawnlist.rename_prompt",
Placeholder = "#spawnmenu.spawnlist.name_placeholder",
ConfirmLabel = "#spawnmenu.spawnlist.rename_button",
InitialValue = GetData().Name,
OnConfirm = RenameSpawnlist,
Parent = FindPopupPanel()
};
}
void RenameSpawnlist( string name )
{
if ( !CanRename ) return;
SpawnlistData.Rename( Entry, name );
RefreshCache();
Ancestors.OfType<SpawnlistsPage>().FirstOrDefault()?.RefreshList();
}
void ToggleEditMode()
{
if ( Entry?.Files.IsReadOnly != false ) return;
_editMode = !_editMode;
}
void AddTitle()
{
SpawnlistData.AddMarker( Entry, SpawnlistItemKind.Title, Game.Language.GetPhrase( "spawnmenu.spawnlist.new_section" ) );
RefreshCache();
}
void AddDivider()
{
SpawnlistData.AddMarker( Entry, SpawnlistItemKind.Divider );
RefreshCache();
}
public bool CanAcceptDrop( PanelEvent e )
{
if ( !CanReorder ) return false;
var drag = DragHandler.Current ?? e?.Target?.UserData as DragData;
return drag is not null && drag.SpawnlistItemId != Guid.Empty;
}
void OnDropIndex( int index, PanelEvent e )
{
var drag = DragHandler.Current ?? e.Target?.UserData as DragData;
if ( drag is null || drag.SpawnlistItemId == Guid.Empty ) return;
drag.Handled = true;
if ( drag.Source is not null ) drag.Source.UserData = null;
SpawnlistData.MoveItem( Entry, drag.SpawnlistItemId, index );
RefreshCache();
}
void RenameItem( Guid id, string title )
{
title = title?.Trim() ?? "";
SpawnlistData.RenameItem( Entry, id, title );
var item = _cachedData?.Items.FirstOrDefault( item => item.Id == id );
if ( item is not null ) item.Title = title;
StateHasChanged();
}
static bool ItemsMatch( IReadOnlyList<SpawnlistItem> left, IReadOnlyList<SpawnlistItem> right, bool compareIds )
{
if ( left is null || right is null ) return left == right;
if ( left.Count != right.Count ) return false;
for ( var i = 0; i < left.Count; i++ )
{
var a = left[i];
var b = right[i];
if ( (compareIds && a.Id != b.Id) || a.Kind != b.Kind || a.Ident != b.Ident || a.Title != b.Title || a.Icon != b.Icon )
return false;
}
return true;
}
void RemoveItem( Guid id )
{
SpawnlistData.RemoveItem( Entry, id );
RefreshCache();
}
void OnPublish()
{
if ( Entry is null ) return;
SpawnlistData.Publish( Entry );
}
void OnDelete()
{
if ( Entry is null ) return;
var page = Ancestors.OfType<SpawnlistsPage>().FirstOrDefault();
page?.Collection.Delete( Entry );
}
void OnUninstall()
{
if ( Entry is null ) return;
var workshopId = Entry.GetMeta( "_workshopId", 0ul );
var page = Ancestors.OfType<SpawnlistsPage>().FirstOrDefault();
page?.Collection.Uninstall( workshopId );
}
}