SearchView.razor 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. @inherits ViewBase
  2. @code {
  3. private Dictionary<string, string> AllTrackSet;
  4. private string searchExpression { get => LocalStorage.Get<string>(); set => LocalStorage.Set(value); }
  5. private string searchTrackSetKey { get => LocalStorage.Get<string>() ?? "main"; set => LocalStorage.Set(value); }
  6. private int searchLimitCount { get => LocalStorage.Get<int?>() ?? 20; set => LocalStorage.Set(value); }
  7. private bool isSearching = false;
  8. private ProgressBar searchProgressBar;
  9. private static string resultText;
  10. private static IGrouping<FeDisc, FeTrack>[] resultEntries;
  11. private DiscDialog dlgDisc;
  12. private PlaylistAddDialog dlgPlaylistAdd;
  13. }
  14. <DiscDialog @ref="dlgDisc" Disc="CurrentDisc"></DiscDialog>
  15. <PlaylistAddDialog @ref="dlgPlaylistAdd"></PlaylistAddDialog>
  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="Search expression"
  25. Value="@searchExpression"
  26. ValueExpression="@(()=>searchExpression)"
  27. @oninput="SearchChanged"
  28. onsearch="@SearchGo">
  29. </InputText>
  30. <label>Search expression</label>
  31. </div>
  32. <button @onclick="@SearchGo" class="btn btn-outline-primary" type="button">Search</button>
  33. </div>
  34. <div class="row mt-3">
  35. <label for="staticEmail" class="col-sm-2 col-form-label">Track set</label>
  36. <div class="col-sm-10">
  37. <InputRadioGroup @bind-Value="TrackSetKeyProperty">
  38. <div class="btn-group" role="group" aria-label="Basic radio toggle button group">
  39. @foreach (var item in AllTrackSet.KeepNoEmpty().WithIndex())
  40. {
  41. <InputRadio class="btn-check" id="@("stk-"+item.index)" Value="item.item.Key"></InputRadio>
  42. <label class="btn btn-outline-primary" for="@("stk-"+item.index)">@(item.item.Value)</label>
  43. }
  44. </div>
  45. </InputRadioGroup>
  46. </div>
  47. </div>
  48. <div class="row mt-3">
  49. <label for="staticEmail" class="col-sm-2 col-form-label">Result count</label>
  50. <div class="col-sm-10">
  51. <InputRadioGroup @bind-Value="ResultCountProperty">
  52. <div class="btn-group" role="group" aria-label="Basic radio toggle button group">
  53. @foreach (var lc in new[] { 20, 50, 100, 200 })
  54. {
  55. <InputRadio class="btn-check" id="@($"limit-count-{lc}")" Value="@lc"></InputRadio>
  56. <label class="btn btn-outline-primary" for="@($"limit-count-{lc}")">@lc</label>
  57. }
  58. </div>
  59. </InputRadioGroup>
  60. </div>
  61. </div>
  62. @*<div class="row mt-3">
  63. <label for="staticEmail" class="col-sm-2 col-form-label">Debug</label>
  64. <div class="col-sm-10">
  65. <button class="btn btn-primary" @onclick="()=>isSearching=true">Show progressbar</button>
  66. </div>
  67. </div>*@
  68. </fieldset>
  69. </div>
  70. </div>
  71. @if (isSearching)
  72. {
  73. <div class="row mt-4">
  74. <div class="col-12">
  75. <div class="btn-group w-100">
  76. <ProgressBar @ref="searchProgressBar" Throttle="333" class="btn border w-75"></ProgressBar>
  77. <button class="btn border btn-danger" @onclick="()=>isSearching=false">Stop search</button>
  78. </div>
  79. </div>
  80. </div>
  81. }
  82. <div class="row mt-3">
  83. <div class="col-12">
  84. <fieldset class="border rounded-3 p-2">
  85. <legend class="float-none w-auto px-2 d-flex flex-row align-items-center">
  86. <span>Result</span>
  87. </legend>
  88. <div class="mb-3 text-center">@resultText</div>
  89. @if (resultEntries?.Length > 0)
  90. {
  91. foreach (var item in resultEntries)
  92. {
  93. var disc = item.Key;
  94. <table class="table w-100">
  95. <tbody>
  96. <tr>
  97. <td><img src="@disc.CoverPath" style="height:50px" /></td>
  98. <td colspan="4" class="w-100">@item.Key.Name</td>
  99. <td colspan="2" class="text-end">
  100. <button class="btn btn-primary" @onclick="()=>dlgDisc.Show(disc)">
  101. <i class="bi bi-window-stack"></i>
  102. </button>
  103. </td>
  104. </tr>
  105. @foreach (var tr in item.OrderBy(p => p.Name))
  106. {
  107. <tr>
  108. <td></td>
  109. <td><FileIcon FileName="@tr.Key"></FileIcon></td>
  110. <td class="w-100">
  111. <a href="@tr.Path" target="@FnzConst.PlayPageTarget">
  112. <HighLightKeyword Text="@tr.GetTitleOrFilename()" Keywords="@(new []{ searchExpression})"></HighLightKeyword>
  113. </a>
  114. </td>
  115. <td>@tr.Tag.Duration.SecondToDur()</td>
  116. <td>@tr.Tag.Length.BytesToFileSize()</td>
  117. <td class="text-center">
  118. <button type="button" class="btn btn-link" style="padding:0.1rem" @onclick="()=>dlgPlaylistAdd.Show(tr)">
  119. <i class="bi bi-folder-plus"></i>
  120. </button>
  121. </td>
  122. </tr>
  123. }
  124. </tbody>
  125. </table>
  126. }
  127. }
  128. </fieldset>
  129. </div>
  130. </div>
  131. @code {
  132. private async Task SearchGo()
  133. {
  134. isSearching = true;
  135. StateHasChanged();
  136. await Task.Delay(1);
  137. var filteredList = new List<FeTrack>(searchLimitCount);
  138. var matchedCount = 0;
  139. var flagPlus = false;
  140. var filters = new List<Func<FeTrack, bool>> { p => p.TrackSetKey == searchTrackSetKey };
  141. //TODO: Expression implement
  142. if (string.IsNullOrEmpty(searchExpression) == false) {
  143. filters.Add(p => p.GetTitleOrFilename().Contains(searchExpression,StringComparison.OrdinalIgnoreCase));
  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. //TODO: search in disc name
  164. resultText = $"Result for {searchExpression.NullOrEmptyEscape("<NONE>")} on {searchTrackSetKey} limit {searchLimitCount} of {matchedCount}{(flagPlus ? "+" : "")}";
  165. resultEntries = filteredList
  166. .GroupBy(p => p.Disc)
  167. .ToArray();
  168. isSearching = false;
  169. StateHasChanged();
  170. }
  171. protected override async Task OnAfterRenderAsync(bool firstRender)
  172. {
  173. await base.OnAfterRenderAsync(firstRender);
  174. if (firstRender)
  175. {
  176. AllTrackSet = DataSet.AllTracksSet.GroupBy(p => p.Key).ToDictionary(p => p.Key, p => p.First().Name);
  177. if (resultEntries == null) await SearchGo();
  178. else StateHasChanged();
  179. }
  180. }
  181. private void SearchChanged(ChangeEventArgs e)
  182. {
  183. searchExpression = e.Value.ToString().Trim();
  184. }
  185. public string TrackSetKeyProperty
  186. {
  187. get => searchTrackSetKey;
  188. set
  189. {
  190. if (searchTrackSetKey == value) return;
  191. searchTrackSetKey = value;
  192. SearchGo();
  193. }
  194. }
  195. public int ResultCountProperty
  196. {
  197. get => searchLimitCount;
  198. set
  199. {
  200. if (searchLimitCount == value) return;
  201. searchLimitCount = value;
  202. SearchGo();
  203. }
  204. }
  205. }