diff --git a/MakerPrompt.Shared/Components/ControlPanel.razor b/MakerPrompt.Shared/Components/ControlPanel.razor index 3f70082..85ce554 100644 --- a/MakerPrompt.Shared/Components/ControlPanel.razor +++ b/MakerPrompt.Shared/Components/ControlPanel.razor @@ -66,6 +66,8 @@
@* ── Left: Position + Webcam ── *@ + @if (SupportsDirectControl) + {
@@ -144,6 +146,15 @@
+ } + else + { +
+
+ +
+
+ } @* ── Right: Temps + Extrude + Speed/Flow + Calibration ── *@
@@ -155,18 +166,21 @@ @Localizer[Resources.ControlPanel_Heating]
- @* Preheat presets *@ -
- @foreach (var (label, hotend, bed) in PreheatProfiles) - { - var h = hotend; var b = bed; - - } -
+ @* Preheat presets (control-capable backends only) *@ + @if (SupportsDirectControl) + { +
+ @foreach (var (label, hotend, bed) in PreheatProfiles) + { + var h = hotend; var b = bed; + + } +
+ } @* Hotend *@
@@ -183,11 +197,14 @@
60 ? "bg-warning" : "bg-secondary")" style="width:@($"{Math.Min(Telemetry.HotendTemp / 300.0 * 100, 100):F0}%")">
-
- - °C - -
+ @if (SupportsDirectControl) + { +
+ + °C + +
+ }
@* Bed *@
@@ -204,11 +221,14 @@
-
- - °C - -
+ @if (SupportsDirectControl) + { +
+ + °C + +
+ }
@* Chamber temp (shown when data is available from printer) *@ @if (Telemetry.ChamberTemp > 0 || Telemetry.ChamberTarget > 0) @@ -241,16 +261,21 @@
-
- - % - -
+ @if (SupportsDirectControl) + { +
+ + % + +
+ }
- @* Extrude *@ + @* Extrude (direct-control backends only) *@ + @if (SupportsDirectControl) + {
@@ -283,6 +308,7 @@
+ } @* Speed / Flow *@
@@ -297,26 +323,34 @@ @Localizer[Resources.ControlPanel_PrintSpeed] @Telemetry.FeedRate% -
- - % -
+ @if (SupportsDirectControl) + { +
+ + % +
+ }
-
- - % -
+ @if (SupportsDirectControl) + { +
+ + % +
+ }
- @* Calibration *@ + @* Calibration (direct-control backends only) *@ + @if (SupportsDirectControl) + {
@@ -326,6 +360,45 @@
+ } + + @* Printer Queue (Moonraker only) *@ + @if (SupportsPrinterQueue) + { +
+
+ + @Localizer[Resources.ControlPanel_PrinterQueue] + +
+
+ @if (!_printerQueue.Any()) + { +
+ +

@Localizer[Resources.ControlPanel_PrinterQueueEmpty]

+
+ } + else + { +
+ @foreach (var job in _printerQueue) + { +
+
+ + @job.FileName + @DateTimeOffset.FromUnixTimeSeconds((long)job.TimeAdded).ToLocalTime().ToString("HH:mm") +
+
+ } +
+ } +
+
+ } @* ── File storage ── *@ @@ -346,6 +419,11 @@ private int printSpeed = 100; private int printFlow = 100; + private List _printerQueue = []; + + private bool SupportsDirectControl => PrinterServiceFactory.Current?.SupportsDirectControl ?? true; + private bool SupportsPrinterQueue => PrinterServiceFactory.Current?.SupportsPrinterQueue ?? false; + private static readonly (string Label, int Hotend, int Bed)[] PreheatProfiles = [ ("PLA", 200, 60), @@ -460,6 +538,33 @@ return printer?.SetPrintFlow(printFlow) ?? Task.CompletedTask; } + protected override void OnInitialized() + { + base.OnInitialized(); + if (SupportsPrinterQueue) + _ = SafeRefreshPrinterQueueAsync(); + } + + protected override void HandleConnectionChanged(object? sender, bool connected) + { + base.HandleConnectionChanged(sender, connected); + if (connected && SupportsPrinterQueue) + _ = SafeRefreshPrinterQueueAsync(); + } + + private async Task RefreshPrinterQueueAsync() + { + if (PrinterServiceFactory.Current is { SupportsPrinterQueue: true } printer) + { + _printerQueue = await printer.GetPrinterQueueAsync(); + await InvokeAsync(StateHasChanged); + } + } + + // Wraps RefreshPrinterQueueAsync with error handling for fire-and-forget calls + private Task SafeRefreshPrinterQueueAsync() => + RunAsync(RefreshPrinterQueueAsync, Localizer[Resources.ControlPanel_PrinterQueue]); + private readonly PrinterTelemetry fallbackTelemetry = new(); private PrinterTelemetry Telemetry => PrinterServiceFactory.Current?.LastTelemetry ?? fallbackTelemetry; } diff --git a/MakerPrompt.Shared/Components/PrinterConnectionModal.razor b/MakerPrompt.Shared/Components/PrinterConnectionModal.razor index 0f0b8ad..9509666 100644 --- a/MakerPrompt.Shared/Components/PrinterConnectionModal.razor +++ b/MakerPrompt.Shared/Components/PrinterConnectionModal.razor @@ -67,6 +67,20 @@

@localizer[Resources.NavConnection_DemoServiceDescription]

} + else if (_editConnectionType == PrinterConnectionType.PrusaConnect) + { +
+

@localizer[Resources.NavConnect_PrusaConnectDescription]

+
+
+ + +
+
+ + +
+ } else {
@@ -165,11 +179,9 @@ private PrinterConnectionType _editConnectionType = PrinterConnectionType.Demo; private List _availablePorts = new(); private readonly List BaudRates = [9600, 19200, 38400, 57600, 115200, 250000]; - // PrusaConnect uses the fleet-picker flow (PrusaConnectProvider), not this modal. + // PrusaConnect uses UUID + Bearer token from the mobile API. private static readonly PrinterConnectionType[] _connectionTypes = - Enum.GetValues() - .Where(t => t != PrinterConnectionType.PrusaConnect) - .ToArray(); + Enum.GetValues().ToArray(); public async Task ShowAddAsync() { @@ -225,7 +237,10 @@ } // Validate URL for API-based backends before attempting anything. - if (_editConnectionType != PrinterConnectionType.Serial && _editConnectionType != PrinterConnectionType.Demo + // PrusaConnect uses UUID + API key, not a URL — skip URL validation for it. + if (_editConnectionType != PrinterConnectionType.Serial + && _editConnectionType != PrinterConnectionType.Demo + && _editConnectionType != PrinterConnectionType.PrusaConnect && string.IsNullOrWhiteSpace(_editApiSettings.Url)) { _connectionError = "URL is required. Enter the printer\'s IP address or hostname (e.g. http://192.168.1.100)."; diff --git a/MakerPrompt.Shared/Infrastructure/IPrinterCommunicationService.cs b/MakerPrompt.Shared/Infrastructure/IPrinterCommunicationService.cs index 0ddcb7a..172599e 100644 --- a/MakerPrompt.Shared/Infrastructure/IPrinterCommunicationService.cs +++ b/MakerPrompt.Shared/Infrastructure/IPrinterCommunicationService.cs @@ -11,11 +11,29 @@ public interface IPrinterCommunicationService : IAsyncDisposable bool IsConnected { get; } bool IsPrinting { get; } + /// + /// True when the backend supports direct printer control commands + /// (temperature set, motion, extrusion, fan, etc.). + /// Monitoring-only backends (PrusaLink, PrusaConnect) return false. + /// + bool SupportsDirectControl => true; + + /// + /// True when the backend exposes a queryable printer-side print queue. + /// Currently only Moonraker supports this. + /// + bool SupportsPrinterQueue => false; + Task ConnectAsync(PrinterConnectionSettings connectionSettings); Task DisconnectAsync(); Task WriteDataAsync(string command); Task GetPrinterTelemetryAsync(); Task> GetFilesAsync(); + /// + /// Returns the printer-side print queue entries. Returns an empty list + /// for backends that do not support a print queue ( is false). + /// + Task> GetPrinterQueueAsync() => Task.FromResult(new List()); Task SetHotendTemp(int targetTemp = 0); Task SetBedTemp(int targetTemp = 0); Task Home(bool x = true, bool y = true, bool z = true); diff --git a/MakerPrompt.Shared/Layout/MainLayout.razor b/MakerPrompt.Shared/Layout/MainLayout.razor index b336e13..1eccdbe 100644 --- a/MakerPrompt.Shared/Layout/MainLayout.razor +++ b/MakerPrompt.Shared/Layout/MainLayout.razor @@ -14,7 +14,7 @@ @ConfigService.Configuration.FarmName }
- +
diff --git a/MakerPrompt.Shared/Layout/NavMenu.razor b/MakerPrompt.Shared/Layout/NavMenu.razor index 9d3a082..9162b13 100644 --- a/MakerPrompt.Shared/Layout/NavMenu.razor +++ b/MakerPrompt.Shared/Layout/NavMenu.razor @@ -12,6 +12,12 @@ @Resources.PageTitle_Fleet + } else { diff --git a/MakerPrompt.Shared/Pages/ProjectHub.razor b/MakerPrompt.Shared/Pages/ProjectHub.razor new file mode 100644 index 0000000..b83a1f9 --- /dev/null +++ b/MakerPrompt.Shared/Pages/ProjectHub.razor @@ -0,0 +1,20 @@ +@page "/projecthub" +@inject IAppConfigurationService ConfigService +@inject NavigationManager Navigation + + + +
+ +
+ +@code { + protected override async Task OnInitializedAsync() + { + await ConfigService.InitializeAsync(); + if (!ConfigService.Configuration.FarmModeEnabled) + { + Navigation.NavigateTo("/dashboard", replace: true); + } + } +} diff --git a/MakerPrompt.Shared/Properties/Resources.Designer.cs b/MakerPrompt.Shared/Properties/Resources.Designer.cs index 152196a..c0dbdaf 100644 --- a/MakerPrompt.Shared/Properties/Resources.Designer.cs +++ b/MakerPrompt.Shared/Properties/Resources.Designer.cs @@ -671,6 +671,18 @@ public static string ControlPanel_FanSpeed { return ResourceManager.GetString("ControlPanel_FanSpeed", resourceCulture); } } + + public static string ControlPanel_PrinterQueue { + get { + return ResourceManager.GetString("ControlPanel_PrinterQueue", resourceCulture); + } + } + + public static string ControlPanel_PrinterQueueEmpty { + get { + return ResourceManager.GetString("ControlPanel_PrinterQueueEmpty", resourceCulture); + } + } /// /// Looks up a localized string similar to Heating. @@ -1958,9 +1970,15 @@ public static string PageTitle_Fleet { return ResourceManager.GetString("PageTitle_Fleet", resourceCulture); } } + + public static string PageTitle_ProjectHub { + get { + return ResourceManager.GetString("PageTitle_ProjectHub", resourceCulture); + } + } /// - /// Looks up a localized string similar to Print Queue. + /// Looks up a localized string similar to Project Hub. /// public static string PrintQueue_Title { get { diff --git a/MakerPrompt.Shared/Properties/Resources.resx b/MakerPrompt.Shared/Properties/Resources.resx index ae4d6df..791aeaf 100644 --- a/MakerPrompt.Shared/Properties/Resources.resx +++ b/MakerPrompt.Shared/Properties/Resources.resx @@ -483,6 +483,12 @@ Fan speed + + Printer Queue + + + No jobs in printer queue + Home all @@ -750,8 +756,11 @@ Fleet + + Project Hub + - Print Queue + Project Hub No files available. Connect a printer to browse files. diff --git a/MakerPrompt.Shared/Services/MoonrakerApiService.cs b/MakerPrompt.Shared/Services/MoonrakerApiService.cs index a95c3ac..a8fe3da 100644 --- a/MakerPrompt.Shared/Services/MoonrakerApiService.cs +++ b/MakerPrompt.Shared/Services/MoonrakerApiService.cs @@ -9,6 +9,7 @@ public class MoonrakerApiService : BasePrinterConnectionService, IPrinterCommuni private string _jwtToken = string.Empty; private string _refreshToken = string.Empty; public override PrinterConnectionType ConnectionType { get; } = PrinterConnectionType.Moonraker; + public bool SupportsPrinterQueue => true; public MoonrakerApiService() { @@ -291,6 +292,45 @@ public async Task> GetFilesAsync() }).ToList(); } + /// + /// Returns the current Moonraker print queue (queued job entries). + /// Returns an empty list when not connected or the API call fails. + /// + public async Task> GetPrinterQueueAsync() + { + if (!IsConnected) return []; + + try + { + var response = await Client.GetAsync("/server/print_queue", _cts.Token); + if (!response.IsSuccessStatusCode) return []; + + var json = await response.Content.ReadAsStringAsync(); + using var doc = JsonDocument.Parse(json); + if (!doc.RootElement.TryGetProperty("result", out var result)) return []; + if (!result.TryGetProperty("queued_jobs", out var jobs) || + jobs.ValueKind != JsonValueKind.Array) return []; + + var entries = new List(); + foreach (var job in jobs.EnumerateArray()) + { + entries.Add(new PrintQueueEntry + { + JobId = job.TryGetProperty("job_id", out var id) ? id.GetString() ?? string.Empty : string.Empty, + FileName = job.TryGetProperty("filename", out var fn) ? fn.GetString() ?? string.Empty : string.Empty, + TimeAdded = job.TryGetProperty("time_added", out var ta) ? ta.GetDouble() : 0, + }); + } + return entries; + } + catch + { + // Swallow API errors silently — the caller (ControlPanel) handles + // the empty-list case and uses RunAsync for user-facing error reporting. + return []; + } + } + public async Task OpenReadAsync(string fullPath, CancellationToken cancellationToken = default) { if (!IsConnected) return null; @@ -619,3 +659,14 @@ private sealed record WebcamEntry } } + +/// +/// Represents a single entry in the Moonraker print queue. +/// +public sealed class PrintQueueEntry +{ + public string JobId { get; init; } = string.Empty; + public string FileName { get; init; } = string.Empty; + /// Unix timestamp when the job was added to the queue. + public double TimeAdded { get; init; } +} diff --git a/MakerPrompt.Shared/Services/PrusaConnectApiService.cs b/MakerPrompt.Shared/Services/PrusaConnectApiService.cs index 60fbc35..4601626 100644 --- a/MakerPrompt.Shared/Services/PrusaConnectApiService.cs +++ b/MakerPrompt.Shared/Services/PrusaConnectApiService.cs @@ -33,6 +33,7 @@ public sealed class PrusaConnectApiService : BasePrinterConnectionService, IPrin private string? _printerUuid; public override PrinterConnectionType ConnectionType => PrinterConnectionType.PrusaConnect; + public bool SupportsDirectControl => false; public PrusaConnectApiService() { } diff --git a/MakerPrompt.Shared/Services/PrusaLinkApiService.cs b/MakerPrompt.Shared/Services/PrusaLinkApiService.cs index 3681ef9..97930e7 100644 --- a/MakerPrompt.Shared/Services/PrusaLinkApiService.cs +++ b/MakerPrompt.Shared/Services/PrusaLinkApiService.cs @@ -10,6 +10,7 @@ public class PrusaLinkApiService : BasePrinterConnectionService, IPrinterCommuni private Uri? _baseUri; public override PrinterConnectionType ConnectionType => PrinterConnectionType.PrusaLink; + public bool SupportsDirectControl => false; public PrusaLinkApiService() {