Aggiunto progetto preliminare dotnet10 x GPW Smart (testing wasm/hybrid/server mixed), aggiunto proj shared nuovo

This commit is contained in:
Samuele Locatelli
2026-03-26 15:57:53 +01:00
parent edfb857755
commit 2004452f30
92 changed files with 61260 additions and 55 deletions
@@ -0,0 +1,66 @@
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.JSInterop
@implements IDisposable
@inject IJSRuntime JS
<div style="padding:1rem; border:1px solid #ccc; border-radius:8px; max-width:350px;">
<h4>Execution Context Test</h4>
<p>
<strong>Context:</strong>
<span style="color:@(IsWasm ? "green" : "blue")">
@(IsWasm ? "WASM (Client)" : "Server")
</span>
</p>
<p>
<strong>Time:</strong> @CurrentTime.ToLongTimeString()
</p>
<button class="btn btn-primary btn-sm" @onclick="DoClick">Test Me</button>
<p>@lblOut</p>
</div>
@code {
bool IsWasm;
DateTime CurrentTime = DateTime.Now;
private CancellationTokenSource? _cts;
private string lblOut = "";
protected override void OnInitialized()
{
IsWasm = OperatingSystem.IsBrowser();
_cts = new CancellationTokenSource();
// avvia loop asincrono che funziona sia in server che in WASM
_ = TimeLoopAsync(_cts.Token);
}
private async Task TimeLoopAsync(CancellationToken token)
{
try
{
while (!token.IsCancellationRequested)
{
CurrentTime = DateTime.Now;
await InvokeAsync(StateHasChanged);
await Task.Delay(1000, token);
}
}
catch (TaskCanceledException) { }
}
private Task DoClick()
{
lblOut = $"Clicked | {DateTime.Now:HH:mm:ss}";
// log su console browser se siamo in WASM
if (IsWasm)
_ = JS.InvokeVoidAsync("console.log", $"ExecutionContextTest DoClick at {DateTime.Now}");
return Task.CompletedTask;
}
public void Dispose()
{
_cts?.Cancel();
_cts?.Dispose();
}
}
@@ -0,0 +1,73 @@
@using Microsoft.AspNetCore.Components.Routing
@using GPW.CORE.Services
@inject NavigationManager Nav
@inject RouteModeService RouteModeSvc
<a href="@Href" class="nav-link @CssClass" @onclick="OnClick" @onclick:preventDefault="true">
@ChildContent
</a>
@code {
[Parameter] public string Href { get; set; } = string.Empty;
[Parameter] public RenderFragment? ChildContent { get; set; }
[Parameter] public NavLinkMatch Match { get; set; } = NavLinkMatch.Prefix;
[Parameter] public bool ServerOnly { get; set; } = false;
[Parameter] public bool ClientOnly { get; set; } = false;
// Utility per active class
private string CssClass
{
get
{
var baseClass = ""; // puoi passare classi esterne concatenandole se vuoi
if (IsActive()) baseClass = string.IsNullOrEmpty(baseClass) ? "active" : $"{baseClass} active";
return baseClass;
}
}
protected override void OnInitialized()
{
var mode = ServerOnly ? RouteMode.ServerOnly : (ClientOnly ? RouteMode.ClientOnly : RouteMode.Hybrid);
RouteModeSvc.SetMode(Href, mode);
}
// protected override async Task OnInitializedAsync()
// {
// var mode = ServerOnly ? RouteMode.ServerOnly : (ClientOnly ? RouteMode.ClientOnly : RouteMode.Hybrid);
// // Assicuriamoci di chiamare il service sul Dispatcher UI
// await InvokeAsync(() => RouteModeSvc.SetMode(Href, mode));
// }
private bool IsActive()
{
var absoluteHref = Nav.ToAbsoluteUri(Href).AbsoluteUri;
var current = Nav.Uri;
if (Match == NavLinkMatch.All)
return string.Equals(current.TrimEnd('/'), absoluteHref.TrimEnd('/'), StringComparison.OrdinalIgnoreCase);
return current.StartsWith(absoluteHref, StringComparison.OrdinalIgnoreCase);
}
private void OnClick()
{
if (ServerOnly && OperatingSystem.IsBrowser())
{
Nav.NavigateTo(Href, forceLoad: true);
return;
}
if (ClientOnly && !OperatingSystem.IsBrowser())
{
Nav.NavigateTo(Href, forceLoad: true);
return;
}
// // Se la destinazione è server-only forziamo il reload completo
// if (ServerOnly)
// {
// Nav.NavigateTo(Href, forceLoad: true);
// return;
// }
// Navigazione client-side normale
Nav.NavigateTo(Href);
}
}
@@ -0,0 +1,90 @@
@using GPW.CORE.Services
@using Microsoft.JSInterop
@implements IDisposable
@inject NavigationManager Nav
@inject RouteModeService RouteModeSvc
@inject IJSRuntime JS
<span class="page-mode-indicator" title="@Title">
@if (displayMode == RouteMode.ServerOnly)
{
<span class="text-danger" title="Server">🖥️</span>
}
else if (displayMode == RouteMode.ClientOnly)
{
@if (!isClientActive)
{
<span class="text-warning" title="Client (prerender)">🟡</span>
}
else
{
<span class="text-success" title="WASM">🟢</span>
}
}
else
{
<span class="text-info" title="Hybrid">🔀</span>
}
</span>
@code {
private RouteMode displayMode = RouteMode.Hybrid;
private bool isClientActive;
private string? Title;
private IDisposable? _subscription;
protected override void OnInitialized()
{
Nav.LocationChanged += OnLocationChanged;
// Subscribe passing a callback that marshals to the component dispatcher
_subscription = RouteModeSvc.Subscribe(() => InvokeAsync(StateHasChanged));
UpdateForCurrentUri();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender || true)
{
// JS check: se fallisce siamo in prerender
try
{
await JS.InvokeVoidAsync("console.debug", "client active check");
isClientActive = true;
}
catch
{
isClientActive = false;
}
await InvokeAsync(StateHasChanged);
}
}
private void OnLocationChanged(object? s, Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs e)
{
UpdateForCurrentUri();
// StateHasChanged will be triggered by the subscription callback when SetMode is called,
// but we also want to update immediately for location changes:
_ = InvokeAsync(StateHasChanged);
}
private void UpdateForCurrentUri()
{
var uri = Nav.ToBaseRelativePath(Nav.Uri).Split('?')[0];
var mode = RouteModeSvc.GetModeFor(uri);
displayMode = mode ?? RouteMode.Hybrid;
Title = displayMode switch
{
RouteMode.ServerOnly => "Pagina Server Only",
RouteMode.ClientOnly => "Pagina Client Only",
_ => "Pagina Hybrid"
};
}
public void Dispose()
{
Nav.LocationChanged -= OnLocationChanged;
_subscription?.Dispose();
}
}
+20
View File
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
</PropertyGroup>
<!--<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>-->
<ItemGroup>
<!-- API Blazor necessarie per ComponentBase, InvokeAsync, StateHasChanged -->
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="10.0.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GPW.CORE\GPW.CORE.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoDefaultLaunchSettingsFile>true</NoDefaultLaunchSettingsFile>
<StaticWebAssetProjectMode>Default</StaticWebAssetProjectMode>
<BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\GPW.CORE.Shared\GPW.CORE.Shared.csproj" />
<ProjectReference Include="..\..\GPW.CORE\GPW.CORE.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,23 @@
@page "/counter"
@rendermode InteractiveWebAssembly
@* @rendermode InteractiveAuto *@
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
<ExecutionContextTest></ExecutionContextTest>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
Console.WriteLine($"Curr: {currentCount}");
}
}
@@ -0,0 +1,7 @@
using GPW.CORE.Services;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.Services.AddSingleton<RouteModeService>();
await builder.Build().RunAsync();
@@ -0,0 +1,11 @@
@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 static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using GPW.CORE.Smart10.Client
@using GPW.CORE.Shared
@using GPW.CORE.Shared.Components
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
+6
View File
@@ -0,0 +1,6 @@
<Solution>
<Project Path="../GPW.CORE.Shared/GPW.CORE.Shared.csproj" />
<Project Path="../GPW.CORE/GPW.CORE.csproj" />
<Project Path="GPW.CORE.Smart10.Client/GPW.CORE.Smart10.Client.csproj" />
<Project Path="GPW.CORE.Smart10/GPW.CORE.Smart10.csproj" />
</Solution>
@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<base href="/" />
<ResourcePreloader />
<link rel="stylesheet" href="@Assets["lib/bootstrap/dist/css/bootstrap.min.css"]" />
<link rel="stylesheet" href="@Assets["app.css"]" />
<link rel="stylesheet" href="@Assets["GPW.CORE.Smart10.styles.css"]" />
<ImportMap />
<link rel="icon" type="image/png" href="favicon.png" />
<HeadOutlet />
</head>
<body>
<Routes />
<ReconnectModal />
<script src="@Assets["_framework/blazor.web.js"]"></script>
</body>
</html>
@@ -0,0 +1,23 @@
@inherits LayoutComponentBase
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<main>
<div class="top-row px-4">
<a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a>
</div>
<article class="content px-4">
@Body
</article>
</main>
</div>
<div id="blazor-error-ui" data-nosnippet>
An unhandled error has occurred.
<a href="." class="reload">Reload</a>
<span class="dismiss">🗙</span>
</div>
@@ -0,0 +1,98 @@
.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;
}
}
#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;
}
@@ -0,0 +1,39 @@
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="">GPW <PageModeIndicator /></a>
</div>
</div>
<input type="checkbox" title="Navigation menu" class="navbar-toggler" />
<div class="nav-scrollable" onclick="document.querySelector('.navbar-toggler').click()">
<nav class="nav flex-column">
<div class="nav-item px-3">
@* <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
</NavLink> *@
<HybridNavLink Href="" Match="NavLinkMatch.All">
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
</HybridNavLink>
</div>
<div class="nav-item px-3">
@* <NavLink class="nav-link" href="counter">
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Counter
</NavLink> *@
<HybridNavLink Href="counter" ClientOnly="true">
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Counter
</HybridNavLink>
</div>
<div class="nav-item px-3">
@* <NavLink class="nav-link" href="weather">
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Weather
</NavLink> *@
<HybridNavLink Href="weather" ServerOnly="true">
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Weather
</HybridNavLink>
</div>
</nav>
</div>
@@ -0,0 +1,105 @@
.navbar-toggler {
appearance: none;
cursor: pointer;
width: 3.5rem;
height: 2.5rem;
color: white;
position: absolute;
top: 0.5rem;
right: 1rem;
border: 1px solid rgba(255, 255, 255, 0.1);
background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") no-repeat center/1.75rem rgba(255, 255, 255, 0.1);
}
.navbar-toggler:checked {
background-color: rgba(255, 255, 255, 0.5);
}
.top-row {
min-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 .nav-link {
color: #d7d7d7;
background: none;
border: none;
border-radius: 4px;
height: 3rem;
display: flex;
align-items: center;
line-height: 3rem;
width: 100%;
}
.nav-item ::deep a.active {
background-color: rgba(255,255,255,0.37);
color: white;
}
.nav-item ::deep .nav-link:hover {
background-color: rgba(255,255,255,0.1);
color: white;
}
.nav-scrollable {
display: none;
}
.navbar-toggler:checked ~ .nav-scrollable {
display: block;
}
@media (min-width: 641px) {
.navbar-toggler {
display: none;
}
.nav-scrollable {
/* Never collapse the sidebar for wide screens */
display: block;
/* Allow sidebar to scroll for tall menus */
height: calc(100vh - 3.5rem);
overflow-y: auto;
}
}
@@ -0,0 +1,31 @@
<script type="module" src="@Assets["Components/Layout/ReconnectModal.razor.js"]"></script>
<dialog id="components-reconnect-modal" data-nosnippet>
<div class="components-reconnect-container">
<div class="components-rejoining-animation" aria-hidden="true">
<div></div>
<div></div>
</div>
<p class="components-reconnect-first-attempt-visible">
Rejoining the server...
</p>
<p class="components-reconnect-repeated-attempt-visible">
Rejoin failed... trying again in <span id="components-seconds-to-next-attempt"></span> seconds.
</p>
<p class="components-reconnect-failed-visible">
Failed to rejoin.<br />Please retry or reload the page.
</p>
<button id="components-reconnect-button" class="components-reconnect-failed-visible">
Retry
</button>
<p class="components-pause-visible">
The session has been paused by the server.
</p>
<p class="components-resume-failed-visible">
Failed to resume the session.<br />Please retry or reload the page.
</p>
<button id="components-resume-button" class="components-pause-visible components-resume-failed-visible">
Resume
</button>
</div>
</dialog>
@@ -0,0 +1,157 @@
.components-reconnect-first-attempt-visible,
.components-reconnect-repeated-attempt-visible,
.components-reconnect-failed-visible,
.components-pause-visible,
.components-resume-failed-visible,
.components-rejoining-animation {
display: none;
}
#components-reconnect-modal.components-reconnect-show .components-reconnect-first-attempt-visible,
#components-reconnect-modal.components-reconnect-show .components-rejoining-animation,
#components-reconnect-modal.components-reconnect-paused .components-pause-visible,
#components-reconnect-modal.components-reconnect-resume-failed .components-resume-failed-visible,
#components-reconnect-modal.components-reconnect-retrying,
#components-reconnect-modal.components-reconnect-retrying .components-reconnect-repeated-attempt-visible,
#components-reconnect-modal.components-reconnect-retrying .components-rejoining-animation,
#components-reconnect-modal.components-reconnect-failed,
#components-reconnect-modal.components-reconnect-failed .components-reconnect-failed-visible {
display: block;
}
#components-reconnect-modal {
background-color: white;
width: 20rem;
margin: 20vh auto;
padding: 2rem;
border: 0;
border-radius: 0.5rem;
box-shadow: 0 3px 6px 2px rgba(0, 0, 0, 0.3);
opacity: 0;
transition: display 0.5s allow-discrete, overlay 0.5s allow-discrete;
animation: components-reconnect-modal-fadeOutOpacity 0.5s both;
&[open]
{
animation: components-reconnect-modal-slideUp 1.5s cubic-bezier(.05, .89, .25, 1.02) 0.3s, components-reconnect-modal-fadeInOpacity 0.5s ease-in-out 0.3s;
animation-fill-mode: both;
}
}
#components-reconnect-modal::backdrop {
background-color: rgba(0, 0, 0, 0.4);
animation: components-reconnect-modal-fadeInOpacity 0.5s ease-in-out;
opacity: 1;
}
@keyframes components-reconnect-modal-slideUp {
0% {
transform: translateY(30px) scale(0.95);
}
100% {
transform: translateY(0);
}
}
@keyframes components-reconnect-modal-fadeInOpacity {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes components-reconnect-modal-fadeOutOpacity {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
.components-reconnect-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
}
#components-reconnect-modal p {
margin: 0;
text-align: center;
}
#components-reconnect-modal button {
border: 0;
background-color: #6b9ed2;
color: white;
padding: 4px 24px;
border-radius: 4px;
}
#components-reconnect-modal button:hover {
background-color: #3b6ea2;
}
#components-reconnect-modal button:active {
background-color: #6b9ed2;
}
.components-rejoining-animation {
position: relative;
width: 80px;
height: 80px;
}
.components-rejoining-animation div {
position: absolute;
border: 3px solid #0087ff;
opacity: 1;
border-radius: 50%;
animation: components-rejoining-animation 1.5s cubic-bezier(0, 0.2, 0.8, 1) infinite;
}
.components-rejoining-animation div:nth-child(2) {
animation-delay: -0.5s;
}
@keyframes components-rejoining-animation {
0% {
top: 40px;
left: 40px;
width: 0;
height: 0;
opacity: 0;
}
4.9% {
top: 40px;
left: 40px;
width: 0;
height: 0;
opacity: 0;
}
5% {
top: 40px;
left: 40px;
width: 0;
height: 0;
opacity: 1;
}
100% {
top: 0px;
left: 0px;
width: 80px;
height: 80px;
opacity: 0;
}
}
@@ -0,0 +1,63 @@
// Set up event handlers
const reconnectModal = document.getElementById("components-reconnect-modal");
reconnectModal.addEventListener("components-reconnect-state-changed", handleReconnectStateChanged);
const retryButton = document.getElementById("components-reconnect-button");
retryButton.addEventListener("click", retry);
const resumeButton = document.getElementById("components-resume-button");
resumeButton.addEventListener("click", resume);
function handleReconnectStateChanged(event) {
if (event.detail.state === "show") {
reconnectModal.showModal();
} else if (event.detail.state === "hide") {
reconnectModal.close();
} else if (event.detail.state === "failed") {
document.addEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
} else if (event.detail.state === "rejected") {
location.reload();
}
}
async function retry() {
document.removeEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
try {
// Reconnect will asynchronously return:
// - true to mean success
// - false to mean we reached the server, but it rejected the connection (e.g., unknown circuit ID)
// - exception to mean we didn't reach the server (this can be sync or async)
const successful = await Blazor.reconnect();
if (!successful) {
// We have been able to reach the server, but the circuit is no longer available.
// We'll reload the page so the user can continue using the app as quickly as possible.
const resumeSuccessful = await Blazor.resumeCircuit();
if (!resumeSuccessful) {
location.reload();
} else {
reconnectModal.close();
}
}
} catch (err) {
// We got an exception, server is currently unavailable
document.addEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
}
}
async function resume() {
try {
const successful = await Blazor.resumeCircuit();
if (!successful) {
location.reload();
}
} catch {
reconnectModal.classList.replace("components-reconnect-paused", "components-reconnect-resume-failed");
}
}
async function retryWhenDocumentBecomesVisible() {
if (document.visibilityState === "visible") {
await retry();
}
}
@@ -0,0 +1,36 @@
@page "/Error"
@using System.Diagnostics
<PageTitle>Error</PageTitle>
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
@code{
[CascadingParameter]
private HttpContext? HttpContext { get; set; }
private string? RequestId { get; set; }
private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
protected override void OnInitialized() =>
RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
}
@@ -0,0 +1,18 @@
@page "/"
<PageTitle>Home</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<div class="row">
<div class="col">
<button class="btn btn-success">One</button>
</div>
<div class="col">
<button class="btn btn-success">Two</button>
</div>
<div class="col">
<button class="btn btn-success">Three</button>
</div>
</div>
@@ -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>
@@ -0,0 +1,135 @@
@page "/weather"
@rendermode InteractiveServer
@attribute [StreamRendering]
<PageTitle>Weather</PageTitle>
<h1>Weather</h1>
<p>This component demonstrates showing data.</p>
@if (isLoading)
{
<p><em>pre-loading...</em></p>
}
else
{
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
<ExecutionContextTest></ExecutionContextTest>
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th aria-label="Temperature in Celsius">Temp. (C)</th>
<th aria-label="Temperature in Fahrenheit">Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
}
@code {
private bool isLoading = false;
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
Console.WriteLine($"Curr: {currentCount}");
}
private WeatherForecast[]? forecasts;
[Inject]
private IJSRuntime JS { get; set; } = null!;
// protected override async Task OnInitializedAsync()
// {
// // Simulate asynchronous loading to demonstrate streaming rendering
// isLoading = true;
// await InvokeAsync(StateHasChanged);
// await Task.Delay(400);
// isLoading = false;
// await InvokeAsync(StateHasChanged);
// await Task.Delay(600);
// LoadForecasts();
// }
private bool _interactiveStarted;
private bool _loadingStarted;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender) return;
// Prova una semplice chiamata JS: se fallisce siamo in prerender
try
{
await JS.InvokeVoidAsync("console.debug", "client active check");
_interactiveStarted = true;
}
catch
{
_interactiveStarted = false;
}
if (_interactiveStarted && !_loadingStarted)
{
_loadingStarted = true;
await StartLoadingAsync(); // avvia il lavoro "pesante" solo qui
}
}
private async Task StartLoadingAsync()
{
// esempio: simulazione caricamento
isLoading = true;
StateHasChanged();
await Task.Delay(400);
isLoading = false;
StateHasChanged();
await Task.Delay(600);
// carica i dati veri
LoadForecasts();
StateHasChanged();
}
private void LoadForecasts()
{
var startDate = DateOnly.FromDateTime(DateTime.Now);
var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };
forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
}).ToArray();
}
private class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public string? Summary { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
}
@@ -0,0 +1,6 @@
<Router AppAssembly="typeof(Program).Assembly" AdditionalAssemblies="new[] { typeof(Client._Imports).Assembly }" NotFoundPage="typeof(Pages.NotFound)">
<Found Context="routeData">
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
<FocusOnNavigate RouteData="routeData" Selector="h1" />
</Found>
</Router>
@@ -0,0 +1,15 @@
@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 static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using GPW.CORE.Smart10
@using GPW.CORE.Smart10.Client
@using GPW.CORE.Smart10.Components
@using GPW.CORE.Smart10.Components.Layout
@using GPW.CORE.Shared
@using GPW.CORE.Shared.Components
@using GPW.CORE.Shared.Components.Layout
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\GPW.CORE.Shared\GPW.CORE.Shared.csproj" />
<ProjectReference Include="..\..\GPW.CORE\GPW.CORE.csproj" />
<ProjectReference Include="..\GPW.CORE.Smart10.Client\GPW.CORE.Smart10.Client.csproj" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.4" />
</ItemGroup>
</Project>
@@ -0,0 +1,37 @@
using GPW.CORE.Services;
using GPW.CORE.Smart10.Components;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents()
.AddInteractiveWebAssemblyComponents();
builder.Services.AddSingleton<RouteModeService>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseWebAssemblyDebugging();
}
else
{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true);
app.UseHttpsRedirection();
app.UseAntiforgery();
app.MapStaticAssets();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode()
.AddInteractiveWebAssemblyRenderMode()
.AddAdditionalAssemblies(typeof(GPW.CORE.Smart10.Client._Imports).Assembly);
app.Run();
@@ -0,0 +1,25 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "http://localhost:5215",
"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:7202;http://localhost:5215",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
@@ -0,0 +1,60 @@
html, body {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}
a, .btn-link {
color: #006bb7;
}
.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;
}
h1:focus {
outline: none;
}
.valid.modified:not([type=checkbox]) {
outline: 1px solid #26b050;
}
.invalid {
outline: 1px solid #e50000;
}
.validation-message {
color: #e50000;
}
.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."
}
.darker-border-checkbox.form-check-input {
border-color: #929292;
}
.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;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,597 @@
/*!
* Bootstrap Reboot v5.3.3 (https://getbootstrap.com/)
* Copyright 2011-2024 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
:root,
[data-bs-theme=light] {
--bs-blue: #0d6efd;
--bs-indigo: #6610f2;
--bs-purple: #6f42c1;
--bs-pink: #d63384;
--bs-red: #dc3545;
--bs-orange: #fd7e14;
--bs-yellow: #ffc107;
--bs-green: #198754;
--bs-teal: #20c997;
--bs-cyan: #0dcaf0;
--bs-black: #000;
--bs-white: #fff;
--bs-gray: #6c757d;
--bs-gray-dark: #343a40;
--bs-gray-100: #f8f9fa;
--bs-gray-200: #e9ecef;
--bs-gray-300: #dee2e6;
--bs-gray-400: #ced4da;
--bs-gray-500: #adb5bd;
--bs-gray-600: #6c757d;
--bs-gray-700: #495057;
--bs-gray-800: #343a40;
--bs-gray-900: #212529;
--bs-primary: #0d6efd;
--bs-secondary: #6c757d;
--bs-success: #198754;
--bs-info: #0dcaf0;
--bs-warning: #ffc107;
--bs-danger: #dc3545;
--bs-light: #f8f9fa;
--bs-dark: #212529;
--bs-primary-rgb: 13, 110, 253;
--bs-secondary-rgb: 108, 117, 125;
--bs-success-rgb: 25, 135, 84;
--bs-info-rgb: 13, 202, 240;
--bs-warning-rgb: 255, 193, 7;
--bs-danger-rgb: 220, 53, 69;
--bs-light-rgb: 248, 249, 250;
--bs-dark-rgb: 33, 37, 41;
--bs-primary-text-emphasis: #052c65;
--bs-secondary-text-emphasis: #2b2f32;
--bs-success-text-emphasis: #0a3622;
--bs-info-text-emphasis: #055160;
--bs-warning-text-emphasis: #664d03;
--bs-danger-text-emphasis: #58151c;
--bs-light-text-emphasis: #495057;
--bs-dark-text-emphasis: #495057;
--bs-primary-bg-subtle: #cfe2ff;
--bs-secondary-bg-subtle: #e2e3e5;
--bs-success-bg-subtle: #d1e7dd;
--bs-info-bg-subtle: #cff4fc;
--bs-warning-bg-subtle: #fff3cd;
--bs-danger-bg-subtle: #f8d7da;
--bs-light-bg-subtle: #fcfcfd;
--bs-dark-bg-subtle: #ced4da;
--bs-primary-border-subtle: #9ec5fe;
--bs-secondary-border-subtle: #c4c8cb;
--bs-success-border-subtle: #a3cfbb;
--bs-info-border-subtle: #9eeaf9;
--bs-warning-border-subtle: #ffe69c;
--bs-danger-border-subtle: #f1aeb5;
--bs-light-border-subtle: #e9ecef;
--bs-dark-border-subtle: #adb5bd;
--bs-white-rgb: 255, 255, 255;
--bs-black-rgb: 0, 0, 0;
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
--bs-body-font-family: var(--bs-font-sans-serif);
--bs-body-font-size: 1rem;
--bs-body-font-weight: 400;
--bs-body-line-height: 1.5;
--bs-body-color: #212529;
--bs-body-color-rgb: 33, 37, 41;
--bs-body-bg: #fff;
--bs-body-bg-rgb: 255, 255, 255;
--bs-emphasis-color: #000;
--bs-emphasis-color-rgb: 0, 0, 0;
--bs-secondary-color: rgba(33, 37, 41, 0.75);
--bs-secondary-color-rgb: 33, 37, 41;
--bs-secondary-bg: #e9ecef;
--bs-secondary-bg-rgb: 233, 236, 239;
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
--bs-tertiary-color-rgb: 33, 37, 41;
--bs-tertiary-bg: #f8f9fa;
--bs-tertiary-bg-rgb: 248, 249, 250;
--bs-heading-color: inherit;
--bs-link-color: #0d6efd;
--bs-link-color-rgb: 13, 110, 253;
--bs-link-decoration: underline;
--bs-link-hover-color: #0a58ca;
--bs-link-hover-color-rgb: 10, 88, 202;
--bs-code-color: #d63384;
--bs-highlight-color: #212529;
--bs-highlight-bg: #fff3cd;
--bs-border-width: 1px;
--bs-border-style: solid;
--bs-border-color: #dee2e6;
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
--bs-border-radius: 0.375rem;
--bs-border-radius-sm: 0.25rem;
--bs-border-radius-lg: 0.5rem;
--bs-border-radius-xl: 1rem;
--bs-border-radius-xxl: 2rem;
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
--bs-border-radius-pill: 50rem;
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
--bs-focus-ring-width: 0.25rem;
--bs-focus-ring-opacity: 0.25;
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
--bs-form-valid-color: #198754;
--bs-form-valid-border-color: #198754;
--bs-form-invalid-color: #dc3545;
--bs-form-invalid-border-color: #dc3545;
}
[data-bs-theme=dark] {
color-scheme: dark;
--bs-body-color: #dee2e6;
--bs-body-color-rgb: 222, 226, 230;
--bs-body-bg: #212529;
--bs-body-bg-rgb: 33, 37, 41;
--bs-emphasis-color: #fff;
--bs-emphasis-color-rgb: 255, 255, 255;
--bs-secondary-color: rgba(222, 226, 230, 0.75);
--bs-secondary-color-rgb: 222, 226, 230;
--bs-secondary-bg: #343a40;
--bs-secondary-bg-rgb: 52, 58, 64;
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
--bs-tertiary-color-rgb: 222, 226, 230;
--bs-tertiary-bg: #2b3035;
--bs-tertiary-bg-rgb: 43, 48, 53;
--bs-primary-text-emphasis: #6ea8fe;
--bs-secondary-text-emphasis: #a7acb1;
--bs-success-text-emphasis: #75b798;
--bs-info-text-emphasis: #6edff6;
--bs-warning-text-emphasis: #ffda6a;
--bs-danger-text-emphasis: #ea868f;
--bs-light-text-emphasis: #f8f9fa;
--bs-dark-text-emphasis: #dee2e6;
--bs-primary-bg-subtle: #031633;
--bs-secondary-bg-subtle: #161719;
--bs-success-bg-subtle: #051b11;
--bs-info-bg-subtle: #032830;
--bs-warning-bg-subtle: #332701;
--bs-danger-bg-subtle: #2c0b0e;
--bs-light-bg-subtle: #343a40;
--bs-dark-bg-subtle: #1a1d20;
--bs-primary-border-subtle: #084298;
--bs-secondary-border-subtle: #41464b;
--bs-success-border-subtle: #0f5132;
--bs-info-border-subtle: #087990;
--bs-warning-border-subtle: #997404;
--bs-danger-border-subtle: #842029;
--bs-light-border-subtle: #495057;
--bs-dark-border-subtle: #343a40;
--bs-heading-color: inherit;
--bs-link-color: #6ea8fe;
--bs-link-hover-color: #8bb9fe;
--bs-link-color-rgb: 110, 168, 254;
--bs-link-hover-color-rgb: 139, 185, 254;
--bs-code-color: #e685b5;
--bs-highlight-color: #dee2e6;
--bs-highlight-bg: #664d03;
--bs-border-color: #495057;
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
--bs-form-valid-color: #75b798;
--bs-form-valid-border-color: #75b798;
--bs-form-invalid-color: #ea868f;
--bs-form-invalid-border-color: #ea868f;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: smooth;
}
}
body {
margin: 0;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
hr {
margin: 1rem 0;
color: inherit;
border: 0;
border-top: var(--bs-border-width) solid;
opacity: 0.25;
}
h6, h5, h4, h3, h2, h1 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
color: var(--bs-heading-color);
}
h1 {
font-size: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 {
font-size: 2.5rem;
}
}
h2 {
font-size: calc(1.325rem + 0.9vw);
}
@media (min-width: 1200px) {
h2 {
font-size: 2rem;
}
}
h3 {
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3 {
font-size: 1.75rem;
}
}
h4 {
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
h4 {
font-size: 1.5rem;
}
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul {
padding-left: 2rem;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: 0.5rem;
margin-left: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 0.875em;
}
mark {
padding: 0.1875em;
color: var(--bs-highlight-color);
background-color: var(--bs-highlight-bg);
}
sub,
sup {
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
a {
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
text-decoration: underline;
}
a:hover {
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
}
a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: var(--bs-font-monospace);
font-size: 1em;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
}
pre code {
font-size: inherit;
color: inherit;
word-break: normal;
}
code {
font-size: 0.875em;
color: var(--bs-code-color);
word-wrap: break-word;
}
a > code {
color: inherit;
}
kbd {
padding: 0.1875rem 0.375rem;
font-size: 0.875em;
color: var(--bs-body-bg);
background-color: var(--bs-body-color);
border-radius: 0.25rem;
}
kbd kbd {
padding: 0;
font-size: 1em;
}
figure {
margin: 0 0 1rem;
}
img,
svg {
vertical-align: middle;
}
table {
caption-side: bottom;
border-collapse: collapse;
}
caption {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: var(--bs-secondary-color);
text-align: left;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
tbody,
tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
}
label {
display: inline-block;
}
button {
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
select {
text-transform: none;
}
[role=button] {
cursor: pointer;
}
select {
word-wrap: normal;
}
select:disabled {
opacity: 1;
}
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
display: none !important;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
}
button:not(:disabled),
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
cursor: pointer;
}
::-moz-focus-inner {
padding: 0;
border-style: none;
}
textarea {
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
float: left;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
font-size: calc(1.275rem + 0.3vw);
line-height: inherit;
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: left;
}
::-webkit-datetime-edit-fields-wrapper,
::-webkit-datetime-edit-text,
::-webkit-datetime-edit-minute,
::-webkit-datetime-edit-hour-field,
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-year-field {
padding: 0;
}
::-webkit-inner-spin-button {
height: auto;
}
[type=search] {
-webkit-appearance: textfield;
outline-offset: -2px;
}
/* rtl:raw:
[type="tel"],
[type="url"],
[type="email"],
[type="number"] {
direction: ltr;
}
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-color-swatch-wrapper {
padding: 0;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
::file-selector-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
iframe {
border: 0;
}
summary {
display: list-item;
cursor: pointer;
}
progress {
vertical-align: baseline;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.css.map */
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,594 @@
/*!
* Bootstrap Reboot v5.3.3 (https://getbootstrap.com/)
* Copyright 2011-2024 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
:root,
[data-bs-theme=light] {
--bs-blue: #0d6efd;
--bs-indigo: #6610f2;
--bs-purple: #6f42c1;
--bs-pink: #d63384;
--bs-red: #dc3545;
--bs-orange: #fd7e14;
--bs-yellow: #ffc107;
--bs-green: #198754;
--bs-teal: #20c997;
--bs-cyan: #0dcaf0;
--bs-black: #000;
--bs-white: #fff;
--bs-gray: #6c757d;
--bs-gray-dark: #343a40;
--bs-gray-100: #f8f9fa;
--bs-gray-200: #e9ecef;
--bs-gray-300: #dee2e6;
--bs-gray-400: #ced4da;
--bs-gray-500: #adb5bd;
--bs-gray-600: #6c757d;
--bs-gray-700: #495057;
--bs-gray-800: #343a40;
--bs-gray-900: #212529;
--bs-primary: #0d6efd;
--bs-secondary: #6c757d;
--bs-success: #198754;
--bs-info: #0dcaf0;
--bs-warning: #ffc107;
--bs-danger: #dc3545;
--bs-light: #f8f9fa;
--bs-dark: #212529;
--bs-primary-rgb: 13, 110, 253;
--bs-secondary-rgb: 108, 117, 125;
--bs-success-rgb: 25, 135, 84;
--bs-info-rgb: 13, 202, 240;
--bs-warning-rgb: 255, 193, 7;
--bs-danger-rgb: 220, 53, 69;
--bs-light-rgb: 248, 249, 250;
--bs-dark-rgb: 33, 37, 41;
--bs-primary-text-emphasis: #052c65;
--bs-secondary-text-emphasis: #2b2f32;
--bs-success-text-emphasis: #0a3622;
--bs-info-text-emphasis: #055160;
--bs-warning-text-emphasis: #664d03;
--bs-danger-text-emphasis: #58151c;
--bs-light-text-emphasis: #495057;
--bs-dark-text-emphasis: #495057;
--bs-primary-bg-subtle: #cfe2ff;
--bs-secondary-bg-subtle: #e2e3e5;
--bs-success-bg-subtle: #d1e7dd;
--bs-info-bg-subtle: #cff4fc;
--bs-warning-bg-subtle: #fff3cd;
--bs-danger-bg-subtle: #f8d7da;
--bs-light-bg-subtle: #fcfcfd;
--bs-dark-bg-subtle: #ced4da;
--bs-primary-border-subtle: #9ec5fe;
--bs-secondary-border-subtle: #c4c8cb;
--bs-success-border-subtle: #a3cfbb;
--bs-info-border-subtle: #9eeaf9;
--bs-warning-border-subtle: #ffe69c;
--bs-danger-border-subtle: #f1aeb5;
--bs-light-border-subtle: #e9ecef;
--bs-dark-border-subtle: #adb5bd;
--bs-white-rgb: 255, 255, 255;
--bs-black-rgb: 0, 0, 0;
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
--bs-body-font-family: var(--bs-font-sans-serif);
--bs-body-font-size: 1rem;
--bs-body-font-weight: 400;
--bs-body-line-height: 1.5;
--bs-body-color: #212529;
--bs-body-color-rgb: 33, 37, 41;
--bs-body-bg: #fff;
--bs-body-bg-rgb: 255, 255, 255;
--bs-emphasis-color: #000;
--bs-emphasis-color-rgb: 0, 0, 0;
--bs-secondary-color: rgba(33, 37, 41, 0.75);
--bs-secondary-color-rgb: 33, 37, 41;
--bs-secondary-bg: #e9ecef;
--bs-secondary-bg-rgb: 233, 236, 239;
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
--bs-tertiary-color-rgb: 33, 37, 41;
--bs-tertiary-bg: #f8f9fa;
--bs-tertiary-bg-rgb: 248, 249, 250;
--bs-heading-color: inherit;
--bs-link-color: #0d6efd;
--bs-link-color-rgb: 13, 110, 253;
--bs-link-decoration: underline;
--bs-link-hover-color: #0a58ca;
--bs-link-hover-color-rgb: 10, 88, 202;
--bs-code-color: #d63384;
--bs-highlight-color: #212529;
--bs-highlight-bg: #fff3cd;
--bs-border-width: 1px;
--bs-border-style: solid;
--bs-border-color: #dee2e6;
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
--bs-border-radius: 0.375rem;
--bs-border-radius-sm: 0.25rem;
--bs-border-radius-lg: 0.5rem;
--bs-border-radius-xl: 1rem;
--bs-border-radius-xxl: 2rem;
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
--bs-border-radius-pill: 50rem;
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
--bs-focus-ring-width: 0.25rem;
--bs-focus-ring-opacity: 0.25;
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
--bs-form-valid-color: #198754;
--bs-form-valid-border-color: #198754;
--bs-form-invalid-color: #dc3545;
--bs-form-invalid-border-color: #dc3545;
}
[data-bs-theme=dark] {
color-scheme: dark;
--bs-body-color: #dee2e6;
--bs-body-color-rgb: 222, 226, 230;
--bs-body-bg: #212529;
--bs-body-bg-rgb: 33, 37, 41;
--bs-emphasis-color: #fff;
--bs-emphasis-color-rgb: 255, 255, 255;
--bs-secondary-color: rgba(222, 226, 230, 0.75);
--bs-secondary-color-rgb: 222, 226, 230;
--bs-secondary-bg: #343a40;
--bs-secondary-bg-rgb: 52, 58, 64;
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
--bs-tertiary-color-rgb: 222, 226, 230;
--bs-tertiary-bg: #2b3035;
--bs-tertiary-bg-rgb: 43, 48, 53;
--bs-primary-text-emphasis: #6ea8fe;
--bs-secondary-text-emphasis: #a7acb1;
--bs-success-text-emphasis: #75b798;
--bs-info-text-emphasis: #6edff6;
--bs-warning-text-emphasis: #ffda6a;
--bs-danger-text-emphasis: #ea868f;
--bs-light-text-emphasis: #f8f9fa;
--bs-dark-text-emphasis: #dee2e6;
--bs-primary-bg-subtle: #031633;
--bs-secondary-bg-subtle: #161719;
--bs-success-bg-subtle: #051b11;
--bs-info-bg-subtle: #032830;
--bs-warning-bg-subtle: #332701;
--bs-danger-bg-subtle: #2c0b0e;
--bs-light-bg-subtle: #343a40;
--bs-dark-bg-subtle: #1a1d20;
--bs-primary-border-subtle: #084298;
--bs-secondary-border-subtle: #41464b;
--bs-success-border-subtle: #0f5132;
--bs-info-border-subtle: #087990;
--bs-warning-border-subtle: #997404;
--bs-danger-border-subtle: #842029;
--bs-light-border-subtle: #495057;
--bs-dark-border-subtle: #343a40;
--bs-heading-color: inherit;
--bs-link-color: #6ea8fe;
--bs-link-hover-color: #8bb9fe;
--bs-link-color-rgb: 110, 168, 254;
--bs-link-hover-color-rgb: 139, 185, 254;
--bs-code-color: #e685b5;
--bs-highlight-color: #dee2e6;
--bs-highlight-bg: #664d03;
--bs-border-color: #495057;
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
--bs-form-valid-color: #75b798;
--bs-form-valid-border-color: #75b798;
--bs-form-invalid-color: #ea868f;
--bs-form-invalid-border-color: #ea868f;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: smooth;
}
}
body {
margin: 0;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
hr {
margin: 1rem 0;
color: inherit;
border: 0;
border-top: var(--bs-border-width) solid;
opacity: 0.25;
}
h6, h5, h4, h3, h2, h1 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
color: var(--bs-heading-color);
}
h1 {
font-size: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 {
font-size: 2.5rem;
}
}
h2 {
font-size: calc(1.325rem + 0.9vw);
}
@media (min-width: 1200px) {
h2 {
font-size: 2rem;
}
}
h3 {
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3 {
font-size: 1.75rem;
}
}
h4 {
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
h4 {
font-size: 1.5rem;
}
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul {
padding-right: 2rem;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: 0.5rem;
margin-right: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 0.875em;
}
mark {
padding: 0.1875em;
color: var(--bs-highlight-color);
background-color: var(--bs-highlight-bg);
}
sub,
sup {
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
a {
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
text-decoration: underline;
}
a:hover {
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
}
a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: var(--bs-font-monospace);
font-size: 1em;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
}
pre code {
font-size: inherit;
color: inherit;
word-break: normal;
}
code {
font-size: 0.875em;
color: var(--bs-code-color);
word-wrap: break-word;
}
a > code {
color: inherit;
}
kbd {
padding: 0.1875rem 0.375rem;
font-size: 0.875em;
color: var(--bs-body-bg);
background-color: var(--bs-body-color);
border-radius: 0.25rem;
}
kbd kbd {
padding: 0;
font-size: 1em;
}
figure {
margin: 0 0 1rem;
}
img,
svg {
vertical-align: middle;
}
table {
caption-side: bottom;
border-collapse: collapse;
}
caption {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: var(--bs-secondary-color);
text-align: right;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
tbody,
tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
}
label {
display: inline-block;
}
button {
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
select {
text-transform: none;
}
[role=button] {
cursor: pointer;
}
select {
word-wrap: normal;
}
select:disabled {
opacity: 1;
}
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
display: none !important;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
}
button:not(:disabled),
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
cursor: pointer;
}
::-moz-focus-inner {
padding: 0;
border-style: none;
}
textarea {
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
float: right;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
font-size: calc(1.275rem + 0.3vw);
line-height: inherit;
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: right;
}
::-webkit-datetime-edit-fields-wrapper,
::-webkit-datetime-edit-text,
::-webkit-datetime-edit-minute,
::-webkit-datetime-edit-hour-field,
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-year-field {
padding: 0;
}
::-webkit-inner-spin-button {
height: auto;
}
[type=search] {
-webkit-appearance: textfield;
outline-offset: -2px;
}
[type="tel"],
[type="url"],
[type="email"],
[type="number"] {
direction: ltr;
}
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-color-swatch-wrapper {
padding: 0;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
::file-selector-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
iframe {
border: 0;
}
summary {
display: list-item;
cursor: pointer;
}
progress {
vertical-align: baseline;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.rtl.css.map */
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+17
View File
@@ -0,0 +1,17 @@
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.WebAssembly.Hosting
<Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p>Pagina non trovata</p>
</LayoutView>
</NotFound>
</Router>
<HeadOutlet />
@@ -0,0 +1,33 @@
@rendermode InteractiveWebAssembly
@inject IJSRuntime JS
@inject GPW.CORE.Services.UserStateService UState
@inject ILogger<UserBootstrapClient> Log
@code {
private readonly Guid _instanceId = Guid.NewGuid();
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender) return;
Log.LogInformation("UserBootstrapClient OnAfterRenderAsync instance {Id}", _instanceId);
try
{
var json = await JS.InvokeAsync<string>("localStorage.getItem", "currUser");
if (!string.IsNullOrEmpty(json))
{
var user = System.Text.Json.JsonSerializer.Deserialize<UserDTO>(json);
if (user != null)
{
Log.LogInformation("UserBootstrapClient setting user in instance {Id}", _instanceId);
UState.SetUser(user);
}
}
}
catch (Exception ex)
{
Log.LogError(ex, "UserBootstrapClient error instance {Id}", _instanceId);
}
}
}
@@ -0,0 +1,90 @@
@inherits LayoutComponentBase
@inject GPW.CORE.Services.UserStateService UState
@inject ILogger<MainLayout> Log
<UserBootstrapClient />
<PageTitle>GPW.CORE.Smart (Client)</PageTitle>
<div class="page vh-100">
<main class="vw-100">
<CmpTop IpIsLocal="@isIpLocal" CurrUser="@currUser" EC_ReturnHome="@ReturnHome" EC_ForceReset="@ForceReset" />
<article class="content p-2">
@Body
</article>
@if (dataLoaded)
{
<NavBottom CurrConf="@currConfig" IdxDipendente="@idxDip" />
}
</main>
</div>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
@code {
private readonly Guid _instanceId = Guid.NewGuid();
private bool _subscribed;
private bool isIpLocal = false;
private bool dataLoaded = true;
private UserDTO? currUser;
private ConfigDTO? currConfig;
private int idxDip
{
get => (currUser != null && currUser?.IdxDip != null) ? (int)currUser?.IdxDip : 0;
}
protected override void OnInitialized()
{
Log.LogInformation("MainLayout OnInitialized client instance {Id}", _instanceId);
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender) return;
Log.LogInformation("MainLayout OnAfterRenderAsync firstRender client instance {Id}, CurrentUser {User}",
_instanceId, UState.CurrentUser?.Utente ?? "<null>");
if (!_subscribed)
{
UState.OnChange += UState_OnChange;
_subscribed = true;
Log.LogInformation("MainLayout subscribed to UserState in client instance {Id}", _instanceId);
}
if (UState.CurrentUser != null)
{
currUser = UState.CurrentUser;
await InvokeAsync(StateHasChanged);
}
}
private void UState_OnChange()
{
_ = InvokeAsync(() =>
{
Log.LogInformation("MainLayout UState_OnChange client instance {Id} invoked", _instanceId);
currUser = UState.CurrentUser;
StateHasChanged();
});
}
public void Dispose()
{
if (_subscribed)
{
UState.OnChange -= UState_OnChange;
_subscribed = false;
Log.LogInformation("MainLayout unsubscribed in Dispose client instance {Id}", _instanceId);
}
}
private Task ReturnHome() => Task.CompletedTask;
private Task ForceReset() => Task.CompletedTask;
}
@@ -0,0 +1,65 @@
.page {
position: relative;
display: flex;
flex-direction: column;
background: -webkit-linear-gradient(to bottom, #554e93, #1aafc8);
/* Chrome 10-25, Safari 5.1-6 */
background: linear-gradient(to bottom, #554e93, #1aafc8);
/* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
}
main {
flex: 1;
}
.sidebar {
background-image: linear-gradient(180deg, #052767 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 .btn-link {
white-space: nowrap;
margin-left: 1.5rem;
}
.top-row a:first-child {
overflow: hidden;
text-overflow: ellipsis;
}
@media (max-width: 640.98px) {
.top-row:not(.auth) {
display: none;
}
.top-row.auth {
justify-content: space-between;
}
.top-row a,
.top-row .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,
article {
padding-left: 2rem !important;
padding-right: 1.5rem !important;
}
}
@@ -0,0 +1,72 @@
.page {
position: relative;
display: flex;
flex-direction: column;
background: -webkit-linear-gradient(to bottom, #554e93, #1aafc8); /* Chrome 10-25, Safari 5.1-6 */
background: linear-gradient(to bottom, #554e93, #1aafc8); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
}
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 .btn-link {
white-space: nowrap;
margin-left: 1.5rem;
}
.top-row a:first-child {
overflow: hidden;
text-overflow: ellipsis;
}
@media (max-width: 640.98px) {
.top-row:not(.auth) {
display: none;
}
.top-row.auth {
justify-content: space-between;
}
.top-row a, .top-row .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, article {
padding-left: 2rem !important;
padding-right: 1.5rem !important;
}
}
@@ -0,0 +1 @@
.page{position:relative;display:flex;flex-direction:column;background:-webkit-linear-gradient(to bottom,#554e93,#1aafc8);background:linear-gradient(to bottom,#554e93,#1aafc8);}main{flex:1;}.sidebar{background-image:linear-gradient(180deg,#052767 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 .btn-link{white-space:nowrap;margin-left:1.5rem;}.top-row a:first-child{overflow:hidden;text-overflow:ellipsis;}@media(max-width:640.98px){.top-row:not(.auth){display:none;}.top-row.auth{justify-content:space-between;}.top-row a,.top-row .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,article{padding-left:2rem!important;padding-right:1.5rem!important;}}
@@ -36,8 +36,4 @@
<ProjectReference Include="..\GPW.CORE\GPW.CORE.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Components\Compo\" />
</ItemGroup>
</Project>
+2 -1
View File
@@ -1,4 +1,5 @@
using GPW.CORE.Services;
using GPW.CORE.Smart8.Client;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
@@ -6,7 +7,7 @@ var builder = WebAssemblyHostBuilder.CreateDefault(args);
//CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("it-IT");
//CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo("it-IT");
//builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<App>("#app");
// HttpClient di base (tipico)
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
+2 -1
View File
@@ -8,8 +8,9 @@
@using Microsoft.JSInterop
@using GPW.CORE.Smart8.Client
@using GPW.CORE.Smart8.Client.Components
@* @using GPW.CORE.Smart8.Client.Components.Compo *@
@using GPW.CORE.Smart8.Client.Components.Compo
@using GPW.CORE.Smart8.Client.Components.Layout
@using GPW.CORE.Smart8.Client.Components.Pages
@using GPW.CORE.Smart8.Shared
@using GPW.CORE.Smart8.Shared.Components
@using GPW.CORE.Smart8.Shared.Components.Common
+13
View File
@@ -0,0 +1,13 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>GPW Client</title>
<base href="/" />
</head>
<body>
<div id="app">Loading...</div>
<script src="_framework/blazor.webassembly.js"></script>
</body>
</html>
+52 -46
View File
@@ -1,57 +1,63 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<Version>4.1.2603.2610</Version>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<Version>4.1.2603.2611</Version>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Components\Compo\CmpTop.razor.cs" />
<Compile Remove="Components\Compo\NavBottom.razor.cs" />
</ItemGroup>
<ItemGroup>
<Compile Remove="Components\Compo\CmpTop.razor.cs" />
<Compile Remove="Components\Compo\NavBottom.razor.cs" />
</ItemGroup>
<ItemGroup>
<Content Remove="compilerconfig.json" />
<Content Remove="Components\Compo\CmpTop.razor" />
<Content Remove="Components\Compo\NavBottom.razor" />
</ItemGroup>
<ItemGroup>
<Content Remove="compilerconfig.json" />
<Content Remove="Components\Compo\CmpTop.razor" />
<Content Remove="Components\Compo\NavBottom.razor" />
</ItemGroup>
<ItemGroup>
<None Remove="Components\Compo\CmpTop.razor.css" />
<None Remove="Components\Compo\CmpTop.razor.less" />
<None Remove="Components\Compo\CmpTop.razor.min.css" />
<None Remove="Components\Compo\NavBottom.razor.css" />
<None Remove="Components\Compo\NavBottom.razor.less" />
<None Remove="Components\Compo\NavBottom.razor.min.css" />
</ItemGroup>
<ItemGroup>
<None Remove="Components\Compo\CmpTop.razor.css" />
<None Remove="Components\Compo\CmpTop.razor.less" />
<None Remove="Components\Compo\CmpTop.razor.min.css" />
<None Remove="Components\Compo\NavBottom.razor.css" />
<None Remove="Components\Compo\NavBottom.razor.less" />
<None Remove="Components\Compo\NavBottom.razor.min.css" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GPW.CORE.Data\GPW.CORE.Data.csproj" />
<ProjectReference Include="..\GPW.CORE.Smart8.Client\GPW.CORE.Smart8.Client.csproj" />
<ProjectReference Include="..\GPW.CORE.Smart8.Shared\GPW.CORE.Smart8.Shared.csproj" />
<PackageReference Include="EgwCoreLib.Razor" Version="1.5.2511.312" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.25" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GPW.CORE.Data\GPW.CORE.Data.csproj" />
<ProjectReference Include="..\GPW.CORE.Smart8.Client\GPW.CORE.Smart8.Client.csproj" />
<ProjectReference Include="..\GPW.CORE.Smart8.Shared\GPW.CORE.Smart8.Shared.csproj" />
<PackageReference Include="EgwCoreLib.Razor" Version="1.5.2511.312" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.25" />
</ItemGroup>
<ItemGroup>
<None Include="compilerconfig.json" />
<None Include="Properties\PublishProfiles\IIS-INT.pubxml.user" />
<None Include="Properties\PublishProfiles\IIS03.pubxml.user" />
</ItemGroup>
<ItemGroup>
<None Include="compilerconfig.json" />
<None Include="Properties\PublishProfiles\IIS-INT.pubxml.user" />
<None Include="Properties\PublishProfiles\IIS03.pubxml.user" />
</ItemGroup>
<ItemGroup>
<None Update="logs\.placeholder">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="post-build.ps1">
</None>
</ItemGroup>
<ItemGroup>
<None Update="logs\.placeholder">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="post-build.ps1">
</None>
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="powershell.exe -ExecutionPolicy Unrestricted -NoProfile -NonInteractive -File $(ProjectDir)\post-build.ps1 -ProjectDir $(ProjectDir) -ProjectPath $(ProjectPath)" />
</Target>
<Target Name="CopyClientToServer" AfterTargets="Build">
<ItemGroup>
<ClientDist Include="..\Client\bin\$(Configuration)\net8.0\wwwroot\**\*.*" />
</ItemGroup>
<Copy SourceFiles="@(ClientDist)" DestinationFolder="$(ProjectDir)wwwroot\%(RecursiveDir)" SkipUnchangedFiles="true" />
</Target>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="powershell.exe -ExecutionPolicy Unrestricted -NoProfile -NonInteractive -File $(ProjectDir)\post-build.ps1 -ProjectDir $(ProjectDir) -ProjectPath $(ProjectPath)" />
</Target>
</Project>
+9
View File
@@ -0,0 +1,9 @@
namespace GPW.CORE.Services
{
public enum RouteMode
{
Hybrid,
ServerOnly,
ClientOnly
}
}
+70
View File
@@ -0,0 +1,70 @@
namespace GPW.CORE.Services
{
// RouteModeService.cs
using System;
using System.Collections.Concurrent;
public class RouteModeService
{
private readonly ConcurrentDictionary<string, RouteMode> _map = new();
private readonly ConcurrentDictionary<Guid, Action> _subscribers = new();
public void SetMode(string route, RouteMode mode)
{
if (string.IsNullOrWhiteSpace(route)) return;
_map[Normalize(route)] = mode;
// Notifica tutti i subscriber in modo sincrono; i subscriber devono essere responsabili
// di eseguire InvokeAsync(StateHasChanged) se necessario.
foreach (var kv in _subscribers)
{
try
{
kv.Value?.Invoke();
}
catch
{
// swallow: non vogliamo che un subscriber blocchi gli altri
}
}
}
public RouteMode? GetModeFor(string route)
{
if (string.IsNullOrWhiteSpace(route)) return null;
_map.TryGetValue(Normalize(route), out var mode);
return _map.ContainsKey(Normalize(route)) ? mode : (RouteMode?)null;
}
public IDisposable Subscribe(Action callback)
{
if (callback == null) throw new ArgumentNullException(nameof(callback));
var id = Guid.NewGuid();
_subscribers[id] = callback;
return new Unsubscriber(_subscribers, id);
}
private static string Normalize(string route)
=> route.Trim().TrimStart('/').ToLowerInvariant();
private sealed class Unsubscriber : IDisposable
{
private readonly ConcurrentDictionary<Guid, Action> _dict;
private readonly Guid _id;
private bool _disposed;
public Unsubscriber(ConcurrentDictionary<Guid, Action> dict, Guid id)
{
_dict = dict;
_id = id;
}
public void Dispose()
{
if (_disposed) return;
_dict.TryRemove(_id, out _);
_disposed = true;
}
}
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
<body>
<i>GPW - Gestione Presenze Web</i>
<h4>Versione: 4.1.2603.2610</h4>
<h4>Versione: 4.1.2603.2611</h4>
<br /> Note di rilascio:
<ul>
<li>
+1 -1
View File
@@ -1 +1 @@
4.1.2603.2610
4.1.2603.2611
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<item>
<version>4.1.2603.2610</version>
<version>4.1.2603.2611</version>
<url>http://nexus.steamware.net/repository/SWS/GPW/stable/GPW.Smart.zip</url>
<changelog>http://nexus.steamware.net/repository/SWS/GPW/stable/ChangeLog.html</changelog>
<mandatory>false</mandatory>