From 2c04ca2b8d6e0b9385e0cd2b2c8beb1faf8aa2fb Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Wed, 4 Dec 2024 12:20:34 +0700 Subject: [PATCH 001/108] import frontend work from setup blazor web branch --- frontend/viewer/src/HomeView.svelte | 3 +- frontend/viewer/src/lib/SyncConfig.svelte | 3 +- .../viewer/src/lib/history/HistoryView.svelte | 37 ++-------- .../src/lib/services/history-service.ts | 67 +++++++++++++++++++ .../src/lib/services/projects-service.ts | 4 -- .../lib/services/service-provider-dotnet.ts | 67 ++++++++++++------- .../src/lib/services/service-provider.ts | 28 +++++--- frontend/viewer/src/main.ts | 2 + 8 files changed, 143 insertions(+), 68 deletions(-) create mode 100644 frontend/viewer/src/lib/services/history-service.ts diff --git a/frontend/viewer/src/HomeView.svelte b/frontend/viewer/src/HomeView.svelte index 59d9c6b06..5eb908bff 100644 --- a/frontend/viewer/src/HomeView.svelte +++ b/frontend/viewer/src/HomeView.svelte @@ -13,8 +13,9 @@ import {Button, Card, type ColumnDef, Table, TextField, tableCell, Icon, ProgressCircle} from 'svelte-ux'; import flexLogo from './lib/assets/flex-logo.png'; import DevContent, {isDev} from './lib/layout/DevContent.svelte'; - import {useProjectsService, type Project, type ServerStatus} from './lib/services/projects-service'; + import {type Project, type ServerStatus} from './lib/services/projects-service'; import {onMount} from 'svelte'; + import {useProjectsService} from './lib/services/service-provider'; const projectsService = useProjectsService(); diff --git a/frontend/viewer/src/lib/SyncConfig.svelte b/frontend/viewer/src/lib/SyncConfig.svelte index b92793273..c53239c4e 100644 --- a/frontend/viewer/src/lib/SyncConfig.svelte +++ b/frontend/viewer/src/lib/SyncConfig.svelte @@ -3,9 +3,10 @@ import {mdiBookArrowUpOutline, mdiBookSyncOutline} from '@mdi/js'; import {Button, SelectField} from 'svelte-ux'; import {writable} from 'svelte/store'; - import {type ServerStatus, useProjectsService} from './services/projects-service'; + import {type ServerStatus} from './services/projects-service'; import {getContext} from 'svelte'; import {AppNotification} from './notifications/notifications'; + import {useProjectsService} from './services/service-provider'; const projectsService = useProjectsService(); let projectName = getContext('project-name'); diff --git a/frontend/viewer/src/lib/history/HistoryView.svelte b/frontend/viewer/src/lib/history/HistoryView.svelte index 2e4d3dfd3..46362b42c 100644 --- a/frontend/viewer/src/lib/history/HistoryView.svelte +++ b/frontend/viewer/src/lib/history/HistoryView.svelte @@ -1,51 +1,28 @@  + + + + From 0a1ea7766b865b6a978127b0f920c218ecbc4797 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Wed, 4 Dec 2024 14:00:16 +0700 Subject: [PATCH 003/108] import fw lite shared blazor changes --- .../FwLite/FwLiteShared/ExampleJsInterop.cs | 38 ++++++ .../FwLite/FwLiteShared/FwLiteShared.csproj | 11 +- .../FwLite/FwLiteShared/FwLiteSharedKernel.cs | 3 + .../FwLiteShared/Layout/EmptyLayout.razor | 3 + .../FwLiteShared/Layout/MainLayout.razor | 23 ++++ .../FwLiteShared/Layout/MainLayout.razor.css | 97 ++++++++++++++++ .../FwLite/FwLiteShared/Layout/NavMenu.razor | 30 +++++ .../FwLiteShared/Layout/NavMenu.razor.css | 105 +++++++++++++++++ .../FwLite/FwLiteShared/Pages/Counter.razor | 18 +++ backend/FwLite/FwLiteShared/Pages/Home.razor | 42 +++++++ .../FwLite/FwLiteShared/Pages/Weather.razor | 40 +++++++ .../Projects/CombinedProjectsService.cs | 2 + .../Reinforced.Typings.settings.xml | 108 ++++++++++++++++++ backend/FwLite/FwLiteShared/Routes.razor | 6 + .../FwLiteShared/Services/FwLiteProvider.cs | 9 ++ .../FwLiteShared/Services/IFormFactor.cs | 7 ++ .../FwLiteShared/TypeGen/FwLiteTypeGenSpec.cs | 45 ++++++++ backend/FwLite/FwLiteShared/_Imports.razor | 8 ++ backend/FwLite/FwLiteShared/tgconfig.json | 6 + .../FwLiteShared/wwwroot/background.png | Bin 0 -> 378 bytes .../wwwroot/bootstrap/bootstrap.min.css | 7 ++ .../wwwroot/bootstrap/bootstrap.min.css.map | 1 + .../FwLiteShared/wwwroot/exampleJsInterop.js | 6 + .../FwLite/FwLiteShared/wwwroot/favicon.png | Bin 0 -> 1148 bytes 24 files changed, 614 insertions(+), 1 deletion(-) create mode 100644 backend/FwLite/FwLiteShared/ExampleJsInterop.cs create mode 100644 backend/FwLite/FwLiteShared/Layout/EmptyLayout.razor create mode 100644 backend/FwLite/FwLiteShared/Layout/MainLayout.razor create mode 100644 backend/FwLite/FwLiteShared/Layout/MainLayout.razor.css create mode 100644 backend/FwLite/FwLiteShared/Layout/NavMenu.razor create mode 100644 backend/FwLite/FwLiteShared/Layout/NavMenu.razor.css create mode 100644 backend/FwLite/FwLiteShared/Pages/Counter.razor create mode 100644 backend/FwLite/FwLiteShared/Pages/Home.razor create mode 100644 backend/FwLite/FwLiteShared/Pages/Weather.razor create mode 100644 backend/FwLite/FwLiteShared/Reinforced.Typings.settings.xml create mode 100644 backend/FwLite/FwLiteShared/Routes.razor create mode 100644 backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs create mode 100644 backend/FwLite/FwLiteShared/Services/IFormFactor.cs create mode 100644 backend/FwLite/FwLiteShared/TypeGen/FwLiteTypeGenSpec.cs create mode 100644 backend/FwLite/FwLiteShared/_Imports.razor create mode 100644 backend/FwLite/FwLiteShared/tgconfig.json create mode 100644 backend/FwLite/FwLiteShared/wwwroot/background.png create mode 100644 backend/FwLite/FwLiteShared/wwwroot/bootstrap/bootstrap.min.css create mode 100644 backend/FwLite/FwLiteShared/wwwroot/bootstrap/bootstrap.min.css.map create mode 100644 backend/FwLite/FwLiteShared/wwwroot/exampleJsInterop.js create mode 100644 backend/FwLite/FwLiteShared/wwwroot/favicon.png diff --git a/backend/FwLite/FwLiteShared/ExampleJsInterop.cs b/backend/FwLite/FwLiteShared/ExampleJsInterop.cs new file mode 100644 index 000000000..09af79ce5 --- /dev/null +++ b/backend/FwLite/FwLiteShared/ExampleJsInterop.cs @@ -0,0 +1,38 @@ +using Microsoft.JSInterop; + +namespace FwLiteShared; + +// This class provides an example of how JavaScript functionality can be wrapped +// in a .NET class for easy consumption. The associated JavaScript module is +// loaded on demand when first needed. +// +// This class can be registered as scoped DI service and then injected into Blazor +// components for use. + +public class ExampleJsInterop : IAsyncDisposable +{ + // private readonly Lazy> moduleTask; + + public ExampleJsInterop(IJSRuntime jsRuntime) + { + // moduleTask = new(() => jsRuntime.InvokeAsync( + // "import", + // "./_content/FwLiteShared/exampleJsInterop.js").AsTask()); + } + + public async ValueTask Prompt(string message) + { + // var module = await moduleTask.Value; + // return await module.InvokeAsync("showPrompt", message); + return "Hello, world!"; + } + + public async ValueTask DisposeAsync() + { + // if (moduleTask.IsValueCreated) + // { + // var module = await moduleTask.Value; + // await module.DisposeAsync(); + // } + } +} diff --git a/backend/FwLite/FwLiteShared/FwLiteShared.csproj b/backend/FwLite/FwLiteShared/FwLiteShared.csproj index 9da6832da..9d8cfa001 100644 --- a/backend/FwLite/FwLiteShared/FwLiteShared.csproj +++ b/backend/FwLite/FwLiteShared/FwLiteShared.csproj @@ -1,21 +1,30 @@ - + + + + + + + + + + diff --git a/backend/FwLite/FwLiteShared/FwLiteSharedKernel.cs b/backend/FwLite/FwLiteShared/FwLiteSharedKernel.cs index fc0ca094f..bc6d75159 100644 --- a/backend/FwLite/FwLiteShared/FwLiteSharedKernel.cs +++ b/backend/FwLite/FwLiteShared/FwLiteSharedKernel.cs @@ -2,6 +2,7 @@ using FwLiteProjectSync; using FwLiteShared.Auth; using FwLiteShared.Projects; +using FwLiteShared.Services; using FwLiteShared.Sync; using LcmCrdt; using Microsoft.Extensions.DependencyInjection; @@ -23,6 +24,8 @@ public static IServiceCollection AddFwLiteShared(this IServiceCollection service services.AddScoped(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(s => s.GetRequiredService()); diff --git a/backend/FwLite/FwLiteShared/Layout/EmptyLayout.razor b/backend/FwLite/FwLiteShared/Layout/EmptyLayout.razor new file mode 100644 index 000000000..e1a9a7567 --- /dev/null +++ b/backend/FwLite/FwLiteShared/Layout/EmptyLayout.razor @@ -0,0 +1,3 @@ +@inherits LayoutComponentBase + +@Body diff --git a/backend/FwLite/FwLiteShared/Layout/MainLayout.razor b/backend/FwLite/FwLiteShared/Layout/MainLayout.razor new file mode 100644 index 000000000..78624f3dd --- /dev/null +++ b/backend/FwLite/FwLiteShared/Layout/MainLayout.razor @@ -0,0 +1,23 @@ +@inherits LayoutComponentBase + +
+ + +
+
+ About +
+ +
+ @Body +
+
+
+ +
+ An unhandled error has occurred. + Reload + 🗙 +
diff --git a/backend/FwLite/FwLiteShared/Layout/MainLayout.razor.css b/backend/FwLite/FwLiteShared/Layout/MainLayout.razor.css new file mode 100644 index 000000000..54b45387f --- /dev/null +++ b/backend/FwLite/FwLiteShared/Layout/MainLayout.razor.css @@ -0,0 +1,97 @@ +.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 { + 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; + } diff --git a/backend/FwLite/FwLiteShared/Layout/NavMenu.razor b/backend/FwLite/FwLiteShared/Layout/NavMenu.razor new file mode 100644 index 000000000..498c5542a --- /dev/null +++ b/backend/FwLite/FwLiteShared/Layout/NavMenu.razor @@ -0,0 +1,30 @@ + + + + + + diff --git a/backend/FwLite/FwLiteShared/Layout/NavMenu.razor.css b/backend/FwLite/FwLiteShared/Layout/NavMenu.razor.css new file mode 100644 index 000000000..26e461e85 --- /dev/null +++ b/backend/FwLite/FwLiteShared/Layout/NavMenu.razor.css @@ -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 { + 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; + } +} diff --git a/backend/FwLite/FwLiteShared/Pages/Counter.razor b/backend/FwLite/FwLiteShared/Pages/Counter.razor new file mode 100644 index 000000000..ef23cb316 --- /dev/null +++ b/backend/FwLite/FwLiteShared/Pages/Counter.razor @@ -0,0 +1,18 @@ +@page "/counter" + +Counter + +

Counter

+ +

Current count: @currentCount

+ + + +@code { + private int currentCount = 0; + + private void IncrementCount() + { + currentCount++; + } +} diff --git a/backend/FwLite/FwLiteShared/Pages/Home.razor b/backend/FwLite/FwLiteShared/Pages/Home.razor new file mode 100644 index 000000000..920022b10 --- /dev/null +++ b/backend/FwLite/FwLiteShared/Pages/Home.razor @@ -0,0 +1,42 @@ +@page "/" +@using FwLiteShared.Layout +@using FwLiteShared.Services +@inject IJSRuntime JS +@inject FwLiteProvider FwLiteProvider +@layout EmptyLayout; + + @* *@ + + + + +
+ +
+ +@code { + private IJSObjectReference? module; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + var projectService = DotNetObjectReference.Create(FwLiteProvider.ProjectService); + await JS.InvokeVoidAsync("setOverrideService", "ProjectsService", projectService); + module = await JS.InvokeAsync("import", + "/" + Assets["_content/FwLiteShared/viewer/index.js"]); + } + } + +} diff --git a/backend/FwLite/FwLiteShared/Pages/Weather.razor b/backend/FwLite/FwLiteShared/Pages/Weather.razor new file mode 100644 index 000000000..4bf8d259a --- /dev/null +++ b/backend/FwLite/FwLiteShared/Pages/Weather.razor @@ -0,0 +1,40 @@ +@page "/weather" + +Files + +

Files

+ +@if (files == null) +{ +

Loading...

+} +else +{ + + + + + + + + @foreach (var file in files) + { + + + + } + +
File name
@file.Name
+} + +@code { + private FileSystemInfo[]? files; + + protected override async Task OnInitializedAsync() + { + // Simulate asynchronous loading to demonstrate a loading indicator + await Task.Delay(500); + + files = new DirectoryInfo("/").GetFileSystemInfos(); + } +} diff --git a/backend/FwLite/FwLiteShared/Projects/CombinedProjectsService.cs b/backend/FwLite/FwLiteShared/Projects/CombinedProjectsService.cs index f53b28070..06284f58e 100644 --- a/backend/FwLite/FwLiteShared/Projects/CombinedProjectsService.cs +++ b/backend/FwLite/FwLiteShared/Projects/CombinedProjectsService.cs @@ -3,6 +3,7 @@ using FwLiteShared.Sync; using LcmCrdt; using Microsoft.Extensions.DependencyInjection; +using Microsoft.JSInterop; namespace FwLiteShared.Projects; @@ -39,6 +40,7 @@ public async Task RemoteProjects() return serverProjects; } + [JSInvokable] public async Task> LocalProjects() { var crdtProjects = await crdtProjectsService.ListProjects(); diff --git a/backend/FwLite/FwLiteShared/Reinforced.Typings.settings.xml b/backend/FwLite/FwLiteShared/Reinforced.Typings.settings.xml new file mode 100644 index 000000000..78ae931ef --- /dev/null +++ b/backend/FwLite/FwLiteShared/Reinforced.Typings.settings.xml @@ -0,0 +1,108 @@ + + + + + + + + $(ProjectDir)..\..\..\frontend\viewer\src\lib\generated-types\generated.ts + + + FwLiteShared.TypeGen.ReinforcedFwLiteTypingConfig.Configure + + + false + + + $(ProjectDir)Scripts\MyApplication + + + + false + + + false + + + + + + RTW0013;RTW0014 + + + + diff --git a/backend/FwLite/FwLiteShared/Routes.razor b/backend/FwLite/FwLiteShared/Routes.razor new file mode 100644 index 000000000..bfe6497c0 --- /dev/null +++ b/backend/FwLite/FwLiteShared/Routes.razor @@ -0,0 +1,6 @@ + + + + + + diff --git a/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs b/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs new file mode 100644 index 000000000..d98375d01 --- /dev/null +++ b/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs @@ -0,0 +1,9 @@ +using FwLiteShared.Projects; +using Microsoft.JSInterop; + +namespace FwLiteShared.Services; + +public class FwLiteProvider(CombinedProjectsService projectService) +{ + public CombinedProjectsService ProjectService { get; } = projectService; +} diff --git a/backend/FwLite/FwLiteShared/Services/IFormFactor.cs b/backend/FwLite/FwLiteShared/Services/IFormFactor.cs new file mode 100644 index 000000000..06b5a31a7 --- /dev/null +++ b/backend/FwLite/FwLiteShared/Services/IFormFactor.cs @@ -0,0 +1,7 @@ +namespace FwLiteShared.Services; + +public interface IFormFactor +{ + public string GetFormFactor(); + public string GetPlatform(); +} diff --git a/backend/FwLite/FwLiteShared/TypeGen/FwLiteTypeGenSpec.cs b/backend/FwLite/FwLiteShared/TypeGen/FwLiteTypeGenSpec.cs new file mode 100644 index 000000000..c35918612 --- /dev/null +++ b/backend/FwLite/FwLiteShared/TypeGen/FwLiteTypeGenSpec.cs @@ -0,0 +1,45 @@ +using FwLiteShared.Services; +using MiniLcm; +using MiniLcm.Models; +using Reinforced.Typings.Ast.TypeNames; +using Reinforced.Typings.Fluent; +using TypeGen.Core.SpecGeneration; + +namespace FwLiteShared.TypeGen; + +public class FwLiteTypeGenSpec: GenerationSpec +{ + public FwLiteTypeGenSpec() + { + AddClass(); + AddInterface(); + } + + public override void OnBeforeGeneration(OnBeforeGenerationArgs args) + { + args.GeneratorOptions.CustomTypeMappings.Add(typeof(WritingSystemId).FullName!, "string"); + } +} + +public static class ReinforcedFwLiteTypingConfig +{ + public static void Configure(ConfigurationBuilder builder) + { + builder.Substitute(typeof(WritingSystemId), new RtSimpleTypeName("string")); + builder.Substitute(typeof(Guid), new RtSimpleTypeName("string")); + builder.Substitute(typeof(DateTimeOffset), new RtSimpleTypeName("string")); + builder.Substitute(typeof(MultiString), new RtDictionaryType(new RtSimpleTypeName("string"), new RtSimpleTypeName("string"))); + builder.ExportAsInterfaces([ + typeof(Entry), + typeof(Sense), + typeof(ExampleSentence), + typeof(WritingSystem), + typeof(PartOfSpeech), + typeof(SemanticDomain), + typeof(ComplexFormType), + typeof(ComplexFormComponent), + ], exportBuilder => exportBuilder.WithPublicProperties()); + builder.ExportAsInterface().FlattenHierarchy().WithPublicProperties().WithPublicMethods(); + builder.ExportAsInterface().WithPublicMethods(); + } +} diff --git a/backend/FwLite/FwLiteShared/_Imports.razor b/backend/FwLite/FwLiteShared/_Imports.razor new file mode 100644 index 000000000..d88eed9f7 --- /dev/null +++ b/backend/FwLite/FwLiteShared/_Imports.razor @@ -0,0 +1,8 @@ +@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 diff --git a/backend/FwLite/FwLiteShared/tgconfig.json b/backend/FwLite/FwLiteShared/tgconfig.json new file mode 100644 index 000000000..8ba82e91d --- /dev/null +++ b/backend/FwLite/FwLiteShared/tgconfig.json @@ -0,0 +1,6 @@ +{ + "generationSpecs": [ + "FwLiteTypeGenSpec" + ], + "outputPath": "../../../frontend/viewer/src/lib/generated-types" +} diff --git a/backend/FwLite/FwLiteShared/wwwroot/background.png b/backend/FwLite/FwLiteShared/wwwroot/background.png new file mode 100644 index 0000000000000000000000000000000000000000..e15a3bde6e2bdb380df6a0b46d7ed00bdeb0aaa8 GIT binary patch literal 378 zcmeAS@N?(olHy`uVBq!ia0vp^x**KK1SGdsl%54rjKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1qucLCF%=h?3y^w370~qEv>0#LT=By}Z;C1rt33 zJwr2>%=KS^ie7oTIEF;HpS|GCbyPusHSqiXaCu3qf)82(9Gq&mZq2{Kq}M*X&MWtJ zSi1Jo7ZzfImg%g=t(qo=wsSR2lZoP(Rj#3wacN=q0?Br(rXzgZEGK2$ID{|A=5S{xJEuzSH>!M+7wSY6hB<=-E^*n0W7 S8wY^CX7F_Nb6Mw<&;$S{dxtsz literal 0 HcmV?d00001 diff --git a/backend/FwLite/FwLiteShared/wwwroot/bootstrap/bootstrap.min.css b/backend/FwLite/FwLiteShared/wwwroot/bootstrap/bootstrap.min.css new file mode 100644 index 000000000..5668bce58 --- /dev/null +++ b/backend/FwLite/FwLiteShared/wwwroot/bootstrap/bootstrap.min.css @@ -0,0 +1,7 @@ +@charset "UTF-8";/*! + * Bootstrap v5.1.0 (https://getbootstrap.com/) + * Copyright 2011-2021 The Bootstrap Authors + * Copyright 2011-2021 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */:root{--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-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-white-rgb:255,255,255;--bs-black-rgb:0,0,0;--bs-body-rgb:33,37,41;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",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-bg:#fff}*,::after,::before{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:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){.h1,h1{font-size:2.5rem}}.h2,h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){.h2,h2{font-size:2rem}}.h3,h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){.h3,h3{font-size:1.75rem}}.h4,h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){.h4,h4{font-size:1.5rem}}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],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}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}.small,small{font-size:.875em}.mark,mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,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]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button: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:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-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}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:.875em;color:#6c757d}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:.875em;color:#6c757d}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{width:100%;padding-right:var(--bs-gutter-x,.75rem);padding-left:var(--bs-gutter-x,.75rem);margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}@media (min-width:1400px){.container,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{max-width:1320px}}.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0;display:flex;flex-wrap:wrap;margin-top:calc(var(--bs-gutter-y) * -1);margin-right:calc(var(--bs-gutter-x) * -.5);margin-left:calc(var(--bs-gutter-x) * -.5)}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-top:var(--bs-gutter-y)}.col{flex:1 0 0%}.row-cols-auto>*{flex:0 0 auto;width:auto}.row-cols-1>*{flex:0 0 auto;width:100%}.row-cols-2>*{flex:0 0 auto;width:50%}.row-cols-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-4>*{flex:0 0 auto;width:25%}.row-cols-5>*{flex:0 0 auto;width:20%}.row-cols-6>*{flex:0 0 auto;width:16.6666666667%}.col-auto{flex:0 0 auto;width:auto}.col-1{flex:0 0 auto;width:8.33333333%}.col-2{flex:0 0 auto;width:16.66666667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.33333333%}.col-5{flex:0 0 auto;width:41.66666667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.33333333%}.col-8{flex:0 0 auto;width:66.66666667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.33333333%}.col-11{flex:0 0 auto;width:91.66666667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.33333333%}.offset-2{margin-left:16.66666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333333%}.offset-5{margin-left:41.66666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333333%}.offset-8{margin-left:66.66666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333333%}.offset-11{margin-left:91.66666667%}.g-0,.gx-0{--bs-gutter-x:0}.g-0,.gy-0{--bs-gutter-y:0}.g-1,.gx-1{--bs-gutter-x:0.25rem}.g-1,.gy-1{--bs-gutter-y:0.25rem}.g-2,.gx-2{--bs-gutter-x:0.5rem}.g-2,.gy-2{--bs-gutter-y:0.5rem}.g-3,.gx-3{--bs-gutter-x:1rem}.g-3,.gy-3{--bs-gutter-y:1rem}.g-4,.gx-4{--bs-gutter-x:1.5rem}.g-4,.gy-4{--bs-gutter-y:1.5rem}.g-5,.gx-5{--bs-gutter-x:3rem}.g-5,.gy-5{--bs-gutter-y:3rem}@media (min-width:576px){.col-sm{flex:1 0 0%}.row-cols-sm-auto>*{flex:0 0 auto;width:auto}.row-cols-sm-1>*{flex:0 0 auto;width:100%}.row-cols-sm-2>*{flex:0 0 auto;width:50%}.row-cols-sm-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-sm-4>*{flex:0 0 auto;width:25%}.row-cols-sm-5>*{flex:0 0 auto;width:20%}.row-cols-sm-6>*{flex:0 0 auto;width:16.6666666667%}.col-sm-auto{flex:0 0 auto;width:auto}.col-sm-1{flex:0 0 auto;width:8.33333333%}.col-sm-2{flex:0 0 auto;width:16.66666667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.33333333%}.col-sm-5{flex:0 0 auto;width:41.66666667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.33333333%}.col-sm-8{flex:0 0 auto;width:66.66666667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.33333333%}.col-sm-11{flex:0 0 auto;width:91.66666667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333333%}.offset-sm-2{margin-left:16.66666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333333%}.offset-sm-5{margin-left:41.66666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333333%}.offset-sm-8{margin-left:66.66666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333333%}.offset-sm-11{margin-left:91.66666667%}.g-sm-0,.gx-sm-0{--bs-gutter-x:0}.g-sm-0,.gy-sm-0{--bs-gutter-y:0}.g-sm-1,.gx-sm-1{--bs-gutter-x:0.25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y:0.25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x:0.5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y:0.5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x:1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y:1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x:1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y:1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x:3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y:3rem}}@media (min-width:768px){.col-md{flex:1 0 0%}.row-cols-md-auto>*{flex:0 0 auto;width:auto}.row-cols-md-1>*{flex:0 0 auto;width:100%}.row-cols-md-2>*{flex:0 0 auto;width:50%}.row-cols-md-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-md-4>*{flex:0 0 auto;width:25%}.row-cols-md-5>*{flex:0 0 auto;width:20%}.row-cols-md-6>*{flex:0 0 auto;width:16.6666666667%}.col-md-auto{flex:0 0 auto;width:auto}.col-md-1{flex:0 0 auto;width:8.33333333%}.col-md-2{flex:0 0 auto;width:16.66666667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.33333333%}.col-md-5{flex:0 0 auto;width:41.66666667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.33333333%}.col-md-8{flex:0 0 auto;width:66.66666667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.33333333%}.col-md-11{flex:0 0 auto;width:91.66666667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333333%}.offset-md-2{margin-left:16.66666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333333%}.offset-md-5{margin-left:41.66666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333333%}.offset-md-8{margin-left:66.66666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333333%}.offset-md-11{margin-left:91.66666667%}.g-md-0,.gx-md-0{--bs-gutter-x:0}.g-md-0,.gy-md-0{--bs-gutter-y:0}.g-md-1,.gx-md-1{--bs-gutter-x:0.25rem}.g-md-1,.gy-md-1{--bs-gutter-y:0.25rem}.g-md-2,.gx-md-2{--bs-gutter-x:0.5rem}.g-md-2,.gy-md-2{--bs-gutter-y:0.5rem}.g-md-3,.gx-md-3{--bs-gutter-x:1rem}.g-md-3,.gy-md-3{--bs-gutter-y:1rem}.g-md-4,.gx-md-4{--bs-gutter-x:1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y:1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x:3rem}.g-md-5,.gy-md-5{--bs-gutter-y:3rem}}@media (min-width:992px){.col-lg{flex:1 0 0%}.row-cols-lg-auto>*{flex:0 0 auto;width:auto}.row-cols-lg-1>*{flex:0 0 auto;width:100%}.row-cols-lg-2>*{flex:0 0 auto;width:50%}.row-cols-lg-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-lg-4>*{flex:0 0 auto;width:25%}.row-cols-lg-5>*{flex:0 0 auto;width:20%}.row-cols-lg-6>*{flex:0 0 auto;width:16.6666666667%}.col-lg-auto{flex:0 0 auto;width:auto}.col-lg-1{flex:0 0 auto;width:8.33333333%}.col-lg-2{flex:0 0 auto;width:16.66666667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.33333333%}.col-lg-5{flex:0 0 auto;width:41.66666667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.33333333%}.col-lg-8{flex:0 0 auto;width:66.66666667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.33333333%}.col-lg-11{flex:0 0 auto;width:91.66666667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333333%}.offset-lg-2{margin-left:16.66666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333333%}.offset-lg-5{margin-left:41.66666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333333%}.offset-lg-8{margin-left:66.66666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333333%}.offset-lg-11{margin-left:91.66666667%}.g-lg-0,.gx-lg-0{--bs-gutter-x:0}.g-lg-0,.gy-lg-0{--bs-gutter-y:0}.g-lg-1,.gx-lg-1{--bs-gutter-x:0.25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y:0.25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x:0.5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y:0.5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x:1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y:1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x:1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y:1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x:3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y:3rem}}@media (min-width:1200px){.col-xl{flex:1 0 0%}.row-cols-xl-auto>*{flex:0 0 auto;width:auto}.row-cols-xl-1>*{flex:0 0 auto;width:100%}.row-cols-xl-2>*{flex:0 0 auto;width:50%}.row-cols-xl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xl-4>*{flex:0 0 auto;width:25%}.row-cols-xl-5>*{flex:0 0 auto;width:20%}.row-cols-xl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xl-auto{flex:0 0 auto;width:auto}.col-xl-1{flex:0 0 auto;width:8.33333333%}.col-xl-2{flex:0 0 auto;width:16.66666667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.33333333%}.col-xl-5{flex:0 0 auto;width:41.66666667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.33333333%}.col-xl-8{flex:0 0 auto;width:66.66666667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.33333333%}.col-xl-11{flex:0 0 auto;width:91.66666667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333333%}.offset-xl-2{margin-left:16.66666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333333%}.offset-xl-5{margin-left:41.66666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333333%}.offset-xl-8{margin-left:66.66666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333333%}.offset-xl-11{margin-left:91.66666667%}.g-xl-0,.gx-xl-0{--bs-gutter-x:0}.g-xl-0,.gy-xl-0{--bs-gutter-y:0}.g-xl-1,.gx-xl-1{--bs-gutter-x:0.25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y:0.25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x:0.5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y:0.5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x:1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y:1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x:1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y:1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x:3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y:3rem}}@media (min-width:1400px){.col-xxl{flex:1 0 0%}.row-cols-xxl-auto>*{flex:0 0 auto;width:auto}.row-cols-xxl-1>*{flex:0 0 auto;width:100%}.row-cols-xxl-2>*{flex:0 0 auto;width:50%}.row-cols-xxl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xxl-4>*{flex:0 0 auto;width:25%}.row-cols-xxl-5>*{flex:0 0 auto;width:20%}.row-cols-xxl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xxl-auto{flex:0 0 auto;width:auto}.col-xxl-1{flex:0 0 auto;width:8.33333333%}.col-xxl-2{flex:0 0 auto;width:16.66666667%}.col-xxl-3{flex:0 0 auto;width:25%}.col-xxl-4{flex:0 0 auto;width:33.33333333%}.col-xxl-5{flex:0 0 auto;width:41.66666667%}.col-xxl-6{flex:0 0 auto;width:50%}.col-xxl-7{flex:0 0 auto;width:58.33333333%}.col-xxl-8{flex:0 0 auto;width:66.66666667%}.col-xxl-9{flex:0 0 auto;width:75%}.col-xxl-10{flex:0 0 auto;width:83.33333333%}.col-xxl-11{flex:0 0 auto;width:91.66666667%}.col-xxl-12{flex:0 0 auto;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.33333333%}.offset-xxl-2{margin-left:16.66666667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.33333333%}.offset-xxl-5{margin-left:41.66666667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.33333333%}.offset-xxl-8{margin-left:66.66666667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.33333333%}.offset-xxl-11{margin-left:91.66666667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x:0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y:0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x:0.25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y:0.25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x:0.5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y:0.5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x:1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y:1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x:1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y:1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x:3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y:3rem}}.table{--bs-table-bg:transparent;--bs-table-accent-bg:transparent;--bs-table-striped-color:#212529;--bs-table-striped-bg:rgba(0, 0, 0, 0.05);--bs-table-active-color:#212529;--bs-table-active-bg:rgba(0, 0, 0, 0.1);--bs-table-hover-color:#212529;--bs-table-hover-bg:rgba(0, 0, 0, 0.075);width:100%;margin-bottom:1rem;color:#212529;vertical-align:top;border-color:#dee2e6}.table>:not(caption)>*>*{padding:.5rem .5rem;background-color:var(--bs-table-bg);border-bottom-width:1px;box-shadow:inset 0 0 0 9999px var(--bs-table-accent-bg)}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table>:not(:last-child)>:last-child>*{border-bottom-color:currentColor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:1px 0}.table-bordered>:not(caption)>*>*{border-width:0 1px}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-striped>tbody>tr:nth-of-type(odd){--bs-table-accent-bg:var(--bs-table-striped-bg);color:var(--bs-table-striped-color)}.table-active{--bs-table-accent-bg:var(--bs-table-active-bg);color:var(--bs-table-active-color)}.table-hover>tbody>tr:hover{--bs-table-accent-bg:var(--bs-table-hover-bg);color:var(--bs-table-hover-color)}.table-primary{--bs-table-bg:#cfe2ff;--bs-table-striped-bg:#c5d7f2;--bs-table-striped-color:#000;--bs-table-active-bg:#bacbe6;--bs-table-active-color:#000;--bs-table-hover-bg:#bfd1ec;--bs-table-hover-color:#000;color:#000;border-color:#bacbe6}.table-secondary{--bs-table-bg:#e2e3e5;--bs-table-striped-bg:#d7d8da;--bs-table-striped-color:#000;--bs-table-active-bg:#cbccce;--bs-table-active-color:#000;--bs-table-hover-bg:#d1d2d4;--bs-table-hover-color:#000;color:#000;border-color:#cbccce}.table-success{--bs-table-bg:#d1e7dd;--bs-table-striped-bg:#c7dbd2;--bs-table-striped-color:#000;--bs-table-active-bg:#bcd0c7;--bs-table-active-color:#000;--bs-table-hover-bg:#c1d6cc;--bs-table-hover-color:#000;color:#000;border-color:#bcd0c7}.table-info{--bs-table-bg:#cff4fc;--bs-table-striped-bg:#c5e8ef;--bs-table-striped-color:#000;--bs-table-active-bg:#badce3;--bs-table-active-color:#000;--bs-table-hover-bg:#bfe2e9;--bs-table-hover-color:#000;color:#000;border-color:#badce3}.table-warning{--bs-table-bg:#fff3cd;--bs-table-striped-bg:#f2e7c3;--bs-table-striped-color:#000;--bs-table-active-bg:#e6dbb9;--bs-table-active-color:#000;--bs-table-hover-bg:#ece1be;--bs-table-hover-color:#000;color:#000;border-color:#e6dbb9}.table-danger{--bs-table-bg:#f8d7da;--bs-table-striped-bg:#eccccf;--bs-table-striped-color:#000;--bs-table-active-bg:#dfc2c4;--bs-table-active-color:#000;--bs-table-hover-bg:#e5c7ca;--bs-table-hover-color:#000;color:#000;border-color:#dfc2c4}.table-light{--bs-table-bg:#f8f9fa;--bs-table-striped-bg:#ecedee;--bs-table-striped-color:#000;--bs-table-active-bg:#dfe0e1;--bs-table-active-color:#000;--bs-table-hover-bg:#e5e6e7;--bs-table-hover-color:#000;color:#000;border-color:#dfe0e1}.table-dark{--bs-table-bg:#212529;--bs-table-striped-bg:#2c3034;--bs-table-striped-color:#fff;--bs-table-active-bg:#373b3e;--bs-table-active-color:#fff;--bs-table-hover-bg:#323539;--bs-table-hover-color:#fff;color:#fff;border-color:#373b3e}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media (max-width:575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem}.form-text{margin-top:.25rem;font-size:.875em;color:#6c757d}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:#212529;background-color:#fff;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-control::-webkit-date-and-time-value{height:1.5em}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}.form-control::file-selector-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:#dde0e3}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;-webkit-transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:#dde0e3}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + .75rem + 2px)}textarea.form-control-sm{min-height:calc(1.5em + .5rem + 2px)}textarea.form-control-lg{min-height:calc(1.5em + 1rem + 2px)}.form-control-color{width:3rem;height:auto;padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{height:1.5em;border-radius:.25rem}.form-control-color::-webkit-color-swatch{height:1.5em;border-radius:.25rem}.form-select{display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;-moz-padding-start:calc(0.75rem - 3px);font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-select{transition:none}}.form-select:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:#e9ecef}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #212529}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:.125rem}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-input{width:1em;height:1em;margin-top:.25em;vertical-align:top;background-color:#fff;background-repeat:no-repeat;background-position:center;background-size:contain;border:1px solid rgba(0,0,0,.25);-webkit-appearance:none;-moz-appearance:none;appearance:none;-webkit-print-color-adjust:exact;color-adjust:exact}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-check-input:checked{background-color:#0d6efd;border-color:#0d6efd}.form-check-input:checked[type=checkbox]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate{background-color:#0d6efd;border-color:#0d6efd;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{width:2em;margin-left:-2.5em;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");background-position:left center;border-radius:2em;transition:background-position .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.btn-check:disabled+.btn,.btn-check[disabled]+.btn{pointer-events:none;filter:none;opacity:.65}.form-range{width:100%;height:1.5rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#0d6efd;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#b6d4fe}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#0d6efd;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-moz-range-thumb{-moz-transition:none;transition:none}}.form-range::-moz-range-thumb:active{background-color:#b6d4fe}.form-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.form-range:disabled::-moz-range-thumb{background-color:#adb5bd}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-select{height:calc(3.5rem + 2px);line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;height:100%;padding:1rem .75rem;pointer-events:none;border:1px solid transparent;transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media (prefers-reduced-motion:reduce){.form-floating>label{transition:none}}.form-floating>.form-control{padding:1rem .75rem}.form-floating>.form-control::-moz-placeholder{color:transparent}.form-floating>.form-control::placeholder{color:transparent}.form-floating>.form-control:not(:-moz-placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:not(:-moz-placeholder-shown)~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:-webkit-autofill~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-select:focus{z-index:3}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:3}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-lg>.btn,.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.input-group-sm>.btn,.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#198754}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(25,135,84,.9);border-radius:.25rem}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#198754;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-valid,.was-validated .form-select:valid{border-color:#198754}.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"],.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-valid:focus,.was-validated .form-select:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid,.was-validated .form-check-input:valid{border-color:#198754}.form-check-input.is-valid:checked,.was-validated .form-check-input:valid:checked{background-color:#198754}.form-check-input.is-valid:focus,.was-validated .form-check-input:valid:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#198754}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.input-group .form-control.is-valid,.input-group .form-select.is-valid,.was-validated .input-group .form-control:valid,.was-validated .input-group .form-select:valid{z-index:1}.input-group .form-control.is-valid:focus,.input-group .form-select.is-valid:focus,.was-validated .input-group .form-control:valid:focus,.was-validated .input-group .form-select:valid:focus{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-invalid,.was-validated .form-select:invalid{border-color:#dc3545}.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"],.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-invalid:focus,.was-validated .form-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid,.was-validated .form-check-input:invalid{border-color:#dc3545}.form-check-input.is-invalid:checked,.was-validated .form-check-input:invalid:checked{background-color:#dc3545}.form-check-input.is-invalid:focus,.was-validated .form-check-input:invalid:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.input-group .form-control.is-invalid,.input-group .form-select.is-invalid,.was-validated .input-group .form-control:invalid,.was-validated .input-group .form-select:invalid{z-index:2}.input-group .form-control.is-invalid:focus,.input-group .form-select.is-invalid:focus,.was-validated .input-group .form-control:invalid:focus,.was-validated .input-group .form-select:invalid:focus{z-index:3}.btn{display:inline-block;font-weight:400;line-height:1.5;color:#212529;text-align:center;text-decoration:none;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529}.btn-check:focus+.btn,.btn:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.btn.disabled,.btn:disabled,fieldset:disabled .btn{pointer-events:none;opacity:.65}.btn-primary{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-primary:hover{color:#fff;background-color:#0b5ed7;border-color:#0a58ca}.btn-check:focus+.btn-primary,.btn-primary:focus{color:#fff;background-color:#0b5ed7;border-color:#0a58ca;box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-check:active+.btn-primary,.btn-check:checked+.btn-primary,.btn-primary.active,.btn-primary:active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0a58ca;border-color:#0a53be}.btn-check:active+.btn-primary:focus,.btn-check:checked+.btn-primary:focus,.btn-primary.active:focus,.btn-primary:active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5c636a;border-color:#565e64}.btn-check:focus+.btn-secondary,.btn-secondary:focus{color:#fff;background-color:#5c636a;border-color:#565e64;box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-check:active+.btn-secondary,.btn-check:checked+.btn-secondary,.btn-secondary.active,.btn-secondary:active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#565e64;border-color:#51585e}.btn-check:active+.btn-secondary:focus,.btn-check:checked+.btn-secondary:focus,.btn-secondary.active:focus,.btn-secondary:active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-success{color:#fff;background-color:#198754;border-color:#198754}.btn-success:hover{color:#fff;background-color:#157347;border-color:#146c43}.btn-check:focus+.btn-success,.btn-success:focus{color:#fff;background-color:#157347;border-color:#146c43;box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-check:active+.btn-success,.btn-check:checked+.btn-success,.btn-success.active,.btn-success:active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#146c43;border-color:#13653f}.btn-check:active+.btn-success:focus,.btn-check:checked+.btn-success:focus,.btn-success.active:focus,.btn-success:active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#198754;border-color:#198754}.btn-info{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-info:hover{color:#000;background-color:#31d2f2;border-color:#25cff2}.btn-check:focus+.btn-info,.btn-info:focus{color:#000;background-color:#31d2f2;border-color:#25cff2;box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-check:active+.btn-info,.btn-check:checked+.btn-info,.btn-info.active,.btn-info:active,.show>.btn-info.dropdown-toggle{color:#000;background-color:#3dd5f3;border-color:#25cff2}.btn-check:active+.btn-info:focus,.btn-check:checked+.btn-info:focus,.btn-info.active:focus,.btn-info:active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-info.disabled,.btn-info:disabled{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-warning{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#000;background-color:#ffca2c;border-color:#ffc720}.btn-check:focus+.btn-warning,.btn-warning:focus{color:#000;background-color:#ffca2c;border-color:#ffc720;box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-check:active+.btn-warning,.btn-check:checked+.btn-warning,.btn-warning.active,.btn-warning:active,.show>.btn-warning.dropdown-toggle{color:#000;background-color:#ffcd39;border-color:#ffc720}.btn-check:active+.btn-warning:focus,.btn-check:checked+.btn-warning:focus,.btn-warning.active:focus,.btn-warning:active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#bb2d3b;border-color:#b02a37}.btn-check:focus+.btn-danger,.btn-danger:focus{color:#fff;background-color:#bb2d3b;border-color:#b02a37;box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-check:active+.btn-danger,.btn-check:checked+.btn-danger,.btn-danger.active,.btn-danger:active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#b02a37;border-color:#a52834}.btn-check:active+.btn-danger:focus,.btn-check:checked+.btn-danger:focus,.btn-danger.active:focus,.btn-danger:active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-light{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:focus+.btn-light,.btn-light:focus{color:#000;background-color:#f9fafb;border-color:#f9fafb;box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-check:active+.btn-light,.btn-check:checked+.btn-light,.btn-light.active,.btn-light:active,.show>.btn-light.dropdown-toggle{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:active+.btn-light:focus,.btn-check:checked+.btn-light:focus,.btn-light.active:focus,.btn-light:active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-light.disabled,.btn-light:disabled{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-dark{color:#fff;background-color:#212529;border-color:#212529}.btn-dark:hover{color:#fff;background-color:#1c1f23;border-color:#1a1e21}.btn-check:focus+.btn-dark,.btn-dark:focus{color:#fff;background-color:#1c1f23;border-color:#1a1e21;box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-check:active+.btn-dark,.btn-check:checked+.btn-dark,.btn-dark.active,.btn-dark:active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1a1e21;border-color:#191c1f}.btn-check:active+.btn-dark:focus,.btn-check:checked+.btn-dark:focus,.btn-dark.active:focus,.btn-dark:active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#212529;border-color:#212529}.btn-outline-primary{color:#0d6efd;border-color:#0d6efd}.btn-outline-primary:hover{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:focus+.btn-outline-primary,.btn-outline-primary:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-check:active+.btn-outline-primary,.btn-check:checked+.btn-outline-primary,.btn-outline-primary.active,.btn-outline-primary.dropdown-toggle.show,.btn-outline-primary:active{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:active+.btn-outline-primary:focus,.btn-check:checked+.btn-outline-primary:focus,.btn-outline-primary.active:focus,.btn-outline-primary.dropdown-toggle.show:focus,.btn-outline-primary:active:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#0d6efd;background-color:transparent}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:focus+.btn-outline-secondary,.btn-outline-secondary:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-check:active+.btn-outline-secondary,.btn-check:checked+.btn-outline-secondary,.btn-outline-secondary.active,.btn-outline-secondary.dropdown-toggle.show,.btn-outline-secondary:active{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:active+.btn-outline-secondary:focus,.btn-check:checked+.btn-outline-secondary:focus,.btn-outline-secondary.active:focus,.btn-outline-secondary.dropdown-toggle.show:focus,.btn-outline-secondary:active:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-success{color:#198754;border-color:#198754}.btn-outline-success:hover{color:#fff;background-color:#198754;border-color:#198754}.btn-check:focus+.btn-outline-success,.btn-outline-success:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-check:active+.btn-outline-success,.btn-check:checked+.btn-outline-success,.btn-outline-success.active,.btn-outline-success.dropdown-toggle.show,.btn-outline-success:active{color:#fff;background-color:#198754;border-color:#198754}.btn-check:active+.btn-outline-success:focus,.btn-check:checked+.btn-outline-success:focus,.btn-outline-success.active:focus,.btn-outline-success.dropdown-toggle.show:focus,.btn-outline-success:active:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#198754;background-color:transparent}.btn-outline-info{color:#0dcaf0;border-color:#0dcaf0}.btn-outline-info:hover{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:focus+.btn-outline-info,.btn-outline-info:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-check:active+.btn-outline-info,.btn-check:checked+.btn-outline-info,.btn-outline-info.active,.btn-outline-info.dropdown-toggle.show,.btn-outline-info:active{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:active+.btn-outline-info:focus,.btn-check:checked+.btn-outline-info:focus,.btn-outline-info.active:focus,.btn-outline-info.dropdown-toggle.show:focus,.btn-outline-info:active:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#0dcaf0;background-color:transparent}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:focus+.btn-outline-warning,.btn-outline-warning:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-check:active+.btn-outline-warning,.btn-check:checked+.btn-outline-warning,.btn-outline-warning.active,.btn-outline-warning.dropdown-toggle.show,.btn-outline-warning:active{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:active+.btn-outline-warning:focus,.btn-check:checked+.btn-outline-warning:focus,.btn-outline-warning.active:focus,.btn-outline-warning.dropdown-toggle.show:focus,.btn-outline-warning:active:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:focus+.btn-outline-danger,.btn-outline-danger:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-check:active+.btn-outline-danger,.btn-check:checked+.btn-outline-danger,.btn-outline-danger.active,.btn-outline-danger.dropdown-toggle.show,.btn-outline-danger:active{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:active+.btn-outline-danger:focus,.btn-check:checked+.btn-outline-danger:focus,.btn-outline-danger.active:focus,.btn-outline-danger.dropdown-toggle.show:focus,.btn-outline-danger:active:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:focus+.btn-outline-light,.btn-outline-light:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-check:active+.btn-outline-light,.btn-check:checked+.btn-outline-light,.btn-outline-light.active,.btn-outline-light.dropdown-toggle.show,.btn-outline-light:active{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:active+.btn-outline-light:focus,.btn-check:checked+.btn-outline-light:focus,.btn-outline-light.active:focus,.btn-outline-light.dropdown-toggle.show:focus,.btn-outline-light:active:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-dark{color:#212529;border-color:#212529}.btn-outline-dark:hover{color:#fff;background-color:#212529;border-color:#212529}.btn-check:focus+.btn-outline-dark,.btn-outline-dark:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-check:active+.btn-outline-dark,.btn-check:checked+.btn-outline-dark,.btn-outline-dark.active,.btn-outline-dark.dropdown-toggle.show,.btn-outline-dark:active{color:#fff;background-color:#212529;border-color:#212529}.btn-check:active+.btn-outline-dark:focus,.btn-check:checked+.btn-outline-dark:focus,.btn-outline-dark.active:focus,.btn-outline-dark.dropdown-toggle.show:focus,.btn-outline-dark:active:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#212529;background-color:transparent}.btn-link{font-weight:400;color:#0d6efd;text-decoration:underline}.btn-link:hover{color:#0a58ca}.btn-link.disabled,.btn-link:disabled{color:#6c757d}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s ease}@media (prefers-reduced-motion:reduce){.collapsing.collapse-horizontal{transition:none}}.dropdown,.dropend,.dropstart,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;z-index:1000;display:none;min-width:10rem;padding:.5rem 0;margin:0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:.125rem}.dropdown-menu-start{--bs-position:start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position:end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-start{--bs-position:start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position:end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-start{--bs-position:start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position:end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-start{--bs-position:start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position:end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-start{--bs-position:start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position:end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1400px){.dropdown-menu-xxl-start{--bs-position:start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position:end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid rgba(0,0,0,.15)}.dropdown-item{display:block;width:100%;padding:.25rem 1rem;clear:both;font-weight:400;color:#212529;text-align:inherit;text-decoration:none;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#1e2125;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#0d6efd}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1rem;color:#212529}.dropdown-menu-dark{color:#dee2e6;background-color:#343a40;border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item{color:#dee2e6}.dropdown-menu-dark .dropdown-item:focus,.dropdown-menu-dark .dropdown-item:hover{color:#fff;background-color:rgba(255,255,255,.15)}.dropdown-menu-dark .dropdown-item.active,.dropdown-menu-dark .dropdown-item:active{color:#fff;background-color:#0d6efd}.dropdown-menu-dark .dropdown-item.disabled,.dropdown-menu-dark .dropdown-item:disabled{color:#adb5bd}.dropdown-menu-dark .dropdown-divider{border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item-text{color:#dee2e6}.dropdown-menu-dark .dropdown-header{color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:1 1 auto}.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn~.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem;color:#0d6efd;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media (prefers-reduced-motion:reduce){.nav-link{transition:none}}.nav-link:focus,.nav-link:hover{color:#0a58ca}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;background:0 0;border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6;isolation:isolate}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{background:0 0;border:0;border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#0d6efd}.nav-fill .nav-item,.nav-fill>.nav-link{flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{flex-basis:0;flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding-top:.5rem;padding-bottom:.5rem}.navbar>.container,.navbar>.container-fluid,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container-xl,.navbar>.container-xxl{display:flex;flex-wrap:inherit;align-items:center;justify-content:space-between}.navbar-brand{padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;text-decoration:none;white-space:nowrap}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem;transition:box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 .25rem}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height,75vh);overflow-y:auto}@media (min-width:576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas-header{display:none}.navbar-expand-sm .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-sm .offcanvas-bottom,.navbar-expand-sm .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-sm .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas-header{display:none}.navbar-expand-md .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-md .offcanvas-bottom,.navbar-expand-md .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-md .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas-header{display:none}.navbar-expand-lg .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-lg .offcanvas-bottom,.navbar-expand-lg .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-lg .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas-header{display:none}.navbar-expand-xl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xl .offcanvas-bottom,.navbar-expand-xl .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-xl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xxl .offcanvas-bottom,.navbar-expand-xxl .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-xxl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas-header{display:none}.navbar-expand .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand .offcanvas-bottom,.navbar-expand .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.55)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.55);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.55)}.navbar-light .navbar-text a,.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.55)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.55);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image: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")}.navbar-dark .navbar-text{color:rgba(255,255,255,.55)}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:flex;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;padding:1rem 1rem}.card-title{margin-bottom:.5rem}.card-subtitle{margin-top:-.25rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:1rem}.card-header{padding:.5rem 1rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-footer{padding:.5rem 1rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.5rem;margin-bottom:-.5rem;margin-left:-.5rem;border-bottom:0}.card-header-pills{margin-right:-.5rem;margin-left:-.5rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom,.card-img-top{width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-group>.card{margin-bottom:.75rem}@media (min-width:576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.accordion-button{position:relative;display:flex;align-items:center;width:100%;padding:1rem 1.25rem;font-size:1rem;color:#212529;text-align:left;background-color:#fff;border:0;border-radius:0;overflow-anchor:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,border-radius .15s ease}@media (prefers-reduced-motion:reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:#0c63e4;background-color:#e7f1ff;box-shadow:inset 0 -1px 0 rgba(0,0,0,.125)}.accordion-button:not(.collapsed)::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%230c63e4'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");transform:rotate(-180deg)}.accordion-button::after{flex-shrink:0;width:1.25rem;height:1.25rem;margin-left:auto;content:"";background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-size:1.25rem;transition:transform .2s ease-in-out}@media (prefers-reduced-motion:reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.accordion-header{margin-bottom:0}.accordion-item{background-color:#fff;border:1px solid rgba(0,0,0,.125)}.accordion-item:first-of-type{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.accordion-item:first-of-type .accordion-button{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-item:last-of-type .accordion-button.collapsed{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.accordion-item:last-of-type .accordion-collapse{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-body{padding:1rem 1.25rem}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.accordion-flush .accordion-item .accordion-button{border-radius:0}.breadcrumb{display:flex;flex-wrap:wrap;padding:0 0;margin-bottom:1rem;list-style:none}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:var(--bs-breadcrumb-divider, "/")}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;color:#0d6efd;text-decoration:none;background-color:#fff;border:1px solid #dee2e6;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:#0a58ca;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;color:#0a58ca;background-color:#e9ecef;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.page-item:not(:first-child) .page-link{margin-left:-1px}.page-item.active .page-link{z-index:3;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;background-color:#fff;border-color:#dee2e6}.page-link{padding:.375rem .75rem}.page-item:first-child .page-link{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.35em .65em;font-size:.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{position:relative;padding:1rem 1rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-primary{color:#084298;background-color:#cfe2ff;border-color:#b6d4fe}.alert-primary .alert-link{color:#06357a}.alert-secondary{color:#41464b;background-color:#e2e3e5;border-color:#d3d6d8}.alert-secondary .alert-link{color:#34383c}.alert-success{color:#0f5132;background-color:#d1e7dd;border-color:#badbcc}.alert-success .alert-link{color:#0c4128}.alert-info{color:#055160;background-color:#cff4fc;border-color:#b6effb}.alert-info .alert-link{color:#04414d}.alert-warning{color:#664d03;background-color:#fff3cd;border-color:#ffecb5}.alert-warning .alert-link{color:#523e02}.alert-danger{color:#842029;background-color:#f8d7da;border-color:#f5c2c7}.alert-danger .alert-link{color:#6a1a21}.alert-light{color:#636464;background-color:#fefefe;border-color:#fdfdfe}.alert-light .alert-link{color:#4f5050}.alert-dark{color:#141619;background-color:#d3d3d4;border-color:#bcbebf}.alert-dark .alert-link{color:#101214}@-webkit-keyframes progress-bar-stripes{0%{background-position-x:1rem}}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress{display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#0d6efd;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:1s linear infinite progress-bar-stripes;animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.list-group{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>li::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.5rem 1rem;color:#212529;text-decoration:none;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#084298;background-color:#cfe2ff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#084298;background-color:#bacbe6}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#084298;border-color:#084298}.list-group-item-secondary{color:#41464b;background-color:#e2e3e5}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#41464b;background-color:#cbccce}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#41464b;border-color:#41464b}.list-group-item-success{color:#0f5132;background-color:#d1e7dd}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#0f5132;background-color:#bcd0c7}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#0f5132;border-color:#0f5132}.list-group-item-info{color:#055160;background-color:#cff4fc}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#055160;background-color:#badce3}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#055160;border-color:#055160}.list-group-item-warning{color:#664d03;background-color:#fff3cd}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#664d03;background-color:#e6dbb9}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#664d03;border-color:#664d03}.list-group-item-danger{color:#842029;background-color:#f8d7da}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#842029;background-color:#dfc2c4}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#842029;border-color:#842029}.list-group-item-light{color:#636464;background-color:#fefefe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#636464;background-color:#e5e5e5}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#636464;border-color:#636464}.list-group-item-dark{color:#141619;background-color:#d3d3d4}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#141619;background-color:#bebebf}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#141619;border-color:#141619}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:#000;background:transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;border:0;border-radius:.25rem;opacity:.5}.btn-close:hover{color:#000;text-decoration:none;opacity:.75}.btn-close:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25);opacity:1}.btn-close.disabled,.btn-close:disabled{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;opacity:.25}.btn-close-white{filter:invert(1) grayscale(100%) brightness(200%)}.toast{width:350px;max-width:100%;font-size:.875rem;pointer-events:auto;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .5rem 1rem rgba(0,0,0,.15);border-radius:.25rem}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:.75rem}.toast-header{display:flex;align-items:center;padding:.5rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.toast-header .btn-close{margin-right:-.375rem;margin-left:.75rem}.toast-body{padding:.75rem;word-wrap:break-word}.modal{position:fixed;top:0;left:0;z-index:1055;display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - 1rem)}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1050;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;flex-shrink:0;align-items:center;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .btn-close{padding:.5rem .5rem;margin:-.5rem -.5rem -.5rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;padding:1rem}.modal-footer{display:flex;flex-wrap:wrap;flex-shrink:0;align-items:center;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{height:calc(100% - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}.modal-fullscreen .modal-footer{border-radius:0}@media (max-width:575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}.modal-fullscreen-sm-down .modal-footer{border-radius:0}}@media (max-width:767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}.modal-fullscreen-md-down .modal-footer{border-radius:0}}@media (max-width:991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}.modal-fullscreen-lg-down .modal-footer{border-radius:0}}@media (max-width:1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}.modal-fullscreen-xl-down .modal-footer{border-radius:0}}@media (max-width:1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}.modal-fullscreen-xxl-down .modal-footer{border-radius:0}}.tooltip{position:absolute;z-index:1080;display:block;margin:0;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .tooltip-arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[data-popper-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow,.bs-tooltip-top .tooltip-arrow{bottom:0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before,.bs-tooltip-top .tooltip-arrow::before{top:-1px;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[data-popper-placement^=right],.bs-tooltip-end{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow,.bs-tooltip-end .tooltip-arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before,.bs-tooltip-end .tooltip-arrow::before{right:-1px;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[data-popper-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow,.bs-tooltip-bottom .tooltip-arrow{top:0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before,.bs-tooltip-bottom .tooltip-arrow::before{bottom:-1px;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[data-popper-placement^=left],.bs-tooltip-start{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow,.bs-tooltip-start .tooltip-arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before,.bs-tooltip-start .tooltip-arrow::before{left:-1px;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1070;display:block;max-width:276px;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .popover-arrow{position:absolute;display:block;width:1rem;height:.5rem}.popover .popover-arrow::after,.popover .popover-arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow,.bs-popover-top>.popover-arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-top>.popover-arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow,.bs-popover-end>.popover-arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-end>.popover-arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow,.bs-popover-bottom>.popover-arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f0f0f0}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow,.bs-popover-start>.popover-arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-start>.popover-arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem 1rem;margin-bottom:0;font-size:1rem;background-color:#f0f0f0;border-bottom:1px solid rgba(0,0,0,.2);border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:1rem 1rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-end,.carousel-item-next:not(.carousel-item-start){transform:translateX(100%)}.active.carousel-item-start,.carousel-item-prev:not(.carousel-item-end){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:0 0;border:0;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%;list-style:none}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-next-icon,.carousel-dark .carousel-control-prev-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}@-webkit-keyframes spinner-border{to{transform:rotate(360deg)}}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:.75s linear infinite spinner-border;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:.75s linear infinite spinner-grow;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{-webkit-animation-duration:1.5s;animation-duration:1.5s}}.offcanvas{position:fixed;bottom:0;z-index:1045;display:flex;flex-direction:column;max-width:100%;visibility:hidden;background-color:#fff;background-clip:padding-box;outline:0;transition:transform .3s ease-in-out}@media (prefers-reduced-motion:reduce){.offcanvas{transition:none}}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;align-items:center;justify-content:space-between;padding:1rem 1rem}.offcanvas-header .btn-close{padding:.5rem .5rem;margin-top:-.5rem;margin-right:-.5rem;margin-bottom:-.5rem}.offcanvas-title{margin-bottom:0;line-height:1.5}.offcanvas-body{flex-grow:1;padding:1rem 1rem;overflow-y:auto}.offcanvas-start{top:0;left:0;width:400px;border-right:1px solid rgba(0,0,0,.2);transform:translateX(-100%)}.offcanvas-end{top:0;right:0;width:400px;border-left:1px solid rgba(0,0,0,.2);transform:translateX(100%)}.offcanvas-top{top:0;right:0;left:0;height:30vh;max-height:100%;border-bottom:1px solid rgba(0,0,0,.2);transform:translateY(-100%)}.offcanvas-bottom{right:0;left:0;height:30vh;max-height:100%;border-top:1px solid rgba(0,0,0,.2);transform:translateY(100%)}.offcanvas.show{transform:none}.placeholder{display:inline-block;min-height:1em;vertical-align:middle;cursor:wait;background-color:currentColor;opacity:.5}.placeholder.btn::before{display:inline-block;content:""}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{-webkit-animation:placeholder-glow 2s ease-in-out infinite;animation:placeholder-glow 2s ease-in-out infinite}@-webkit-keyframes placeholder-glow{50%{opacity:.2}}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{-webkit-mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);-webkit-mask-size:200% 100%;mask-size:200% 100%;-webkit-animation:placeholder-wave 2s linear infinite;animation:placeholder-wave 2s linear infinite}@-webkit-keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0%;mask-position:-200% 0%}}@keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0%;mask-position:-200% 0%}}.clearfix::after{display:block;clear:both;content:""}.link-primary{color:#0d6efd}.link-primary:focus,.link-primary:hover{color:#0a58ca}.link-secondary{color:#6c757d}.link-secondary:focus,.link-secondary:hover{color:#565e64}.link-success{color:#198754}.link-success:focus,.link-success:hover{color:#146c43}.link-info{color:#0dcaf0}.link-info:focus,.link-info:hover{color:#3dd5f3}.link-warning{color:#ffc107}.link-warning:focus,.link-warning:hover{color:#ffcd39}.link-danger{color:#dc3545}.link-danger:focus,.link-danger:hover{color:#b02a37}.link-light{color:#f8f9fa}.link-light:focus,.link-light:hover{color:#f9fafb}.link-dark{color:#212529}.link-dark:focus,.link-dark:hover{color:#1a1e21}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio:100%}.ratio-4x3{--bs-aspect-ratio:calc(3 / 4 * 100%)}.ratio-16x9{--bs-aspect-ratio:calc(9 / 16 * 100%)}.ratio-21x9{--bs-aspect-ratio:calc(9 / 21 * 100%)}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}@media (min-width:576px){.sticky-sm-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:768px){.sticky-md-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:992px){.sticky-lg-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1200px){.sticky-xl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1400px){.sticky-xxl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.hstack{display:flex;flex-direction:row;align-items:center;align-self:stretch}.vstack{display:flex;flex:1 1 auto;flex-direction:column;align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;width:1px;min-height:1em;background-color:currentColor;opacity:.25}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.float-start{float:left!important}.float-end{float:right!important}.float-none{float:none!important}.opacity-0{opacity:0!important}.opacity-25{opacity:.25!important}.opacity-50{opacity:.5!important}.opacity-75{opacity:.75!important}.opacity-100{opacity:1!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-visible{overflow:visible!important}.overflow-scroll{overflow:scroll!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-grid{display:grid!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}.d-none{display:none!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.top-0{top:0!important}.top-50{top:50%!important}.top-100{top:100%!important}.bottom-0{bottom:0!important}.bottom-50{bottom:50%!important}.bottom-100{bottom:100%!important}.start-0{left:0!important}.start-50{left:50%!important}.start-100{left:100%!important}.end-0{right:0!important}.end-50{right:50%!important}.end-100{right:100%!important}.translate-middle{transform:translate(-50%,-50%)!important}.translate-middle-x{transform:translateX(-50%)!important}.translate-middle-y{transform:translateY(-50%)!important}.border{border:1px solid #dee2e6!important}.border-0{border:0!important}.border-top{border-top:1px solid #dee2e6!important}.border-top-0{border-top:0!important}.border-end{border-right:1px solid #dee2e6!important}.border-end-0{border-right:0!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-bottom-0{border-bottom:0!important}.border-start{border-left:1px solid #dee2e6!important}.border-start-0{border-left:0!important}.border-primary{border-color:#0d6efd!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#198754!important}.border-info{border-color:#0dcaf0!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#212529!important}.border-white{border-color:#fff!important}.border-1{border-width:1px!important}.border-2{border-width:2px!important}.border-3{border-width:3px!important}.border-4{border-width:4px!important}.border-5{border-width:5px!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.mw-100{max-width:100%!important}.vw-100{width:100vw!important}.min-vw-100{min-width:100vw!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mh-100{max-height:100%!important}.vh-100{height:100vh!important}.min-vh-100{min-height:100vh!important}.flex-fill{flex:1 1 auto!important}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.justify-content-evenly{justify-content:space-evenly!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}.order-first{order:-1!important}.order-0{order:0!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.order-5{order:5!important}.order-last{order:6!important}.m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.m-3{margin:1rem!important}.m-4{margin:1.5rem!important}.m-5{margin:3rem!important}.m-auto{margin:auto!important}.mx-0{margin-right:0!important;margin-left:0!important}.mx-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-3{margin-right:1rem!important;margin-left:1rem!important}.mx-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-5{margin-right:3rem!important;margin-left:3rem!important}.mx-auto{margin-right:auto!important;margin-left:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-0{margin-top:0!important}.mt-1{margin-top:.25rem!important}.mt-2{margin-top:.5rem!important}.mt-3{margin-top:1rem!important}.mt-4{margin-top:1.5rem!important}.mt-5{margin-top:3rem!important}.mt-auto{margin-top:auto!important}.me-0{margin-right:0!important}.me-1{margin-right:.25rem!important}.me-2{margin-right:.5rem!important}.me-3{margin-right:1rem!important}.me-4{margin-right:1.5rem!important}.me-5{margin-right:3rem!important}.me-auto{margin-right:auto!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.25rem!important}.mb-2{margin-bottom:.5rem!important}.mb-3{margin-bottom:1rem!important}.mb-4{margin-bottom:1.5rem!important}.mb-5{margin-bottom:3rem!important}.mb-auto{margin-bottom:auto!important}.ms-0{margin-left:0!important}.ms-1{margin-left:.25rem!important}.ms-2{margin-left:.5rem!important}.ms-3{margin-left:1rem!important}.ms-4{margin-left:1.5rem!important}.ms-5{margin-left:3rem!important}.ms-auto{margin-left:auto!important}.p-0{padding:0!important}.p-1{padding:.25rem!important}.p-2{padding:.5rem!important}.p-3{padding:1rem!important}.p-4{padding:1.5rem!important}.p-5{padding:3rem!important}.px-0{padding-right:0!important;padding-left:0!important}.px-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-3{padding-right:1rem!important;padding-left:1rem!important}.px-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-5{padding-right:3rem!important;padding-left:3rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-0{padding-top:0!important}.pt-1{padding-top:.25rem!important}.pt-2{padding-top:.5rem!important}.pt-3{padding-top:1rem!important}.pt-4{padding-top:1.5rem!important}.pt-5{padding-top:3rem!important}.pe-0{padding-right:0!important}.pe-1{padding-right:.25rem!important}.pe-2{padding-right:.5rem!important}.pe-3{padding-right:1rem!important}.pe-4{padding-right:1.5rem!important}.pe-5{padding-right:3rem!important}.pb-0{padding-bottom:0!important}.pb-1{padding-bottom:.25rem!important}.pb-2{padding-bottom:.5rem!important}.pb-3{padding-bottom:1rem!important}.pb-4{padding-bottom:1.5rem!important}.pb-5{padding-bottom:3rem!important}.ps-0{padding-left:0!important}.ps-1{padding-left:.25rem!important}.ps-2{padding-left:.5rem!important}.ps-3{padding-left:1rem!important}.ps-4{padding-left:1.5rem!important}.ps-5{padding-left:3rem!important}.font-monospace{font-family:var(--bs-font-monospace)!important}.fs-1{font-size:calc(1.375rem + 1.5vw)!important}.fs-2{font-size:calc(1.325rem + .9vw)!important}.fs-3{font-size:calc(1.3rem + .6vw)!important}.fs-4{font-size:calc(1.275rem + .3vw)!important}.fs-5{font-size:1.25rem!important}.fs-6{font-size:1rem!important}.fst-italic{font-style:italic!important}.fst-normal{font-style:normal!important}.fw-light{font-weight:300!important}.fw-lighter{font-weight:lighter!important}.fw-normal{font-weight:400!important}.fw-bold{font-weight:700!important}.fw-bolder{font-weight:bolder!important}.lh-1{line-height:1!important}.lh-sm{line-height:1.25!important}.lh-base{line-height:1.5!important}.lh-lg{line-height:2!important}.text-start{text-align:left!important}.text-end{text-align:right!important}.text-center{text-align:center!important}.text-decoration-none{text-decoration:none!important}.text-decoration-underline{text-decoration:underline!important}.text-decoration-line-through{text-decoration:line-through!important}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-primary{--bs-text-opacity:1;color:rgba(var(--bs-primary-rgb),var(--bs-text-opacity))!important}.text-secondary{--bs-text-opacity:1;color:rgba(var(--bs-secondary-rgb),var(--bs-text-opacity))!important}.text-success{--bs-text-opacity:1;color:rgba(var(--bs-success-rgb),var(--bs-text-opacity))!important}.text-info{--bs-text-opacity:1;color:rgba(var(--bs-info-rgb),var(--bs-text-opacity))!important}.text-warning{--bs-text-opacity:1;color:rgba(var(--bs-warning-rgb),var(--bs-text-opacity))!important}.text-danger{--bs-text-opacity:1;color:rgba(var(--bs-danger-rgb),var(--bs-text-opacity))!important}.text-light{--bs-text-opacity:1;color:rgba(var(--bs-light-rgb),var(--bs-text-opacity))!important}.text-dark{--bs-text-opacity:1;color:rgba(var(--bs-dark-rgb),var(--bs-text-opacity))!important}.text-black{--bs-text-opacity:1;color:rgba(var(--bs-black-rgb),var(--bs-text-opacity))!important}.text-white{--bs-text-opacity:1;color:rgba(var(--bs-white-rgb),var(--bs-text-opacity))!important}.text-body{--bs-text-opacity:1;color:rgba(var(--bs-body-rgb),var(--bs-text-opacity))!important}.text-muted{--bs-text-opacity:1;color:#6c757d!important}.text-black-50{--bs-text-opacity:1;color:rgba(0,0,0,.5)!important}.text-white-50{--bs-text-opacity:1;color:rgba(255,255,255,.5)!important}.text-reset{--bs-text-opacity:1;color:inherit!important}.text-opacity-25{--bs-text-opacity:0.25}.text-opacity-50{--bs-text-opacity:0.5}.text-opacity-75{--bs-text-opacity:0.75}.text-opacity-100{--bs-text-opacity:1}.bg-primary{--bs-bg-opacity:1;background-color:rgba(var(--bs-primary-rgb),var(--bs-bg-opacity))!important}.bg-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-rgb),var(--bs-bg-opacity))!important}.bg-success{--bs-bg-opacity:1;background-color:rgba(var(--bs-success-rgb),var(--bs-bg-opacity))!important}.bg-info{--bs-bg-opacity:1;background-color:rgba(var(--bs-info-rgb),var(--bs-bg-opacity))!important}.bg-warning{--bs-bg-opacity:1;background-color:rgba(var(--bs-warning-rgb),var(--bs-bg-opacity))!important}.bg-danger{--bs-bg-opacity:1;background-color:rgba(var(--bs-danger-rgb),var(--bs-bg-opacity))!important}.bg-light{--bs-bg-opacity:1;background-color:rgba(var(--bs-light-rgb),var(--bs-bg-opacity))!important}.bg-dark{--bs-bg-opacity:1;background-color:rgba(var(--bs-dark-rgb),var(--bs-bg-opacity))!important}.bg-black{--bs-bg-opacity:1;background-color:rgba(var(--bs-black-rgb),var(--bs-bg-opacity))!important}.bg-white{--bs-bg-opacity:1;background-color:rgba(var(--bs-white-rgb),var(--bs-bg-opacity))!important}.bg-body{--bs-bg-opacity:1;background-color:rgba(var(--bs-body-rgb),var(--bs-bg-opacity))!important}.bg-transparent{--bs-bg-opacity:1;background-color:transparent!important}.bg-opacity-10{--bs-bg-opacity:0.1}.bg-opacity-25{--bs-bg-opacity:0.25}.bg-opacity-50{--bs-bg-opacity:0.5}.bg-opacity-75{--bs-bg-opacity:0.75}.bg-opacity-100{--bs-bg-opacity:1}.bg-gradient{background-image:var(--bs-gradient)!important}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;user-select:none!important}.pe-none{pointer-events:none!important}.pe-auto{pointer-events:auto!important}.rounded{border-radius:.25rem!important}.rounded-0{border-radius:0!important}.rounded-1{border-radius:.2rem!important}.rounded-2{border-radius:.25rem!important}.rounded-3{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-end{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-start{border-bottom-left-radius:.25rem!important;border-top-left-radius:.25rem!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media (min-width:576px){.float-sm-start{float:left!important}.float-sm-end{float:right!important}.float-sm-none{float:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}.d-sm-none{display:none!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-sm-0{gap:0!important}.gap-sm-1{gap:.25rem!important}.gap-sm-2{gap:.5rem!important}.gap-sm-3{gap:1rem!important}.gap-sm-4{gap:1.5rem!important}.gap-sm-5{gap:3rem!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.justify-content-sm-evenly{justify-content:space-evenly!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}.order-sm-first{order:-1!important}.order-sm-0{order:0!important}.order-sm-1{order:1!important}.order-sm-2{order:2!important}.order-sm-3{order:3!important}.order-sm-4{order:4!important}.order-sm-5{order:5!important}.order-sm-last{order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.25rem!important}.m-sm-2{margin:.5rem!important}.m-sm-3{margin:1rem!important}.m-sm-4{margin:1.5rem!important}.m-sm-5{margin:3rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-right:0!important;margin-left:0!important}.mx-sm-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-sm-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-sm-3{margin-right:1rem!important;margin-left:1rem!important}.mx-sm-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-sm-5{margin-right:3rem!important;margin-left:3rem!important}.mx-sm-auto{margin-right:auto!important;margin-left:auto!important}.my-sm-0{margin-top:0!important;margin-bottom:0!important}.my-sm-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-sm-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-sm-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-sm-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-sm-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-sm-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.25rem!important}.mt-sm-2{margin-top:.5rem!important}.mt-sm-3{margin-top:1rem!important}.mt-sm-4{margin-top:1.5rem!important}.mt-sm-5{margin-top:3rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.25rem!important}.me-sm-2{margin-right:.5rem!important}.me-sm-3{margin-right:1rem!important}.me-sm-4{margin-right:1.5rem!important}.me-sm-5{margin-right:3rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.25rem!important}.mb-sm-2{margin-bottom:.5rem!important}.mb-sm-3{margin-bottom:1rem!important}.mb-sm-4{margin-bottom:1.5rem!important}.mb-sm-5{margin-bottom:3rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.25rem!important}.ms-sm-2{margin-left:.5rem!important}.ms-sm-3{margin-left:1rem!important}.ms-sm-4{margin-left:1.5rem!important}.ms-sm-5{margin-left:3rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.25rem!important}.p-sm-2{padding:.5rem!important}.p-sm-3{padding:1rem!important}.p-sm-4{padding:1.5rem!important}.p-sm-5{padding:3rem!important}.px-sm-0{padding-right:0!important;padding-left:0!important}.px-sm-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-sm-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-sm-3{padding-right:1rem!important;padding-left:1rem!important}.px-sm-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-sm-5{padding-right:3rem!important;padding-left:3rem!important}.py-sm-0{padding-top:0!important;padding-bottom:0!important}.py-sm-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-sm-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-sm-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-sm-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-sm-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.25rem!important}.pt-sm-2{padding-top:.5rem!important}.pt-sm-3{padding-top:1rem!important}.pt-sm-4{padding-top:1.5rem!important}.pt-sm-5{padding-top:3rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.25rem!important}.pe-sm-2{padding-right:.5rem!important}.pe-sm-3{padding-right:1rem!important}.pe-sm-4{padding-right:1.5rem!important}.pe-sm-5{padding-right:3rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.25rem!important}.pb-sm-2{padding-bottom:.5rem!important}.pb-sm-3{padding-bottom:1rem!important}.pb-sm-4{padding-bottom:1.5rem!important}.pb-sm-5{padding-bottom:3rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.25rem!important}.ps-sm-2{padding-left:.5rem!important}.ps-sm-3{padding-left:1rem!important}.ps-sm-4{padding-left:1.5rem!important}.ps-sm-5{padding-left:3rem!important}.text-sm-start{text-align:left!important}.text-sm-end{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.float-md-start{float:left!important}.float-md-end{float:right!important}.float-md-none{float:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}.d-md-none{display:none!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-md-0{gap:0!important}.gap-md-1{gap:.25rem!important}.gap-md-2{gap:.5rem!important}.gap-md-3{gap:1rem!important}.gap-md-4{gap:1.5rem!important}.gap-md-5{gap:3rem!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.justify-content-md-evenly{justify-content:space-evenly!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}.order-md-first{order:-1!important}.order-md-0{order:0!important}.order-md-1{order:1!important}.order-md-2{order:2!important}.order-md-3{order:3!important}.order-md-4{order:4!important}.order-md-5{order:5!important}.order-md-last{order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.25rem!important}.m-md-2{margin:.5rem!important}.m-md-3{margin:1rem!important}.m-md-4{margin:1.5rem!important}.m-md-5{margin:3rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-right:0!important;margin-left:0!important}.mx-md-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-md-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-md-3{margin-right:1rem!important;margin-left:1rem!important}.mx-md-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-md-5{margin-right:3rem!important;margin-left:3rem!important}.mx-md-auto{margin-right:auto!important;margin-left:auto!important}.my-md-0{margin-top:0!important;margin-bottom:0!important}.my-md-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-md-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-md-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-md-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-md-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-md-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.25rem!important}.mt-md-2{margin-top:.5rem!important}.mt-md-3{margin-top:1rem!important}.mt-md-4{margin-top:1.5rem!important}.mt-md-5{margin-top:3rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.25rem!important}.me-md-2{margin-right:.5rem!important}.me-md-3{margin-right:1rem!important}.me-md-4{margin-right:1.5rem!important}.me-md-5{margin-right:3rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.25rem!important}.mb-md-2{margin-bottom:.5rem!important}.mb-md-3{margin-bottom:1rem!important}.mb-md-4{margin-bottom:1.5rem!important}.mb-md-5{margin-bottom:3rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.25rem!important}.ms-md-2{margin-left:.5rem!important}.ms-md-3{margin-left:1rem!important}.ms-md-4{margin-left:1.5rem!important}.ms-md-5{margin-left:3rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.25rem!important}.p-md-2{padding:.5rem!important}.p-md-3{padding:1rem!important}.p-md-4{padding:1.5rem!important}.p-md-5{padding:3rem!important}.px-md-0{padding-right:0!important;padding-left:0!important}.px-md-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-md-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-md-3{padding-right:1rem!important;padding-left:1rem!important}.px-md-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-md-5{padding-right:3rem!important;padding-left:3rem!important}.py-md-0{padding-top:0!important;padding-bottom:0!important}.py-md-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-md-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-md-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-md-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-md-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.25rem!important}.pt-md-2{padding-top:.5rem!important}.pt-md-3{padding-top:1rem!important}.pt-md-4{padding-top:1.5rem!important}.pt-md-5{padding-top:3rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.25rem!important}.pe-md-2{padding-right:.5rem!important}.pe-md-3{padding-right:1rem!important}.pe-md-4{padding-right:1.5rem!important}.pe-md-5{padding-right:3rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.25rem!important}.pb-md-2{padding-bottom:.5rem!important}.pb-md-3{padding-bottom:1rem!important}.pb-md-4{padding-bottom:1.5rem!important}.pb-md-5{padding-bottom:3rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.25rem!important}.ps-md-2{padding-left:.5rem!important}.ps-md-3{padding-left:1rem!important}.ps-md-4{padding-left:1.5rem!important}.ps-md-5{padding-left:3rem!important}.text-md-start{text-align:left!important}.text-md-end{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.float-lg-start{float:left!important}.float-lg-end{float:right!important}.float-lg-none{float:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}.d-lg-none{display:none!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-lg-0{gap:0!important}.gap-lg-1{gap:.25rem!important}.gap-lg-2{gap:.5rem!important}.gap-lg-3{gap:1rem!important}.gap-lg-4{gap:1.5rem!important}.gap-lg-5{gap:3rem!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.justify-content-lg-evenly{justify-content:space-evenly!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}.order-lg-first{order:-1!important}.order-lg-0{order:0!important}.order-lg-1{order:1!important}.order-lg-2{order:2!important}.order-lg-3{order:3!important}.order-lg-4{order:4!important}.order-lg-5{order:5!important}.order-lg-last{order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.25rem!important}.m-lg-2{margin:.5rem!important}.m-lg-3{margin:1rem!important}.m-lg-4{margin:1.5rem!important}.m-lg-5{margin:3rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-right:0!important;margin-left:0!important}.mx-lg-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-lg-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-lg-3{margin-right:1rem!important;margin-left:1rem!important}.mx-lg-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-lg-5{margin-right:3rem!important;margin-left:3rem!important}.mx-lg-auto{margin-right:auto!important;margin-left:auto!important}.my-lg-0{margin-top:0!important;margin-bottom:0!important}.my-lg-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-lg-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-lg-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-lg-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-lg-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-lg-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.25rem!important}.mt-lg-2{margin-top:.5rem!important}.mt-lg-3{margin-top:1rem!important}.mt-lg-4{margin-top:1.5rem!important}.mt-lg-5{margin-top:3rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.25rem!important}.me-lg-2{margin-right:.5rem!important}.me-lg-3{margin-right:1rem!important}.me-lg-4{margin-right:1.5rem!important}.me-lg-5{margin-right:3rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.25rem!important}.mb-lg-2{margin-bottom:.5rem!important}.mb-lg-3{margin-bottom:1rem!important}.mb-lg-4{margin-bottom:1.5rem!important}.mb-lg-5{margin-bottom:3rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.25rem!important}.ms-lg-2{margin-left:.5rem!important}.ms-lg-3{margin-left:1rem!important}.ms-lg-4{margin-left:1.5rem!important}.ms-lg-5{margin-left:3rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.25rem!important}.p-lg-2{padding:.5rem!important}.p-lg-3{padding:1rem!important}.p-lg-4{padding:1.5rem!important}.p-lg-5{padding:3rem!important}.px-lg-0{padding-right:0!important;padding-left:0!important}.px-lg-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-lg-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-lg-3{padding-right:1rem!important;padding-left:1rem!important}.px-lg-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-lg-5{padding-right:3rem!important;padding-left:3rem!important}.py-lg-0{padding-top:0!important;padding-bottom:0!important}.py-lg-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-lg-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-lg-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-lg-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.25rem!important}.pt-lg-2{padding-top:.5rem!important}.pt-lg-3{padding-top:1rem!important}.pt-lg-4{padding-top:1.5rem!important}.pt-lg-5{padding-top:3rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.25rem!important}.pe-lg-2{padding-right:.5rem!important}.pe-lg-3{padding-right:1rem!important}.pe-lg-4{padding-right:1.5rem!important}.pe-lg-5{padding-right:3rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.25rem!important}.pb-lg-2{padding-bottom:.5rem!important}.pb-lg-3{padding-bottom:1rem!important}.pb-lg-4{padding-bottom:1.5rem!important}.pb-lg-5{padding-bottom:3rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.25rem!important}.ps-lg-2{padding-left:.5rem!important}.ps-lg-3{padding-left:1rem!important}.ps-lg-4{padding-left:1.5rem!important}.ps-lg-5{padding-left:3rem!important}.text-lg-start{text-align:left!important}.text-lg-end{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.float-xl-start{float:left!important}.float-xl-end{float:right!important}.float-xl-none{float:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}.d-xl-none{display:none!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xl-0{gap:0!important}.gap-xl-1{gap:.25rem!important}.gap-xl-2{gap:.5rem!important}.gap-xl-3{gap:1rem!important}.gap-xl-4{gap:1.5rem!important}.gap-xl-5{gap:3rem!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.justify-content-xl-evenly{justify-content:space-evenly!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}.order-xl-first{order:-1!important}.order-xl-0{order:0!important}.order-xl-1{order:1!important}.order-xl-2{order:2!important}.order-xl-3{order:3!important}.order-xl-4{order:4!important}.order-xl-5{order:5!important}.order-xl-last{order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.25rem!important}.m-xl-2{margin:.5rem!important}.m-xl-3{margin:1rem!important}.m-xl-4{margin:1.5rem!important}.m-xl-5{margin:3rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-right:0!important;margin-left:0!important}.mx-xl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xl-auto{margin-right:auto!important;margin-left:auto!important}.my-xl-0{margin-top:0!important;margin-bottom:0!important}.my-xl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.25rem!important}.mt-xl-2{margin-top:.5rem!important}.mt-xl-3{margin-top:1rem!important}.mt-xl-4{margin-top:1.5rem!important}.mt-xl-5{margin-top:3rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.25rem!important}.me-xl-2{margin-right:.5rem!important}.me-xl-3{margin-right:1rem!important}.me-xl-4{margin-right:1.5rem!important}.me-xl-5{margin-right:3rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.25rem!important}.mb-xl-2{margin-bottom:.5rem!important}.mb-xl-3{margin-bottom:1rem!important}.mb-xl-4{margin-bottom:1.5rem!important}.mb-xl-5{margin-bottom:3rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.25rem!important}.ms-xl-2{margin-left:.5rem!important}.ms-xl-3{margin-left:1rem!important}.ms-xl-4{margin-left:1.5rem!important}.ms-xl-5{margin-left:3rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.25rem!important}.p-xl-2{padding:.5rem!important}.p-xl-3{padding:1rem!important}.p-xl-4{padding:1.5rem!important}.p-xl-5{padding:3rem!important}.px-xl-0{padding-right:0!important;padding-left:0!important}.px-xl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xl-0{padding-top:0!important;padding-bottom:0!important}.py-xl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.25rem!important}.pt-xl-2{padding-top:.5rem!important}.pt-xl-3{padding-top:1rem!important}.pt-xl-4{padding-top:1.5rem!important}.pt-xl-5{padding-top:3rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.25rem!important}.pe-xl-2{padding-right:.5rem!important}.pe-xl-3{padding-right:1rem!important}.pe-xl-4{padding-right:1.5rem!important}.pe-xl-5{padding-right:3rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.25rem!important}.pb-xl-2{padding-bottom:.5rem!important}.pb-xl-3{padding-bottom:1rem!important}.pb-xl-4{padding-bottom:1.5rem!important}.pb-xl-5{padding-bottom:3rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.25rem!important}.ps-xl-2{padding-left:.5rem!important}.ps-xl-3{padding-left:1rem!important}.ps-xl-4{padding-left:1.5rem!important}.ps-xl-5{padding-left:3rem!important}.text-xl-start{text-align:left!important}.text-xl-end{text-align:right!important}.text-xl-center{text-align:center!important}}@media (min-width:1400px){.float-xxl-start{float:left!important}.float-xxl-end{float:right!important}.float-xxl-none{float:none!important}.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:flex!important}.d-xxl-inline-flex{display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{flex:1 1 auto!important}.flex-xxl-row{flex-direction:row!important}.flex-xxl-column{flex-direction:column!important}.flex-xxl-row-reverse{flex-direction:row-reverse!important}.flex-xxl-column-reverse{flex-direction:column-reverse!important}.flex-xxl-grow-0{flex-grow:0!important}.flex-xxl-grow-1{flex-grow:1!important}.flex-xxl-shrink-0{flex-shrink:0!important}.flex-xxl-shrink-1{flex-shrink:1!important}.flex-xxl-wrap{flex-wrap:wrap!important}.flex-xxl-nowrap{flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xxl-0{gap:0!important}.gap-xxl-1{gap:.25rem!important}.gap-xxl-2{gap:.5rem!important}.gap-xxl-3{gap:1rem!important}.gap-xxl-4{gap:1.5rem!important}.gap-xxl-5{gap:3rem!important}.justify-content-xxl-start{justify-content:flex-start!important}.justify-content-xxl-end{justify-content:flex-end!important}.justify-content-xxl-center{justify-content:center!important}.justify-content-xxl-between{justify-content:space-between!important}.justify-content-xxl-around{justify-content:space-around!important}.justify-content-xxl-evenly{justify-content:space-evenly!important}.align-items-xxl-start{align-items:flex-start!important}.align-items-xxl-end{align-items:flex-end!important}.align-items-xxl-center{align-items:center!important}.align-items-xxl-baseline{align-items:baseline!important}.align-items-xxl-stretch{align-items:stretch!important}.align-content-xxl-start{align-content:flex-start!important}.align-content-xxl-end{align-content:flex-end!important}.align-content-xxl-center{align-content:center!important}.align-content-xxl-between{align-content:space-between!important}.align-content-xxl-around{align-content:space-around!important}.align-content-xxl-stretch{align-content:stretch!important}.align-self-xxl-auto{align-self:auto!important}.align-self-xxl-start{align-self:flex-start!important}.align-self-xxl-end{align-self:flex-end!important}.align-self-xxl-center{align-self:center!important}.align-self-xxl-baseline{align-self:baseline!important}.align-self-xxl-stretch{align-self:stretch!important}.order-xxl-first{order:-1!important}.order-xxl-0{order:0!important}.order-xxl-1{order:1!important}.order-xxl-2{order:2!important}.order-xxl-3{order:3!important}.order-xxl-4{order:4!important}.order-xxl-5{order:5!important}.order-xxl-last{order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.25rem!important}.m-xxl-2{margin:.5rem!important}.m-xxl-3{margin:1rem!important}.m-xxl-4{margin:1.5rem!important}.m-xxl-5{margin:3rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-right:0!important;margin-left:0!important}.mx-xxl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xxl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xxl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xxl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xxl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xxl-auto{margin-right:auto!important;margin-left:auto!important}.my-xxl-0{margin-top:0!important;margin-bottom:0!important}.my-xxl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xxl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xxl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xxl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xxl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xxl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.25rem!important}.mt-xxl-2{margin-top:.5rem!important}.mt-xxl-3{margin-top:1rem!important}.mt-xxl-4{margin-top:1.5rem!important}.mt-xxl-5{margin-top:3rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.25rem!important}.me-xxl-2{margin-right:.5rem!important}.me-xxl-3{margin-right:1rem!important}.me-xxl-4{margin-right:1.5rem!important}.me-xxl-5{margin-right:3rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.25rem!important}.mb-xxl-2{margin-bottom:.5rem!important}.mb-xxl-3{margin-bottom:1rem!important}.mb-xxl-4{margin-bottom:1.5rem!important}.mb-xxl-5{margin-bottom:3rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.25rem!important}.ms-xxl-2{margin-left:.5rem!important}.ms-xxl-3{margin-left:1rem!important}.ms-xxl-4{margin-left:1.5rem!important}.ms-xxl-5{margin-left:3rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.25rem!important}.p-xxl-2{padding:.5rem!important}.p-xxl-3{padding:1rem!important}.p-xxl-4{padding:1.5rem!important}.p-xxl-5{padding:3rem!important}.px-xxl-0{padding-right:0!important;padding-left:0!important}.px-xxl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xxl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xxl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xxl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xxl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xxl-0{padding-top:0!important;padding-bottom:0!important}.py-xxl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xxl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xxl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xxl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xxl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.25rem!important}.pt-xxl-2{padding-top:.5rem!important}.pt-xxl-3{padding-top:1rem!important}.pt-xxl-4{padding-top:1.5rem!important}.pt-xxl-5{padding-top:3rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.25rem!important}.pe-xxl-2{padding-right:.5rem!important}.pe-xxl-3{padding-right:1rem!important}.pe-xxl-4{padding-right:1.5rem!important}.pe-xxl-5{padding-right:3rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.25rem!important}.pb-xxl-2{padding-bottom:.5rem!important}.pb-xxl-3{padding-bottom:1rem!important}.pb-xxl-4{padding-bottom:1.5rem!important}.pb-xxl-5{padding-bottom:3rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.25rem!important}.ps-xxl-2{padding-left:.5rem!important}.ps-xxl-3{padding-left:1rem!important}.ps-xxl-4{padding-left:1.5rem!important}.ps-xxl-5{padding-left:3rem!important}.text-xxl-start{text-align:left!important}.text-xxl-end{text-align:right!important}.text-xxl-center{text-align:center!important}}@media (min-width:1200px){.fs-1{font-size:2.5rem!important}.fs-2{font-size:2rem!important}.fs-3{font-size:1.75rem!important}.fs-4{font-size:1.5rem!important}}@media print{.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-grid{display:grid!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}.d-print-none{display:none!important}} +/*# sourceMappingURL=bootstrap.min.css.map */ diff --git a/backend/FwLite/FwLiteShared/wwwroot/bootstrap/bootstrap.min.css.map b/backend/FwLite/FwLiteShared/wwwroot/bootstrap/bootstrap.min.css.map new file mode 100644 index 000000000..54fb628cc --- /dev/null +++ b/backend/FwLite/FwLiteShared/wwwroot/bootstrap/bootstrap.min.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../scss/bootstrap.scss","../../scss/_root.scss","../../scss/_reboot.scss","dist/css/bootstrap.css","../../scss/vendor/_rfs.scss","../../scss/mixins/_border-radius.scss","../../scss/_type.scss","../../scss/mixins/_lists.scss","../../scss/_images.scss","../../scss/mixins/_image.scss","../../scss/_containers.scss","../../scss/mixins/_container.scss","../../scss/mixins/_breakpoints.scss","../../scss/_grid.scss","../../scss/mixins/_grid.scss","../../scss/_tables.scss","../../scss/mixins/_table-variants.scss","../../scss/forms/_labels.scss","../../scss/forms/_form-text.scss","../../scss/forms/_form-control.scss","../../scss/mixins/_transition.scss","../../scss/mixins/_gradients.scss","../../scss/forms/_form-select.scss","../../scss/forms/_form-check.scss","../../scss/forms/_form-range.scss","../../scss/forms/_floating-labels.scss","../../scss/forms/_input-group.scss","../../scss/mixins/_forms.scss","../../scss/_buttons.scss","../../scss/mixins/_buttons.scss","../../scss/_transitions.scss","../../scss/_dropdown.scss","../../scss/mixins/_caret.scss","../../scss/_button-group.scss","../../scss/_nav.scss","../../scss/_navbar.scss","../../scss/_card.scss","../../scss/_accordion.scss","../../scss/_breadcrumb.scss","../../scss/_pagination.scss","../../scss/mixins/_pagination.scss","../../scss/_badge.scss","../../scss/_alert.scss","../../scss/mixins/_alert.scss","../../scss/_progress.scss","../../scss/_list-group.scss","../../scss/mixins/_list-group.scss","../../scss/_close.scss","../../scss/_toasts.scss","../../scss/_modal.scss","../../scss/mixins/_backdrop.scss","../../scss/_tooltip.scss","../../scss/mixins/_reset-text.scss","../../scss/_popover.scss","../../scss/_carousel.scss","../../scss/mixins/_clearfix.scss","../../scss/_spinners.scss","../../scss/_offcanvas.scss","../../scss/_placeholders.scss","../../scss/helpers/_colored-links.scss","../../scss/helpers/_ratio.scss","../../scss/helpers/_position.scss","../../scss/helpers/_stacks.scss","../../scss/helpers/_visually-hidden.scss","../../scss/mixins/_visually-hidden.scss","../../scss/helpers/_stretched-link.scss","../../scss/helpers/_text-truncation.scss","../../scss/mixins/_text-truncate.scss","../../scss/helpers/_vr.scss","../../scss/mixins/_utilities.scss","../../scss/utilities/_api.scss"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Bootstrap v5.1.0 (https://getbootstrap.com/)\n * Copyright 2011-2021 The Bootstrap Authors\n * Copyright 2011-2021 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n */\n\n// scss-docs-start import-stack\n// Configuration\n@import \"functions\";\n@import \"variables\";\n@import \"mixins\";\n@import \"utilities\";\n\n// Layout & components\n@import \"root\";\n@import \"reboot\";\n@import \"type\";\n@import \"images\";\n@import \"containers\";\n@import \"grid\";\n@import \"tables\";\n@import \"forms\";\n@import \"buttons\";\n@import \"transitions\";\n@import \"dropdown\";\n@import \"button-group\";\n@import \"nav\";\n@import \"navbar\";\n@import \"card\";\n@import \"accordion\";\n@import \"breadcrumb\";\n@import \"pagination\";\n@import \"badge\";\n@import \"alert\";\n@import \"progress\";\n@import \"list-group\";\n@import \"close\";\n@import \"toasts\";\n@import \"modal\";\n@import \"tooltip\";\n@import \"popover\";\n@import \"carousel\";\n@import \"spinners\";\n@import \"offcanvas\";\n@import \"placeholders\";\n\n// Helpers\n@import \"helpers\";\n\n// Utilities\n@import \"utilities/api\";\n// scss-docs-end import-stack\n",":root {\n // Note: Custom variable values only support SassScript inside `#{}`.\n\n // Colors\n //\n // Generate palettes for full colors, grays, and theme colors.\n\n @each $color, $value in $colors {\n --#{$variable-prefix}#{$color}: #{$value};\n }\n\n @each $color, $value in $grays {\n --#{$variable-prefix}gray-#{$color}: #{$value};\n }\n\n @each $color, $value in $theme-colors {\n --#{$variable-prefix}#{$color}: #{$value};\n }\n\n @each $color, $value in $theme-colors-rgb {\n --#{$variable-prefix}#{$color}-rgb: #{$value};\n }\n\n --#{$variable-prefix}white-rgb: #{to-rgb($white)};\n --#{$variable-prefix}black-rgb: #{to-rgb($black)};\n --#{$variable-prefix}body-rgb: #{to-rgb($body-color)};\n\n // Fonts\n\n // Note: Use `inspect` for lists so that quoted items keep the quotes.\n // See https://github.com/sass/sass/issues/2383#issuecomment-336349172\n --#{$variable-prefix}font-sans-serif: #{inspect($font-family-sans-serif)};\n --#{$variable-prefix}font-monospace: #{inspect($font-family-monospace)};\n --#{$variable-prefix}gradient: #{$gradient};\n\n // Root and body\n // stylelint-disable custom-property-empty-line-before\n // scss-docs-start root-body-variables\n @if $font-size-root != null {\n --#{$variable-prefix}root-font-size: #{$font-size-root};\n }\n --#{$variable-prefix}body-font-family: #{$font-family-base};\n --#{$variable-prefix}body-font-size: #{$font-size-base};\n --#{$variable-prefix}body-font-weight: #{$font-weight-base};\n --#{$variable-prefix}body-line-height: #{$line-height-base};\n --#{$variable-prefix}body-color: #{$body-color};\n @if $body-text-align != null {\n --#{$variable-prefix}body-text-align: #{$body-text-align};\n }\n --#{$variable-prefix}body-bg: #{$body-bg};\n // scss-docs-end root-body-variables\n // stylelint-enable custom-property-empty-line-before\n}\n","// stylelint-disable declaration-no-important, selector-no-qualifying-type, property-no-vendor-prefix\n\n\n// Reboot\n//\n// Normalization of HTML elements, manually forked from Normalize.css to remove\n// styles targeting irrelevant browsers while applying new styles.\n//\n// Normalize is licensed MIT. https://github.com/necolas/normalize.css\n\n\n// Document\n//\n// Change from `box-sizing: content-box` so that `width` is not affected by `padding` or `border`.\n\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\n\n// Root\n//\n// Ability to the value of the root font sizes, affecting the value of `rem`.\n// null by default, thus nothing is generated.\n\n:root {\n @if $font-size-root != null {\n font-size: var(--#{$variable-prefix}-root-font-size);\n }\n\n @if $enable-smooth-scroll {\n @media (prefers-reduced-motion: no-preference) {\n scroll-behavior: smooth;\n }\n }\n}\n\n\n// Body\n//\n// 1. Remove the margin in all browsers.\n// 2. As a best practice, apply a default `background-color`.\n// 3. Prevent adjustments of font size after orientation changes in iOS.\n// 4. Change the default tap highlight to be completely transparent in iOS.\n\n// scss-docs-start reboot-body-rules\nbody {\n margin: 0; // 1\n font-family: var(--#{$variable-prefix}body-font-family);\n @include font-size(var(--#{$variable-prefix}body-font-size));\n font-weight: var(--#{$variable-prefix}body-font-weight);\n line-height: var(--#{$variable-prefix}body-line-height);\n color: var(--#{$variable-prefix}body-color);\n text-align: var(--#{$variable-prefix}body-text-align);\n background-color: var(--#{$variable-prefix}body-bg); // 2\n -webkit-text-size-adjust: 100%; // 3\n -webkit-tap-highlight-color: rgba($black, 0); // 4\n}\n// scss-docs-end reboot-body-rules\n\n\n// Content grouping\n//\n// 1. Reset Firefox's gray color\n// 2. Set correct height and prevent the `size` attribute to make the `hr` look like an input field\n\nhr {\n margin: $hr-margin-y 0;\n color: $hr-color; // 1\n background-color: currentColor;\n border: 0;\n opacity: $hr-opacity;\n}\n\nhr:not([size]) {\n height: $hr-height; // 2\n}\n\n\n// Typography\n//\n// 1. Remove top margins from headings\n// By default, `

`-`

` all receive top and bottom margins. We nuke the top\n// margin for easier control within type scales as it avoids margin collapsing.\n\n%heading {\n margin-top: 0; // 1\n margin-bottom: $headings-margin-bottom;\n font-family: $headings-font-family;\n font-style: $headings-font-style;\n font-weight: $headings-font-weight;\n line-height: $headings-line-height;\n color: $headings-color;\n}\n\nh1 {\n @extend %heading;\n @include font-size($h1-font-size);\n}\n\nh2 {\n @extend %heading;\n @include font-size($h2-font-size);\n}\n\nh3 {\n @extend %heading;\n @include font-size($h3-font-size);\n}\n\nh4 {\n @extend %heading;\n @include font-size($h4-font-size);\n}\n\nh5 {\n @extend %heading;\n @include font-size($h5-font-size);\n}\n\nh6 {\n @extend %heading;\n @include font-size($h6-font-size);\n}\n\n\n// Reset margins on paragraphs\n//\n// Similarly, the top margin on `

`s get reset. However, we also reset the\n// bottom margin to use `rem` units instead of `em`.\n\np {\n margin-top: 0;\n margin-bottom: $paragraph-margin-bottom;\n}\n\n\n// Abbreviations\n//\n// 1. Duplicate behavior to the data-bs-* attribute for our tooltip plugin\n// 2. Add the correct text decoration in Chrome, Edge, Opera, and Safari.\n// 3. Add explicit cursor to indicate changed behavior.\n// 4. Prevent the text-decoration to be skipped.\n\nabbr[title],\nabbr[data-bs-original-title] { // 1\n text-decoration: underline dotted; // 2\n cursor: help; // 3\n text-decoration-skip-ink: none; // 4\n}\n\n\n// Address\n\naddress {\n margin-bottom: 1rem;\n font-style: normal;\n line-height: inherit;\n}\n\n\n// Lists\n\nol,\nul {\n padding-left: 2rem;\n}\n\nol,\nul,\ndl {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nol ol,\nul ul,\nol ul,\nul ol {\n margin-bottom: 0;\n}\n\ndt {\n font-weight: $dt-font-weight;\n}\n\n// 1. Undo browser default\n\ndd {\n margin-bottom: .5rem;\n margin-left: 0; // 1\n}\n\n\n// Blockquote\n\nblockquote {\n margin: 0 0 1rem;\n}\n\n\n// Strong\n//\n// Add the correct font weight in Chrome, Edge, and Safari\n\nb,\nstrong {\n font-weight: $font-weight-bolder;\n}\n\n\n// Small\n//\n// Add the correct font size in all browsers\n\nsmall {\n @include font-size($small-font-size);\n}\n\n\n// Mark\n\nmark {\n padding: $mark-padding;\n background-color: $mark-bg;\n}\n\n\n// Sub and Sup\n//\n// Prevent `sub` and `sup` elements from affecting the line height in\n// all browsers.\n\nsub,\nsup {\n position: relative;\n @include font-size($sub-sup-font-size);\n line-height: 0;\n vertical-align: baseline;\n}\n\nsub { bottom: -.25em; }\nsup { top: -.5em; }\n\n\n// Links\n\na {\n color: $link-color;\n text-decoration: $link-decoration;\n\n &:hover {\n color: $link-hover-color;\n text-decoration: $link-hover-decoration;\n }\n}\n\n// And undo these styles for placeholder links/named anchors (without href).\n// It would be more straightforward to just use a[href] in previous block, but that\n// causes specificity issues in many other styles that are too complex to fix.\n// See https://github.com/twbs/bootstrap/issues/19402\n\na:not([href]):not([class]) {\n &,\n &:hover {\n color: inherit;\n text-decoration: none;\n }\n}\n\n\n// Code\n\npre,\ncode,\nkbd,\nsamp {\n font-family: $font-family-code;\n @include font-size(1em); // Correct the odd `em` font sizing in all browsers.\n direction: ltr #{\"/* rtl:ignore */\"};\n unicode-bidi: bidi-override;\n}\n\n// 1. Remove browser default top margin\n// 2. Reset browser default of `1em` to use `rem`s\n// 3. Don't allow content to break outside\n\npre {\n display: block;\n margin-top: 0; // 1\n margin-bottom: 1rem; // 2\n overflow: auto; // 3\n @include font-size($code-font-size);\n color: $pre-color;\n\n // Account for some code outputs that place code tags in pre tags\n code {\n @include font-size(inherit);\n color: inherit;\n word-break: normal;\n }\n}\n\ncode {\n @include font-size($code-font-size);\n color: $code-color;\n word-wrap: break-word;\n\n // Streamline the style when inside anchors to avoid broken underline and more\n a > & {\n color: inherit;\n }\n}\n\nkbd {\n padding: $kbd-padding-y $kbd-padding-x;\n @include font-size($kbd-font-size);\n color: $kbd-color;\n background-color: $kbd-bg;\n @include border-radius($border-radius-sm);\n\n kbd {\n padding: 0;\n @include font-size(1em);\n font-weight: $nested-kbd-font-weight;\n }\n}\n\n\n// Figures\n//\n// Apply a consistent margin strategy (matches our type styles).\n\nfigure {\n margin: 0 0 1rem;\n}\n\n\n// Images and content\n\nimg,\nsvg {\n vertical-align: middle;\n}\n\n\n// Tables\n//\n// Prevent double borders\n\ntable {\n caption-side: bottom;\n border-collapse: collapse;\n}\n\ncaption {\n padding-top: $table-cell-padding-y;\n padding-bottom: $table-cell-padding-y;\n color: $table-caption-color;\n text-align: left;\n}\n\n// 1. Removes font-weight bold by inheriting\n// 2. Matches default `` alignment by inheriting `text-align`.\n// 3. Fix alignment for Safari\n\nth {\n font-weight: $table-th-font-weight; // 1\n text-align: inherit; // 2\n text-align: -webkit-match-parent; // 3\n}\n\nthead,\ntbody,\ntfoot,\ntr,\ntd,\nth {\n border-color: inherit;\n border-style: solid;\n border-width: 0;\n}\n\n\n// Forms\n//\n// 1. Allow labels to use `margin` for spacing.\n\nlabel {\n display: inline-block; // 1\n}\n\n// Remove the default `border-radius` that macOS Chrome adds.\n// See https://github.com/twbs/bootstrap/issues/24093\n\nbutton {\n // stylelint-disable-next-line property-disallowed-list\n border-radius: 0;\n}\n\n// Explicitly remove focus outline in Chromium when it shouldn't be\n// visible (e.g. as result of mouse click or touch tap). It already\n// should be doing this automatically, but seems to currently be\n// confused and applies its very visible two-tone outline anyway.\n\nbutton:focus:not(:focus-visible) {\n outline: 0;\n}\n\n// 1. Remove the margin in Firefox and Safari\n\ninput,\nbutton,\nselect,\noptgroup,\ntextarea {\n margin: 0; // 1\n font-family: inherit;\n @include font-size(inherit);\n line-height: inherit;\n}\n\n// Remove the inheritance of text transform in Firefox\nbutton,\nselect {\n text-transform: none;\n}\n// Set the cursor for non-` + {:else} - + {/if} {@const serverProjects = remoteProjects[server.authority]?.filter(p => p.crdt) ?? []} From 9c81f76ac05a33f5328eccda0e24a59357d97ec0 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Mon, 9 Dec 2024 09:38:26 +0700 Subject: [PATCH 021/108] remove unused constructor fields on entry component change --- .../FwLite/LcmCrdt/Changes/Entries/AddEntryComponentChange.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/backend/FwLite/LcmCrdt/Changes/Entries/AddEntryComponentChange.cs b/backend/FwLite/LcmCrdt/Changes/Entries/AddEntryComponentChange.cs index f93630058..f12bb9d85 100644 --- a/backend/FwLite/LcmCrdt/Changes/Entries/AddEntryComponentChange.cs +++ b/backend/FwLite/LcmCrdt/Changes/Entries/AddEntryComponentChange.cs @@ -16,9 +16,7 @@ public class AddEntryComponentChange : CreateChange, ISelf [JsonConstructor] public AddEntryComponentChange(Guid entityId, Guid complexFormEntryId, - string? complexFormHeadword, Guid componentEntryId, - string? componentHeadword, Guid? componentSenseId = null) : base(entityId) { ComplexFormEntryId = complexFormEntryId; @@ -28,9 +26,7 @@ public AddEntryComponentChange(Guid entityId, public AddEntryComponentChange(ComplexFormComponent component) : this(component.Id == default ? Guid.NewGuid() : component.Id, component.ComplexFormEntryId, - component.ComplexFormHeadword, component.ComponentEntryId, - component.ComponentHeadword, component.ComponentSenseId) { } From bdba9d712273c5a20458ea8cdad29c5195f12b09 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Mon, 9 Dec 2024 09:38:47 +0700 Subject: [PATCH 022/108] populate datacache when injecting a crdt project to the active scope --- backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs b/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs index fcae16d11..31849c51b 100644 --- a/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs +++ b/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs @@ -56,6 +56,7 @@ public async Task SetService(DotnetService service, object serviceInstance) public async Task InjectCrdtProject(string projectName) { crdtProjectsService.SetActiveProject(projectName); + await serviceProvider.GetRequiredService().PopulateProjectDataCache(); var service = new MiniLcmJsInvokable(serviceProvider.GetRequiredService()); await SetService(DotnetService.MiniLcmApi, service); return Defer.Async(async () => await jsRuntime.InvokeVoidAsync("setOverrideService", DotnetService.MiniLcmApi.ToString(), null)); From 7f8ecf2b9014e59c4558ff2c5d35a74747a0cbb0 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Mon, 9 Dec 2024 09:44:34 +0700 Subject: [PATCH 023/108] configure oauth client to support protocol handler on android to allow callbacks to return to the app --- .../FwLite/FwLiteDesktop/FwLiteDesktop.csproj | 30 ++++++++----------- .../FwLiteDesktop/FwLiteDesktopKernel.cs | 4 ++- .../Platforms/Android/MainActivity.cs | 30 ++++++++++++++++++- .../FwLite/FwLiteShared/Auth/AuthConfig.cs | 5 +++- .../FwLite/FwLiteShared/Auth/OAuthClient.cs | 16 +++++++--- .../FwLite/FwLiteShared/Auth/OAuthService.cs | 8 +++-- backend/LexData/SeedingData.cs | 3 +- 7 files changed, 69 insertions(+), 27 deletions(-) diff --git a/backend/FwLite/FwLiteDesktop/FwLiteDesktop.csproj b/backend/FwLite/FwLiteDesktop/FwLiteDesktop.csproj index 1bdc67e46..7e86391d2 100644 --- a/backend/FwLite/FwLiteDesktop/FwLiteDesktop.csproj +++ b/backend/FwLite/FwLiteDesktop/FwLiteDesktop.csproj @@ -1,10 +1,8 @@ - - net9.0-windows10.0.19041.0 - - + net9.0-android;net9.0-ios;net9.0-maccatalyst + $(TargetFrameworks);net9.0-windows10.0.19041.0 @@ -22,8 +20,8 @@ enable false enable - true - true + + FieldWorks Lite @@ -35,15 +33,16 @@ 1.0 1 - 11.0 - 13.1 - 21.0 - 10.0.17763.0 - 10.0.17763.0 - 6.5 + 15.0 + 15.0 + 24.0 + 10.0.17763.0 + 10.0.17763.0 + 6.5 + None + - None false @@ -53,10 +52,6 @@ partial - - true - true - @@ -81,6 +76,7 @@ + diff --git a/backend/FwLite/FwLiteDesktop/FwLiteDesktopKernel.cs b/backend/FwLite/FwLiteDesktop/FwLiteDesktopKernel.cs index 41eb523fe..cf2665c40 100644 --- a/backend/FwLite/FwLiteDesktop/FwLiteDesktopKernel.cs +++ b/backend/FwLite/FwLiteDesktop/FwLiteDesktopKernel.cs @@ -24,7 +24,6 @@ public static void AddFwLiteDesktopServices(this IServiceCollection services, new(new("https://lexbox.dev.languagetechnology.org"), "Lexbox Dev"), new(new("https://staging.languagedepot.org"), "Lexbox Staging") ]); - services.Configure(c => c.ClientId = "becf2856-0690-434b-b192-a4032b72067f"); string environment = "Production"; #if DEBUG @@ -33,6 +32,9 @@ public static void AddFwLiteDesktopServices(this IServiceCollection services, var env = new HostingEnvironment() { EnvironmentName = environment }; services.AddSingleton(env); services.AddFwLiteShared(env); +#if ANDROID + services.Configure(config => config.ParentActivityOrWindow = Platform.CurrentActivity); +#endif var defaultDataPath = IsPackagedApp ? FileSystem.AppDataDirectory : Directory.GetCurrentDirectory(); var baseDataPath = Path.GetFullPath(configuration.GetSection("FwLiteDesktop").GetValue("BaseDataDir") ?? defaultDataPath); diff --git a/backend/FwLite/FwLiteDesktop/Platforms/Android/MainActivity.cs b/backend/FwLite/FwLiteDesktop/Platforms/Android/MainActivity.cs index bdfcb0c8d..10206b2fe 100644 --- a/backend/FwLite/FwLiteDesktop/Platforms/Android/MainActivity.cs +++ b/backend/FwLite/FwLiteDesktop/Platforms/Android/MainActivity.cs @@ -1,9 +1,37 @@ using Android.App; +using Android.Content; using Android.Content.PM; using Android.OS; +using FwLiteShared.Auth; +using Microsoft.Identity.Client; namespace FwLiteDesktop; -[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)] + +[Activity(Theme = "@style/Maui.SplashTheme", + MainLauncher = true, + LaunchMode = LaunchMode.SingleTop, + ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | + ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)] public class MainActivity : MauiAppCompatActivity { + protected override void OnActivityResult(int requestCode, Result resultCode, Intent? data) + { + base.OnActivityResult(requestCode, resultCode, data); + AuthenticationContinuationHelper.SetAuthenticationContinuationEventArgs(requestCode, resultCode, data); + } +} + + +[Activity(Exported = true)] +[IntentFilter(new[] { Intent.ActionView }, + Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault }, + DataHost = "auth", + DataScheme = "msal" + AuthConfig.DefaultClientId)] +public class MsalActivity : BrowserTabActivity +{ + protected override void OnActivityResult(int requestCode, Result resultCode, Intent? data) + { + base.OnActivityResult(requestCode, resultCode, data); + AuthenticationContinuationHelper.SetAuthenticationContinuationEventArgs(requestCode, resultCode, data); + } } diff --git a/backend/FwLite/FwLiteShared/Auth/AuthConfig.cs b/backend/FwLite/FwLiteShared/Auth/AuthConfig.cs index 92f3dcf11..2c20387b1 100644 --- a/backend/FwLite/FwLiteShared/Auth/AuthConfig.cs +++ b/backend/FwLite/FwLiteShared/Auth/AuthConfig.cs @@ -5,11 +5,14 @@ namespace FwLiteShared.Auth; public class AuthConfig { + public const string DefaultClientId = "becf2856-0690-434b-b192-a4032b72067f"; [Required] public required LexboxServer[] LexboxServers { get; set; } - public required string ClientId { get; set; } + + public required string ClientId { get; set; } = DefaultClientId; public string CacheFileName { get; set; } = Path.GetFullPath("msal.json"); public bool SystemWebViewLogin { get; set; } = false; + public object? ParentActivityOrWindow { get; set; } public LexboxServer DefaultServer => LexboxServers.First(); public LexboxServer GetServerByAuthority(string authority) diff --git a/backend/FwLite/FwLiteShared/Auth/OAuthClient.cs b/backend/FwLite/FwLiteShared/Auth/OAuthClient.cs index 95fed3d23..a4e258389 100644 --- a/backend/FwLite/FwLiteShared/Auth/OAuthClient.cs +++ b/backend/FwLite/FwLiteShared/Auth/OAuthClient.cs @@ -47,13 +47,21 @@ public OAuthClient(LoggerAdapter loggerAdapter, : redirectUrlProvider?.GetRedirectUrl() ?? throw new InvalidOperationException("No IRedirectUrlProvider configured, required for non-system web view login"); //todo configure token cache as seen here //https://github.com/AzureAD/microsoft-authentication-extensions-for-dotnet/wiki/Cross-platform-Token-Cache - _application = PublicClientApplicationBuilder.Create(options.Value.ClientId) + var builder = PublicClientApplicationBuilder.Create(options.Value.ClientId) .WithExperimentalFeatures() .WithLogging(loggerAdapter, hostEnvironment?.IsDevelopment() ?? false) .WithHttpClientFactory(new HttpClientFactoryAdapter(httpMessageHandlerFactory)) - .WithRedirectUri(RedirectUrl) - .WithOidcAuthority(lexboxServer.Authority.ToString()) - .Build(); + .WithParentActivityOrWindow(() => options.Value.ParentActivityOrWindow) + .WithOidcAuthority(lexboxServer.Authority.ToString()); + if (!options.Value.SystemWebViewLogin) + { + builder.WithRedirectUri(RedirectUrl); + } + else + { + builder.WithDefaultRedirectUri(); + } + _application = builder.Build(); _ = MsalCacheHelper.CreateAsync(BuildCacheProperties(options.Value.CacheFileName)).ContinueWith( task => { diff --git a/backend/FwLite/FwLiteShared/Auth/OAuthService.cs b/backend/FwLite/FwLiteShared/Auth/OAuthService.cs index aca1a451c..4de28e3ad 100644 --- a/backend/FwLite/FwLiteShared/Auth/OAuthService.cs +++ b/backend/FwLite/FwLiteShared/Auth/OAuthService.cs @@ -34,7 +34,8 @@ public async Task SubmitLoginRequest(IPublicClientApplication appl } //step 1 - var uri = await request.GetAuthUri(applicationLifetime?.ApplicationStopping.Merge(cancellation) ?? cancellation); + var uri = await request.GetAuthUri(applicationLifetime?.ApplicationStopping.Merge(cancellation) ?? + cancellation); //step 4 if (request.State is null) throw new InvalidOperationException("State is null"); _oAuthLoginRequests[request.State] = request; @@ -45,6 +46,7 @@ private async Task HandleSystemWebViewLogin(IPublicClientApplication application { var result = await application.AcquireTokenInteractive(OAuthClient.DefaultScopes) .WithUseEmbeddedWebView(false) + .WithParentActivityOrWindow(options.Value.ParentActivityOrWindow) .WithSystemWebViewOptions(new() { }) .ExecuteAsync(cancellation); } @@ -58,7 +60,9 @@ private async Task HandleSystemWebViewLogin(IPublicClientApplication application throw new InvalidOperationException("Invalid state"); //step 5 request.SetReturnUri(uri); - return (await request.GetAuthenticationResult(applicationLifetime?.ApplicationStopping.Merge(cancellation) ?? cancellation), + return ( + await request.GetAuthenticationResult(applicationLifetime?.ApplicationStopping.Merge(cancellation) ?? + cancellation), request.ClientReturnUrl); //step 8 } diff --git a/backend/LexData/SeedingData.cs b/backend/LexData/SeedingData.cs index 553699e8b..5505176ac 100644 --- a/backend/LexData/SeedingData.cs +++ b/backend/LexData/SeedingData.cs @@ -302,7 +302,8 @@ async ValueTask UpdateApp(object dbApp, OpenIddictApplicationDescriptor seedApp, RedirectUris = { new Uri("http://localhost/api/auth/oauth-callback"), new Uri("http://127.0.0.1/api/auth/oauth-callback"), - new Uri("http://localhost")//handles system web view case + new Uri("http://localhost"),//handles system web view case + new Uri("msalbecf2856-0690-434b-b192-a4032b72067f://auth")//handles android web view callback } } ], a => a.ClientId ?? throw new InvalidOperationException("ClientId is null")); From dc14a6e62bf817df18955366f80aee3419e8c0e9 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Mon, 9 Dec 2024 09:45:06 +0700 Subject: [PATCH 024/108] fix port forwarding for staging --- deployment/Taskfile.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deployment/Taskfile.yml b/deployment/Taskfile.yml index 990a841a0..8c3a7d263 100644 --- a/deployment/Taskfile.yml +++ b/deployment/Taskfile.yml @@ -79,11 +79,11 @@ tasks: staging-db-forward: cmds: - - kubectl port-forward service/db 5434:5432 -n languagedepot --context dallas-rke + - kubectl port-forward service/db 5434:5432 -n languagedepot --context dallas-stage develop-db-forward: cmds: - - kubectl port-forward service/db 5436:5432 -n languagedepot-dev --context dallas-rke + - kubectl port-forward service/db 5436:5432 -n languagedepot-dev --context dallas-stage prod-db-forward: cmds: @@ -93,7 +93,7 @@ tasks: diff-staging: dir: ./staging cmds: - - kubectl diff -k . --context dallas-rke + - kubectl diff -k . --context dallas-stage print-staging: dir: ./staging From 6eb33aae476226da4007dfd3d0bdbdc6cba2dfb1 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Fri, 13 Dec 2024 10:24:50 +0700 Subject: [PATCH 025/108] refactor some service setup after merge --- backend/FwLite/FwLiteDesktop/FwLiteDesktop.csproj | 7 +++---- backend/FwLite/FwLiteDesktop/FwLiteDesktopKernel.cs | 6 ++++++ backend/FwLite/FwLiteDesktop/MauiProgram.cs | 9 --------- .../FwLiteDesktop/Platforms/Windows/WindowsKernel.cs | 6 +++--- 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/backend/FwLite/FwLiteDesktop/FwLiteDesktop.csproj b/backend/FwLite/FwLiteDesktop/FwLiteDesktop.csproj index 6096aa890..5c57fa398 100644 --- a/backend/FwLite/FwLiteDesktop/FwLiteDesktop.csproj +++ b/backend/FwLite/FwLiteDesktop/FwLiteDesktop.csproj @@ -20,8 +20,8 @@ enable false enable - - + true + true FieldWorks Lite @@ -39,10 +39,9 @@ 10.0.19041.0 10.0.19041.0 6.5 - None - + None false diff --git a/backend/FwLite/FwLiteDesktop/FwLiteDesktopKernel.cs b/backend/FwLite/FwLiteDesktop/FwLiteDesktopKernel.cs index e94ee8fcf..c780d828b 100644 --- a/backend/FwLite/FwLiteDesktop/FwLiteDesktopKernel.cs +++ b/backend/FwLite/FwLiteDesktop/FwLiteDesktopKernel.cs @@ -29,10 +29,16 @@ public static void AddFwLiteDesktopServices(this IServiceCollection services, string environment = "Production"; #if DEBUG environment = "Development"; + services.AddBlazorWebViewDeveloperTools(); #endif var env = new HostingEnvironment() { EnvironmentName = environment }; services.AddSingleton(env); services.AddFwLiteShared(env); + services.AddMauiBlazorWebView(); + +#if WINDOWS + services.AddFwLiteWindows(); +#endif #if ANDROID services.Configure(config => config.ParentActivityOrWindow = Platform.CurrentActivity); #endif diff --git a/backend/FwLite/FwLiteDesktop/MauiProgram.cs b/backend/FwLite/FwLiteDesktop/MauiProgram.cs index 1350c69f0..2d32a0283 100644 --- a/backend/FwLite/FwLiteDesktop/MauiProgram.cs +++ b/backend/FwLite/FwLiteDesktop/MauiProgram.cs @@ -30,15 +30,6 @@ public static MauiApp CreateMauiApp() { essentialsBuilder.UseVersionTracking(); }); - #if WINDOWS - builder.AddFwLiteWindows(); - #endif - builder.Services.AddMauiBlazorWebView(); -#if DEBUG - builder.Services.AddBlazorWebViewDeveloperTools(); - builder.Logging.AddDebug(); -#endif - Directory.CreateDirectory(FileSystem.AppDataDirectory); builder.Services.AddFwLiteDesktopServices(builder.Configuration, builder.Logging); var app = builder.Build(); diff --git a/backend/FwLite/FwLiteDesktop/Platforms/Windows/WindowsKernel.cs b/backend/FwLite/FwLiteDesktop/Platforms/Windows/WindowsKernel.cs index 675e1faa4..f10949ea7 100644 --- a/backend/FwLite/FwLiteDesktop/Platforms/Windows/WindowsKernel.cs +++ b/backend/FwLite/FwLiteDesktop/Platforms/Windows/WindowsKernel.cs @@ -4,13 +4,13 @@ namespace FwLiteDesktop; public static class WindowsKernel { - public static void AddFwLiteWindows(this MauiAppBuilder builder) + public static void AddFwLiteWindows(this IServiceCollection services) { if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; if (FwLiteDesktopKernel.IsPackagedApp) { - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); } } } From bc080cb7ad2f3abab68269f31895a7b52dc0063e Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Fri, 13 Dec 2024 10:27:30 +0700 Subject: [PATCH 026/108] refactor IsPackagedApp into IsPortableApp --- backend/FwLite/FwLiteDesktop/FwLiteDesktopKernel.cs | 9 ++++++--- .../FwLiteDesktop/Platforms/Windows/WindowsKernel.cs | 2 +- .../Platforms/Windows/WindowsShortcutService.cs | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/backend/FwLite/FwLiteDesktop/FwLiteDesktopKernel.cs b/backend/FwLite/FwLiteDesktop/FwLiteDesktopKernel.cs index c780d828b..96a8eb1d1 100644 --- a/backend/FwLite/FwLiteDesktop/FwLiteDesktopKernel.cs +++ b/backend/FwLite/FwLiteDesktop/FwLiteDesktopKernel.cs @@ -43,7 +43,7 @@ public static void AddFwLiteDesktopServices(this IServiceCollection services, services.Configure(config => config.ParentActivityOrWindow = Platform.CurrentActivity); #endif - var defaultDataPath = IsPackagedApp ? FileSystem.AppDataDirectory : Directory.GetCurrentDirectory(); + var defaultDataPath = IsPortableApp ? Directory.GetCurrentDirectory() : FileSystem.AppDataDirectory; var baseDataPath = Path.GetFullPath(configuration.GetSection("FwLiteDesktop").GetValue("BaseDataDir") ?? defaultDataPath); Directory.CreateDirectory(baseDataPath); @@ -83,8 +83,11 @@ public static void AddFwLiteDesktopServices(this IServiceCollection services, return false; }); - public static bool IsPackagedApp => IsPackagedAppLazy.Value; + public static bool IsPortableApp => !IsPackagedAppLazy.Value; #else - private static bool IsPackagedApp => true; + ///

+ /// indicates that the app is running in portable mode and it should put log files and data in the current directory + /// + public static bool IsPortableApp => false; #endif } diff --git a/backend/FwLite/FwLiteDesktop/Platforms/Windows/WindowsKernel.cs b/backend/FwLite/FwLiteDesktop/Platforms/Windows/WindowsKernel.cs index f10949ea7..b8a66d880 100644 --- a/backend/FwLite/FwLiteDesktop/Platforms/Windows/WindowsKernel.cs +++ b/backend/FwLite/FwLiteDesktop/Platforms/Windows/WindowsKernel.cs @@ -7,7 +7,7 @@ public static class WindowsKernel public static void AddFwLiteWindows(this IServiceCollection services) { if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; - if (FwLiteDesktopKernel.IsPackagedApp) + if (!FwLiteDesktopKernel.IsPortableApp) { services.AddSingleton(); services.AddSingleton(); diff --git a/backend/FwLite/FwLiteDesktop/Platforms/Windows/WindowsShortcutService.cs b/backend/FwLite/FwLiteDesktop/Platforms/Windows/WindowsShortcutService.cs index e757bf66b..b58effb69 100644 --- a/backend/FwLite/FwLiteDesktop/Platforms/Windows/WindowsShortcutService.cs +++ b/backend/FwLite/FwLiteDesktop/Platforms/Windows/WindowsShortcutService.cs @@ -9,7 +9,7 @@ public class WindowsShortcutService(IVersionTracking versionTracking) : IMauiIni { public void Initialize(IServiceProvider services) { - if (!FwLiteDesktopKernel.IsPackagedApp || !versionTracking.IsFirstLaunchEver || !RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; + if (FwLiteDesktopKernel.IsPortableApp || !versionTracking.IsFirstLaunchEver || !RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; var package = Package.Current; IShellLink link = (IShellLink)new ShellLink(); link.SetPath($@"shell:AppsFolder\{package.Id.FamilyName}!App"); From 34c2552daaa0cc260923cea4183a7152e5795d15 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Fri, 13 Dec 2024 10:42:15 +0700 Subject: [PATCH 027/108] refactor FwLiteProvider to use constant for js function name and simplify removing a service --- .../{EmptyLayout.razor => SvelteLayout.razor} | 2 +- .../FwLiteShared/Pages/CrdtProject.razor | 3 +- .../FwLiteShared/Pages/FwdataProject.razor | 3 +- backend/FwLite/FwLiteShared/Pages/Home.razor | 2 +- backend/FwLite/FwLiteShared/Routes.razor | 2 +- .../FwLiteShared/Services/FwLiteProvider.cs | 29 ++++++++++++------- 6 files changed, 24 insertions(+), 17 deletions(-) rename backend/FwLite/FwLiteShared/Layout/{EmptyLayout.razor => SvelteLayout.razor} (94%) diff --git a/backend/FwLite/FwLiteShared/Layout/EmptyLayout.razor b/backend/FwLite/FwLiteShared/Layout/SvelteLayout.razor similarity index 94% rename from backend/FwLite/FwLiteShared/Layout/EmptyLayout.razor rename to backend/FwLite/FwLiteShared/Layout/SvelteLayout.razor index add30eda7..592d57147 100644 --- a/backend/FwLite/FwLiteShared/Layout/EmptyLayout.razor +++ b/backend/FwLite/FwLiteShared/Layout/SvelteLayout.razor @@ -7,7 +7,7 @@ -
+
@Body diff --git a/frontend/viewer/index.html b/frontend/viewer/index.html index fa9de6784..6ea52d850 100644 --- a/frontend/viewer/index.html +++ b/frontend/viewer/index.html @@ -14,7 +14,7 @@ -
+
diff --git a/frontend/viewer/src/main.ts b/frontend/viewer/src/main.ts index 6b68f1397..055068464 100644 --- a/frontend/viewer/src/main.ts +++ b/frontend/viewer/src/main.ts @@ -7,7 +7,7 @@ import {setupDotnetServiceProvider} from './lib/services/service-provider-dotnet setupDotnetServiceProvider(); new App({ - target: document.getElementById('app')!, + target: document.getElementById('svelte-app')!, }); /*/// v2 Run with web-component in shadow dom From db8e92956d0f5d55aca7f91ab363937fa95a8ec1 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Fri, 13 Dec 2024 10:51:44 +0700 Subject: [PATCH 029/108] fix compile issues and dependency missmatch --- backend/FwLite/FwLiteDesktop/FwLiteDesktop.csproj | 4 ++++ backend/FwLite/FwLiteDesktop/MauiProgram.cs | 6 ------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/backend/FwLite/FwLiteDesktop/FwLiteDesktop.csproj b/backend/FwLite/FwLiteDesktop/FwLiteDesktop.csproj index 5c57fa398..c33e0d025 100644 --- a/backend/FwLite/FwLiteDesktop/FwLiteDesktop.csproj +++ b/backend/FwLite/FwLiteDesktop/FwLiteDesktop.csproj @@ -77,6 +77,10 @@ + + + + diff --git a/backend/FwLite/FwLiteDesktop/MauiProgram.cs b/backend/FwLite/FwLiteDesktop/MauiProgram.cs index 2d32a0283..9b52f16b9 100644 --- a/backend/FwLite/FwLiteDesktop/MauiProgram.cs +++ b/backend/FwLite/FwLiteDesktop/MauiProgram.cs @@ -1,10 +1,4 @@ -using FwLiteDesktop.ServerBridge; -using FwLiteDesktop.WinUI; -using LcmCrdt; -using LocalWebApp; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; -using Microsoft.Maui.LifecycleEvents; namespace FwLiteDesktop; From 07ff5dec641eed3af87e41f3c5741e39e87071cb Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Fri, 13 Dec 2024 16:37:05 +0700 Subject: [PATCH 030/108] prevent svelte from catching navigation to allow blazor to handle it, then notify svelte of navigation from the blazor event --- .../FwLiteDesktop/FwLiteDesktopKernel.cs | 1 + .../FwLite/FwLiteDesktop/wwwroot/index.html | 7 +++++++ .../FwLiteShared/Layout/SvelteLayout.razor | 6 +++++- .../FwLiteShared/Pages/CrdtProject.razor | 4 +++- backend/FwLite/FwLiteShared/Routes.razor | 9 ++++++++- frontend/viewer/src/App.svelte | 19 +++++++++++++------ frontend/viewer/src/DotnetProjectView.svelte | 6 ++++-- frontend/viewer/src/HomeView.svelte | 4 ++-- .../lib/services/service-provider-dotnet.ts | 4 ++++ 9 files changed, 47 insertions(+), 13 deletions(-) diff --git a/backend/FwLite/FwLiteDesktop/FwLiteDesktopKernel.cs b/backend/FwLite/FwLiteDesktop/FwLiteDesktopKernel.cs index 96a8eb1d1..ebe29a4c7 100644 --- a/backend/FwLite/FwLiteDesktop/FwLiteDesktopKernel.cs +++ b/backend/FwLite/FwLiteDesktop/FwLiteDesktopKernel.cs @@ -46,6 +46,7 @@ public static void AddFwLiteDesktopServices(this IServiceCollection services, var defaultDataPath = IsPortableApp ? Directory.GetCurrentDirectory() : FileSystem.AppDataDirectory; var baseDataPath = Path.GetFullPath(configuration.GetSection("FwLiteDesktop").GetValue("BaseDataDir") ?? defaultDataPath); + logging.AddFilter("FwLiteShared.Auth.LoggerAdapter", LogLevel.Warning); Directory.CreateDirectory(baseDataPath); services.Configure(config => { diff --git a/backend/FwLite/FwLiteDesktop/wwwroot/index.html b/backend/FwLite/FwLiteDesktop/wwwroot/index.html index e74f55a15..cd909c837 100644 --- a/backend/FwLite/FwLiteDesktop/wwwroot/index.html +++ b/backend/FwLite/FwLiteDesktop/wwwroot/index.html @@ -6,6 +6,13 @@ FwLiteDesktop + diff --git a/backend/FwLite/FwLiteShared/Layout/SvelteLayout.razor b/backend/FwLite/FwLiteShared/Layout/SvelteLayout.razor index c716a92b5..a4ebdc457 100644 --- a/backend/FwLite/FwLiteShared/Layout/SvelteLayout.razor +++ b/backend/FwLite/FwLiteShared/Layout/SvelteLayout.razor @@ -1,13 +1,15 @@ @inherits LayoutComponentBase @using FwLiteShared.Services @using LexCore.Entities +@using Microsoft.Extensions.Logging @inject IJSRuntime JS +@inject ILogger Logger @inject FwLiteProvider FwLiteProvider
@@ -30,6 +33,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender) { + Logger.LogInformation("OnAfterRenderAsync SvelteLayout"); if (firstRender) { foreach (var (serviceKey, service) in FwLiteProvider.GetServices()) diff --git a/backend/FwLite/FwLiteShared/Pages/CrdtProject.razor b/backend/FwLite/FwLiteShared/Pages/CrdtProject.razor index 8635109fa..e7827143d 100644 --- a/backend/FwLite/FwLiteShared/Pages/CrdtProject.razor +++ b/backend/FwLite/FwLiteShared/Pages/CrdtProject.razor @@ -1,7 +1,9 @@ @page "/project/{projectName}" @using FwLiteShared.Layout @using FwLiteShared.Services +@using Microsoft.Extensions.Logging @inject FwLiteProvider FwLiteProvider +@inject ILogger Logger @implements IAsyncDisposable @layout SvelteLayout; @@ -14,7 +16,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender) { - await base.OnAfterRenderAsync(firstRender); + Logger.LogInformation("OnAfterRenderAsync CrdtProject, firstRender: {firstRender}", firstRender); if (firstRender) { _disposable = await FwLiteProvider.InjectCrdtProject(ProjectName); diff --git a/backend/FwLite/FwLiteShared/Routes.razor b/backend/FwLite/FwLiteShared/Routes.razor index 59fd46a50..43f8cfac4 100644 --- a/backend/FwLite/FwLiteShared/Routes.razor +++ b/backend/FwLite/FwLiteShared/Routes.razor @@ -1,4 +1,11 @@ - +@inject IJSRuntime jsRuntime +@code { + private async Task OnNavigateAsync(NavigationContext context) + { + await jsRuntime.InvokeVoidAsync("onBlazorNavigate", context.Path); + } +} + diff --git a/frontend/viewer/src/App.svelte b/frontend/viewer/src/App.svelte index 4eb0c6fe8..67d83859b 100644 --- a/frontend/viewer/src/App.svelte +++ b/frontend/viewer/src/App.svelte @@ -1,11 +1,18 @@ - + diff --git a/frontend/viewer/src/HomeView.svelte b/frontend/viewer/src/HomeView.svelte index 3bb7b9a80..4b53ae200 100644 --- a/frontend/viewer/src/HomeView.svelte +++ b/frontend/viewer/src/HomeView.svelte @@ -158,7 +158,7 @@ {#each data ?? [] as project, rowIndex} {#each columns as column (column.name)} - + {#if column.name === 'fwdata'} {#if project.fwdata} diff --git a/frontend/viewer/src/lib/services/service-provider-dotnet.ts b/frontend/viewer/src/lib/services/service-provider-dotnet.ts index 38ef35043..fe27c9edd 100644 --- a/frontend/viewer/src/lib/services/service-provider-dotnet.ts +++ b/frontend/viewer/src/lib/services/service-provider-dotnet.ts @@ -19,6 +19,10 @@ export class DotNetServiceProvider { this.services = fwLiteProvider; } + public hasService(key: ServiceKey): boolean { + return !!this.services[key]; + } + public getService(key: K): LexboxServiceRegistry[K] | undefined { this.validateAllServices(); let service = this.services[key] as unknown as DotNet.DotNetObject; From 7c21439dbc8448cdb77618d733770b65adf286e6 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Fri, 13 Dec 2024 16:44:47 +0700 Subject: [PATCH 031/108] setup watch builder build task --- frontend/Taskfile.yml | 3 +++ frontend/viewer/Taskfile.yml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/frontend/Taskfile.yml b/frontend/Taskfile.yml index ead41d753..27d490840 100644 --- a/frontend/Taskfile.yml +++ b/frontend/Taskfile.yml @@ -50,6 +50,9 @@ tasks: build-viewer-app: deps: [viewer:build-app] label: vite + watch-viewer-app: + deps: [viewer:watch-app] + label: vite viewer-dev: deps: [viewer:app] label: vite diff --git a/frontend/viewer/Taskfile.yml b/frontend/viewer/Taskfile.yml index da7be4c08..bc5201a5e 100644 --- a/frontend/viewer/Taskfile.yml +++ b/frontend/viewer/Taskfile.yml @@ -19,6 +19,9 @@ tasks: build-app: deps: [ install ] cmd: pnpm run build-app + watch-app: + deps: [ install ] + cmd: pnpm run build-app --watch install: method: checksum From e98faaf9c1fdb742831848dba2e552ccf280b01b Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Mon, 16 Dec 2024 11:28:49 +0700 Subject: [PATCH 032/108] fix issue with rollup not resolving $lib --- frontend/viewer/vite.config.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frontend/viewer/vite.config.ts b/frontend/viewer/vite.config.ts index e3c2faaf2..d5da0c35a 100644 --- a/frontend/viewer/vite.config.ts +++ b/frontend/viewer/vite.config.ts @@ -36,6 +36,9 @@ export default defineConfig(({ mode }) => { } }, }, + resolve: { + alias: [{find: "$lib", replacement: "/src/lib"}] + }, plugins: [svelte({ onwarn: (warning, handler) => { // we don't have control over these warnings and there are lots From f31f3712a82366f55e5f591489b39ab75d42e38a Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Mon, 16 Dec 2024 11:48:27 +0700 Subject: [PATCH 033/108] configure linq2db to use the Microsoft SQLite library instead of system data sqlite --- backend/FwLite/LcmCrdt/LcmCrdtKernel.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/FwLite/LcmCrdt/LcmCrdtKernel.cs b/backend/FwLite/LcmCrdt/LcmCrdtKernel.cs index 9b1495579..4504b1159 100644 --- a/backend/FwLite/LcmCrdt/LcmCrdtKernel.cs +++ b/backend/FwLite/LcmCrdt/LcmCrdtKernel.cs @@ -78,6 +78,7 @@ private static void ConfigureDbOptions(IServiceProvider provider, DbContextOptio mappingSchema.SetConvertExpression((WritingSystemId id) => new DataParameter { Value = id.Code, DataType = DataType.Text }); optionsBuilder.AddMappingSchema(mappingSchema); + optionsBuilder.AddCustomOptions(options => options.UseSQLiteMicrosoft()); var loggerFactory = provider.GetService(); if (loggerFactory is not null) optionsBuilder.AddCustomOptions(dataOptions => dataOptions.UseLoggerFactory(loggerFactory)); From 5ee022dd088a835d6d1b60aae43a5550327ef139 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Mon, 16 Dec 2024 12:32:40 +0700 Subject: [PATCH 034/108] prevent throwing errors when sending change notifications fails after a sync --- .../FwLite/FwLiteShared/Sync/SyncService.cs | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/backend/FwLite/FwLiteShared/Sync/SyncService.cs b/backend/FwLite/FwLiteShared/Sync/SyncService.cs index e797be14d..ae6466fdd 100644 --- a/backend/FwLite/FwLiteShared/Sync/SyncService.cs +++ b/backend/FwLite/FwLiteShared/Sync/SyncService.cs @@ -19,7 +19,7 @@ public class SyncService( IMiniLcmApi lexboxApi, ILogger logger) { - public async Task ExecuteSync() + public async Task ExecuteSync(bool skipNotifications = false) { var project = await currentProjectService.GetProjectData(); if (string.IsNullOrEmpty(project.OriginDomain)) @@ -38,32 +38,40 @@ public async Task ExecuteSync() project.OriginDomain); return new SyncResults([], [], false); } + var remoteModel = await remoteSyncServiceServer.CreateProjectSyncable(project, httpClient); var syncResults = await dataModel.SyncWith(remoteModel); //need to await this, otherwise the database connection will be closed before the notifications are sent - await SendNotifications(syncResults); + if (!skipNotifications) await SendNotifications(syncResults); return syncResults; } private async Task SendNotifications(SyncResults syncResults) { - await foreach (var entryId in syncResults.MissingFromLocal - .SelectMany(c => c.Snapshots, (commit, snapshot) => snapshot.Entity) - .ToAsyncEnumerable() - .SelectAwait(async e => await GetEntryId(e.DbObject as IObjectWithId)) - .Distinct()) + try { - if (entryId is null) continue; - var entry = await lexboxApi.GetEntry(entryId.Value); - if (entry is not null) - { - changeEventBus.NotifyEntryUpdated(entry); - } - else + await foreach (var entryId in syncResults.MissingFromLocal + .SelectMany(c => c.Snapshots, (commit, snapshot) => snapshot.Entity) + .ToAsyncEnumerable() + .SelectAwait(async e => await GetEntryId(e.DbObject as IObjectWithId)) + .Distinct()) { - logger.LogError("Failed to get entry {EntryId}, was not found", entryId); + if (entryId is null) continue; + var entry = await lexboxApi.GetEntry(entryId.Value); + if (entry is not null) + { + changeEventBus.NotifyEntryUpdated(entry); + } + else + { + logger.LogError("Failed to get entry {EntryId}, was not found", entryId); + } } } + catch (Exception e) + { + logger.LogError(e, "Failed to send notifications, continuing"); + } } private async ValueTask GetEntryId(IObjectWithId? entity) From 0b3b21ff01f41fa788f35983837ded72c0cb4591 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Mon, 16 Dec 2024 14:37:33 +0700 Subject: [PATCH 035/108] ensure hosted services run at startup as expected --- .../FwLiteDesktop/FwLiteDesktopKernel.cs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/backend/FwLite/FwLiteDesktop/FwLiteDesktopKernel.cs b/backend/FwLite/FwLiteDesktop/FwLiteDesktopKernel.cs index ebe29a4c7..5edefa47e 100644 --- a/backend/FwLite/FwLiteDesktop/FwLiteDesktopKernel.cs +++ b/backend/FwLite/FwLiteDesktop/FwLiteDesktopKernel.cs @@ -35,6 +35,7 @@ public static void AddFwLiteDesktopServices(this IServiceCollection services, services.AddSingleton(env); services.AddFwLiteShared(env); services.AddMauiBlazorWebView(); + services.AddSingleton(); #if WINDOWS services.AddFwLiteWindows(); @@ -47,6 +48,7 @@ public static void AddFwLiteDesktopServices(this IServiceCollection services, var baseDataPath = Path.GetFullPath(configuration.GetSection("FwLiteDesktop").GetValue("BaseDataDir") ?? defaultDataPath); logging.AddFilter("FwLiteShared.Auth.LoggerAdapter", LogLevel.Warning); + logging.AddFilter("Microsoft.EntityFrameworkCore.Database", LogLevel.Warning); Directory.CreateDirectory(baseDataPath); services.Configure(config => { @@ -57,6 +59,7 @@ public static void AddFwLiteDesktopServices(this IServiceCollection services, config.CacheFileName = Path.Combine(baseDataPath, "msal.cache"); config.SystemWebViewLogin = true; }); + // logging.AddFile(Path.Combine(baseDataPath, "app.log")); services.AddSingleton(Preferences.Default); services.AddSingleton(VersionTracking.Default); @@ -91,4 +94,29 @@ public static void AddFwLiteDesktopServices(this IServiceCollection services, /// public static bool IsPortableApp => false; #endif + + private class HostedServiceAdapter(IEnumerable hostedServices, ILogger logger) : IMauiInitializeService, IAsyncDisposable + { + private CancellationTokenSource _cts = new(); + public void Initialize(IServiceProvider services) + { + logger.LogInformation("Initializing hosted services"); + foreach (var hostedService in hostedServices) + { + _ = Task.Run(() => hostedService.StartAsync(_cts.Token), _cts.Token); + } + } + + public async ValueTask DisposeAsync() + { + //todo this is never called because the service provider is not disposed + logger.LogInformation("Disposing hosted services"); + foreach (var hostedService in hostedServices) + { + await hostedService.StopAsync(_cts.Token); + } + await _cts.CancelAsync(); + _cts.Dispose(); + } + } } From bcca0d6d05f15c5d98e014c24f7d5f7fb68fb528 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Mon, 16 Dec 2024 14:38:23 +0700 Subject: [PATCH 036/108] return the project when setting active project context --- backend/FwLite/FwLiteShared/Layout/SvelteLayout.razor | 5 +++++ backend/FwLite/LcmCrdt/CrdtProjectsService.cs | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/backend/FwLite/FwLiteShared/Layout/SvelteLayout.razor b/backend/FwLite/FwLiteShared/Layout/SvelteLayout.razor index a4ebdc457..930094573 100644 --- a/backend/FwLite/FwLiteShared/Layout/SvelteLayout.razor +++ b/backend/FwLite/FwLiteShared/Layout/SvelteLayout.razor @@ -21,6 +21,11 @@ const services = window.lexbox.FwLiteProvider; services[key] = service; }; + window['notifyEntryUpdated'] = (projectName, entry) => { + if (window.lexbox?.EventBus) { + window.lexbox.EventBus.notifyEntryUpdated(entry); + } + }
diff --git a/backend/FwLite/LcmCrdt/CrdtProjectsService.cs b/backend/FwLite/LcmCrdt/CrdtProjectsService.cs index aadc0dbaf..4260482dd 100644 --- a/backend/FwLite/LcmCrdt/CrdtProjectsService.cs +++ b/backend/FwLite/LcmCrdt/CrdtProjectsService.cs @@ -106,10 +106,11 @@ public void SetProjectScope(CrdtProject crdtProject) projectContext.Project = crdtProject; } - public void SetActiveProject(string name) + public CrdtProject SetActiveProject(string name) { var project = GetProject(name) ?? throw new InvalidOperationException($"Crdt Project {name} not found"); SetProjectScope(project); + return project; } [GeneratedRegex("^[a-zA-Z0-9][a-zA-Z0-9-_]+$")] From 9ad5e327c33d52abdeb246fcbbd40caf7bd83e44 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Mon, 16 Dec 2024 14:38:55 +0700 Subject: [PATCH 037/108] !fixup ensure hosted services run at startup as expected --- backend/FwLite/FwLiteShared/Sync/BackgroundSyncService.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/FwLite/FwLiteShared/Sync/BackgroundSyncService.cs b/backend/FwLite/FwLiteShared/Sync/BackgroundSyncService.cs index 86b66ee21..b3444fb6d 100644 --- a/backend/FwLite/FwLiteShared/Sync/BackgroundSyncService.cs +++ b/backend/FwLite/FwLiteShared/Sync/BackgroundSyncService.cs @@ -16,6 +16,7 @@ public class BackgroundSyncService( IHostApplicationLifetime? applicationLifetime = null) : BackgroundService { private readonly Channel _syncResultsChannel = Channel.CreateUnbounded(); + private bool _running = false; public void TriggerSync(Guid projectId, Guid? ignoredClientId = null) { @@ -47,6 +48,7 @@ public void TriggerSync() public void TriggerSync(CrdtProject crdtProject) { + if (!_running) throw new InvalidOperationException("Background sync service is not running"); _syncResultsChannel.Writer.TryWrite(crdtProject); } @@ -62,6 +64,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) { //need to wait until application is started, otherwise Server urls will be unknown which prevents creating downstream services await StartedAsync(); + _running = true; var crdtProjects = await crdtProjectsService.ListProjects(); foreach (var crdtProject in crdtProjects) { From 5a3722ecb3ef8c8e7729d4d73eaf1f05d63f4b30 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Mon, 16 Dec 2024 14:39:17 +0700 Subject: [PATCH 038/108] make all methods return async in generated ts code, support ValueTask --- .../TypeGen/ReinforcedFwLiteTypingConfig.cs | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/backend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs b/backend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs index ddc406823..35996673e 100644 --- a/backend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs +++ b/backend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs @@ -9,6 +9,7 @@ using Reinforced.Typings.Ast.TypeNames; using Reinforced.Typings.Fluent; using Reinforced.Typings.Visitors.TypeScript; +using StructureMap.TypeRules; namespace FwLiteShared.TypeGen; @@ -27,6 +28,7 @@ public static void Configure(ConfigurationBuilder builder) builder.Substitute(typeof(WritingSystemId), new RtSimpleTypeName("string")); builder.Substitute(typeof(Guid), new RtSimpleTypeName("string")); builder.Substitute(typeof(DateTimeOffset), new RtSimpleTypeName("string")); + builder.SubstituteGeneric(typeof(ValueTask<>), (type, resolver) => resolver.ResolveTypeName(typeof(Task<>).MakeGenericType(type.GenericTypeArguments[0]), true)); //todo generate a multistring type rather than just substituting it everywhere builder.ExportAsThirdParty().WithName("IMultiString").Imports([new () { @@ -43,10 +45,12 @@ public static void Configure(ConfigurationBuilder builder) typeof(SemanticDomain), typeof(ComplexFormType), typeof(ComplexFormComponent), + + typeof(MiniLcmJsInvokable.MiniLcmFeatures), ], exportBuilder => exportBuilder.WithPublicProperties()); builder.ExportAsEnum().UseString(); - builder.ExportAsInterface().FlattenHierarchy().WithPublicProperties().WithPublicMethods( + builder.ExportAsInterface().FlattenHierarchy().WithPublicProperties().WithPublicMethods( exportBuilder => { var isUpdatePatchMethod = exportBuilder.Member.GetParameters() @@ -54,6 +58,22 @@ public static void Configure(ConfigurationBuilder builder) if (isUpdatePatchMethod) { exportBuilder.Ignore(); + return; + } + var isTaskMethod = (exportBuilder.Member.ReturnType.IsGenericType && + (exportBuilder.Member.ReturnType.GetGenericTypeDefinition() == typeof(Task<>) + || exportBuilder.Member.ReturnType.GetGenericTypeDefinition() == typeof(ValueTask<>))) + || exportBuilder.Member.ReturnType == typeof(Task) + || exportBuilder.Member.ReturnType == typeof(ValueTask); + if (!isTaskMethod) + { + if (exportBuilder.Member.ReturnType == typeof(void)) + { + exportBuilder.Returns(typeof(Task)); + } else + { + exportBuilder.Returns(typeof(Task<>).MakeGenericType(exportBuilder.Member.ReturnType)); + } } }); builder.ExportAsEnum().UseString(); From fca878bfda243610c754e5ac2541bf19cc970814 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Mon, 16 Dec 2024 14:43:01 +0700 Subject: [PATCH 039/108] update generated types --- .../FwLiteShared/Services/IFwLiteProvider.ts | 2 +- .../FwLiteShared/Services/IMiniLcmFeatures.ts | 14 +++++++ .../Services/IMiniLcmJsInvokable.ts} | 37 ++++++++++--------- 3 files changed, 35 insertions(+), 18 deletions(-) create mode 100644 frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/IMiniLcmFeatures.ts rename frontend/viewer/src/lib/dotnet-types/generated-types/{MiniLcm/IMiniLcmApi.ts => FwLiteShared/Services/IMiniLcmJsInvokable.ts} (69%) diff --git a/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/IFwLiteProvider.ts b/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/IFwLiteProvider.ts index 70c62d056..eec785296 100644 --- a/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/IFwLiteProvider.ts +++ b/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/IFwLiteProvider.ts @@ -10,7 +10,7 @@ export interface IFwLiteProvider dispose() : void; getServices() : { [key in DotnetService]: any }; getService(service: DotnetService) : any; - setService(service: DotnetService, serviceInstance: any) : Promise; + setService(service: DotnetService, serviceInstance?: any) : Promise; injectCrdtProject(projectName: string) : Promise; injectFwDataProject(projectName: string) : Promise; } diff --git a/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/IMiniLcmFeatures.ts b/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/IMiniLcmFeatures.ts new file mode 100644 index 000000000..2b0c27f0e --- /dev/null +++ b/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/IMiniLcmFeatures.ts @@ -0,0 +1,14 @@ +/* eslint-disable */ +// This code was generated by a Reinforced.Typings tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. + +export interface IMiniLcmFeatures +{ + history: boolean; + write: boolean; + openWithFlex: boolean; + feedback: boolean; + sync: boolean; +} +/* eslint-enable */ diff --git a/frontend/viewer/src/lib/dotnet-types/generated-types/MiniLcm/IMiniLcmApi.ts b/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/IMiniLcmJsInvokable.ts similarity index 69% rename from frontend/viewer/src/lib/dotnet-types/generated-types/MiniLcm/IMiniLcmApi.ts rename to frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/IMiniLcmJsInvokable.ts index fbbb09554..4332b6f1e 100644 --- a/frontend/viewer/src/lib/dotnet-types/generated-types/MiniLcm/IMiniLcmApi.ts +++ b/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/IMiniLcmJsInvokable.ts @@ -3,27 +3,30 @@ // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. -import type {IWritingSystems} from './Models/IWritingSystems'; -import type {IComplexFormType} from './Models/IComplexFormType'; -import type {IQueryOptions} from './IQueryOptions'; -import type {IEntry} from './Models/IEntry'; -import type {ISense} from './Models/ISense'; -import type {IPartOfSpeech} from './Models/IPartOfSpeech'; -import type {ISemanticDomain} from './Models/ISemanticDomain'; -import type {IExampleSentence} from './Models/IExampleSentence'; -import type {IWritingSystem} from './Models/IWritingSystem'; -import type {WritingSystemType} from './Models/WritingSystemType'; -import type {IComplexFormComponent} from './Models/IComplexFormComponent'; +import type {IMiniLcmFeatures} from './IMiniLcmFeatures'; +import type {IWritingSystems} from '../../MiniLcm/Models/IWritingSystems'; +import type {IPartOfSpeech} from '../../MiniLcm/Models/IPartOfSpeech'; +import type {ISemanticDomain} from '../../MiniLcm/Models/ISemanticDomain'; +import type {IComplexFormType} from '../../MiniLcm/Models/IComplexFormType'; +import type {IEntry} from '../../MiniLcm/Models/IEntry'; +import type {IQueryOptions} from '../../MiniLcm/IQueryOptions'; +import type {ISense} from '../../MiniLcm/Models/ISense'; +import type {IExampleSentence} from '../../MiniLcm/Models/IExampleSentence'; +import type {IWritingSystem} from '../../MiniLcm/Models/IWritingSystem'; +import type {WritingSystemType} from '../../MiniLcm/Models/WritingSystemType'; +import type {IComplexFormComponent} from '../../MiniLcm/Models/IComplexFormComponent'; -export interface IMiniLcmApi +export interface IMiniLcmJsInvokable { + disposeAsync() : any; + supportedFeatures() : Promise; getWritingSystems() : Promise; - getPartsOfSpeech() : any; - getSemanticDomains() : any; - getComplexFormTypes() : any; + getPartsOfSpeech() : Promise; + getSemanticDomains() : Promise; + getComplexFormTypes() : Promise; getComplexFormType(id: string) : Promise; - getEntries(options?: IQueryOptions) : any; - searchEntries(query: string, options?: IQueryOptions) : any; + getEntries(options?: IQueryOptions) : Promise; + searchEntries(query: string, options?: IQueryOptions) : Promise; getEntry(id: string) : Promise; getSense(entryId: string, id: string) : Promise; getPartOfSpeech(id: string) : Promise; From 465f98e5c891b93f1df238320be5014490e13f91 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Mon, 16 Dec 2024 14:45:28 +0700 Subject: [PATCH 040/108] connect js event bus to C# ChangeEventBus --- backend/FwLite/FwLiteShared/ChangeEventBus.cs | 12 ++++++--- .../FwLiteShared/Services/FwLiteProvider.cs | 23 +++++++++++----- .../Services/MiniLcmJsInvokable.cs | 25 +++++++++++++++--- frontend/viewer/src/ProjectView.svelte | 26 ++++++++++--------- .../src/lib/services/service-provider.ts | 10 ++++--- 5 files changed, 65 insertions(+), 31 deletions(-) diff --git a/backend/FwLite/FwLiteShared/ChangeEventBus.cs b/backend/FwLite/FwLiteShared/ChangeEventBus.cs index 29734cc0e..b1b85f16d 100644 --- a/backend/FwLite/FwLiteShared/ChangeEventBus.cs +++ b/backend/FwLite/FwLiteShared/ChangeEventBus.cs @@ -13,14 +13,18 @@ private record struct ChangeNotification(Entry Entry, string ProjectName); private readonly Subject _entryUpdated = new(); + public IObservable OnProjectEntryUpdated(CrdtProject project) + { + var projectName = project.Name; + return _entryUpdated + .Where(n => n.ProjectName == projectName) + .Select(n => n.Entry); + } public IObservable OnEntryUpdated { get { - var projectName = projectContext.Project?.Name ?? throw new InvalidOperationException("Not in a project"); - return _entryUpdated - .Where(n => n.ProjectName == projectName) - .Select(n => n.Entry); + return OnProjectEntryUpdated(projectContext.Project ?? throw new InvalidOperationException("Not in a project")); } } diff --git a/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs b/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs index e2d10d17d..100e5798f 100644 --- a/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs +++ b/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs @@ -18,6 +18,8 @@ public class FwLiteProvider( IServiceProvider serviceProvider, FwDataProjectContext fwDataProjectContext, FieldWorksProjectList fieldWorksProjectList, + LexboxProjectService lexboxProjectService, + ChangeEventBus changeEventBus, IJSRuntime jsRuntime ) : IDisposable { @@ -63,19 +65,26 @@ public async Task SetService(DotnetService service, object? serviceInstance) public async Task InjectCrdtProject(string projectName) { - crdtProjectsService.SetActiveProject(projectName); - await serviceProvider.GetRequiredService().PopulateProjectDataCache(); - var service = new MiniLcmJsInvokable(serviceProvider.GetRequiredService()); + var project = crdtProjectsService.SetActiveProject(projectName); + var projectData = await serviceProvider.GetRequiredService().PopulateProjectDataCache(); + await lexboxProjectService.ListenForProjectChanges(projectData, CancellationToken.None); + var entryUpdatedSubscription = changeEventBus.OnProjectEntryUpdated(project).Subscribe(entry => + { + _ = jsRuntime.InvokeVoidAsync("notifyEntryUpdated", projectName, entry); + }); + var service = ActivatorUtilities.CreateInstance(serviceProvider, project); await SetService(DotnetService.MiniLcmApi, service); - return Defer.Async(async () => await SetService(DotnetService.MiniLcmApi, null)); + return Defer.Async(async () => + { + entryUpdatedSubscription.Dispose(); + await SetService(DotnetService.MiniLcmApi, null); + }); } public async Task InjectFwDataProject(string projectName) { fwDataProjectContext.Project = fieldWorksProjectList.GetProject(projectName); - var service = - new MiniLcmJsInvokable( - serviceProvider.GetRequiredKeyedService(FwDataBridgeKernel.FwDataApiKey)); + var service = ActivatorUtilities.CreateInstance(serviceProvider, serviceProvider.GetRequiredKeyedService(FwDataBridgeKernel.FwDataApiKey)); await SetService(DotnetService.MiniLcmApi, service); return Defer.Async(async () => await SetService(DotnetService.MiniLcmApi, null)); } diff --git a/backend/FwLite/FwLiteShared/Services/MiniLcmJsInvokable.cs b/backend/FwLite/FwLiteShared/Services/MiniLcmJsInvokable.cs index 7af7e67aa..4ab17f984 100644 --- a/backend/FwLite/FwLiteShared/Services/MiniLcmJsInvokable.cs +++ b/backend/FwLite/FwLiteShared/Services/MiniLcmJsInvokable.cs @@ -1,4 +1,5 @@ using FwDataMiniLcmBridge.Api; +using FwLiteShared.Sync; using LcmCrdt; using Microsoft.JSInterop; using MiniLcm; @@ -6,15 +7,28 @@ namespace FwLiteShared.Services; -internal class MiniLcmJsInvokable(IMiniLcmApi api) +internal class MiniLcmJsInvokable( + IMiniLcmApi api, + BackgroundSyncService backgroundSyncService, + CrdtProject? crdtProject = null) { + public record MiniLcmFeatures(bool History, bool Write, bool OpenWithFlex, bool Feedback, bool Sync); + private bool SupportsSync => api is CrdtMiniLcmApi; [JSInvokable] public MiniLcmFeatures SupportedFeatures() { var isCrdtProject = api is CrdtMiniLcmApi; var isFwDataProject = api is FwDataMiniLcmApi; - return new(History: isCrdtProject, Write: true, OpenWithFlex: isFwDataProject, Feedback: true, Sync: isCrdtProject); + return new(History: isCrdtProject, Write: true, OpenWithFlex: isFwDataProject, Feedback: true, Sync: SupportsSync); + } + + private void TriggerSync() + { + if (SupportsSync && crdtProject is not null) + { + backgroundSyncService.TriggerSync(crdtProject); + } } [JSInvokable] @@ -162,9 +176,12 @@ public Task CreateEntry(Entry entry) } [JSInvokable] - public Task UpdateEntry(Entry before, Entry after) + public async Task UpdateEntry(Entry before, Entry after) { - return api.UpdateEntry(before, after); + //todo trigger sync on the test + var result = await api.UpdateEntry(before, after); + TriggerSync(); + return result; } [JSInvokable] diff --git a/frontend/viewer/src/ProjectView.svelte b/frontend/viewer/src/ProjectView.svelte index d7f2ea2d0..a5412d124 100644 --- a/frontend/viewer/src/ProjectView.svelte +++ b/frontend/viewer/src/ProjectView.svelte @@ -14,9 +14,9 @@ import {useLexboxApi} from './lib/services/service-provider'; import type {IEntry} from './lib/mini-lcm'; import {onDestroy, onMount, setContext} from 'svelte'; - import {derived, writable, type Readable} from 'svelte/store'; - import {deriveAsync, makeDebouncer} from './lib/utils/time'; - import { type LexboxPermissions, type LexboxFeatures} from './lib/config-types'; + import {derived, type Readable, writable} from 'svelte/store'; + import {deriveAsync} from './lib/utils/time'; + import {type LexboxFeatures, type LexboxPermissions} from './lib/config-types'; import ViewOptionsDrawer from './lib/layout/ViewOptionsDrawer.svelte'; import EntryList from './lib/layout/EntryList.svelte'; import Toc from './lib/layout/Toc.svelte'; @@ -25,10 +25,10 @@ import NewEntryDialog from './lib/entry-editor/NewEntryDialog.svelte'; import SearchBar from './lib/search-bar/SearchBar.svelte'; import ActivityView from './lib/activity/ActivityView.svelte'; - import { getAvailableHeightForElement } from './lib/utils/size'; - import { ViewerSearchParam, getSearchParam, getSearchParams, updateSearchParam } from './lib/utils/search-params'; + import {getAvailableHeightForElement} from './lib/utils/size'; + import {getSearchParam, getSearchParams, updateSearchParam, ViewerSearchParam} from './lib/utils/search-params'; import SaveStatus from './lib/status/SaveStatus.svelte'; - import { saveEventDispatcher, saveHandler } from './lib/services/save-event-service'; + import {saveEventDispatcher, saveHandler} from './lib/services/save-event-service'; import {AppNotification} from './lib/notifications/notifications'; import flexLogo from './lib/assets/flex-logo.png'; import {initView, initViewSettings} from './lib/services/view-service'; @@ -36,8 +36,9 @@ import {initWritingSystems} from './lib/writing-systems'; import {useEventBus} from './lib/services/event-bus'; import AboutDialog from './lib/about/AboutDialog.svelte'; - import { initProjectCommands, type NewEntryDialogOptions } from './lib/commands'; + import {initProjectCommands, type NewEntryDialogOptions} from './lib/commands'; import throttle from 'just-throttle'; + import {SortField} from '$lib/dotnet-types/generated-types/MiniLcm/SortField'; export let loading = false; @@ -62,10 +63,11 @@ })); const lexboxApi = useLexboxApi(); - void lexboxApi.SupportedFeatures().then(f => { + void lexboxApi.supportedFeatures().then(f => { features.set(f); }); - const features = writable({}); + //not having write enabled at the start fixes an issue where the default viewSetting.hideEmptyFields would be incorrect + const features = writable({write: true}); setContext>('features', features); setContext('saveEvents', saveEventDispatcher); setContext('saveHandler', saveHandler); @@ -106,7 +108,7 @@ const writingSystems = initWritingSystems(deriveAsync(connected, isConnected => { if (!isConnected) return Promise.resolve(null); - return lexboxApi.GetWritingSystems(); + return lexboxApi.getWritingSystems(); }).value); const indexExamplars = derived(writingSystems, wsList => { return wsList?.vernacular[0].exemplars; @@ -136,11 +138,11 @@ function fetchEntries(s: string, isConnected: boolean, exemplar: string | null): Promise { if (!isConnected) return Promise.resolve(undefined); - return lexboxApi.SearchEntries(s ?? '', { + return lexboxApi.searchEntries(s ?? '', { offset: 0, // we always load full exemplar lists for now, so we can guaruntee that the selected entry is in the list count: exemplar ? 1_000_000_000 : 1000, - order: {field: 'headword', writingSystem: 'default'}, + order: {field: SortField.Headword, writingSystem: 'default', ascending: true}, exemplar: exemplar ? {value: exemplar, writingSystem: 'default'} : undefined }); } diff --git a/frontend/viewer/src/lib/services/service-provider.ts b/frontend/viewer/src/lib/services/service-provider.ts index a7a4dca66..949247b74 100644 --- a/frontend/viewer/src/lib/services/service-provider.ts +++ b/frontend/viewer/src/lib/services/service-provider.ts @@ -1,8 +1,9 @@ -import type {LexboxApiClient} from './lexbox-api'; import {openSearch} from '../search-bar/search'; import {ProjectService} from './projects-service'; import {DotnetService, type ICombinedProjectsService, type IAuthService} from '../dotnet-types'; import type {IImportFwdataService} from '$lib/dotnet-types/generated-types/FwLiteShared/Projects/IImportFwdataService'; +import type {IMiniLcmJsInvokable} from '$lib/dotnet-types/generated-types/FwLiteShared/Services/IMiniLcmJsInvokable'; +import {type EventBus, useEventBus} from '$lib/services/event-bus'; declare global { @@ -10,6 +11,7 @@ declare global { /* eslint-disable @typescript-eslint/naming-convention */ ServiceProvider: LexboxServiceProvider; Search: {openSearch: (search: string) => void}; + EventBus: EventBus; /* eslint-enable @typescript-eslint/naming-convention */ } @@ -24,7 +26,7 @@ export enum LexboxService { export type ServiceKey = keyof LexboxServiceRegistry; export type LexboxServiceRegistry = { - [DotnetService.MiniLcmApi]: LexboxApiClient, + [DotnetService.MiniLcmApi]: IMiniLcmJsInvokable, [DotnetService.CombinedProjectsService]: ICombinedProjectsService, [DotnetService.AuthService]: IAuthService, [DotnetService.ImportFwdataService]: IImportFwdataService, @@ -56,7 +58,7 @@ export class LexboxServiceProvider { { // eslint-disable-next-line @typescript-eslint/naming-convention - const lexbox = {ServiceProvider: new LexboxServiceProvider(), Search: {openSearch: openSearch}} + const lexbox = {ServiceProvider: new LexboxServiceProvider(), Search: {openSearch: openSearch}, EventBus: useEventBus()} if (!window.lexbox) { window.lexbox = lexbox; } else { @@ -64,7 +66,7 @@ export class LexboxServiceProvider { } } -export function useLexboxApi(): LexboxApiClient { +export function useLexboxApi(): IMiniLcmJsInvokable { return window.lexbox.ServiceProvider.getService(DotnetService.MiniLcmApi); } From 94fd1bd1e64f95ad81bb799eac9b20a4d51ddcd8 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Mon, 16 Dec 2024 16:58:27 +0700 Subject: [PATCH 041/108] remove ProjectContext.cs and use stateful CurrentProjectService.cs instead --- backend/FwHeadless/Program.cs | 4 +- .../Services/ProjectContextFromIdService.cs | 3 +- .../Fixtures/TestingKernel.cs | 7 --- .../FwLiteProjectSync.Tests/SyncTests.cs | 2 +- backend/FwLite/FwLiteShared/ChangeEventBus.cs | 14 ++---- .../FwLiteShared/Services/FwLiteProvider.cs | 4 +- .../Sync/BackgroundSyncService.cs | 10 ++-- .../FwLite/FwLiteShared/Sync/SyncService.cs | 2 +- .../LcmCrdt.Tests/DataModelSnapshotTests.cs | 13 ++--- .../LcmCrdt.Tests/LcmCrdtTestsKernel.cs | 24 ++++++++++ .../FwLite/LcmCrdt.Tests/MiniLcmApiFixture.cs | 11 ++--- .../LcmCrdt.Tests/Mocks/MockProjectContext.cs | 6 --- .../FwLite/LcmCrdt.Tests/OpenProjectTests.cs | 6 +-- backend/FwLite/LcmCrdt/CrdtProjectsService.cs | 28 ++--------- .../FwLite/LcmCrdt/CurrentProjectService.cs | 47 +++++++++++++++---- backend/FwLite/LcmCrdt/LcmCrdtKernel.cs | 12 ++--- backend/FwLite/LcmCrdt/ProjectContext.cs | 34 -------------- .../LocalWebApp/Hubs/CrdtMiniLcmApiHub.cs | 21 +++++---- .../FwLite/LocalWebApp/LocalWebAppServer.cs | 6 +-- .../FwLite/LocalWebApp/Routes/TestRoutes.cs | 9 ++-- 20 files changed, 118 insertions(+), 145 deletions(-) create mode 100644 backend/FwLite/LcmCrdt.Tests/LcmCrdtTestsKernel.cs delete mode 100644 backend/FwLite/LcmCrdt.Tests/Mocks/MockProjectContext.cs delete mode 100644 backend/FwLite/LcmCrdt/ProjectContext.cs diff --git a/backend/FwHeadless/Program.cs b/backend/FwHeadless/Program.cs index 7f737b755..cd8b42b68 100644 --- a/backend/FwHeadless/Program.cs +++ b/backend/FwHeadless/Program.cs @@ -130,7 +130,7 @@ static async Task, NotFound, ProblemHttpResult>> ExecuteM } static async Task, NotFound>> GetMergeStatus( - ProjectContext projectContext, + CurrentProjectService projectContext, ProjectLookupService projectLookupService, SyncJobStatusService syncJobStatusService, IServiceProvider services, @@ -139,7 +139,7 @@ static async Task, NotFound>> GetMergeStatus( { var jobStatus = syncJobStatusService.SyncStatus(projectId); if (jobStatus == SyncJobStatus.Running) return TypedResults.Ok(ProjectSyncStatus.Syncing); - var project = projectContext.Project; + var project = projectContext.MaybeProject; if (project is null) { // 404 only means "project doesn't exist"; if we don't know the status, then it hasn't synced before and is therefore ready to sync diff --git a/backend/FwHeadless/Services/ProjectContextFromIdService.cs b/backend/FwHeadless/Services/ProjectContextFromIdService.cs index 56c3ebc40..a846aead2 100644 --- a/backend/FwHeadless/Services/ProjectContextFromIdService.cs +++ b/backend/FwHeadless/Services/ProjectContextFromIdService.cs @@ -18,8 +18,7 @@ public async Task PopulateProjectContext(HttpContext context, Func next) if (File.Exists(crdtFile)) { var project = new CrdtProject("crdt", crdtFile); - projectsService.SetProjectScope(project); - await context.RequestServices.GetRequiredService().PopulateProjectDataCache(); + await context.RequestServices.GetRequiredService().SetupProjectContext(project); } } } diff --git a/backend/FwLite/FwLiteProjectSync.Tests/Fixtures/TestingKernel.cs b/backend/FwLite/FwLiteProjectSync.Tests/Fixtures/TestingKernel.cs index 6eb9f8b40..107e290b1 100644 --- a/backend/FwLite/FwLiteProjectSync.Tests/Fixtures/TestingKernel.cs +++ b/backend/FwLite/FwLiteProjectSync.Tests/Fixtures/TestingKernel.cs @@ -13,15 +13,8 @@ public static IServiceCollection AddSyncServices(this IServiceCollection service return services.AddLcmCrdtClient() .AddTestFwDataBridge(mockFwProjectLoader) .AddFwLiteProjectSync() - .AddSingleton(new MockProjectContext(null)) .Configure(c => c.ProjectsFolder = Path.Combine(".", projectName, "FwData")) .Configure(c => c.ProjectPath = Path.Combine(".", projectName, "LcmCrdt")) .AddLogging(builder => builder.AddDebug()); } - - public class MockProjectContext(CrdtProject? project) : ProjectContext - { - public override CrdtProject? Project { get; set; } = project; - } - } diff --git a/backend/FwLite/FwLiteProjectSync.Tests/SyncTests.cs b/backend/FwLite/FwLiteProjectSync.Tests/SyncTests.cs index 49a9fd860..bdb2edc75 100644 --- a/backend/FwLite/FwLiteProjectSync.Tests/SyncTests.cs +++ b/backend/FwLite/FwLiteProjectSync.Tests/SyncTests.cs @@ -110,7 +110,7 @@ public static async Task SyncFailsWithMismatchedProjectIds() var newFwProjectId = Guid.NewGuid(); await fixture.Services.GetRequiredService().ProjectData. ExecuteUpdateAsync(updates => updates.SetProperty(p => p.FwProjectId, newFwProjectId)); - await fixture.Services.GetRequiredService().PopulateProjectDataCache(force: true); + await fixture.Services.GetRequiredService().RefreshProjectData(); Func syncTask = async () => await fixture.SyncService.Sync(crdtApi, fwdataApi); await syncTask.Should().ThrowAsync(); diff --git a/backend/FwLite/FwLiteShared/ChangeEventBus.cs b/backend/FwLite/FwLiteShared/ChangeEventBus.cs index b1b85f16d..7b4d0bfe8 100644 --- a/backend/FwLite/FwLiteShared/ChangeEventBus.cs +++ b/backend/FwLite/FwLiteShared/ChangeEventBus.cs @@ -5,7 +5,7 @@ namespace FwLiteShared; -public class ChangeEventBus(ProjectContext projectContext) +public class ChangeEventBus : IDisposable { @@ -20,18 +20,10 @@ public IObservable OnProjectEntryUpdated(CrdtProject project) .Where(n => n.ProjectName == projectName) .Select(n => n.Entry); } - public IObservable OnEntryUpdated - { - get - { - return OnProjectEntryUpdated(projectContext.Project ?? throw new InvalidOperationException("Not in a project")); - } - } - public void NotifyEntryUpdated(Entry entry) + public void NotifyEntryUpdated(Entry entry, CrdtProject project) { - _entryUpdated.OnNext(new ChangeNotification(entry, - projectContext.Project?.Name ?? throw new InvalidOperationException("Not in a project"))); + _entryUpdated.OnNext(new ChangeNotification(entry, project.Name)); } public void Dispose() diff --git a/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs b/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs index 100e5798f..9b7695f3f 100644 --- a/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs +++ b/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs @@ -65,8 +65,8 @@ public async Task SetService(DotnetService service, object? serviceInstance) public async Task InjectCrdtProject(string projectName) { - var project = crdtProjectsService.SetActiveProject(projectName); - var projectData = await serviceProvider.GetRequiredService().PopulateProjectDataCache(); + var project = crdtProjectsService.GetProject(projectName) ?? throw new InvalidOperationException($"Crdt Project {projectName} not found"); + var projectData = await serviceProvider.GetRequiredService().SetupProjectContext(project); await lexboxProjectService.ListenForProjectChanges(projectData, CancellationToken.None); var entryUpdatedSubscription = changeEventBus.OnProjectEntryUpdated(project).Subscribe(entry => { diff --git a/backend/FwLite/FwLiteShared/Sync/BackgroundSyncService.cs b/backend/FwLite/FwLiteShared/Sync/BackgroundSyncService.cs index b3444fb6d..ff09e1a03 100644 --- a/backend/FwLite/FwLiteShared/Sync/BackgroundSyncService.cs +++ b/backend/FwLite/FwLiteShared/Sync/BackgroundSyncService.cs @@ -10,9 +10,9 @@ namespace FwLiteShared.Sync; public class BackgroundSyncService( CrdtProjectsService crdtProjectsService, - ProjectContext projectContext, ILogger logger, IMemoryCache memoryCache, + IServiceProvider serviceProvider, IHostApplicationLifetime? applicationLifetime = null) : BackgroundService { private readonly Channel _syncResultsChannel = Channel.CreateUnbounded(); @@ -41,10 +41,6 @@ public void TriggerSync(Guid projectId, Guid? ignoredClientId = null) TriggerSync(crdtProject); } - public void TriggerSync() - { - TriggerSync(projectContext.Project ?? throw new InvalidOperationException("No project selected")); - } public void TriggerSync(CrdtProject crdtProject) { @@ -83,8 +79,8 @@ private async Task SyncProject(CrdtProject crdtProject) { try { - await using var serviceScope = crdtProjectsService.CreateProjectScope(crdtProject); - await serviceScope.ServiceProvider.GetRequiredService().PopulateProjectDataCache(); + await using var serviceScope = serviceProvider.CreateAsyncScope(); + await serviceScope.ServiceProvider.GetRequiredService().SetupProjectContext(crdtProject); var syncService = serviceScope.ServiceProvider.GetRequiredService(); return await syncService.ExecuteSync(); } diff --git a/backend/FwLite/FwLiteShared/Sync/SyncService.cs b/backend/FwLite/FwLiteShared/Sync/SyncService.cs index ae6466fdd..c5eead34c 100644 --- a/backend/FwLite/FwLiteShared/Sync/SyncService.cs +++ b/backend/FwLite/FwLiteShared/Sync/SyncService.cs @@ -60,7 +60,7 @@ private async Task SendNotifications(SyncResults syncResults) var entry = await lexboxApi.GetEntry(entryId.Value); if (entry is not null) { - changeEventBus.NotifyEntryUpdated(entry); + changeEventBus.NotifyEntryUpdated(entry, currentProjectService.Project); } else { diff --git a/backend/FwLite/LcmCrdt.Tests/DataModelSnapshotTests.cs b/backend/FwLite/LcmCrdt.Tests/DataModelSnapshotTests.cs index 3173e290d..1322bd493 100644 --- a/backend/FwLite/LcmCrdt.Tests/DataModelSnapshotTests.cs +++ b/backend/FwLite/LcmCrdt.Tests/DataModelSnapshotTests.cs @@ -1,6 +1,5 @@ using FluentAssertions.Execution; using LcmCrdt.Objects; -using LcmCrdt.Tests.Mocks; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.Extensions.DependencyInjection; @@ -25,14 +24,14 @@ public class DataModelSnapshotTests : IAsyncLifetime protected readonly AsyncServiceScope _services; private readonly LcmCrdtDbContext _crdtDbContext; private CrdtConfig _crdtConfig; + private CrdtProject _crdtProject; + public DataModelSnapshotTests() { - + _crdtProject = new CrdtProject("sena-3", $"sena-3-{Guid.NewGuid()}.sqlite"); var services = new ServiceCollection() - .AddLcmCrdtClient() + .AddTestLcmCrdtClient(_crdtProject) .AddLogging(builder => builder.AddDebug()) - .RemoveAll(typeof(ProjectContext)) - .AddSingleton(new MockProjectContext(new CrdtProject("sena-3", ":memory:"))) .BuildServiceProvider(); _services = services.CreateAsyncScope(); _crdtDbContext = _services.ServiceProvider.GetRequiredService(); @@ -45,11 +44,13 @@ public async Task InitializeAsync() //can't use ProjectsService.CreateProject because it opens and closes the db context, this would wipe out the in memory db. await CrdtProjectsService.InitProjectDb(_crdtDbContext, new ProjectData("Sena 3", Guid.NewGuid(), null, Guid.NewGuid())); - await _services.ServiceProvider.GetRequiredService().PopulateProjectDataCache(); + await _services.ServiceProvider.GetRequiredService().SetupProjectContext(_crdtProject); } public async Task DisposeAsync() { + await _crdtDbContext.Database.CloseConnectionAsync(); + await _crdtDbContext.Database.EnsureDeletedAsync(); await _services.DisposeAsync(); } diff --git a/backend/FwLite/LcmCrdt.Tests/LcmCrdtTestsKernel.cs b/backend/FwLite/LcmCrdt.Tests/LcmCrdtTestsKernel.cs new file mode 100644 index 000000000..ccce423b6 --- /dev/null +++ b/backend/FwLite/LcmCrdt.Tests/LcmCrdtTestsKernel.cs @@ -0,0 +1,24 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace LcmCrdt.Tests; + +public static class LcmCrdtTestsKernel +{ + public static IServiceCollection AddTestLcmCrdtClient(this IServiceCollection services, CrdtProject? project = null) + { + services.TryAddSingleton(new ConfigurationRoot([])); + services.AddLcmCrdtClient(); + if (project is not null) + { + services.AddSingleton(provider => + { + var currentProjectService = ActivatorUtilities.CreateInstance(provider); + currentProjectService.SetupProjectContextForNewDb(project); + return currentProjectService; + }); + } + return services; + } +} diff --git a/backend/FwLite/LcmCrdt.Tests/MiniLcmApiFixture.cs b/backend/FwLite/LcmCrdt.Tests/MiniLcmApiFixture.cs index dcc249a14..fb841588e 100644 --- a/backend/FwLite/LcmCrdt.Tests/MiniLcmApiFixture.cs +++ b/backend/FwLite/LcmCrdt.Tests/MiniLcmApiFixture.cs @@ -1,6 +1,6 @@ -using LcmCrdt.Tests.Mocks; -using Meziantou.Extensions.Logging.Xunit; +using Meziantou.Extensions.Logging.Xunit; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; @@ -26,14 +26,13 @@ public MiniLcmApiFixture() public async Task InitializeAsync() { + var crdtProject = new CrdtProject("sena-3", ":memory:"); var services = new ServiceCollection() - .AddLcmCrdtClient() + .AddTestLcmCrdtClient(crdtProject) .AddLogging(builder => builder.AddDebug() .AddProvider(new LateXUnitLoggerProvider(this)) .AddFilter("LinqToDB", LogLevel.Trace) .SetMinimumLevel(LogLevel.Error)) - .RemoveAll(typeof(ProjectContext)) - .AddSingleton(new MockProjectContext(new CrdtProject("sena-3", ":memory:"))) .BuildServiceProvider(); _services = services.CreateAsyncScope(); _crdtDbContext = _services.ServiceProvider.GetRequiredService(); @@ -41,7 +40,7 @@ public async Task InitializeAsync() //can't use ProjectsService.CreateProject because it opens and closes the db context, this would wipe out the in memory db. await CrdtProjectsService.InitProjectDb(_crdtDbContext, new ProjectData("Sena 3", Guid.NewGuid(), null, Guid.NewGuid())); - await _services.ServiceProvider.GetRequiredService().PopulateProjectDataCache(); + await _services.ServiceProvider.GetRequiredService().RefreshProjectData(); await Api.CreateWritingSystem(WritingSystemType.Vernacular, new WritingSystem() diff --git a/backend/FwLite/LcmCrdt.Tests/Mocks/MockProjectContext.cs b/backend/FwLite/LcmCrdt.Tests/Mocks/MockProjectContext.cs deleted file mode 100644 index bd4149acf..000000000 --- a/backend/FwLite/LcmCrdt.Tests/Mocks/MockProjectContext.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace LcmCrdt.Tests.Mocks; - -public class MockProjectContext(CrdtProject project) : ProjectContext -{ - public override CrdtProject? Project { get; set; } = project; -} diff --git a/backend/FwLite/LcmCrdt.Tests/OpenProjectTests.cs b/backend/FwLite/LcmCrdt.Tests/OpenProjectTests.cs index 9fb67472a..61aa2999e 100644 --- a/backend/FwLite/LcmCrdt.Tests/OpenProjectTests.cs +++ b/backend/FwLite/LcmCrdt.Tests/OpenProjectTests.cs @@ -12,12 +12,12 @@ public async Task OpeningAProjectWorks() var sqliteConnectionString = "OpeningAProjectWorks.sqlite"; if (File.Exists(sqliteConnectionString)) File.Delete(sqliteConnectionString); var builder = Host.CreateEmptyApplicationBuilder(null); - builder.Services.AddLcmCrdtClient(); + builder.Services.AddTestLcmCrdtClient(); using var host = builder.Build(); var services = host.Services; var asyncScope = services.CreateAsyncScope(); - await asyncScope.ServiceProvider.GetRequiredService() - .CreateProject(new(Name: "OpeningAProjectWorks", Path: "", SeedNewProjectData: true)); + var crdtProjectsService = asyncScope.ServiceProvider.GetRequiredService(); + await crdtProjectsService.CreateProject(new(Name: "OpeningAProjectWorks", Path: "", SeedNewProjectData: true)); var miniLcmApi = (CrdtMiniLcmApi)await asyncScope.ServiceProvider.OpenCrdtProject(new CrdtProject("OpeningAProjectWorks", sqliteConnectionString)); miniLcmApi.ProjectData.Name.Should().Be("OpeningAProjectWorks"); diff --git a/backend/FwLite/LcmCrdt/CrdtProjectsService.cs b/backend/FwLite/LcmCrdt/CrdtProjectsService.cs index 4260482dd..44b6e94da 100644 --- a/backend/FwLite/LcmCrdt/CrdtProjectsService.cs +++ b/backend/FwLite/LcmCrdt/CrdtProjectsService.cs @@ -8,7 +8,7 @@ namespace LcmCrdt; -public partial class CrdtProjectsService(IServiceProvider provider, ProjectContext projectContext, ILogger logger, IOptions config, IMemoryCache memoryCache) +public partial class CrdtProjectsService(IServiceProvider provider, ILogger logger, IOptions config, IMemoryCache memoryCache) { public Task ListProjects() { @@ -56,7 +56,9 @@ public async Task CreateProject(CreateProjectRequest request) var sqliteFile = Path.Combine(request.Path ?? config.Value.ProjectPath, $"{name}.sqlite"); if (File.Exists(sqliteFile)) throw new InvalidOperationException("Project already exists"); var crdtProject = new CrdtProject(name, sqliteFile); - await using var serviceScope = CreateProjectScope(crdtProject); + await using var serviceScope = provider.CreateAsyncScope(); + var currentProjectService = serviceScope.ServiceProvider.GetRequiredService(); + currentProjectService.SetupProjectContextForNewDb(crdtProject); var db = serviceScope.ServiceProvider.GetRequiredService(); try { @@ -65,7 +67,7 @@ public async Task CreateProject(CreateProjectRequest request) ProjectData.GetOriginDomain(request.Domain), Guid.NewGuid(), request.FwProjectId); await InitProjectDb(db, projectData); - await serviceScope.ServiceProvider.GetRequiredService().PopulateProjectDataCache(); + await currentProjectService.RefreshProjectData(); if (request.SeedNewProjectData) await SeedSystemData(serviceScope.ServiceProvider.GetRequiredService(), projectData.ClientId); await (request.AfterCreate?.Invoke(serviceScope.ServiceProvider, crdtProject) ?? Task.CompletedTask); @@ -93,26 +95,6 @@ internal static async Task SeedSystemData(DataModel dataModel, Guid clientId) await PreDefinedData.PredefinedSemanticDomains(dataModel, clientId); } - public AsyncServiceScope CreateProjectScope(CrdtProject crdtProject) - { - //todo make this helper method call `CurrentProjectService.PopulateProjectDataCache` - var serviceScope = provider.CreateAsyncScope(); - SetProjectScope(crdtProject); - return serviceScope; - } - - public void SetProjectScope(CrdtProject crdtProject) - { - projectContext.Project = crdtProject; - } - - public CrdtProject SetActiveProject(string name) - { - var project = GetProject(name) ?? throw new InvalidOperationException($"Crdt Project {name} not found"); - SetProjectScope(project); - return project; - } - [GeneratedRegex("^[a-zA-Z0-9][a-zA-Z0-9-_]+$")] public static partial Regex ProjectName(); diff --git a/backend/FwLite/LcmCrdt/CurrentProjectService.cs b/backend/FwLite/LcmCrdt/CurrentProjectService.cs index e99edde78..c13ed435f 100644 --- a/backend/FwLite/LcmCrdt/CurrentProjectService.cs +++ b/backend/FwLite/LcmCrdt/CurrentProjectService.cs @@ -1,12 +1,16 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.DependencyInjection; namespace LcmCrdt; -public class CurrentProjectService(LcmCrdtDbContext dbContext, ProjectContext projectContext, IMemoryCache memoryCache) +public class CurrentProjectService(IServiceProvider services, IMemoryCache memoryCache, CrdtProjectsService crdtProjectsService) { - public CrdtProject Project => - projectContext.Project ?? throw new NullReferenceException("Not in the context of a project"); + private CrdtProject? _project; + //creating a DbContext depends on the CurrentProjectService, so we can't create it in the constructor otherwise we'll create a circular dependency + private LcmCrdtDbContext DbContext => services.GetRequiredService(); + public CrdtProject Project => _project ?? throw new NullReferenceException("Not in the context of a project"); + public CrdtProject? MaybeProject => _project; //only works because PopulateProjectDataCache is called first in the request pipeline public ProjectData ProjectData => memoryCache.Get(CacheKey(Project)) ?? throw new InvalidOperationException("Project data not found, call PopulateProjectDataCache first or use GetProjectData"); @@ -16,7 +20,7 @@ public async ValueTask GetProjectData() var key = CacheKey(Project); if (!memoryCache.TryGetValue(key, out object? result)) { - result = await dbContext.ProjectData.AsNoTracking().FirstAsync(); + result = await DbContext.ProjectData.AsNoTracking().FirstAsync(); memoryCache.Set(key, result); memoryCache.Set(CacheKey(((ProjectData)result).Id), result); } @@ -25,6 +29,11 @@ public async ValueTask GetProjectData() return (ProjectData)result; } + public void ValidateProjectScope() + { + if (Project is null) throw new InvalidOperationException($"Project is null, there's a bug and {nameof(SetupProjectContext)} was not called"); + } + private static string CacheKey(CrdtProject project) { return project.Name + "|ProjectData"; @@ -45,9 +54,28 @@ private static string CacheKey(Guid projectId) return memoryCache.Get(CacheKey(projectId)); } - public async ValueTask PopulateProjectDataCache(bool force = false) + /// + /// Setup the project context for a new db, this will not trigger a refresh or setup for ProjectData, you probably want to call SetupProjectContext instead + /// + public void SetupProjectContextForNewDb(CrdtProject project) { - if (force) RemoveProjectDataCache(); + _project = project; + } + public async ValueTask SetupProjectContext(CrdtProject project) + { + if (_project != null && project != _project) throw new InvalidOperationException("Can't setup project context for a different project"); + _project = project; + return await RefreshProjectData(); + } + + public async ValueTask SetupProjectContext(string projectName) + { + return await SetupProjectContext(crdtProjectsService.GetProject(projectName) ?? throw new InvalidOperationException($"Crdt Project {projectName} not found")); + } + + public async ValueTask RefreshProjectData() + { + RemoveProjectDataCache(); var projectData = await GetProjectData(); return projectData; } @@ -62,17 +90,16 @@ public async Task SetProjectSyncOrigin(Uri? domain, Guid? id) var originDomain = ProjectData.GetOriginDomain(domain); if (id is null) { - await dbContext.Set() + await DbContext.Set() .ExecuteUpdateAsync(calls => calls.SetProperty(p => p.OriginDomain, originDomain)); } else { - await dbContext.Set() + await DbContext.Set() .ExecuteUpdateAsync(calls => calls.SetProperty(p => p.OriginDomain, originDomain) .SetProperty(p => p.Id, id)); } - RemoveProjectDataCache(); - await PopulateProjectDataCache(); + await RefreshProjectData(); } } diff --git a/backend/FwLite/LcmCrdt/LcmCrdtKernel.cs b/backend/FwLite/LcmCrdt/LcmCrdtKernel.cs index 4504b1159..e8a35750f 100644 --- a/backend/FwLite/LcmCrdt/LcmCrdtKernel.cs +++ b/backend/FwLite/LcmCrdt/LcmCrdtKernel.cs @@ -42,7 +42,6 @@ public static IServiceCollection AddLcmCrdtClient(this IServiceCollection servic services.AddMiniLcmValidators(); services.AddScoped(); services.AddScoped(); - services.AddSingleton(); services.AddSingleton(); services.AddHttpClient(); @@ -60,8 +59,8 @@ public static IServiceCollection AddLcmCrdtClient(this IServiceCollection servic private static void ConfigureDbOptions(IServiceProvider provider, DbContextOptionsBuilder builder) { - var projectContext = provider.GetRequiredService(); - if (projectContext.Project is null) throw new NullReferenceException("Project is null"); + var projectContext = provider.GetRequiredService(); + projectContext.ValidateProjectScope(); #if DEBUG builder.EnableSensitiveDataLogging(); #endif @@ -212,13 +211,12 @@ public static Task OpenCrdtProject(this IServiceProvider services, //the project is stored in the async scope, if a new scope is created in this method then it will be gone once the method returns //making the lcm api unusable var projectsService = services.GetRequiredService(); - projectsService.SetProjectScope(project); - return LoadMiniLcmApi(services); + return LoadMiniLcmApi(services, project); } - private static async Task LoadMiniLcmApi(IServiceProvider services) + private static async Task LoadMiniLcmApi(IServiceProvider services, CrdtProject project) { - await services.GetRequiredService().PopulateProjectDataCache(); + await services.GetRequiredService().SetupProjectContext(project); return services.GetRequiredService(); } } diff --git a/backend/FwLite/LcmCrdt/ProjectContext.cs b/backend/FwLite/LcmCrdt/ProjectContext.cs deleted file mode 100644 index 30bfb3c58..000000000 --- a/backend/FwLite/LcmCrdt/ProjectContext.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace LcmCrdt; - -public class ProjectContext -{ - private sealed class ProjectHolder - { - public CrdtProject? Project; - } - - private static readonly AsyncLocal _projectHolder = new(); - - public virtual CrdtProject? Project - { - get => _projectHolder.Value?.Project; - set - { - //don't change existing if they are the same, that will break nested scopes - if (Project == value) return; - var holder = _projectHolder.Value; - if (holder != null) - { - // Clear current Project trapped in the AsyncLocals, as its done. - holder.Project = null; - } - - if (value is not null) - { - // Use an object indirection to hold the Project in the AsyncLocal, - // so it can be cleared in all ExecutionContexts when its cleared above. - _projectHolder.Value = new ProjectHolder { Project = value }; - } - } - } -} diff --git a/backend/FwLite/LocalWebApp/Hubs/CrdtMiniLcmApiHub.cs b/backend/FwLite/LocalWebApp/Hubs/CrdtMiniLcmApiHub.cs index e9725f397..5c73b7c1b 100644 --- a/backend/FwLite/LocalWebApp/Hubs/CrdtMiniLcmApiHub.cs +++ b/backend/FwLite/LocalWebApp/Hubs/CrdtMiniLcmApiHub.cs @@ -37,12 +37,17 @@ public override async Task OnConnectedAsync() await syncService.ExecuteSync(); Cleanup = [ - changeEventBus.OnEntryUpdated.Subscribe(e => OnEntryChangedExternal(e, hubContext, memoryCache, Context.ConnectionId)) + changeEventBus.OnProjectEntryUpdated(projectContext.Project).Subscribe(e => OnEntryChangedExternal(e, hubContext, memoryCache, Context.ConnectionId)) ]; await lexboxProjectService.ListenForProjectChanges(projectContext.ProjectData, Context.ConnectionAborted); } + private void TriggerSync() + { + backgroundSyncService.TriggerSync(projectContext.Project); + } + private static void OnEntryChangedExternal(Entry entry, IHubContext hubContext, IMemoryCache cache, @@ -94,7 +99,7 @@ public override IAsyncEnumerable SearchEntries(string query, QueryOptions public override async Task CreateWritingSystem(WritingSystemType type, WritingSystem writingSystem) { var newWritingSystem = await base.CreateWritingSystem(type, writingSystem); - backgroundSyncService.TriggerSync(); + TriggerSync(); return newWritingSystem; } @@ -103,21 +108,21 @@ public override async Task UpdateWritingSystem(WritingSystemId id JsonPatchDocument update) { var writingSystem = await base.UpdateWritingSystem(id, type, update); - backgroundSyncService.TriggerSync(); + TriggerSync(); return writingSystem; } public override async Task CreateSense(Guid entryId, Sense sense) { var createdSense = await base.CreateSense(entryId, sense); - backgroundSyncService.TriggerSync(); + TriggerSync(); return createdSense; } public override async Task UpdateSense(Guid entryId, Guid senseId, JsonPatchDocument update) { var sense = await base.UpdateSense(entryId, senseId, update); - backgroundSyncService.TriggerSync(); + TriggerSync(); return sense; } @@ -126,7 +131,7 @@ public override async Task CreateExampleSentence(Guid entryId, ExampleSentence exampleSentence) { var createdSentence = await base.CreateExampleSentence(entryId, senseId, exampleSentence); - backgroundSyncService.TriggerSync(); + TriggerSync(); return createdSentence; } @@ -136,13 +141,13 @@ public override async Task UpdateExampleSentence(Guid entryId, JsonPatchDocument update) { var sentence = await base.UpdateExampleSentence(entryId, senseId, exampleSentenceId, update); - backgroundSyncService.TriggerSync(); + TriggerSync(); return sentence; } protected override async Task NotifyEntryUpdated(Entry entry) { await base.NotifyEntryUpdated(entry); - backgroundSyncService.TriggerSync(); + TriggerSync(); } } diff --git a/backend/FwLite/LocalWebApp/LocalWebAppServer.cs b/backend/FwLite/LocalWebApp/LocalWebAppServer.cs index 8804569d8..16a7db166 100644 --- a/backend/FwLite/LocalWebApp/LocalWebAppServer.cs +++ b/backend/FwLite/LocalWebApp/LocalWebAppServer.cs @@ -79,11 +79,7 @@ public static WebApplication SetupAppServer(WebApplicationOptions options, Actio var projectName = context.GetProjectName(); if (!string.IsNullOrWhiteSpace(projectName)) { - var projectsService = context.RequestServices.GetRequiredService(); - projectsService.SetProjectScope(projectsService.GetProject(projectName) ?? - throw new InvalidOperationException( - $"Project {projectName} not found")); - await context.RequestServices.GetRequiredService().PopulateProjectDataCache(); + await context.RequestServices.GetRequiredService().SetupProjectContext(projectName); } var fwData = context.GetFwDataName(); if (!string.IsNullOrWhiteSpace(fwData)) diff --git a/backend/FwLite/LocalWebApp/Routes/TestRoutes.cs b/backend/FwLite/LocalWebApp/Routes/TestRoutes.cs index 965442f43..9d21ba34f 100644 --- a/backend/FwLite/LocalWebApp/Routes/TestRoutes.cs +++ b/backend/FwLite/LocalWebApp/Routes/TestRoutes.cs @@ -1,4 +1,5 @@ using FwLiteShared; +using LcmCrdt; using LocalWebApp.Hubs; using LocalWebApp.Services; using Microsoft.OpenApi.Models; @@ -26,16 +27,16 @@ public static IEndpointConventionBuilder MapTest(this WebApplication app) { return api.GetEntries(); }); - group.MapPost("/set-entry-note", async (IMiniLcmApi api, ChangeEventBus eventBus, Guid entryId, string ws, string note) => + group.MapPost("/set-entry-note", async (IMiniLcmApi api, CurrentProjectService projectContext, ChangeEventBus eventBus, Guid entryId, string ws, string note) => { var entry = await api.UpdateEntry(entryId, new UpdateObjectInput().Set(e => e.Note[ws], note)); - eventBus.NotifyEntryUpdated(entry); + eventBus.NotifyEntryUpdated(entry, projectContext.Project); }); group.MapPost("/add-new-entry", - async (IMiniLcmApi api, ChangeEventBus eventBus, Entry entry) => + async (IMiniLcmApi api, CurrentProjectService projectContext, ChangeEventBus eventBus, Entry entry) => { var createdEntry = await api.CreateEntry(entry); - eventBus.NotifyEntryUpdated(createdEntry); + eventBus.NotifyEntryUpdated(createdEntry, projectContext.Project); }); return group; } From 353d78a489e3b13985b95fe3a5e685949158da0d Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Tue, 17 Dec 2024 11:47:23 +0700 Subject: [PATCH 042/108] correct nrt error --- backend/Testing/SyncReverseProxy/SendReceiveServiceTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/Testing/SyncReverseProxy/SendReceiveServiceTests.cs b/backend/Testing/SyncReverseProxy/SendReceiveServiceTests.cs index 8d6edc151..444b3f58d 100644 --- a/backend/Testing/SyncReverseProxy/SendReceiveServiceTests.cs +++ b/backend/Testing/SyncReverseProxy/SendReceiveServiceTests.cs @@ -117,7 +117,7 @@ public async Task ModifyProjectData(HgProtocol protocol) // Verify the push updated the last commit date var lastCommitDateAfter = await _adminApiTester.GetProjectLastCommit(projectConfig.Code); - lastCommitDateAfter.Should().BeAfter(lastCommitDate.Value); + lastCommitDateAfter.Should().BeAfter(lastCommitDate!.Value); } [Theory] From bb6a3f8ccfe6bd0413b8ca5156f0acdcbc38e717 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Tue, 17 Dec 2024 11:56:07 +0700 Subject: [PATCH 043/108] update swashbuckle version to fix build error error was `error : Unable to find service type 'Microsoft.Extensions.ApiDescriptions.IDocumentProvider' in loaded assemblies.` --- backend/LexBoxApi/LexBoxApi.csproj | 2 +- backend/SyncReverseProxy/SyncReverseProxy.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/LexBoxApi/LexBoxApi.csproj b/backend/LexBoxApi/LexBoxApi.csproj index a2cfe8e59..d8960afa8 100644 --- a/backend/LexBoxApi/LexBoxApi.csproj +++ b/backend/LexBoxApi/LexBoxApi.csproj @@ -51,7 +51,7 @@ - + diff --git a/backend/SyncReverseProxy/SyncReverseProxy.csproj b/backend/SyncReverseProxy/SyncReverseProxy.csproj index fe72988bf..847c4e381 100644 --- a/backend/SyncReverseProxy/SyncReverseProxy.csproj +++ b/backend/SyncReverseProxy/SyncReverseProxy.csproj @@ -8,7 +8,7 @@ - + From 8a4b35463853f7ea0dde1755b4a78ad3818e256c Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Tue, 17 Dec 2024 12:06:25 +0700 Subject: [PATCH 044/108] ignore csproj.user files --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 8283534be..9a4f25740 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,5 @@ artifacts/ #Verify *.received.* backend/FwLite/FwLiteShared/wwwroot/viewer + +*.csproj.user From e6af4e495b9cd28eda93489caa1b1099524fa4b9 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Tue, 17 Dec 2024 14:41:49 +0700 Subject: [PATCH 045/108] remove unused layout files --- .../FwLiteShared/Layout/MainLayout.razor | 23 ---- .../FwLiteShared/Layout/MainLayout.razor.css | 97 ---------------- .../FwLite/FwLiteShared/Layout/NavMenu.razor | 30 ----- .../FwLiteShared/Layout/NavMenu.razor.css | 105 ------------------ backend/FwLite/FwLiteShared/Routes.razor | 2 +- 5 files changed, 1 insertion(+), 256 deletions(-) delete mode 100644 backend/FwLite/FwLiteShared/Layout/MainLayout.razor delete mode 100644 backend/FwLite/FwLiteShared/Layout/MainLayout.razor.css delete mode 100644 backend/FwLite/FwLiteShared/Layout/NavMenu.razor delete mode 100644 backend/FwLite/FwLiteShared/Layout/NavMenu.razor.css diff --git a/backend/FwLite/FwLiteShared/Layout/MainLayout.razor b/backend/FwLite/FwLiteShared/Layout/MainLayout.razor deleted file mode 100644 index 78624f3dd..000000000 --- a/backend/FwLite/FwLiteShared/Layout/MainLayout.razor +++ /dev/null @@ -1,23 +0,0 @@ -@inherits LayoutComponentBase - -
- - -
- - -
- @Body -
-
-
- -
- An unhandled error has occurred. - Reload - 🗙 -
diff --git a/backend/FwLite/FwLiteShared/Layout/MainLayout.razor.css b/backend/FwLite/FwLiteShared/Layout/MainLayout.razor.css deleted file mode 100644 index 54b45387f..000000000 --- a/backend/FwLite/FwLiteShared/Layout/MainLayout.razor.css +++ /dev/null @@ -1,97 +0,0 @@ -.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 { - 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; - } diff --git a/backend/FwLite/FwLiteShared/Layout/NavMenu.razor b/backend/FwLite/FwLiteShared/Layout/NavMenu.razor deleted file mode 100644 index 498c5542a..000000000 --- a/backend/FwLite/FwLiteShared/Layout/NavMenu.razor +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - diff --git a/backend/FwLite/FwLiteShared/Layout/NavMenu.razor.css b/backend/FwLite/FwLiteShared/Layout/NavMenu.razor.css deleted file mode 100644 index 26e461e85..000000000 --- a/backend/FwLite/FwLiteShared/Layout/NavMenu.razor.css +++ /dev/null @@ -1,105 +0,0 @@ -.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 { - 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; - } -} diff --git a/backend/FwLite/FwLiteShared/Routes.razor b/backend/FwLite/FwLiteShared/Routes.razor index 43f8cfac4..2613ad901 100644 --- a/backend/FwLite/FwLiteShared/Routes.razor +++ b/backend/FwLite/FwLiteShared/Routes.razor @@ -5,7 +5,7 @@ await jsRuntime.InvokeVoidAsync("onBlazorNavigate", context.Path); } } - + From bb94fa3b4c16138b0bf9541c427d2dbffe56c4a8 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Tue, 17 Dec 2024 14:42:24 +0700 Subject: [PATCH 046/108] dispose of the module properly when the SvelteLayout is disposed of --- backend/FwLite/FwLiteShared/Layout/SvelteLayout.razor | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/backend/FwLite/FwLiteShared/Layout/SvelteLayout.razor b/backend/FwLite/FwLiteShared/Layout/SvelteLayout.razor index 930094573..ae4ab106f 100644 --- a/backend/FwLite/FwLiteShared/Layout/SvelteLayout.razor +++ b/backend/FwLite/FwLiteShared/Layout/SvelteLayout.razor @@ -1,10 +1,10 @@ @inherits LayoutComponentBase @using FwLiteShared.Services -@using LexCore.Entities @using Microsoft.Extensions.Logging @inject IJSRuntime JS @inject ILogger Logger @inject FwLiteProvider FwLiteProvider +@implements IAsyncDisposable @@ -21,6 +21,7 @@ const services = window.lexbox.FwLiteProvider; services[key] = service; }; + //called from FwLiteProvider.InjectCrdtProject window['notifyEntryUpdated'] = (projectName, entry) => { if (window.lexbox?.EventBus) { window.lexbox.EventBus.notifyEntryUpdated(entry); @@ -50,4 +51,10 @@ "/" + Assets["_content/FwLiteShared/viewer/index.js"]); } } + + public async ValueTask DisposeAsync() + { + await (module?.DisposeAsync() ?? ValueTask.CompletedTask); + } + } From 33aa391b800b7234c530c405c7d964dd0626fc7c Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Tue, 17 Dec 2024 14:43:53 +0700 Subject: [PATCH 047/108] change crdt project pages to use OwningComponentBase which ensures they have a service scope to use for project access --- .../FwLite/FwLiteShared/FwLiteSharedKernel.cs | 2 +- .../FwLiteShared/Pages/CrdtProject.razor | 16 ++++++------ .../FwLiteShared/Pages/FwdataProject.razor | 25 +++++++++++++------ .../FwLiteShared/Services/FwLiteProvider.cs | 12 ++++----- 4 files changed, 32 insertions(+), 23 deletions(-) diff --git a/backend/FwLite/FwLiteShared/FwLiteSharedKernel.cs b/backend/FwLite/FwLiteShared/FwLiteSharedKernel.cs index a8ec5ad51..bc6d75159 100644 --- a/backend/FwLite/FwLiteShared/FwLiteSharedKernel.cs +++ b/backend/FwLite/FwLiteShared/FwLiteSharedKernel.cs @@ -24,7 +24,7 @@ public static IServiceCollection AddFwLiteShared(this IServiceCollection service services.AddScoped(); services.AddSingleton(); services.AddSingleton(); - services.AddScoped(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/backend/FwLite/FwLiteShared/Pages/CrdtProject.razor b/backend/FwLite/FwLiteShared/Pages/CrdtProject.razor index e7827143d..af6d88fa5 100644 --- a/backend/FwLite/FwLiteShared/Pages/CrdtProject.razor +++ b/backend/FwLite/FwLiteShared/Pages/CrdtProject.razor @@ -1,9 +1,8 @@ @page "/project/{projectName}" @using FwLiteShared.Layout @using FwLiteShared.Services -@using Microsoft.Extensions.Logging -@inject FwLiteProvider FwLiteProvider -@inject ILogger Logger +@using Microsoft.Extensions.DependencyInjection +@inherits OwningComponentBase @implements IAsyncDisposable @layout SvelteLayout; @@ -14,19 +13,18 @@ private IAsyncDisposable? _disposable; - protected override async Task OnAfterRenderAsync(bool firstRender) + protected override async Task OnInitializedAsync() { - Logger.LogInformation("OnAfterRenderAsync CrdtProject, firstRender: {firstRender}", firstRender); - if (firstRender) - { - _disposable = await FwLiteProvider.InjectCrdtProject(ProjectName); - } + var fwLiteProvider = ScopedServices.GetRequiredService(); + _disposable = await fwLiteProvider.InjectCrdtProject(ScopedServices, ProjectName); } public async ValueTask DisposeAsync() { if (_disposable is not null) await _disposable.DisposeAsync(); + //dispose isn't called because this component implements IAsyncDisposable + ((IDisposable)this).Dispose(); } } diff --git a/backend/FwLite/FwLiteShared/Pages/FwdataProject.razor b/backend/FwLite/FwLiteShared/Pages/FwdataProject.razor index c1cb981fa..f13225a9b 100644 --- a/backend/FwLite/FwLiteShared/Pages/FwdataProject.razor +++ b/backend/FwLite/FwLiteShared/Pages/FwdataProject.razor @@ -1,19 +1,30 @@ @page "/fwdata/{projectName}" @using FwLiteShared.Layout @using FwLiteShared.Services -@inject FwLiteProvider FwLiteProvider +@using Microsoft.Extensions.DependencyInjection +@inherits OwningComponentBase +@implements IAsyncDisposable @layout SvelteLayout; @code { + [Parameter] public required string ProjectName { get; set; } - protected override async Task OnAfterRenderAsync(bool firstRender) + private IAsyncDisposable? _disposable; + + protected override async Task OnInitializedAsync() + { + var fwLiteProvider = ScopedServices.GetRequiredService(); + _disposable = await fwLiteProvider.InjectFwDataProject(ScopedServices, ProjectName); + } + + public async ValueTask DisposeAsync() { - await base.OnAfterRenderAsync(firstRender); - if (firstRender) - { - await FwLiteProvider.InjectFwDataProject(ProjectName); - } + if (_disposable is not null) + await _disposable.DisposeAsync(); + //dispose isn't called because this component implements IAsyncDisposable + ((IDisposable)this).Dispose(); } + } diff --git a/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs b/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs index 9b7695f3f..95ee7a0fd 100644 --- a/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs +++ b/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs @@ -15,7 +15,6 @@ public class FwLiteProvider( AuthService authService, ImportFwdataService importFwdataService, CrdtProjectsService crdtProjectsService, - IServiceProvider serviceProvider, FwDataProjectContext fwDataProjectContext, FieldWorksProjectList fieldWorksProjectList, LexboxProjectService lexboxProjectService, @@ -63,16 +62,16 @@ public async Task SetService(DotnetService service, object? serviceInstance) await jsRuntime.InvokeVoidAsync(OverrideServiceFunctionName, service.ToString(), reference); } - public async Task InjectCrdtProject(string projectName) + public async Task InjectCrdtProject(IServiceProvider scopedServices, string projectName) { var project = crdtProjectsService.GetProject(projectName) ?? throw new InvalidOperationException($"Crdt Project {projectName} not found"); - var projectData = await serviceProvider.GetRequiredService().SetupProjectContext(project); + var projectData = await scopedServices.GetRequiredService().SetupProjectContext(project); await lexboxProjectService.ListenForProjectChanges(projectData, CancellationToken.None); var entryUpdatedSubscription = changeEventBus.OnProjectEntryUpdated(project).Subscribe(entry => { _ = jsRuntime.InvokeVoidAsync("notifyEntryUpdated", projectName, entry); }); - var service = ActivatorUtilities.CreateInstance(serviceProvider, project); + var service = ActivatorUtilities.CreateInstance(scopedServices, project); await SetService(DotnetService.MiniLcmApi, service); return Defer.Async(async () => { @@ -81,10 +80,11 @@ public async Task InjectCrdtProject(string projectName) }); } - public async Task InjectFwDataProject(string projectName) + public async Task InjectFwDataProject(IServiceProvider scopedServices, string projectName) { fwDataProjectContext.Project = fieldWorksProjectList.GetProject(projectName); - var service = ActivatorUtilities.CreateInstance(serviceProvider, serviceProvider.GetRequiredKeyedService(FwDataBridgeKernel.FwDataApiKey)); + var service = ActivatorUtilities.CreateInstance(scopedServices, + scopedServices.GetRequiredKeyedService(FwDataBridgeKernel.FwDataApiKey)); await SetService(DotnetService.MiniLcmApi, service); return Defer.Async(async () => await SetService(DotnetService.MiniLcmApi, null)); } From 8dfe1ba9e7bd88f04611e4ca8fd59dc8ee850053 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Tue, 17 Dec 2024 14:53:15 +0700 Subject: [PATCH 048/108] pass in jsRuntime as a parameter to FwLiteProvider rather than injecting it since it's a scoped service --- .../FwLite/FwLiteShared/Layout/SvelteLayout.razor | 2 +- .../FwLiteShared/Services/FwLiteProvider.cs | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/backend/FwLite/FwLiteShared/Layout/SvelteLayout.razor b/backend/FwLite/FwLiteShared/Layout/SvelteLayout.razor index ae4ab106f..a7e87a9d5 100644 --- a/backend/FwLite/FwLiteShared/Layout/SvelteLayout.razor +++ b/backend/FwLite/FwLiteShared/Layout/SvelteLayout.razor @@ -44,7 +44,7 @@ { foreach (var (serviceKey, service) in FwLiteProvider.GetServices()) { - await FwLiteProvider.SetService(serviceKey, service); + await FwLiteProvider.SetService(JS, serviceKey, service); } module = await JS.InvokeAsync("import", diff --git a/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs b/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs index 95ee7a0fd..c1eecc295 100644 --- a/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs +++ b/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs @@ -18,8 +18,7 @@ public class FwLiteProvider( FwDataProjectContext fwDataProjectContext, FieldWorksProjectList fieldWorksProjectList, LexboxProjectService lexboxProjectService, - ChangeEventBus changeEventBus, - IJSRuntime jsRuntime + ChangeEventBus changeEventBus ) : IDisposable { public const string OverrideServiceFunctionName = "setOverrideService"; @@ -50,7 +49,7 @@ public object GetService(DotnetService service) }; } - public async Task SetService(DotnetService service, object? serviceInstance) + public async Task SetService(IJSRuntime jsRuntime, DotnetService service, object? serviceInstance) { DotNetObjectReference? reference = null; if (serviceInstance is not null) @@ -64,6 +63,7 @@ public async Task SetService(DotnetService service, object? serviceInstance) public async Task InjectCrdtProject(IServiceProvider scopedServices, string projectName) { + var jsRuntime = scopedServices.GetRequiredService(); var project = crdtProjectsService.GetProject(projectName) ?? throw new InvalidOperationException($"Crdt Project {projectName} not found"); var projectData = await scopedServices.GetRequiredService().SetupProjectContext(project); await lexboxProjectService.ListenForProjectChanges(projectData, CancellationToken.None); @@ -72,21 +72,22 @@ public async Task InjectCrdtProject(IServiceProvider scopedSer _ = jsRuntime.InvokeVoidAsync("notifyEntryUpdated", projectName, entry); }); var service = ActivatorUtilities.CreateInstance(scopedServices, project); - await SetService(DotnetService.MiniLcmApi, service); + await SetService(jsRuntime,DotnetService.MiniLcmApi, service); return Defer.Async(async () => { entryUpdatedSubscription.Dispose(); - await SetService(DotnetService.MiniLcmApi, null); + await SetService(jsRuntime, DotnetService.MiniLcmApi, null); }); } public async Task InjectFwDataProject(IServiceProvider scopedServices, string projectName) { + var jsRuntime = scopedServices.GetRequiredService(); fwDataProjectContext.Project = fieldWorksProjectList.GetProject(projectName); var service = ActivatorUtilities.CreateInstance(scopedServices, scopedServices.GetRequiredKeyedService(FwDataBridgeKernel.FwDataApiKey)); - await SetService(DotnetService.MiniLcmApi, service); - return Defer.Async(async () => await SetService(DotnetService.MiniLcmApi, null)); + await SetService(jsRuntime, DotnetService.MiniLcmApi, service); + return Defer.Async(async () => await SetService(jsRuntime, DotnetService.MiniLcmApi, null)); } } From 4335e0585f26cb3f6af672fb10c626410e3801a5 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Tue, 17 Dec 2024 15:08:22 +0700 Subject: [PATCH 049/108] make login buttons reactive --- frontend/viewer/src/HomeView.svelte | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/frontend/viewer/src/HomeView.svelte b/frontend/viewer/src/HomeView.svelte index 4b53ae200..d855efed3 100644 --- a/frontend/viewer/src/HomeView.svelte +++ b/frontend/viewer/src/HomeView.svelte @@ -77,6 +77,23 @@ throw error; }); + let loadingServer: string = ''; + async function login(server: ILexboxServer) { + loadingServer = server.authority; + await authService.signInWebView(server); + await fetchRemoteProjects(); + serversStatus = await authService.servers(); + loadingServer = ''; + } + + async function logout(server: ILexboxServer) { + loadingServer = server.authority; + await authService.logout(server); + await fetchRemoteProjects(); + serversStatus = await authService.servers(); + loadingServer = ''; + } + let serversStatus: IServerStatus[] = []; onMount(async () => serversStatus = await authService.servers()); @@ -232,9 +249,9 @@

{status.loggedInAs}

{/if} {#if status.loggedIn} - + {:else} - + {/if} {@const serverProjects = remoteProjects[server.authority]?.filter(p => p.crdt) ?? []} From 674a3ac8353853db4530f9d69fb2000d3e98f27c Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Tue, 17 Dec 2024 15:21:12 +0700 Subject: [PATCH 050/108] remove unused components --- .../FwLite/FwLiteShared/Pages/Counter.razor | 18 --------- .../FwLite/FwLiteShared/Pages/Weather.razor | 40 ------------------- 2 files changed, 58 deletions(-) delete mode 100644 backend/FwLite/FwLiteShared/Pages/Counter.razor delete mode 100644 backend/FwLite/FwLiteShared/Pages/Weather.razor diff --git a/backend/FwLite/FwLiteShared/Pages/Counter.razor b/backend/FwLite/FwLiteShared/Pages/Counter.razor deleted file mode 100644 index ef23cb316..000000000 --- a/backend/FwLite/FwLiteShared/Pages/Counter.razor +++ /dev/null @@ -1,18 +0,0 @@ -@page "/counter" - -Counter - -

Counter

- -

Current count: @currentCount

- - - -@code { - private int currentCount = 0; - - private void IncrementCount() - { - currentCount++; - } -} diff --git a/backend/FwLite/FwLiteShared/Pages/Weather.razor b/backend/FwLite/FwLiteShared/Pages/Weather.razor deleted file mode 100644 index 4bf8d259a..000000000 --- a/backend/FwLite/FwLiteShared/Pages/Weather.razor +++ /dev/null @@ -1,40 +0,0 @@ -@page "/weather" - -Files - -

Files

- -@if (files == null) -{ -

Loading...

-} -else -{ - - - - - - - - @foreach (var file in files) - { - - - - } - -
File name
@file.Name
-} - -@code { - private FileSystemInfo[]? files; - - protected override async Task OnInitializedAsync() - { - // Simulate asynchronous loading to demonstrate a loading indicator - await Task.Delay(500); - - files = new DirectoryInfo("/").GetFileSystemInfos(); - } -} From 67dd28a3d4576f677128f9a5648545bf4fba3b88 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Tue, 17 Dec 2024 15:21:39 +0700 Subject: [PATCH 051/108] create an async disposing OwningComponentBase and use it for project pages --- .../FwLiteShared/Pages/CrdtProject.razor | 12 ++--- .../FwLiteShared/Pages/FwdataProject.razor | 11 ++-- backend/FwLite/FwLiteShared/Pages/Home.razor | 1 + .../Pages/OwningComponentBaseAsync.cs | 50 +++++++++++++++++++ .../FwLiteShared/Services/FwLiteProvider.cs | 8 +-- 5 files changed, 65 insertions(+), 17 deletions(-) create mode 100644 backend/FwLite/FwLiteShared/Pages/OwningComponentBaseAsync.cs diff --git a/backend/FwLite/FwLiteShared/Pages/CrdtProject.razor b/backend/FwLite/FwLiteShared/Pages/CrdtProject.razor index af6d88fa5..921c47617 100644 --- a/backend/FwLite/FwLiteShared/Pages/CrdtProject.razor +++ b/backend/FwLite/FwLiteShared/Pages/CrdtProject.razor @@ -2,9 +2,9 @@ @using FwLiteShared.Layout @using FwLiteShared.Services @using Microsoft.Extensions.DependencyInjection -@inherits OwningComponentBase -@implements IAsyncDisposable +@inherits OwningComponentBaseAsync @layout SvelteLayout; +@inject IJSRuntime JS; @code { @@ -16,15 +16,13 @@ protected override async Task OnInitializedAsync() { var fwLiteProvider = ScopedServices.GetRequiredService(); - _disposable = await fwLiteProvider.InjectCrdtProject(ScopedServices, ProjectName); + _disposable = await fwLiteProvider.InjectCrdtProject(JS, ScopedServices, ProjectName); } - public async ValueTask DisposeAsync() + protected override async ValueTask DisposeAsyncCore() { + await base.DisposeAsyncCore(); if (_disposable is not null) await _disposable.DisposeAsync(); - //dispose isn't called because this component implements IAsyncDisposable - ((IDisposable)this).Dispose(); } - } diff --git a/backend/FwLite/FwLiteShared/Pages/FwdataProject.razor b/backend/FwLite/FwLiteShared/Pages/FwdataProject.razor index f13225a9b..20110c079 100644 --- a/backend/FwLite/FwLiteShared/Pages/FwdataProject.razor +++ b/backend/FwLite/FwLiteShared/Pages/FwdataProject.razor @@ -2,9 +2,9 @@ @using FwLiteShared.Layout @using FwLiteShared.Services @using Microsoft.Extensions.DependencyInjection -@inherits OwningComponentBase -@implements IAsyncDisposable +@inherits OwningComponentBaseAsync @layout SvelteLayout; +@inject IJSRuntime JS; @code { @@ -16,15 +16,14 @@ protected override async Task OnInitializedAsync() { var fwLiteProvider = ScopedServices.GetRequiredService(); - _disposable = await fwLiteProvider.InjectFwDataProject(ScopedServices, ProjectName); + _disposable = await fwLiteProvider.InjectFwDataProject(JS, ScopedServices, ProjectName); } - public async ValueTask DisposeAsync() + protected override async ValueTask DisposeAsyncCore() { + await base.DisposeAsyncCore(); if (_disposable is not null) await _disposable.DisposeAsync(); - //dispose isn't called because this component implements IAsyncDisposable - ((IDisposable)this).Dispose(); } } diff --git a/backend/FwLite/FwLiteShared/Pages/Home.razor b/backend/FwLite/FwLiteShared/Pages/Home.razor index 77af5d078..50056ef07 100644 --- a/backend/FwLite/FwLiteShared/Pages/Home.razor +++ b/backend/FwLite/FwLiteShared/Pages/Home.razor @@ -2,3 +2,4 @@ @using FwLiteShared.Layout @layout SvelteLayout; +@*this looks empty because it is, but it's required to declare the route which is then used by the svelte router*@ diff --git a/backend/FwLite/FwLiteShared/Pages/OwningComponentBaseAsync.cs b/backend/FwLite/FwLiteShared/Pages/OwningComponentBaseAsync.cs new file mode 100644 index 000000000..a4d9be009 --- /dev/null +++ b/backend/FwLite/FwLiteShared/Pages/OwningComponentBaseAsync.cs @@ -0,0 +1,50 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.DependencyInjection; + +namespace FwLiteShared.Pages; + +/// +/// copy of OwningComponentBase, but implements IAsyncDisposable instead of IDisposable +/// +public abstract class OwningComponentBaseAsync: ComponentBase, IAsyncDisposable +{ + private AsyncServiceScope? _scope; + + [Inject] + IServiceScopeFactory ScopeFactory { get; set; } = default!; + + /// + /// Gets a value determining if the component and associated services have been disposed. + /// + protected bool IsDisposed { get; private set; } + + protected IServiceProvider ScopedServices + { + get + { + if (ScopeFactory == null) + { + throw new InvalidOperationException("Services cannot be accessed before the component is initialized."); + } + + ObjectDisposedException.ThrowIf(IsDisposed, this); + + _scope ??= ScopeFactory.CreateAsyncScope(); + return _scope.Value.ServiceProvider; + } + } + protected virtual async ValueTask DisposeAsyncCore() + { + if (!IsDisposed) + { + await (_scope?.DisposeAsync() ?? ValueTask.CompletedTask); + _scope = null; + IsDisposed = true; + } + } + + public async ValueTask DisposeAsync() + { + await DisposeAsyncCore(); + } +} diff --git a/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs b/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs index c1eecc295..cdebe7366 100644 --- a/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs +++ b/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs @@ -61,9 +61,10 @@ public async Task SetService(IJSRuntime jsRuntime, DotnetService service, object await jsRuntime.InvokeVoidAsync(OverrideServiceFunctionName, service.ToString(), reference); } - public async Task InjectCrdtProject(IServiceProvider scopedServices, string projectName) + public async Task InjectCrdtProject(IJSRuntime jsRuntime, + IServiceProvider scopedServices, + string projectName) { - var jsRuntime = scopedServices.GetRequiredService(); var project = crdtProjectsService.GetProject(projectName) ?? throw new InvalidOperationException($"Crdt Project {projectName} not found"); var projectData = await scopedServices.GetRequiredService().SetupProjectContext(project); await lexboxProjectService.ListenForProjectChanges(projectData, CancellationToken.None); @@ -80,9 +81,8 @@ public async Task InjectCrdtProject(IServiceProvider scopedSer }); } - public async Task InjectFwDataProject(IServiceProvider scopedServices, string projectName) + public async Task InjectFwDataProject(IJSRuntime jsRuntime, IServiceProvider scopedServices, string projectName) { - var jsRuntime = scopedServices.GetRequiredService(); fwDataProjectContext.Project = fieldWorksProjectList.GetProject(projectName); var service = ActivatorUtilities.CreateInstance(scopedServices, scopedServices.GetRequiredKeyedService(FwDataBridgeKernel.FwDataApiKey)); From 4e3ce27aa30748002a598bbf3ad5f3e8e9054932 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Tue, 17 Dec 2024 15:57:58 +0700 Subject: [PATCH 052/108] fix app header under status bar issue on android, ensure that status bar color matches fw lite theme --- backend/FwLite/FwLiteDesktop/App.xaml | 20 +------------------ .../Platforms/Android/MainActivity.cs | 7 +++++++ .../Android/Resources/values-v35/styles.xml | 6 ++++++ .../Android/Resources/values/colors.xml | 8 ++++---- .../Android/Resources/values/styles.xml | 5 +++++ 5 files changed, 23 insertions(+), 23 deletions(-) create mode 100644 backend/FwLite/FwLiteDesktop/Platforms/Android/Resources/values-v35/styles.xml create mode 100644 backend/FwLite/FwLiteDesktop/Platforms/Android/Resources/values/styles.xml diff --git a/backend/FwLite/FwLiteDesktop/App.xaml b/backend/FwLite/FwLiteDesktop/App.xaml index 188c14802..88566e95a 100644 --- a/backend/FwLite/FwLiteDesktop/App.xaml +++ b/backend/FwLite/FwLiteDesktop/App.xaml @@ -6,27 +6,9 @@ - #512bdf + #152747 White - - - - diff --git a/backend/FwLite/FwLiteDesktop/Platforms/Android/MainActivity.cs b/backend/FwLite/FwLiteDesktop/Platforms/Android/MainActivity.cs index 10206b2fe..41e05b101 100644 --- a/backend/FwLite/FwLiteDesktop/Platforms/Android/MainActivity.cs +++ b/backend/FwLite/FwLiteDesktop/Platforms/Android/MainActivity.cs @@ -14,6 +14,13 @@ namespace FwLiteDesktop; ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)] public class MainActivity : MauiAppCompatActivity { + protected override void OnCreate(Bundle? savedInstanceState) + { + //custom style, declared in Android/Resources/values/styles.xml, values-v35 is used based on the android version + Theme?.ApplyStyle(Resource.Style.OptOutEdgeToEdgeEnforcement, force: false); + base.OnCreate(savedInstanceState); + } + protected override void OnActivityResult(int requestCode, Result resultCode, Intent? data) { base.OnActivityResult(requestCode, resultCode, data); diff --git a/backend/FwLite/FwLiteDesktop/Platforms/Android/Resources/values-v35/styles.xml b/backend/FwLite/FwLiteDesktop/Platforms/Android/Resources/values-v35/styles.xml new file mode 100644 index 000000000..549691883 --- /dev/null +++ b/backend/FwLite/FwLiteDesktop/Platforms/Android/Resources/values-v35/styles.xml @@ -0,0 +1,6 @@ + + + + diff --git a/backend/FwLite/FwLiteDesktop/Platforms/Android/Resources/values/colors.xml b/backend/FwLite/FwLiteDesktop/Platforms/Android/Resources/values/colors.xml index c04d7492a..a0f2f0d51 100644 --- a/backend/FwLite/FwLiteDesktop/Platforms/Android/Resources/values/colors.xml +++ b/backend/FwLite/FwLiteDesktop/Platforms/Android/Resources/values/colors.xml @@ -1,6 +1,6 @@ - #512BD4 - #2B0B98 - #2B0B98 - \ No newline at end of file + #D6E6FF + #152747 + #152747 + diff --git a/backend/FwLite/FwLiteDesktop/Platforms/Android/Resources/values/styles.xml b/backend/FwLite/FwLiteDesktop/Platforms/Android/Resources/values/styles.xml new file mode 100644 index 000000000..2c76e4450 --- /dev/null +++ b/backend/FwLite/FwLiteDesktop/Platforms/Android/Resources/values/styles.xml @@ -0,0 +1,5 @@ + + + + From a2a73d42220906bcd2df3cd331f6dba6e9ef3a34 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Tue, 17 Dec 2024 16:06:58 +0700 Subject: [PATCH 053/108] use hsl color space rather than oklch to support older browsers on old phones --- frontend/viewer/tailwind.config.cjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/viewer/tailwind.config.cjs b/frontend/viewer/tailwind.config.cjs index 2e0d53eeb..bc5653344 100644 --- a/frontend/viewer/tailwind.config.cjs +++ b/frontend/viewer/tailwind.config.cjs @@ -16,7 +16,7 @@ module.exports = { // Search showing aliases and version (of root source) icons were introduced: https://pictogrammers.com/library/mdi/ collections: getIconCollections(['mdi']), }), - svelteUx({ colorSpace: 'oklch' }), + svelteUx({ colorSpace: 'hsl' }), require('@tailwindcss/typography'), ], ux: { From 807e7f13eaa77b2ebd2560facc9729c9ab9e1d4d Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Tue, 17 Dec 2024 16:29:16 +0700 Subject: [PATCH 054/108] remove unused generated types --- backend/FwLite/FwLiteShared/FwLiteShared.csproj | 2 -- .../TypeGen/ReinforcedFwLiteTypingConfig.cs | 5 ++++- .../FwLiteShared/Auth/ILexboxServer.ts | 2 +- .../Projects/IImportFwdataService.ts | 4 +++- .../FwLiteShared/Services/IFwLiteProvider.ts | 17 ----------------- .../Services/IMiniLcmJsInvokable.ts | 1 - 6 files changed, 8 insertions(+), 23 deletions(-) delete mode 100644 frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/IFwLiteProvider.ts diff --git a/backend/FwLite/FwLiteShared/FwLiteShared.csproj b/backend/FwLite/FwLiteShared/FwLiteShared.csproj index 3714bd907..2eac6352d 100644 --- a/backend/FwLite/FwLiteShared/FwLiteShared.csproj +++ b/backend/FwLite/FwLiteShared/FwLiteShared.csproj @@ -17,8 +17,6 @@ - - diff --git a/backend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs b/backend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs index 35996673e..93406870e 100644 --- a/backend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs +++ b/backend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs @@ -2,6 +2,7 @@ using FwLiteShared.Auth; using FwLiteShared.Projects; using FwLiteShared.Services; +using LcmCrdt; using MiniLcm; using MiniLcm.Models; using Reinforced.Typings; @@ -13,6 +14,7 @@ namespace FwLiteShared.TypeGen; +//docs https://github.com/reinforced/Reinforced.Typings/wiki/Fluent-configuration public static class ReinforcedFwLiteTypingConfig { public static void Configure(ConfigurationBuilder builder) @@ -27,6 +29,7 @@ public static void Configure(ConfigurationBuilder builder) DisableEsLintChecks(builder); builder.Substitute(typeof(WritingSystemId), new RtSimpleTypeName("string")); builder.Substitute(typeof(Guid), new RtSimpleTypeName("string")); + builder.Substitute(typeof(Uri), new RtSimpleTypeName("string")); builder.Substitute(typeof(DateTimeOffset), new RtSimpleTypeName("string")); builder.SubstituteGeneric(typeof(ValueTask<>), (type, resolver) => resolver.ResolveTypeName(typeof(Task<>).MakeGenericType(type.GenericTypeArguments[0]), true)); //todo generate a multistring type rather than just substituting it everywhere @@ -79,7 +82,6 @@ public static void Configure(ConfigurationBuilder builder) builder.ExportAsEnum().UseString(); builder.ExportAsInterfaces([typeof(QueryOptions), typeof(SortOptions), typeof(ExemplarOptions)], exportBuilder => exportBuilder.WithProperties(BindingFlags.Public | BindingFlags.Instance)); - builder.ExportAsInterface().WithPublicMethods(); builder.ExportAsEnum().UseString(); builder.ExportAsInterface().WithPublicMethods(); @@ -89,6 +91,7 @@ public static void Configure(ConfigurationBuilder builder) builder.ExportAsInterface().WithPublicProperties(); builder.ExportAsInterface().WithPublicProperties(); builder.ExportAsInterface().WithPublicProperties(); + builder.ExportAsInterface().WithPublicProperties(); } private static void DisableEsLintChecks(ConfigurationBuilder builder) diff --git a/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Auth/ILexboxServer.ts b/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Auth/ILexboxServer.ts index b27bcb6b4..6260ebdbe 100644 --- a/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Auth/ILexboxServer.ts +++ b/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Auth/ILexboxServer.ts @@ -5,7 +5,7 @@ export interface ILexboxServer { - authority: any; + authority: string; displayName: string; id: string; } diff --git a/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Projects/IImportFwdataService.ts b/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Projects/IImportFwdataService.ts index a61049df5..fd4818aa3 100644 --- a/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Projects/IImportFwdataService.ts +++ b/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Projects/IImportFwdataService.ts @@ -3,8 +3,10 @@ // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. +import type {ICrdtProject} from '../../LcmCrdt/ICrdtProject'; + export interface IImportFwdataService { - import(projectName: string) : Promise; + import(projectName: string) : Promise; } /* eslint-enable */ diff --git a/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/IFwLiteProvider.ts b/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/IFwLiteProvider.ts deleted file mode 100644 index eec785296..000000000 --- a/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/IFwLiteProvider.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* eslint-disable */ -// This code was generated by a Reinforced.Typings tool. -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. - -import type {DotnetService} from './DotnetService'; - -export interface IFwLiteProvider -{ - dispose() : void; - getServices() : { [key in DotnetService]: any }; - getService(service: DotnetService) : any; - setService(service: DotnetService, serviceInstance?: any) : Promise; - injectCrdtProject(projectName: string) : Promise; - injectFwDataProject(projectName: string) : Promise; -} -/* eslint-enable */ diff --git a/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/IMiniLcmJsInvokable.ts b/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/IMiniLcmJsInvokable.ts index 4332b6f1e..2933e57b9 100644 --- a/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/IMiniLcmJsInvokable.ts +++ b/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/IMiniLcmJsInvokable.ts @@ -18,7 +18,6 @@ import type {IComplexFormComponent} from '../../MiniLcm/Models/IComplexFormCompo export interface IMiniLcmJsInvokable { - disposeAsync() : any; supportedFeatures() : Promise; getWritingSystems() : Promise; getPartsOfSpeech() : Promise; From a8b6c3a9428ff315616fadb60f3200443772d048 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Tue, 17 Dec 2024 16:59:28 +0700 Subject: [PATCH 055/108] ensure that a specific target framework is set. this fixes issues with publish where specifying a single framework is required. This then breaks FwLiteDesktop and causes it to ignore the TargetFrameworks, fix that by defining an empty target framework --- backend/Directory.Build.props | 2 +- backend/FwLite/FwLiteDesktop/FwLiteDesktop.csproj | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/backend/Directory.Build.props b/backend/Directory.Build.props index cd811496f..f42632795 100644 --- a/backend/Directory.Build.props +++ b/backend/Directory.Build.props @@ -10,7 +10,7 @@ dev - net9.0 + net9.0 false enable enable diff --git a/backend/FwLite/FwLiteDesktop/FwLiteDesktop.csproj b/backend/FwLite/FwLiteDesktop/FwLiteDesktop.csproj index c33e0d025..39f5fece6 100644 --- a/backend/FwLite/FwLiteDesktop/FwLiteDesktop.csproj +++ b/backend/FwLite/FwLiteDesktop/FwLiteDesktop.csproj @@ -1,10 +1,11 @@ + + + net9.0-android;net9.0-ios;net9.0-maccatalyst $(TargetFrameworks);net9.0-windows10.0.19041.0 - - false false From 52f35148138d965f927ae6920a404cb7c78c43f4 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Wed, 18 Dec 2024 13:49:14 +0700 Subject: [PATCH 061/108] refactor FwDataProjectContext.cs to be scoped and not a singleton --- .../Fixtures/MockFwProjectList.cs | 3 +- .../FieldWorksProjectList.cs | 8 +++++- .../FwDataMiniLcmBridge/FwDataBridgeKernel.cs | 10 +++++-- .../FwDataMiniLcmBridge/FwDataFactory.cs | 23 ++------------- .../FwDataProjectContext.cs | 28 +------------------ .../LocalWebApp/Hubs/FwDataMiniLcmHub.cs | 2 +- .../LocalWebApp/Routes/FwIntegrationRoutes.cs | 2 +- 7 files changed, 22 insertions(+), 54 deletions(-) diff --git a/backend/FwLite/FwDataMiniLcmBridge.Tests/Fixtures/MockFwProjectList.cs b/backend/FwLite/FwDataMiniLcmBridge.Tests/Fixtures/MockFwProjectList.cs index 6463670c1..0ec4a36e2 100644 --- a/backend/FwLite/FwDataMiniLcmBridge.Tests/Fixtures/MockFwProjectList.cs +++ b/backend/FwLite/FwDataMiniLcmBridge.Tests/Fixtures/MockFwProjectList.cs @@ -3,7 +3,8 @@ namespace FwDataMiniLcmBridge.Tests.Fixtures; -public class MockFwProjectList(IOptions config, MockFwProjectLoader loader) : FieldWorksProjectList(config) +public class MockFwProjectList(IOptions config, MockFwProjectLoader loader, + FwDataFactory fwDataFactory) : FieldWorksProjectList(config, fwDataFactory) { public override IEnumerable EnumerateProjects() { diff --git a/backend/FwLite/FwDataMiniLcmBridge/FieldWorksProjectList.cs b/backend/FwLite/FwDataMiniLcmBridge/FieldWorksProjectList.cs index f77bf944c..734f19c9c 100644 --- a/backend/FwLite/FwDataMiniLcmBridge/FieldWorksProjectList.cs +++ b/backend/FwLite/FwDataMiniLcmBridge/FieldWorksProjectList.cs @@ -4,7 +4,7 @@ namespace FwDataMiniLcmBridge; -public class FieldWorksProjectList(IOptions config) +public class FieldWorksProjectList(IOptions config, FwDataFactory fwDataFactory) : IProjectProvider { protected readonly IOptions _config = config; @@ -24,4 +24,10 @@ public virtual IEnumerable EnumerateProjects() { return EnumerateProjects().OfType().FirstOrDefault(p => p.Name == name); } + + public IMiniLcmApi OpenProject(IProjectIdentifier project, bool saveOnDispose = true) + { + if (project is not FwDataProject fwDataProject) throw new ArgumentException("Project is not a fwdata project"); + return fwDataFactory.GetFwDataMiniLcmApi(fwDataProject, saveOnDispose); + } } diff --git a/backend/FwLite/FwDataMiniLcmBridge/FwDataBridgeKernel.cs b/backend/FwLite/FwDataMiniLcmBridge/FwDataBridgeKernel.cs index 395c5c78d..ba16aecf9 100644 --- a/backend/FwLite/FwDataMiniLcmBridge/FwDataBridgeKernel.cs +++ b/backend/FwLite/FwDataMiniLcmBridge/FwDataBridgeKernel.cs @@ -16,9 +16,15 @@ public static IServiceCollection AddFwDataBridge(this IServiceCollection service services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddKeyedScoped(FwDataApiKey, (provider, o) => provider.GetRequiredService().GetCurrentFwDataMiniLcmApi(true)); + services.AddKeyedScoped(FwDataApiKey, + (provider, o) => + { + var projectList = provider.GetRequiredService(); + var projectContext = provider.GetRequiredService(); + return projectList.OpenProject(projectContext.Project ?? throw new InvalidOperationException("No project is set in the context.")); + }); services.AddMiniLcmValidators(); - services.AddSingleton(); + services.AddScoped(); return services; } } diff --git a/backend/FwLite/FwDataMiniLcmBridge/FwDataFactory.cs b/backend/FwLite/FwDataMiniLcmBridge/FwDataFactory.cs index f780b9c13..658a85c18 100644 --- a/backend/FwLite/FwDataMiniLcmBridge/FwDataFactory.cs +++ b/backend/FwLite/FwDataMiniLcmBridge/FwDataFactory.cs @@ -10,7 +10,6 @@ namespace FwDataMiniLcmBridge; public class FwDataFactory( - FwDataProjectContext context, ILogger fwdataLogger, IMemoryCache cache, ILogger logger, @@ -19,14 +18,13 @@ public class FwDataFactory( MiniLcmValidators validators) : IDisposable { private bool _shuttingDown = false; - public FwDataFactory(FwDataProjectContext context, - ILogger fwdataLogger, + public FwDataFactory(ILogger fwdataLogger, IMemoryCache cache, ILogger logger, IProjectLoader projectLoader, IHostApplicationLifetime lifetime, FieldWorksProjectList fieldWorksProjectList, - MiniLcmValidators validators) : this(context, fwdataLogger, cache, logger, projectLoader, fieldWorksProjectList, validators) + MiniLcmValidators validators) : this(fwdataLogger, cache, logger, projectLoader, fieldWorksProjectList, validators) { lifetime.ApplicationStopping.Register(() => { @@ -107,23 +105,6 @@ public void Dispose() } } - public FwDataMiniLcmApi GetCurrentFwDataMiniLcmApi(bool saveOnDispose) - { - var fwDataProject = context.Project; - if (fwDataProject is null) - { - throw new InvalidOperationException("No project is set in the context."); - } - return GetFwDataMiniLcmApi(fwDataProject, true); - } - - public void CloseCurrentProject() - { - var fwDataProject = context.Project; - if (fwDataProject is null) return; - CloseProject(fwDataProject); - } - public void CloseProject(FwDataProject project) { // if we are shutting down, don't do anything because we want project dispose to be called as part of the shutdown process. diff --git a/backend/FwLite/FwDataMiniLcmBridge/FwDataProjectContext.cs b/backend/FwLite/FwDataMiniLcmBridge/FwDataProjectContext.cs index b40d03407..86e449494 100644 --- a/backend/FwLite/FwDataMiniLcmBridge/FwDataProjectContext.cs +++ b/backend/FwLite/FwDataMiniLcmBridge/FwDataProjectContext.cs @@ -2,31 +2,5 @@ public class FwDataProjectContext { - private sealed class ProjectHolder - { - public FwDataProject? Project; - } - - private static readonly AsyncLocal _projectHolder = new(); - - public virtual FwDataProject? Project - { - get => _projectHolder.Value?.Project; - set - { - var holder = _projectHolder.Value; - if (holder != null) - { - // Clear current Project trapped in the AsyncLocals, as its done. - holder.Project = null; - } - - if (value is not null) - { - // Use an object indirection to hold the Project in the AsyncLocal, - // so it can be cleared in all ExecutionContexts when its cleared above. - _projectHolder.Value = new ProjectHolder { Project = value }; - } - } - } + public FwDataProject? Project { get; set; } } diff --git a/backend/FwLite/LocalWebApp/Hubs/FwDataMiniLcmHub.cs b/backend/FwLite/LocalWebApp/Hubs/FwDataMiniLcmHub.cs index 61ddd1620..92a56d6c0 100644 --- a/backend/FwLite/LocalWebApp/Hubs/FwDataMiniLcmHub.cs +++ b/backend/FwLite/LocalWebApp/Hubs/FwDataMiniLcmHub.cs @@ -24,12 +24,12 @@ public override async Task OnConnectedAsync() public override async Task OnDisconnectedAsync(Exception? exception) { //todo if multiple clients are connected, this will close the project for all of them. - fwDataFactory.CloseCurrentProject(); var project = context.Project; if (project is null) { throw new InvalidOperationException("No project is set in the context."); } + fwDataFactory.CloseProject(project); if (exception is LcmFileLockedException) { diff --git a/backend/FwLite/LocalWebApp/Routes/FwIntegrationRoutes.cs b/backend/FwLite/LocalWebApp/Routes/FwIntegrationRoutes.cs index 22270afc9..017797744 100644 --- a/backend/FwLite/LocalWebApp/Routes/FwIntegrationRoutes.cs +++ b/backend/FwLite/LocalWebApp/Routes/FwIntegrationRoutes.cs @@ -23,7 +23,7 @@ public static IEndpointConventionBuilder MapFwIntegrationRoutes(this WebApplicat { if (context.Project is null) return Results.BadRequest("No project is set in the context"); await hubContext.Clients.Group(context.Project.Name).OnProjectClosed(CloseReason.Locked); - factory.CloseCurrentProject(); + factory.CloseProject(context.Project); //need to use redirect as a way to not trigger flex until after we have closed the project return Results.Redirect($"silfw://localhost/link?database={context.Project.Name}&tool=lexiconEdit&guid={id}"); }); From 681ee30ba2dd273b9f6bd46a43d26ef83c249791 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Wed, 18 Dec 2024 13:49:30 +0700 Subject: [PATCH 062/108] make miniLcm disposable --- backend/FwLite/FwLiteProjectSync/DryRunMiniLcmApi.cs | 4 ++++ backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs | 3 +++ backend/FwLite/MiniLcm/IMiniLcmApi.cs | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/backend/FwLite/FwLiteProjectSync/DryRunMiniLcmApi.cs b/backend/FwLite/FwLiteProjectSync/DryRunMiniLcmApi.cs index 82d5190ca..5540a1faf 100644 --- a/backend/FwLite/FwLiteProjectSync/DryRunMiniLcmApi.cs +++ b/backend/FwLite/FwLiteProjectSync/DryRunMiniLcmApi.cs @@ -5,6 +5,10 @@ namespace FwLiteProjectSync; public class DryRunMiniLcmApi(IMiniLcmApi api) : IMiniLcmApi { + public void Dispose() + { + } + public List DryRunRecords { get; } = []; public record DryRunRecord(string Method, string Description); diff --git a/backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs b/backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs index 3955ab957..2262126b5 100644 --- a/backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs +++ b/backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs @@ -552,4 +552,7 @@ public async Task DeleteExampleSentence(Guid entryId, Guid senseId, Guid example await dataModel.AddChange(ClientId, new DeleteChange(exampleSentenceId)); } + public void Dispose() + { + } } diff --git a/backend/FwLite/MiniLcm/IMiniLcmApi.cs b/backend/FwLite/MiniLcm/IMiniLcmApi.cs index 6beb6db07..01849fbdd 100644 --- a/backend/FwLite/MiniLcm/IMiniLcmApi.cs +++ b/backend/FwLite/MiniLcm/IMiniLcmApi.cs @@ -1,3 +1,3 @@ namespace MiniLcm; -public interface IMiniLcmApi: IMiniLcmReadApi, IMiniLcmWriteApi; +public interface IMiniLcmApi: IMiniLcmReadApi, IMiniLcmWriteApi, IDisposable; From 522912e573b710e76d2f21485d13fe78e87b5521 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Wed, 18 Dec 2024 13:52:55 +0700 Subject: [PATCH 063/108] refactor FieldWorksProjectList.cs and CrdtProjectsService.cs into a common IProjectProvider.cs interface --- .../FieldWorksProjectList.cs | 12 ++++++++ .../FwDataMiniLcmBridge/FwDataBridgeKernel.cs | 2 ++ .../FwDataMiniLcmBridge/FwDataProject.cs | 2 +- .../Projects/CombinedProjectsService.cs | 30 +++++++++++-------- .../FwLiteShared/Services/FwLiteProvider.cs | 17 ++++++----- .../Services/MiniLcmJsInvokable.cs | 20 ++++++++----- .../Sync/BackgroundSyncService.cs | 8 ++++- backend/FwLite/LcmCrdt/CrdtProject.cs | 2 +- backend/FwLite/LcmCrdt/CrdtProjectsService.cs | 26 +++++++++++++--- backend/FwLite/LcmCrdt/LcmCrdtKernel.cs | 2 ++ .../LocalWebApp/Routes/ProjectRoutes.cs | 2 +- .../MiniLcm/Models/ProjectIdentifier.cs | 8 ++++- .../FwLite/MiniLcm/Project/IProjectImport.cs | 8 +++++ .../MiniLcm/Project/IProjectProvider.cs | 11 +++++++ 14 files changed, 114 insertions(+), 36 deletions(-) create mode 100644 backend/FwLite/MiniLcm/Project/IProjectImport.cs create mode 100644 backend/FwLite/MiniLcm/Project/IProjectProvider.cs diff --git a/backend/FwLite/FwDataMiniLcmBridge/FieldWorksProjectList.cs b/backend/FwLite/FwDataMiniLcmBridge/FieldWorksProjectList.cs index 734f19c9c..c169fff34 100644 --- a/backend/FwLite/FwDataMiniLcmBridge/FieldWorksProjectList.cs +++ b/backend/FwLite/FwDataMiniLcmBridge/FieldWorksProjectList.cs @@ -1,12 +1,24 @@ using FwDataMiniLcmBridge.LcmUtils; using Microsoft.Extensions.Options; +using MiniLcm; using MiniLcm.Models; +using MiniLcm.Project; namespace FwDataMiniLcmBridge; public class FieldWorksProjectList(IOptions config, FwDataFactory fwDataFactory) : IProjectProvider { + public ProjectDataFormat DataFormat => ProjectDataFormat.FwData; protected readonly IOptions _config = config; + IEnumerable IProjectProvider.ListProjects() + { + return EnumerateProjects(); + } + + IProjectIdentifier? IProjectProvider.GetProject(string name) + { + return GetProject(name); + } public virtual IEnumerable EnumerateProjects() { diff --git a/backend/FwLite/FwDataMiniLcmBridge/FwDataBridgeKernel.cs b/backend/FwLite/FwDataMiniLcmBridge/FwDataBridgeKernel.cs index ba16aecf9..cbeceb503 100644 --- a/backend/FwLite/FwDataMiniLcmBridge/FwDataBridgeKernel.cs +++ b/backend/FwLite/FwDataMiniLcmBridge/FwDataBridgeKernel.cs @@ -1,6 +1,7 @@ using FwDataMiniLcmBridge.LcmUtils; using Microsoft.Extensions.DependencyInjection; using MiniLcm; +using MiniLcm.Project; using MiniLcm.Validators; namespace FwDataMiniLcmBridge; @@ -15,6 +16,7 @@ public static IServiceCollection AddFwDataBridge(this IServiceCollection service services.AddOptions().BindConfiguration("FwDataBridge"); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(s => s.GetRequiredService()); services.AddSingleton(); services.AddKeyedScoped(FwDataApiKey, (provider, o) => diff --git a/backend/FwLite/FwDataMiniLcmBridge/FwDataProject.cs b/backend/FwLite/FwDataMiniLcmBridge/FwDataProject.cs index f02321cfb..c00a99315 100644 --- a/backend/FwLite/FwDataMiniLcmBridge/FwDataProject.cs +++ b/backend/FwLite/FwDataMiniLcmBridge/FwDataProject.cs @@ -20,5 +20,5 @@ public class FwDataProject(string name, string projectsPath) : IProjectIdentifie /// eg: Project name : "MyProject", path: "/data/projects/" is the value of this property, then the fwdata file must be in /data/projects/MyProject/MyProject.fwdata /// public string ProjectsPath { get; } = projectsPath; - public string Origin { get; } = "FieldWorks"; + public ProjectDataFormat DataFormat => ProjectDataFormat.FwData; } diff --git a/backend/FwLite/FwLiteShared/Projects/CombinedProjectsService.cs b/backend/FwLite/FwLiteShared/Projects/CombinedProjectsService.cs index 2ee1b60ef..bbe626d57 100644 --- a/backend/FwLite/FwLiteShared/Projects/CombinedProjectsService.cs +++ b/backend/FwLite/FwLiteShared/Projects/CombinedProjectsService.cs @@ -1,9 +1,10 @@ -using FwDataMiniLcmBridge; -using FwLiteShared.Auth; +using FwLiteShared.Auth; using FwLiteShared.Sync; using LcmCrdt; using Microsoft.Extensions.DependencyInjection; using Microsoft.JSInterop; +using MiniLcm.Models; +using MiniLcm.Project; namespace FwLiteShared.Projects; @@ -17,8 +18,9 @@ public record ProjectModel( public record ServerProjects(LexboxServer Server, ProjectModel[] Projects); public class CombinedProjectsService(LexboxProjectService lexboxProjectService, CrdtProjectsService crdtProjectsService, - FieldWorksProjectList fieldWorksProjectList) + IEnumerable projectProviders) { + private IProjectProvider? FwDataProjectProvider => projectProviders.FirstOrDefault(p => p.DataFormat == ProjectDataFormat.FwData); [JSInvokable] public async Task RemoteProjects() { @@ -42,9 +44,9 @@ public async Task RemoteProjects() } [JSInvokable] - public async Task> LocalProjects() + public IReadOnlyCollection LocalProjects() { - var crdtProjects = await crdtProjectsService.ListProjects(); + var crdtProjects = crdtProjectsService.ListProjects(); //todo get project Id and use that to specify the Id in the model. Also pull out server var projects = crdtProjects.ToDictionary(p => p.Name, p => @@ -58,18 +60,22 @@ public async Task> LocalProjects() p.Data?.Id); }); //basically populate projects and indicate if they are lexbox or fwdata - foreach (var p in fieldWorksProjectList.EnumerateProjects()) + if (FwDataProjectProvider is not null) { - if (projects.TryGetValue(p.Name, out var project)) + foreach (var p in FwDataProjectProvider.ListProjects()) { - projects[p.Name] = project with { Fwdata = true }; - } - else - { - projects.Add(p.Name, new ProjectModel(p.Name, false, true)); + if (projects.TryGetValue(p.Name, out var project)) + { + projects[p.Name] = project with { Fwdata = true }; + } + else + { + projects.Add(p.Name, new ProjectModel(p.Name, false, true)); + } } } + return projects.Values; } diff --git a/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs b/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs index cdebe7366..05c564546 100644 --- a/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs +++ b/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs @@ -1,12 +1,12 @@ using System.Text.Json.Serialization; -using FwDataMiniLcmBridge; using FwLiteShared.Auth; using FwLiteShared.Projects; using LcmCrdt; using LexCore.Utils; using Microsoft.Extensions.DependencyInjection; using Microsoft.JSInterop; -using MiniLcm; +using MiniLcm.Models; +using MiniLcm.Project; namespace FwLiteShared.Services; @@ -15,15 +15,17 @@ public class FwLiteProvider( AuthService authService, ImportFwdataService importFwdataService, CrdtProjectsService crdtProjectsService, - FwDataProjectContext fwDataProjectContext, - FieldWorksProjectList fieldWorksProjectList, LexboxProjectService lexboxProjectService, - ChangeEventBus changeEventBus + ChangeEventBus changeEventBus, + IEnumerable projectProviders ) : IDisposable { public const string OverrideServiceFunctionName = "setOverrideService"; private readonly List _disposables = []; + private IProjectProvider? FwDataProjectProvider => + projectProviders.FirstOrDefault(p => p.DataFormat == ProjectDataFormat.FwData); + public void Dispose() { foreach (var disposable in _disposables) @@ -83,9 +85,10 @@ public async Task InjectCrdtProject(IJSRuntime jsRuntime, public async Task InjectFwDataProject(IJSRuntime jsRuntime, IServiceProvider scopedServices, string projectName) { - fwDataProjectContext.Project = fieldWorksProjectList.GetProject(projectName); + if (FwDataProjectProvider is null) throw new InvalidOperationException("FwData Project provider is not available"); + var project = FwDataProjectProvider.GetProject(projectName) ?? throw new InvalidOperationException($"FwData Project {projectName} not found"); var service = ActivatorUtilities.CreateInstance(scopedServices, - scopedServices.GetRequiredKeyedService(FwDataBridgeKernel.FwDataApiKey)); + FwDataProjectProvider.OpenProject(project), project); await SetService(jsRuntime, DotnetService.MiniLcmApi, service); return Defer.Async(async () => await SetService(jsRuntime, DotnetService.MiniLcmApi, null)); } diff --git a/backend/FwLite/FwLiteShared/Services/MiniLcmJsInvokable.cs b/backend/FwLite/FwLiteShared/Services/MiniLcmJsInvokable.cs index 4ab17f984..7a8038f8b 100644 --- a/backend/FwLite/FwLiteShared/Services/MiniLcmJsInvokable.cs +++ b/backend/FwLite/FwLiteShared/Services/MiniLcmJsInvokable.cs @@ -1,5 +1,4 @@ -using FwDataMiniLcmBridge.Api; -using FwLiteShared.Sync; +using FwLiteShared.Sync; using LcmCrdt; using Microsoft.JSInterop; using MiniLcm; @@ -10,24 +9,24 @@ namespace FwLiteShared.Services; internal class MiniLcmJsInvokable( IMiniLcmApi api, BackgroundSyncService backgroundSyncService, - CrdtProject? crdtProject = null) + IProjectIdentifier project) : IDisposable { public record MiniLcmFeatures(bool History, bool Write, bool OpenWithFlex, bool Feedback, bool Sync); - private bool SupportsSync => api is CrdtMiniLcmApi; + private bool SupportsSync => project.DataFormat == ProjectDataFormat.Harmony && api is CrdtMiniLcmApi; [JSInvokable] public MiniLcmFeatures SupportedFeatures() { - var isCrdtProject = api is CrdtMiniLcmApi; - var isFwDataProject = api is FwDataMiniLcmApi; + var isCrdtProject = project.DataFormat == ProjectDataFormat.Harmony; + var isFwDataProject = project.DataFormat == ProjectDataFormat.FwData; return new(History: isCrdtProject, Write: true, OpenWithFlex: isFwDataProject, Feedback: true, Sync: SupportsSync); } private void TriggerSync() { - if (SupportsSync && crdtProject is not null) + if (SupportsSync) { - backgroundSyncService.TriggerSync(crdtProject); + backgroundSyncService.TriggerSync(project); } } @@ -261,4 +260,9 @@ public Task DeleteExampleSentence(Guid entryId, Guid senseId, Guid exampleSenten { return api.DeleteExampleSentence(entryId, senseId, exampleSentenceId); } + + public void Dispose() + { + api.Dispose(); + } } diff --git a/backend/FwLite/FwLiteShared/Sync/BackgroundSyncService.cs b/backend/FwLite/FwLiteShared/Sync/BackgroundSyncService.cs index ff09e1a03..b64378072 100644 --- a/backend/FwLite/FwLiteShared/Sync/BackgroundSyncService.cs +++ b/backend/FwLite/FwLiteShared/Sync/BackgroundSyncService.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using MiniLcm.Models; using SIL.Harmony; namespace FwLiteShared.Sync; @@ -42,6 +43,11 @@ public void TriggerSync(Guid projectId, Guid? ignoredClientId = null) TriggerSync(crdtProject); } + public void TriggerSync(IProjectIdentifier project) + { + if (project.DataFormat == ProjectDataFormat.FwData) throw new NotSupportedException("Background sync service does not support fwdata projects"); + TriggerSync((CrdtProject)project); + } public void TriggerSync(CrdtProject crdtProject) { if (!_running) throw new InvalidOperationException("Background sync service is not running"); @@ -61,7 +67,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) //need to wait until application is started, otherwise Server urls will be unknown which prevents creating downstream services await StartedAsync(); _running = true; - var crdtProjects = await crdtProjectsService.ListProjects(); + var crdtProjects = crdtProjectsService.ListProjects(); foreach (var crdtProject in crdtProjects) { await SyncProject(crdtProject); diff --git a/backend/FwLite/LcmCrdt/CrdtProject.cs b/backend/FwLite/LcmCrdt/CrdtProject.cs index 1dce69d9b..14c0a051e 100644 --- a/backend/FwLite/LcmCrdt/CrdtProject.cs +++ b/backend/FwLite/LcmCrdt/CrdtProject.cs @@ -3,7 +3,7 @@ public class CrdtProject(string name, string dbPath) : IProjectIdentifier { public string Name { get; } = name; - public string Origin { get; } = "CRDT"; + public ProjectDataFormat DataFormat => ProjectDataFormat.Harmony; public string DbPath { get; } = dbPath; public ProjectData? Data { get; set; } } diff --git a/backend/FwLite/LcmCrdt/CrdtProjectsService.cs b/backend/FwLite/LcmCrdt/CrdtProjectsService.cs index 44b6e94da..abf23c424 100644 --- a/backend/FwLite/LcmCrdt/CrdtProjectsService.cs +++ b/backend/FwLite/LcmCrdt/CrdtProjectsService.cs @@ -5,21 +5,39 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using LcmCrdt.Objects; +using MiniLcm.Project; namespace LcmCrdt; -public partial class CrdtProjectsService(IServiceProvider provider, ILogger logger, IOptions config, IMemoryCache memoryCache) +public partial class CrdtProjectsService(IServiceProvider provider, ILogger logger, IOptions config, IMemoryCache memoryCache): IProjectProvider { - public Task ListProjects() + public ProjectDataFormat DataFormat { get; } = ProjectDataFormat.Harmony; + IEnumerable IProjectProvider.ListProjects() { - return Task.FromResult(Directory.EnumerateFiles(config.Value.ProjectPath, "*.sqlite").Select(file => + return ListProjects(); + } + + IProjectIdentifier? IProjectProvider.GetProject(string name) + { + return GetProject(name); + } + + IMiniLcmApi IProjectProvider.OpenProject(IProjectIdentifier project, bool saveChangesOnDispose = true) + { + //todo not sure if we should implement this, it's mostly there for the FwData version + throw new NotImplementedException(); + } + + public IEnumerable ListProjects() + { + return Directory.EnumerateFiles(config.Value.ProjectPath, "*.sqlite").Select(file => { var name = Path.GetFileNameWithoutExtension(file); return new CrdtProject(name, file) { Data = CurrentProjectService.LookupProjectData(memoryCache, name) }; - }).ToArray()); + }); } public CrdtProject? GetProject(string name) diff --git a/backend/FwLite/LcmCrdt/LcmCrdtKernel.cs b/backend/FwLite/LcmCrdt/LcmCrdtKernel.cs index e8a35750f..a5f7f0417 100644 --- a/backend/FwLite/LcmCrdt/LcmCrdtKernel.cs +++ b/backend/FwLite/LcmCrdt/LcmCrdtKernel.cs @@ -19,6 +19,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using MiniLcm.Project; using MiniLcm.Validators; using Refit; using SIL.Harmony.Db; @@ -43,6 +44,7 @@ public static IServiceCollection AddLcmCrdtClient(this IServiceCollection servic services.AddScoped(); services.AddScoped(); services.AddSingleton(); + services.AddSingleton(s => s.GetRequiredService()); services.AddHttpClient(); services.AddSingleton(provider => new RefitSettings diff --git a/backend/FwLite/LocalWebApp/Routes/ProjectRoutes.cs b/backend/FwLite/LocalWebApp/Routes/ProjectRoutes.cs index dc29e4463..58734ef15 100644 --- a/backend/FwLite/LocalWebApp/Routes/ProjectRoutes.cs +++ b/backend/FwLite/LocalWebApp/Routes/ProjectRoutes.cs @@ -25,7 +25,7 @@ public static IEndpointConventionBuilder MapProjectRoutes(this WebApplication ap return (await combinedProjectsService.RemoteProjects()).ToDictionary(p => p.Server.Authority.Authority, p => p.Projects); }); group.MapGet("/localProjects", - async (CombinedProjectsService combinedProjectsService) => await combinedProjectsService.LocalProjects()); + (CombinedProjectsService combinedProjectsService) => combinedProjectsService.LocalProjects()); group.MapPost("/project", async (CrdtProjectsService projectService, string name) => { diff --git a/backend/FwLite/MiniLcm/Models/ProjectIdentifier.cs b/backend/FwLite/MiniLcm/Models/ProjectIdentifier.cs index 624389bb3..efcc89d2b 100644 --- a/backend/FwLite/MiniLcm/Models/ProjectIdentifier.cs +++ b/backend/FwLite/MiniLcm/Models/ProjectIdentifier.cs @@ -3,5 +3,11 @@ public interface IProjectIdentifier { string Name { get; } - string Origin { get; } + ProjectDataFormat DataFormat { get; } +} + +public enum ProjectDataFormat +{ + Harmony, + FwData } diff --git a/backend/FwLite/MiniLcm/Project/IProjectImport.cs b/backend/FwLite/MiniLcm/Project/IProjectImport.cs new file mode 100644 index 000000000..edc316888 --- /dev/null +++ b/backend/FwLite/MiniLcm/Project/IProjectImport.cs @@ -0,0 +1,8 @@ +using MiniLcm.Models; + +namespace MiniLcm.Project; + +public interface IProjectImport +{ + Task Import(IProjectIdentifier project); +} diff --git a/backend/FwLite/MiniLcm/Project/IProjectProvider.cs b/backend/FwLite/MiniLcm/Project/IProjectProvider.cs new file mode 100644 index 000000000..ee606544f --- /dev/null +++ b/backend/FwLite/MiniLcm/Project/IProjectProvider.cs @@ -0,0 +1,11 @@ +using MiniLcm.Models; + +namespace MiniLcm.Project; + +public interface IProjectProvider +{ + ProjectDataFormat DataFormat { get; } + IEnumerable ListProjects(); + IProjectIdentifier? GetProject(string name); + IMiniLcmApi OpenProject(IProjectIdentifier project, bool saveChangesOnDispose = true); +} From f1999b8537941d02be452be9b48df68cd2635f30 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Wed, 18 Dec 2024 13:53:44 +0700 Subject: [PATCH 064/108] refactor project import to make it optional. if import is called at runtime, it will throw --- .../FwLiteProjectSyncKernel.cs | 2 + .../FwLite/FwLiteProjectSync/MiniLcmImport.cs | 41 +++++++++++++- .../Projects/ImportFwdataService.cs | 55 ++++++------------- 3 files changed, 57 insertions(+), 41 deletions(-) diff --git a/backend/FwLite/FwLiteProjectSync/FwLiteProjectSyncKernel.cs b/backend/FwLite/FwLiteProjectSync/FwLiteProjectSyncKernel.cs index b4b69ffba..1529de90e 100644 --- a/backend/FwLite/FwLiteProjectSync/FwLiteProjectSyncKernel.cs +++ b/backend/FwLite/FwLiteProjectSync/FwLiteProjectSyncKernel.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.DependencyInjection; +using MiniLcm.Project; namespace FwLiteProjectSync; @@ -8,6 +9,7 @@ public static IServiceCollection AddFwLiteProjectSync(this IServiceCollection se { services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(s => s.GetRequiredService()); return services; } } diff --git a/backend/FwLite/FwLiteProjectSync/MiniLcmImport.cs b/backend/FwLite/FwLiteProjectSync/MiniLcmImport.cs index 45bb17968..58c7f98b1 100644 --- a/backend/FwLite/FwLiteProjectSync/MiniLcmImport.cs +++ b/backend/FwLite/FwLiteProjectSync/MiniLcmImport.cs @@ -1,12 +1,49 @@ -using LcmCrdt; +using System.Diagnostics; +using FwDataMiniLcmBridge; +using Humanizer; +using LcmCrdt; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using MiniLcm; using MiniLcm.Models; +using MiniLcm.Project; namespace FwLiteProjectSync; -public class MiniLcmImport(ILogger logger) +public class MiniLcmImport( + ILogger logger, + FwDataFactory fwDataFactory, + CrdtProjectsService crdtProjectsService + ) : IProjectImport { + public async Task Import(IProjectIdentifier project) + { + if (project is not FwDataProject fwDataProject) throw new ArgumentException("Project is not a fwdata project"); + var startTime = Stopwatch.GetTimestamp(); + try + { + using var fwDataApi = fwDataFactory.GetFwDataMiniLcmApi(fwDataProject, false); + var harmonyProject = await crdtProjectsService.CreateProject(new(fwDataProject.Name, + SeedNewProjectData: false, + FwProjectId: fwDataApi.ProjectId, + AfterCreate: async (provider, _) => + { + var crdtApi = provider.GetRequiredService(); + await ImportProject(crdtApi, fwDataApi, fwDataApi.EntryCount); + })); + var timeSpent = Stopwatch.GetElapsedTime(startTime); + logger.LogInformation("Import of {ProjectName} complete, took {TimeSpend}", + fwDataProject.Name, + timeSpent.Humanize(2)); + return harmonyProject; + } + catch + { + logger.LogError("Import of {ProjectName} failed, deleting project", fwDataProject.Name); + throw; + } + } + public async Task ImportProject(IMiniLcmApi importTo, IMiniLcmApi importFrom, int entryCount) { await ImportWritingSystems(importTo, importFrom); diff --git a/backend/FwLite/FwLiteShared/Projects/ImportFwdataService.cs b/backend/FwLite/FwLiteShared/Projects/ImportFwdataService.cs index a75c0e5a9..4292830d9 100644 --- a/backend/FwLite/FwLiteShared/Projects/ImportFwdataService.cs +++ b/backend/FwLite/FwLiteShared/Projects/ImportFwdataService.cs @@ -1,52 +1,29 @@ -using System.Diagnostics; -using FwDataMiniLcmBridge; -using FwLiteProjectSync; -using Humanizer; -using LcmCrdt; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; using Microsoft.JSInterop; -using MiniLcm; +using MiniLcm.Models; +using MiniLcm.Project; namespace FwLiteShared.Projects; -public class ImportFwdataService( - CrdtProjectsService crdtProjectsService, - ILogger logger, - FwDataFactory fwDataFactory, - FieldWorksProjectList fieldWorksProjectList, - MiniLcmImport miniLcmImport -) +public class ImportFwdataService(IEnumerable projectProviders, IProjectImport? miniLcmImport = null) { + private IProjectProvider? FwDataProjectProvider => + projectProviders.FirstOrDefault(p => p.DataFormat == ProjectDataFormat.FwData); + [JSInvokable] - public async Task Import(string projectName) + public async Task Import(string projectName) { - var startTime = Stopwatch.GetTimestamp(); - var fwDataProject = fieldWorksProjectList.GetProject(projectName); - if (fwDataProject is null) - { - throw new InvalidOperationException($"Project {projectName} not found."); - } - try + if (miniLcmImport is null) throw new InvalidOperationException("MiniLcmImport is not available and import is not supported in this version"); + if (FwDataProjectProvider is null) { - using var fwDataApi = fwDataFactory.GetFwDataMiniLcmApi(fwDataProject, false); - var project = await crdtProjectsService.CreateProject(new(fwDataProject.Name, - SeedNewProjectData: false, - FwProjectId: fwDataApi.ProjectId, - AfterCreate: async (provider, project) => - { - var crdtApi = provider.GetRequiredService(); - await miniLcmImport.ImportProject(crdtApi, fwDataApi, fwDataApi.EntryCount); - })); - var timeSpent = Stopwatch.GetElapsedTime(startTime); - logger.LogInformation("Import of {ProjectName} complete, took {TimeSpend}", fwDataProject.Name, timeSpent.Humanize(2)); - return project; + throw new InvalidOperationException("FwData Project provider is not available"); } - catch + + var fwDataProject = FwDataProjectProvider.GetProject(projectName); + if (fwDataProject is null) { - logger.LogError("Import of {ProjectName} failed, deleting project", fwDataProject.Name); - throw; + throw new InvalidOperationException($"Project {projectName} not found."); } - } + return await miniLcmImport.Import(fwDataProject); + } } From 4001121fac46f14dac0450ac627931ee813f1cfb Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Wed, 18 Dec 2024 13:54:17 +0700 Subject: [PATCH 065/108] refactor FwShared to not depend on FwDataBridge, put that dependency into FwLiteDesktop windows builds --- backend/FwLite/FwLiteDesktop/FwLiteDesktop.csproj | 4 ++++ backend/FwLite/FwLiteDesktop/FwLiteDesktopKernel.cs | 5 +++-- backend/FwLite/FwLiteShared/FwLiteShared.csproj | 3 +-- backend/FwLite/FwLiteShared/FwLiteSharedKernel.cs | 6 +----- .../FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs | 1 - 5 files changed, 9 insertions(+), 10 deletions(-) diff --git a/backend/FwLite/FwLiteDesktop/FwLiteDesktop.csproj b/backend/FwLite/FwLiteDesktop/FwLiteDesktop.csproj index ae36fd853..bfe5791f6 100644 --- a/backend/FwLite/FwLiteDesktop/FwLiteDesktop.csproj +++ b/backend/FwLite/FwLiteDesktop/FwLiteDesktop.csproj @@ -89,5 +89,9 @@ + + + + diff --git a/backend/FwLite/FwLiteDesktop/FwLiteDesktopKernel.cs b/backend/FwLite/FwLiteDesktop/FwLiteDesktopKernel.cs index 5edefa47e..08b77d221 100644 --- a/backend/FwLite/FwLiteDesktop/FwLiteDesktopKernel.cs +++ b/backend/FwLite/FwLiteDesktop/FwLiteDesktopKernel.cs @@ -1,4 +1,3 @@ -using System.Runtime.InteropServices; using FwLiteShared; using FwLiteShared.Auth; using LcmCrdt; @@ -6,7 +5,6 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting.Internal; using Microsoft.Extensions.Logging; -using NReco.Logging.File; namespace FwLiteDesktop; @@ -38,6 +36,9 @@ public static void AddFwLiteDesktopServices(this IServiceCollection services, services.AddSingleton(); #if WINDOWS + //need to call them like this since they're only available in the windows project + FwDataMiniLcmBridge.FwDataBridgeKernel.AddFwDataBridge(services); + FwLiteProjectSync.FwLiteProjectSyncKernel.AddFwLiteProjectSync(services); services.AddFwLiteWindows(); #endif #if ANDROID diff --git a/backend/FwLite/FwLiteShared/FwLiteShared.csproj b/backend/FwLite/FwLiteShared/FwLiteShared.csproj index 2eac6352d..b13f3291d 100644 --- a/backend/FwLite/FwLiteShared/FwLiteShared.csproj +++ b/backend/FwLite/FwLiteShared/FwLiteShared.csproj @@ -20,8 +20,7 @@ - - + diff --git a/backend/FwLite/FwLiteShared/FwLiteSharedKernel.cs b/backend/FwLite/FwLiteShared/FwLiteSharedKernel.cs index bc6d75159..ef7fe8eec 100644 --- a/backend/FwLite/FwLiteShared/FwLiteSharedKernel.cs +++ b/backend/FwLite/FwLiteShared/FwLiteSharedKernel.cs @@ -1,6 +1,4 @@ -using FwDataMiniLcmBridge; -using FwLiteProjectSync; -using FwLiteShared.Auth; +using FwLiteShared.Auth; using FwLiteShared.Projects; using FwLiteShared.Services; using FwLiteShared.Sync; @@ -17,8 +15,6 @@ public static IServiceCollection AddFwLiteShared(this IServiceCollection service services.AddHttpClient(); services.AddAuthHelpers(environment); services.AddLcmCrdtClient(); - services.AddFwDataBridge(); - services.AddFwLiteProjectSync(); services.AddSingleton(); services.AddScoped(); diff --git a/backend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs b/backend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs index 93406870e..5cb51dcf3 100644 --- a/backend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs +++ b/backend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs @@ -10,7 +10,6 @@ using Reinforced.Typings.Ast.TypeNames; using Reinforced.Typings.Fluent; using Reinforced.Typings.Visitors.TypeScript; -using StructureMap.TypeRules; namespace FwLiteShared.TypeGen; From 46abe3354a4c8e78478742659f29fb0ca6a67ea7 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Wed, 18 Dec 2024 14:02:38 +0700 Subject: [PATCH 066/108] fix circular dependency between FieldWorksProjectList and FwDataFactory --- .../Fixtures/ProjectLoaderFixture.cs | 10 +++------- backend/FwLite/FwDataMiniLcmBridge/FwDataFactory.cs | 10 +--------- backend/FwLite/FwLiteDesktop/FwLiteDesktop.csproj | 5 ++++- backend/FwLite/FwLiteDesktop/FwLiteDesktopKernel.cs | 7 ++++--- backend/FwLite/FwLiteProjectSync/Program.cs | 9 ++++++++- backend/LfNext/LcmDebugger/Program.cs | 4 +++- 6 files changed, 23 insertions(+), 22 deletions(-) diff --git a/backend/FwLite/FwDataMiniLcmBridge.Tests/Fixtures/ProjectLoaderFixture.cs b/backend/FwLite/FwDataMiniLcmBridge.Tests/Fixtures/ProjectLoaderFixture.cs index 2ea91318d..a9de985c5 100644 --- a/backend/FwLite/FwDataMiniLcmBridge.Tests/Fixtures/ProjectLoaderFixture.cs +++ b/backend/FwLite/FwDataMiniLcmBridge.Tests/Fixtures/ProjectLoaderFixture.cs @@ -22,16 +22,12 @@ public ProjectLoaderFixture() _config = provider.GetRequiredService>(); } - public FwDataMiniLcmApi CreateApi(string projectName) - { - return _fwDataFactory.GetFwDataMiniLcmApi(projectName, false); - } - public FwDataMiniLcmApi NewProjectApi(string projectName, string analysisWs, string vernacularWs) { projectName = $"{projectName}_{Guid.NewGuid()}"; - MockFwProjectLoader.NewProject(new FwDataProject(projectName, _config.Value.ProjectsFolder), analysisWs, vernacularWs); - return CreateApi(projectName); + var fwDataProject = new FwDataProject(projectName, _config.Value.ProjectsFolder); + MockFwProjectLoader.NewProject(fwDataProject, analysisWs, vernacularWs); + return _fwDataFactory.GetFwDataMiniLcmApi(fwDataProject, false); } public void Dispose() diff --git a/backend/FwLite/FwDataMiniLcmBridge/FwDataFactory.cs b/backend/FwLite/FwDataMiniLcmBridge/FwDataFactory.cs index 658a85c18..64919b466 100644 --- a/backend/FwLite/FwDataMiniLcmBridge/FwDataFactory.cs +++ b/backend/FwLite/FwDataMiniLcmBridge/FwDataFactory.cs @@ -14,7 +14,6 @@ public class FwDataFactory( IMemoryCache cache, ILogger logger, IProjectLoader projectLoader, - FieldWorksProjectList fieldWorksProjectList, MiniLcmValidators validators) : IDisposable { private bool _shuttingDown = false; @@ -23,8 +22,7 @@ public FwDataFactory(ILogger fwdataLogger, ILogger logger, IProjectLoader projectLoader, IHostApplicationLifetime lifetime, - FieldWorksProjectList fieldWorksProjectList, - MiniLcmValidators validators) : this(fwdataLogger, cache, logger, projectLoader, fieldWorksProjectList, validators) + MiniLcmValidators validators) : this(fwdataLogger, cache, logger, projectLoader, validators) { lifetime.ApplicationStopping.Register(() => { @@ -34,12 +32,6 @@ public FwDataFactory(ILogger fwdataLogger, }); } - public FwDataMiniLcmApi GetFwDataMiniLcmApi(string projectName, bool saveOnDispose) - { - var project = fieldWorksProjectList.GetProject(projectName) ?? throw new InvalidOperationException($"FwData Project {projectName} not found."); - return GetFwDataMiniLcmApi(project, saveOnDispose); - } - private string CacheKey(FwDataProject project) => $"{nameof(FwDataFactory)}|{project.FilePath}"; public FwDataMiniLcmApi GetFwDataMiniLcmApi(FwDataProject project, bool saveOnDispose) diff --git a/backend/FwLite/FwLiteDesktop/FwLiteDesktop.csproj b/backend/FwLite/FwLiteDesktop/FwLiteDesktop.csproj index bfe5791f6..760c8556c 100644 --- a/backend/FwLite/FwLiteDesktop/FwLiteDesktop.csproj +++ b/backend/FwLite/FwLiteDesktop/FwLiteDesktop.csproj @@ -23,6 +23,9 @@ enable true true + false + true + $(DefineConstants);INCLUDE_FWDATA_BRIDGE FieldWorks Lite @@ -89,7 +92,7 @@ - + diff --git a/backend/FwLite/FwLiteDesktop/FwLiteDesktopKernel.cs b/backend/FwLite/FwLiteDesktop/FwLiteDesktopKernel.cs index 08b77d221..280209106 100644 --- a/backend/FwLite/FwLiteDesktop/FwLiteDesktopKernel.cs +++ b/backend/FwLite/FwLiteDesktop/FwLiteDesktopKernel.cs @@ -34,11 +34,12 @@ public static void AddFwLiteDesktopServices(this IServiceCollection services, services.AddFwLiteShared(env); services.AddMauiBlazorWebView(); services.AddSingleton(); - -#if WINDOWS - //need to call them like this since they're only available in the windows project +#if INCLUDE_FWDATA_BRIDGE + //need to call them like this otherwise we need a using statement at the top of the file FwDataMiniLcmBridge.FwDataBridgeKernel.AddFwDataBridge(services); FwLiteProjectSync.FwLiteProjectSyncKernel.AddFwLiteProjectSync(services); +#endif +#if WINDOWS services.AddFwLiteWindows(); #endif #if ANDROID diff --git a/backend/FwLite/FwLiteProjectSync/Program.cs b/backend/FwLite/FwLiteProjectSync/Program.cs index d587f5fb0..7d6fa3196 100644 --- a/backend/FwLite/FwLiteProjectSync/Program.cs +++ b/backend/FwLite/FwLiteProjectSync/Program.cs @@ -1,5 +1,6 @@ using System.CommandLine; using FwDataMiniLcmBridge; +using FwDataMiniLcmBridge.Api; using LcmCrdt; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -51,7 +52,13 @@ public static Task Main(string[] args) await using var scope = serviceRoot.CreateAsyncScope(); var services = scope.ServiceProvider; var logger = services.GetRequiredService>(); - var fwdataApi = services.GetRequiredService().GetFwDataMiniLcmApi(fwProjectName, true); + var fieldWorksProjectList = services.GetRequiredService(); + var fwProject = fieldWorksProjectList.GetProject(fwProjectName); + if (fwProject is null) + { + throw new InvalidOperationException("Could not find fwdata project " + fwProjectName); + } + var fwdataApi = (FwDataMiniLcmApi) fieldWorksProjectList.OpenProject(fwProject); var projectsService = services.GetRequiredService(); var crdtProject = projectsService.GetProject(crdtProjectName); if (crdtProject is null) diff --git a/backend/LfNext/LcmDebugger/Program.cs b/backend/LfNext/LcmDebugger/Program.cs index 5a2ea43ed..693f94ada 100644 --- a/backend/LfNext/LcmDebugger/Program.cs +++ b/backend/LfNext/LcmDebugger/Program.cs @@ -9,7 +9,9 @@ var app = builder.Build(); +var fieldWorksProjectList = app.Services.GetRequiredService(); +var fwDataProject = fieldWorksProjectList.GetProject("fruit") ?? throw new InvalidOperationException("Could not find project"); var fwDataFactory = app.Services.GetRequiredService(); -var miniLcmApi = fwDataFactory.GetFwDataMiniLcmApi("fruit", false); +var miniLcmApi = fwDataFactory.GetFwDataMiniLcmApi(fwDataProject, false); var entries = await miniLcmApi.GetEntries().ToArrayAsync(); var complexEntryTypesOa = miniLcmApi.Cache.LangProject.LexDbOA.ComplexEntryTypesOA; From 050768c9494c1464d6eb93346201112e98b5ccb7 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Wed, 18 Dec 2024 14:20:40 +0700 Subject: [PATCH 067/108] ensure lcm service is cleared when going home --- .../FwLiteShared/Pages/CrdtProject.razor | 1 + backend/FwLite/FwLiteShared/Pages/Home.razor | 10 ++++++ .../FwLiteShared/Services/FwLiteProvider.cs | 31 +++++++++++++------ 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/backend/FwLite/FwLiteShared/Pages/CrdtProject.razor b/backend/FwLite/FwLiteShared/Pages/CrdtProject.razor index 921c47617..2a775469e 100644 --- a/backend/FwLite/FwLiteShared/Pages/CrdtProject.razor +++ b/backend/FwLite/FwLiteShared/Pages/CrdtProject.razor @@ -21,6 +21,7 @@ protected override async ValueTask DisposeAsyncCore() { + //sadly this is not called when the page is left, not sure how we can fix that yet await base.DisposeAsyncCore(); if (_disposable is not null) await _disposable.DisposeAsync(); diff --git a/backend/FwLite/FwLiteShared/Pages/Home.razor b/backend/FwLite/FwLiteShared/Pages/Home.razor index 50056ef07..d1c1ecff4 100644 --- a/backend/FwLite/FwLiteShared/Pages/Home.razor +++ b/backend/FwLite/FwLiteShared/Pages/Home.razor @@ -1,5 +1,15 @@ @page "/" @using FwLiteShared.Layout +@using FwLiteShared.Services @layout SvelteLayout; +@inject FwLiteProvider FwLiteProvider; +@inject IJSRuntime JS; @*this looks empty because it is, but it's required to declare the route which is then used by the svelte router*@ +@code +{ + protected override async Task OnInitializedAsync() + { + await FwLiteProvider.SetService(JS, DotnetService.MiniLcmApi, null); + } +} diff --git a/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs b/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs index 05c564546..d7408d26b 100644 --- a/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs +++ b/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs @@ -1,9 +1,11 @@ -using System.Text.Json.Serialization; +using System.Diagnostics.CodeAnalysis; +using System.Text.Json.Serialization; using FwLiteShared.Auth; using FwLiteShared.Projects; using LcmCrdt; using LexCore.Utils; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Microsoft.JSInterop; using MiniLcm.Models; using MiniLcm.Project; @@ -17,7 +19,8 @@ public class FwLiteProvider( CrdtProjectsService crdtProjectsService, LexboxProjectService lexboxProjectService, ChangeEventBus changeEventBus, - IEnumerable projectProviders + IEnumerable projectProviders, + ILogger logger ) : IDisposable { public const string OverrideServiceFunctionName = "setOverrideService"; @@ -51,16 +54,20 @@ public object GetService(DotnetService service) }; } - public async Task SetService(IJSRuntime jsRuntime, DotnetService service, object? serviceInstance) + public async Task SetService(IJSRuntime jsRuntime, DotnetService service, object? serviceInstance) { DotNetObjectReference? reference = null; if (serviceInstance is not null) { reference = DotNetObjectReference.Create(serviceInstance); - _disposables.Add(reference); + } + else + { + logger.LogInformation("Clearing Service {Service}", service); } await jsRuntime.InvokeVoidAsync(OverrideServiceFunctionName, service.ToString(), reference); + return reference; } public async Task InjectCrdtProject(IJSRuntime jsRuntime, @@ -75,11 +82,12 @@ public async Task InjectCrdtProject(IJSRuntime jsRuntime, _ = jsRuntime.InvokeVoidAsync("notifyEntryUpdated", projectName, entry); }); var service = ActivatorUtilities.CreateInstance(scopedServices, project); - await SetService(jsRuntime,DotnetService.MiniLcmApi, service); - return Defer.Async(async () => + var reference = await SetService(jsRuntime,DotnetService.MiniLcmApi, service); + return Defer.Async(() => { + reference?.Dispose(); entryUpdatedSubscription.Dispose(); - await SetService(jsRuntime, DotnetService.MiniLcmApi, null); + return Task.CompletedTask; }); } @@ -89,8 +97,13 @@ public async Task InjectFwDataProject(IJSRuntime jsRuntime, IS var project = FwDataProjectProvider.GetProject(projectName) ?? throw new InvalidOperationException($"FwData Project {projectName} not found"); var service = ActivatorUtilities.CreateInstance(scopedServices, FwDataProjectProvider.OpenProject(project), project); - await SetService(jsRuntime, DotnetService.MiniLcmApi, service); - return Defer.Async(async () => await SetService(jsRuntime, DotnetService.MiniLcmApi, null)); + var reference = await SetService(jsRuntime, DotnetService.MiniLcmApi, service); + return Defer.Async(() => + { + reference?.Dispose(); + service.Dispose(); + return Task.CompletedTask; + }); } } From 32f7085d080d496cbb5ab7b28e854da0c823e8ed Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Wed, 18 Dec 2024 14:24:54 +0700 Subject: [PATCH 068/108] update typegen --- .../FwLiteShared/TypeGen/FwLiteTypeGenSpec.cs | 19 ------------------- .../TypeGen/ReinforcedFwLiteTypingConfig.cs | 3 +++ .../Projects/ICombinedProjectsService.ts | 2 +- .../Projects/IImportFwdataService.ts | 4 ++-- .../Services/IMiniLcmJsInvokable.ts | 1 + .../generated-types/LcmCrdt/ICrdtProject.ts | 10 +++++++--- .../generated-types/LcmCrdt/IProjectData.ts | 14 ++++++++++++++ .../MiniLcm/Models/IProjectIdentifier.ts | 13 +++++++++++++ .../MiniLcm/Models/ProjectDataFormat.ts | 10 ++++++++++ 9 files changed, 51 insertions(+), 25 deletions(-) delete mode 100644 backend/FwLite/FwLiteShared/TypeGen/FwLiteTypeGenSpec.cs create mode 100644 frontend/viewer/src/lib/dotnet-types/generated-types/LcmCrdt/IProjectData.ts create mode 100644 frontend/viewer/src/lib/dotnet-types/generated-types/MiniLcm/Models/IProjectIdentifier.ts create mode 100644 frontend/viewer/src/lib/dotnet-types/generated-types/MiniLcm/Models/ProjectDataFormat.ts diff --git a/backend/FwLite/FwLiteShared/TypeGen/FwLiteTypeGenSpec.cs b/backend/FwLite/FwLiteShared/TypeGen/FwLiteTypeGenSpec.cs deleted file mode 100644 index e7e02a342..000000000 --- a/backend/FwLite/FwLiteShared/TypeGen/FwLiteTypeGenSpec.cs +++ /dev/null @@ -1,19 +0,0 @@ -using FwLiteShared.Services; -using MiniLcm; -using MiniLcm.Models; - -namespace FwLiteShared.TypeGen; - -// public class FwLiteTypeGenSpec: GenerationSpec -// { -// public FwLiteTypeGenSpec() -// { -// AddClass(); -// AddInterface(); -// } -// -// public override void OnBeforeGeneration(OnBeforeGenerationArgs args) -// { -// args.GeneratorOptions.CustomTypeMappings.Add(typeof(WritingSystemId).FullName!, "string"); -// } -// } diff --git a/backend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs b/backend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs index 5cb51dcf3..f2ab99bde 100644 --- a/backend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs +++ b/backend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs @@ -91,6 +91,9 @@ public static void Configure(ConfigurationBuilder builder) builder.ExportAsInterface().WithPublicProperties(); builder.ExportAsInterface().WithPublicProperties(); builder.ExportAsInterface().WithPublicProperties(); + builder.ExportAsInterface().WithPublicProperties(); + builder.ExportAsInterface().WithPublicProperties(); + builder.ExportAsEnum(); } private static void DisableEsLintChecks(ConfigurationBuilder builder) diff --git a/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Projects/ICombinedProjectsService.ts b/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Projects/ICombinedProjectsService.ts index 63d1855ef..394a30ec6 100644 --- a/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Projects/ICombinedProjectsService.ts +++ b/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Projects/ICombinedProjectsService.ts @@ -10,7 +10,7 @@ import type {ILexboxServer} from '../Auth/ILexboxServer'; export interface ICombinedProjectsService { remoteProjects() : Promise; - localProjects() : Promise; + localProjects() : IProjectModel[]; downloadProject(lexboxProjectId: string, projectName: string, server: ILexboxServer) : Promise; createProject(name: string) : Promise; } diff --git a/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Projects/IImportFwdataService.ts b/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Projects/IImportFwdataService.ts index fd4818aa3..6af139aed 100644 --- a/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Projects/IImportFwdataService.ts +++ b/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Projects/IImportFwdataService.ts @@ -3,10 +3,10 @@ // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. -import type {ICrdtProject} from '../../LcmCrdt/ICrdtProject'; +import type {IProjectIdentifier} from '../../MiniLcm/Models/IProjectIdentifier'; export interface IImportFwdataService { - import(projectName: string) : Promise; + import(projectName: string) : Promise; } /* eslint-enable */ diff --git a/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/IMiniLcmJsInvokable.ts b/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/IMiniLcmJsInvokable.ts index 2933e57b9..3a80ba807 100644 --- a/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/IMiniLcmJsInvokable.ts +++ b/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/IMiniLcmJsInvokable.ts @@ -57,5 +57,6 @@ export interface IMiniLcmJsInvokable createExampleSentence(entryId: string, senseId: string, exampleSentence: IExampleSentence) : Promise; updateExampleSentence(entryId: string, senseId: string, before: IExampleSentence, after: IExampleSentence) : Promise; deleteExampleSentence(entryId: string, senseId: string, exampleSentenceId: string) : Promise; + dispose() : Promise; } /* eslint-enable */ diff --git a/frontend/viewer/src/lib/dotnet-types/generated-types/LcmCrdt/ICrdtProject.ts b/frontend/viewer/src/lib/dotnet-types/generated-types/LcmCrdt/ICrdtProject.ts index 165a1b1f1..872c80e55 100644 --- a/frontend/viewer/src/lib/dotnet-types/generated-types/LcmCrdt/ICrdtProject.ts +++ b/frontend/viewer/src/lib/dotnet-types/generated-types/LcmCrdt/ICrdtProject.ts @@ -3,11 +3,15 @@ // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. -export interface ICrdtProject +import type {IProjectIdentifier} from '../MiniLcm/Models/IProjectIdentifier'; +import type {ProjectDataFormat} from '../MiniLcm/Models/ProjectDataFormat'; +import type {IProjectData} from './IProjectData'; + +export interface ICrdtProject extends IProjectIdentifier { name: string; - origin: string; + dataFormat: ProjectDataFormat; dbPath: string; - data?: any; + data?: IProjectData; } /* eslint-enable */ diff --git a/frontend/viewer/src/lib/dotnet-types/generated-types/LcmCrdt/IProjectData.ts b/frontend/viewer/src/lib/dotnet-types/generated-types/LcmCrdt/IProjectData.ts new file mode 100644 index 000000000..034094462 --- /dev/null +++ b/frontend/viewer/src/lib/dotnet-types/generated-types/LcmCrdt/IProjectData.ts @@ -0,0 +1,14 @@ +/* eslint-disable */ +// This code was generated by a Reinforced.Typings tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. + +export interface IProjectData +{ + name: string; + id: string; + originDomain?: string; + clientId: string; + fwProjectId?: string; +} +/* eslint-enable */ diff --git a/frontend/viewer/src/lib/dotnet-types/generated-types/MiniLcm/Models/IProjectIdentifier.ts b/frontend/viewer/src/lib/dotnet-types/generated-types/MiniLcm/Models/IProjectIdentifier.ts new file mode 100644 index 000000000..640113a7a --- /dev/null +++ b/frontend/viewer/src/lib/dotnet-types/generated-types/MiniLcm/Models/IProjectIdentifier.ts @@ -0,0 +1,13 @@ +/* eslint-disable */ +// This code was generated by a Reinforced.Typings tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. + +import type {ProjectDataFormat} from './ProjectDataFormat'; + +export interface IProjectIdentifier +{ + name: string; + dataFormat: ProjectDataFormat; +} +/* eslint-enable */ diff --git a/frontend/viewer/src/lib/dotnet-types/generated-types/MiniLcm/Models/ProjectDataFormat.ts b/frontend/viewer/src/lib/dotnet-types/generated-types/MiniLcm/Models/ProjectDataFormat.ts new file mode 100644 index 000000000..6bdd9b261 --- /dev/null +++ b/frontend/viewer/src/lib/dotnet-types/generated-types/MiniLcm/Models/ProjectDataFormat.ts @@ -0,0 +1,10 @@ +/* eslint-disable */ +// This code was generated by a Reinforced.Typings tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. + +export enum ProjectDataFormat { + Harmony = 0, + FwData = 1 +} +/* eslint-enable */ From c845a13f6a44a44bf6cf9fd9a0b1fc7bda025450 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Wed, 18 Dec 2024 17:46:47 +0700 Subject: [PATCH 069/108] only enable fwdata bridge when building for windows --- backend/FwLite/FwLiteDesktop/FwLiteDesktop.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/FwLite/FwLiteDesktop/FwLiteDesktop.csproj b/backend/FwLite/FwLiteDesktop/FwLiteDesktop.csproj index 760c8556c..4b4797f5c 100644 --- a/backend/FwLite/FwLiteDesktop/FwLiteDesktop.csproj +++ b/backend/FwLite/FwLiteDesktop/FwLiteDesktop.csproj @@ -24,7 +24,7 @@ true true false - true + true $(DefineConstants);INCLUDE_FWDATA_BRIDGE From 3c3a487a3e24c4835f936cd56629b93d8b5a7a70 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Wed, 18 Dec 2024 17:59:12 +0700 Subject: [PATCH 070/108] use supportsFwData to determine if we show the fieldworks column --- .../FwLiteShared/Projects/CombinedProjectsService.cs | 2 ++ frontend/viewer/src/HomeView.svelte | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/backend/FwLite/FwLiteShared/Projects/CombinedProjectsService.cs b/backend/FwLite/FwLiteShared/Projects/CombinedProjectsService.cs index bbe626d57..556c97e9e 100644 --- a/backend/FwLite/FwLiteShared/Projects/CombinedProjectsService.cs +++ b/backend/FwLite/FwLiteShared/Projects/CombinedProjectsService.cs @@ -22,6 +22,8 @@ public class CombinedProjectsService(LexboxProjectService lexboxProjectService, { private IProjectProvider? FwDataProjectProvider => projectProviders.FirstOrDefault(p => p.DataFormat == ProjectDataFormat.FwData); [JSInvokable] + public bool SupportsFwData() => FwDataProjectProvider is not null; + [JSInvokable] public async Task RemoteProjects() { var lexboxServers = lexboxProjectService.Servers(); diff --git a/frontend/viewer/src/HomeView.svelte b/frontend/viewer/src/HomeView.svelte index d855efed3..7f2fffbc0 100644 --- a/frontend/viewer/src/HomeView.svelte +++ b/frontend/viewer/src/HomeView.svelte @@ -96,7 +96,11 @@ let serversStatus: IServerStatus[] = []; - onMount(async () => serversStatus = await authService.servers()); + onMount(async () => { + supportsFwData = await projectsService.supportsFwData(); + serversStatus = await authService.servers(); + }); + let supportsFwData = false; $: columns = [ { @@ -106,6 +110,7 @@ { name: 'fwdata', header: 'FieldWorks', + hidden: !supportsFwData, }, { name: 'crdt', From bf4b63a6397479b6038a7a69bd594b76d48d3df0 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Wed, 18 Dec 2024 18:01:06 +0700 Subject: [PATCH 071/108] introduce DurableInvoke helper which uses a method defined on window in index which will always be there, to call methods which might not always be there --- backend/FwLite/FwLiteDesktop/wwwroot/index.html | 9 ++++++--- backend/FwLite/FwLiteShared/JsInteropInvokeHelper.cs | 11 +++++++++++ backend/FwLite/FwLiteShared/Routes.razor | 2 +- .../FwLite/FwLiteShared/Services/FwLiteProvider.cs | 4 ++-- backend/FwLite/LocalWebApp/Components/App.razor | 10 ++++++++++ 5 files changed, 30 insertions(+), 6 deletions(-) create mode 100644 backend/FwLite/FwLiteShared/JsInteropInvokeHelper.cs diff --git a/backend/FwLite/FwLiteDesktop/wwwroot/index.html b/backend/FwLite/FwLiteDesktop/wwwroot/index.html index cd909c837..7822cd2e9 100644 --- a/backend/FwLite/FwLiteDesktop/wwwroot/index.html +++ b/backend/FwLite/FwLiteDesktop/wwwroot/index.html @@ -7,10 +7,13 @@ diff --git a/backend/FwLite/FwLiteShared/JsInteropInvokeHelper.cs b/backend/FwLite/FwLiteShared/JsInteropInvokeHelper.cs new file mode 100644 index 000000000..2380fd9b4 --- /dev/null +++ b/backend/FwLite/FwLiteShared/JsInteropInvokeHelper.cs @@ -0,0 +1,11 @@ +using Microsoft.JSInterop; + +namespace FwLiteShared; + +public static class JsInteropInvokeHelper +{ + public static async ValueTask DurableInvokeVoidAsync(this IJSRuntime jsRuntime, string identifier, params object?[] args) + { + await jsRuntime.InvokeVoidAsync("invokeOnWindow", identifier, args); + } +} diff --git a/backend/FwLite/FwLiteShared/Routes.razor b/backend/FwLite/FwLiteShared/Routes.razor index 2613ad901..00e759ac5 100644 --- a/backend/FwLite/FwLiteShared/Routes.razor +++ b/backend/FwLite/FwLiteShared/Routes.razor @@ -2,7 +2,7 @@ @code { private async Task OnNavigateAsync(NavigationContext context) { - await jsRuntime.InvokeVoidAsync("onBlazorNavigate", context.Path); + await jsRuntime.DurableInvokeVoidAsync("svelteNavigate", context.Path); } } diff --git a/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs b/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs index d7408d26b..cfd2ad81a 100644 --- a/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs +++ b/backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs @@ -66,7 +66,7 @@ public object GetService(DotnetService service) logger.LogInformation("Clearing Service {Service}", service); } - await jsRuntime.InvokeVoidAsync(OverrideServiceFunctionName, service.ToString(), reference); + await jsRuntime.DurableInvokeVoidAsync(OverrideServiceFunctionName, service.ToString(), reference); return reference; } @@ -79,7 +79,7 @@ public async Task InjectCrdtProject(IJSRuntime jsRuntime, await lexboxProjectService.ListenForProjectChanges(projectData, CancellationToken.None); var entryUpdatedSubscription = changeEventBus.OnProjectEntryUpdated(project).Subscribe(entry => { - _ = jsRuntime.InvokeVoidAsync("notifyEntryUpdated", projectName, entry); + _ = jsRuntime.DurableInvokeVoidAsync("notifyEntryUpdated", projectName, entry); }); var service = ActivatorUtilities.CreateInstance(scopedServices, project); var reference = await SetService(jsRuntime,DotnetService.MiniLcmApi, service); diff --git a/backend/FwLite/LocalWebApp/Components/App.razor b/backend/FwLite/LocalWebApp/Components/App.razor index 3e74742c0..61ad16ed7 100644 --- a/backend/FwLite/LocalWebApp/Components/App.razor +++ b/backend/FwLite/LocalWebApp/Components/App.razor @@ -7,6 +7,16 @@ + From 0a6ec6053ffdeb2966b111abe9ba35d806ce38b5 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Wed, 18 Dec 2024 18:01:31 +0700 Subject: [PATCH 072/108] always export methods as promises when converting from c# to TS --- .../TypeGen/ReinforcedFwLiteTypingConfig.cs | 65 ++++++++++--------- .../Projects/ICombinedProjectsService.ts | 3 +- 2 files changed, 38 insertions(+), 30 deletions(-) diff --git a/backend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs b/backend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs index f2ab99bde..e232e6534 100644 --- a/backend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs +++ b/backend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs @@ -52,41 +52,19 @@ public static void Configure(ConfigurationBuilder builder) ], exportBuilder => exportBuilder.WithPublicProperties()); builder.ExportAsEnum().UseString(); - builder.ExportAsInterface().FlattenHierarchy().WithPublicProperties().WithPublicMethods( - exportBuilder => - { - var isUpdatePatchMethod = exportBuilder.Member.GetParameters() - .Any(p => p.ParameterType.IsGenericType && p.ParameterType.GetGenericTypeDefinition() == (typeof(UpdateObjectInput<>))); - if (isUpdatePatchMethod) - { - exportBuilder.Ignore(); - return; - } - var isTaskMethod = (exportBuilder.Member.ReturnType.IsGenericType && - (exportBuilder.Member.ReturnType.GetGenericTypeDefinition() == typeof(Task<>) - || exportBuilder.Member.ReturnType.GetGenericTypeDefinition() == typeof(ValueTask<>))) - || exportBuilder.Member.ReturnType == typeof(Task) - || exportBuilder.Member.ReturnType == typeof(ValueTask); - if (!isTaskMethod) - { - if (exportBuilder.Member.ReturnType == typeof(void)) - { - exportBuilder.Returns(typeof(Task)); - } else - { - exportBuilder.Returns(typeof(Task<>).MakeGenericType(exportBuilder.Member.ReturnType)); - } - } - }); + builder.ExportAsInterface() + .FlattenHierarchy() + .WithPublicProperties() + .WithPublicMethods(b => b.AlwaysReturnPromise()); builder.ExportAsEnum().UseString(); builder.ExportAsInterfaces([typeof(QueryOptions), typeof(SortOptions), typeof(ExemplarOptions)], exportBuilder => exportBuilder.WithProperties(BindingFlags.Public | BindingFlags.Instance)); builder.ExportAsEnum().UseString(); - builder.ExportAsInterface().WithPublicMethods(); - builder.ExportAsInterface().WithPublicMethods(); + builder.ExportAsInterface().WithPublicMethods(b => b.AlwaysReturnPromise()); + builder.ExportAsInterface().WithPublicMethods(b => b.AlwaysReturnPromise()); builder.ExportAsInterface().WithPublicProperties(); - builder.ExportAsInterface().WithPublicMethods(); + builder.ExportAsInterface().WithPublicMethods(b => b.AlwaysReturnPromise()); builder.ExportAsInterface().WithPublicProperties(); builder.ExportAsInterface().WithPublicProperties(); builder.ExportAsInterface().WithPublicProperties(); @@ -96,6 +74,35 @@ public static void Configure(ConfigurationBuilder builder) builder.ExportAsEnum(); } + private static void AlwaysReturnPromise(this MethodExportBuilder exportBuilder) + { + var isUpdatePatchMethod = exportBuilder.Member.GetParameters() + .Any(p => p.ParameterType.IsGenericType && + p.ParameterType.GetGenericTypeDefinition() == (typeof(UpdateObjectInput<>))); + if (isUpdatePatchMethod) + { + exportBuilder.Ignore(); + return; + } + + var isTaskMethod = (exportBuilder.Member.ReturnType.IsGenericType && + (exportBuilder.Member.ReturnType.GetGenericTypeDefinition() == typeof(Task<>) + || exportBuilder.Member.ReturnType.GetGenericTypeDefinition() == typeof(ValueTask<>))) + || exportBuilder.Member.ReturnType == typeof(Task) + || exportBuilder.Member.ReturnType == typeof(ValueTask); + if (!isTaskMethod) + { + if (exportBuilder.Member.ReturnType == typeof(void)) + { + exportBuilder.Returns(typeof(Task)); + } + else + { + exportBuilder.Returns(typeof(Task<>).MakeGenericType(exportBuilder.Member.ReturnType)); + } + } + } + private static void DisableEsLintChecks(ConfigurationBuilder builder) { typeof(ExportContext).GetProperty(nameof(ExportContext.FileOperations))?.SetValue(builder.Context, diff --git a/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Projects/ICombinedProjectsService.ts b/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Projects/ICombinedProjectsService.ts index 394a30ec6..e25259e2a 100644 --- a/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Projects/ICombinedProjectsService.ts +++ b/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Projects/ICombinedProjectsService.ts @@ -9,8 +9,9 @@ import type {ILexboxServer} from '../Auth/ILexboxServer'; export interface ICombinedProjectsService { + supportsFwData() : Promise; remoteProjects() : Promise; - localProjects() : IProjectModel[]; + localProjects() : Promise; downloadProject(lexboxProjectId: string, projectName: string, server: ILexboxServer) : Promise; createProject(name: string) : Promise; } From 599e6b5e1c6056636128b468ccd6236ac22c701b Mon Sep 17 00:00:00 2001 From: Tim Haasdyk Date: Wed, 18 Dec 2024 17:27:01 +0100 Subject: [PATCH 073/108] Replace remaining oklch colors --- frontend/viewer/src/lib/layout/DictionaryEntryViewer.svelte | 2 +- frontend/viewer/tailwind.config.cjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/viewer/src/lib/layout/DictionaryEntryViewer.svelte b/frontend/viewer/src/lib/layout/DictionaryEntryViewer.svelte index 8cf46cbff..df27873af 100644 --- a/frontend/viewer/src/lib/layout/DictionaryEntryViewer.svelte +++ b/frontend/viewer/src/lib/layout/DictionaryEntryViewer.svelte @@ -30,6 +30,6 @@ diff --git a/frontend/viewer/tailwind.config.cjs b/frontend/viewer/tailwind.config.cjs index bc5653344..7b22ec053 100644 --- a/frontend/viewer/tailwind.config.cjs +++ b/frontend/viewer/tailwind.config.cjs @@ -29,7 +29,7 @@ module.exports = { "secondary": "#D6E6FF", "accent": "#75d7ce", "neutral": "#70acc7", - "surface-100": "oklch(100% 0 0)", + "surface-100": "#ffffff", "surface-200": "#f4f5f6", "surface-300": "#d1d5db", "surface-content": "#394E6A", From 1f526afa76c111e6ff22769a7315704ea64827c3 Mon Sep 17 00:00:00 2001 From: Tim Haasdyk Date: Wed, 18 Dec 2024 17:27:30 +0100 Subject: [PATCH 074/108] Add global error handling and tidy up error notifications --- frontend/viewer/src/App.svelte | 3 +++ frontend/viewer/src/lib/errors/global-errors.ts | 11 +++++++++++ .../src/lib/notifications/NotificationOutlet.svelte | 4 ++-- .../viewer/src/lib/notifications/notifications.ts | 4 ++-- 4 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 frontend/viewer/src/lib/errors/global-errors.ts diff --git a/frontend/viewer/src/App.svelte b/frontend/viewer/src/App.svelte index 67d83859b..aa6beb691 100644 --- a/frontend/viewer/src/App.svelte +++ b/frontend/viewer/src/App.svelte @@ -14,6 +14,7 @@ import Sandbox from './lib/sandbox/Sandbox.svelte'; import {settings} from 'svelte-ux'; import DotnetProjectView from './DotnetProjectView.svelte'; + import {setupGlobalErrorHandlers} from '$lib/errors/global-errors'; export let url = ''; @@ -45,6 +46,8 @@ }, }); /* eslint-enable @typescript-eslint/naming-convention */ + + setupGlobalErrorHandlers(); diff --git a/frontend/viewer/src/lib/errors/global-errors.ts b/frontend/viewer/src/lib/errors/global-errors.ts new file mode 100644 index 000000000..8f9aa4fb2 --- /dev/null +++ b/frontend/viewer/src/lib/errors/global-errors.ts @@ -0,0 +1,11 @@ +import {AppNotification} from '$lib/notifications/notifications'; + +export function setupGlobalErrorHandlers() { + window.addEventListener('error', (event: ErrorEvent) => { + AppNotification.display(event.message, 'error', undefined); + }); + + window.addEventListener('unhandledrejection', (event: PromiseRejectionEvent) => { + AppNotification.display(event.reason as string, 'error', undefined); + }); +} diff --git a/frontend/viewer/src/lib/notifications/NotificationOutlet.svelte b/frontend/viewer/src/lib/notifications/NotificationOutlet.svelte index acdfaff52..59d9f32e4 100644 --- a/frontend/viewer/src/lib/notifications/NotificationOutlet.svelte +++ b/frontend/viewer/src/lib/notifications/NotificationOutlet.svelte @@ -13,8 +13,8 @@ {#if $notifications.length}
{#each $notifications as notification} -
- +
+
{#if notification.type === 'success'} diff --git a/frontend/viewer/src/lib/notifications/notifications.ts b/frontend/viewer/src/lib/notifications/notifications.ts index 39711b883..5c961966a 100644 --- a/frontend/viewer/src/lib/notifications/notifications.ts +++ b/frontend/viewer/src/lib/notifications/notifications.ts @@ -11,10 +11,10 @@ export class AppNotification { return this._notifications; } - public static display(message: string, type: 'success' | 'error' | 'info' | 'warning', timeout: 'short' | 'long' | number = 'short') { + public static display(message: string, type: 'success' | 'error' | 'info' | 'warning', timeout?: 'short' | 'long' | number) { const notification = new AppNotification(message, type); this._notifications.update(notifications => [...notifications, notification]); - if (timeout === -1) return; + if (!timeout || typeof timeout === 'number' && timeout <= 0) return; if (typeof timeout === 'string') { timeout = timeout === 'short' ? 5000 : 30000; } From a47095e069685969348eacff23e670bf5f0d9516 Mon Sep 17 00:00:00 2001 From: Tim Haasdyk Date: Wed, 18 Dec 2024 17:28:21 +0100 Subject: [PATCH 075/108] Explicitly empty viewer app out dir to prevent unexpected residue --- frontend/viewer/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/viewer/package.json b/frontend/viewer/package.json index f25291346..759388e03 100644 --- a/frontend/viewer/package.json +++ b/frontend/viewer/package.json @@ -19,7 +19,7 @@ "dev": "vite build -m web-component --watch", "lexbox-dev": "vite build -m web-component", "build": "vite build -m web-component", - "build-app": "vite build", + "build-app": "vite build --emptyOutDir", "preview": "vite preview", "test": "vitest run", "test:ui": "vitest --ui", From 17a7bedb70bf19528f9ff38312e169b4ef877182 Mon Sep 17 00:00:00 2001 From: Tim Haasdyk Date: Wed, 18 Dec 2024 17:32:31 +0100 Subject: [PATCH 076/108] Add finally blocks to home view actions --- frontend/viewer/src/HomeView.svelte | 51 +++++++++++++++++++---------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/frontend/viewer/src/HomeView.svelte b/frontend/viewer/src/HomeView.svelte index 7f2fffbc0..21a6497df 100644 --- a/frontend/viewer/src/HomeView.svelte +++ b/frontend/viewer/src/HomeView.svelte @@ -37,9 +37,12 @@ async function importFwDataProject(name: string) { if (importing) return; importing = name; - await importFwdataService.import(name); - await refreshProjects(); - importing = ''; + try { + await importFwdataService.import(name); + await refreshProjects(); + } finally { + importing = ''; + } } let downloading = ''; @@ -47,9 +50,12 @@ async function downloadCrdtProject(project: Project, server: ILexboxServer) { downloading = project.name; if (project.id == null) throw new Error('Project id is null'); - await projectsService.downloadProject(project.id, project.name, server); - await refreshProjects(); - downloading = ''; + try { + await projectsService.downloadProject(project.id, project.name, server); + await refreshProjects(); + } finally { + downloading = ''; + } } let projectsPromise = projectsService.localProjects().then(p => projects = p); @@ -65,11 +71,14 @@ let loadingRemoteProjects = false; async function fetchRemoteProjects(): Promise { loadingRemoteProjects = true; - let result = await projectsService.remoteProjects(); - for (let serverProjects of result) { - remoteProjects[serverProjects.server.authority] = serverProjects.projects; + try { + let result = await projectsService.remoteProjects(); + for (let serverProjects of result) { + remoteProjects[serverProjects.server.authority] = serverProjects.projects; + } + } finally { + loadingRemoteProjects = false; } - loadingRemoteProjects = false; } fetchRemoteProjects().catch((error) => { @@ -80,18 +89,24 @@ let loadingServer: string = ''; async function login(server: ILexboxServer) { loadingServer = server.authority; - await authService.signInWebView(server); - await fetchRemoteProjects(); - serversStatus = await authService.servers(); - loadingServer = ''; + try { + await authService.signInWebView(server); + await fetchRemoteProjects(); + serversStatus = await authService.servers(); + } finally { + loadingServer = ''; + } } async function logout(server: ILexboxServer) { loadingServer = server.authority; - await authService.logout(server); - await fetchRemoteProjects(); - serversStatus = await authService.servers(); - loadingServer = ''; + try { + await authService.logout(server); + await fetchRemoteProjects(); + serversStatus = await authService.servers(); + } finally { + loadingServer = ''; + } } From 2df2affd492adf19322acfb0cd46122e23128091 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Thu, 19 Dec 2024 10:53:49 +0700 Subject: [PATCH 077/108] fix compile error in test --- .../FwLite/FwLiteProjectSync.Tests/Fixtures/SyncFixture.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/FwLite/FwLiteProjectSync.Tests/Fixtures/SyncFixture.cs b/backend/FwLite/FwLiteProjectSync.Tests/Fixtures/SyncFixture.cs index 7a1279ba1..96f8b758f 100644 --- a/backend/FwLite/FwLiteProjectSync.Tests/Fixtures/SyncFixture.cs +++ b/backend/FwLite/FwLiteProjectSync.Tests/Fixtures/SyncFixture.cs @@ -41,9 +41,10 @@ public async Task InitializeAsync() .ProjectsFolder; if (Path.Exists(projectsFolder)) Directory.Delete(projectsFolder, true); Directory.CreateDirectory(projectsFolder); + var fwDataProject = new FwDataProject(_projectName, projectsFolder); _services.ServiceProvider.GetRequiredService() - .NewProject(new FwDataProject(_projectName, projectsFolder), "en", "en"); - FwDataApi = _services.ServiceProvider.GetRequiredService().GetFwDataMiniLcmApi(_projectName, false); + .NewProject(fwDataProject, "en", "en"); + FwDataApi = _services.ServiceProvider.GetRequiredService().GetFwDataMiniLcmApi(fwDataProject, false); var crdtProjectsFolder = _services.ServiceProvider.GetRequiredService>().Value.ProjectPath; From a5f1aa1f374fdf7759aad0974b41bfa5af8f3f0e Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Thu, 19 Dec 2024 10:54:02 +0700 Subject: [PATCH 078/108] don't always copy Mercurial stuff --- .../FwLiteProjectSync.Tests/FwLiteProjectSync.Tests.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/FwLite/FwLiteProjectSync.Tests/FwLiteProjectSync.Tests.csproj b/backend/FwLite/FwLiteProjectSync.Tests/FwLiteProjectSync.Tests.csproj index abe85567f..ae185c9c2 100644 --- a/backend/FwLite/FwLiteProjectSync.Tests/FwLiteProjectSync.Tests.csproj +++ b/backend/FwLite/FwLiteProjectSync.Tests/FwLiteProjectSync.Tests.csproj @@ -44,8 +44,8 @@ - - + + From 21fc21f365b3f3dd6862233a6c35327b4afa7e5a Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Thu, 19 Dec 2024 10:54:51 +0700 Subject: [PATCH 079/108] mark tests as slow --- backend/FwLite/FwLiteProjectSync.Tests/Sena3SyncTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/FwLite/FwLiteProjectSync.Tests/Sena3SyncTests.cs b/backend/FwLite/FwLiteProjectSync.Tests/Sena3SyncTests.cs index 54e991041..80f10f643 100644 --- a/backend/FwLite/FwLiteProjectSync.Tests/Sena3SyncTests.cs +++ b/backend/FwLite/FwLiteProjectSync.Tests/Sena3SyncTests.cs @@ -103,6 +103,7 @@ public async Task DryRunSync_MakesNoChanges() } [Fact] + [Trait("Category", "Slow")] public async Task DryRunSync_MakesTheSameChangesAsSync() { //syncing requires querying entries, which fails if there are no writing systems, so we import those first @@ -133,6 +134,7 @@ public async Task FirstSena3SyncJustDoesAnSync() } [Fact] + [Trait("Category", "Slow")] public async Task SyncWithoutImport_CrdtShouldMatchFwdata() { await BypassImport(); From aa5067cb555b5a8d8818db21b7920a00ca2daa71 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Thu, 19 Dec 2024 11:03:40 +0700 Subject: [PATCH 080/108] keep `CurrentProjectService` scoped when running tests --- backend/FwLite/LcmCrdt.Tests/LcmCrdtTestsKernel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/FwLite/LcmCrdt.Tests/LcmCrdtTestsKernel.cs b/backend/FwLite/LcmCrdt.Tests/LcmCrdtTestsKernel.cs index ccce423b6..e0d4e4204 100644 --- a/backend/FwLite/LcmCrdt.Tests/LcmCrdtTestsKernel.cs +++ b/backend/FwLite/LcmCrdt.Tests/LcmCrdtTestsKernel.cs @@ -12,7 +12,7 @@ public static IServiceCollection AddTestLcmCrdtClient(this IServiceCollection se services.AddLcmCrdtClient(); if (project is not null) { - services.AddSingleton(provider => + services.AddScoped(provider => { var currentProjectService = ActivatorUtilities.CreateInstance(provider); currentProjectService.SetupProjectContextForNewDb(project); From 382cbd407f03d0a6b5859af8bb77c0e0dbd96f44 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Thu, 19 Dec 2024 11:45:02 +0700 Subject: [PATCH 081/108] fix frontend build issue because viewer output file was renamed --- frontend/viewer/package.json | 4 ++-- frontend/viewer/vite.config.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/viewer/package.json b/frontend/viewer/package.json index 759388e03..0c823a0c5 100644 --- a/frontend/viewer/package.json +++ b/frontend/viewer/package.json @@ -8,9 +8,9 @@ }, "version": "1.0.0", "type": "module", - "main": "dist-web-component/viewer.js", + "main": "dist-web-component/web-component.js", "exports": { - "./component": "./dist-web-component/viewer.js", + "./component": "./dist-web-component/web-component.js", "./lexbox-api": "./src/lib/services/lexbox-api.ts", "./service-provider": "./src/lib/services/service-provider.ts" }, diff --git a/frontend/viewer/vite.config.ts b/frontend/viewer/vite.config.ts index d5da0c35a..f5756d8e9 100644 --- a/frontend/viewer/vite.config.ts +++ b/frontend/viewer/vite.config.ts @@ -25,7 +25,7 @@ export default defineConfig(({ mode }) => { entryFileNames: '[name].js', chunkFileNames: '[name].js', assetFileNames: '[name][extname]', - manualChunks: { + manualChunks: webComponent ? {} : { 'svelte-ux': ['svelte-ux'], }, }, From f4149cb3a77561de6670bd51efd77cb58be0efdc Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Thu, 19 Dec 2024 14:04:26 +0700 Subject: [PATCH 082/108] migrate away from types in viewer/lib/mini-lcm and use generated dotnet types instead --- .../Services/MiniLcmJsInvokable.cs | 2 +- .../TypeGen/ReinforcedFwLiteTypingConfig.cs | 18 +- .../[project_code]/viewer/+page.svelte | 9 +- .../viewer/lfClassicLexboxApi.ts | 77 ++---- frontend/viewer/src/App.svelte | 5 + frontend/viewer/src/ProjectView.svelte | 7 +- frontend/viewer/src/TestProjectView.svelte | 6 +- frontend/viewer/src/WebComponent.svelte | 3 +- .../viewer/src/lib/DictionaryEntry.svelte | 3 +- frontend/viewer/src/lib/Editor.svelte | 10 +- frontend/viewer/src/lib/SyncConfig.svelte | 102 -------- .../viewer/src/lib/about/AboutDialog.svelte | 5 +- frontend/viewer/src/lib/commands.ts | 2 +- frontend/viewer/src/lib/complex-form-types.ts | 10 +- frontend/viewer/src/lib/config-data.ts | 2 +- frontend/viewer/src/lib/config-types.ts | 16 +- .../FwLiteShared/Services/IMiniLcmFeatures.ts | 10 +- .../MiniLcm/Models/IWritingSystem.ts | 1 - frontend/viewer/src/lib/dotnet-types/index.ts | 17 ++ frontend/viewer/src/lib/entry-data.ts | 40 ++- .../entry-editor/EntryOrSensePicker.svelte | 9 +- .../lib/entry-editor/NewEntryDialog.svelte | 4 +- .../ComplexFormComponents.svelte | 7 +- .../field-editors/ComplexFormTypes.svelte | 6 +- .../field-editors/ComplexForms.svelte | 7 +- .../field-editors/FieldEditor.svelte | 4 +- .../field-editors/MultiFieldEditor.svelte | 6 +- .../field-editors/SingleFieldEditor.svelte | 2 +- .../entry-editor/inputs/CrdtTextField.svelte | 2 +- .../object-editors/EntryEditor.svelte | 8 +- .../object-editors/ExampleEditor.svelte | 2 +- .../object-editors/SenseEditor.svelte | 2 +- .../Lexbox.ClientServer.Hubs.ts | 8 +- .../TypedSignalR.Client/index.ts | 240 ++++++++++++------ .../viewer/src/lib/in-memory-api-service.ts | 178 +++++++++---- .../lib/layout/DictionaryEntryViewer.svelte | 2 +- .../viewer/src/lib/layout/EntryList.svelte | 2 +- frontend/viewer/src/lib/layout/Toc.svelte | 2 +- .../src/lib/layout/ViewOptionsDrawer.svelte | 4 - .../lib/mini-lcm/complex-form-component.ts | 13 - .../src/lib/mini-lcm/complex-form-type.ts | 6 - frontend/viewer/src/lib/mini-lcm/entry.ts | 26 -- .../src/lib/mini-lcm/example-sentence.ts | 19 -- .../lib/mini-lcm/i-complex-form-component.ts | 13 - frontend/viewer/src/lib/mini-lcm/i-entry.ts | 21 -- .../src/lib/mini-lcm/i-example-sentence.ts | 13 - .../viewer/src/lib/mini-lcm/i-multi-string.ts | 1 - frontend/viewer/src/lib/mini-lcm/i-sense.ts | 17 -- frontend/viewer/src/lib/mini-lcm/index.ts | 20 -- .../viewer/src/lib/mini-lcm/multi-string.ts | 10 - .../viewer/src/lib/mini-lcm/part-of-speech.ts | 7 - .../viewer/src/lib/mini-lcm/query-options.ts | 21 -- .../src/lib/mini-lcm/semantic-domain.ts | 8 - frontend/viewer/src/lib/mini-lcm/sense.ts | 23 -- .../viewer/src/lib/mini-lcm/writing-system.ts | 19 -- .../src/lib/mini-lcm/writing-systems.ts | 11 - frontend/viewer/src/lib/parts-of-speech.ts | 12 +- .../viewer/src/lib/sandbox/Sandbox.svelte | 3 + .../src/lib/search-bar/SearchBar.svelte | 16 +- frontend/viewer/src/lib/semantic-domains.ts | 10 +- frontend/viewer/src/lib/services/event-bus.ts | 8 +- .../src/lib/services/history-service.ts | 2 +- .../viewer/src/lib/services/lexbox-api.ts | 83 +++--- .../src/lib/services/projects-service.ts | 20 +- .../lib/services/service-provider-signalr.ts | 17 +- frontend/viewer/src/lib/utils.ts | 17 +- frontend/viewer/src/lib/writing-systems.ts | 8 +- 67 files changed, 569 insertions(+), 715 deletions(-) delete mode 100644 frontend/viewer/src/lib/SyncConfig.svelte delete mode 100644 frontend/viewer/src/lib/mini-lcm/complex-form-component.ts delete mode 100644 frontend/viewer/src/lib/mini-lcm/complex-form-type.ts delete mode 100644 frontend/viewer/src/lib/mini-lcm/entry.ts delete mode 100644 frontend/viewer/src/lib/mini-lcm/example-sentence.ts delete mode 100644 frontend/viewer/src/lib/mini-lcm/i-complex-form-component.ts delete mode 100644 frontend/viewer/src/lib/mini-lcm/i-entry.ts delete mode 100644 frontend/viewer/src/lib/mini-lcm/i-example-sentence.ts delete mode 100644 frontend/viewer/src/lib/mini-lcm/i-multi-string.ts delete mode 100644 frontend/viewer/src/lib/mini-lcm/i-sense.ts delete mode 100644 frontend/viewer/src/lib/mini-lcm/index.ts delete mode 100644 frontend/viewer/src/lib/mini-lcm/multi-string.ts delete mode 100644 frontend/viewer/src/lib/mini-lcm/part-of-speech.ts delete mode 100644 frontend/viewer/src/lib/mini-lcm/query-options.ts delete mode 100644 frontend/viewer/src/lib/mini-lcm/semantic-domain.ts delete mode 100644 frontend/viewer/src/lib/mini-lcm/sense.ts delete mode 100644 frontend/viewer/src/lib/mini-lcm/writing-system.ts delete mode 100644 frontend/viewer/src/lib/mini-lcm/writing-systems.ts diff --git a/backend/FwLite/FwLiteShared/Services/MiniLcmJsInvokable.cs b/backend/FwLite/FwLiteShared/Services/MiniLcmJsInvokable.cs index 7a8038f8b..6e89e6ecc 100644 --- a/backend/FwLite/FwLiteShared/Services/MiniLcmJsInvokable.cs +++ b/backend/FwLite/FwLiteShared/Services/MiniLcmJsInvokable.cs @@ -12,7 +12,7 @@ internal class MiniLcmJsInvokable( IProjectIdentifier project) : IDisposable { - public record MiniLcmFeatures(bool History, bool Write, bool OpenWithFlex, bool Feedback, bool Sync); + public record MiniLcmFeatures(bool? History, bool? Write, bool? OpenWithFlex, bool? Feedback, bool? Sync); private bool SupportsSync => project.DataFormat == ProjectDataFormat.Harmony && api is CrdtMiniLcmApi; [JSInvokable] public MiniLcmFeatures SupportedFeatures() diff --git a/backend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs b/backend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs index e232e6534..13e805cd4 100644 --- a/backend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs +++ b/backend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs @@ -37,9 +37,15 @@ public static void Configure(ConfigurationBuilder builder) From = "$lib/dotnet-types/i-multi-string", Target = "type {IMultiString}" }]); + builder.ExportAsInterface().WithPublicNonStaticProperties(exportBuilder => + { + if (exportBuilder.Member.Name == nameof(Sense.Order)) + { + exportBuilder.Ignore(); + } + }); builder.ExportAsInterfaces([ typeof(Entry), - typeof(Sense), typeof(ExampleSentence), typeof(WritingSystem), typeof(WritingSystems), @@ -50,7 +56,7 @@ public static void Configure(ConfigurationBuilder builder) typeof(MiniLcmJsInvokable.MiniLcmFeatures), ], - exportBuilder => exportBuilder.WithPublicProperties()); + exportBuilder => exportBuilder.WithPublicNonStaticProperties()); builder.ExportAsEnum().UseString(); builder.ExportAsInterface() .FlattenHierarchy() @@ -58,7 +64,7 @@ public static void Configure(ConfigurationBuilder builder) .WithPublicMethods(b => b.AlwaysReturnPromise()); builder.ExportAsEnum().UseString(); builder.ExportAsInterfaces([typeof(QueryOptions), typeof(SortOptions), typeof(ExemplarOptions)], - exportBuilder => exportBuilder.WithProperties(BindingFlags.Public | BindingFlags.Instance)); + exportBuilder => exportBuilder.WithPublicNonStaticProperties()); builder.ExportAsEnum().UseString(); builder.ExportAsInterface().WithPublicMethods(b => b.AlwaysReturnPromise()); @@ -103,6 +109,12 @@ private static void AlwaysReturnPromise(this MethodExportBuilder exportBuilder) } } + private static T WithPublicNonStaticProperties(this T tc, Action? configuration = null) + where T : ClassOrInterfaceExportBuilder + { + return tc.WithProperties(BindingFlags.Public | BindingFlags.Instance, configuration); + } + private static void DisableEsLintChecks(ConfigurationBuilder builder) { typeof(ExportContext).GetProperty(nameof(ExportContext.FileOperations))?.SetValue(builder.Context, diff --git a/frontend/src/routes/(authenticated)/project/[project_code]/viewer/+page.svelte b/frontend/src/routes/(authenticated)/project/[project_code]/viewer/+page.svelte index 5433b0f72..a8aa8b694 100644 --- a/frontend/src/routes/(authenticated)/project/[project_code]/viewer/+page.svelte +++ b/frontend/src/routes/(authenticated)/project/[project_code]/viewer/+page.svelte @@ -1,21 +1,18 @@ {#if service} {#key service} - + {/key} {/if} diff --git a/frontend/src/routes/(authenticated)/project/[project_code]/viewer/lfClassicLexboxApi.ts b/frontend/src/routes/(authenticated)/project/[project_code]/viewer/lfClassicLexboxApi.ts index fca833cb2..e6f32ef21 100644 --- a/frontend/src/routes/(authenticated)/project/[project_code]/viewer/lfClassicLexboxApi.ts +++ b/frontend/src/routes/(authenticated)/project/[project_code]/viewer/lfClassicLexboxApi.ts @@ -1,19 +1,17 @@ /* eslint-disable @typescript-eslint/naming-convention */ import type { - ComplexFormType, + IComplexFormType, IEntry, IExampleSentence, ISense, - JsonPatch, LexboxApiClient, - LexboxApiFeatures, - PartOfSpeech, - QueryOptions, - SemanticDomain, - WritingSystem, + IPartOfSpeech, + IQueryOptions, + ISemanticDomain, + IWritingSystem, WritingSystemType, - WritingSystems, + IWritingSystems, } from 'viewer/lexbox-api'; import type {Readable} from 'svelte/store'; @@ -31,29 +29,22 @@ function prepareEntriesForUi(entries: IEntry[]): void { }); } -function preparePartsOfSpeedForUi(partsOfSpeech: PartOfSpeech[]): void { +function preparePartsOfSpeedForUi(partsOfSpeech: IPartOfSpeech[]): void { partsOfSpeech.forEach(pos => { pos.id = pos.name['__key']; }); } export class LfClassicLexboxApi implements LexboxApiClient { - constructor(private projectCode: string, private aboutMarkdown: Readable) { + constructor(private projectCode: string) { } - SupportedFeatures(): LexboxApiFeatures { - return { - feedback: true, - about: this.aboutMarkdown, - }; - } - - async GetWritingSystems(): Promise { + async getWritingSystems(): Promise { const result = await fetch(`/api/lfclassic/${this.projectCode}/writingSystems`); - return (await result.json()) as WritingSystems; + return (await result.json()) as IWritingSystems; } - async GetEntries(_options: QueryOptions | undefined): Promise { + async getEntries(_options: IQueryOptions | undefined): Promise { //todo pass query options into query const result = await fetch(`/api/lfclassic/${this.projectCode}/entries${this.toQueryParams(_options)}`); const entries = (await result.json()) as IEntry[]; @@ -61,7 +52,7 @@ export class LfClassicLexboxApi implements LexboxApiClient { return entries; } - async SearchEntries(_query: string, _options: QueryOptions | undefined): Promise { + async searchEntries(_query: string, _options: IQueryOptions | undefined): Promise { //todo pass query options into query const result = await fetch(`/api/lfclassic/${this.projectCode}/entries/${encodeURIComponent(_query)}${this.toQueryParams(_options)}`); const entries = (await result.json()) as IEntry[]; @@ -69,7 +60,7 @@ export class LfClassicLexboxApi implements LexboxApiClient { return entries; } - private toQueryParams(options: QueryOptions | undefined): string { + private toQueryParams(options: IQueryOptions | undefined): string { if (!options) return ''; /* eslint-disable @typescript-eslint/no-unsafe-assignment */ @@ -89,70 +80,54 @@ export class LfClassicLexboxApi implements LexboxApiClient { return '?' + params.toString(); } - async GetPartsOfSpeech(): Promise { + async getPartsOfSpeech(): Promise { const result = await fetch(`/api/lfclassic/${this.projectCode}/parts-of-speech`); - const partsOfSpeech = (await result.json()) as PartOfSpeech[]; + const partsOfSpeech = (await result.json()) as IPartOfSpeech[]; preparePartsOfSpeedForUi(partsOfSpeech); return partsOfSpeech; } - GetSemanticDomains(): Promise { + getSemanticDomains(): Promise { return Promise.resolve(SEMANTIC_DOMAINS_EN); } - GetComplexFormTypes(): Promise { + getComplexFormTypes(): Promise { return Promise.resolve([]); } - CreateWritingSystem(_type: WritingSystemType, _writingSystem: WritingSystem): Promise { - throw new Error('Method not implemented.'); - } - - UpdateWritingSystem(_wsId: string, _type: WritingSystemType, _update: JsonPatch): Promise { - throw new Error('Method not implemented.'); - } - - GetEntry(_guid: string): Promise { - throw new Error('Method not implemented.'); - } - - CreateEntry(_entry: IEntry): Promise { - throw new Error('Method not implemented.'); - } - - UpdateEntry(_before: IEntry, _after: IEntry): Promise { + createWritingSystem(_type: WritingSystemType, _writingSystem: IWritingSystem): Promise { throw new Error('Method not implemented.'); } - CreateSense(_entryGuid: string, _sense: ISense): Promise { + getEntry(_guid: string): Promise { throw new Error('Method not implemented.'); } - UpdateSense(_entryGuid: string, _senseGuid: string, _update: JsonPatch): Promise { + createEntry(_entry: IEntry): Promise { throw new Error('Method not implemented.'); } - CreateExampleSentence(_entryGuid: string, _senseGuid: string, _exampleSentence: IExampleSentence): Promise { + updateEntry(_before: IEntry, _after: IEntry): Promise { throw new Error('Method not implemented.'); } - UpdateExampleSentence(_entryGuid: string, _senseGuid: string, _exampleSentenceGuid: string, _update: JsonPatch): Promise { + createSense(_entryGuid: string, _sense: ISense): Promise { throw new Error('Method not implemented.'); } - GetExemplars(): Promise { + createExampleSentence(_entryGuid: string, _senseGuid: string, _exampleSentence: IExampleSentence): Promise { throw new Error('Method not implemented.'); } - DeleteEntry(_guid: string): Promise { + deleteEntry(_guid: string): Promise { throw new Error('Method not implemented.'); } - DeleteSense(_entryGuid: string, _senseGuid: string): Promise { + deleteSense(_entryGuid: string, _senseGuid: string): Promise { throw new Error('Method not implemented.'); } - DeleteExampleSentence(_entryGuid: string, _senseGuid: string, _exampleSentenceGuid: string): Promise { + deleteExampleSentence(_entryGuid: string, _senseGuid: string, _exampleSentenceGuid: string): Promise { throw new Error('Method not implemented.'); } diff --git a/frontend/viewer/src/App.svelte b/frontend/viewer/src/App.svelte index aa6beb691..42694a135 100644 --- a/frontend/viewer/src/App.svelte +++ b/frontend/viewer/src/App.svelte @@ -1,5 +1,10 @@  diff --git a/frontend/viewer/src/WebComponent.svelte b/frontend/viewer/src/WebComponent.svelte index 82fa73ada..0c281ac81 100644 --- a/frontend/viewer/src/WebComponent.svelte +++ b/frontend/viewer/src/WebComponent.svelte @@ -7,6 +7,7 @@ let loading = true; export let projectName: string; + export let about: string | undefined; onMount(() => { const shadowRoot = document.querySelector('lexbox-svelte')?.shadowRoot; @@ -43,5 +44,5 @@
- +
diff --git a/frontend/viewer/src/lib/DictionaryEntry.svelte b/frontend/viewer/src/lib/DictionaryEntry.svelte index 939a539da..680024990 100644 --- a/frontend/viewer/src/lib/DictionaryEntry.svelte +++ b/frontend/viewer/src/lib/DictionaryEntry.svelte @@ -11,11 +11,10 @@ 'text-rose-500 dark:text-rose-400', ] as const; - diff --git a/frontend/viewer/src/lib/SyncConfig.svelte b/frontend/viewer/src/lib/SyncConfig.svelte deleted file mode 100644 index c53239c4e..000000000 --- a/frontend/viewer/src/lib/SyncConfig.svelte +++ /dev/null @@ -1,102 +0,0 @@ - - -{#if $servers.length > 1 && !isUploaded} - ({ value: server.authority, label: server.displayName, group: server.displayName }))} - bind:value={$projectServer} - classes={{root: 'view-select w-auto', options: 'view-select-options'}} - clearable={false} - labelPlacement="top" - clearSearchOnOpen={false} - fieldActions={(elem) => /* a hack to disable typing/filtering */ {elem.readOnly = true; return [];}} - search={() => /* a hack to always show all options */ Promise.resolve()}> - -{:else if isUploaded && server.loggedIn} - -{/if} -{#if $projectServer && !isUploaded && server.loggedIn} - {#await serverProjectsForUpload($projectServer)} -
- {:then projects} - ({ value: p.id, label: p.name, group: p.name }))} - bind:value={targetProjectId} - classes={{root: 'view-select w-auto', options: 'view-select-options'}} - clearable={false} - labelPlacement="top" - clearSearchOnOpen={false} - fieldActions={(elem) => /* a hack to disable typing/filtering */ {elem.readOnly = true; return [];}} - search={() => /* a hack to always show all options */ Promise.resolve()}> - - {/await} - -{/if} -{#if server && !server.loggedIn} - {#if isUploaded} - Your login has expired to sync with {server.displayName}. Please login again. - {/if} - -{/if} diff --git a/frontend/viewer/src/lib/about/AboutDialog.svelte b/frontend/viewer/src/lib/about/AboutDialog.svelte index 24c651676..9199edecf 100644 --- a/frontend/viewer/src/lib/about/AboutDialog.svelte +++ b/frontend/viewer/src/lib/about/AboutDialog.svelte @@ -1,12 +1,11 @@