diff --git a/src/MultiShop/Client/App.razor.cs b/src/MultiShop/Client/App.razor.cs index fbfd49c..4af60c7 100644 --- a/src/MultiShop/Client/App.razor.cs +++ b/src/MultiShop/Client/App.razor.cs @@ -1,11 +1,13 @@ using System.Collections.Generic; using System.Net.Http; using System.Net.Http.Json; +using System.Text.Json; using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.Extensions.Logging; +using Microsoft.JSInterop; using MultiShop.Client.Module; -using MultiShop.Client.Shared; using MultiShop.Shared.Models; using MultiShop.Shop.Framework; @@ -13,19 +15,26 @@ namespace MultiShop.Client { public partial class App { + [Inject] + private IJSRuntime JS { get; set; } + [Inject] + private IHttpClientFactory HttpClientFactory {get; set;} + private IJSObjectReference localStorageManager; private ICollection dependencies = new List(); protected override void OnInitialized() { base.OnInitialized(); - dependencies.Add(new RuntimeDependencyManager.Dependency(typeof(IReadOnlyDictionary), "Shops", DownloadShops)); + dependencies.Add(new RuntimeDependencyManager.Dependency(typeof(IJSObjectReference), "JS Modules", (pHttp, http, auth, logger) => new ValueTask(localStorageManager), "LocalStorageManager")); + dependencies.Add(new RuntimeDependencyManager.Dependency(typeof(IJSObjectReference), "JS Modules", async (pHttp, http, auth, logger) => await JS.InvokeAsync("import", "./js/Components/ComponentSupport.js"), "ComponentSupport")); + dependencies.Add(new RuntimeDependencyManager.Dependency(typeof(IReadOnlyDictionary), "Shops", async (publicHttp, authenticatedHttp, auth, logger) => await (new ShopModuleLoader(publicHttp, logger)).GetShops())); dependencies.Add(new RuntimeDependencyManager.Dependency(typeof(ApplicationProfile), "Application Profile", DownloadApplicationProfile)); } - private async ValueTask DownloadShops(HttpClient publicHttp, HttpClient http, AuthenticationState authState, ILogger logger) + protected override async Task OnInitializedAsync() { - ShopModuleLoader loader = new ShopModuleLoader(publicHttp, logger); - return await loader.GetShops(); + await base.OnInitializedAsync(); + localStorageManager = await JS.InvokeAsync("import", "./js/LocalStorageManager.js"); } private async ValueTask DownloadApplicationProfile(HttpClient publicHttp, HttpClient http, AuthenticationState authState, ILogger logger) @@ -39,7 +48,10 @@ namespace MultiShop.Client return await response.Content.ReadFromJsonAsync(); } } + ApplicationProfile profile = await localStorageManager.InvokeAsync("retrieve", "ApplicationProfile"); + if (profile != null) return profile; return new ApplicationProfile(); } + } } \ No newline at end of file diff --git a/src/MultiShop/Client/Pages/Configuration.razor.cs b/src/MultiShop/Client/Pages/Configuration.razor.cs index 1fc3c96..8bfb4ce 100644 --- a/src/MultiShop/Client/Pages/Configuration.razor.cs +++ b/src/MultiShop/Client/Pages/Configuration.razor.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.Extensions.Logging; +using Microsoft.JSInterop; using MultiShop.Client.Services; using MultiShop.Shared.Models; @@ -70,6 +71,7 @@ namespace MultiShop.Client.Pages Logger.LogDebug($"User is authenticated. Attempting to save configuration to server."); await Http.PutAsJsonAsync("Profile/Application", ApplicationProfile); } + await RuntimeDependencyManager.Get("LocalStorageManager").InvokeVoidAsync("save", "ApplicationProfile", ApplicationProfile); } } } \ No newline at end of file diff --git a/src/MultiShop/Client/Pages/Search.razor.cs b/src/MultiShop/Client/Pages/Search.razor.cs index 6eb35f9..0a135ca 100644 --- a/src/MultiShop/Client/Pages/Search.razor.cs +++ b/src/MultiShop/Client/Pages/Search.razor.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.Extensions.Logging; +using Microsoft.JSInterop; using MultiShop.Client.Extensions; using MultiShop.Client.Listing; using MultiShop.Client.Services; @@ -17,9 +18,6 @@ namespace MultiShop.Client.Pages { public partial class Search : IAsyncDisposable { - [Inject] - private LayoutStateChangeNotifier LayoutStateChangeNotifier { get; set; } - [Inject] private ILogger Logger { get; set; } @@ -53,7 +51,6 @@ namespace MultiShop.Client.Pages protected override void OnInitialized() { base.OnInitialized(); - LayoutStateChangeNotifier.Notify += UpdateState; Shops = RuntimeDependencyManager.Get>(); } @@ -71,36 +68,24 @@ namespace MultiShop.Client.Pages { activeSearchProfile = await searchProfileResponse.Content.ReadFromJsonAsync(); } - else - { - Logger.LogWarning("Could not load search profile from server. Using default."); - activeSearchProfile = new SearchProfile(); - } HttpResponseMessage resultsProfileResponse = await Http.GetAsync("Profile/Results"); if (resultsProfileResponse.IsSuccessStatusCode) { activeResultsProfile = await resultsProfileResponse.Content.ReadFromJsonAsync(); } - else - { - Logger.LogWarning("Could not load results profile from server. Using default."); - activeResultsProfile = new ResultsProfile(); - } } - else - { - activeSearchProfile = new SearchProfile(); - activeResultsProfile = new ResultsProfile(); - } - activeSearchProfile.ShopStates.TotalShops = Shops.Count; - } + IJSObjectReference localStorageManager = RuntimeDependencyManager.Get("LocalStorageManager"); + if (activeSearchProfile == null) activeSearchProfile = await localStorageManager.InvokeAsync("retrieve", "SearchProfile"); + if (activeResultsProfile == null) activeResultsProfile = await localStorageManager.InvokeAsync("retrieve", "ResultsProfile"); - protected override async Task OnParametersSetAsync() - { - await base.OnParametersSetAsync(); - if (!string.IsNullOrEmpty(Query)) + if (activeSearchProfile == null) activeSearchProfile = new SearchProfile(); + if (activeResultsProfile == null) activeResultsProfile = new ResultsProfile(); + activeSearchProfile.ShopStates.TotalShops = Shops.Count; + + if (Query != null) { + searchBar.Searching = true; await PerformSearch(Query); } } @@ -111,9 +96,6 @@ namespace MultiShop.Client.Pages if (firstRender) { 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 (status.Searching) return; + searchBar.Searching = true; SearchProfile searchProfile = activeSearchProfile.DeepCopy(); status.Searching = true; Logger.LogDebug($"Received search request for \"{query}\"."); @@ -187,6 +170,7 @@ namespace MultiShop.Client.Pages } status.Searching = false; status.Searched = true; + searchBar.Searching = false; int tagsAdded = 0; foreach (ResultsProfile.Category c in greatest.Keys) @@ -272,12 +256,6 @@ namespace MultiShop.Client.Pages StateHasChanged(); } - private async Task UpdateState() { - await InvokeAsync(() => { - StateHasChanged(); - }); - } - public async ValueTask DisposeAsync() { AuthenticationState authState = await AuthenticationStateTask; @@ -286,7 +264,9 @@ namespace MultiShop.Client.Pages await Http.PutAsJsonAsync("Profile/Search", activeSearchProfile); await Http.PutAsJsonAsync("Profile/Results", activeResultsProfile); } - LayoutStateChangeNotifier.Notify -= UpdateState; + IJSObjectReference localStorageManager = RuntimeDependencyManager.Get("LocalStorageManager"); + await localStorageManager.InvokeVoidAsync("save", "SearchProfile", activeSearchProfile); + await localStorageManager.InvokeVoidAsync("save", "ResultsProfile", activeResultsProfile); } public class Status diff --git a/src/MultiShop/Client/wwwroot/js/LocalStorageManager.js b/src/MultiShop/Client/wwwroot/js/LocalStorageManager.js new file mode 100644 index 0000000..d62ab51 --- /dev/null +++ b/src/MultiShop/Client/wwwroot/js/LocalStorageManager.js @@ -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); + } + } +} \ No newline at end of file