diff --git a/backend/FwLite/FwLiteDesktop/App.xaml.cs b/backend/FwLite/FwLiteDesktop/App.xaml.cs
index db4ab86a6..7c66fda27 100644
--- a/backend/FwLite/FwLiteDesktop/App.xaml.cs
+++ b/backend/FwLite/FwLiteDesktop/App.xaml.cs
@@ -2,10 +2,10 @@
public partial class App : Application
{
- public App()
+ public App(MainPage mainPage)
{
InitializeComponent();
- MainPage = new AppShell();
+ MainPage = mainPage;
}
}
diff --git a/backend/FwLite/FwLiteDesktop/AppShell.xaml b/backend/FwLite/FwLiteDesktop/AppShell.xaml
deleted file mode 100644
index fd1aa4386..000000000
--- a/backend/FwLite/FwLiteDesktop/AppShell.xaml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
diff --git a/backend/FwLite/FwLiteDesktop/AppShell.xaml.cs b/backend/FwLite/FwLiteDesktop/AppShell.xaml.cs
deleted file mode 100644
index 16f8c1aaa..000000000
--- a/backend/FwLite/FwLiteDesktop/AppShell.xaml.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace FwLiteDesktop;
-
-public partial class AppShell : Shell
-{
- public AppShell()
- {
- InitializeComponent();
- }
-}
diff --git a/backend/FwLite/FwLiteDesktop/MainPage.xaml b/backend/FwLite/FwLiteDesktop/MainPage.xaml
index 024cbab1d..dca7fed91 100644
--- a/backend/FwLite/FwLiteDesktop/MainPage.xaml
+++ b/backend/FwLite/FwLiteDesktop/MainPage.xaml
@@ -1,14 +1,8 @@
-
+
-
-
-
-
-
+
diff --git a/backend/FwLite/FwLiteDesktop/MainPage.xaml.cs b/backend/FwLite/FwLiteDesktop/MainPage.xaml.cs
index b72e7e649..014c647dc 100644
--- a/backend/FwLite/FwLiteDesktop/MainPage.xaml.cs
+++ b/backend/FwLite/FwLiteDesktop/MainPage.xaml.cs
@@ -1,10 +1,17 @@
-namespace FwLiteDesktop;
+using Microsoft.Extensions.Options;
+
+namespace FwLiteDesktop;
public partial class MainPage : ContentPage
{
- public MainPage()
+ public MainPage(IOptionsMonitor options)
{
InitializeComponent();
+ options.OnChange(o =>
+ {
+ webView.Dispatcher.Dispatch(() => webView.Source = o.Url);
+ });
+ webView.Source = options.CurrentValue.Url;
}
}
diff --git a/backend/FwLite/FwLiteDesktop/MauiProgram.cs b/backend/FwLite/FwLiteDesktop/MauiProgram.cs
index 19cbe534d..c92970feb 100644
--- a/backend/FwLite/FwLiteDesktop/MauiProgram.cs
+++ b/backend/FwLite/FwLiteDesktop/MauiProgram.cs
@@ -1,4 +1,8 @@
-using Microsoft.Extensions.Logging;
+using FwLiteDesktop.ServerBridge;
+using LocalWebApp;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using Microsoft.Maui.LifecycleEvents;
namespace FwLiteDesktop;
@@ -15,17 +19,19 @@ public static MauiApp CreateMauiApp()
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
+ builder.Services.AddSingleton();
- builder.Services.AddOptions().Configure(config =>
- {
- config.Url = "http://localhost:5000";
- }
- );
+ var serverManager = new ServerManager();
+ builder.Services.AddSingleton(serverManager);
+ builder.Configuration.Add(source => source.ServerManager = serverManager);
+ builder.Services.AddOptions().BindConfiguration("LocalWebApp");
#if DEBUG
builder.Logging.AddDebug();
#endif
- return builder.Build();
+ var app = builder.Build();
+ app.Services.GetRequiredService().Start();
+ return app;
}
}
diff --git a/backend/FwLite/FwLiteDesktop/Properties/launchSettings.json b/backend/FwLite/FwLiteDesktop/Properties/launchSettings.json
index bccb9bf16..1f221c031 100644
--- a/backend/FwLite/FwLiteDesktop/Properties/launchSettings.json
+++ b/backend/FwLite/FwLiteDesktop/Properties/launchSettings.json
@@ -3,7 +3,7 @@
"Run": {
"commandName": "Project",
"environmentVariables": {
- "COREHOST_TRACE": "1"
+ "COREHOST_TRACE": "0"
}
},
"Windows Machine": {
diff --git a/backend/FwLite/FwLiteDesktop/ServerBridge/ServerConfigProvider.cs b/backend/FwLite/FwLiteDesktop/ServerBridge/ServerConfigProvider.cs
new file mode 100644
index 000000000..634f62d38
--- /dev/null
+++ b/backend/FwLite/FwLiteDesktop/ServerBridge/ServerConfigProvider.cs
@@ -0,0 +1,32 @@
+using Microsoft.Extensions.Configuration;
+
+namespace FwLiteDesktop.ServerBridge;
+
+public class ServerConfigSource: IConfigurationSource
+{
+ public ServerManager? ServerManager { get; set; }
+
+ public IConfigurationProvider Build(IConfigurationBuilder builder)
+ {
+ if (ServerManager is null)
+ throw new InvalidOperationException("ServerManager is not set");
+ return new ServerConfigProvider(ServerManager);
+ }
+}
+
+public class ServerConfigProvider : ConfigurationProvider
+{
+ public ServerConfigProvider(ServerManager serverManager)
+ {
+ _ = serverManager.Started.ContinueWith(t =>
+ {
+ Data = new Dictionary { ["LocalWebApp:Url"] = t.Result.Urls.First() };
+ OnReload();
+ },
+ scheduler: TaskScheduler.Default);
+ }
+
+ public override void Load()
+ {
+ }
+}
diff --git a/backend/FwLite/FwLiteDesktop/ServerBridge/ServerManager.cs b/backend/FwLite/FwLiteDesktop/ServerBridge/ServerManager.cs
new file mode 100644
index 000000000..8835a6586
--- /dev/null
+++ b/backend/FwLite/FwLiteDesktop/ServerBridge/ServerManager.cs
@@ -0,0 +1,38 @@
+using LocalWebApp;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.Hosting;
+
+namespace FwLiteDesktop;
+
+public class ServerManager : IAsyncDisposable
+{
+ private readonly TaskCompletionSource _started = new();
+ public Task Started => _started.Task;
+ private WebApplication? _webApp;
+
+ public void Start()
+ {
+ _webApp = LocalWebAppServer.SetupAppServer([]);
+ _ = Task.Run(async () =>
+ {
+ try
+ {
+ await _webApp.StartAsync();
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e);
+ throw;
+ }
+
+ _started.SetResult(_webApp);
+ });
+ }
+
+ public async ValueTask DisposeAsync()
+ {
+ if (_webApp is null) return;
+ await _webApp.StopAsync(TimeSpan.FromSeconds(10));
+ await _webApp.DisposeAsync();
+ }
+}
diff --git a/backend/FwLite/LocalWebApp/LocalWebAppServer.cs b/backend/FwLite/LocalWebApp/LocalWebAppServer.cs
new file mode 100644
index 000000000..4eba3deda
--- /dev/null
+++ b/backend/FwLite/LocalWebApp/LocalWebAppServer.cs
@@ -0,0 +1,100 @@
+#if !DISABLE_FW_BRIDGE
+using FwDataMiniLcmBridge;
+using FwDataMiniLcmBridge.LcmUtils;
+#endif
+using LcmCrdt;
+using LocalWebApp;
+using LocalWebApp.Hubs;
+using LocalWebApp.Auth;
+using LocalWebApp.Routes;
+using LocalWebApp.Utils;
+using Microsoft.AspNetCore.SignalR;
+using Microsoft.AspNetCore.StaticFiles.Infrastructure;
+using Microsoft.Extensions.FileProviders;
+
+namespace LocalWebApp;
+
+public static class LocalWebAppServer
+{
+ public static WebApplication SetupAppServer(string[] args)
+ {
+ var builder = WebApplication.CreateBuilder(args);
+ if (!builder.Environment.IsDevelopment())
+ builder.WebHost.UseUrls("http://127.0.0.1:0");
+ if (builder.Environment.IsDevelopment())
+ {
+#if !DISABLE_FW_BRIDGE
+ //do this early so we catch bugs on startup
+ ProjectLoader.Init();
+#endif
+ }
+
+ builder.ConfigureDev(config =>
+ config.DefaultAuthority = new("https://lexbox.dev.languagetechnology.org"));
+//for now prod builds will also use lt dev until we deploy oauth to prod
+ builder.ConfigureProd(config =>
+ config.DefaultAuthority = new("https://lexbox.dev.languagetechnology.org"));
+ builder.Services.Configure(c => c.ClientId = "becf2856-0690-434b-b192-a4032b72067f");
+
+ builder.Services.AddLocalAppServices(builder.Environment);
+ builder.Services.AddEndpointsApiExplorer();
+ builder.Services.AddSwaggerGen();
+ builder.Services.AddSignalR(options =>
+ {
+ options.AddFilter(new LockedProjectFilter());
+ options.EnableDetailedErrors = true;
+ }).AddJsonProtocol();
+
+ var app = builder.Build();
+// Configure the HTTP request pipeline.
+ if (app.Environment.IsDevelopment())
+ {
+ app.UseSwagger();
+ app.UseSwaggerUI();
+ }
+
+//configure dotnet to serve static files from the embedded resources
+ var sharedOptions =
+ new SharedOptions() { FileProvider = new ManifestEmbeddedFileProvider(typeof(Program).Assembly) };
+ app.UseDefaultFiles(new DefaultFilesOptions(sharedOptions));
+ var staticFileOptions = new StaticFileOptions(sharedOptions);
+ app.UseStaticFiles(staticFileOptions);
+
+ app.Use(async (context, next) =>
+ {
+ 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();
+ }
+#if !DISABLE_FW_BRIDGE
+ var fwData = context.GetFwDataName();
+ if (!string.IsNullOrWhiteSpace(fwData))
+ {
+ var fwDataProjectContext = context.RequestServices.GetRequiredService();
+ fwDataProjectContext.Project =
+ FieldWorksProjectList.GetProject(fwData) ?? throw new InvalidOperationException($"FwData {fwData} not found");
+ }
+#endif
+
+ await next(context);
+ });
+ app.MapHub($"/api/hub/{{{CrdtMiniLcmApiHub.ProjectRouteKey}}}/lexbox");
+#if !DISABLE_FW_BRIDGE
+app.MapHub($"/api/hub/{{{FwDataMiniLcmHub.ProjectRouteKey}}}/fwdata");
+#endif
+ app.MapHistoryRoutes();
+ app.MapActivities();
+ app.MapProjectRoutes();
+ app.MapFwIntegrationRoutes();
+ app.MapTest();
+ app.MapImport();
+ app.MapAuthRoutes();
+ app.MapFallbackToFile("index.html", staticFileOptions);
+ return app;
+ }
+}
diff --git a/backend/FwLite/LocalWebApp/Program.cs b/backend/FwLite/LocalWebApp/Program.cs
index 928c4277e..99f815b90 100644
--- a/backend/FwLite/LocalWebApp/Program.cs
+++ b/backend/FwLite/LocalWebApp/Program.cs
@@ -1,89 +1,7 @@
-#if !DISABLE_FW_BRIDGE
-using FwDataMiniLcmBridge;
-using FwDataMiniLcmBridge.LcmUtils;
-#endif
-using LcmCrdt;
-using LocalWebApp;
-using LocalWebApp.Hubs;
-using LocalWebApp.Auth;
-using LocalWebApp.Routes;
-using LocalWebApp.Utils;
-using Microsoft.AspNetCore.SignalR;
-using Microsoft.AspNetCore.StaticFiles.Infrastructure;
-using Microsoft.Extensions.FileProviders;
-
-var builder = WebApplication.CreateBuilder(args);
-if (!builder.Environment.IsDevelopment())
- builder.WebHost.UseUrls("http://127.0.0.1:0");
-if (builder.Environment.IsDevelopment())
-{
- #if !DISABLE_FW_BRIDGE
- //do this early so we catch bugs on startup
- ProjectLoader.Init();
- #endif
-}
-builder.ConfigureDev(config => config.DefaultAuthority = new("https://lexbox.dev.languagetechnology.org"));
-//for now prod builds will also use lt dev until we deploy oauth to prod
-builder.ConfigureProd(config => config.DefaultAuthority = new("https://lexbox.dev.languagetechnology.org"));
-builder.Services.Configure(c => c.ClientId = "becf2856-0690-434b-b192-a4032b72067f");
-
-builder.Services.AddLocalAppServices(builder.Environment);
-builder.Services.AddEndpointsApiExplorer();
-builder.Services.AddSwaggerGen();
-builder.Services.AddSignalR(options =>
-{
- options.AddFilter(new LockedProjectFilter());
- options.EnableDetailedErrors = true;
-}).AddJsonProtocol();
-
-var app = builder.Build();
-// Configure the HTTP request pipeline.
-if (app.Environment.IsDevelopment())
-{
- app.UseSwagger();
- app.UseSwaggerUI();
-}
-//configure dotnet to serve static files from the embedded resources
-var sharedOptions = new SharedOptions() { FileProvider = new ManifestEmbeddedFileProvider(typeof(Program).Assembly) };
-app.UseDefaultFiles(new DefaultFilesOptions(sharedOptions));
-var staticFileOptions = new StaticFileOptions(sharedOptions);
-app.UseStaticFiles(staticFileOptions);
-
-app.Use(async (context, next) =>
-{
- 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();
- }
- #if !DISABLE_FW_BRIDGE
- var fwData = context.GetFwDataName();
- if (!string.IsNullOrWhiteSpace(fwData))
- {
- var fwDataProjectContext = context.RequestServices.GetRequiredService();
- fwDataProjectContext.Project = FieldWorksProjectList.GetProject(fwData) ?? throw new InvalidOperationException($"FwData {fwData} not found");
- }
- #endif
-
- await next(context);
-});
-app.MapHub($"/api/hub/{{{CrdtMiniLcmApiHub.ProjectRouteKey}}}/lexbox");
-#if !DISABLE_FW_BRIDGE
-app.MapHub($"/api/hub/{{{FwDataMiniLcmHub.ProjectRouteKey}}}/fwdata");
-#endif
-app.MapHistoryRoutes();
-app.MapActivities();
-app.MapProjectRoutes();
-app.MapFwIntegrationRoutes();
-app.MapTest();
-app.MapImport();
-app.MapAuthRoutes();
-app.MapFallbackToFile("index.html", staticFileOptions);
+using LocalWebApp;
+var app = LocalWebAppServer.SetupAppServer(args);
await using (app)
{
await app.StartAsync();