Blazor webassembly with radzen setup

This commit is contained in:
DouweRavers
2026-03-27 13:51:58 +01:00
commit 617fdf10e0
21 changed files with 874 additions and 0 deletions

289
.gitignore vendored Normal file
View File

@@ -0,0 +1,289 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
**/Properties/launchSettings.json
# VS Code
.vscode/
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Typescript v1 declaration files
typings/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush
.cr/
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs

6
App.razor Normal file
View File

@@ -0,0 +1,6 @@
<Router AppAssembly="@typeof(App).Assembly" NotFoundPage="typeof(Pages.NotFound)">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
</Router>

22
BattSim.csproj Normal file
View File

@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<OverrideHtmlAssetPlaceholders>true</OverrideHtmlAssetPlaceholders>
<ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest>
<WasmAllowUndefinedSymbols>true</WasmAllowUndefinedSymbols>
<RunAOTCompilation>true</RunAOTCompilation>
<WasmShellGenerateAOTProfile>true</WasmShellGenerateAOTProfile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.5" PrivateAssets="all" />
<PackageReference Include="Radzen.Blazor" Version="10.0.6" />
</ItemGroup>
<ItemGroup>
<ServiceWorker Include="wwwroot\service-worker.js" PublishedContent="wwwroot\service-worker.published.js" />
</ItemGroup>
</Project>

3
Layout/MainLayout.razor Normal file
View File

@@ -0,0 +1,3 @@
@inherits LayoutComponentBase
@Body

View File

@@ -0,0 +1,14 @@
using System;
namespace BatteryCostAnalysis.Models
{
public class BatteryDayResult
{
public DateTime Date { get; set; }
public double ChargedEnergy { get; set; }
public double UsedEnergy { get; set; }
public double RemainingEnergy { get; set; }
public double ReducedConsumption { get; set; }
public double ReducedProduction { get; set; }
}
}

13
Models/EnergyData.cs Normal file
View File

@@ -0,0 +1,13 @@
using System;
namespace BatteryCostAnalysis.Models
{
public class EnergyData
{
public DateTime Date { get; set; }
public double DayConsumption { get; set; }
public double NightConsumption { get; set; }
public double DayProduction { get; set; }
public double NightProduction { get; set; }
}
}

96
Pages/Home.razor Normal file
View File

@@ -0,0 +1,96 @@
@page "/"
@using Radzen
@using Radzen.Blazor
@using System.Globalization
<PageTitle>Home</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<h2>A Radzen chart:</h2>
<RadzenChart SeriesClick=@OnSeriesClick style="height: 400px">
<RadzenBarSeries Data="@revenue2024" CategoryProperty="Quarter" Title="2024" LineType="LineType.Dashed" ValueProperty="Revenue">
<RadzenSeriesDataLabels Visible="@showDataLabels" />
</RadzenBarSeries>
<RadzenBarSeries Data="@revenue2023" CategoryProperty="Quarter" Title="2023" ValueProperty="Revenue">
<RadzenSeriesDataLabels Visible="@showDataLabels" />
</RadzenBarSeries>
<RadzenValueAxis Formatter="@FormatAsUSD">
<RadzenGridLines Visible="true" />
<RadzenAxisTitle Text="Revenue in USD" />
</RadzenValueAxis>
<RadzenBarOptions Radius="5" />
</RadzenChart>
@code {
bool showDataLabels = false;
void OnSeriesClick(SeriesClickEventArgs args)
{
}
class DataItem
{
public string Quarter { get; set; }
public double Revenue { get; set; }
}
string FormatAsUSD(object value)
{
return ((double)value).ToString("C0", CultureInfo.CreateSpecificCulture("en-US"));
}
DataItem[] revenue2023 = new DataItem[]
{
new DataItem
{
Quarter = "Q1",
Revenue = 234000
},
new DataItem
{
Quarter = "Q2",
Revenue = 284000
},
new DataItem
{
Quarter = "Q3",
Revenue = 274000
},
new DataItem
{
Quarter = "Q4",
Revenue = 294000
},
};
DataItem[] revenue2024 = new DataItem[] {
new DataItem
{
Quarter = "Q1",
Revenue = 254000
},
new DataItem
{
Quarter = "Q2",
Revenue = 324000
},
new DataItem
{
Quarter = "Q3",
Revenue = 354000
},
new DataItem
{
Quarter = "Q4",
Revenue = 394000
},
};
}

5
Pages/NotFound.razor Normal file
View File

@@ -0,0 +1,5 @@
@page "/not-found"
@layout MainLayout
<h3>Not Found</h3>
<p>Sorry, the content you are looking for does not exist.</p>

13
Program.cs Normal file
View File

@@ -0,0 +1,13 @@
using BattSim;
using Radzen;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddRadzenComponents();
await builder.Build().RunAsync();

8
ReadMe.md Normal file
View File

@@ -0,0 +1,8 @@
# BatSim
A tool that uses your current home energy data an simulates the impact of installing a home battery. It also does a cost analysis.
The tool works specifically for the flemish system with input data from fluvius and cost analysis based on the flemish energy pricing.
## Used Tools
This project makes use of the Radzen.blazer package for UI components.

View File

@@ -0,0 +1,45 @@
using System.Collections.Generic;
using BatteryCostAnalysis.Models;
namespace BatteryCostAnalysis.Services
{
public class BatterySimulator
{
public static List<BatteryDayResult> SimulateBattery(List<EnergyData> data, double batteryCapacity)
{
var results = new List<BatteryDayResult>();
double remainingEnergy = 0;
foreach (var day in data)
{
// Charge battery from production
var totalProduction = day.DayProduction + day.NightProduction;
var chargedEnergy = System.Math.Min(totalProduction, batteryCapacity);
var excessProduction = totalProduction - chargedEnergy;
// Use battery for consumption
var totalConsumption = day.DayConsumption + day.NightConsumption;
var usedEnergy = System.Math.Min(chargedEnergy + remainingEnergy, totalConsumption);
var remainingAfterUse = chargedEnergy + remainingEnergy - usedEnergy;
// Calculate reduced values
var reducedConsumption = System.Math.Min(usedEnergy, totalConsumption);
var reducedProduction = totalProduction - chargedEnergy;
results.Add(new BatteryDayResult
{
Date = day.Date,
ChargedEnergy = chargedEnergy,
UsedEnergy = usedEnergy,
RemainingEnergy = remainingAfterUse,
ReducedConsumption = reducedConsumption,
ReducedProduction = reducedProduction
});
remainingEnergy = remainingAfterUse;
}
return results;
}
}
}

54
Services/DataLoader.cs Normal file
View File

@@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using BatteryCostAnalysis.Models;
namespace BatteryCostAnalysis.Services
{
public class DataLoader
{
public static List<EnergyData> LoadAndProcessData(string filePath)
{
var energyData = new List<EnergyData>();
var lines = File.ReadAllLines(filePath).Skip(1); // Skip header
foreach (var line in lines)
{
var parts = line.Split(';');
if (parts.Length < 9) continue;
var date = DateTime.Parse(parts[0], new CultureInfo("nl-BE"));
var register = parts[7].Trim();
var volumeStr = parts[8].Trim();
var volume = string.IsNullOrEmpty(volumeStr) ? 0 : double.Parse(volumeStr.Replace(",", "."), CultureInfo.InvariantCulture);
var existing = energyData.FirstOrDefault(e => e.Date == date);
if (existing == null)
{
existing = new EnergyData { Date = date };
energyData.Add(existing);
}
switch (register)
{
case "Afname Dag":
existing.DayConsumption = volume;
break;
case "Afname Nacht":
existing.NightConsumption = volume;
break;
case "Injectie Dag":
existing.DayProduction = volume;
break;
case "Injectie Nacht":
existing.NightProduction = volume;
break;
}
}
return energyData;
}
}
}

View File

@@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using BatteryCostAnalysis.Models;
namespace BatteryCostAnalysis.Services
{
public class ReportGenerator
{
public static void GenerateReport(List<EnergyData> originalData, Dictionary<int, List<BatteryDayResult>> scenarios)
{
using (var writer = new StreamWriter("../../../BatteryAnalysisReport.md"))
{
writer.WriteLine("# Battery Cost Analysis Report\n");
// Original data summary
writer.WriteLine("## Original Data Summary");
var totalConsumption = originalData.Sum(d => d.DayConsumption + d.NightConsumption);
var totalProduction = originalData.Sum(d => d.DayProduction + d.NightProduction);
writer.WriteLine($"Total Consumption: {totalConsumption:F2} kWh");
writer.WriteLine($"Total Production: {totalProduction:F2} kWh\n");
// Generate graph data
writer.WriteLine("### Energy Consumption and Production\n");
writer.WriteLine("```");
writer.WriteLine("Date,Total Production,Total Consumption");
foreach (var day in originalData)
{
var prod = day.DayProduction + day.NightProduction;
var cons = day.DayConsumption + day.NightConsumption;
writer.WriteLine($"{day.Date:yyyy-MM-dd},{prod:F2},{cons:F2}");
}
writer.WriteLine("```\n");
writer.WriteLine("![Energy Graph](energy_graph.png)");
writer.WriteLine("(Graph showing total production in green and total consumption in red)\n");
// Scenario analysis
foreach (var scenario in scenarios)
{
var batteryCount = scenario.Key;
var results = scenario.Value;
writer.WriteLine($"## Scenario with {batteryCount} Battery{(batteryCount > 1 ? "ies" : "")}");
writer.WriteLine($"Battery Capacity: {batteryCount * 2.6} kWh\n");
var totalReducedConsumption = results.Sum(r => r.ReducedConsumption);
var totalReducedProduction = results.Sum(r => r.ReducedProduction);
var savings = totalReducedConsumption * 0.30; // Assuming €0.30 per kWh
writer.WriteLine($"Total Reduced Consumption: {totalReducedConsumption:F2} kWh");
writer.WriteLine($"Total Reduced Production: {totalReducedProduction:F2} kWh");
writer.WriteLine($"Estimated Annual Savings: €{savings:F2}\n");
// Generate scenario graph data
writer.WriteLine($"### Battery Performance for {batteryCount} Battery{(batteryCount > 1 ? "ies" : "")}\n");
writer.WriteLine("```");
writer.WriteLine("Date,Charged Energy,Used Energy,Remaining Energy");
foreach (var result in results)
{
writer.WriteLine($"{result.Date:yyyy-MM-dd},{result.ChargedEnergy:F2},{result.UsedEnergy:F2},{result.RemainingEnergy:F2}");
}
writer.WriteLine("```\n");
writer.WriteLine($"![Battery Performance Graph](battery_{batteryCount}_graph.png)");
writer.WriteLine("(Graph showing charged energy in blue, used energy in orange, and remaining energy in gray)\n");
// Detailed daily results
writer.WriteLine("### Daily Results");
writer.WriteLine("| Date | Charged (kWh) | Used (kWh) | Remaining (kWh) |");
writer.WriteLine("|------|---------------|------------|-----------------|");
foreach (var result in results.Take(10)) // Show first 10 days
{
writer.WriteLine($"| {result.Date:yyyy-MM-dd} | {result.ChargedEnergy:F2} | {result.UsedEnergy:F2} | {result.RemainingEnergy:F2} |");
}
writer.WriteLine("\n---\n");
}
}
Console.WriteLine("Report generated: BatteryAnalysisReport.md");
}
}
}

10
_Imports.razor Normal file
View File

@@ -0,0 +1,10 @@
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using BattSim
@using BattSim.Layout

89
wwwroot/css/app.css Normal file
View File

@@ -0,0 +1,89 @@
.valid.modified:not([type=checkbox]) {
outline: 1px solid #26b050;
}
.invalid {
outline: 1px solid red;
}
.validation-message {
color: red;
}
#blazor-error-ui {
color-scheme: light only;
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
box-sizing: border-box;
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
}
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}
.blazor-error-boundary {
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
padding: 1rem 1rem 1rem 3.7rem;
color: white;
}
.blazor-error-boundary::after {
content: "An error has occurred."
}
.loading-progress {
position: absolute;
display: block;
width: 8rem;
height: 8rem;
inset: 20vh 0 auto 0;
margin: 0 auto 0 auto;
}
.loading-progress circle {
fill: none;
stroke: #e0e0e0;
stroke-width: 0.6rem;
transform-origin: 50% 50%;
transform: rotate(-90deg);
}
.loading-progress circle:last-child {
stroke: #1b6ec2;
stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;
transition: stroke-dasharray 0.05s ease-in-out;
}
.loading-progress-text {
position: absolute;
text-align: center;
font-weight: bold;
inset: calc(20vh + 3.25rem) 0 auto 0.2rem;
}
.loading-progress-text:after {
content: var(--blazor-load-percentage-text, "Loading");
}
code {
color: #c02d76;
}
.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
color: var(--bs-secondary-color);
text-align: end;
}
.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
text-align: start;
}

BIN
wwwroot/icon-192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
wwwroot/icon-512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

40
wwwroot/index.html Normal file
View File

@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BattSim</title>
<base href="/" />
<link rel="preload" id="webassembly" />
<link rel="stylesheet" href="css/app.css" />
<!-- If you add any scoped CSS files, uncomment the following to load them
<link href="BattSim.styles.css" rel="stylesheet" /> -->
<link href="manifest.webmanifest" rel="manifest" />
<link rel="apple-touch-icon" sizes="512x512" href="icon-512.png" />
<link rel="apple-touch-icon" sizes="192x192" href="icon-192.png" />
<script type="importmap"></script>
<link rel="stylesheet" href="_content/Radzen.Blazor/css/default.css">
<script src="_content/Radzen.Blazor/Radzen.Blazor.js"></script>
</head>
<body>
<div id="app">
<svg class="loading-progress">
<circle r="40%" cx="50%" cy="50%" />
<circle r="40%" cx="50%" cy="50%" />
</svg>
<div class="loading-progress-text"></div>
</div>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="." class="reload">Reload</a>
<span class="dismiss">🗙</span>
</div>
<script src="_framework/blazor.webassembly#[.{fingerprint}].js"></script>
<script>navigator.serviceWorker.register('service-worker.js', { updateViaCache: 'none' });</script>
</body>
</html>

View File

@@ -0,0 +1,22 @@
{
"name": "BattSim",
"short_name": "BattSim",
"id": "./",
"start_url": "./",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#03173d",
"prefer_related_applications": false,
"icons": [
{
"src": "icon-512.png",
"type": "image/png",
"sizes": "512x512"
},
{
"src": "icon-192.png",
"type": "image/png",
"sizes": "192x192"
}
]
}

View File

@@ -0,0 +1,4 @@
// In development, always fetch from the network and do not enable offline support.
// This is because caching would make development more difficult (changes would not
// be reflected on the first load after each change).
// Removed empty fetch handler to avoid "no-op fetch handler" warning

View File

@@ -0,0 +1,55 @@
// Caution! Be sure you understand the caveats before publishing an application with
// offline support. See https://aka.ms/blazor-offline-considerations
self.importScripts('./service-worker-assets.js');
self.addEventListener('install', event => event.waitUntil(onInstall(event)));
self.addEventListener('activate', event => event.waitUntil(onActivate(event)));
self.addEventListener('fetch', event => event.respondWith(onFetch(event)));
const cacheNamePrefix = 'offline-cache-';
const cacheName = `${cacheNamePrefix}${self.assetsManifest.version}`;
const offlineAssetsInclude = [ /\.dll$/, /\.pdb$/, /\.wasm/, /\.html/, /\.js$/, /\.json$/, /\.css$/, /\.woff$/, /\.png$/, /\.jpe?g$/, /\.gif$/, /\.ico$/, /\.blat$/, /\.dat$/, /\.webmanifest$/ ];
const offlineAssetsExclude = [ /^service-worker\.js$/ ];
// Replace with your base path if you are hosting on a subfolder. Ensure there is a trailing '/'.
const base = "/";
const baseUrl = new URL(base, self.origin);
const manifestUrlList = self.assetsManifest.assets.map(asset => new URL(asset.url, baseUrl).href);
async function onInstall(event) {
console.info('Service worker: Install');
// Fetch and cache all matching items from the assets manifest
const assetsRequests = self.assetsManifest.assets
.filter(asset => offlineAssetsInclude.some(pattern => pattern.test(asset.url)))
.filter(asset => !offlineAssetsExclude.some(pattern => pattern.test(asset.url)))
.map(asset => new Request(asset.url, { integrity: asset.hash, cache: 'no-cache' }));
await caches.open(cacheName).then(cache => cache.addAll(assetsRequests));
}
async function onActivate(event) {
console.info('Service worker: Activate');
// Delete unused caches
const cacheKeys = await caches.keys();
await Promise.all(cacheKeys
.filter(key => key.startsWith(cacheNamePrefix) && key !== cacheName)
.map(key => caches.delete(key)));
}
async function onFetch(event) {
let cachedResponse = null;
if (event.request.method === 'GET') {
// For all navigation requests, try to serve index.html from cache,
// unless that request is for an offline resource.
// If you need some URLs to be server-rendered, edit the following check to exclude those URLs
const shouldServeIndexHtml = event.request.mode === 'navigate'
&& !manifestUrlList.some(url => url === event.request.url);
const request = shouldServeIndexHtml ? 'index.html' : event.request;
const cache = await caches.open(cacheName);
cachedResponse = await cache.match(request);
}
return cachedResponse || fetch(event.request);
}