From 8d50d759c9f992e92e4dbf7b25574ef605c1cf1d Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Fri, 17 May 2024 10:47:16 +0200 Subject: [PATCH] Add URL history to tabs --- src/Markdowser/Commands/ChangeThemeCommand.cs | 2 +- src/Markdowser/Commands/HomeCommand.cs | 2 +- src/Markdowser/Commands/HyperlinkCommand.cs | 4 +- src/Markdowser/Models/TabState.cs | 41 +++++++ src/Markdowser/Utilities/GlobalState.cs | 46 ++------ src/Markdowser/Utilities/HttpPathResolver.cs | 2 +- .../ViewModels/MainWindowViewModel.cs | 102 +++++++++++------- 7 files changed, 116 insertions(+), 83 deletions(-) create mode 100644 src/Markdowser/Models/TabState.cs diff --git a/src/Markdowser/Commands/ChangeThemeCommand.cs b/src/Markdowser/Commands/ChangeThemeCommand.cs index 074c100..fcc1ef2 100644 --- a/src/Markdowser/Commands/ChangeThemeCommand.cs +++ b/src/Markdowser/Commands/ChangeThemeCommand.cs @@ -30,7 +30,7 @@ internal class ChangeThemeCommand : ICommand ThemeVariant theme = app.ActualThemeVariant; app.RequestedThemeVariant = theme == ThemeVariant.Dark ? ThemeVariant.Light : ThemeVariant.Dark; Settings.Current.DarkMode = app.RequestedThemeVariant == ThemeVariant.Dark; - GlobalState.ReloadContent(this); + GlobalState.InvokeThemeChanged(); } } } \ No newline at end of file diff --git a/src/Markdowser/Commands/HomeCommand.cs b/src/Markdowser/Commands/HomeCommand.cs index f58d239..9d083f0 100644 --- a/src/Markdowser/Commands/HomeCommand.cs +++ b/src/Markdowser/Commands/HomeCommand.cs @@ -21,6 +21,6 @@ internal class HomeCommand : ICommand public void Execute(object? parameter) { - GlobalState.Url = Settings.Current.HomeUrl ?? string.Empty; + GlobalState.CurrentTabState.Url = Settings.Current.HomeUrl ?? string.Empty; } } \ No newline at end of file diff --git a/src/Markdowser/Commands/HyperlinkCommand.cs b/src/Markdowser/Commands/HyperlinkCommand.cs index c2a10d8..1e3e4cf 100644 --- a/src/Markdowser/Commands/HyperlinkCommand.cs +++ b/src/Markdowser/Commands/HyperlinkCommand.cs @@ -26,11 +26,11 @@ public class HyperlinkCommand : ICommand if (Uri.IsWellFormedUriString(url, UriKind.Absolute)) { - GlobalState.Url = url; + GlobalState.CurrentTabState.Url = url; } else if (Uri.IsWellFormedUriString(url, UriKind.Relative) || url.StartsWith('/')) { - GlobalState.Url = new Uri(new Uri(GlobalState.Url), url).ToString(); + GlobalState.CurrentTabState.Url = new Uri(new Uri(GlobalState.CurrentTabState.Url), url).ToString(); } else { diff --git a/src/Markdowser/Models/TabState.cs b/src/Markdowser/Models/TabState.cs new file mode 100644 index 0000000..c7ff6ae --- /dev/null +++ b/src/Markdowser/Models/TabState.cs @@ -0,0 +1,41 @@ +using Markdowser.ViewModels; + +using System; +using System.Collections.Generic; + +namespace Markdowser.Models; + +public class TabState +{ + public event EventHandler? UrlChanged; + + private string url = string.Empty; + + public Stack BackHistory { get; } = new(); + + public Stack ForwardHistory { get; } = new(); + + public string Url + { + get => url; + set + { + if (url != value) + { + BackHistory.Push(url); + ForwardHistory.Clear(); + } + + url = value; + UrlChanged?.Invoke(this, EventArgs.Empty); + } + } + + public ContentViewModelBase? Content { get; set; } + + public void SetUrl(string url, object? sender = null) + { + this.url = url; + UrlChanged?.Invoke(sender ?? this, EventArgs.Empty); + } +} \ No newline at end of file diff --git a/src/Markdowser/Utilities/GlobalState.cs b/src/Markdowser/Utilities/GlobalState.cs index 38cc0f6..a6acb90 100644 --- a/src/Markdowser/Utilities/GlobalState.cs +++ b/src/Markdowser/Utilities/GlobalState.cs @@ -1,22 +1,16 @@ -using Avalonia.Controls; +using CuteUtils.Logging; -using CuteUtils.Logging; +using Markdowser.Models; using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics.CodeAnalysis; namespace Markdowser.Utilities; -[SuppressMessage("Major Code Smell", "S4220:Events should have proper arguments", Justification = "")] internal static class GlobalState { - public static event EventHandler? UrlChanged; + public static event EventHandler? ThemeChanged; - public static event EventHandler? ContentReload; - - private static string url = string.Empty; + public static TabState CurrentTabState { get; internal set; } = new(); public static Logger Logger { get; } = new() { @@ -54,36 +48,8 @@ internal static class GlobalState } }; - public static Stack BackHistory { get; } = new(); - - public static Stack ForwardHistory { get; } = new(); - - public static string Url + public static void InvokeThemeChanged() { - get => url; - set - { - if (url != value) - { - BackHistory.Push(url); - ForwardHistory.Clear(); - } - - url = value; - UrlChanged?.Invoke(null, url); - } - } - - public static ObservableCollection Tabs { get; internal set; } = [new TabItem() { Header = "New Tab" }]; - - internal static void SetUrl(object sender, string url) - { - GlobalState.url = url; - UrlChanged?.Invoke(sender, url); - } - - internal static void ReloadContent(object sender) - { - ContentReload?.Invoke(sender, EventArgs.Empty); + ThemeChanged?.Invoke(null, EventArgs.Empty); } } \ No newline at end of file diff --git a/src/Markdowser/Utilities/HttpPathResolver.cs b/src/Markdowser/Utilities/HttpPathResolver.cs index a121f17..cdc2c4b 100644 --- a/src/Markdowser/Utilities/HttpPathResolver.cs +++ b/src/Markdowser/Utilities/HttpPathResolver.cs @@ -36,7 +36,7 @@ public class HttpPathResolver : IPathResolver if (!Uri.IsWellFormedUriString(relativeOrAbsolutePath, UriKind.Absolute)) { - relativeOrAbsolutePath = new Uri(new Uri(GlobalState.Url), relativeOrAbsolutePath).ToString(); + relativeOrAbsolutePath = new Uri(new Uri(GlobalState.CurrentTabState.Url), relativeOrAbsolutePath).ToString(); } GlobalState.Logger.LogDebug($"Resolving image: {relativeOrAbsolutePath}"); diff --git a/src/Markdowser/ViewModels/MainWindowViewModel.cs b/src/Markdowser/ViewModels/MainWindowViewModel.cs index 00a53e6..ab0cf7e 100644 --- a/src/Markdowser/ViewModels/MainWindowViewModel.cs +++ b/src/Markdowser/ViewModels/MainWindowViewModel.cs @@ -27,32 +27,50 @@ public partial class MainWindowViewModel : ViewModelBase private readonly ContentProcessorManager contentProcessorManager = new(); private readonly CacheService cacheService = new CacheService(); - private ContentViewModelBase content; private TabItem currentTab = null!; private WindowState windowState; private bool showSidePanel; private bool isBusy; private int progress; - public ObservableCollection Tabs => GlobalState.Tabs; + public ObservableCollection Tabs { get; } = [new TabItem() { Header = "New Tab" }]; public TabItem CurrentTab { get => currentTab; set { - if (currentTab is not null) - { - currentTab.Tag = Url; - } + CurrentTabState.UrlChanged -= UrlChanged; _ = this.RaiseAndSetIfChanged(ref currentTab!, value); + this.RaisePropertyChanged(nameof(CurrentTabState)); + this.RaisePropertyChanged(nameof(Url)); + this.RaisePropertyChanged(nameof(BackEnabled)); + this.RaisePropertyChanged(nameof(ForwardEnabled)); - Url = value?.Tag?.ToString() ?? string.Empty; + GlobalState.CurrentTabState = CurrentTabState; + CurrentTabState.UrlChanged += UrlChanged; FetchUrl(true); } } + public TabState CurrentTabState + { + get + { + if (CurrentTab is null) + { + return new(); + } + + return Dispatcher.UIThread.Invoke(() => + { + CurrentTab.Tag ??= new TabState(); + return (TabState)CurrentTab.Tag; + }); + } + } + public WindowState WindowState { get => windowState; @@ -61,14 +79,18 @@ public partial class MainWindowViewModel : ViewModelBase public ContentViewModelBase Content { - get => content; - set => this.RaiseAndSetIfChanged(ref content, value); + get => CurrentTabState.Content!; + set + { + CurrentTabState.Content = value; + this.RaisePropertyChanged(); + } } public string Url { - get => GlobalState.Url; - set => GlobalState.SetUrl(this, value); + get => CurrentTabState.Url; + set => CurrentTabState.SetUrl(value, this); } public bool ShowSidePanel @@ -97,18 +119,18 @@ public partial class MainWindowViewModel : ViewModelBase public RawHtmlViewModel RawHtmlViewModel => new RawHtmlViewModel(new()); public RawMarkdownViewModel RawMarkdownViewModel => new RawMarkdownViewModel(() => new()); public bool CloseTabEnabled => Tabs.Count > 1; - public bool BackEnabled => GlobalState.BackHistory.Count > 0; - public bool ForwardEnabled => GlobalState.ForwardHistory.Count > 0; + public bool BackEnabled => CurrentTabState.BackHistory.Count > 0; + public bool ForwardEnabled => CurrentTabState.ForwardHistory.Count > 0; public bool ProgressIndeterminate => Progress == 0; public ICommand Browse => ReactiveCommand.Create(() => FetchUrl(true)); public ICommand Reload => ReactiveCommand.Create(() => FetchUrl(false)); public ICommand Back => ReactiveCommand.Create(() => { - if (GlobalState.BackHistory.Count > 0) + if (CurrentTabState.BackHistory.Count > 0) { - GlobalState.ForwardHistory.Push(GlobalState.Url); - Url = GlobalState.BackHistory.Pop(); + CurrentTabState.ForwardHistory.Push(CurrentTabState.Url); + Url = CurrentTabState.BackHistory.Pop(); FetchUrl(); this.RaisePropertyChanged(nameof(BackEnabled)); @@ -118,10 +140,10 @@ public partial class MainWindowViewModel : ViewModelBase public ICommand Forward => ReactiveCommand.Create(() => { - if (GlobalState.ForwardHistory.Count > 0) + if (CurrentTabState.ForwardHistory.Count > 0) { - GlobalState.BackHistory.Push(GlobalState.Url); - Url = GlobalState.ForwardHistory.Pop(); + CurrentTabState.BackHistory.Push(CurrentTabState.Url); + Url = CurrentTabState.ForwardHistory.Pop(); FetchUrl(); this.RaisePropertyChanged(nameof(ForwardEnabled)); @@ -178,30 +200,14 @@ public partial class MainWindowViewModel : ViewModelBase { httpClient.Timeout = TimeSpan.FromSeconds(10); - content = DefaultContent; + Tabs[0].Tag = new TabState() { Content = DefaultContent }; contentProcessorManager.RegisterProcessor(new HtmlProcessor()); contentProcessorManager.RegisterProcessor(new CommonImageProcessor()); - GlobalState.UrlChanged += (sender, url) => + GlobalState.ThemeChanged += (s, e) => { - this.RaisePropertyChanged(nameof(ForwardEnabled)); - this.RaisePropertyChanged(nameof(BackEnabled)); - this.RaisePropertyChanged(nameof(Url)); - - if (sender == this) - { - return; - } - - FetchUrl(); - }; - - GlobalState.ContentReload += (sender, _) => - { - // Update icon when dark mode changes this.RaisePropertyChanged(nameof(Icon)); - this.RaisePropertyChanged(nameof(Content)); }; } @@ -272,10 +278,10 @@ public partial class MainWindowViewModel : ViewModelBase HttpResponseMessage httpResponseMessage = await httpClient.GetAsync(Url); string? newUrl = httpResponseMessage.RequestMessage?.RequestUri?.ToString(); + string oldUrl = Url; if (newUrl is not null && newUrl != Url) { - string oldUrl = Url; Dispatcher.UIThread.Post(() => WindowNotificationManager.Show(new Notification("Redirected", $"Redirected from\n{oldUrl}\nto\n{newUrl}", NotificationType.Warning))); Url = newUrl; } @@ -294,6 +300,12 @@ public partial class MainWindowViewModel : ViewModelBase } Content = await contentProcessorManager.ProcessContent(httpResponseMessage, new Progress(p => Progress = p.Percentage)); + + if (oldUrl != Url) + { + cacheService.Set(oldUrl, Content); + } + cacheService.Set(Url, Content); } @@ -340,4 +352,18 @@ public partial class MainWindowViewModel : ViewModelBase } }); } + + private void UrlChanged(object? sender, EventArgs e) + { + this.RaisePropertyChanged(nameof(ForwardEnabled)); + this.RaisePropertyChanged(nameof(BackEnabled)); + this.RaisePropertyChanged(nameof(Url)); + + if (sender == this) + { + return; + } + + FetchUrl(); + } } \ No newline at end of file