Skip to content

Commit

Permalink
Blazor Hybrid (#1320)
Browse files Browse the repository at this point in the history
* import frontend work from setup blazor web branch

* import fw lite desktop changes

* import fw lite shared blazor changes

* import local web app changes

* tweak type generation to support async and i async enumerable, as well as other types that hadn't been included yet

* continue polishing type generation, pull out multi-string to allow a custom definition

* rewrite `fetchProjects` to `localProjects`, let js call dotnet methods as camelCase and convert to PascalCase

* develop a way to map services between js and dotnet

* expose more services and use them from HomeView.svelte

* commit generated ts types

* add create project api and call from home page

* fix server authority issues

* wire up js invokable miniLcm service for crdts and fwdata

* create dotnet project view to solve late binding of api service, fix issue with IHostApplicationLifetime not being available in Maui

* remove reference which was breaking android build of FwLiteDesktop

* dont set TargetFramework in build props as that prevents projects from setting more specific frameworks

* fix some broken stuff due to services not existing

* fix android app icon missing issue

* remove usage of `toSorted` which is not supported on older android phones

* allow invoking logout/login via js

* remove unused constructor fields on entry component change

* populate datacache when injecting a crdt project to the active scope

* configure oauth client to support protocol handler on android to allow callbacks to return to the app

* fix port forwarding for staging

* refactor some service setup after merge

* refactor IsPackagedApp into IsPortableApp

* refactor FwLiteProvider to use constant for js function name and simplify removing a service

* change svelte app binding to look for `id="svelte-app"` instead of just `app`

* fix compile issues and dependency missmatch

* prevent svelte from catching navigation to allow blazor to handle it, then notify svelte of navigation from the blazor event

* setup watch builder build task

* fix issue with rollup not resolving $lib

* configure linq2db to use the Microsoft SQLite library instead of system data sqlite

* prevent throwing errors when sending change notifications fails after a sync

* ensure hosted services run at startup as expected

* return the project when setting active project context

* !fixup ensure hosted services run at startup as expected

* make all methods return async in generated ts code, support ValueTask

* update generated types

* connect js event bus to C# ChangeEventBus

* remove ProjectContext.cs and use stateful CurrentProjectService.cs instead

* correct nrt error

* update swashbuckle version to fix build error

error was `error : Unable to find service type 'Microsoft.Extensions.ApiDescriptions.IDocumentProvider' in loaded assemblies.`

* ignore csproj.user files

* remove unused layout files

* dispose of the module properly when the SvelteLayout is disposed of

* change crdt project pages to use OwningComponentBase which ensures they have a service scope to use for project access

* pass in jsRuntime as a parameter to FwLiteProvider rather than injecting it since it's a scoped service

* make login buttons reactive

* remove unused components

* create an async disposing OwningComponentBase and use it for project pages

* fix app header under status bar issue on android, ensure that status bar color matches fw lite theme

* use hsl color space rather than oklch to support older browsers on old phones

* remove unused generated types

* 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

* !fixup remove unused generated types

* configure `fw-lite:maui-desktop` task to run build-viewer-app

* remove deleted generated file from index

* update caniuse-lite

* specify condition for windows related properties

* refactor FwDataProjectContext.cs to be scoped and not a singleton

* make miniLcm disposable

* refactor FieldWorksProjectList.cs and CrdtProjectsService.cs into a common IProjectProvider.cs interface

* refactor project import to make it optional. if import is called at runtime, it will throw

* refactor FwShared to not depend on FwDataBridge, put that dependency into FwLiteDesktop windows builds

* fix circular dependency between FieldWorksProjectList and FwDataFactory

* ensure lcm service is cleared when going home

* update typegen

* only enable fwdata bridge when building for windows

* use supportsFwData to determine if we show the fieldworks column

* 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

* always export methods as promises when converting from c# to TS

* Replace remaining oklch colors

* Add global error handling and tidy up error notifications

* Explicitly empty viewer app out dir to prevent unexpected residue

* Add finally blocks to home view actions

* fix compile error in test

* don't always copy Mercurial stuff

* mark tests as slow

* keep `CurrentProjectService` scoped when running tests

* fix frontend build issue because viewer output file was renamed

* migrate away from types in viewer/lib/mini-lcm and use generated dotnet types instead

* make ProjectView about optional

* resolve import errors when importing code to the frontend from the viewer

* change fw-lite build to use ubunut-latest on main build

* build android

* build maui on windows

* fix eslint errors

* specify os when building for android

* don't import Dotnet service in viewer because it breaks the build

* make release depend on the publish-android job

* build android on mac, run tests on ubuntu, fix windows build

* remove setup maui from initial build since it doesn't work on linux

* use windows for tests since linux doesn't seem to like the slnf file

* install maui instead of maui-android

* fix os condition

* Add Vs code tasks for FwLiteDesktop and android task

* avoid trimming SqliteConnection ClearAllPools

* publish single file when making portable windows builds

* only use MsalCacheHelper on desktop platforms

* activate the desktop window once login is successful

* Try to dispose service provider on shutdown

* Make some JS more readable

* Add FwLiteShared to FwLiteOnly.slnf

* remove typegen tool

* markup route parameters as services

* enable file logging again

* Move comment

---------

Co-authored-by: Tim Haasdyk <tim_haasdyk@sil.org>
  • Loading branch information
hahn-kev and myieye authored Dec 20, 2024
1 parent 2c27c73 commit 4917ae3
Show file tree
Hide file tree
Showing 227 changed files with 3,376 additions and 2,044 deletions.
2 changes: 1 addition & 1 deletion .config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@
"rollForward": false
}
}
}
}
84 changes: 54 additions & 30 deletions .github/workflows/fw-lite.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ on:
branches:
- develop
- main

env:
VIEWER_BUILD_OUTPUT_DIR: backend/FwLite/FwLiteShared/wwwroot/viewer
jobs:
build-and-test:
name: Build FW Lite and run tests
timeout-minutes: 30
timeout-minutes: 40
runs-on: windows-latest
outputs:
version: ${{ steps.setVersion.outputs.VERSION }}
Expand All @@ -40,9 +41,6 @@ jobs:
with:
node-version-file: './frontend/package.json'

- name: Setup Maui
run: dotnet workload install maui-windows

- name: Set Version
id: setVersion
shell: bash
Expand All @@ -59,20 +57,15 @@ jobs:
pnpm install
pnpm run build-app
- name: Dotnet build
working-directory: backend/FwLite/FwLiteDesktop
run: |
dotnet build --configuration Release
- name: Dotnet test
run: dotnet test FwLiteOnly.slnf --configuration Release --logger GitHubActions
run: dotnet test FwLiteOnly.slnf --logger GitHubActions

- name: Upload viewer artifacts
uses: actions/upload-artifact@v4
with:
name: fw-lite-viewer-app
name: fw-lite-viewer-js
if-no-files-found: error
path: frontend/viewer/dist
path: ${{ env.VIEWER_BUILD_OUTPUT_DIR }}

publish-mac:
name: Publish FW Lite app for Mac
Expand All @@ -86,8 +79,8 @@ jobs:
submodules: true
- uses: actions/download-artifact@v4
with:
name: fw-lite-viewer-app
path: frontend/viewer/dist
name: fw-lite-viewer-js
path: ${{ env.VIEWER_BUILD_OUTPUT_DIR }}
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.x'
Expand Down Expand Up @@ -123,16 +116,12 @@ jobs:
submodules: true
- uses: actions/download-artifact@v4
with:
name: fw-lite-viewer-app
path: frontend/viewer/dist
name: fw-lite-viewer-js
path: ${{ env.VIEWER_BUILD_OUTPUT_DIR }}
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.x'

- name: Dotnet build
working-directory: backend/FwLite/LocalWebApp
run: dotnet build --configuration Release

- name: Publish Linux
working-directory: backend/FwLite/LocalWebApp
run: dotnet publish -r linux-x64 --artifacts-path ../artifacts -p:PublishSingleFile=true -p:Version=${{ needs.build-and-test.outputs.semver-version }}
Expand All @@ -144,6 +133,38 @@ jobs:
if-no-files-found: error
path: backend/FwLite/artifacts/publish/LocalWebApp/*

publish-android:
name: Publish FW Lite app for Android
needs: build-and-test
timeout-minutes: 30
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: true
- uses: actions/download-artifact@v4
with:
name: fw-lite-viewer-js
path: ${{ env.VIEWER_BUILD_OUTPUT_DIR }}
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.x'

- name: Setup Maui
run: dotnet workload install maui

- name: Publish Android
working-directory: backend/FwLite/FwLiteDesktop
run: dotnet publish -f net9.0-android --artifacts-path ../artifacts -p:ApplicationDisplayVersion=${{ needs.build-and-test.outputs.semver-version }} -p:InformationalVersion=${{ needs.build-and-test.outputs.version }}

- name: Upload FWLite App artifacts
uses: actions/upload-artifact@v4
with:
name: fw-lite-android
if-no-files-found: error
path: backend/FwLite/artifacts/publish/FwLiteDesktop/*/*

publish-win:
name: Publish FW Lite app for Windows
needs: build-and-test
Expand All @@ -156,30 +177,26 @@ jobs:
submodules: true
- uses: actions/download-artifact@v4
with:
name: fw-lite-viewer-app
path: frontend/viewer/dist
name: fw-lite-viewer-js
path: ${{ env.VIEWER_BUILD_OUTPUT_DIR }}
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.x'

- name: Setup Maui
run: dotnet workload install maui-windows

- name: Dotnet build
working-directory: backend/FwLite/LocalWebApp
run: dotnet build --configuration Release

- name: Publish Windows MAUI portable app
working-directory: backend/FwLite/FwLiteDesktop
run: |
dotnet publish -r win-x64 --artifacts-path ../artifacts -p:WindowsPackageType=None -p:ApplicationDisplayVersion=${{ needs.build-and-test.outputs.semver-version }} -p:InformationalVersion=${{ needs.build-and-test.outputs.version }}
dotnet publish -f net9.0-windows10.0.19041.0 --artifacts-path ../artifacts -p:WindowsPackageType=None -p:ApplicationDisplayVersion=${{ needs.build-and-test.outputs.semver-version }} -p:InformationalVersion=${{ needs.build-and-test.outputs.version }}
mkdir -p ../artifacts/sign/portable
cp -r ../artifacts/publish/FwLiteDesktop/* ../artifacts/sign/portable/
- name: Publish Windows MAUI msix app
working-directory: backend/FwLite/FwLiteDesktop
run: |
dotnet publish -r win-x64 --artifacts-path ../artifacts -p:ApplicationDisplayVersion=${{ needs.build-and-test.outputs.semver-version }} -p:InformationalVersion=${{ needs.build-and-test.outputs.version }}
dotnet publish -f net9.0-windows10.0.19041.0 --artifacts-path ../artifacts -p:ApplicationDisplayVersion=${{ needs.build-and-test.outputs.semver-version }} -p:InformationalVersion=${{ needs.build-and-test.outputs.version }}
mkdir -p ../artifacts/msix
cp ../artifacts/bin/FwLiteDesktop/*/AppPackages/*/*.msix ../artifacts/msix/
Expand Down Expand Up @@ -223,7 +240,7 @@ jobs:
create-release:
if: ${{ github.ref_name == 'main' }}
name: Create Release
needs: [ build-and-test, publish-win, publish-linux, publish-mac]
needs: [ build-and-test, publish-win, publish-linux, publish-mac, publish-android]
runs-on: ubuntu-latest

steps:
Expand All @@ -239,6 +256,10 @@ jobs:
with:
name: fw-lite-local-web-app-linux
path: fw-lite-local-web-app-linux
- uses: actions/download-artifact@v4
with:
name: fw-lite-android
path: fw-lite-android

- name: Zip artifacts
run: |
Expand All @@ -256,6 +277,9 @@ jobs:
fw-lite-msix/*
fw-lite-portable.zip
fw-lite-local-web-app-linux.zip
fw-lite-android/*.apk
fw-lite-android/*.aab
- name: Invalidate Lexbox Release endpoint
continue-on-error: true
run: |
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ test-results/
msal.json
msal.cache
artifacts/
.vs/

#Verify
*.received.*
backend/FwLite/FwLiteShared/wwwroot/viewer

*.csproj.user
11 changes: 10 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@
"request": "launch",
"projectPath": "${workspaceFolder}/backend/FwLite/LocalWebApp/LocalWebApp.csproj"
},
{
"name": "FwLite Blazor",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "Build FwLite Blazor (Windows)",
"program": "${workspaceFolder}/backend/FwLite/FwLiteDesktop/bin/Debug/net9.0-windows10.0.19041.0/win10-x64/FwLiteDesktop.exe",
"cwd": "${workspaceFolder}/backend/FwLite/FwLiteDesktop",
"stopAtEntry": false,
},
{
"name": ".NET Core Launch (web)",
"type": "coreclr",
Expand All @@ -21,7 +30,7 @@
"stopAtEntry": false,
"serverReadyAction": {
"action": "openExternally",
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
"pattern": "/\bNow listening on:\\s+(https?://\\S+)"
},
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
Expand Down
14 changes: 13 additions & 1 deletion .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Build FwLite Blazor (Windows)",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/backend/FwLite/FwLiteDesktop/FwLiteDesktop.csproj",
"--framework",
"net9.0-windows10.0.19041.0",
],
"problemMatcher": "$msCompile"
},
{
"label": "build",
"command": "dotnet",
Expand Down Expand Up @@ -38,4 +50,4 @@
"problemMatcher": "$msCompile"
}
]
}
}
3 changes: 2 additions & 1 deletion FwLiteOnly.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@
"backend\\FwLite\\LcmCrdt.Tests\\LcmCrdt.Tests.csproj",
"backend\\FWLite\\LocalWebApp\\LocalWebApp.csproj",
"backend\\FwLite\\FwLiteDesktop\\FwLiteDesktop.csproj",
"backend\\FwLite\\FwLiteShared\\FwLiteShared.csproj",
"backend\\FWLite\\FwDataMiniLcmBridge\\FwDataMiniLcmBridge.csproj",
"backend\\FwLite\\FwDataMiniLcmBridge.Tests\\FwDataMiniLcmBridge.Tests.csproj",
"backend\\FWLite\\FwLiteProjectSync\\FwLiteProjectSync.csproj",
"backend\\FWLite\\FwLiteProjectSync.Tests\\FwLiteProjectSync.Tests.csproj",
"backend\\LfClassicData\\LfClassicData.csproj",
]
}
}
}
2 changes: 1 addition & 1 deletion backend/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
</PropertyGroup>
<PropertyGroup>
<InformationalVersion>dev</InformationalVersion>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework Condition="'$(TargetFramework)' == ''">net9.0</TargetFramework>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
Expand Down
4 changes: 2 additions & 2 deletions backend/FwHeadless/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ static async Task<Results<Ok<SyncResult>, NotFound, ProblemHttpResult>> ExecuteM
}

static async Task<Results<Ok<ProjectSyncStatus>, NotFound>> GetMergeStatus(
ProjectContext projectContext,
CurrentProjectService projectContext,
ProjectLookupService projectLookupService,
SyncJobStatusService syncJobStatusService,
IServiceProvider services,
Expand All @@ -139,7 +139,7 @@ static async Task<Results<Ok<ProjectSyncStatus>, 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
Expand Down
3 changes: 1 addition & 2 deletions backend/FwHeadless/Services/ProjectContextFromIdService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ public async Task PopulateProjectContext(HttpContext context, Func<Task> next)
if (File.Exists(crdtFile))
{
var project = new CrdtProject("crdt", crdtFile);
projectsService.SetProjectScope(project);
await context.RequestServices.GetRequiredService<CurrentProjectService>().PopulateProjectDataCache();
await context.RequestServices.GetRequiredService<CurrentProjectService>().SetupProjectContext(project);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

namespace FwDataMiniLcmBridge.Tests.Fixtures;

public class MockFwProjectList(IOptions<FwDataBridgeConfig> config, MockFwProjectLoader loader) : FieldWorksProjectList(config)
public class MockFwProjectList(IOptions<FwDataBridgeConfig> config, MockFwProjectLoader loader,
FwDataFactory fwDataFactory) : FieldWorksProjectList(config, fwDataFactory)
{
public override IEnumerable<IProjectIdentifier> EnumerateProjects()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,12 @@ public ProjectLoaderFixture()
_config = provider.GetRequiredService<IOptions<FwDataBridgeConfig>>();
}

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()
Expand Down
20 changes: 19 additions & 1 deletion backend/FwLite/FwDataMiniLcmBridge/FieldWorksProjectList.cs
Original file line number Diff line number Diff line change
@@ -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<FwDataBridgeConfig> config)
public class FieldWorksProjectList(IOptions<FwDataBridgeConfig> config, FwDataFactory fwDataFactory) : IProjectProvider
{
public ProjectDataFormat DataFormat => ProjectDataFormat.FwData;
protected readonly IOptions<FwDataBridgeConfig> _config = config;
IEnumerable<IProjectIdentifier> IProjectProvider.ListProjects()
{
return EnumerateProjects();
}

IProjectIdentifier? IProjectProvider.GetProject(string name)
{
return GetProject(name);
}

public virtual IEnumerable<IProjectIdentifier> EnumerateProjects()
{
Expand All @@ -24,4 +36,10 @@ public virtual IEnumerable<IProjectIdentifier> EnumerateProjects()
{
return EnumerateProjects().OfType<FwDataProject>().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);
}
}
12 changes: 10 additions & 2 deletions backend/FwLite/FwDataMiniLcmBridge/FwDataBridgeKernel.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using FwDataMiniLcmBridge.LcmUtils;
using Microsoft.Extensions.DependencyInjection;
using MiniLcm;
using MiniLcm.Project;
using MiniLcm.Validators;

namespace FwDataMiniLcmBridge;
Expand All @@ -15,10 +16,17 @@ public static IServiceCollection AddFwDataBridge(this IServiceCollection service
services.AddOptions<FwDataBridgeConfig>().BindConfiguration("FwDataBridge");
services.AddSingleton<FwDataFactory>();
services.AddSingleton<FieldWorksProjectList>();
services.AddSingleton<IProjectProvider>(s => s.GetRequiredService<FieldWorksProjectList>());
services.AddSingleton<IProjectLoader, ProjectLoader>();
services.AddKeyedScoped<IMiniLcmApi>(FwDataApiKey, (provider, o) => provider.GetRequiredService<FwDataFactory>().GetCurrentFwDataMiniLcmApi(true));
services.AddKeyedScoped<IMiniLcmApi>(FwDataApiKey,
(provider, o) =>
{
var projectList = provider.GetRequiredService<FieldWorksProjectList>();
var projectContext = provider.GetRequiredService<FwDataProjectContext>();
return projectList.OpenProject(projectContext.Project ?? throw new InvalidOperationException("No project is set in the context."));
});
services.AddMiniLcmValidators();
services.AddSingleton<FwDataProjectContext>();
services.AddScoped<FwDataProjectContext>();
return services;
}
}
Loading

0 comments on commit 4917ae3

Please sign in to comment.