Phase 0 (T1-3/4/7/9/11): find bar w/ native Find API + search engine + site zoom + force reload + F11, wired into MainWindow, E2E smoke (39 tests)
This commit is contained in:
parent
470c9267f2
commit
00a822eaeb
11 changed files with 584 additions and 18 deletions
126
VideoDownloader.App/Browser/FindSession.cs
Normal file
126
VideoDownloader.App/Browser/FindSession.cs
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
using Microsoft.Web.WebView2.Core;
|
||||
|
||||
namespace VideoDownloader.App.Browser;
|
||||
|
||||
/// <summary>네이티브 Find API 의 최소 추상화(단위 테스트용 fake 교체 가능).</summary>
|
||||
public interface IFindApi
|
||||
{
|
||||
Task StartAsync(string term, bool caseSensitive, bool matchWord, bool highlightAll);
|
||||
void FindNext();
|
||||
void FindPrevious();
|
||||
void Stop();
|
||||
event EventHandler<int>? MatchCountChanged;
|
||||
event EventHandler<int>? ActiveMatchIndexChanged;
|
||||
}
|
||||
|
||||
/// <summary>WebView2 네이티브 Find(CoreWebView2.Find) 를 IFindApi 로 감싼 어댑터.
|
||||
/// 옵션 객체는 public ctor 가 없어 CoreWebView2Environment.CreateFindOptions() 로 생성.</summary>
|
||||
public sealed class WebView2FindApi : IFindApi
|
||||
{
|
||||
private readonly CoreWebView2 _cv;
|
||||
private readonly CoreWebView2Environment _env;
|
||||
|
||||
public WebView2FindApi(CoreWebView2 cv, CoreWebView2Environment env)
|
||||
{
|
||||
_cv = cv;
|
||||
_env = env;
|
||||
}
|
||||
|
||||
public Task StartAsync(string term, bool caseSensitive, bool matchWord, bool highlightAll)
|
||||
{
|
||||
var o = _env.CreateFindOptions();
|
||||
o.FindTerm = term;
|
||||
o.IsCaseSensitive = caseSensitive;
|
||||
o.ShouldMatchWord = matchWord;
|
||||
o.ShouldHighlightAllMatches = highlightAll;
|
||||
o.SuppressDefaultFindDialog = true; // 자체 WPF 검색 바 사용
|
||||
return _cv.Find.StartAsync(o);
|
||||
}
|
||||
|
||||
public void FindNext() => _cv.Find.FindNext();
|
||||
public void FindPrevious() => _cv.Find.FindPrevious();
|
||||
public void Stop() => _cv.Find.Stop();
|
||||
|
||||
// 이벤트 인자가 object 로 전달되므로 매치 수는 Find 프로퍼티에서 직접 읽는다.
|
||||
public event EventHandler<int>? MatchCountChanged
|
||||
{
|
||||
add => _cv.Find.MatchCountChanged += (s, _) => value?.Invoke(s, _cv.Find.MatchCount);
|
||||
remove { }
|
||||
}
|
||||
|
||||
public event EventHandler<int>? ActiveMatchIndexChanged
|
||||
{
|
||||
add => _cv.Find.ActiveMatchIndexChanged += (s, _) => value?.Invoke(s, _cv.Find.ActiveMatchIndex);
|
||||
remove { }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 페이지 내 검색 세션 상태 머신(T1-3). 용어·매치 수·활성 인덱스를 유지하고
|
||||
/// UI(검색 바)가 표시할 "3 / 17" 텍스트를 제공한다.
|
||||
/// </summary>
|
||||
public sealed class FindSession : IDisposable
|
||||
{
|
||||
private readonly IFindApi _api;
|
||||
|
||||
public string Term { get; private set; } = string.Empty;
|
||||
public int MatchCount { get; private set; }
|
||||
public int ActiveMatchIndex { get; private set; }
|
||||
public bool IsCaseSensitive { get; private set; }
|
||||
public bool MatchWholeWord { get; private set; }
|
||||
|
||||
public event Action? StateChanged;
|
||||
|
||||
public FindSession(IFindApi api)
|
||||
{
|
||||
_api = api;
|
||||
_api.MatchCountChanged += (_, n) => { MatchCount = n; StateChanged?.Invoke(); };
|
||||
_api.ActiveMatchIndexChanged += (_, n) => { ActiveMatchIndex = n; StateChanged?.Invoke(); };
|
||||
}
|
||||
|
||||
public string StatusText
|
||||
{
|
||||
get
|
||||
{
|
||||
if (MatchCount <= 0) return "결과 없음";
|
||||
var oneBased = ActiveMatchIndex >= 0 ? ActiveMatchIndex + 1 : 0;
|
||||
return $"{oneBased} / {MatchCount}";
|
||||
}
|
||||
}
|
||||
|
||||
public async Task StartAsync(string term)
|
||||
{
|
||||
Term = term ?? "";
|
||||
if (string.IsNullOrWhiteSpace(term))
|
||||
{
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
MatchCount = 0;
|
||||
ActiveMatchIndex = -1;
|
||||
await _api.StartAsync(Term, IsCaseSensitive, MatchWholeWord, highlightAll: true);
|
||||
StateChanged?.Invoke();
|
||||
}
|
||||
|
||||
public void SetOptions(bool caseSensitive, bool matchWholeWord)
|
||||
{
|
||||
IsCaseSensitive = caseSensitive;
|
||||
MatchWholeWord = matchWholeWord;
|
||||
if (!string.IsNullOrWhiteSpace(Term))
|
||||
_ = StartAsync(Term); // 옵션 변경은 재시작 필요(네이티브 제약)
|
||||
}
|
||||
|
||||
public void Next() => _api.FindNext();
|
||||
public void Previous() => _api.FindPrevious();
|
||||
|
||||
public void Close()
|
||||
{
|
||||
_api.Stop();
|
||||
Term = string.Empty;
|
||||
MatchCount = 0;
|
||||
ActiveMatchIndex = -1;
|
||||
StateChanged?.Invoke();
|
||||
}
|
||||
|
||||
public void Dispose() => Close();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue