SearchView.razor 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  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. <div class="table-responsive">
  95. <table class="table w-100">
  96. <tbody>
  97. <tr class="table-primary">
  98. <td><img src="@disc.CoverPath" style="height:50px" /></td>
  99. <td colspan="2" class="w-100">@item.Key.Name</td>
  100. <td colspan="2" class="text-end">
  101. <button class="btn btn-primary" @onclick="()=>dlgDisc.Show(disc)">
  102. <i class="bi bi-window-stack"></i>
  103. </button>
  104. </td>
  105. </tr>
  106. @foreach (var tr in item.OrderBy(p => p.Name))
  107. {
  108. <tr class="mouse-hilight">
  109. <td><FileIcon FileName="@tr.Key"></FileIcon></td>
  110. <td class="w-100 text-nowrap">
  111. <div>
  112. <a href="@tr.Path" target="@FnzConst.PlayPageTarget">
  113. <HighLightKeyword Text="@tr.GetTitleOrFilename()" Keywords="@(new []{ searchExpression})"></HighLightKeyword>
  114. </a>
  115. </div>
  116. <div class="text-nowrap text-muted">@tr.Tag?.Artist</div>
  117. </td>
  118. <td>
  119. <div class="text-nowrap">@tr.Tag.Duration.SecondToDur()</div>
  120. <div class="text-nowrap text-muted">@tr.Tag.Length.BytesToFileSize()</div>
  121. </td>
  122. <td class="text-center">
  123. <button type="button" class="btn btn-link" style="padding:0.1rem" @onclick="()=>dlgPlaylistAdd.Show(tr)">
  124. <i class="bi bi-folder-plus"></i>
  125. </button>
  126. </td>
  127. </tr>
  128. }
  129. </tbody>
  130. </table>
  131. </div>
  132. }
  133. }
  134. </fieldset>
  135. </div>
  136. </div>
  137. @code {
  138. private async Task SearchGo()
  139. {
  140. isSearching = true;
  141. StateHasChanged();
  142. await Task.Delay(1);
  143. var filteredList = new List<FeTrack>(searchLimitCount);
  144. var matchedCount = 0;
  145. var flagPlus = false;
  146. var filters = new List<Func<FeTrack, bool>> { p => p.TrackSetKey == searchTrackSetKey };
  147. //TODO: Expression implement
  148. if (string.IsNullOrEmpty(searchExpression) == false)
  149. {
  150. filters.Add(p => p.GetTitleOrFilename().Contains(searchExpression, StringComparison.OrdinalIgnoreCase));
  151. }
  152. for (var i = 0; i < DataSet.AllTracks.Length && isSearching; i++)
  153. {
  154. var item = DataSet.AllTracks[i];
  155. if (filters.All(p => p(item)))
  156. {
  157. ++matchedCount;
  158. if (filteredList.Count < searchLimitCount)
  159. {
  160. filteredList.Add(item);
  161. }
  162. else if (matchedCount >= searchLimitCount * 2)
  163. {
  164. flagPlus = true;
  165. break;
  166. }
  167. }
  168. await searchProgressBar.SetProgress((float)i / DataSet.AllTracks.Length, $"Searching {i + 1}/{DataSet.AllTracks.Length} matched:{matchedCount}");
  169. }
  170. //TODO: search in disc name
  171. resultText = $"Result for {searchExpression.NullOrEmptyEscape("<NONE>")} on {searchTrackSetKey} limit {searchLimitCount} of {matchedCount}{(flagPlus ? "+" : "")}";
  172. resultEntries = filteredList
  173. .GroupBy(p => p.Disc)
  174. .ToArray();
  175. isSearching = false;
  176. StateHasChanged();
  177. }
  178. protected override async Task OnAfterRenderAsync(bool firstRender)
  179. {
  180. await base.OnAfterRenderAsync(firstRender);
  181. if (firstRender)
  182. {
  183. AllTrackSet = DataSet.AllTracksSet.GroupBy(p => p.Key).ToDictionary(p => p.Key, p => p.First().Name);
  184. if (resultEntries == null) await SearchGo();
  185. else StateHasChanged();
  186. }
  187. }
  188. private void SearchChanged(ChangeEventArgs e)
  189. {
  190. searchExpression = e.Value.ToString().Trim();
  191. }
  192. public string TrackSetKeyProperty
  193. {
  194. get => searchTrackSetKey;
  195. set
  196. {
  197. if (searchTrackSetKey == value) return;
  198. searchTrackSetKey = value;
  199. SearchGo();
  200. }
  201. }
  202. public int ResultCountProperty
  203. {
  204. get => searchLimitCount;
  205. set
  206. {
  207. if (searchLimitCount == value) return;
  208. searchLimitCount = value;
  209. SearchGo();
  210. }
  211. }
  212. }