Add URL history to tabs

This commit is contained in:
Stone_Red
2024-05-17 10:47:32 +02:00
parent c5c55c1164
commit 8d50d759c9
7 changed files with 116 additions and 83 deletions
@@ -30,7 +30,7 @@ internal class ChangeThemeCommand : ICommand
ThemeVariant theme = app.ActualThemeVariant; ThemeVariant theme = app.ActualThemeVariant;
app.RequestedThemeVariant = theme == ThemeVariant.Dark ? ThemeVariant.Light : ThemeVariant.Dark; app.RequestedThemeVariant = theme == ThemeVariant.Dark ? ThemeVariant.Light : ThemeVariant.Dark;
Settings.Current.DarkMode = app.RequestedThemeVariant == ThemeVariant.Dark; Settings.Current.DarkMode = app.RequestedThemeVariant == ThemeVariant.Dark;
GlobalState.ReloadContent(this); GlobalState.InvokeThemeChanged();
} }
} }
} }
+1 -1
View File
@@ -21,6 +21,6 @@ internal class HomeCommand : ICommand
public void Execute(object? parameter) public void Execute(object? parameter)
{ {
GlobalState.Url = Settings.Current.HomeUrl ?? string.Empty; GlobalState.CurrentTabState.Url = Settings.Current.HomeUrl ?? string.Empty;
} }
} }
+2 -2
View File
@@ -26,11 +26,11 @@ public class HyperlinkCommand : ICommand
if (Uri.IsWellFormedUriString(url, UriKind.Absolute)) if (Uri.IsWellFormedUriString(url, UriKind.Absolute))
{ {
GlobalState.Url = url; GlobalState.CurrentTabState.Url = url;
} }
else if (Uri.IsWellFormedUriString(url, UriKind.Relative) || url.StartsWith('/')) 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 else
{ {
+41
View File
@@ -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<string> BackHistory { get; } = new();
public Stack<string> 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);
}
}
+6 -40
View File
@@ -1,22 +1,16 @@
using Avalonia.Controls; using CuteUtils.Logging;
using CuteUtils.Logging; using Markdowser.Models;
using System; using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
namespace Markdowser.Utilities; namespace Markdowser.Utilities;
[SuppressMessage("Major Code Smell", "S4220:Events should have proper arguments", Justification = "<Pending>")]
internal static class GlobalState internal static class GlobalState
{ {
public static event EventHandler<string>? UrlChanged; public static event EventHandler? ThemeChanged;
public static event EventHandler? ContentReload; public static TabState CurrentTabState { get; internal set; } = new();
private static string url = string.Empty;
public static Logger Logger { get; } = new() public static Logger Logger { get; } = new()
{ {
@@ -54,36 +48,8 @@ internal static class GlobalState
} }
}; };
public static Stack<string> BackHistory { get; } = new(); public static void InvokeThemeChanged()
public static Stack<string> ForwardHistory { get; } = new();
public static string Url
{ {
get => url; ThemeChanged?.Invoke(null, EventArgs.Empty);
set
{
if (url != value)
{
BackHistory.Push(url);
ForwardHistory.Clear();
}
url = value;
UrlChanged?.Invoke(null, url);
}
}
public static ObservableCollection<TabItem> 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);
} }
} }
+1 -1
View File
@@ -36,7 +36,7 @@ public class HttpPathResolver : IPathResolver
if (!Uri.IsWellFormedUriString(relativeOrAbsolutePath, UriKind.Absolute)) 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}"); GlobalState.Logger.LogDebug($"Resolving image: {relativeOrAbsolutePath}");
@@ -27,32 +27,50 @@ public partial class MainWindowViewModel : ViewModelBase
private readonly ContentProcessorManager contentProcessorManager = new(); private readonly ContentProcessorManager contentProcessorManager = new();
private readonly CacheService cacheService = new CacheService(); private readonly CacheService cacheService = new CacheService();
private ContentViewModelBase content;
private TabItem currentTab = null!; private TabItem currentTab = null!;
private WindowState windowState; private WindowState windowState;
private bool showSidePanel; private bool showSidePanel;
private bool isBusy; private bool isBusy;
private int progress; private int progress;
public ObservableCollection<TabItem> Tabs => GlobalState.Tabs; public ObservableCollection<TabItem> Tabs { get; } = [new TabItem() { Header = "New Tab" }];
public TabItem CurrentTab public TabItem CurrentTab
{ {
get => currentTab; get => currentTab;
set set
{ {
if (currentTab is not null) CurrentTabState.UrlChanged -= UrlChanged;
{
currentTab.Tag = Url;
}
_ = this.RaiseAndSetIfChanged(ref currentTab!, value); _ = 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); 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 public WindowState WindowState
{ {
get => windowState; get => windowState;
@@ -61,14 +79,18 @@ public partial class MainWindowViewModel : ViewModelBase
public ContentViewModelBase Content public ContentViewModelBase Content
{ {
get => content; get => CurrentTabState.Content!;
set => this.RaiseAndSetIfChanged(ref content, value); set
{
CurrentTabState.Content = value;
this.RaisePropertyChanged();
}
} }
public string Url public string Url
{ {
get => GlobalState.Url; get => CurrentTabState.Url;
set => GlobalState.SetUrl(this, value); set => CurrentTabState.SetUrl(value, this);
} }
public bool ShowSidePanel public bool ShowSidePanel
@@ -97,18 +119,18 @@ public partial class MainWindowViewModel : ViewModelBase
public RawHtmlViewModel RawHtmlViewModel => new RawHtmlViewModel(new()); public RawHtmlViewModel RawHtmlViewModel => new RawHtmlViewModel(new());
public RawMarkdownViewModel RawMarkdownViewModel => new RawMarkdownViewModel(() => new()); public RawMarkdownViewModel RawMarkdownViewModel => new RawMarkdownViewModel(() => new());
public bool CloseTabEnabled => Tabs.Count > 1; public bool CloseTabEnabled => Tabs.Count > 1;
public bool BackEnabled => GlobalState.BackHistory.Count > 0; public bool BackEnabled => CurrentTabState.BackHistory.Count > 0;
public bool ForwardEnabled => GlobalState.ForwardHistory.Count > 0; public bool ForwardEnabled => CurrentTabState.ForwardHistory.Count > 0;
public bool ProgressIndeterminate => Progress == 0; public bool ProgressIndeterminate => Progress == 0;
public ICommand Browse => ReactiveCommand.Create(() => FetchUrl(true)); public ICommand Browse => ReactiveCommand.Create(() => FetchUrl(true));
public ICommand Reload => ReactiveCommand.Create(() => FetchUrl(false)); public ICommand Reload => ReactiveCommand.Create(() => FetchUrl(false));
public ICommand Back => ReactiveCommand.Create(() => public ICommand Back => ReactiveCommand.Create(() =>
{ {
if (GlobalState.BackHistory.Count > 0) if (CurrentTabState.BackHistory.Count > 0)
{ {
GlobalState.ForwardHistory.Push(GlobalState.Url); CurrentTabState.ForwardHistory.Push(CurrentTabState.Url);
Url = GlobalState.BackHistory.Pop(); Url = CurrentTabState.BackHistory.Pop();
FetchUrl(); FetchUrl();
this.RaisePropertyChanged(nameof(BackEnabled)); this.RaisePropertyChanged(nameof(BackEnabled));
@@ -118,10 +140,10 @@ public partial class MainWindowViewModel : ViewModelBase
public ICommand Forward => ReactiveCommand.Create(() => public ICommand Forward => ReactiveCommand.Create(() =>
{ {
if (GlobalState.ForwardHistory.Count > 0) if (CurrentTabState.ForwardHistory.Count > 0)
{ {
GlobalState.BackHistory.Push(GlobalState.Url); CurrentTabState.BackHistory.Push(CurrentTabState.Url);
Url = GlobalState.ForwardHistory.Pop(); Url = CurrentTabState.ForwardHistory.Pop();
FetchUrl(); FetchUrl();
this.RaisePropertyChanged(nameof(ForwardEnabled)); this.RaisePropertyChanged(nameof(ForwardEnabled));
@@ -178,30 +200,14 @@ public partial class MainWindowViewModel : ViewModelBase
{ {
httpClient.Timeout = TimeSpan.FromSeconds(10); httpClient.Timeout = TimeSpan.FromSeconds(10);
content = DefaultContent; Tabs[0].Tag = new TabState() { Content = DefaultContent };
contentProcessorManager.RegisterProcessor(new HtmlProcessor()); contentProcessorManager.RegisterProcessor(new HtmlProcessor());
contentProcessorManager.RegisterProcessor(new CommonImageProcessor()); 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(Icon));
this.RaisePropertyChanged(nameof(Content));
}; };
} }
@@ -272,10 +278,10 @@ public partial class MainWindowViewModel : ViewModelBase
HttpResponseMessage httpResponseMessage = await httpClient.GetAsync(Url); HttpResponseMessage httpResponseMessage = await httpClient.GetAsync(Url);
string? newUrl = httpResponseMessage.RequestMessage?.RequestUri?.ToString(); string? newUrl = httpResponseMessage.RequestMessage?.RequestUri?.ToString();
string oldUrl = Url;
if (newUrl is not null && newUrl != 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))); Dispatcher.UIThread.Post(() => WindowNotificationManager.Show(new Notification("Redirected", $"Redirected from\n{oldUrl}\nto\n{newUrl}", NotificationType.Warning)));
Url = newUrl; Url = newUrl;
} }
@@ -294,6 +300,12 @@ public partial class MainWindowViewModel : ViewModelBase
} }
Content = await contentProcessorManager.ProcessContent(httpResponseMessage, new Progress<ProcessingProgress>(p => Progress = p.Percentage)); Content = await contentProcessorManager.ProcessContent(httpResponseMessage, new Progress<ProcessingProgress>(p => Progress = p.Percentage));
if (oldUrl != Url)
{
cacheService.Set(oldUrl, Content);
}
cacheService.Set(Url, 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();
}
} }