Editor/Panels/SharePanel.cs

Editor UI panel for the SuperShot tool that manages Discord webhook channels and sharing options. It builds a UI to list, add, remove, favorite, and configure webhook channels, choose message mode per channel, save settings, and trigger capture/post actions.

NetworkingFile Access
using System;
using Sandbox;

namespace Editor.SuperShot;

public sealed class SharePanel : Widget
{
	readonly SuperShotWindow _window;
	Widget _channels;

	public SharePanel( SuperShotWindow window ) : base( null )
	{
		_window = window;
		Name = "Discord";
		WindowTitle = "Discord";
		SetWindowIcon( "forum" );
		Layout = Layout.Column();
		Layout.Margin = 8;
		Layout.Spacing = 8;

		Build();
	}

	void Build()
	{
		SuperShotUI.AddBanner( Layout, "Discord", "Post the current shot to your channels. Posting always saves a local copy too.", "forum" );

		var scroll = new ScrollArea( this );
		scroll.Canvas = new Widget( scroll );
		scroll.Canvas.Layout = Layout.Column();
		scroll.Canvas.Layout.Spacing = 8;
		var body = scroll.Canvas.Layout;

		var channelsCard = SuperShotUI.AddCard( body, "Discord Channels", "forum" );
		channelsCard.Body.Add( new Label( "Each channel is a Discord webhook. Star a channel to pin it to the Discord menu for one-click posting." ) );

		_channels = new Widget( null );
		_channels.Layout = Layout.Column();
		_channels.Layout.Spacing = 6;
		channelsCard.Body.Add( _channels );
		RebuildChannels();

		var addRow = channelsCard.Body.AddRow();
		addRow.Spacing = 4;
		addRow.Add( new Button( "Add Channel", "add" ) { Tint = new Color( 0.27f, 0.58f, 0.38f ), Clicked = AddChannel } );
		addRow.AddStretchCell();

		var msgSo = _window.Settings.Share.Message.GetSerialized();
		msgSo.OnPropertyChanged += _ => _window.Settings.Save();
		SuperShotUI.AddSection( body, "Shared Message Format", "edit_note",
			SuperShotUI.SheetWidget( msgSo ), cookie: "supershot.share.message" );

		body.AddStretchCell();
		Layout.Add( scroll, 1 );

		var shareRow = Layout.AddRow();
		shareRow.Spacing = 4;
		shareRow.Add( new Button.Primary( "Save + Post to All", "send" ) { Clicked = SaveAndPostAll } );
		shareRow.Add( new Button( "Post to All", "share" ) { Clicked = () => _ = _window.PostCurrentToAll() } );

		var utilRow = Layout.AddRow();
		utilRow.Spacing = 4;
		utilRow.Add( new Button( "Copy Path", "content_copy" ) { ToolTip = "Save the shot and copy its file path", Clicked = CopyPath } );
		utilRow.Add( new Button( "Upload to s&box Art Page", "image" ) { ToolTip = "Save the shot, copy its path, and open Steam's s&box artwork upload page", Clicked = _window.UploadToArtPage } );
	}

	static bool IsChannelBasic( SerializedProperty p )
	{
		return p.Name is nameof( DiscordWebhookConfig.Name )
			or nameof( DiscordWebhookConfig.Enabled );
	}

	static string MessageModeLabel( WebhookMessageMode mode ) => mode switch
	{
		WebhookMessageMode.Custom => "Message: Custom",
		WebhookMessageMode.None => "Message: None (image only)",
		_ => "Message: Shared format",
	};

	static string MessageModeIcon( WebhookMessageMode mode ) => mode switch
	{
		WebhookMessageMode.Custom => "edit",
		WebhookMessageMode.None => "hide_image",
		_ => "forum",
	};

	void RebuildChannels()
	{
		_channels.Layout.Clear( true );

		var list = _window.Settings.Share.Webhooks;
		if ( list.Count == 0 )
		{
			_channels.Layout.Add( new Label( "No channels yet. Click \"Add Channel\" and paste a Discord webhook URL." ) );
			return;
		}

		foreach ( var webhook in list.ToArray() )
			_channels.Layout.Add( BuildChannelCard( webhook ) );
	}

	Widget BuildChannelCard( DiscordWebhookConfig webhook )
	{
		var card = new Card();

		var header = card.Body.AddRow();
		header.Spacing = 6;
		header.Add( new IconLabel( "tag" ) );
		var title = new Label.Subtitle( string.IsNullOrWhiteSpace( webhook.Name ) ? "Unnamed channel" : webhook.Name );
		header.Add( title );
		header.AddStretchCell();

		var star = new Button( "Favorite", webhook.Favorite ? "star" : "star_outline" )
		{
			ToolTip = webhook.Favorite ? "Unpin from the quick-post menus" : "Pin to the quick-post menus",
			Clicked = () =>
			{
				webhook.Favorite = !webhook.Favorite;
				_window.Settings.Save();
				RebuildChannels();
			}
		};
		if ( webhook.Favorite )
			star.Tint = new Color( 0.95f, 0.75f, 0.2f );
		header.Add( star );

		var so = webhook.GetSerialized();
		// Only persist on edit. Don't rebuild or touch other widgets here - doing so collapses the URL
		// expander and pulls focus out of the field being typed in. The header refreshes on the next rebuild.
		so.OnPropertyChanged += _ => _window.Settings.Save();
		card.Body.Add( SuperShotUI.SheetWidget( so, IsChannelBasic ) );
		SuperShotUI.AddSection( card.Body, "Webhook URL", "link",
			SuperShotUI.SheetWidget( so, p => p.Name == nameof( DiscordWebhookConfig.Url ) ),
			defaultOpen: !webhook.IsConfigured );

		var msgRow = card.Body.AddRow();
		msgRow.Spacing = 4;
		msgRow.Add( new Button( MessageModeLabel( webhook.MessageMode ), MessageModeIcon( webhook.MessageMode ) )
		{
			ToolTip = "Click to cycle what this channel posts:\n"
				+ "Shared = the Shared Message Format below\n"
				+ "Custom = this channel's own message/embed\n"
				+ "None = the image only, no text or embed",
			Clicked = () =>
			{
				webhook.MessageMode = (WebhookMessageMode)(((int)webhook.MessageMode + 1) % 3);
				_window.Settings.Save();
				RebuildChannels();
			}
		} );
		msgRow.AddStretchCell();

		if ( webhook.MessageMode == WebhookMessageMode.Custom )
		{
			var msgSo = webhook.CustomMessage.GetSerialized();
			msgSo.OnPropertyChanged += _ => _window.Settings.Save();
			card.Body.Add( SuperShotUI.SheetWidget( msgSo ) );
		}

		var actions = card.Body.AddRow();
		actions.Spacing = 4;
		actions.Add( new Button.Primary( "Capture + Post", "photo_camera" )
		{
			ToolTip = "Take a fresh capture, save it locally, and post it to this channel",
			Clicked = () => _ = SuperShotService.CaptureAndPostTo( webhook )
		} );
		actions.AddStretchCell();
		actions.Add( new Button.Danger( "Remove", "delete" ) { Clicked = () => RemoveChannel( webhook ) } );

		return card;
	}

	void AddChannel()
	{
		_window.Settings.Share.Webhooks.Add( new DiscordWebhookConfig() );
		_window.Settings.Save();
		RebuildChannels();
	}

	void RemoveChannel( DiscordWebhookConfig webhook )
	{
		_window.Settings.Share.Webhooks.Remove( webhook );
		_window.Settings.Save();
		RebuildChannels();
	}

	void SaveAndPostAll()
	{
		_window.SaveCurrent();
		_ = _window.PostCurrentToAll();
	}

	void CopyPath()
	{
		var path = _window.SaveCurrent();
		SuperShotService.CopyPathToClipboard( path );
	}
}