Skip to content

Commit 68ab0d4

Browse files
committed
fix: fixes
1 parent 67172a9 commit 68ab0d4

37 files changed

Lines changed: 6972 additions & 264 deletions

‎MakerPrompt.MAUI/MauiProgram.cs‎

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Microsoft.Extensions.Logging;
44
using MakerPrompt.Shared.Utils;
55
using MakerPrompt.Shared.Services;
6+
using MakerPrompt.MAUI.Services;
67
using Microsoft.AspNetCore.Builder;
78
using MakerPrompt.MAUI.Services;
89
using MakerPrompt.MAUI.Storage;
@@ -23,15 +24,17 @@ public static MauiApp CreateMauiApp()
2324
});
2425

2526
builder.Services.AddMauiBlazorWebView();
27+
// Enable WebGL in the embedded WebView2 on Windows.
28+
// WebView2 silently disables WebGL for GPUs on its blocklist — bypassing that
29+
// is required for the GCode visual viewer (Three.js / WebGL canvas) to work.
30+
var webViewArgs = "--ignore-gpu-blocklist --enable-gpu-rasterization";
2631
#if DEBUG
27-
// Enable WebView2 remote debugging so E2E tests can connect via
28-
// Chrome DevTools Protocol (CDP) with Playwright.
29-
Environment.SetEnvironmentVariable(
30-
"WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS",
31-
"--remote-debugging-port=9222");
32+
// Append remote-debugging port so E2E tests can connect via CDP / Playwright.
33+
webViewArgs += " --remote-debugging-port=9222";
3234
builder.Services.AddBlazorWebViewDeveloperTools();
3335
builder.Logging.AddDebug();
3436
#endif
37+
Environment.SetEnvironmentVariable("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS", webViewArgs);
3538

3639
builder.Services.AddLocalization(options =>
3740
{
@@ -51,6 +54,8 @@ public static MauiApp CreateMauiApp()
5154
RestoreSavedCulture(supportedCultures);
5255

5356
builder.Services.RegisterMakerPromptSharedServices<AppConfigurationService, SerialService>();
57+
// Override the passthrough camera proxy with MAUI native HttpClient fetcher
58+
builder.Services.AddSingleton<ICameraProxyService, MauiCameraProxyService>();
5459
builder.Services.AddScoped<IAppLocalStorageProvider, MauiAppLocalStorageProvider>();
5560
// MAUI: Use AES-256-GCM encryption for stored credentials
5661
var deviceId = DeviceInfo.Current.Idiom.ToString();
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using MakerPrompt.Shared.Services;
2+
3+
namespace MakerPrompt.MAUI.Services
4+
{
5+
/// <summary>
6+
/// MAUI camera proxy: fetches snapshot images via the native HttpClient,
7+
/// bypassing the WebView's cross-origin restrictions, and returns them
8+
/// as base64 data URLs so the img tag renders without CORS issues.
9+
/// </summary>
10+
public sealed class MauiCameraProxyService : ICameraProxyService, IDisposable
11+
{
12+
private readonly HttpClient _http;
13+
14+
public MauiCameraProxyService()
15+
{
16+
_http = new HttpClient(new HttpClientHandler
17+
{
18+
// Allow HTTP (non-TLS) camera endpoints on local network
19+
ServerCertificateCustomValidationCallback = null
20+
})
21+
{
22+
Timeout = TimeSpan.FromSeconds(5)
23+
};
24+
}
25+
26+
public bool NativeProxyRequired => true;
27+
28+
public async Task<string?> FetchSnapshotAsDataUrlAsync(string url, CancellationToken ct = default)
29+
{
30+
try
31+
{
32+
var bytes = await _http.GetByteArrayAsync(url, ct).ConfigureAwait(false);
33+
return "data:image/jpeg;base64," + Convert.ToBase64String(bytes);
34+
}
35+
catch
36+
{
37+
return null;
38+
}
39+
}
40+
41+
public void Dispose() => _http.Dispose();
42+
}
43+
}

‎MakerPrompt.Shared/Components/ControlPanel.razor‎

Lines changed: 262 additions & 130 deletions
Large diffs are not rendered by default.

‎MakerPrompt.Shared/Components/CultureSelector.razor‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
<ul class="dropdown-menu dropdown-menu-end">
1212
@foreach (var culture in supportedCultures)
1313
{
14-
<li><a class="dropdown-item" @onclick="() => ApplySelectedCultureAsync(culture)">@culture.DisplayName</a></li>
14+
<li><a class="dropdown-item" @onclick="() => ApplySelectedCultureAsync(culture)">@culture.NativeName</a></li>
1515
}
1616
</ul>
1717
</li>

‎MakerPrompt.Shared/Components/FileExplorer.razor‎

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,16 @@
2222
<i class="bi bi-eye"></i><span> Open</span>
2323
}
2424
</button>
25-
<button class="btn btn-sm btn-outline-success" @onclick="CopySelectedToApp" disabled="@(selectedFile == null)">
26-
<i class="bi bi-arrow-left-square"></i> Copy to App
25+
<button class="btn btn-sm btn-outline-success" @onclick="CopySelectedToApp" disabled="@(selectedFile == null || _isCopying)">
26+
@if (_isCopying)
27+
{
28+
<span class="spinner-border spinner-border-sm me-1" role="status" aria-hidden="true"></span>
29+
<span>Copying…</span>
30+
}
31+
else
32+
{
33+
<i class="bi bi-arrow-left-square"></i><span> Copy to App</span>
34+
}
2735
</button>
2836
</div>
2937
</div>
@@ -78,6 +86,7 @@
7886
private List<FileEntry> _files = new();
7987
private bool _isLoading;
8088
private bool _isOpening;
89+
private bool _isCopying;
8190
private FileEntry? selectedFile;
8291

8392
protected override async Task OnInitializedAsync()
@@ -158,16 +167,26 @@
158167
return RunAsync(() => printer.StartPrint(file), "Failed to start print");
159168
}
160169

161-
private Task CopySelectedToApp()
170+
private async Task CopySelectedToApp()
162171
{
163-
if (selectedFile == null) return Task.CompletedTask;
172+
if (selectedFile == null) return;
164173
var file = selectedFile;
165-
return RunAsync(async () =>
174+
_isCopying = true;
175+
StateHasChanged();
176+
try
166177
{
167-
await using var stream = await PrinterStorage.OpenReadAsync(file.FullPath) ?? Stream.Null;
168-
if (stream == Stream.Null) return;
169-
await AppStorage.SaveFileAsync(Path.GetFileName(file.FullPath), stream);
170-
}, "Failed to copy file");
178+
await RunAsync(async () =>
179+
{
180+
await using var stream = await PrinterStorage.OpenReadAsync(file.FullPath) ?? Stream.Null;
181+
if (stream == Stream.Null) return;
182+
await AppStorage.SaveFileAsync(Path.GetFileName(file.FullPath), stream);
183+
}, "Failed to copy file");
184+
}
185+
finally
186+
{
187+
_isCopying = false;
188+
StateHasChanged();
189+
}
171190
}
172191

173192
private async Task OpenSelectedPrinter()

‎MakerPrompt.Shared/Components/GCodeViewer.razor‎

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,36 @@
4343
<div class="card-body d-flex flex-column overflow-hidden">
4444
@if (showVisual)
4545
{
46-
<div @ref="viewerContainer" id="gcode-container" class="flex-grow-1 w-100" style="min-height:0;"></div>
46+
@if (_viewerFailed)
47+
{
48+
<div class="alert alert-warning d-flex align-items-center mb-2">
49+
<i class="bi bi-exclamation-triangle-fill me-2"></i>
50+
Visual G-code preview is not available on this device. Switch to text view.
51+
</div>
52+
}
53+
<div class="position-relative flex-grow-1 w-100" style="min-height:0;">
54+
@if (_viewerLoading)
55+
{
56+
<div class="position-absolute top-0 start-0 w-100 h-100 d-flex justify-content-center align-items-center" style="z-index:1;">
57+
<div class="spinner-border text-secondary" role="status">
58+
<span class="visually-hidden">Loading viewer…</span>
59+
</div>
60+
</div>
61+
}
62+
<div @ref="viewerContainer" id="gcode-container" class="w-100 h-100" style="@(_viewerFailed ? "display:none;" : "")"></div>
63+
</div>
4764
}
4865
else
4966
{
67+
@if (IsGCodeTruncated)
68+
{
69+
<div class="alert alert-info d-flex align-items-center mb-1 py-1 small">
70+
<i class="bi bi-info-circle me-2"></i>
71+
Large file — showing first @(MaxTextChars / 1024) KB of @($"{GCodeDoc.CurrentGCode!.Length / 1024.0 / 1024.0:0.1}") MB
72+
</div>
73+
}
5074
<textarea class="form-control flex-grow-1" readonly style="font-family: monospace; white-space: pre; overflow:auto; min-height:0;">
51-
@GCodeDoc.CurrentGCode
75+
@GCodeDisplayText
5276
</textarea>
5377
}
5478
</div>
@@ -59,7 +83,14 @@
5983
private bool showVisual = false;
6084
// Only reinitialise when content or mode actually changes — prevents re-init on every telemetry poll.
6185
private bool _viewerNeedsReset = false;
86+
private bool _viewerFailed = false;
87+
private bool _viewerLoading = false;
6288
private string? _viewerGCode = null;
89+
// Cap text preview to avoid freezing the browser with very large files.
90+
private const int MaxTextChars = 500_000;
91+
private string? GCodeDisplayText => GCodeDoc.CurrentGCode?.Length > MaxTextChars
92+
? GCodeDoc.CurrentGCode.Substring(0, MaxTextChars) : GCodeDoc.CurrentGCode;
93+
private bool IsGCodeTruncated => (GCodeDoc.CurrentGCode?.Length ?? 0) > MaxTextChars;
6394

6495
private bool CanPrint => IsConnected && !string.IsNullOrWhiteSpace(GCodeDoc.CurrentGCode) && PrinterServiceFactory.Current != null;
6596

@@ -75,6 +106,8 @@
75106
{
76107
_viewerNeedsReset = false;
77108
_viewerGCode = GCodeDoc.CurrentGCode;
109+
_viewerLoading = true;
110+
StateHasChanged();
78111
try
79112
{
80113
await JS.InitializeViewerAsync(viewerContainer, GCodeDoc.CurrentGCode!);
@@ -85,16 +118,23 @@
85118
}
86119
catch (Exception)
87120
{
88-
// Swallow viewer errors silently — non-critical and surfacing them would spam toasts
89-
// on every re-render triggered by background telemetry polling.
121+
_viewerFailed = true;
122+
}
123+
finally
124+
{
125+
_viewerLoading = false;
126+
StateHasChanged();
90127
}
91128
}
92129
}
93130

94131
private void OnDocChanged()
95132
{
96133
if (GCodeDoc.CurrentGCode != _viewerGCode)
134+
{
97135
_viewerNeedsReset = true;
136+
_viewerFailed = false;
137+
}
98138
StateHasChanged();
99139
}
100140

@@ -103,7 +143,10 @@
103143
var was = showVisual;
104144
showVisual = e.Value is bool b ? b : showVisual;
105145
if (showVisual && !was)
146+
{
106147
_viewerNeedsReset = true;
148+
_viewerFailed = false;
149+
}
107150
StateHasChanged();
108151
}
109152

‎MakerPrompt.Shared/Components/StorageExplorer.razor‎

Lines changed: 72 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717

1818
private List<FileEntry> _files = new();
1919
private bool _isLoading;
20+
private bool _isOpening;
21+
private bool _isUploading;
22+
private bool _isCopying;
2023
private FileEntry? selectedFile;
2124

2225
protected override async Task OnInitializedAsync()
@@ -41,27 +44,57 @@
4144
if (!EnableUpload) return;
4245
var file = e.File;
4346
if (file == null) return;
44-
await using var stream = file.OpenReadStream(long.MaxValue);
45-
await Provider.SaveFileAsync(file.Name, stream);
47+
_isUploading = true;
48+
StateHasChanged();
49+
try
50+
{
51+
await using var stream = file.OpenReadStream(long.MaxValue);
52+
await Provider.SaveFileAsync(file.Name, stream);
53+
}
54+
finally
55+
{
56+
_isUploading = false;
57+
StateHasChanged();
58+
}
4659
await RefreshFiles();
4760
}
4861

4962
private async Task CopySelected()
5063
{
5164
if (!EnableCopy || selectedFile == null || TargetProvider == null) return;
52-
await using var stream = await Provider.OpenReadAsync(selectedFile.FullPath) ?? Stream.Null;
53-
if (stream == Stream.Null) return;
54-
await TargetProvider.SaveFileAsync(Path.GetFileName(selectedFile.FullPath), stream);
65+
_isCopying = true;
66+
StateHasChanged();
67+
try
68+
{
69+
await using var stream = await Provider.OpenReadAsync(selectedFile.FullPath) ?? Stream.Null;
70+
if (stream == Stream.Null) return;
71+
await TargetProvider.SaveFileAsync(Path.GetFileName(selectedFile.FullPath), stream);
72+
}
73+
finally
74+
{
75+
_isCopying = false;
76+
StateHasChanged();
77+
}
5578
}
5679

5780
private async Task OpenSelected()
5881
{
5982
if (!EnableOpen || selectedFile == null) return;
60-
await using var stream = await Provider.OpenReadAsync(selectedFile.FullPath) ?? Stream.Null;
61-
if (stream == Stream.Null) return;
62-
using var reader = new StreamReader(stream);
63-
var content = await reader.ReadToEndAsync();
64-
GCodeDoc.SetGCode(content);
83+
_isOpening = true;
84+
StateHasChanged();
85+
try
86+
{
87+
await using var stream = await Provider.OpenReadAsync(selectedFile.FullPath) ?? Stream.Null;
88+
if (stream == Stream.Null) return;
89+
using var reader = new StreamReader(stream);
90+
var content = await reader.ReadToEndAsync();
91+
GCodeDoc.SetGCode(content);
92+
}
93+
finally
94+
{
95+
_isOpening = false;
96+
StateHasChanged();
97+
}
6598
}
6699

67100
private async Task DeleteSelected()
@@ -108,9 +141,17 @@
108141
{
109142
var uploadId = $"fileUpload-{Title?.Replace(" ", string.Empty)}";
110143
<div class="input-group input-group-sm" style="max-width: 260px;">
111-
<InputFile id="@uploadId" class="form-control" OnChange="OnFileSelected" />
144+
<InputFile id="@uploadId" class="form-control" OnChange="OnFileSelected" disabled="@_isUploading" />
112145
<label class="input-group-text" for="@uploadId">
113-
<i class="bi bi-upload me-1"></i>Upload
146+
@if (_isUploading)
147+
{
148+
<span class="spinner-border spinner-border-sm me-1" role="status" aria-hidden="true"></span>
149+
}
150+
else
151+
{
152+
<i class="bi bi-upload me-1"></i>
153+
}
154+
@Localizer[Resources.StorageExplorer_Upload]
114155
</label>
115156
</div>
116157
}
@@ -120,14 +161,28 @@
120161
</button>
121162
@if (EnableCopy)
122163
{
123-
<button class="btn btn-sm btn-outline-success" @onclick="CopySelected" disabled="@(selectedFile == null)">
124-
<i class="bi bi-arrow-left-right"></i>
164+
<button class="btn btn-sm btn-outline-success" @onclick="CopySelected" disabled="@(selectedFile == null || _isCopying)">
165+
@if (_isCopying)
166+
{
167+
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
168+
}
169+
else
170+
{
171+
<i class="bi bi-arrow-left-right"></i>
172+
}
125173
</button>
126174
}
127175
@if (EnableOpen)
128176
{
129-
<button class="btn btn-sm btn-outline-info" @onclick="OpenSelected" disabled="@(selectedFile == null)">
130-
<i class="bi bi-eye"></i>
177+
<button class="btn btn-sm btn-outline-info" @onclick="OpenSelected" disabled="@(selectedFile == null || _isOpening)">
178+
@if (_isOpening)
179+
{
180+
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
181+
}
182+
else
183+
{
184+
<i class="bi bi-eye"></i>
185+
}
131186
</button>
132187
}
133188
@if (EnableDelete)
@@ -155,7 +210,7 @@
155210
<tr>
156211
<td colspan="5" class="text-center">
157212
<div class="spinner-border text-primary" role="status">
158-
<span class="visually-hidden">Loading...</span>
213+
<span class="visually-hidden">@Localizer[Resources.StorageExplorer_Loading]</span>
159214
</div>
160215
</td>
161216
</tr>

0 commit comments

Comments
 (0)