From 38a825b50bf27a9a6d4dc5f9e88ef8fd9c3491d0 Mon Sep 17 00:00:00 2001 From: astankovmi Date: Tue, 30 Jun 2026 16:26:31 +0300 Subject: [PATCH] UDP Visor --- Visor/Visor.sln | 25 +++ Visor/Visor/.mcp.json | 8 + Visor/Visor/App.razor | 47 +++++ .../Visor/Components/Layout/FooterMenu.razor | 4 + Visor/Visor/Components/Layout/SideMenu.razor | 56 ++++++ .../Layout/ThemeColorSelector.razor | 26 +++ .../Layout/ThemeColorSelector.razor.css | 16 ++ Visor/Visor/Components/Layout/TopMenu.razor | 163 ++++++++++++++++++ .../Visor/Components/TodoApp/BaseTodoItems.cs | 68 ++++++++ Visor/Visor/Components/TodoApp/Filter.cs | 9 + Visor/Visor/Components/TodoApp/Todo.cs | 9 + Visor/Visor/Components/TodoApp/TodoItem.razor | 25 +++ .../Visor/Components/TodoApp/TodoItems.razor | 57 ++++++ Visor/Visor/GlobalUsings.cs | 2 + Visor/Visor/Layouts/MainLayout.razor | 69 ++++++++ Visor/Visor/Layouts/MainLayout.razor.cs | 89 ++++++++++ Visor/Visor/Pages/Dashboard.razor | 36 ++++ Visor/Visor/Pages/Error.cshtml | 37 ++++ Visor/Visor/Pages/Error.cshtml.cs | 27 +++ Visor/Visor/Pages/SimpleDataGridPage.razor | 75 ++++++++ Visor/Visor/Pages/SimpleFormPage.razor | 124 +++++++++++++ Visor/Visor/Pages/TodoAppPage.razor | 3 + Visor/Visor/Pages/_Host.cshtml | 8 + Visor/Visor/Pages/_Layout.cshtml | 41 +++++ Visor/Visor/Program.cs | 41 +++++ Visor/Visor/Properties/launchSettings.json | 28 +++ Visor/Visor/Visor.csproj | 29 ++++ Visor/Visor/_Imports.razor | 12 ++ Visor/Visor/appsettings.Development.json | 9 + Visor/Visor/appsettings.json | 9 + Visor/Visor/wwwroot/brand-logo.png | Bin 0 -> 13904 bytes Visor/Visor/wwwroot/css/blazor-ui.css | 28 +++ Visor/Visor/wwwroot/favicon.ico | Bin 0 -> 9062 bytes lsSoft.slnx | 1 + 34 files changed, 1181 insertions(+) create mode 100644 Visor/Visor.sln create mode 100644 Visor/Visor/.mcp.json create mode 100644 Visor/Visor/App.razor create mode 100644 Visor/Visor/Components/Layout/FooterMenu.razor create mode 100644 Visor/Visor/Components/Layout/SideMenu.razor create mode 100644 Visor/Visor/Components/Layout/ThemeColorSelector.razor create mode 100644 Visor/Visor/Components/Layout/ThemeColorSelector.razor.css create mode 100644 Visor/Visor/Components/Layout/TopMenu.razor create mode 100644 Visor/Visor/Components/TodoApp/BaseTodoItems.cs create mode 100644 Visor/Visor/Components/TodoApp/Filter.cs create mode 100644 Visor/Visor/Components/TodoApp/Todo.cs create mode 100644 Visor/Visor/Components/TodoApp/TodoItem.razor create mode 100644 Visor/Visor/Components/TodoApp/TodoItems.razor create mode 100644 Visor/Visor/GlobalUsings.cs create mode 100644 Visor/Visor/Layouts/MainLayout.razor create mode 100644 Visor/Visor/Layouts/MainLayout.razor.cs create mode 100644 Visor/Visor/Pages/Dashboard.razor create mode 100644 Visor/Visor/Pages/Error.cshtml create mode 100644 Visor/Visor/Pages/Error.cshtml.cs create mode 100644 Visor/Visor/Pages/SimpleDataGridPage.razor create mode 100644 Visor/Visor/Pages/SimpleFormPage.razor create mode 100644 Visor/Visor/Pages/TodoAppPage.razor create mode 100644 Visor/Visor/Pages/_Host.cshtml create mode 100644 Visor/Visor/Pages/_Layout.cshtml create mode 100644 Visor/Visor/Program.cs create mode 100644 Visor/Visor/Properties/launchSettings.json create mode 100644 Visor/Visor/Visor.csproj create mode 100644 Visor/Visor/_Imports.razor create mode 100644 Visor/Visor/appsettings.Development.json create mode 100644 Visor/Visor/appsettings.json create mode 100644 Visor/Visor/wwwroot/brand-logo.png create mode 100644 Visor/Visor/wwwroot/css/blazor-ui.css create mode 100644 Visor/Visor/wwwroot/favicon.ico diff --git a/Visor/Visor.sln b/Visor/Visor.sln new file mode 100644 index 0000000..08a9cf6 --- /dev/null +++ b/Visor/Visor.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.4.32916.344 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Visor", "Visor\Visor.csproj", "{A1073A31-9C1C-4B5F-8752-F4397F84E210}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A1073A31-9C1C-4B5F-8752-F4397F84E210}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1073A31-9C1C-4B5F-8752-F4397F84E210}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1073A31-9C1C-4B5F-8752-F4397F84E210}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1073A31-9C1C-4B5F-8752-F4397F84E210}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C59BEA9D-058E-406A-BE53-A3BC184DCF72} + EndGlobalSection +EndGlobal diff --git a/Visor/Visor/.mcp.json b/Visor/Visor/.mcp.json new file mode 100644 index 0000000..681a523 --- /dev/null +++ b/Visor/Visor/.mcp.json @@ -0,0 +1,8 @@ +{ + "servers": { + "blazorise-docs": { + "type": "http", + "url": "https://mcp.blazorise.com/mcp" + } + } +} diff --git a/Visor/Visor/App.razor b/Visor/Visor/App.razor new file mode 100644 index 0000000..033391a --- /dev/null +++ b/Visor/Visor/App.razor @@ -0,0 +1,47 @@ + + + + + + +

Sorry, there's nothing at this address.

+
+
+ + +
+@code { + private Theme theme = new() + { + BarOptions = new() + { + HorizontalHeight = "72px" + }, + ColorOptions = new() + { + Primary = "#0288D1", + Secondary = "#A65529", + Success = "#23C02E", + Info = "#9BD8FE", + Warning = "#F8B86C", + Danger = "#F95741", + Light = "#F0F0F0", + Dark = "#535353", + }, + BackgroundOptions = new() + { + Primary = "#0288D1", + Secondary = "#A65529", + Success = "#23C02E", + Info = "#9BD8FE", + Warning = "#F8B86C", + Danger = "#F95741", + Light = "#F0F0F0", + Dark = "#535353", + }, + InputOptions = new() + { + CheckColor = "#0288D1", + } + }; +} \ No newline at end of file diff --git a/Visor/Visor/Components/Layout/FooterMenu.razor b/Visor/Visor/Components/Layout/FooterMenu.razor new file mode 100644 index 0000000..231121f --- /dev/null +++ b/Visor/Visor/Components/Layout/FooterMenu.razor @@ -0,0 +1,4 @@ +@using System.Reflection + + @($"{Assembly.GetExecutingAssembly().GetName().Name} v{Assembly.GetExecutingAssembly().GetName().Version}") + \ No newline at end of file diff --git a/Visor/Visor/Components/Layout/SideMenu.razor b/Visor/Visor/Components/Layout/SideMenu.razor new file mode 100644 index 0000000..a0fde1c --- /dev/null +++ b/Visor/Visor/Components/Layout/SideMenu.razor @@ -0,0 +1,56 @@ + + + + + + + Visor + + + + + + + + + Dashboard + + + + + + + Pages + + + Simple Form + + + Simple DataGrid + + + + + + + + + + Apps + + + + Todo + + + + + + + +@code { + private bool pagesBarVisible = true; + + RenderFragment customIcon =@; +} \ No newline at end of file diff --git a/Visor/Visor/Components/Layout/ThemeColorSelector.razor b/Visor/Visor/Components/Layout/ThemeColorSelector.razor new file mode 100644 index 0000000..b23b351 --- /dev/null +++ b/Visor/Visor/Components/Layout/ThemeColorSelector.razor @@ -0,0 +1,26 @@ +
+ @foreach ( var color in ThemeColors.Items.Values ) + { +
+ @foreach ( var shade in color.Shades.Values ) + { + var temp = shade.Value; + +
+ } +
+ } +
+@code{ + [Parameter] + public string? Value { get; set; } + + [Parameter] + public EventCallback ValueChanged { get; set; } + + Task OnThemeColorSelect( string value ) + { + Value = value; + return ValueChanged.InvokeAsync(value); + } +} \ No newline at end of file diff --git a/Visor/Visor/Components/Layout/ThemeColorSelector.razor.css b/Visor/Visor/Components/Layout/ThemeColorSelector.razor.css new file mode 100644 index 0000000..0d2ad7b --- /dev/null +++ b/Visor/Visor/Components/Layout/ThemeColorSelector.razor.css @@ -0,0 +1,16 @@ +.demo-theme-color-item { + display: table-cell; + width: 36px; + height: 36px; + vertical-align: top; + text-align: center; + color: white; + cursor: pointer; +} + + .demo-theme-color-item .material-icons { + display: flex; + height: 100%; + align-items: center; + justify-content: center; + } diff --git a/Visor/Visor/Components/Layout/TopMenu.razor b/Visor/Visor/Components/Layout/TopMenu.razor new file mode 100644 index 0000000..6dd6782 --- /dev/null +++ b/Visor/Visor/Components/Layout/TopMenu.razor @@ -0,0 +1,163 @@ +@using Blazorise.Localization + + + + + + + Visor + + + + + + + + Home + + + Documentation + + + + More + + + Quick-Start Guide + + + + Usage + + + + + + + Layout + + + @if ( LayoutType == "fixed-header" ) + { + + } + Fixed Header + + + @if ( LayoutType == "fixed-header-footer-only" ) + { + + } + Fixed Header and Footer only + + + @if ( LayoutType == "sider-with-header-on-top" ) + { + + } + Sider with Header on top + + + + + + + + + + + + + @foreach ( var cultureInfo in LocalizationService!.AvailableCultures ) + { + + @if ( cultureInfo.IsNeutralCulture ) + { + @cultureInfo.EnglishName + } + else + { + @cultureInfo.Parent.EnglishName + } + + } + + + + + + Theme + + + + + Theme enabled + + + + + + + Gradient colors + + + Rounded elements + + + + + + + + + + + + + + + GitHub + + + + +@code { + protected override async Task OnInitializedAsync() + { + await SelectCulture( "en-US" ); + + await base.OnInitializedAsync(); + } + + Task SelectCulture( string name ) + { + LocalizationService!.ChangeLanguage( name ); + + return Task.CompletedTask; + } + + private bool topbarVisible = false; + + Task OnLayoutTypeChecked( string layoutType ) + { + LayoutType = layoutType; + + return LayoutTypeChanged.InvokeAsync( layoutType ); + } + + [Parameter] public EventCallback ThemeEnabledChanged { get; set; } + + [Parameter] public EventCallback ThemeGradientChanged { get; set; } + + [Parameter] public EventCallback ThemeRoundedChanged { get; set; } + + [Parameter] public EventCallback ThemeColorChanged { get; set; } + + [Parameter] public string? LayoutType { get; set; } + + [Parameter] public EventCallback LayoutTypeChanged { get; set; } + + [Inject] protected ITextLocalizerService? LocalizationService { get; set; } + + [CascadingParameter] protected Theme? Theme { get; set; } +} \ No newline at end of file diff --git a/Visor/Visor/Components/TodoApp/BaseTodoItems.cs b/Visor/Visor/Components/TodoApp/BaseTodoItems.cs new file mode 100644 index 0000000..16b2d41 --- /dev/null +++ b/Visor/Visor/Components/TodoApp/BaseTodoItems.cs @@ -0,0 +1,68 @@ +using Microsoft.AspNetCore.Components; + +namespace Visor.Components.TodoApp +{ + public abstract class BaseTodoItems : ComponentBase + { + protected Validations? validations; + + protected string? description; + + protected Filter filter = Filter.All; + + protected List todos = new() + { + new() { Description = "Buy milk" }, + new() { Description = "Call John regarding the meeting" }, + new() { Description = "Walk a dog" }, + }; + + protected IEnumerable Todos + { + get + { + var query = from t in todos select t; + + if (filter == Filter.Active) + query = from q in query where !q.Completed select q; + + if (filter == Filter.Completed) + query = from q in query where q.Completed select q; + + return query; + } + } + + protected void SetFilter(Filter filter) + { + this.filter = filter; + } + + protected void OnCheckAll(bool isChecked) + { + todos.ForEach(x => x.Completed = isChecked); + } + + protected async Task OnAddTodo() + { + if (await validations!.ValidateAll()) + { + todos.Add(new() { Description = description }); + description = null; + + await validations.ClearAll(); + } + } + + protected void OnClearCompleted() + { + todos.RemoveAll(x => x.Completed); + filter = Filter.All; + } + + protected Task OnTodoStatusChanged(bool isChecked) + { + return InvokeAsync(StateHasChanged); + } + } +} diff --git a/Visor/Visor/Components/TodoApp/Filter.cs b/Visor/Visor/Components/TodoApp/Filter.cs new file mode 100644 index 0000000..e4d2542 --- /dev/null +++ b/Visor/Visor/Components/TodoApp/Filter.cs @@ -0,0 +1,9 @@ +namespace Visor.Components.TodoApp +{ + public enum Filter + { + All, + Active, + Completed, + } +} diff --git a/Visor/Visor/Components/TodoApp/Todo.cs b/Visor/Visor/Components/TodoApp/Todo.cs new file mode 100644 index 0000000..8f28939 --- /dev/null +++ b/Visor/Visor/Components/TodoApp/Todo.cs @@ -0,0 +1,9 @@ +namespace Visor.Components.TodoApp +{ + public class Todo + { + public bool Completed { get; set; } + + public string? Description { get; set; } + } +} diff --git a/Visor/Visor/Components/TodoApp/TodoItem.razor b/Visor/Visor/Components/TodoApp/TodoItem.razor new file mode 100644 index 0000000..a1b9356 --- /dev/null +++ b/Visor/Visor/Components/TodoApp/TodoItem.razor @@ -0,0 +1,25 @@ +@if (Todo is not null) +{ + + + + + + + @Todo.Description + + + +} +@code { + Task OnCheckedChanged(bool isChecked) + { + Todo!.Completed = isChecked; + + return StatusChanged?.Invoke(isChecked) ?? Task.CompletedTask; + } + + [Parameter] public Todo? Todo { get; set; } + + [Parameter] public Func? StatusChanged { get; set; } +} \ No newline at end of file diff --git a/Visor/Visor/Components/TodoApp/TodoItems.razor b/Visor/Visor/Components/TodoApp/TodoItems.razor new file mode 100644 index 0000000..934e33e --- /dev/null +++ b/Visor/Visor/Components/TodoApp/TodoItems.razor @@ -0,0 +1,57 @@ +@inherits BaseTodoItems + + + + + + Todo List + + + + + All + + + + + + + + + + + + + + + + + + + + @foreach ( var todo in Todos ) + { + + } + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Visor/Visor/GlobalUsings.cs b/Visor/Visor/GlobalUsings.cs new file mode 100644 index 0000000..be71087 --- /dev/null +++ b/Visor/Visor/GlobalUsings.cs @@ -0,0 +1,2 @@ +global using Blazorise; +global using Blazorise.DataGrid; diff --git a/Visor/Visor/Layouts/MainLayout.razor b/Visor/Visor/Layouts/MainLayout.razor new file mode 100644 index 0000000..b0e5a37 --- /dev/null +++ b/Visor/Visor/Layouts/MainLayout.razor @@ -0,0 +1,69 @@ +@using Visor.Components.Layout + +@inherits LayoutComponentBase + + +@if ( layoutType == "fixed-header" ) +{ + + + + + + + + + + + + @Body + + + +} +else if ( layoutType == "fixed-header-footer-only" ) +{ + + + + + + @Body + + + + + +} +else if ( layoutType == "sider-with-header-on-top" ) +{ + + + + + + + + + + + + + @Body + + + + +} \ No newline at end of file diff --git a/Visor/Visor/Layouts/MainLayout.razor.cs b/Visor/Visor/Layouts/MainLayout.razor.cs new file mode 100644 index 0000000..4b2de2e --- /dev/null +++ b/Visor/Visor/Layouts/MainLayout.razor.cs @@ -0,0 +1,89 @@ +using Blazorise; +using Blazorise.Localization; + +using Microsoft.AspNetCore.Components; + +namespace Visor.Layouts +{ + public partial class MainLayout + { + [Inject] protected ITextLocalizerService? LocalizationService { get; set; } + + [CascadingParameter] protected Theme? Theme { get; set; } + + protected string layoutType = "fixed-header"; + + protected override async Task OnInitializedAsync() + { + await SelectCulture("en-US"); + + await base.OnInitializedAsync(); + } + + private Task SelectCulture(string name) + { + LocalizationService!.ChangeLanguage(name); + + return Task.CompletedTask; + } + + Task OnThemeEnabledChanged(bool value) + { + if (Theme is null) + return Task.CompletedTask; + + Theme.Enabled = value; + + return InvokeAsync(Theme.ThemeHasChanged); + } + + Task OnThemeGradientChanged(bool value) + { + if (Theme is null) + return Task.CompletedTask; + + Theme.IsGradient = value; + + return InvokeAsync(Theme.ThemeHasChanged); + } + + Task OnThemeRoundedChanged(bool value) + { + if (Theme is null) + return Task.CompletedTask; + + Theme.IsRounded = value; + + return InvokeAsync(Theme.ThemeHasChanged); + } + + Task OnThemeColorChanged(string value) + { + if (Theme is null) + return Task.CompletedTask; + + Theme.ColorOptions ??= new(); + + Theme.BackgroundOptions ??= new(); + + Theme.TextColorOptions ??= new(); + + Theme.ColorOptions.Primary = value; + Theme.BackgroundOptions.Primary = value; + Theme.TextColorOptions.Primary = value; + + Theme.InputOptions ??= new(); + + Theme.InputOptions.CheckColor = value; + Theme.InputOptions.SliderColor = value; + + Theme.SpinKitOptions ??= new(); + + Theme.SpinKitOptions.Color = value; + + return InvokeAsync(Theme.ThemeHasChanged); + } + + + } +} \ No newline at end of file diff --git a/Visor/Visor/Pages/Dashboard.razor b/Visor/Visor/Pages/Dashboard.razor new file mode 100644 index 0000000..b735c21 --- /dev/null +++ b/Visor/Visor/Pages/Dashboard.razor @@ -0,0 +1,36 @@ +@page "/" +@inject IVersionProvider VersionProvider +Blazorise + + + Blazorise is a component library built on top of Blazor and CSS frameworks like Bootstrap, FluentUI2, Tailwind, Bulma, Ant Design, and Material. It can be used to build responsive, single-page web applications. + + + + + This is a Blazorise Starting Template allowing you to quickly get started building your project! + + + + The following Blazorise packages have been installed for you: + + + + Blazorise @($"v{VersionProvider.MilestoneVersion}") + The Blazorise Bootstrap5 Provider + The Blazorise FontAwesome Icon Provider + The Blazorise DataGrid extension + + + + However Blazorise has many more extensions at your disposal. + You can find them here. + + + + Please visit the official Blazorise Demo for component examples. + + + Please visit the official Blazorise Documentation to learn more about the available components. + + diff --git a/Visor/Visor/Pages/Error.cshtml b/Visor/Visor/Pages/Error.cshtml new file mode 100644 index 0000000..91c9f5d --- /dev/null +++ b/Visor/Visor/Pages/Error.cshtml @@ -0,0 +1,37 @@ +@page +@model Visor.Pages.ErrorModel + + + + + + + + Error + + + +

Error.

+

An error occurred while processing your request.

+ + @if (Model.ShowRequestId) + { +

+ Request ID: @Model.RequestId +

+ } + +

Development Mode

+

+ Swapping to the Development environment displays detailed information about the error that occurred. +

+

+ The Development environment shouldn't be enabled for deployed applications. + It can result in displaying sensitive information from exceptions to end users. + For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development + and restarting the app. +

+ + + + diff --git a/Visor/Visor/Pages/Error.cshtml.cs b/Visor/Visor/Pages/Error.cshtml.cs new file mode 100644 index 0000000..35f5504 --- /dev/null +++ b/Visor/Visor/Pages/Error.cshtml.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using System.Diagnostics; + +namespace Visor.Pages +{ + [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] + [IgnoreAntiforgeryToken] + public class ErrorModel : PageModel + { + public string? RequestId { get; set; } + + public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); + + private readonly ILogger _logger; + + public ErrorModel(ILogger logger) + { + _logger = logger; + } + + public void OnGet() + { + RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; + } + } +} diff --git a/Visor/Visor/Pages/SimpleDataGridPage.razor b/Visor/Visor/Pages/SimpleDataGridPage.razor new file mode 100644 index 0000000..f6d6082 --- /dev/null +++ b/Visor/Visor/Pages/SimpleDataGridPage.razor @@ -0,0 +1,75 @@ +@page "/simple-datagrid" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@code { + private List data = new() + { + new("Harry Potter and the Sorcerer's Stone", "J.K. Rowling","9780590353427", new(1997,6,26), "Scholastic Press" ), + new("To Kill a Mockingbird", "Harper Lee", "9780061120084", new(1960,7,11), "J.B. Lippincott & Co." ), + new("The Catcher in the Rye", "J.D. Salinger","9780316769488", new(1951,7,16), "Little, Brown and Company" ), + new("The Great Gatsby", "F. Scott Fitzgerald","9780743273565", new(1925,4,10), "Charles Scribner's Sons" ), + new("The Lord of the Rings", "J.R.R. Tolkien", "9780544003415", new(1954,7,29), "George Allen & Unwin"), + new("The Hobbit", "J.R.R. Tolkien", "9780547928227", new(1937,9,21), "George Allen & Unwin"), + new("The Chronicles of Narnia: The Lion, the Witch, and the Wardrobe", "C.S. Lewis", "9780064471073", new(1950,10,16), "Geoffrey Bles"), + new("Animal Farm", "George Orwell", "978451219811", new(1945,8,17), "Secker & Warburg"), + new("The Adventures of Huckleberry Finn", "Mark Twain", "9780142437254", new(1884,12,10), "Chatto & Windus"), + new("The Diary of a Young Girl", "Anne Frank", "9780385473788", new(1947,6,25), "Doubleday"), + new("The Hunger Games", "Suzanne Collins", "9780439023481", new(2008,9,14), "Scholastic Press"), + new("The Maze Runner", "James Dashner", "9780385737965", new(2009,10,6), "Delacorte Press"), + new("The Secret Life of Bees", "Sue Monk Kidd", "9780670034852", new(2002,5,5), "Viking Press"), + new("The Strange Case of Dr. Jekyll and Mr. Hyde", "Robert Louis Stevenson", "9781593080171", new(1886,1,5), "Longmans, Green and Co"), + new("Harry Potter and the Chamber of Secrets", "J.K. Rowling", "9780439064866", new(1998,7,2), "Scholastic Press"), + new("Harry Potter and the Prisoner of Azkaban", "J.K. Rowling", "9780439136365", new(1999,7,8), "Scholastic Press"), + new("Catching Fire", "Suzanne Collins", "9780439023498", new(2009,9,1), "Scholastic Press"), + new("Mockingjay", "Suzanne Collins", "9780439023528", new(2010,8,24), "Scholastic Press"), + new("The Children of Hurin", "J.R.R. Tolkien", "9780544003422", new(2007,4,17), "Houghton Mifflin Harcourt"), + new("Beren and Luthien", "J.R.R. Tolkien", "9780544116813", new(2017,5,23), "Houghton Mifflin Harcourt") + }; + + private record Book( string Title, string Author, string Isbn, DateOnly PublicationDate, string Publisher ); + + +} \ No newline at end of file diff --git a/Visor/Visor/Pages/SimpleFormPage.razor b/Visor/Visor/Pages/SimpleFormPage.razor new file mode 100644 index 0000000..8a607bc --- /dev/null +++ b/Visor/Visor/Pages/SimpleFormPage.razor @@ -0,0 +1,124 @@ +@page "/simple-form" + + + + + Basic Example + + + + + + Email address + + We'll never share your email with anyone else. + + + + + Password + + + + + + Remember me? + + + + + + + + + + Horizontal Form + + + + + + Email address + + + + + + + + Password + + + + + + + + Re Password + + + + + + + + Remember me? + + + + + + + + + + + + +@code { + Validations? validationsBasicExampleRef; + Validations? validationsHorizontalFormRef; + string? password; + + void ValidatePassword( ValidatorEventArgs e ) + { + e.Status = Convert.ToString( e.Value )?.Length >= 6 ? ValidationStatus.Success : ValidationStatus.Error; + } + + void ValidatePassword2( ValidatorEventArgs e ) + { + var password2 = Convert.ToString( e.Value ); + + if ( password2?.Length < 6 ) + { + e.Status = ValidationStatus.Error; + e.ErrorText = "Password must be at least 6 characters long!"; + } + else if ( password2 != password ) + { + e.Status = ValidationStatus.Error; + } + else + { + e.Status = ValidationStatus.Success; + } + } + + async Task SubmitBasicExample() + { + if ( await validationsBasicExampleRef!.ValidateAll() ) + { + await validationsBasicExampleRef.ClearAll(); + } + } + + async Task SubmitHorizontalForm() + { + if ( await validationsHorizontalFormRef!.ValidateAll() ) + { + await validationsHorizontalFormRef.ClearAll(); + } + } + + +} \ No newline at end of file diff --git a/Visor/Visor/Pages/TodoAppPage.razor b/Visor/Visor/Pages/TodoAppPage.razor new file mode 100644 index 0000000..9a9ab4f --- /dev/null +++ b/Visor/Visor/Pages/TodoAppPage.razor @@ -0,0 +1,3 @@ +@using Visor.Components.TodoApp +@page "/apps/todo" + \ No newline at end of file diff --git a/Visor/Visor/Pages/_Host.cshtml b/Visor/Visor/Pages/_Host.cshtml new file mode 100644 index 0000000..0156356 --- /dev/null +++ b/Visor/Visor/Pages/_Host.cshtml @@ -0,0 +1,8 @@ +@page "/" +@namespace Visor.Pages +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@{ + Layout = "_Layout"; +} + + diff --git a/Visor/Visor/Pages/_Layout.cshtml b/Visor/Visor/Pages/_Layout.cshtml new file mode 100644 index 0000000..3aeec43 --- /dev/null +++ b/Visor/Visor/Pages/_Layout.cshtml @@ -0,0 +1,41 @@ +@using Microsoft.AspNetCore.Components.Web +@namespace Visor.Pages +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers + + + + + + + Visor + + + + + + + + + + + + + + + + @RenderBody() + +
+ + An error has occurred. This application may no longer respond until reloaded. + + + An unhandled exception has occurred. See browser dev tools for details. + + Reload + 🗙 +
+ + + + diff --git a/Visor/Visor/Program.cs b/Visor/Visor/Program.cs new file mode 100644 index 0000000..093540f --- /dev/null +++ b/Visor/Visor/Program.cs @@ -0,0 +1,41 @@ +using Blazorise.Bootstrap5; +using Blazorise.Icons.FontAwesome; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +builder.Services.AddRazorPages(); +builder.Services.AddServerSideBlazor(); + +AddBlazorise(builder.Services); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (!app.Environment.IsDevelopment()) +{ + app.UseExceptionHandler("/Error"); + // 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.UseHttpsRedirection(); + +app.UseStaticFiles(); + +app.UseRouting(); + +app.MapBlazorHub(); +app.MapFallbackToPage("/_Host"); + +app.Run(); + + +void AddBlazorise(IServiceCollection services) +{ + services + .AddBlazorise(); + services + .AddBootstrap5Providers() + .AddFontAwesomeIcons(); +} diff --git a/Visor/Visor/Properties/launchSettings.json b/Visor/Visor/Properties/launchSettings.json new file mode 100644 index 0000000..a59427d --- /dev/null +++ b/Visor/Visor/Properties/launchSettings.json @@ -0,0 +1,28 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:61646", + "sslPort": 44332 + } + }, + "profiles": { + "Visor": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:7297;http://localhost:5255", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/Visor/Visor/Visor.csproj b/Visor/Visor/Visor.csproj new file mode 100644 index 0000000..8cab721 --- /dev/null +++ b/Visor/Visor/Visor.csproj @@ -0,0 +1,29 @@ + + + + net8.0 + enable + enable + 2.2.1 + Megabit + Megabit + + This is a Blazorise Template for a Blazor Server Application. + Blazorise is a component library built on top of Blazor and CSS frameworks like Bootstrap, FluentUI2, Tailwind, Bulma and Material. + + Copyright 2018-2026 Megabit + + + + + + + + + + + + + + + diff --git a/Visor/Visor/_Imports.razor b/Visor/Visor/_Imports.razor new file mode 100644 index 0000000..e467b30 --- /dev/null +++ b/Visor/Visor/_Imports.razor @@ -0,0 +1,12 @@ +@using System.Net.Http +@using Microsoft.AspNetCore.Authorization +@using Microsoft.AspNetCore.Components.Authorization +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.JSInterop + +@using Visor +@using Blazorise +@using Blazorise.DataGrid diff --git a/Visor/Visor/appsettings.Development.json b/Visor/Visor/appsettings.Development.json new file mode 100644 index 0000000..770d3e9 --- /dev/null +++ b/Visor/Visor/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "DetailedErrors": true, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Visor/Visor/appsettings.json b/Visor/Visor/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/Visor/Visor/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/Visor/Visor/wwwroot/brand-logo.png b/Visor/Visor/wwwroot/brand-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..374600396ae76e9dff5cc99d0db43adf9a848b48 GIT binary patch literal 13904 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 +