Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

apply feedback from user testing #1384

Merged
merged 14 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions backend/FwLite/FwDataMiniLcmBridge/LcmUtils/FwLink.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace FwDataMiniLcmBridge.LcmUtils;

public static class FwLink
{
public static string ToEntry(Guid entryId, string projectName)
{
return $"silfw://localhost/link?database={projectName}&tool=lexiconEdit&guid={entryId}";
}
}
34 changes: 21 additions & 13 deletions backend/FwLite/FwLiteMaui/FwLiteMauiKernel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,40 +20,48 @@ public static void AddFwLiteMauiServices(this IServiceCollection services,
services.AddSingleton<MainPage>();
configuration.AddJsonFile("appsettings.json", optional: true);

services.Configure<AuthConfig>(config =>
config.LexboxServers =
[
new(new("https://lexbox.dev.languagetechnology.org"), "Lexbox Dev"),
new(new("https://staging.languagedepot.org"), "Lexbox Staging")
]);

string environment = "Production";
#if DEBUG
environment = "Development";
services.AddBlazorWebViewDeveloperTools();
#endif
var env = new HostingEnvironment() { EnvironmentName = environment };
IHostEnvironment env = new HostingEnvironment() { EnvironmentName = environment };
services.AddSingleton<IHostEnvironment>(env);
services.AddFwLiteShared(env);
services.AddMauiBlazorWebView();
services.AddSingleton<HostedServiceAdapter>();
services.AddSingleton<IMauiInitializeService>(sp => sp.GetRequiredService<HostedServiceAdapter>());
services.Configure<AuthConfig>(config =>
{
List<LexboxServer> servers =
[
new(new("https://staging.languagedepot.org"), "Lexbox Staging")
];
if (env.IsDevelopment())
{
servers.Add(new(new("https://lexbox.dev.languagetechnology.org"), "Lexbox Dev"));
}

config.LexboxServers = servers.ToArray();
config.AfterLoginWebView = () =>
{
var window = Application.Current?.Windows.FirstOrDefault();
if (window is not null) Application.Current?.ActivateWindow(window);
};
});
#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);
services.AddSingleton<FwLiteShared.Services.IAppLauncher, FwLiteMaui.Services.AppLauncher>();
#endif
#if WINDOWS
services.AddFwLiteWindows(env);
#endif
#if ANDROID
services.Configure<AuthConfig>(config => config.ParentActivityOrWindow = Platform.CurrentActivity);
#endif
services.Configure<AuthConfig>(config => config.AfterLoginWebView = () =>
{
var window = Application.Current?.Windows.FirstOrDefault();
if (window is not null) Application.Current?.ActivateWindow(window);
});

services.Configure<FwLiteConfig>(config =>
{
config.AppVersion = AppVersion.Version;
Expand Down
24 changes: 23 additions & 1 deletion backend/FwLite/FwLiteMaui/Platforms/Windows/WindowsKernel.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,44 @@
using System.Runtime.InteropServices;
using FwLiteShared;
using FwLiteShared.Auth;
using Microsoft.Extensions.Hosting;
using Microsoft.Maui.Platform;

namespace FwLiteMaui;

public static class WindowsKernel
{

public static void AddFwLiteWindows(this IServiceCollection services, IHostEnvironment environment)
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return;
services.AddSingleton<IMauiInitializeService, AppUpdateService>();
services.AddSingleton<IMauiInitializeService, AppUpdateService>();
if (!FwLiteMauiKernel.IsPortableApp)
{
services.AddSingleton<IMauiInitializeService, WindowsShortcutService>();
}

services.Configure<AuthConfig>(config =>
{
config.AfterLoginWebView = () =>
{
var window = Application.Current?.Windows.FirstOrDefault()?.Handler?.PlatformView as Microsoft.UI.Xaml.Window;
if (window is null) throw new InvalidOperationException("Could not find window");
//note, window.Activate() does not work per https://github.com/microsoft/microsoft-ui-xaml/issues/7595
var hwnd = window.GetWindowHandle();
WindowHelper.SetForegroundWindow(hwnd);
};
});

services.Configure<FwLiteConfig>(config =>
{
config.UseDevAssets = environment.IsDevelopment();
});
}
}

public class WindowHelper
{
[DllImport("user32.dll")]
public static extern void SetForegroundWindow(IntPtr hWnd);
}
40 changes: 40 additions & 0 deletions backend/FwLite/FwLiteMaui/Services/AppLauncher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#if INCLUDE_FWDATA_BRIDGE
using FwDataMiniLcmBridge;
using FwDataMiniLcmBridge.LcmUtils;
using FwLiteShared.Services;
using Microsoft.JSInterop;

namespace FwLiteMaui.Services;

public class AppLauncher(FwDataFactory fwDataFactory, FieldWorksProjectList projectList) : IAppLauncher
{
private readonly ILauncher _launcher = Launcher.Default;

[JSInvokable]
public Task<bool> CanOpen(string uri)
{
return _launcher.CanOpenAsync(uri);
}

[JSInvokable]
public Task Open(string uri)
{
return _launcher.OpenAsync(uri);
}

[JSInvokable]
public Task<bool> TryOpen(string uri)
{
return _launcher.TryOpenAsync(uri);
}

[JSInvokable]
public Task<bool> OpenInFieldWorks(Guid entryId, string projectName)
{
var project = projectList.GetProject(projectName);
if (project is null) return Task.FromResult(false);
fwDataFactory.CloseProject(project);
return _launcher.TryOpenAsync(FwLink.ToEntry(entryId, projectName));
}
}
#endif
12 changes: 12 additions & 0 deletions backend/FwLite/FwLiteShared/Auth/OAuthClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,12 @@ private void InvalidateProjectCache()
{
_authResult = await _application.AcquireTokenSilent(DefaultScopes, account).ExecuteAsync();
}
catch (MsalUiRequiredException)
{
_logger.LogWarning("Ui required, logging out");
await _application.RemoveAsync(account);
_authResult = null;
}
catch (MsalClientException e) when (e.ErrorCode == "multiple_matching_tokens_detected")
{
_logger.LogWarning(e, "Multiple matching tokens detected, logging out");
Expand All @@ -161,6 +167,12 @@ await _application
.RemoveAsync(account); //todo might not be the best way to handle this, maybe it's a transient error?
_authResult = null;
}
catch (Exception e)
{
_logger.LogError(e, "Failed to acquire token silently");
await _application.RemoveAsync(account);
_authResult = null;
}

return _authResult;
}
Expand Down
1 change: 1 addition & 0 deletions backend/FwLite/FwLiteShared/FwLiteSharedKernel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

services.AddSingleton<ImportFwdataService>();
services.AddScoped<SyncService>();
services.AddScoped<ProjectServicesProvider>();
services.AddSingleton<LexboxProjectService>();
services.AddSingleton<CombinedProjectsService>();
//this is scoped so that there will be once instance per blazor circuit, this prevents issues where the same instance is used when reloading the page.
Expand Down Expand Up @@ -50,7 +51,7 @@
return new HttpClientHandler
{
ClientCertificateOptions = ClientCertificateOption.Manual,
ServerCertificateCustomValidationCallback = (message, certificate2, arg3, arg4) => true

Check warning on line 54 in backend/FwLite/FwLiteShared/FwLiteSharedKernel.cs

View workflow job for this annotation

GitHub Actions / Build FW Lite and run tests

This call site is reachable on all platforms. 'HttpClientHandler.ServerCertificateCustomValidationCallback' is unsupported on: 'browser'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)
};
});
}
Expand Down
9 changes: 5 additions & 4 deletions backend/FwLite/FwLiteShared/Layout/SvelteLayout.razor
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
@inject ILogger<SvelteLayout> Logger
@inject FwLiteProvider FwLiteProvider
@inject IOptions<FwLiteConfig> Config;
@inject ProjectServicesProvider ProjectServicesProvider;
@implements IAsyncDisposable
@if (useDevAssets)
{
Expand Down Expand Up @@ -47,7 +48,6 @@
@code {
private bool useDevAssets => Config.Value.UseDevAssets;
// private bool useDevAssets => false;
private IJSObjectReference? module;

protected override async Task OnAfterRenderAsync(bool firstRender)
{
Expand All @@ -59,20 +59,21 @@
await FwLiteProvider.SetService(JS, serviceKey, service);
}

await FwLiteProvider.SetService(JS, DotnetService.ProjectServicesProvider, ProjectServicesProvider);

if (useDevAssets)
{
module = await JS.InvokeAsync<IJSObjectReference>("import", "http://localhost:5173/src/main.ts");
await JS.InvokeAsync<IJSObjectReference>("import", "http://localhost:5173/src/main.ts");
} else
{
module = await JS.InvokeAsync<IJSObjectReference>("import",
await JS.InvokeAsync<IJSObjectReference>("import",
"/" + Assets["_content/FwLiteShared/viewer/main.js"]);
}
}
}

public async ValueTask DisposeAsync()

Check warning on line 75 in backend/FwLite/FwLiteShared/Layout/SvelteLayout.razor

View workflow job for this annotation

GitHub Actions / Build FW Lite and run tests

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
{
await (module?.DisposeAsync() ?? ValueTask.CompletedTask);
}

}
24 changes: 0 additions & 24 deletions backend/FwLite/FwLiteShared/Pages/CrdtProject.razor
Original file line number Diff line number Diff line change
@@ -1,32 +1,8 @@
@page "/project/{projectName}"
@using FwLiteShared.Layout
@using FwLiteShared.Services
@using Microsoft.Extensions.DependencyInjection
@inherits OwningComponentBaseAsync
@layout SvelteLayout;
@inject IJSRuntime JS;
@* injecting here means we get a provider scoped to the current circuit *@
@inject FwLiteProvider FwLiteProvider;

@code {

[Parameter]
public required string ProjectName { get; set; }

private IAsyncDisposable? _disposable;

protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender) return;
//scoped services here are per page render, meaning they will get cleaned up when the page is disposed
_disposable = await FwLiteProvider.InjectCrdtProject(JS, ScopedServices, ProjectName);
}

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();
}
}
21 changes: 0 additions & 21 deletions backend/FwLite/FwLiteShared/Pages/FwdataProject.razor
Original file line number Diff line number Diff line change
@@ -1,31 +1,10 @@
@page "/fwdata/{projectName}"
@using FwLiteShared.Layout
@using FwLiteShared.Services
@using Microsoft.Extensions.DependencyInjection
@inherits OwningComponentBaseAsync
@layout SvelteLayout;
@inject IJSRuntime JS;
@inject FwLiteProvider FwLiteProvider;

@code {

[Parameter]
public required string ProjectName { get; set; }

private IAsyncDisposable? _disposable;

protected override Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender) return Task.CompletedTask;
_disposable = FwLiteProvider.InjectFwDataProject(ScopedServices, ProjectName);
return Task.CompletedTask;
}

protected override async ValueTask DisposeAsyncCore()
{
await base.DisposeAsyncCore();
if (_disposable is not null)
await _disposable.DisposeAsync();
}

}
10 changes: 0 additions & 10 deletions backend/FwLite/FwLiteShared/Pages/Home.razor
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,5 @@
@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 OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
await FwLiteProvider.SetService(JS, DotnetService.MiniLcmApi, null);
}
}
Loading
Loading