SearchView.razor 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. @inherits ViewBase
  2. @code {
  3. private Dictionary<string, string> AllTrackSet;
  4. private string searchName { get => LocalStorage.GetByCallerMemberName<string>(); set => LocalStorage.SetByCallerMemberName(value); }
  5. private string searchArtist { get => LocalStorage.GetByCallerMemberName<string>(); set => LocalStorage.SetByCallerMemberName(value); }
  6. private string searchFind { get => LocalStorage.GetByCallerMemberName<string>() ?? "Track"; set => LocalStorage.SetByCallerMemberName(value); }
  7. private string searchTrackSetKey { get => LocalStorage.GetByCallerMemberName<string>() ?? "main"; set => LocalStorage.SetByCallerMemberName(value); }
  8. private int searchLimitCount { get => LocalStorage.GetByCallerMemberName<int?>() ?? 20; set => LocalStorage.SetByCallerMemberName(value); }
  9. private bool isSearching = false;
  10. private ProgressBar searchProgressBar;
  11. private static string resultText;
  12. private static IGrouping<FeDisc, FeTrack>[] resultTracks;
  13. private static FeDisc[] resultDiscs;
  14. private DiscDialog dlgDisc;
  15. }
  16. <div class="row">
  17. <div class="col-12">
  18. <fieldset class="border rounded-3 p-2">
  19. <legend class="float-none w-auto px-2 d-flex flex-row align-items-center">
  20. <span>Filter</span>
  21. </legend>
  22. <div class="input-group">
  23. <div class="form-floating">
  24. <InputText type="search" class="form-control" placeholder="Name"
  25. Value="@searchName"
  26. ValueExpression="@(()=>searchName)"
  27. @oninput="x=> searchName = x?.Value?.ToString()?.Trim() ?? string.Empty"
  28. onsearch="@SearchGo">
  29. </InputText>
  30. <label>Name</label>
  31. </div>
  32. </div>
  33. <div class="input-group mt-1">
  34. <div class="form-floating">
  35. <InputText type="search" class="form-control" placeholder="Artist"
  36. Value="@searchArtist"
  37. ValueExpression="@(()=>searchArtist)"
  38. @oninput="x=> searchArtist = x?.Value?.ToString()?.Trim() ?? string.Empty"
  39. onsearch="@SearchGo">
  40. </InputText>
  41. <label>Artist</label>
  42. </div>
  43. </div>
  44. <div class="row mt-3">
  45. <label class="col-sm-2 col-form-label">Find</label>
  46. <div class="col-sm-10">
  47. <FnzRadioGroup TValue="string"
  48. DataSource="@(new[] { "Track", "Disc" })"
  49. Value="searchFind"
  50. ValueChanged="async (v) => {searchFind = v; StateHasChanged(); await SearchGo(); }">
  51. </FnzRadioGroup>
  52. </div>
  53. </div>
  54. @if (searchFind == "Track")
  55. {
  56. <div class="row mt-3">
  57. <label class="col-sm-2 col-form-label">Track set</label>
  58. <div class="col-sm-10">
  59. <FnzRadioGroup TValue="string"
  60. DataSource="@AllTrackSet?.Keys.KeepNoEmpty()"
  61. Value="searchTrackSetKey"
  62. TextFormat="x=>x.ToUpper()"
  63. ValueChanged="async (v) => {searchTrackSetKey = v; StateHasChanged(); await SearchGo(); }">
  64. </FnzRadioGroup>
  65. </div>
  66. </div>
  67. }
  68. <div class="row mt-3">
  69. <label class="col-sm-2 col-form-label">Result count</label>
  70. <div class="col-sm-10">
  71. <FnzRadioGroup TValue="int"
  72. DataSource="@(new [] { 20, 50, 100, 200 })"
  73. Value="searchLimitCount"
  74. ValueChanged="async (v) => {searchLimitCount = v; StateHasChanged(); await SearchGo(); }">
  75. </FnzRadioGroup>
  76. </div>
  77. </div>
  78. <div class="row mt-3">
  79. <div class="col-12 text-end">
  80. <button @onclick="@SearchGo" class="btn btn-primary" type="button">Search</button>
  81. </div>
  82. </div>
  83. </fieldset>
  84. </div>
  85. </div>
  86. @if (isSearching)
  87. {
  88. <div class="row mt-4">
  89. <div class="col-12">
  90. <div class="btn-group w-100">
  91. <ProgressBar @ref="searchProgressBar" Throttle="333" class="btn border w-75"></ProgressBar>
  92. <button class="btn border btn-danger" @onclick="()=>isSearching=false">Stop search</button>
  93. </div>
  94. </div>
  95. </div>
  96. }
  97. <div class="row mt-3">
  98. <div class="col-12">
  99. <fieldset class="border rounded-3 p-2">
  100. <legend class="float-none w-auto px-2 d-flex flex-row align-items-center">
  101. <span>Result</span>
  102. </legend>
  103. <div class="mb-3 text-center">@resultText</div>
  104. @if (searchFind == "Track" && resultTracks?.Length > 0)
  105. {
  106. foreach (var item in resultTracks)
  107. {
  108. <SearchResultGroup DataSource="item" SearchName="@searchName" SearchArtist="@searchArtist"></SearchResultGroup>
  109. }
  110. }
  111. @if (searchFind == "Disc" && resultDiscs?.Length > 0)
  112. {
  113. <div class="row">
  114. @foreach (var disc in resultDiscs)
  115. {
  116. <DiscCard Disc="disc"></DiscCard>
  117. }
  118. </div>
  119. }
  120. </fieldset>
  121. </div>
  122. </div>
  123. @code {
  124. private async Task SearchGo()
  125. {
  126. if (isSearching) return;
  127. isSearching = true;
  128. StateHasChanged();
  129. await Task.Delay(1);
  130. var matchedCount = 0;
  131. var flagPlus = false;
  132. //var filterExpression
  133. if (searchFind == "Track")
  134. {
  135. var filteredList = new List<FeTrack>(searchLimitCount);
  136. var filters = new List<Func<FeTrack, bool>> { p => p.TrackSetKey == searchTrackSetKey };
  137. if (string.IsNullOrEmpty(searchName) == false)
  138. {
  139. filters.Add(p => p.GetTitleOrFilename().Contains(searchName, StringComparison.OrdinalIgnoreCase));
  140. }
  141. if (string.IsNullOrEmpty(searchArtist) == false)
  142. {
  143. filters.Add(p => p.Tag?.Artist.Contains(searchArtist, StringComparison.OrdinalIgnoreCase) == true);
  144. }
  145. for (var i = 0; i < DataSet.AllTracks.Length && isSearching; i++)
  146. {
  147. var item = DataSet.AllTracks[i];
  148. if (filters.All(p => p(item)))
  149. {
  150. ++matchedCount;
  151. if (filteredList.Count < searchLimitCount)
  152. {
  153. filteredList.Add(item);
  154. }
  155. else if (matchedCount >= searchLimitCount * 2)
  156. {
  157. flagPlus = true;
  158. break;
  159. }
  160. }
  161. await searchProgressBar.SetProgress((float)i / DataSet.AllTracks.Length, $"Searching {i + 1}/{DataSet.AllTracks.Length} matched:{matchedCount}");
  162. }
  163. resultText = $"Result for {searchName.NullOrEmptyEscape("<NONE>")} on {DataSet.AllTracksSet.Where(p => p.Key == searchTrackSetKey).Select(p => p.Name).FirstOrDefault() ?? "?"} limit {searchLimitCount} of {matchedCount}{(flagPlus ? "+" : "")}";
  164. resultTracks = filteredList
  165. .GroupBy(p => p.Disc)
  166. .ToArray();
  167. }
  168. else if (searchFind == "Disc")
  169. {
  170. var filteredList = new List<FeDisc>(searchLimitCount);
  171. var filters = new List<Func<FeDisc, bool>> { };
  172. if (string.IsNullOrEmpty(searchName) == false)
  173. {
  174. filters.Add(p => p.Name.Contains(searchName, StringComparison.OrdinalIgnoreCase));
  175. }
  176. if (string.IsNullOrEmpty(searchArtist) == false)
  177. {
  178. filters.Add(p => p.TrackSets.Any(s => s.Tracks.Any(t => t.Tag?.Artist.Contains(searchArtist, StringComparison.OrdinalIgnoreCase) == true)));
  179. }
  180. for (var i = 0; i < DataSet.AllDisc.Length && isSearching; i++)
  181. {
  182. var item = DataSet.AllDisc[i];
  183. if (filters.All(p => p(item)))
  184. {
  185. ++matchedCount;
  186. if (filteredList.Count < searchLimitCount)
  187. {
  188. filteredList.Add(item);
  189. }
  190. else if (matchedCount >= searchLimitCount * 2)
  191. {
  192. flagPlus = true;
  193. break;
  194. }
  195. }
  196. await searchProgressBar.SetProgress((float)i / DataSet.AllDisc.Length, $"Searching {i + 1}/{DataSet.AllDisc.Length} matched:{matchedCount}");
  197. }
  198. resultText = $"Result for Discs limit {searchLimitCount} of {matchedCount}{(flagPlus ? "+" : "")}";
  199. resultDiscs = filteredList.ToArray();
  200. }
  201. isSearching = false;
  202. StateHasChanged();
  203. }
  204. protected override async Task OnAfterRenderAsync(bool firstRender)
  205. {
  206. await base.OnAfterRenderAsync(firstRender);
  207. if (firstRender)
  208. {
  209. AllTrackSet = DataSet.AllTracksSet.GroupBy(p => p.Key).ToDictionary(p => p.Key, p => p.First().Name);
  210. if (searchFind == "Track" && resultTracks == null) await SearchGo();
  211. if (searchFind == "Disc" && resultDiscs == null) await SearchGo();
  212. else StateHasChanged();
  213. }
  214. }
  215. }