UI 복잡 버전 234 RED→GREEN 완료(docs/UI_USECASES.md §A~§J): 라이브 테마 전환(DynamicResource 전환+ThemeResolver+HighContrast 테마, 재시작 없음), 디자인 토큰 체계·WCAG 대비 결함 5건 수정, 설정창 카테고리 내비/검색 재구성+AppConfig 40여 속성, 다운로드 관리자(속도/ETA/세그먼트맵/전체 일시정지-이어받기/CSV 내보내기/예약 게이트/중복 확인), 라이브러리·아카이브·작업공간·재생목록·즐겨찾기 편집 뷰, 접근성(자동화 속성 30여 개·커스텀 피어·docs/ACCESSIBILITY.md), 인터랙션(휠 클릭 새 탭·XButton 탐색·F12·영역 캡처·프로필 전환), 상태/알림(토스트·알림센터·온보딩·CrashReportDialog). 보강 테스트 35건+실창 E2E 4건(StaDispatcher 기반 — 프록시 설정이 기시작 핸들러를 건드리는 크래시 실결함 수정 포함) — 453/453 GREEN. 이전 미커밋 작업(CI 워크플로·Mdns·QueueHub·UpdateChecker·AGENTS/CLAUDE·빌드 스크립트) 일괄 포함

This commit is contained in:
Yun Chan 2026-08-17 15:54:53 +09:00
parent 5a0ce730b2
commit 17f2f91152
88 changed files with 9463 additions and 1127 deletions

View file

@ -0,0 +1,78 @@
namespace VideoDownloader.App.Services;
/// <summary>단일 단축키 정의(명령 키 + 표시 조합).</summary>
public sealed record ShortcutBinding(string Command, string Gesture);
/// <summary>
/// UC-K02/K03 단축키 관리 — 기본 조합표 위에 사용자 재배치(AppConfig.Shortcuts)를 얹고,
/// 저장 전에 같은 조합을 쓰는 명령 충돌을 검증한다.
/// </summary>
public sealed class ShortcutManager
{
private readonly Dictionary<string, string> _user;
/// <summary>기본 단축키표(명령 키 → 기본 조합).</summary>
public static readonly IReadOnlyDictionary<string, string> Defaults = new Dictionary<string, string>
{
["newtab"] = "Ctrl+T",
["closetab"] = "Ctrl+W",
["reopentab"] = "Ctrl+Shift+T",
["find"] = "Ctrl+F",
["address"] = "Ctrl+L",
["downloads"] = "Ctrl+J",
["palette"] = "Ctrl+K",
["devtools"] = "F12",
["fullscreen"] = "F11",
["refresh"] = "F5",
["print"] = "Ctrl+P",
["pauseall"] = "Ctrl+Shift+P",
};
public ShortcutManager(IDictionary<string, string>? userShortcuts = null)
{
_user = new Dictionary<string, string>(userShortcuts ?? new Dictionary<string, string>(), StringComparer.OrdinalIgnoreCase);
}
/// <summary>명령의 현재 조합(사용자 지정 우선, 없으면 기본값).</summary>
public string GestureFor(string command)
=> _user.TryGetValue(command, out var g) ? g : Defaults.GetValueOrDefault(command, "");
/// <summary>전체 목록(기본 전체 + 사용자 오버라이드 반영).</summary>
public List<ShortcutBinding> All()
=> Defaults.Keys
.Select(c => new ShortcutBinding(c, GestureFor(c)))
.Concat(_user.Where(kv => !Defaults.ContainsKey(kv.Key)).Select(kv => new ShortcutBinding(kv.Key, kv.Value)))
.ToList();
/// <summary>UC-K03 충돌 검증 — 같은 조합에 묶인 명령 그룹(2개 이상일 때만 반환).</summary>
public List<List<ShortcutBinding>> FindConflicts()
=> All()
.GroupBy(b => Normalize(b.Gesture))
.Where(g => g.Count() > 1 && g.Key.Length > 0)
.Select(g => g.ToList())
.ToList();
/// <summary>조합 문자열 파싱("Ctrl+Shift+P" → ModifierKeys+Key). 파싱 실패 시 null.</summary>
public static (System.Windows.Input.ModifierKeys Mods, System.Windows.Input.Key Key)? Parse(string gesture)
{
if (string.IsNullOrWhiteSpace(gesture)) return null;
var mods = System.Windows.Input.ModifierKeys.None;
var key = System.Windows.Input.Key.None;
foreach (var part in gesture.Split('+'))
{
switch (part.Trim().ToLowerInvariant())
{
case "ctrl": mods |= System.Windows.Input.ModifierKeys.Control; continue;
case "shift": mods |= System.Windows.Input.ModifierKeys.Shift; continue;
case "alt": mods |= System.Windows.Input.ModifierKeys.Alt; continue;
case "win": mods |= System.Windows.Input.ModifierKeys.Windows; continue;
}
if (!Enum.TryParse(part.Trim(), ignoreCase: true, out key)) return null;
}
return key == System.Windows.Input.Key.None ? null : (mods, key);
}
/// <summary>조합 비교 정규화(대소문자·순서 무관).</summary>
public static string Normalize(string gesture)
=> string.Join("+", (gesture ?? "").Split('+').Select(p => p.Trim().ToLowerInvariant()).OrderBy(p => p));
}