diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..fe1152b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,30 @@ +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md +!**/.gitignore +!.git/HEAD +!.git/config +!.git/packed-refs +!.git/refs/heads/** \ No newline at end of file diff --git a/Thor.BlazorWAsm/App.razor b/Thor.BlazorWAsm/App.razor new file mode 100644 index 0000000..6fd3ed1 --- /dev/null +++ b/Thor.BlazorWAsm/App.razor @@ -0,0 +1,12 @@ + + + + + + + Not found + +

Sorry, there's nothing at this address.

+
+
+
diff --git a/Thor.BlazorWAsm/Components/AnswerStateSelect.razor b/Thor.BlazorWAsm/Components/AnswerStateSelect.razor new file mode 100644 index 0000000..03be865 --- /dev/null +++ b/Thor.BlazorWAsm/Components/AnswerStateSelect.razor @@ -0,0 +1,30 @@ + + + + + + + + + +@code { + ItemAnswer.State _value { get; set; } = ItemAnswer.State.NotAnswed; + [Parameter] + public ItemAnswer.State Value + { + get => _value; + set + { + if(_value != value) + { + _value = value; + if (ValueChanged.HasDelegate) + ValueChanged.InvokeAsync(Value); + } + } + } + + [Parameter] + public EventCallback ValueChanged { get; set; } +} diff --git a/Thor.BlazorWAsm/Components/CommentsView.razor b/Thor.BlazorWAsm/Components/CommentsView.razor new file mode 100644 index 0000000..4f0f9f4 --- /dev/null +++ b/Thor.BlazorWAsm/Components/CommentsView.razor @@ -0,0 +1,5 @@ +

CommentsView

+ +@code { + +} diff --git a/Thor.BlazorWAsm/Components/InspectionGridDisplayer.razor b/Thor.BlazorWAsm/Components/InspectionGridDisplayer.razor new file mode 100644 index 0000000..dbb3459 --- /dev/null +++ b/Thor.BlazorWAsm/Components/InspectionGridDisplayer.razor @@ -0,0 +1,5 @@ +

GridDisplayer

+ +@code { + +} diff --git a/Thor.BlazorWAsm/Components/InspectionItemFiller.razor b/Thor.BlazorWAsm/Components/InspectionItemFiller.razor new file mode 100644 index 0000000..71762f8 --- /dev/null +++ b/Thor.BlazorWAsm/Components/InspectionItemFiller.razor @@ -0,0 +1,128 @@ + + +
+ @if (Item is ItemTitle title) + { + + @title.OrderId - @title.Label + + } + else if (Item is ItemVerification verification) + { +
+ + + + + + @if (verification.Force == ItemVerification.VerificationForce.Optionnal) + { + + + + } + else if (verification.Force == ItemVerification.VerificationForce.Recommandation) + { + + + + } + else if (verification.Force == ItemVerification.VerificationForce.Mandatory) + { + + + + } +
+
+ @Item.OrderId +
+
+ @Item.Label +
+
+ @Item.References +
+
+ +
+ + } +
+ + + + + + +@code { + [Parameter] + public Item Item { get; set; } + + [Parameter] + public Register? Register { get; set; } + + private Modal Details = default!; + + + public string Class + { + get + { + if (Item is not ItemTitle) + return "verification"; + + return "title order-" + Item.Order; + } + } + + private async Task ShowDetails() + { + if (Register is null) + throw new Exception("Cannot ShowDetails with null register."); + + var parameters = new Dictionary(); + parameters.Add(nameof(InspectionItemFillerDetail.Item), Item); + parameters.Add(nameof(InspectionItemFillerDetail.Register), Register); + + await Details.ShowAsync(title: "Détails", parameters: parameters); + } + + private void OnModalHidden() + { + StateHasChanged(); + } +} diff --git a/Thor.BlazorWAsm/Components/InspectionItemFillerDetail.razor b/Thor.BlazorWAsm/Components/InspectionItemFillerDetail.razor new file mode 100644 index 0000000..ec4ca01 --- /dev/null +++ b/Thor.BlazorWAsm/Components/InspectionItemFillerDetail.razor @@ -0,0 +1,49 @@ +@if (Item.Force == ItemVerification.VerificationForce.Optionnal) +{ + + + Cet item est une indication, il ne relève ni d'une recommandation, ni d'une obligation. + +} +else if (Item.Force == ItemVerification.VerificationForce.Recommandation) +{ + + + Cet item relève de la recommandation, il relève des recommandations opposables. + +} +else if (Item.Force == ItemVerification.VerificationForce.Mandatory) +{ + + + Cet item doit obligatoirement être suivi, car il fait l'objet d'un référentiel opposable. + +} + +

@Item.Label

+ +
Statut
+ + +@if (String.IsNullOrEmpty(Item.Description)) +{ +
Description
+

@Item.Description

+} + +
Références
+

@Item.References

+ +
Commentaires
+

+ +
Historique des modifications
+

+ +@code { + [Parameter] + public ItemVerification Item { get; set; } + + [Parameter] + public Register? Register { get; set; } +} diff --git a/Thor.BlazorWAsm/Components/InspectionItemQuickAnswerSelector.razor b/Thor.BlazorWAsm/Components/InspectionItemQuickAnswerSelector.razor new file mode 100644 index 0000000..8f7d276 --- /dev/null +++ b/Thor.BlazorWAsm/Components/InspectionItemQuickAnswerSelector.razor @@ -0,0 +1,37 @@ + + + + + + + + + +@code { + [Parameter] + public ItemVerification Item { get; set; } + + [Parameter] + public Register Register { get; set; } + + ItemAnswer.State Value + { + get + { + return Register.GetItemState(Item.Identifier); + } + set + { + Register.Add(new() + { + Identifier = Guid.NewGuid().ToString(), + Author = "(null)", + Type = Entry.EntryType.Set, + Target = Item.Identifier, + State = value + }); + StateHasChanged(); + } + } +} diff --git a/Thor.BlazorWAsm/Components/InspectionRow.razor b/Thor.BlazorWAsm/Components/InspectionRow.razor new file mode 100644 index 0000000..ef176a5 --- /dev/null +++ b/Thor.BlazorWAsm/Components/InspectionRow.razor @@ -0,0 +1,160 @@ + + +
+ +@if(Row is not null) +{ + @if(Row.IsTitle) + { + + @Row.Item.OrderId - @Row.Item.Label + + } + else if(Row.Item is ItemVerification verification) + { +
+ + + + + + @if (verification.Force == ItemVerification.VerificationForce.Optionnal) + { + + + + } + else if(verification.Force == ItemVerification.VerificationForce.Recommandation) + { + + + + } + else if(verification.Force == ItemVerification.VerificationForce.Mandatory) + { + + + + } +
+
+ @Row.Item.OrderId +
+
+ @Row.Item.Label +
+
+ @Row.Item.References +
+
+ +

Out: @Row.Answer.CurrentState

+
+ } +} +else +{ + +} +
+ + + +@code { + public Shared.InspectionRow? _row; + + [Parameter] + public Shared.InspectionRow? Row + { + get + { + return _row; + } + + set + { + if (_row != value) + { + _row = value; + if (RowChanged.HasDelegate) + RowChanged.InvokeAsync(_row); + } + } + } + + [Parameter] + public EventCallback RowChanged { get; set; } + + + private Modal detailsModal = default!; + + protected override Task OnInitializedAsync() + { + return base.OnInitializedAsync(); + } + + + public string Class + { + get + { + if (Row is null) + return ""; + + if (!Row.IsTitle) + return "verification"; + + return "title order-" + Row.Order; + } + } + + private async Task ShowDetails() + { + if (Row is null) + return; //TODO: add error message + + Action callback = (Shared.InspectionRow row) => + { + StateHasChanged(); + }; + + var parameters = new Dictionary(); + parameters.Add("Row", Row); + parameters.Add("RowChanged", callback); + + await detailsModal.ShowAsync(title: "Détails", parameters: parameters); + } +} diff --git a/Thor.BlazorWAsm/Components/InspectionRowDetails.razor b/Thor.BlazorWAsm/Components/InspectionRowDetails.razor new file mode 100644 index 0000000..3e018b0 --- /dev/null +++ b/Thor.BlazorWAsm/Components/InspectionRowDetails.razor @@ -0,0 +1,71 @@ +@if (Row is not null && Verification is not null) +{ + @if (Verification.Force == ItemVerification.VerificationForce.Optionnal) + { + + + Cet item est une indication, il ne relève ni d'une recommandation, ni d'une obligation. + + } + else if (Verification.Force == ItemVerification.VerificationForce.Recommandation) + { + + + Cet item relève de la recommandation, il relève des recommandations opposables. + + } + else if (Verification.Force == ItemVerification.VerificationForce.Mandatory) + { + + + Cet item doit obligatoirement être suivi, car il fait l'objet d'un référentiel opposable. + + } + +

@Row.Item.Label

+ +
Statut
+ + + @if(String.IsNullOrEmpty(Row.Item.Description)) + { +
Description
+

@Row.Item.Description

+ } + +
Références
+

@Row.Item.References

+ +
Commentaires
+

+ +
Historique des modifications
+

+} +else +{ + +} + +@code { + [Parameter] + public Shared.InspectionRow? Row { get; set; } + + public Shared.ItemVerification? Verification { get { + if (Row == null) + return null; + + return (Shared.ItemVerification)Row.Item ?? null; + } } + + [Parameter] + public Action RowChanged { get; set; } = (Shared.InspectionRow r) => { }; +} diff --git a/Thor.BlazorWAsm/Components/TemplateTable.razor b/Thor.BlazorWAsm/Components/TemplateTable.razor new file mode 100644 index 0000000..bdbac8b --- /dev/null +++ b/Thor.BlazorWAsm/Components/TemplateTable.razor @@ -0,0 +1,4 @@ +

Liste des modèles

+ +@code { +} diff --git a/Thor.BlazorWAsm/Layout/MainLayout.razor b/Thor.BlazorWAsm/Layout/MainLayout.razor new file mode 100644 index 0000000..025a971 --- /dev/null +++ b/Thor.BlazorWAsm/Layout/MainLayout.razor @@ -0,0 +1,18 @@ +@inherits LayoutComponentBase +
+ + +
+
+ About +
+ +
+ @Body +
+
+
+ + \ No newline at end of file diff --git a/Thor.BlazorWAsm/Layout/MainLayout.razor.css b/Thor.BlazorWAsm/Layout/MainLayout.razor.css new file mode 100644 index 0000000..ecf25e5 --- /dev/null +++ b/Thor.BlazorWAsm/Layout/MainLayout.razor.css @@ -0,0 +1,77 @@ +.page { + position: relative; + display: flex; + flex-direction: column; +} + +main { + flex: 1; +} + +.sidebar { + background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); +} + +.top-row { + background-color: #f7f7f7; + border-bottom: 1px solid #d6d5d5; + justify-content: flex-end; + height: 3.5rem; + display: flex; + align-items: center; +} + + .top-row ::deep a, .top-row ::deep .btn-link { + white-space: nowrap; + margin-left: 1.5rem; + text-decoration: none; + } + + .top-row ::deep a:hover, .top-row ::deep .btn-link:hover { + text-decoration: underline; + } + + .top-row ::deep a:first-child { + overflow: hidden; + text-overflow: ellipsis; + } + +@media (max-width: 640.98px) { + .top-row { + justify-content: space-between; + } + + .top-row ::deep a, .top-row ::deep .btn-link { + margin-left: 0; + } +} + +@media (min-width: 641px) { + .page { + flex-direction: row; + } + + .sidebar { + width: 250px; + height: 100vh; + position: sticky; + top: 0; + } + + .top-row { + position: sticky; + top: 0; + z-index: 1; + } + + .top-row.auth ::deep a:first-child { + flex: 1; + text-align: right; + width: 0; + } + + .top-row, article { + padding-left: 2rem !important; + padding-right: 1.5rem !important; + } +} diff --git a/Thor.BlazorWAsm/Layout/NavMenu.razor b/Thor.BlazorWAsm/Layout/NavMenu.razor new file mode 100644 index 0000000..d8c7134 --- /dev/null +++ b/Thor.BlazorWAsm/Layout/NavMenu.razor @@ -0,0 +1,44 @@ + + + + +@code { + private bool collapseNavMenu = false; + + private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null; + + private void ToggleNavMenu() + { + collapseNavMenu = !collapseNavMenu; + } +} diff --git a/Thor.BlazorWAsm/Layout/NavMenu.razor.css b/Thor.BlazorWAsm/Layout/NavMenu.razor.css new file mode 100644 index 0000000..881d128 --- /dev/null +++ b/Thor.BlazorWAsm/Layout/NavMenu.razor.css @@ -0,0 +1,83 @@ +.navbar-toggler { + background-color: rgba(255, 255, 255, 0.1); +} + +.top-row { + height: 3.5rem; + background-color: rgba(0,0,0,0.4); +} + +.navbar-brand { + font-size: 1.1rem; +} + +.bi { + display: inline-block; + position: relative; + width: 1.25rem; + height: 1.25rem; + margin-right: 0.75rem; + top: -1px; + background-size: cover; +} + +.bi-house-door-fill-nav-menu { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E"); +} + +.bi-plus-square-fill-nav-menu { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E"); +} + +.bi-list-nested-nav-menu { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E"); +} + +.nav-item { + font-size: 0.9rem; + padding-bottom: 0.5rem; +} + + .nav-item:first-of-type { + padding-top: 1rem; + } + + .nav-item:last-of-type { + padding-bottom: 1rem; + } + + .nav-item ::deep a { + color: #d7d7d7; + border-radius: 4px; + height: 3rem; + display: flex; + align-items: center; + line-height: 3rem; + } + +.nav-item ::deep a.active { + background-color: rgba(255,255,255,0.37); + color: white; +} + +.nav-item ::deep a:hover { + background-color: rgba(255,255,255,0.1); + color: white; +} + +@media (min-width: 641px) { + .navbar-toggler { + display: none; + } + + .collapse { + /* Never collapse the sidebar for wide screens */ + display: block; + } + + .nav-scrollable { + /* Allow sidebar to scroll for tall menus */ + height: calc(100vh - 3.5rem); + overflow-y: auto; + } +} diff --git a/Thor.BlazorWAsm/Pages/Home.razor b/Thor.BlazorWAsm/Pages/Home.razor new file mode 100644 index 0000000..9001e0b --- /dev/null +++ b/Thor.BlazorWAsm/Pages/Home.razor @@ -0,0 +1,7 @@ +@page "/" + +Home + +

Hello, world!

+ +Welcome to your new app. diff --git a/Thor.BlazorWAsm/Pages/InspectionFiller.razor b/Thor.BlazorWAsm/Pages/InspectionFiller.razor new file mode 100644 index 0000000..05892de --- /dev/null +++ b/Thor.BlazorWAsm/Pages/InspectionFiller.razor @@ -0,0 +1,40 @@ +@page "/inspection" + +@inject Trip TripManager; + +@if(Inspection is not null) +{ +

@Inspection.Name

+
+ +
+ + @foreach(var item in Inspection.Grid.GetItems()) + { + + } +} +else +{ + +} + +@code +{ + + public Inspection? Inspection = null; + public string? Filter {get; set;} = ""; + + class ViewModel + { + public bool Mandatory { get; set; } = true; + public string Reference { get; set; } = "v0a:PECM.1.1.1.0c"; + public string Label { get; set; } = "La structure DEVRAIT avoir défini un plan d'action pour améliorer la PCEM"; + } + + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + Inspection = TripManager.Current; + } +} diff --git a/Thor.BlazorWAsm/Pages/ModelList.razor b/Thor.BlazorWAsm/Pages/ModelList.razor new file mode 100644 index 0000000..741cb89 --- /dev/null +++ b/Thor.BlazorWAsm/Pages/ModelList.razor @@ -0,0 +1,9 @@ +@page "/models" + +

Liste des modèles

+ + + +@code { + +} diff --git a/Thor.BlazorWAsm/Program.cs b/Thor.BlazorWAsm/Program.cs new file mode 100644 index 0000000..adcf954 --- /dev/null +++ b/Thor.BlazorWAsm/Program.cs @@ -0,0 +1,14 @@ +using Microsoft.AspNetCore.Components.Web; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using Thor.BlazorWAsm; +using Thor.BlazorWAsm.Services; + +var builder = WebAssemblyHostBuilder.CreateDefault(args); +builder.RootComponents.Add("#app"); +builder.RootComponents.Add("head::after"); + +builder.Services.AddBlazorBootstrap(); +builder.Services.AddSingleton(); +builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); + +await builder.Build().RunAsync(); diff --git a/Thor.BlazorWAsm/Properties/launchSettings.json b/Thor.BlazorWAsm/Properties/launchSettings.json new file mode 100644 index 0000000..2362a24 --- /dev/null +++ b/Thor.BlazorWAsm/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:10907", + "sslPort": 44312 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "applicationUrl": "http://localhost:5247", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "applicationUrl": "https://localhost:7162;http://localhost:5247", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Thor.BlazorWAsm/Services/Trip.cs b/Thor.BlazorWAsm/Services/Trip.cs new file mode 100644 index 0000000..cd5a3b5 --- /dev/null +++ b/Thor.BlazorWAsm/Services/Trip.cs @@ -0,0 +1,80 @@ +using Thor.Shared; + +namespace Thor.BlazorWAsm.Services; + +public class Trip +{ + public Inspection? Current = null; + + public Trip() { + Current = GenerateSampleInspection(); + } + + public Inspection GenerateSampleInspection() + { + Inspection inspection = new() + { + Identifier = "inspection-001", + Name = "Inspection OxyFake" + }; + + inspection.Grid.Identifier = "inspection-001/doum001"; + inspection.Grid.Name = "Grille Doum v1"; + inspection.Grid.AddItems( + new ItemTitle + { + Identifier = "inspection-001/doum001/1", + OrderId = "1", + ParentId = null, + Label = "Pilotage" + }, + new ItemVerification + { + Identifier = "inspection-001/doum001/1.0a", + OrderId = "1.0a", + ParentId = "inspection-001/doum001/1", + Label = "La personne responsable de qualité et/ou de la gestion des risques liée à la PECM DEVRAIT être désignée." + }, + new ItemVerification + { + Identifier = "inspection-001/doum001/1.0b", + OrderId = "1.0b", + ParentId = "inspection-001/doum001/1", + Label = "Ceci est une indication.", + Force = ItemVerification.VerificationForce.Optionnal + }, + new ItemVerification + { + Identifier = "inspection-001/doum001/1.0c", + OrderId = "1.0c", + ParentId = "inspection-001/doum001/1", + Label = "Ceci est une recommandation.", + Force = ItemVerification.VerificationForce.Recommandation + }, + new ItemVerification + { + Identifier = "inspection-001/doum001/1.0d", + OrderId = "1.0d", + ParentId = "inspection-001/doum001/1", + Label = "Ceci est une obligation.", + Force = ItemVerification.VerificationForce.Mandatory + }, + new ItemTitle + { + Identifier = "inspection-001/doum001/1.1", + OrderId = "1.1", + ParentId = "inspection-001/doum001/1", + Label = "Évaluation" + }, + new ItemVerification + { + Identifier = "inspection-001/doum001/1.1.0a", + OrderId = "1.1.0a", + ParentId = "inspection-001/doum001/1.1", + Label = "Les évaluations internes ou externes DEVRAIENT portent sur la PECM." + } + ); + + return inspection; + } +} diff --git a/Thor.BlazorWAsm/Thor.BlazorWAsm.csproj b/Thor.BlazorWAsm/Thor.BlazorWAsm.csproj new file mode 100644 index 0000000..620f22b --- /dev/null +++ b/Thor.BlazorWAsm/Thor.BlazorWAsm.csproj @@ -0,0 +1,24 @@ + + + + net8.0 + enable + enable + service-worker-assets.js + + + + + + + + + + + + + + + + + diff --git a/Thor.BlazorWAsm/_Imports.razor b/Thor.BlazorWAsm/_Imports.razor new file mode 100644 index 0000000..66ecd17 --- /dev/null +++ b/Thor.BlazorWAsm/_Imports.razor @@ -0,0 +1,14 @@ +@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 Thor.Shared; +@using Thor.BlazorWAsm +@using Thor.BlazorWAsm.Layout +@using Thor.BlazorWAsm.Components; +@using Thor.BlazorWAsm.Services; +@using BlazorBootstrap; \ No newline at end of file diff --git a/Thor.BlazorWAsm/wwwroot/css/app.css b/Thor.BlazorWAsm/wwwroot/css/app.css new file mode 100644 index 0000000..54a8aa3 --- /dev/null +++ b/Thor.BlazorWAsm/wwwroot/css/app.css @@ -0,0 +1,103 @@ +html, body { + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +h1:focus { + outline: none; +} + +a, .btn-link { + color: #0071c1; +} + +.btn-primary { + color: #fff; + background-color: #1b6ec2; + border-color: #1861ac; +} + +.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { + box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; +} + +.content { + padding-top: 1.1rem; +} + +.valid.modified:not([type=checkbox]) { + outline: 1px solid #26b050; +} + +.invalid { + outline: 1px solid red; +} + +.validation-message { + color: red; +} + +#blazor-error-ui { + background: lightyellow; + bottom: 0; + box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); + 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: relative; + display: block; + width: 8rem; + height: 8rem; + margin: 20vh auto 1rem 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; +} diff --git a/Thor.BlazorWAsm/wwwroot/favicon.png b/Thor.BlazorWAsm/wwwroot/favicon.png new file mode 100644 index 0000000..8422b59 Binary files /dev/null and b/Thor.BlazorWAsm/wwwroot/favicon.png differ diff --git a/Thor.BlazorWAsm/wwwroot/icon-192.png b/Thor.BlazorWAsm/wwwroot/icon-192.png new file mode 100644 index 0000000..166f56d Binary files /dev/null and b/Thor.BlazorWAsm/wwwroot/icon-192.png differ diff --git a/Thor.BlazorWAsm/wwwroot/icon-512.png b/Thor.BlazorWAsm/wwwroot/icon-512.png new file mode 100644 index 0000000..c2dd484 Binary files /dev/null and b/Thor.BlazorWAsm/wwwroot/icon-512.png differ diff --git a/Thor.BlazorWAsm/wwwroot/index.html b/Thor.BlazorWAsm/wwwroot/index.html new file mode 100644 index 0000000..3bb2f47 --- /dev/null +++ b/Thor.BlazorWAsm/wwwroot/index.html @@ -0,0 +1,49 @@ + + + + + + + Thor.BlazorWAsm + + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ An unhandled error has occurred. + Reload + 🗙 +
+ + + + + + + + + + + + + + diff --git a/Thor.BlazorWAsm/wwwroot/manifest.webmanifest b/Thor.BlazorWAsm/wwwroot/manifest.webmanifest new file mode 100644 index 0000000..c096365 --- /dev/null +++ b/Thor.BlazorWAsm/wwwroot/manifest.webmanifest @@ -0,0 +1,22 @@ +{ + "name": "Thor.BlazorWAsm", + "short_name": "Thor.BlazorWAsm", + "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/Thor.BlazorWAsm/wwwroot/sample-data/weather.json b/Thor.BlazorWAsm/wwwroot/sample-data/weather.json new file mode 100644 index 0000000..b745973 --- /dev/null +++ b/Thor.BlazorWAsm/wwwroot/sample-data/weather.json @@ -0,0 +1,27 @@ +[ + { + "date": "2022-01-06", + "temperatureC": 1, + "summary": "Freezing" + }, + { + "date": "2022-01-07", + "temperatureC": 14, + "summary": "Bracing" + }, + { + "date": "2022-01-08", + "temperatureC": -13, + "summary": "Freezing" + }, + { + "date": "2022-01-09", + "temperatureC": -16, + "summary": "Balmy" + }, + { + "date": "2022-01-10", + "temperatureC": -2, + "summary": "Chilly" + } +] diff --git a/Thor.BlazorWAsm/wwwroot/service-worker.js b/Thor.BlazorWAsm/wwwroot/service-worker.js new file mode 100644 index 0000000..fe614da --- /dev/null +++ b/Thor.BlazorWAsm/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). +self.addEventListener('fetch', () => { }); diff --git a/Thor.BlazorWAsm/wwwroot/service-worker.published.js b/Thor.BlazorWAsm/wwwroot/service-worker.published.js new file mode 100644 index 0000000..1f7f543 --- /dev/null +++ b/Thor.BlazorWAsm/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$/ ]; +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); +} diff --git a/Thor.BlazorWAsmNAuth/App.razor b/Thor.BlazorWAsmNAuth/App.razor new file mode 100644 index 0000000..6fd3ed1 --- /dev/null +++ b/Thor.BlazorWAsmNAuth/App.razor @@ -0,0 +1,12 @@ + + + + + + + Not found + +

Sorry, there's nothing at this address.

+
+
+
diff --git a/Thor.BlazorWAsmNAuth/Layout/MainLayout.razor b/Thor.BlazorWAsmNAuth/Layout/MainLayout.razor new file mode 100644 index 0000000..e1a9a75 --- /dev/null +++ b/Thor.BlazorWAsmNAuth/Layout/MainLayout.razor @@ -0,0 +1,3 @@ +@inherits LayoutComponentBase + +@Body diff --git a/Thor.BlazorWAsmNAuth/Pages/Home.razor b/Thor.BlazorWAsmNAuth/Pages/Home.razor new file mode 100644 index 0000000..9001e0b --- /dev/null +++ b/Thor.BlazorWAsmNAuth/Pages/Home.razor @@ -0,0 +1,7 @@ +@page "/" + +Home + +

Hello, world!

+ +Welcome to your new app. diff --git a/Thor.BlazorWAsmNAuth/Program.cs b/Thor.BlazorWAsmNAuth/Program.cs new file mode 100644 index 0000000..b6156e9 --- /dev/null +++ b/Thor.BlazorWAsmNAuth/Program.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Components.Web; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using Thor.BlazorWAsmNAuth; + +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) }); + +await builder.Build().RunAsync(); diff --git a/Thor.BlazorWAsmNAuth/Properties/launchSettings.json b/Thor.BlazorWAsmNAuth/Properties/launchSettings.json new file mode 100644 index 0000000..497cb3d --- /dev/null +++ b/Thor.BlazorWAsmNAuth/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:45564", + "sslPort": 44324 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "applicationUrl": "http://localhost:5099", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "applicationUrl": "https://localhost:7069;http://localhost:5099", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Thor.BlazorWAsmNAuth/Thor.BlazorWAsmNAuth.csproj b/Thor.BlazorWAsmNAuth/Thor.BlazorWAsmNAuth.csproj new file mode 100644 index 0000000..d083a14 --- /dev/null +++ b/Thor.BlazorWAsmNAuth/Thor.BlazorWAsmNAuth.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + enable + enable + service-worker-assets.js + + + + + + + + + + + + diff --git a/Thor.BlazorWAsmNAuth/_Imports.razor b/Thor.BlazorWAsmNAuth/_Imports.razor new file mode 100644 index 0000000..d37d73a --- /dev/null +++ b/Thor.BlazorWAsmNAuth/_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 Thor.BlazorWAsmNAuth +@using Thor.BlazorWAsmNAuth.Layout diff --git a/Thor.BlazorWAsmNAuth/wwwroot/css/app.css b/Thor.BlazorWAsmNAuth/wwwroot/css/app.css new file mode 100644 index 0000000..1cb4e4e --- /dev/null +++ b/Thor.BlazorWAsmNAuth/wwwroot/css/app.css @@ -0,0 +1,77 @@ +.valid.modified:not([type=checkbox]) { + outline: 1px solid #26b050; +} + +.invalid { + outline: 1px solid red; +} + +.validation-message { + color: red; +} + +#blazor-error-ui { + background: lightyellow; + bottom: 0; + box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); + 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: relative; + display: block; + width: 8rem; + height: 8rem; + margin: 20vh auto 1rem 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; +} diff --git a/Thor.BlazorWAsmNAuth/wwwroot/icon-192.png b/Thor.BlazorWAsmNAuth/wwwroot/icon-192.png new file mode 100644 index 0000000..166f56d Binary files /dev/null and b/Thor.BlazorWAsmNAuth/wwwroot/icon-192.png differ diff --git a/Thor.BlazorWAsmNAuth/wwwroot/icon-512.png b/Thor.BlazorWAsmNAuth/wwwroot/icon-512.png new file mode 100644 index 0000000..c2dd484 Binary files /dev/null and b/Thor.BlazorWAsmNAuth/wwwroot/icon-512.png differ diff --git a/Thor.BlazorWAsmNAuth/wwwroot/index.html b/Thor.BlazorWAsmNAuth/wwwroot/index.html new file mode 100644 index 0000000..bf5b3ab --- /dev/null +++ b/Thor.BlazorWAsmNAuth/wwwroot/index.html @@ -0,0 +1,35 @@ + + + + + + + Thor.BlazorWAsmNAuth + + + + + + + + + +
+ + + + +
+
+ +
+ An unhandled error has occurred. + Reload + 🗙 +
+ + + + + diff --git a/Thor.BlazorWAsmNAuth/wwwroot/manifest.webmanifest b/Thor.BlazorWAsmNAuth/wwwroot/manifest.webmanifest new file mode 100644 index 0000000..b39efef --- /dev/null +++ b/Thor.BlazorWAsmNAuth/wwwroot/manifest.webmanifest @@ -0,0 +1,22 @@ +{ + "name": "Thor.BlazorWAsmNAuth", + "short_name": "Thor.BlazorWAsmNAuth", + "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/Thor.BlazorWAsmNAuth/wwwroot/service-worker.js b/Thor.BlazorWAsmNAuth/wwwroot/service-worker.js new file mode 100644 index 0000000..fe614da --- /dev/null +++ b/Thor.BlazorWAsmNAuth/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). +self.addEventListener('fetch', () => { }); diff --git a/Thor.BlazorWAsmNAuth/wwwroot/service-worker.published.js b/Thor.BlazorWAsmNAuth/wwwroot/service-worker.published.js new file mode 100644 index 0000000..1f7f543 --- /dev/null +++ b/Thor.BlazorWAsmNAuth/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$/ ]; +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); +} diff --git a/Thor.Server/Controllers/WeatherForecastController.cs b/Thor.Server/Controllers/WeatherForecastController.cs new file mode 100644 index 0000000..9aede5c --- /dev/null +++ b/Thor.Server/Controllers/WeatherForecastController.cs @@ -0,0 +1,33 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Thor.Server.Controllers +{ + [ApiController] + [Route("[controller]")] + public class WeatherForecastController : ControllerBase + { + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + + public WeatherForecastController(ILogger logger) + { + _logger = logger; + } + + [HttpGet(Name = "GetWeatherForecast")] + public IEnumerable Get() + { + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = Summaries[Random.Shared.Next(Summaries.Length)] + }) + .ToArray(); + } + } +} diff --git a/Thor.Server/Dockerfile b/Thor.Server/Dockerfile new file mode 100644 index 0000000..146a783 --- /dev/null +++ b/Thor.Server/Dockerfile @@ -0,0 +1,25 @@ +#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. + +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +USER app +WORKDIR /app +EXPOSE 8080 +EXPOSE 8081 + +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["Thor.Server/Thor.Server.csproj", "Thor.Server/"] +RUN dotnet restore "./Thor.Server/Thor.Server.csproj" +COPY . . +WORKDIR "/src/Thor.Server" +RUN dotnet build "./Thor.Server.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./Thor.Server.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "Thor.Server.dll"] \ No newline at end of file diff --git a/Thor.Server/Program.cs b/Thor.Server/Program.cs new file mode 100644 index 0000000..48863a6 --- /dev/null +++ b/Thor.Server/Program.cs @@ -0,0 +1,25 @@ +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/Thor.Server/Properties/launchSettings.json b/Thor.Server/Properties/launchSettings.json new file mode 100644 index 0000000..fa5e059 --- /dev/null +++ b/Thor.Server/Properties/launchSettings.json @@ -0,0 +1,52 @@ +{ + "profiles": { + "http": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "http://localhost:5157" + }, + "https": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "https://localhost:7121;http://localhost:5157" + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Container (Dockerfile)": { + "commandName": "Docker", + "launchBrowser": true, + "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger", + "environmentVariables": { + "ASPNETCORE_HTTPS_PORTS": "8081", + "ASPNETCORE_HTTP_PORTS": "8080" + }, + "publishAllPorts": true, + "useSSL": true + } + }, + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:5681", + "sslPort": 44304 + } + } +} \ No newline at end of file diff --git a/Thor.Server/Services/GridManager.cs b/Thor.Server/Services/GridManager.cs new file mode 100644 index 0000000..0944372 --- /dev/null +++ b/Thor.Server/Services/GridManager.cs @@ -0,0 +1,5 @@ +namespace Thor.Server.Services; + +public class GridManager +{ +} diff --git a/Thor.Server/Thor.Server.csproj b/Thor.Server/Thor.Server.csproj new file mode 100644 index 0000000..becd2e8 --- /dev/null +++ b/Thor.Server/Thor.Server.csproj @@ -0,0 +1,16 @@ + + + + net8.0 + enable + enable + a0180049-786d-434a-a24b-031cda255505 + Linux + + + + + + + + diff --git a/Thor.Server/Thor.Server.http b/Thor.Server/Thor.Server.http new file mode 100644 index 0000000..4ab3722 --- /dev/null +++ b/Thor.Server/Thor.Server.http @@ -0,0 +1,6 @@ +@Thor.Server_HostAddress = http://localhost:5157 + +GET {{Thor.Server_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/Thor.Server/WeatherForecast.cs b/Thor.Server/WeatherForecast.cs new file mode 100644 index 0000000..9f41f28 --- /dev/null +++ b/Thor.Server/WeatherForecast.cs @@ -0,0 +1,13 @@ +namespace Thor.Server +{ + public class WeatherForecast + { + public DateOnly Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } + } +} diff --git a/Thor.Server/appsettings.Development.json b/Thor.Server/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/Thor.Server/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Thor.Server/appsettings.json b/Thor.Server/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/Thor.Server/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/Thor.Shared/Comments.cs b/Thor.Shared/Comments.cs new file mode 100644 index 0000000..616a691 --- /dev/null +++ b/Thor.Shared/Comments.cs @@ -0,0 +1,5 @@ +namespace Thor.Shared; + +public class Comments +{ +} \ No newline at end of file diff --git a/Thor.Shared/Entry.cs b/Thor.Shared/Entry.cs new file mode 100644 index 0000000..3789f79 --- /dev/null +++ b/Thor.Shared/Entry.cs @@ -0,0 +1,27 @@ +namespace Thor.Shared; + +public class Entry +{ + public enum EntryType + { + Metadata, + Set, + Comment, + Observation, + Attachement, + Show + } + + public string Identifier { get; set; } + public string Author { get; set; } + public EntryType Type { get; set; } + public string Target { get; set; } + + public DateTime When { get; set; } = DateTime.Now; + + public ItemAnswer.State? State { get; set; } = null; + public string? Content { get; set; } = null; + public string? Observation { get; set; } = null; + public string? Attachment { get; set; } = null; + public bool? Display { get; set; } = null; +} \ No newline at end of file diff --git a/Thor.Shared/Grid.cs b/Thor.Shared/Grid.cs new file mode 100644 index 0000000..198a178 --- /dev/null +++ b/Thor.Shared/Grid.cs @@ -0,0 +1,100 @@ +using System.Collections; + +namespace Thor.Shared; + +public class Grid +{ + public string Identifier { get; set; } + public string Name { get; set; } + + protected HashSet Titles { get; set; } = new(); + protected HashSet Verifications { get; set; } = new(); + + public Item? GetItem(string id) + { + var title = Titles.Where(t => t.Identifier == id).FirstOrDefault(); + if (title is not null) + return title; + + var verification = Verifications.Where(v => v.Identifier == id).FirstOrDefault(); + if(verification is not null) + return verification; + + return null; + } + + public IEnumerable GetItems(IEnumerable ids) + { + var lst = ids.ToHashSet(); + foreach (var i + in GetItems() + .Where( + t => lst.Contains(t.Identifier))) + { + yield return i; + } + } + + public IEnumerable GetItems() + { + return Enumerable.Union(Titles, Verifications).OrderBy(x => x.OrderId); + } + + public IEnumerable GetChildren(IEnumerable parentsid) + { + var lst = parentsid.ToHashSet(); + foreach (var i + in Titles + .Where( + i => lst.Contains(i.ParentId))) + { + yield return i; + } + } + + public IEnumerable GetChildren(string id) + { + List toExplore = new(); + HashSet toReturn = new(); + + toExplore.Add(id); + + while (toExplore.Any()) + { + var items = GetItems(toExplore); + toExplore.Clear(); + foreach (var item in items) + { + if (toReturn.Contains(item)) + continue; + toReturn.Add(item); + + if(item is ItemTitle title) + { + var children = GetChildren(title.Identifier); + foreach (var child in children) + if(!toReturn.Contains(child)) + toExplore.Add(child.Identifier); + } + } + } + + return toReturn; + } + + + public void AddItem(Item item) + { + if (item is ItemTitle title) + Titles.Add(title); + else if (item is ItemVerification verification) + Verifications.Add(verification); + else throw new NotImplementedException(); + } + + public void AddItems(params Item[] items) + { + foreach (var i in items) + AddItem(i); + } +} diff --git a/Thor.Shared/Inspection.cs b/Thor.Shared/Inspection.cs new file mode 100644 index 0000000..52828cf --- /dev/null +++ b/Thor.Shared/Inspection.cs @@ -0,0 +1,22 @@ +namespace Thor.Shared; + +public class Inspection +{ + public string Identifier { get; set; } + public string Name { get; set; } = string.Empty; + public Grid Grid { get; } = new(); + public Register Register { get; set; } = new(); + + public IEnumerable GetRows() + { + var items = Grid.GetItems().OrderBy(r => r.OrderId); + foreach(var item in items) + { + if (item is ItemTitle) + yield return new(item); + else if (item is ItemVerification) + yield return new(item, Register.ForgeItemAnswer(item.Identifier)); + else throw new NotImplementedException(); + } + } +} diff --git a/Thor.Shared/InspectionRow.cs b/Thor.Shared/InspectionRow.cs new file mode 100644 index 0000000..1868a71 --- /dev/null +++ b/Thor.Shared/InspectionRow.cs @@ -0,0 +1,22 @@ +namespace Thor.Shared; + +public class InspectionRow +{ + public InspectionRow(Item item) + { + Item = item; + } + + public InspectionRow(Item item, ItemAnswer answer) + { + Item = item; + Answer = answer; + } + + public Item Item { get; } + public ItemAnswer Answer { get; } = new ItemAnswer(); + + public bool IsTitle => Item is ItemTitle; + public int Order => Item.Order; + +} diff --git a/Thor.Shared/Item.cs b/Thor.Shared/Item.cs new file mode 100644 index 0000000..571e803 --- /dev/null +++ b/Thor.Shared/Item.cs @@ -0,0 +1,32 @@ +namespace Thor.Shared; + +public abstract class Item +{ + public string Identifier { get; set; } + public string? ParentId { get; set; } + public string OrderId { get; set; } + public string Label { get; set; } + public string Description { get; set; } = string.Empty; + public string References { get; set; } = string.Empty; + + public int Order => OrderId.Count(x => x == '.'); +} + +public class ItemTitle + : Item +{ + +} + +public class ItemVerification + : Item +{ + public enum VerificationForce + { + Optionnal, + Recommandation, + Mandatory + } + + public VerificationForce Force { get; set; } = VerificationForce.Mandatory; +} \ No newline at end of file diff --git a/Thor.Shared/ItemAnswer.cs b/Thor.Shared/ItemAnswer.cs new file mode 100644 index 0000000..d01a715 --- /dev/null +++ b/Thor.Shared/ItemAnswer.cs @@ -0,0 +1,20 @@ +namespace Thor.Shared; + + +public class ItemAnswer +{ + public enum State + { + NotAnswed, + Compliant, + PartiallyCompliant, + Improper, + Invalid, + Ignored, + } + + public string TargetId { get; set; } + public State CurrentState { get; set; } = State.NotAnswed; + + List Entries { get; } = new(); +} diff --git a/Thor.Shared/Register.cs b/Thor.Shared/Register.cs new file mode 100644 index 0000000..873f26c --- /dev/null +++ b/Thor.Shared/Register.cs @@ -0,0 +1,72 @@ +namespace Thor.Shared; + +public class Register +{ + List Entries = new(); + + public void Add(Entry entry) + { + Entries.Add(entry); + } + + public void Clear() + { + Entries.Clear(); + } + + public List GetEntriesOrdered(string itemid) + { + var entries = Entries + .Where(e => e.Target == itemid) + .ToList(); + entries.Sort((a, b) => a.When.CompareTo(b.When)); + + return entries; + } + + public ItemAnswer ForgeItemAnswer(string itemid) + { + var entries = GetEntriesOrdered(itemid); + + ItemAnswer ret = new(); + ret.TargetId = itemid; + + // fastpass ☺ + if (entries.Count == 0) + return ret; + + foreach (var entry in entries) + { + if (entry.Type == Entry.EntryType.Set) + { + if (!entry.State.HasValue) + throw new Exception("Entry typed Set send without any State."); + + ret.CurrentState = entry.State.Value; + } + else + throw new NotImplementedException(); + + Entries.Add(entry); + } + + return ret; + } + + public ItemAnswer.State GetItemState(string itemid) + { + var entries = Entries + .Where(e + => e.Target == itemid + && e.Type == Entry.EntryType.Set) + .OrderByDescending(e => e.When) + .ToList(); + + if(entries.Count == 0) + return default(ItemAnswer.State); + + var firstEntryState = entries.First().State; + + return firstEntryState ?? default(ItemAnswer.State); + } +} diff --git a/Thor.Shared/Thor.Shared.csproj b/Thor.Shared/Thor.Shared.csproj new file mode 100644 index 0000000..6c86a82 --- /dev/null +++ b/Thor.Shared/Thor.Shared.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + enable + + + + + + + diff --git a/Thor.sln b/Thor.sln new file mode 100644 index 0000000..0004003 --- /dev/null +++ b/Thor.sln @@ -0,0 +1,43 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.10.34928.147 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Thor.Shared", "Thor.Shared\Thor.Shared.csproj", "{A7B9BF92-1563-4F0F-9284-CD5E39CBD0E1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Thor.BlazorWAsmNAuth", "Thor.BlazorWAsmNAuth\Thor.BlazorWAsmNAuth.csproj", "{81457A63-6837-41C5-B1AB-98DFBA9E5E1C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Thor.BlazorWAsm", "Thor.BlazorWAsm\Thor.BlazorWAsm.csproj", "{19B0C572-6AFA-47AB-B80C-68FA9D8F4A89}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Thor.Server", "Thor.Server\Thor.Server.csproj", "{63E58121-7F8F-4FD3-9F1A-513491BFFEDE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A7B9BF92-1563-4F0F-9284-CD5E39CBD0E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A7B9BF92-1563-4F0F-9284-CD5E39CBD0E1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A7B9BF92-1563-4F0F-9284-CD5E39CBD0E1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A7B9BF92-1563-4F0F-9284-CD5E39CBD0E1}.Release|Any CPU.Build.0 = Release|Any CPU + {81457A63-6837-41C5-B1AB-98DFBA9E5E1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {81457A63-6837-41C5-B1AB-98DFBA9E5E1C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {81457A63-6837-41C5-B1AB-98DFBA9E5E1C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {81457A63-6837-41C5-B1AB-98DFBA9E5E1C}.Release|Any CPU.Build.0 = Release|Any CPU + {19B0C572-6AFA-47AB-B80C-68FA9D8F4A89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {19B0C572-6AFA-47AB-B80C-68FA9D8F4A89}.Debug|Any CPU.Build.0 = Debug|Any CPU + {19B0C572-6AFA-47AB-B80C-68FA9D8F4A89}.Release|Any CPU.ActiveCfg = Release|Any CPU + {19B0C572-6AFA-47AB-B80C-68FA9D8F4A89}.Release|Any CPU.Build.0 = Release|Any CPU + {63E58121-7F8F-4FD3-9F1A-513491BFFEDE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {63E58121-7F8F-4FD3-9F1A-513491BFFEDE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {63E58121-7F8F-4FD3-9F1A-513491BFFEDE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {63E58121-7F8F-4FD3-9F1A-513491BFFEDE}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {4700F381-B9D9-404C-BF22-73C085B02CB3} + EndGlobalSection +EndGlobal