Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion demo/RulesEngineEditorServer/Pages/_Host.cshtml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@*
@*
// Copyright (c) Alex Reich.
// Licensed under the CC BY 4.0 License.
*@
Expand Down Expand Up @@ -38,5 +38,6 @@
</div>

<script src="_framework/blazor.server.js"></script>
<script src="culture.js"></script>
</body>
</html>
13 changes: 8 additions & 5 deletions demo/RulesEngineEditorServer/Shared/MainLayout.razor
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
@*
@*
// Copyright (c) Alex Reich.
// Licensed under the CC BY 4.0 License.
*@
@inherits LayoutComponentBase
@using RulesEngineEditor.Resources

<div class="page">
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
Expand All @@ -14,11 +15,13 @@
<button class="navbar-toggler" hidden="@(!collapseNavMenu)" @onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
<a class="navbar-brand" href="">Rules Engine Editor</a>
<a class="navbar-brand" href="">@RulesEngineEditor.Resources.SharedResources.RulesEngineEditorTitle</a>
<div class="float-right">
<a href="/demo">Demo (sample data)</a>
<a href="/demo">@RulesEngineEditor.Resources.SharedResources.DemoSampleData</a>
&nbsp;
<a href="https://github.com/alexreich/RulesEngineEditor" target="_blank">About</a>
<a href="https://github.com/alexreich/RulesEngineEditor" target="_blank">@RulesEngineEditor.Resources.SharedResources.About</a>
&nbsp;
<RulesEngineEditor.Components.CultureSwitcher />
</div>
</div>
</div>
Expand All @@ -37,4 +40,4 @@
{
collapseNavMenu = !collapseNavMenu;
}
}
}
13 changes: 7 additions & 6 deletions demo/RulesEngineEditorServer/Shared/NavMenu.razor
Original file line number Diff line number Diff line change
@@ -1,32 +1,33 @@
@*
@*
// Copyright (c) Alex Reich.
// Licensed under the CC BY 4.0 License.
*@
@using RulesEngineEditor.Resources
<div>
<ul class="nav flex-column">
<li class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home
<span class="oi oi-home" aria-hidden="true"></span> @RulesEngineEditor.Resources.SharedResources.Home
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="demo">
<span class="oi oi-book" aria-hidden="true"></span> Demo
<span class="oi oi-book" aria-hidden="true"></span> @RulesEngineEditor.Resources.SharedResources.Demo
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="demoef">
<span class="oi oi-book" aria-hidden="true"></span> Demo&nbsp; <sup>Enitty Framework</sup>
<span class="oi oi-book" aria-hidden="true"></span> @RulesEngineEditor.Resources.SharedResources.DemoEF
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="new">
<span class="oi oi-aperture" aria-hidden="true"></span> New
<span class="oi oi-aperture" aria-hidden="true"></span> @RulesEngineEditor.Resources.SharedResources.New
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="standalone">
<span class="oi oi-fullscreen-enter" aria-hidden="true"></span> Standalone
<span class="oi oi-fullscreen-enter" aria-hidden="true"></span> @RulesEngineEditor.Resources.SharedResources.Standalone
</NavLink>
</li>
</ul>
Expand Down
11 changes: 11 additions & 0 deletions demo/RulesEngineEditorServer/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
using RulesEngineEditor.Services;
using RulesEngineEditor.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Localization;
using System.Globalization;

namespace RulesEngineEditorServer
{
Expand All @@ -34,6 +36,7 @@ public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddLocalization(options => options.ResourcesPath = "Resources");
services.AddSingleton<WeatherForecastService>();

//services.AddDbContext<RulesEngineEditorDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("RulesEngineEditorDB")));
Expand All @@ -45,6 +48,14 @@ public void ConfigureServices(IServiceCollection services)
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
var supportedCultures = new[] { new CultureInfo("en-US"), new CultureInfo("zh-CN") };
var localizationOptions = new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US"),
SupportedCultures = supportedCultures,
SupportedUICultures = supportedCultures
};
app.UseRequestLocalization(localizationOptions);
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
Expand Down
14 changes: 14 additions & 0 deletions demo/RulesEngineEditorServer/wwwroot/culture.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
window.blazorCulture = {
get: function () {
return window.localStorage['BlazorCulture'];
},
set: function (value) {
window.localStorage['BlazorCulture'] = value;
}
};

window.setCultureCookie = function (culture) {
var cookieValue = 'c=' + culture + '|uic=' + culture;
var secure = (location.protocol === 'https:') ? '; secure' : '';
document.cookie = '.AspNetCore.Culture=' + cookieValue + '; path=/; samesite=lax' + secure;
};
14 changes: 13 additions & 1 deletion demo/RulesEngineEditorWebAssembly/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
using RulesEngineEditor.Services;
using System.Text.Json;
using RulesEngineEditor.Shared;
using Microsoft.JSInterop;
using System.Globalization;

namespace RulesEngineEditorWebAssembly
{
Expand All @@ -27,13 +29,23 @@ public static async Task Main(string[] args)
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

builder.Services.AddRulesEngineEditor();
builder.Services.AddLocalization();

builder.Services.AddScoped<JsonSerializerOptions>(sp =>
{
return RulesEngineEditor.Shared.RulesEngineJsonSourceContext.Default.Options;
});

await builder.Build().RunAsync();
var host = builder.Build();
var js = host.Services.GetRequiredService<IJSRuntime>();
var cultureName = await js.InvokeAsync<string>("blazorCulture.get");
if (!string.IsNullOrWhiteSpace(cultureName))
{
var culture = new CultureInfo(cultureName);
CultureInfo.DefaultThreadCurrentCulture = culture;
CultureInfo.DefaultThreadCurrentUICulture = culture;
}
await host.RunAsync();
}
}
}
13 changes: 8 additions & 5 deletions demo/RulesEngineEditorWebAssembly/Shared/MainLayout.razor
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
@*
@*
// Copyright (c) Alex Reich.
// Licensed under the CC BY 4.0 License.
*@
@inherits LayoutComponentBase
@using RulesEngineEditor.Resources

<div class="page">
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
Expand All @@ -14,11 +15,13 @@
<button class="navbar-toggler" hidden="@(!collapseNavMenu)" @onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
<a class="navbar-brand" href="">Rules Engine Editor</a>
<a class="navbar-brand" href="">@RulesEngineEditor.Resources.SharedResources.RulesEngineEditorTitle</a>
<div class="float-right">
<a href="demo">Demo (sample data)</a>
<a href="demo">@RulesEngineEditor.Resources.SharedResources.DemoSampleData</a>
&nbsp;
<a href="https://github.com/alexreich/RulesEngineEditor" target="_blank">About</a>
<a href="https://github.com/alexreich/RulesEngineEditor" target="_blank">@RulesEngineEditor.Resources.SharedResources.About</a>
&nbsp;
<RulesEngineEditor.Components.CultureSwitcher />
</div>
</div>
</div>
Expand All @@ -37,4 +40,4 @@
{
collapseNavMenu = !collapseNavMenu;
}
}
}
12 changes: 6 additions & 6 deletions demo/RulesEngineEditorWebAssembly/Shared/NavMenu.razor
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
@*
@*
// Copyright (c) Alex Reich.
// Licensed under the CC BY 4.0 License.
*@
@using RulesEngineEditor.Resources
<div>
<ul class="nav flex-column">
<li class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home
<span class="oi oi-home" aria-hidden="true"></span> @RulesEngineEditor.Resources.SharedResources.Home
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="demo">
<span class="oi oi-book" aria-hidden="true"></span> Demo
<span class="oi oi-book" aria-hidden="true"></span> @RulesEngineEditor.Resources.SharedResources.Demo
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="new">
<span class="oi oi-aperture" aria-hidden="true"></span> New
<span class="oi oi-aperture" aria-hidden="true"></span> @RulesEngineEditor.Resources.SharedResources.New
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="standalone">
<span class="oi oi-fullscreen-enter" aria-hidden="true"></span> Standalone
<span class="oi oi-fullscreen-enter" aria-hidden="true"></span> @RulesEngineEditor.Resources.SharedResources.Standalone
</NavLink>
</li>
</ul>
</div>

14 changes: 14 additions & 0 deletions demo/RulesEngineEditorWebAssembly/wwwroot/culture.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
window.blazorCulture = {
get: function () {
return window.localStorage['BlazorCulture'];
},
set: function (value) {
window.localStorage['BlazorCulture'] = value;
}
};

window.setCultureCookie = function (culture) {
var cookieValue = 'c=' + culture + '|uic=' + culture;
var secure = (location.protocol === 'https:') ? '; secure' : '';
document.cookie = '.AspNetCore.Culture=' + cookieValue + '; path=/; samesite=lax' + secure;
};
1 change: 1 addition & 0 deletions demo/RulesEngineEditorWebAssembly/wwwroot/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
<a class="dismiss">🗙</a>
</div>
<script src="_framework/blazor.webassembly.js"></script>
<script src="culture.js"></script>
<script>navigator.serviceWorker.register('service-worker.js');</script>
</body>

Expand Down
18 changes: 18 additions & 0 deletions src/RulesEngineEditor/Components/CultureSwitcher.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
@using Microsoft.JSInterop
@inject IJSRuntime JS
@inject NavigationManager Nav
@code {
private string selectedCulture = System.Globalization.CultureInfo.CurrentUICulture.Name;
private async Task OnChange(ChangeEventArgs e)
{
selectedCulture = e.Value?.ToString() ?? "en-US";
await JS.InvokeVoidAsync("blazorCulture.set", selectedCulture);
await JS.InvokeVoidAsync("setCultureCookie", selectedCulture);
Copy link

Copilot AI Dec 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CultureSwitcher component relies on two JavaScript functions (blazorCulture.set and setCultureCookie) that are defined in culture.js, but there's no error handling if these functions are not available or fail to execute. If culture.js fails to load or the functions are not defined, this will cause a runtime JavaScript exception. Consider adding try-catch around the JS invocations or checking if the functions exist before calling them.

Suggested change
await JS.InvokeVoidAsync("blazorCulture.set", selectedCulture);
await JS.InvokeVoidAsync("setCultureCookie", selectedCulture);
try
{
await JS.InvokeVoidAsync("blazorCulture.set", selectedCulture);
await JS.InvokeVoidAsync("setCultureCookie", selectedCulture);
}
catch (JSException)
{
// If the culture.js script or functions are unavailable, ignore and continue.
}
catch (Exception)
{
// Swallow unexpected errors from JS interop to avoid breaking navigation.
}

Copilot uses AI. Check for mistakes.
Nav.NavigateTo(Nav.Uri, forceLoad: true);
}
}
<select class="form-select form-select-sm" value="@selectedCulture" @onchange="OnChange" style="min-width:120px">
<option value="en-US">English</option>
<option value="zh-CN">中文(简体)</option>

</select>
Copy link

Copilot AI Dec 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The culture switcher select element lacks an accessible label. Screen reader users won't know what this dropdown is for. Add either a visible label, an aria-label attribute, or associate it with a label element using aria-labelledby to describe its purpose (e.g., "Select Language" or "Language Selector").

Copilot uses AI. Check for mistakes.
19 changes: 10 additions & 9 deletions src/RulesEngineEditor/Components/InputEditor.razor
Original file line number Diff line number Diff line change
@@ -1,34 +1,35 @@
@*
@*
// Copyright (c) Alex Reich.
// Licensed under the CC BY 4.0 License.
*@
@using RulesEngineEditor.Models
@inject RulesEngineEditor.Services.WorkflowService WorkflowService
@using RulesEngineEditor.Resources

@if (Input != null)
{
<sp_grid_kvp>
<div>Input Name</div>
<div>@RulesEngineEditor.Resources.SharedResources.InputName</div>
</sp_grid_kvp>
<EditForm EditContext="@EditContext">
<DataAnnotationsValidator />
<ValidationSummary />
<sp_grid_workflow>
<div>
<InputText title="Name" class="form-control" @bind-Value="@Input.InputRuleName" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" />
<InputText title="@RulesEngineEditor.Resources.SharedResources.InputName" class="form-control" @bind-Value="@Input.InputRuleName" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" />
</div>
<div>
<button title="Delete Input" @onclick="@(e => DeleteInput())" class="btn btn-secondary">
<button title="@RulesEngineEditor.Resources.SharedResources.Delete" @onclick="@(e => DeleteInput())" class="btn btn-secondary">
<span class="oi oi-trash"></span>
</button>
</div>
</sp_grid_workflow>
<h5>Parameters</h5>
<button class="reeditor_button" title="Add Input Parameter" @onclick="AddParameter">Add</button>
<button class="reeditor_button @IsPressed(sort, "button_depressed")" @onclick="(() => sort = !sort)">Arrange</button>
<h5>@RulesEngineEditor.Resources.SharedResources.Parameters</h5>
<button class="reeditor_button" title="@RulesEngineEditor.Resources.SharedResources.AddInputParameter" @onclick="AddParameter">@RulesEngineEditor.Resources.SharedResources.Add</button>
<button class="reeditor_button @IsPressed(sort, "button_depressed")" @onclick="(() => sort = !sort)">@RulesEngineEditor.Resources.SharedResources.Arrange</button>
<sp_grid_kvp>
<div>Input Param Name</div>
<div>Value</div>
<div>@RulesEngineEditor.Resources.SharedResources.InputParamName</div>
<div>@RulesEngineEditor.Resources.SharedResources.Value</div>
</sp_grid_kvp>
</EditForm>
@if (sort)
Expand Down
7 changes: 4 additions & 3 deletions src/RulesEngineEditor/Components/InputParamEditor.razor
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
@*
@*
// Copyright (c) Alex Reich.
// Licensed under the CC BY 4.0 License.
*@
@using RulesEngineEditor.Models
@inject RulesEngineEditor.Services.WorkflowService WorkflowService
@using RulesEngineEditor.Resources

<EditForm EditContext="@EditContext">
<DataAnnotationsValidator />
<ValidationSummary />
<sp_grid_inputparameter>
<div>
<InputText title="Input Parameter Name" class="form-control" @bind-Value="@InputParam.Name" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" />
<InputText title="@RulesEngineEditor.Resources.SharedResources.InputParamName" class="form-control" @bind-Value="@InputParam.Name" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" />
</div>
<div>
<InputTextArea title="Value" rows="5" form="Expression" class="form-control" @bind-Value="@InputParam.Value" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" />
<InputTextArea title="@RulesEngineEditor.Resources.SharedResources.Value" rows="5" form="Expression" class="form-control" @bind-Value="@InputParam.Value" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" />
</div>
<div>
<button @onclick="@(e => InputParamDelete.InvokeAsync(InputParam))" class="btn btn-secondary m-1">
Expand Down
Loading