@inherits LocalizableComponentBase
@* On a phone the menu opens from the hamburger over the page, and closes after a choice (any navigation, a persona
switch), on a tap outside it or on Escape; from md it is the sidebar, which scrolls on its own when the window is
short (a phone held sideways). *@
@code {
[Inject] ConfirmService Confirm { get; set; }
[Inject] OverlayService Overlays { get; set; }
[Inject] EmojiService EmojiService { get; set; }
bool isMenuOpen;
IDisposable overlay;
// open, the Back button closes it
void SetMenu(bool open)
{
isMenuOpen = open;
if (open && overlay is null)
overlay = Overlays.Open(() => InvokeAsync(() =>
{
SetMenu(false);
StateHasChanged();
}));
else if (!open && overlay is not null)
{
overlay.Dispose();
overlay = null;
}
}
bool isPersonaMenuOpen;
// the server's custom emoji, for the other personas' names (/clientapi sends none with them), read when the menu opens
List serverEmojis = [];
// the current persona's are its account's; another's, the server's its name names
IReadOnlyCollection EmojisOf(ViewAvatar persona) =>
persona.Id == CascadingState.PersonaId && CascadingState.Persona is { } current ? current.Emojis : serverEmojis;
async Task PersonaMenuToggled()
{
if (isPersonaMenuOpen && CascadingState.Personas.Count > 1)
serverEmojis = await EmojiService.Custom();
}
string PersonaBackground => CascadingState.Persona?.Header is { Length: > 0 } header && !header.Contains("/missing-")
? $"background-image:linear-gradient(to left, var(--color-base-100) 55%, transparent), url('{header}');"
: null;
// The toggle and the slider work on the variant that is showing; with system theming that is the OS's choice.
bool IsDark => CascadingState.Status.IsDarkTheme();
short ThemeIndexColour => IsDark ? CascadingState.PublicCacheData.PageSettings.DarkThemeIndexColour : CascadingState.PublicCacheData.PageSettings.LightThemeIndexColour;
protected override void OnInitialized()
{
IsLoading = false;
Navigation.LocationChanged += OnLocationChanged;
}
void OnLocationChanged(object sender, LocationChangedEventArgs e)
{
if (!isMenuOpen)
return;
SetMenu(false);
InvokeAsync(StateHasChanged);
}
void CloseMenu() => SetMenu(false);
void OnKeyDown(KeyboardEventArgs e)
{
if (e.Key == "Escape")
SetMenu(false);
}
public override async ValueTask DisposeAsync()
{
Navigation.LocationChanged -= OnLocationChanged;
overlay?.Dispose();
await base.DisposeAsync();
}
async Task SwitchPersona(string personaId)
{
SetMenu(false);
var result = await CascadingState.SwitchPersona(personaId);
if (result.IsInvalid)
Toast.AddResponseError(result);
}
private void ToggleNavMenu() => SetMenu(!isMenuOpen);
async Task Logout()
{
SetMenu(false);
if (!await Confirm.Ask(Localizer["Log out?"], Localizer["Every persona of this login is signed out on this device."], Localizer["Logout"], true))
return;
Navigation.NavigateTo("logout");
}
protected async Task ResetToOriginalColour()
{
var isDark = IsDark;
await CascadingState.UpdatePageSettings(settings => settings.SetHue(isDark,
isDark ? PageSettings.DefaultDarkThemeIndexColour : PageSettings.DefaultLightThemeIndexColour));
}
// Picking a hue also leaves the grey themes, which otherwise ignore it.
protected async Task UpdateThemeColour(short indexColour)
{
var isDark = IsDark;
await CascadingState.UpdatePageSettings(settings => settings.SetHue(isDark, indexColour));
}
// Choosing light or dark here leaves system theming; Settings can turn it back on.
protected async Task UpdateThemeDarkMode(bool isDarkMode)
{
await CascadingState.UpdatePageSettings(settings => settings.SetDarkMode(isDarkMode));
}
}