feat(ui/arch): Optimize Graph Dynamics, Immersive Reader, and Core Stability (#19)
This PR introduces a major optimization of graph dynamics, immersive reading experience, and architectural stabilization. ### 🚀 Key Improvements - **Knowledge Graph (Fix #16)**: - Implemented smooth D3.js transitions using the General Update Pattern. - Added "Neon Flash" entry animations and dynamic node dimming for better focus. - **Immersive Reader (Fix #12)**: - Standardized centered layout (`max-width: 800px`) with **Merriweather** typography. - Optimized line-height and letter-spacing for premium readability. - **Technical Code Blocks (Fix #20)**: - High-contrast dark containers for code snippets. - **JetBrains Mono** integration and neon-accented scrollbars. - **Architectural Stabilization**: - Enforced a strict **'no async void'** policy in UI services using `Func<Task>`. - Resolved WASM runtime DI errors by implementing dummy service proxies for server-side dependencies. - Replaced generic 'Not Found' message with a branded Nexus preloader. Fixes #7, Fixes #12, Fixes #16, Fixes #20. Reviewed-on: #19 Co-authored-by: Marek Jasiński <jasins.marek@gmail.com> Co-committed-by: Marek Jasiński <jasins.marek@gmail.com>
This commit was merged in pull request #19.
This commit is contained in:
@@ -46,34 +46,35 @@ public class EpubService : IEpubService
|
||||
return Result.Fail($"EPUB file at '{fullPath}' is not accessible or does not exist.");
|
||||
}
|
||||
|
||||
EpubBook book;
|
||||
try
|
||||
{
|
||||
book = await EpubReader.ReadBookAsync(fullPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result.Fail(new Error($"Failed to parse EPUB file. It might be corrupted or in use. Path: {fullPath}").CausedBy(ex));
|
||||
}
|
||||
var blocks = new List<ContentBlock>();
|
||||
int totalWordCount = 0;
|
||||
int blockCounter = 0;
|
||||
using var bookRef = await EpubReader.OpenBookAsync(fullPath);
|
||||
var readingOrder = bookRef.GetReadingOrder();
|
||||
|
||||
if (book.ReadingOrder == null || !book.ReadingOrder.Any())
|
||||
if (readingOrder == null || !readingOrder.Any())
|
||||
{
|
||||
return Result.Fail("The EPUB has no readable content files in ReadingOrder.");
|
||||
}
|
||||
|
||||
// Ensure index is within bounds
|
||||
if (chapterIndex < 0 || chapterIndex >= book.ReadingOrder.Count)
|
||||
if (chapterIndex < 0 || chapterIndex >= readingOrder.Count)
|
||||
{
|
||||
chapterIndex = 0; // Default to first chapter
|
||||
}
|
||||
|
||||
var chapter = book.ReadingOrder[chapterIndex];
|
||||
var chapterTitle = chapter.FilePath ?? $"Chapter {chapterIndex + 1}";
|
||||
var chapterRef = readingOrder[chapterIndex];
|
||||
|
||||
// Try to find a better title from navigation (TOC)
|
||||
var navigation = bookRef.GetNavigation();
|
||||
var chapterTitle = FindTitleInNavigation(navigation, chapterRef.FilePath)
|
||||
?? Path.GetFileNameWithoutExtension(chapterRef.FilePath)
|
||||
?? $"Chapter {chapterIndex + 1}";
|
||||
|
||||
var paragraphs = ExtractParagraphs(chapter.Content);
|
||||
var chapterContent = await chapterRef.ReadContentAsTextAsync();
|
||||
|
||||
var blocks = new List<ContentBlock>();
|
||||
int totalWordCount = 0;
|
||||
int blockCounter = 0;
|
||||
|
||||
var paragraphs = ExtractParagraphs(chapterContent);
|
||||
foreach (var p in paragraphs)
|
||||
{
|
||||
var sanitizedContent = SanitizeParagraph(p);
|
||||
@@ -99,7 +100,7 @@ public class EpubService : IEpubService
|
||||
blocks.Add(CreateAiTrigger($"trigger-{blockCounter++}"));
|
||||
}
|
||||
|
||||
return Result.Ok(new ReaderPageViewModel(blocks, chapterIndex, book.ReadingOrder.Count, chapterTitle));
|
||||
return Result.Ok(new ReaderPageViewModel(blocks, chapterIndex, readingOrder.Count, chapterTitle));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -162,4 +163,25 @@ public class EpubService : IEpubService
|
||||
new List<string> { "Podsumuj", "Generuj Quiz", "Pomiń" }
|
||||
);
|
||||
}
|
||||
|
||||
private string? FindTitleInNavigation(IEnumerable<EpubNavigationItemRef> navigation, string? filePath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filePath)) return null;
|
||||
|
||||
var fileName = Path.GetFileName(filePath);
|
||||
|
||||
foreach (var item in navigation)
|
||||
{
|
||||
// Match by full path or just filename as fallback
|
||||
if (item.Link?.ContentFilePath == filePath || item.Link?.ContentFilePath == fileName)
|
||||
return item.Title;
|
||||
|
||||
if (item.NestedItems != null && item.NestedItems.Any())
|
||||
{
|
||||
var childTitle = FindTitleInNavigation(item.NestedItems, filePath);
|
||||
if (childTitle != null) return childTitle;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user