This commit is contained in:
Ed Charbeneau 2020-08-14 13:35:54 -04:00
Родитель 3a11429dc4 89a1cde2c8
Коммит 7e9e3a080e
12 изменённых файлов: 232 добавлений и 40 удалений

Просмотреть файл

@ -15,6 +15,7 @@
<!-- /PWA -->
<ItemGroup>
<PackageReference Include="Blazored.LocalStorage" Version="3.0.0" />
<PackageReference Include="BuildWebCompiler" Version="1.12.405" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="3.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Build" Version="3.2.0" PrivateAssets="all" />

Просмотреть файл

@ -1,15 +1,16 @@
@page "/sales"
@using System.Globalization
@attribute [Authorize]
@inject HttpClient Http
@inject ITelerikStringLocalizer L
@inject ILocalStorageService LocalStorage
<SalesByDateChart Data="chartData"></SalesByDateChart>
<CardContainer Title="@L["Sales"]">
<TelerikGrid @ref="Grid" Height="500px" FilterMode="@GridFilterMode.FilterMenu"
Sortable="true" Pageable="true" PageSize="10"
OnStateInit="@((GridStateEventArgs<Sale> args) => OnStateInit(args))"
OnStateChanged="@((GridStateEventArgs<Sale> args) => OnStateChanged(args))"
Data=@Model.CurrentPageData TotalCount=@Model.TotalItemCount OnRead=@ReadItems>
<GridToolBar>
<label>@L["Sales_Grid_Toolbar_ReportRange"]</label>
@ -47,9 +48,12 @@
@code {
#region Data Processing
string storageKey = "BlazingCoffeeSales";
DataEnvelope<Sale> Model { get; set; } = new DataEnvelope<Sale>();
SalesByDateViewModel[] chartData;
async Task ReadItems(GridReadEventArgs args)
{
var response = await Http.PostAsJsonAsync("api/sales", args.Request);
@ -61,7 +65,30 @@
protected override async Task OnInitializedAsync()
{
try
{
var state = await LocalStorage.GetItemAsync<GridState<Sale>>(storageKey);
if (state != null)
{
var startFilter = state.TransactionDateFilters(f => f.Operator == FilterOperator.IsGreaterThan);
var endFilter = state.TransactionDateFilters(f => f.Operator == FilterOperator.IsLessThan);
StartValue = (DateTime?)startFilter.Value;
EndValue = (DateTime?)endFilter.Value;
}
// .StartDate != null ? state.StartDate : StartValue = DateTime.Now.AddYears(-2);
//EndValue = state?.EndDate != null ? state.EndDate : StartValue = DateTime.Now;
}
catch (InvalidOperationException e)
{
// the JS Interop for the local storage cannot be used during pre-rendering
// so the code above will throw. Once the app initializes, it will work fine
}
await GetChartData();
}
async Task GetChartData()
@ -78,19 +105,12 @@
FilterDescriptor StartFilter() => new FilterDescriptor("TransactionDate", FilterOperator.IsGreaterThan, StartValue);
FilterDescriptor EndFilter() => new FilterDescriptor("TransactionDate", FilterOperator.IsLessThan, EndValue);
void OnStateInit(GridStateEventArgs<Sale> args)
{
args.GridState.FilterDescriptors.Add(StartFilter());
args.GridState.FilterDescriptors.Add(EndFilter());
}
void StartValueChangedHandler(DateTime? currStart)
{
//you have to update the model manually because handling the <Parameter>Changed event does not let you use @bind-<Parameter>
//not updating the model will effectively cancel the event
StartValue = currStart;
//Console.WriteLine($"start changed to: {currStart}");
}
async Task EndValueChangedHandler(DateTime? currEnd)
@ -111,7 +131,38 @@
state.FilterDescriptors.Add(EndFilter());
await Grid.SetState(state);
await GetChartData();
await LocalStorage.SetItemAsync<GridState<Sale>>(storageKey, state);
}
}
#endregion
#region State Management
async Task OnStateInit(GridStateEventArgs<Sale> args)
{
try
{
var state = await LocalStorage.GetItemAsync<GridState<Sale>>(storageKey);
if (state != null)
{
args.GridState = state;
}
}
catch (InvalidOperationException e)
{
// the JS Interop for the local storage cannot be used during pre-rendering
// so the code above will throw. Once the app initializes, it will work fine
}
args.GridState.FilterDescriptors.Add(StartFilter());
args.GridState.FilterDescriptors.Add(EndFilter());
}
async Task OnStateChanged(GridStateEventArgs<Sale> args)
{
await LocalStorage.SetItemAsync(storageKey, args.GridState);
}
#endregion
}

Просмотреть файл

@ -0,0 +1,18 @@
using System;
using System.Linq;
using BlazingCoffee.Shared.Models;
using Telerik.Blazor.Components;
using Telerik.DataSource;
namespace BlazingCoffee.Client.Pages.SalesReports
{
public static class SalesExtensions
{
public static FilterDescriptor TransactionDateFilters(this GridState<Sale> gridState,
Func<FilterDescriptor, bool> predicate) =>
gridState.FilterDescriptors
.OfType<FilterDescriptor>()
.Where(f=> f.Member == "TransactionDate")
.First(predicate);
}
}

Просмотреть файл

@ -1,9 +1,9 @@
using BlazingCoffee.Services;
using BlazingCoffee.Shared.Localization;
using Blazored.LocalStorage;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.JSInterop;
using System;
using System.Globalization;
using System.Net.Http;
@ -22,8 +22,8 @@ namespace BlazingCoffee.Client
ConfigureServices(builder);
var host = builder.Build();
var jsInterop = host.Services.GetRequiredService<IJSRuntime>();
var result = await jsInterop.InvokeAsync<string>("blazorCulture.get");
var localStorage = host.Services.GetRequiredService<ILocalStorageService>();
var result = await localStorage.GetItemAsStringAsync("BlazorCulture");
if (result != null)
{
var culture = new CultureInfo(result);
@ -38,6 +38,7 @@ namespace BlazingCoffee.Client
{
builder.Services.AddTelerikBlazor();
builder.Services.AddBlazoredLocalStorage();
builder.Services.AddHttpClient<PublicClient>(client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress));
builder.Services.AddHttpClient("BlazingCoffee.ServerAPI", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

Просмотреть файл

@ -1,12 +1,12 @@
@using System.Globalization
@inject IJSRuntime js
@inject NavigationManager NavigationManager
@inject ILocalStorageService LocalStorage
<div style="margin-bottom: 20px;">
<TelerikDropDownList Data="@Cultures"
Value="@SelectedCulture"
ValueChanged="@((string value) => { OnValueChanged(value); })"
ValueChanged="@((string value) => OnValueChanged(value) )"
TextField="@nameof(CultureData.Text)"
ValueField="@nameof(CultureData.Value)">
</TelerikDropDownList>
@ -29,18 +29,18 @@
public string SelectedCulture { get; set; }
public void OnValueChanged(string eventArgs)
public async Task OnValueChanged(string eventArgs)
{
SelectedCulture = eventArgs;
SetCulture(eventArgs);
await SetCulture(eventArgs);
}
public void SetCulture(string culture)
public async Task SetCulture(string culture)
{
if (CultureInfo.CurrentCulture.Name != culture)
{
js.InvokeVoidAsync("blazorCulture.set", culture);
await LocalStorage.SetItemAsync("BlazorCulture", culture);
NavigationManager.NavigateTo(NavigationManager.Uri, forceLoad: true);
}
@ -48,7 +48,7 @@
public async Task GetCulture()
{
var value = await js.InvokeAsync<string>("blazorCulture.get");
var value = await LocalStorage.GetItemAsStringAsync("BlazorCulture");
if (string.IsNullOrEmpty(value))
{
value = "en-US";

Просмотреть файл

@ -3,10 +3,13 @@
@inherits LayoutComponentBase
@inject ITelerikStringLocalizer L
@inject ILocalStorageService LocalStorage
<TelerikRootComponent>
<TelerikDrawer @bind-Expanded="@Expanded" Width="280px" Data="Data" Mode="DrawerMode.Push" Position="DrawerPosition.Left" MiniMode="true">
<TelerikDrawer @ref="MenuDrawer"
Expanded="@Expanded"
ExpandedChanged="((bool newValue) => ExpandedChangedHandler(newValue))"
Width="280px" Data="Data" Mode="DrawerMode.Push" Position="DrawerPosition.Left" MiniMode="true">
<Template>
<DrawTemplate Data="context"></DrawTemplate>
</Template>
@ -14,7 +17,7 @@
<header class="header">
<div class="nav-container">
<div class="menu-button">
<TelerikButton Icon="@IconName.Menu" OnClick="@(() => Expanded = !Expanded)" />
<TelerikButton Icon="@IconName.Menu" OnClick="ToggleMenuDrawer" />
</div>
<div class="title">
@ -42,7 +45,7 @@
@L["SelectTheme"]
</label>
<div class="k-form-field-wrap">
<TelerikDropDownList Id="theme" Data="Themes" TValue="string" TItem="string" @bind-Value="SelectedTheme" />
<ThemeChooser></ThemeChooser>
</div>
</div>
<div class="k-form-field">
@ -59,7 +62,10 @@
</TelerikRootComponent>
@code {
bool Expanded { get; set; } = true;
TelerikDrawer<DrawerItem> MenuDrawer { get; set; }
bool Expanded { get; set; }
bool SettingsExpanded { get; set; }
IEnumerable<DrawerItem> Data =>
@ -73,9 +79,38 @@
new DrawerItem{ Text = "Telerik", Icon = IconName.HyperlinkGlobe, Url="https://telerik.com", Group = "ext"},
new DrawerItem{ Text = L["Documentation"], Icon = IconName.Html, Url="https://docs.telerik.com/blazor-ui/introduction", Group = "ext"},
new DrawerItem{ Text = L["Support"], Icon = IconName.Question, Url="https://www.telerik.com/account/support-tickets", Group = "ext"}
};
};
string SelectedTheme = "auto";
IEnumerable<string> Themes => new List<string> { "auto" }; // TODO add manual settings, "light", "dark" };
async Task ToggleMenuDrawer()
{
if (Expanded)
{
await MenuDrawer.CollapseAsync();
}
else
{
await MenuDrawer.ExpandAsync();
}
}
}
async Task ExpandedChangedHandler(bool value)
{
Expanded = value;
await LocalStorage.SetItemAsync("drawerState", value);
}
protected override async Task OnInitializedAsync()
{
var hasKey = await LocalStorage.ContainKeyAsync("drawerState");
if (hasKey)
{
Expanded = await LocalStorage.GetItemAsync<bool>("drawerState");
}
else
{
Expanded = true;
}
}
}

Просмотреть файл

@ -0,0 +1,32 @@
@namespace BlazingCoffee.Client.Shared
@inject IJSRuntime js
<TelerikDropDownList Id="theme"
Data="Themes"
TextField="ThemeText"
ValueField="ThemeValue"
Value="@SelectedTheme"
ValueChanged="@( (string v) => HandleThemeSelected(v) )" />
@code {
string SelectedTheme;
IEnumerable<ThemeSetting> Themes => new List<ThemeSetting> {
new ThemeSetting { ThemeText = "Auto (default)", ThemeValue = "auto" },
new ThemeSetting { ThemeText = "Light", ThemeValue = "main" },
new ThemeSetting { ThemeText = "Dark", ThemeValue = "main-dark" }
};
async Task HandleThemeSelected(string value)
{
await js.InvokeVoidAsync("themeChooser.setTheme", value);
SelectedTheme = value;
}
protected override async Task OnInitializedAsync()
{
var theme = await js.InvokeAsync<string>("themeChooser.getTheme");
SelectedTheme = string.IsNullOrEmpty(theme) ? "auto" : theme;
}
}

Просмотреть файл

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BlazingCoffee.Client.Shared
{
public class ThemeSetting
{
public string ThemeText { get; set; }
public string ThemeValue { get; set; }
}
}

Просмотреть файл

@ -17,4 +17,5 @@
@using BlazingCoffee.Client
@using BlazingCoffee.Client.Shared
@using BlazingCoffee.Shared
@using BlazingCoffee.Shared.Models
@using BlazingCoffee.Shared.Models
@using Blazored.LocalStorage

Просмотреть файл

@ -8,8 +8,7 @@
<base href="/" />
<link href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" rel="stylesheet">
<script src="_content/telerik.ui.for.blazor/js/telerik-blazor.js"></script>
<link href="css/main.css" rel="stylesheet" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" />
<link href="css/main-dark.css" rel="stylesheet" media="(prefers-color-scheme: dark)" />
<link id="theme" href="css/main.css" rel="stylesheet" />
<!-- PWA -->
<link href="manifest.json" rel="manifest" />
<link rel="apple-touch-icon" sizes="512x512" href="icon-512.png" />
@ -26,7 +25,10 @@
</div>
<script src="_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js"></script>
<script src="_framework/blazor.webassembly.js"></script>
<script src="js/blazorCulture.js"></script>
<script src="js/themeChooser.js"></script>
<script>
window.themeChooser.init();
</script>
<!-- PWA -->
<script>navigator.serviceWorker.register('service-worker.js');</script>
<!-- /PWA -->

Просмотреть файл

@ -1,9 +0,0 @@
window.blazorCulture = {
get: () => {
return window.localStorage['BlazorCulture'];
},
set: (value) => {
window.localStorage['BlazorCulture'] = value;
}
};

Просмотреть файл

@ -0,0 +1,47 @@
window.themeChooser = {
getTheme: function () {
return window.localStorage['ThemeSetting'];
},
setTheme: function (themeName) {
if (themeName === "auto") {
window.localStorage.removeItem('ThemeSetting');
this.autoDetectTheme();
} else {
window.localStorage['ThemeSetting'] = themeName;
this.changeTheme(themeName);
}
},
changeTheme: function (themeName) {
// Build the new css link
let newLink = document.createElement("link");
newLink.setAttribute("id", "theme");
newLink.setAttribute("rel", "stylesheet");
newLink.setAttribute("type", "text/css");
newLink.setAttribute("href", `css/${themeName}.css`);
// Remove and replace the theme
let head = document.getElementsByTagName("head")[0];
head.querySelector("#theme").remove();
head.appendChild(newLink);
},
autoDetectTheme() {
let isDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
if (isDark) {
this.changeTheme("main-dark");
} else {
this.changeTheme("main");
}
},
init: function () {
let themeSetting = this.getTheme();
let isThemeSet = !themeSetting;
if (isThemeSet) {
this.autoDetectTheme();
}
else {
this.changeTheme(themeSetting);
}
}
}