Implemented local setting persistence and fixed main page search bug.

This commit is contained in:
Harrison Deng 2021-06-04 19:12:55 -05:00
parent 04f6657ed3
commit 11fa15fe62
4 changed files with 72 additions and 40 deletions

View File

@ -1,11 +1,13 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Json; using System.Net.Http.Json;
using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.JSInterop;
using MultiShop.Client.Module; using MultiShop.Client.Module;
using MultiShop.Client.Shared;
using MultiShop.Shared.Models; using MultiShop.Shared.Models;
using MultiShop.Shop.Framework; using MultiShop.Shop.Framework;
@ -13,19 +15,26 @@ namespace MultiShop.Client
{ {
public partial class App public partial class App
{ {
[Inject]
private IJSRuntime JS { get; set; }
[Inject]
private IHttpClientFactory HttpClientFactory {get; set;}
private IJSObjectReference localStorageManager;
private ICollection<RuntimeDependencyManager.Dependency> dependencies = new List<RuntimeDependencyManager.Dependency>(); private ICollection<RuntimeDependencyManager.Dependency> dependencies = new List<RuntimeDependencyManager.Dependency>();
protected override void OnInitialized() protected override void OnInitialized()
{ {
base.OnInitialized(); base.OnInitialized();
dependencies.Add(new RuntimeDependencyManager.Dependency(typeof(IReadOnlyDictionary<string, IShop>), "Shops", DownloadShops)); dependencies.Add(new RuntimeDependencyManager.Dependency(typeof(IJSObjectReference), "JS Modules", (pHttp, http, auth, logger) => new ValueTask<object>(localStorageManager), "LocalStorageManager"));
dependencies.Add(new RuntimeDependencyManager.Dependency(typeof(IJSObjectReference), "JS Modules", async (pHttp, http, auth, logger) => await JS.InvokeAsync<IJSObjectReference>("import", "./js/Components/ComponentSupport.js"), "ComponentSupport"));
dependencies.Add(new RuntimeDependencyManager.Dependency(typeof(IReadOnlyDictionary<string, IShop>), "Shops", async (publicHttp, authenticatedHttp, auth, logger) => await (new ShopModuleLoader(publicHttp, logger)).GetShops()));
dependencies.Add(new RuntimeDependencyManager.Dependency(typeof(ApplicationProfile), "Application Profile", DownloadApplicationProfile)); dependencies.Add(new RuntimeDependencyManager.Dependency(typeof(ApplicationProfile), "Application Profile", DownloadApplicationProfile));
} }
private async ValueTask<object> DownloadShops(HttpClient publicHttp, HttpClient http, AuthenticationState authState, ILogger logger) protected override async Task OnInitializedAsync()
{ {
ShopModuleLoader loader = new ShopModuleLoader(publicHttp, logger); await base.OnInitializedAsync();
return await loader.GetShops(); localStorageManager = await JS.InvokeAsync<IJSObjectReference>("import", "./js/LocalStorageManager.js");
} }
private async ValueTask<object> DownloadApplicationProfile(HttpClient publicHttp, HttpClient http, AuthenticationState authState, ILogger logger) private async ValueTask<object> DownloadApplicationProfile(HttpClient publicHttp, HttpClient http, AuthenticationState authState, ILogger logger)
@ -39,7 +48,10 @@ namespace MultiShop.Client
return await response.Content.ReadFromJsonAsync<ApplicationProfile>(); return await response.Content.ReadFromJsonAsync<ApplicationProfile>();
} }
} }
ApplicationProfile profile = await localStorageManager.InvokeAsync<ApplicationProfile>("retrieve", "ApplicationProfile");
if (profile != null) return profile;
return new ApplicationProfile(); return new ApplicationProfile();
} }
} }
} }

View File

@ -6,6 +6,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.JSInterop;
using MultiShop.Client.Services; using MultiShop.Client.Services;
using MultiShop.Shared.Models; using MultiShop.Shared.Models;
@ -70,6 +71,7 @@ namespace MultiShop.Client.Pages
Logger.LogDebug($"User is authenticated. Attempting to save configuration to server."); Logger.LogDebug($"User is authenticated. Attempting to save configuration to server.");
await Http.PutAsJsonAsync("Profile/Application", ApplicationProfile); await Http.PutAsJsonAsync("Profile/Application", ApplicationProfile);
} }
await RuntimeDependencyManager.Get<IJSObjectReference>("LocalStorageManager").InvokeVoidAsync("save", "ApplicationProfile", ApplicationProfile);
} }
} }
} }

View File

@ -6,6 +6,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.JSInterop;
using MultiShop.Client.Extensions; using MultiShop.Client.Extensions;
using MultiShop.Client.Listing; using MultiShop.Client.Listing;
using MultiShop.Client.Services; using MultiShop.Client.Services;
@ -17,9 +18,6 @@ namespace MultiShop.Client.Pages
{ {
public partial class Search : IAsyncDisposable public partial class Search : IAsyncDisposable
{ {
[Inject]
private LayoutStateChangeNotifier LayoutStateChangeNotifier { get; set; }
[Inject] [Inject]
private ILogger<Search> Logger { get; set; } private ILogger<Search> Logger { get; set; }
@ -53,7 +51,6 @@ namespace MultiShop.Client.Pages
protected override void OnInitialized() protected override void OnInitialized()
{ {
base.OnInitialized(); base.OnInitialized();
LayoutStateChangeNotifier.Notify += UpdateState;
Shops = RuntimeDependencyManager.Get<IReadOnlyDictionary<string, IShop>>(); Shops = RuntimeDependencyManager.Get<IReadOnlyDictionary<string, IShop>>();
} }
@ -71,36 +68,24 @@ namespace MultiShop.Client.Pages
{ {
activeSearchProfile = await searchProfileResponse.Content.ReadFromJsonAsync<SearchProfile>(); activeSearchProfile = await searchProfileResponse.Content.ReadFromJsonAsync<SearchProfile>();
} }
else
{
Logger.LogWarning("Could not load search profile from server. Using default.");
activeSearchProfile = new SearchProfile();
}
HttpResponseMessage resultsProfileResponse = await Http.GetAsync("Profile/Results"); HttpResponseMessage resultsProfileResponse = await Http.GetAsync("Profile/Results");
if (resultsProfileResponse.IsSuccessStatusCode) if (resultsProfileResponse.IsSuccessStatusCode)
{ {
activeResultsProfile = await resultsProfileResponse.Content.ReadFromJsonAsync<ResultsProfile>(); activeResultsProfile = await resultsProfileResponse.Content.ReadFromJsonAsync<ResultsProfile>();
} }
else
{
Logger.LogWarning("Could not load results profile from server. Using default.");
activeResultsProfile = new ResultsProfile();
}
} }
else IJSObjectReference localStorageManager = RuntimeDependencyManager.Get<IJSObjectReference>("LocalStorageManager");
{ if (activeSearchProfile == null) activeSearchProfile = await localStorageManager.InvokeAsync<SearchProfile>("retrieve", "SearchProfile");
activeSearchProfile = new SearchProfile(); if (activeResultsProfile == null) activeResultsProfile = await localStorageManager.InvokeAsync<ResultsProfile>("retrieve", "ResultsProfile");
activeResultsProfile = new ResultsProfile();
}
activeSearchProfile.ShopStates.TotalShops = Shops.Count;
}
protected override async Task OnParametersSetAsync() if (activeSearchProfile == null) activeSearchProfile = new SearchProfile();
{ if (activeResultsProfile == null) activeResultsProfile = new ResultsProfile();
await base.OnParametersSetAsync(); activeSearchProfile.ShopStates.TotalShops = Shops.Count;
if (!string.IsNullOrEmpty(Query))
if (Query != null)
{ {
searchBar.Searching = true;
await PerformSearch(Query); await PerformSearch(Query);
} }
} }
@ -111,9 +96,6 @@ namespace MultiShop.Client.Pages
if (firstRender) if (firstRender)
{ {
searchBar.Query = Query; searchBar.Query = Query;
searchBar.Searching = true;
await PerformSearch(Query);
searchBar.Searching = false;
} }
} }
@ -121,6 +103,7 @@ namespace MultiShop.Client.Pages
{ {
if (string.IsNullOrWhiteSpace(query)) return; if (string.IsNullOrWhiteSpace(query)) return;
if (status.Searching) return; if (status.Searching) return;
searchBar.Searching = true;
SearchProfile searchProfile = activeSearchProfile.DeepCopy(); SearchProfile searchProfile = activeSearchProfile.DeepCopy();
status.Searching = true; status.Searching = true;
Logger.LogDebug($"Received search request for \"{query}\"."); Logger.LogDebug($"Received search request for \"{query}\".");
@ -187,6 +170,7 @@ namespace MultiShop.Client.Pages
} }
status.Searching = false; status.Searching = false;
status.Searched = true; status.Searched = true;
searchBar.Searching = false;
int tagsAdded = 0; int tagsAdded = 0;
foreach (ResultsProfile.Category c in greatest.Keys) foreach (ResultsProfile.Category c in greatest.Keys)
@ -272,12 +256,6 @@ namespace MultiShop.Client.Pages
StateHasChanged(); StateHasChanged();
} }
private async Task UpdateState() {
await InvokeAsync(() => {
StateHasChanged();
});
}
public async ValueTask DisposeAsync() public async ValueTask DisposeAsync()
{ {
AuthenticationState authState = await AuthenticationStateTask; AuthenticationState authState = await AuthenticationStateTask;
@ -286,7 +264,9 @@ namespace MultiShop.Client.Pages
await Http.PutAsJsonAsync("Profile/Search", activeSearchProfile); await Http.PutAsJsonAsync("Profile/Search", activeSearchProfile);
await Http.PutAsJsonAsync("Profile/Results", activeResultsProfile); await Http.PutAsJsonAsync("Profile/Results", activeResultsProfile);
} }
LayoutStateChangeNotifier.Notify -= UpdateState; IJSObjectReference localStorageManager = RuntimeDependencyManager.Get<IJSObjectReference>("LocalStorageManager");
await localStorageManager.InvokeVoidAsync("save", "SearchProfile", activeSearchProfile);
await localStorageManager.InvokeVoidAsync("save", "ResultsProfile", activeResultsProfile);
} }
public class Status public class Status

View File

@ -0,0 +1,38 @@
const APP_STORAGE_KEY = "MultiShop"
export function save(key, value) {
try {
localStorage.setItem(APP_STORAGE_KEY + "/" + key, JSON.stringify(value));
} catch (error) {
console.warn("Failed to save value with key: " + key);
return false;
}
return true;
}
export function retrieve(key) {
let value = localStorage.getItem(APP_STORAGE_KEY + "/" + key);
if (value == null) return null;
try {
return JSON.parse(value);
} catch (error) {
if (error instanceof SyntaxError) {
console.warn("Unable to parse value for key \"" + key + "\". Removing.");
remove(key);
return null;
}
}
}
export function remove(key) {
localStorage.removeItem(APP_STORAGE_KEY + "/" + key);
}
export function clear() {
for (let i = 0; i < localStorage.length; i++) {
let name = localStorage.key(i);
if (name.startsWith(APP_STORAGE_KEY + "/")) {
localStorage.removeItem(name);
}
}
}