Custom guideitem, cleaned models. Added favicon resolving.

This commit is contained in:
Max Holleman 2023-05-15 15:53:08 +02:00
parent ff1185729b
commit 88ce202de4
13 changed files with 171 additions and 119 deletions

View File

@ -1,10 +1,13 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebSharpRSS", "WebSharpRSS\WebSharpRSS.csproj", "{749FE445-8D46-4631-BB57-FC648E22ADB6}"
# Visual Studio Version 17
VisualStudioVersion = 17.5.33627.172
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebSharpRSS", "WebSharpRSS\WebSharpRSS.csproj", "{749FE445-8D46-4631-BB57-FC648E22ADB6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpRss", "SharpRss\SharpRss.csproj", "{DB3777BA-A383-4B83-AF65-DE51907CE75A}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpRss", "SharpRss\SharpRss.csproj", "{DB3777BA-A383-4B83-AF65-DE51907CE75A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ToolQit", "ToolQit\ToolQit\ToolQit.csproj", "{BD905344-9DBF-4986-B853-E70B22848876}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ToolQit", "ToolQit\ToolQit\ToolQit.csproj", "{BD905344-9DBF-4986-B853-E70B22848876}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -25,4 +28,7 @@ Global
{BD905344-9DBF-4986-B853-E70B22848876}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BD905344-9DBF-4986-B853-E70B22848876}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View File

@ -16,7 +16,7 @@ namespace SharpRss
public static async Task<Feed> GetFeed(string urlKey)
{
Log.Verbose("Request for: {UrlKey}", urlKey);
Log.Verbose("Fetching feed: {UrlKey}", urlKey);
if (urlKey.IsNullEmptyWhiteSpace())
{
Log.Error("RSS Url is empty!");

View File

@ -5,7 +5,7 @@ namespace SharpRss.Models
/// <summary>
/// To store and load data from file/database
/// </summary>
public class CategoryModel : IGuideItem
public class CategoryModel
{
public CategoryModel(CategoryModel model)
{
@ -20,7 +20,5 @@ namespace SharpRss.Models
public string Name { get; set; }
public HashSet<FeedModel> Feeds { get; set; }
public bool IsSelected { get; set; }
public bool IsExpanded { get; set; }
}
}

View File

@ -1,43 +1,40 @@
using System.Dynamic;
using System.Linq;
using System.Threading.Tasks;
using CodeHollow.FeedReader;
using Serilog;
using ToolQit.Extensions;
using CodeHollow.FeedReader;
namespace SharpRss.Models
{
public class FeedModel : IGuideItem
public class FeedModel
{
public FeedModel(string rssUrl)
{
_rssUrl = rssUrl;
Task.Run(async () => Base = await FeedCache.GetFeed(_rssUrl));
_fetchTask = FetchAsync();
}
private Task _fetchTask;
private async Task FetchAsync()
{
IsReady = false;
_feed = await FeedCache.GetFeed(_rssUrl);
IsReady = true;
}
public Feed Base { get; private set; }
public bool IsSelected { get; set; }
public bool IsExpanded { get; set; }
private Feed? _feed;
public Feed Base {
get
{
if (_feed == null)
{
if (_fetchTask.IsFaulted)
{ IsFaulted = _fetchTask.IsFaulted; return new Feed(); }
if (!(_fetchTask.Status == TaskStatus.Running || _fetchTask.Status == TaskStatus.WaitingForActivation))
_fetchTask.Start();
_fetchTask.Wait();
}
return _feed ?? new Feed();
}
}
public bool IsFaulted { get; private set; }
public bool IsReady { get; private set; }
private readonly string _rssUrl;
/*public async void Load(bool reload = false)
{
if (Base != null && !reload || !IsReady) return;
if (_rssUrl.IsNullEmptyWhiteSpace())
{
Log.Error("RSS Url is empty!");
return;
}
IsReady = false;
string feedUrl;
var urls = await FeedReader.GetFeedUrlsFromUrlAsync(_rssUrl);
if (!urls.Any())
feedUrl = _rssUrl;
else
feedUrl = urls.First().Url;
Log.Verbose("Creating feed: {FeedUrl}", feedUrl);
Base = await FeedReader.ReadAsync(feedUrl);
IsReady = true;
}*/
}
}

View File

@ -1,8 +0,0 @@
namespace SharpRss.Models
{
public interface IGuideItem
{
public bool IsSelected { get; set; }
public bool IsExpanded { get; set; }
}
}

View File

@ -22,7 +22,7 @@ namespace SharpRss
private static HashSet<FeedModel> feedSet = new HashSet<FeedModel>()
{
new FeedModel("http://fedoramagazine.org/feed/"),
new FeedModel("https://www.nasa.gov/rss/dyn/breaking_news.rss")
new FeedModel("https://www.nasa.gov/rss/dyn/breaking_news.rss"),
};
private static HashSet<FeedModel> feedSet2 = new HashSet<FeedModel>()
{

View File

@ -12,7 +12,8 @@ namespace WebSharpRSS
public static void SetAppDefaultSettings(this DataContainer dataCon)
{
var paths = dataCon["Paths"];
paths.Set("FaviconResolveUrl", "https://icons.duckduckgo.com/ip3/{0}.ico", false);
//paths.Set("FaviconResolveUrl", "https://icons.duckduckgo.com/ip3/{0}.ico", false);
paths.Set("FaviconResolveUrl", "http://www.google.com/s2/favicons?domain={0}", false);
paths.Set("LogPath", Path.Combine(Environment.CurrentDirectory, "logs", "log_.json"), false);
var dbSql = dataCon["SQL"];

View File

@ -1,40 +0,0 @@
using System.Collections.Generic;
using CodeHollow.FeedReader;
using SharpRss.Models;
namespace WebSharpRSS.Models
{
public class GuideItem
{
public GuideItem(CategoryModel catModel)
{
_categoryModel = catModel;
Feeds = _categoryModel.Feeds;
}
public GuideItem(FeedModel feedModel)
{
_feedModel = feedModel;
Feed = _feedModel.Base;
}
private readonly CategoryModel? _categoryModel;
private readonly FeedModel? _feedModel;
public string Title { get; set; }
public bool IsSelected { get; set; }
public string Icon { get; set; }
// Category
public bool IsExpanded { get; set; }
public HashSet<FeedModel>? Feeds { get; set; }
// Feed
public Feed? Feed { get; set; }
// Functions
public async void ItemClick()
{
IsExpanded = !IsExpanded;
}
}
}

View File

@ -0,0 +1,43 @@
using CodeHollow.FeedReader;
using MudBlazor;
using SharpRss.Models;
using ToolQit;
namespace WebSharpRSS.Models
{
public class GuideItemModel
{
public GuideItemModel(CategoryModel catModel)
{
CategoryModel = catModel;
Feeds = CategoryModel.Feeds.Where(x => !x.IsFaulted && x.Base != null).Select(x => new GuideItemModel(x)).ToHashSet();
Title = CategoryModel.Name;
Icon = Icons.Material.Filled.RssFeed;
}
public GuideItemModel(FeedModel feedModel)
{
if (feedModel.IsFaulted)
return;
FeedModel = feedModel;
Feed = FeedModel.Base;
Title = Feed.Title;
string faviconAdress = Feed.Link.Remove(Feed.Link.IndexOf("http"), Feed.Link.IndexOf("://") + 3);
FaviconUrl = string.Format(Caretaker.Settings["Paths"].GetString("FaviconResolveUrl"), faviconAdress);
}
public readonly CategoryModel? CategoryModel;
public readonly FeedModel? FeedModel;
public string Title { get; set; } = string.Empty;
public bool IsSelected { get; set; }
public string? Icon { get; set; }
public string? FaviconUrl { get; set; }
// Category
public bool IsExpanded { get; set; }
public HashSet<GuideItemModel>? Feeds { get; set; }
// Feed
public Feed? Feed { get; set; }
}
}

View File

@ -5,8 +5,10 @@ using MudBlazor;
using MudBlazor.Services;
using Serilog;
using SharpRss;
using SharpRss.Models;
using ToolQit;
using WebSharpRSS;
using WebSharpRSS.Models;
Caretaker.Settings.SetAppDefaultSettings();
Bootstrapper.SetupLogging();

View File

@ -3,10 +3,10 @@
@using ToolQit
@using ToolQit.Containers
@using ToolQit.Extensions
@using WebSharpRSS.Models;
<style>
.cat-item {
background: var(--background-color);
tab-index: 0;
width: 100%;
display: flex;
position: relative;
@ -34,7 +34,6 @@
.feed-item {
background: var(--background-color);
tab-index: 0;
width: 100%;
display: flex;
position: relative;
@ -64,21 +63,31 @@
<div>
<MudText>@HeaderText</MudText>
@foreach (CategoryModel guideCategory in Categories)
@foreach (GuideItemModel guideModel in GuideItems)
{
<GuideItem Model="@guideModel" ItemClicked="ItemClicked"/>
#if false
<div>
<div @onclick="@(() => ItemClicked(guideCategory))" class="cat-item mud-ripple" style="--hover-bg-color: @Colors.Blue.Accent1; --background-color: @(guideCategory.IsSelected ? Colors.Blue.Accent2 : "transparent")">
<div @onclick="@(() => guideItem.ItemClick())" class="cat-item mud-ripple" style="--hover-bg-color: @Colors.Blue.Accent1; --background-color: @(guideItem.IsSelected ? Colors.Blue.Accent2 : "transparent")">
<div class="cat-item-icon">
<MudIcon Class="pointer-events-none" Icon="@Icons.Material.Filled.RssFeed" Size="Size.Medium"/>
@if (guideItem.FaviconUrl != null)
{
<MudImage ObjectFit="ObjectFit.ScaleDown" Src="@guideItem.FaviconUrl" />
}
else
{
<MudIcon Class="pointer-events-none" Icon="@guideItem.Icon" Size="Size.Medium" />
}
</div>
<div class="cat-item-text">
<MudText Class="pointer-events-none" Typo="Typo.subtitle1">@guideCategory.Name</MudText>
<MudText Class="pointer-events-none" Typo="Typo.subtitle1">@guideItem.Title</MudText>
</div>
</div>
@* Feeds *@
@if (guideCategory.IsExpanded && guideCategory.Feeds != null)
@if (guideItem.IsExpanded && guideItem.Feeds != null)
{
foreach (FeedModel feed in guideCategory.Feeds)
foreach (FeedModel feed in guideItem.Feeds)
{
if (feed == null || feed.Base == null) continue;
<div @onclick="() => ItemClicked(feed)" class="feed-item mud-ripple" style="--hover-bg-color: @Colors.Blue.Accent1; --background-color: @(feed.IsSelected ? Colors.Blue.Accent2 : "transparent")">
@ -100,40 +109,41 @@
}
}
</div>
#endif
}
</div>
@code {
[Parameter]
public string HeaderText { get; set; }
public string HeaderText { get; set; } = string.Empty;
[Parameter]
public HashSet<CategoryModel> Categories { get; set; } = new HashSet<CategoryModel>();
public HashSet<GuideItemModel> GuideItems { get; set; } = new HashSet<GuideItemModel>();
[Parameter]
public Action<CategoryModel>? CatItemClicked { get; set; }
[Parameter]
public Action<FeedModel>? FeedItemClicked { get; set; }
IGuideItem? _selectedItem;
GuideItemModel? _selectedItem;
void ItemClicked(IGuideItem categoryItem)
void ItemClicked(GuideItemModel model)
{
categoryItem.IsExpanded = !categoryItem.IsExpanded;
if (_selectedItem != categoryItem)
model.IsExpanded = !model.IsExpanded;
if (_selectedItem != model)
{
if (_selectedItem != null)
_selectedItem.IsSelected = false;
_selectedItem = categoryItem;
_selectedItem = model;
_selectedItem.IsSelected = true;
}
switch (categoryItem)
if (model.Feeds != null && model.Feeds.Count >= 1)
{
case CategoryModel catGuideItem:
CatItemClicked?.Invoke(catGuideItem);
break;
case FeedModel feedGuideItem:
FeedItemClicked?.Invoke(feedGuideItem);
break;
if (model.CategoryModel != null)
CatItemClicked?.Invoke(model.CategoryModel);
}
else
{
if (model.FeedModel != null)
FeedItemClicked?.Invoke(model.FeedModel);
}
}
}

View File

@ -0,0 +1,45 @@
@using WebSharpRSS.Models;
<div>
<div @onclick="@(() => ItemClickedVoid())" class="cat-item mud-ripple" style="--hover-bg-color: @Colors.Blue.Accent1; --background-color: @(Model.IsSelected ? Colors.Blue.Accent2 : "transparent")">
<div class="cat-item-icon">
@if (Model.FaviconUrl != null)
{
<MudImage ObjectFit="ObjectFit.ScaleDown" Src="@Model.FaviconUrl" />
}
else
{
<MudIcon Class="pointer-events-none" Icon="@Model.Icon" Size="Size.Medium" />
}
</div>
<div class="cat-item-text">
<MudText Class="pointer-events-none" Typo="Typo.subtitle1">@Model.Title</MudText>
</div>
</div>
@if (IsExpanded && Model.Feeds != null && !(Model.Feeds.Count <= 0))
{
foreach (GuideItemModel item in Model.Feeds)
{
<div style="margin-left: 20px">
<GuideItem Model="@item" />
</div>
}
}
</div>
@code {
[Parameter]
public GuideItemModel Model { get; set; }
[Parameter]
public EventCallback<GuideItemModel> ItemClicked { get; set; }
public bool IsExpanded { get; set; }
private void ItemClickedVoid()
{
IsExpanded = !IsExpanded;
ItemClicked.InvokeAsync(Model);
}
}

View File

@ -2,6 +2,7 @@
@using MudBlazor.Utilities
@using CodeHollow.FeedReader
@using Serilog
@using WebSharpRSS.Models;
@inject RssService _rssService
@ -9,20 +10,17 @@
<MudNavMenu>
<MudNavLink Href="/" Icon="@Icons.Material.Filled.Home">Home</MudNavLink>
</MudNavMenu>
<CategoryGuide Categories="Categories" CatItemClicked="CategoryClicked" FeedItemClicked="FeedClicked"/>
<CategoryGuide GuideItems="Categories" CatItemClicked="CategoryClicked" FeedItemClicked="FeedClicked"/>
</MudStack>
@code {
public HashSet<CategoryModel> Categories = new HashSet<CategoryModel>();
protected override void OnInitialized()
public HashSet<GuideItemModel> Categories = new HashSet<GuideItemModel>();
protected override async void OnInitialized()
{
Log.Verbose("Setting up test data");
Categories = _rssService.GetCategories().Result;
/*Cats = _rssService.GetCategories().Result.Select(x => new GuideModel(x)).ToHashSet();*/
/*Categories.Add(new CategoryGuideItem() { CategoryTitle = "Social", CategoryIcon = Icons.Material.Filled.People });
Categories.Add(new CategoryGuideItem() { CategoryTitle = "Blogs", CategoryIcon = Icons.Material.Filled.RssFeed, CategoryHexColor = Colors.Green.Accent1 });
Categories.Add(new CategoryGuideItem() { CategoryTitle = "Tech", CategoryIcon = Icons.Material.Filled.Computer, CategoryHexColor = Colors.Brown.Lighten1 });
Categories.Add(new CategoryGuideItem() { CategoryTitle = "News", CategoryIcon = Icons.Material.Filled.Newspaper, CategoryHexColor = Colors.Red.Accent1 });*/
//Categories = _rssService.GetCategories().Result;
HashSet<CategoryModel> cats = await _rssService.GetCategories();
await Task.Run(() => Categories = cats.Select(x => new GuideItemModel(x)).ToHashSet());
}
private void Callback(MudListItem obj)