From 617fdf10e02291a55949ad788a2b2ecb4c4f21b6 Mon Sep 17 00:00:00 2001 From: DouweRavers Date: Fri, 27 Mar 2026 13:51:58 +0100 Subject: [PATCH] Blazor webassembly with radzen setup --- .gitignore | 289 ++++++++++++++++++++++++++++ App.razor | 6 + BattSim.csproj | 22 +++ Layout/MainLayout.razor | 3 + Models/BatteryDayResult.cs | 14 ++ Models/EnergyData.cs | 13 ++ Pages/Home.razor | 96 +++++++++ Pages/NotFound.razor | 5 + Program.cs | 13 ++ ReadMe.md | 8 + Services/BatterySimulator.cs | 45 +++++ Services/DataLoader.cs | 54 ++++++ Services/ReportGenerator.cs | 86 +++++++++ _Imports.razor | 10 + wwwroot/css/app.css | 89 +++++++++ wwwroot/icon-192.png | Bin 0 -> 2626 bytes wwwroot/icon-512.png | Bin 0 -> 6311 bytes wwwroot/index.html | 40 ++++ wwwroot/manifest.webmanifest | 22 +++ wwwroot/service-worker.js | 4 + wwwroot/service-worker.published.js | 55 ++++++ 21 files changed, 874 insertions(+) create mode 100644 .gitignore create mode 100644 App.razor create mode 100644 BattSim.csproj create mode 100644 Layout/MainLayout.razor create mode 100644 Models/BatteryDayResult.cs create mode 100644 Models/EnergyData.cs create mode 100644 Pages/Home.razor create mode 100644 Pages/NotFound.razor create mode 100644 Program.cs create mode 100644 ReadMe.md create mode 100644 Services/BatterySimulator.cs create mode 100644 Services/DataLoader.cs create mode 100644 Services/ReportGenerator.cs create mode 100644 _Imports.razor create mode 100644 wwwroot/css/app.css create mode 100644 wwwroot/icon-192.png create mode 100644 wwwroot/icon-512.png create mode 100644 wwwroot/index.html create mode 100644 wwwroot/manifest.webmanifest create mode 100644 wwwroot/service-worker.js create mode 100644 wwwroot/service-worker.published.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b5d7fe6 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/App.razor b/App.razor new file mode 100644 index 0000000..a8a79e5 --- /dev/null +++ b/App.razor @@ -0,0 +1,6 @@ + + + + + + diff --git a/BattSim.csproj b/BattSim.csproj new file mode 100644 index 0000000..335c097 --- /dev/null +++ b/BattSim.csproj @@ -0,0 +1,22 @@ + + + + net10.0 + enable + enable + true + service-worker-assets.js + true + true + true + + + + + + + + + + + diff --git a/Layout/MainLayout.razor b/Layout/MainLayout.razor new file mode 100644 index 0000000..e1a9a75 --- /dev/null +++ b/Layout/MainLayout.razor @@ -0,0 +1,3 @@ +@inherits LayoutComponentBase + +@Body diff --git a/Models/BatteryDayResult.cs b/Models/BatteryDayResult.cs new file mode 100644 index 0000000..e6f91d8 --- /dev/null +++ b/Models/BatteryDayResult.cs @@ -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; } + } +} \ No newline at end of file diff --git a/Models/EnergyData.cs b/Models/EnergyData.cs new file mode 100644 index 0000000..e609c27 --- /dev/null +++ b/Models/EnergyData.cs @@ -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; } + } +} \ No newline at end of file diff --git a/Pages/Home.razor b/Pages/Home.razor new file mode 100644 index 0000000..2b05933 --- /dev/null +++ b/Pages/Home.razor @@ -0,0 +1,96 @@ +@page "/" +@using Radzen +@using Radzen.Blazor +@using System.Globalization + + +Home + +

Hello, world!

+ +Welcome to your new app. + +

A Radzen chart:

+ + + + + + + + + + + + + + + + +@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 + }, + + }; +} \ No newline at end of file diff --git a/Pages/NotFound.razor b/Pages/NotFound.razor new file mode 100644 index 0000000..917ada1 --- /dev/null +++ b/Pages/NotFound.razor @@ -0,0 +1,5 @@ +@page "/not-found" +@layout MainLayout + +

Not Found

+

Sorry, the content you are looking for does not exist.

\ No newline at end of file diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..1efa159 --- /dev/null +++ b/Program.cs @@ -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"); +builder.RootComponents.Add("head::after"); + +builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); +builder.Services.AddRadzenComponents(); + +await builder.Build().RunAsync(); \ No newline at end of file diff --git a/ReadMe.md b/ReadMe.md new file mode 100644 index 0000000..ef67b78 --- /dev/null +++ b/ReadMe.md @@ -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. \ No newline at end of file diff --git a/Services/BatterySimulator.cs b/Services/BatterySimulator.cs new file mode 100644 index 0000000..96441fc --- /dev/null +++ b/Services/BatterySimulator.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using BatteryCostAnalysis.Models; + +namespace BatteryCostAnalysis.Services +{ + public class BatterySimulator + { + public static List SimulateBattery(List data, double batteryCapacity) + { + var results = new List(); + 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; + } + } +} \ No newline at end of file diff --git a/Services/DataLoader.cs b/Services/DataLoader.cs new file mode 100644 index 0000000..0e757f6 --- /dev/null +++ b/Services/DataLoader.cs @@ -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 LoadAndProcessData(string filePath) + { + var energyData = new List(); + 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; + } + } +} \ No newline at end of file diff --git a/Services/ReportGenerator.cs b/Services/ReportGenerator.cs new file mode 100644 index 0000000..abffff9 --- /dev/null +++ b/Services/ReportGenerator.cs @@ -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 originalData, Dictionary> 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"); + } + } +} \ No newline at end of file diff --git a/_Imports.razor b/_Imports.razor new file mode 100644 index 0000000..b9514d2 --- /dev/null +++ b/_Imports.razor @@ -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 diff --git a/wwwroot/css/app.css b/wwwroot/css/app.css new file mode 100644 index 0000000..3b9a456 --- /dev/null +++ b/wwwroot/css/app.css @@ -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; +} \ No newline at end of file diff --git a/wwwroot/icon-192.png b/wwwroot/icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..166f56da7612ea74df6a297154c8d281a4f28a14 GIT binary patch literal 2626 zcmV-I3cdA-P)v0A9xRwxP|bki~~&uFk>U z#P+PQh zyZ;-jwXKqnKbb6)@RaxQz@vm={%t~VbaZrdbaZrdbaeEeXj>~BG?&`J0XrqR#sSlO zg~N5iUk*15JibvlR1f^^1czzNKWvoJtc!Sj*G37QXbZ8LeD{Fzxgdv#Q{x}ytfZ5q z+^k#NaEp>zX_8~aSaZ`O%B9C&YLHb(mNtgGD&Kezd5S@&C=n~Uy1NWHM`t07VQP^MopUXki{2^#ryd94>UJMYW|(#4qV`kb7eD)Q=~NN zaVIRi@|TJ!Rni8J=5DOutQ#bEyMVr8*;HU|)MEKmVC+IOiDi9y)vz=rdtAUHW$yjt zrj3B7v(>exU=IrzC<+?AE=2vI;%fafM}#ShGDZx=0Nus5QHKdyb9pw&4>4XCpa-o?P(Gnco1CGX|U> z$f+_tA3+V~<{MU^A%eP!8R*-sD9y<>Jc7A(;aC5hVbs;kX9&Sa$JMG!W_BLFQa*hM zri__C@0i0U1X#?)Y=)>JpvTnY6^s;fu#I}K9u>OldV}m!Ch`d1Vs@v9 zb}w(!TvOmSzmMBa9gYvD4xocL2r0ds6%Hs>Z& z#7#o9PGHDmfG%JQq`O5~dt|MAQN@2wyJw_@``7Giyy(yyk(m8U*kk5$X1^;3$a3}N^Lp6hE5!#8l z#~NYHmKAs6IAe&A;bvM8OochRmXN>`D`{N$%#dZCRxp4-dJ?*3P}}T`tYa3?zz5BA zTu7uE#GsDpZ$~j9q=Zq!LYjLbZPXFILZK4?S)C-zE1(dC2d<7nO4-nSCbV#9E|E1MM|V<9>i4h?WX*r*ul1 z5#k6;po8z=fdMiVVz*h+iaTlz#WOYmU^SX5#97H~B32s-#4wk<1NTN#g?LrYieCu> zF7pbOLR;q2D#Q`^t%QcY06*X-jM+ei7%ZuanUTH#9Y%FBi*Z#22({_}3^=BboIsbg zR0#jJ>9QR8SnmtSS6x($?$}6$x+q)697#m${Z@G6Ujf=6iO^S}7P`q8DkH!IHd4lB zDzwxt3BHsPAcXFFY^Fj}(073>NL_$A%v2sUW(CRutd%{G`5ow?L`XYSO*Qu?x+Gzv zBtR}Y6`XF4xX7)Z04D+fH;TMapdQFFameUuHL34NN)r@aF4RO%x&NApeWGtr#mG~M z6sEIZS;Uj1HB1*0hh=O@0q1=Ia@L>-tETu-3n(op+97E z#&~2xggrl(LA|giII;RwBlX2^Q`B{_t}gxNL;iB11gEPC>v` zb4SJ;;BFOB!{chn>?cCeGDKuqI0+!skyWTn*k!WiPNBf=8rn;@y%( znhq%8fj2eAe?`A5mP;TE&iLEmQ^xV%-kmC-8mWao&EUK_^=GW-Y3z ksi~={si~={skwfB0gq6itke#r1ONa407*qoM6N<$g11Kq@c;k- literal 0 HcmV?d00001 diff --git a/wwwroot/icon-512.png b/wwwroot/icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..c2dd4842dc93df73c322218ee03eca142a19a338 GIT binary patch literal 6311 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 literal 0 HcmV?d00001 diff --git a/wwwroot/index.html b/wwwroot/index.html new file mode 100644 index 0000000..fd22d75 --- /dev/null +++ b/wwwroot/index.html @@ -0,0 +1,40 @@ + + + + + + + BattSim + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ An unhandled error has occurred. + Reload + 🗙 +
+ + + + + diff --git a/wwwroot/manifest.webmanifest b/wwwroot/manifest.webmanifest new file mode 100644 index 0000000..971b302 --- /dev/null +++ b/wwwroot/manifest.webmanifest @@ -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" + } + ] +} diff --git a/wwwroot/service-worker.js b/wwwroot/service-worker.js new file mode 100644 index 0000000..904f692 --- /dev/null +++ b/wwwroot/service-worker.js @@ -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 diff --git a/wwwroot/service-worker.published.js b/wwwroot/service-worker.published.js new file mode 100644 index 0000000..51a0e5c --- /dev/null +++ b/wwwroot/service-worker.published.js @@ -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); +}