Skip to content

Commit

Permalink
apply feedback from user testing (#1384)
Browse files Browse the repository at this point in the history
* prevent throwing any errors when trying to acquire an oauth token

* only show staging server in prod maui builds

* allow clicking on synced server projects to open them, change `Syncing with..` to `Synced with`

* prep history service to be called from frontend

* remove code clearing out MiniLcm service when home page is loaded

* rework project providers

* provide history service via project view

* don't throw an error when deleting an example or sense which hasn't been created yet

* replace Entry, Sense with Word and Definition in WeSay and LF views

* add close button to drawer

* fix open in flex on maui

* fix Toc links taking you home

* workaround bug in window.Activate not bringing the window to the front
  • Loading branch information
hahn-kev authored Jan 17, 2025
1 parent 0bcaab4 commit ae49973
Show file tree
Hide file tree
Showing 53 changed files with 689 additions and 307 deletions.
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 @@ public static IServiceCollection AddFwLiteShared(this IServiceCollection service

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
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 @@ else
@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 @@ else
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

0 comments on commit ae49973

Please sign in to comment.