Skip to content

Commit 3b5eea2

Browse files
authored
Merge pull request #56 from Mythetech/feat/json-tools
feat: new json tools in the menu
2 parents 77dca87 + 2449bf1 commit 3b5eea2

File tree

12 files changed

+719
-2
lines changed

12 files changed

+719
-2
lines changed

Apollo.Components/Editor/ApolloMenu.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
{
1010
<ApolloIconButton Class="@(Settings.CurrentTheme.AppIconClass)" Icon="app-icon" Tooltip="About" OnClick="@OpenAboutDialogAsync"/>
1111
}
12-
<MudMenu Dense="true" ListClass="max-w-160" ActivationEvent="MouseEvent.MouseOver">
12+
<MudMenu Dense="true" ActivationEvent="MouseEvent.MouseOver">
1313
<ActivatorContent>
1414
<MudText Typo="Typo.h5" Class="gotu-regular ml-4">Apollo</MudText>
1515
</ActivatorContent>

Apollo.Components/Editor/ToolsMenu.razor

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@
2626
GUID v7 (Timestamp)
2727
</MudMenuItem>
2828
</MudMenu>
29+
<MudMenu StartIcon="@ApolloIcons.Json" Label="JSON">
30+
<MudMenuItem Icon="@ApolloIcons.JsonFormat" OnClick="@(async () => await Bus.PublishAsync(new OpenJsonFormatterDialog()))">
31+
Formatting
32+
</MudMenuItem>
33+
<MudMenuItem Icon="@ApolloIcons.JsonToCSharp" OnClick="@(async () => await Bus.PublishAsync(new OpenJsonToCSharpDialog()))">
34+
C# Converter
35+
</MudMenuItem>
36+
</MudMenu>
2937
</MudMenu>
3038

3139
@code {

Apollo.Components/Theme/ApolloIcons.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,4 +123,8 @@ public static class ApolloIcons
123123
public const string GuidV7 = Icons.Material.TwoTone.Schedule;
124124

125125
public const string Events = Icons.Material.TwoTone.EventNote;
126+
127+
public const string JsonFormat = Icons.Material.TwoTone.FormatIndentIncrease;
128+
129+
public const string JsonToCSharp = Icons.Material.TwoTone.Code;
126130
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
namespace Apollo.Components.Tools.Commands;
2+
3+
public record OpenJsonFormatterDialog();
4+
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
namespace Apollo.Components.Tools.Commands;
2+
3+
public record OpenJsonToCSharpDialog();
4+
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using Apollo.Components.Infrastructure.MessageBus;
2+
using Apollo.Components.Tools.Commands;
3+
using MudBlazor;
4+
5+
namespace Apollo.Components.Tools.Consumers;
6+
7+
public sealed class JsonFormatterDialogOpener : IConsumer<OpenJsonFormatterDialog>
8+
{
9+
private readonly IDialogService _dialogService;
10+
11+
public JsonFormatterDialogOpener(IDialogService dialogService)
12+
{
13+
_dialogService = dialogService;
14+
}
15+
16+
public async Task Consume(OpenJsonFormatterDialog message)
17+
{
18+
var options = new DialogOptions
19+
{
20+
CloseOnEscapeKey = true,
21+
NoHeader = true,
22+
MaxWidth = MaxWidth.ExtraLarge,
23+
FullWidth = true,
24+
};
25+
26+
await _dialogService.ShowAsync<JsonFormatterDialog>("JSON Formatter", options);
27+
}
28+
}
29+
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using Apollo.Components.Infrastructure.MessageBus;
2+
using Apollo.Components.Tools.Commands;
3+
using MudBlazor;
4+
5+
namespace Apollo.Components.Tools.Consumers;
6+
7+
public sealed class JsonToCSharpDialogOpener : IConsumer<OpenJsonToCSharpDialog>
8+
{
9+
private readonly IDialogService _dialogService;
10+
11+
public JsonToCSharpDialogOpener(IDialogService dialogService)
12+
{
13+
_dialogService = dialogService;
14+
}
15+
16+
public async Task Consume(OpenJsonToCSharpDialog message)
17+
{
18+
var options = new DialogOptions
19+
{
20+
CloseOnEscapeKey = true,
21+
NoHeader = true,
22+
MaxWidth = MaxWidth.ExtraLarge,
23+
FullWidth = true,
24+
};
25+
26+
await _dialogService.ShowAsync<JsonToCSharpDialog>("JSON to C# Converter", options);
27+
}
28+
}
29+
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
@using Apollo.Components.Theme
2+
@using Apollo.Components.Solutions
3+
@using Apollo.Components.Shared
4+
@using Apollo.Components.Shared.ApolloNotificationBar
5+
@using BlazorMonaco
6+
@using BlazorMonaco.Editor
7+
@using MudBlazor
8+
@implements IDisposable
9+
10+
<MudDialog Class="pa-4 glassmorphic" Style="width: 85vw; max-width: 1400px; height: 85vh;">
11+
<DialogContent>
12+
<MudGrid Style="height: 100%;">
13+
<MudItem xs="12">
14+
<MudStack Row AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween">
15+
<MudStack Spacing="0">
16+
<MudText Class="app-header-font" Typo="Typo.h5">JSON Formatter</MudText>
17+
<MudText Typo="Typo.caption" Class="mud-text-secondary">Format and beautify JSON</MudText>
18+
</MudStack>
19+
<MudStack Row Spacing="2">
20+
<MudButton Variant="Variant.Outlined" Size="Size.Small" StartIcon="@ApolloIcons.FormatDocument" OnClick="FormatJson" Disabled="@string.IsNullOrWhiteSpace(_jsonInput)">
21+
Format
22+
</MudButton>
23+
<MudButton Variant="Variant.Outlined" Size="Size.Small" StartIcon="@ApolloIcons.Copy" OnClick="CopyFormatted" Disabled="@string.IsNullOrWhiteSpace(_formattedJson)">
24+
Copy
25+
</MudButton>
26+
<MudButton Variant="Variant.Outlined" Size="Size.Small" StartIcon="@ApolloIcons.Save" OnClick="SaveToSolution" Disabled="@string.IsNullOrWhiteSpace(_formattedJson)">
27+
Save to Solution
28+
</MudButton>
29+
<MudButton Variant="Variant.Outlined" Size="Size.Small" StartIcon="@ApolloIcons.Close" OnClick="Close">
30+
Close
31+
</MudButton>
32+
</MudStack>
33+
</MudStack>
34+
</MudItem>
35+
36+
@if (!string.IsNullOrEmpty(_error))
37+
{
38+
<MudItem xs="12">
39+
<MudAlert Severity="Severity.Error" Dense Variant="Variant.Outlined">
40+
<MudText Typo="Typo.body2">@_error</MudText>
41+
</MudAlert>
42+
</MudItem>
43+
}
44+
45+
<MudItem xs="12" md="6" Style="height: calc(85vh - 150px);">
46+
<MudStack Spacing="2" Style="height: 100%;">
47+
<MudText Typo="Typo.subtitle2">Input JSON</MudText>
48+
<MudPaper Elevation="0" Style="height: calc(100% - 40px); background: var(--mud-palette-dark);">
49+
<StandaloneCodeEditor @ref="_inputEditor"
50+
Id="json-formatter-input"
51+
ConstructionOptions="@GetInputEditorOptions"
52+
OnDidInit="OnInputEditorInit"
53+
OnDidChangeModelContent="OnInputEditorContentChanged" />
54+
</MudPaper>
55+
</MudStack>
56+
</MudItem>
57+
58+
<MudItem xs="12" md="6" Style="height: calc(85vh - 150px);">
59+
<MudStack Spacing="2" Style="height: 100%;">
60+
<MudText Typo="Typo.subtitle2">Formatted JSON</MudText>
61+
<MudPaper Elevation="0" Style="height: calc(100% - 40px); background: var(--mud-palette-dark);">
62+
<StandaloneCodeEditor @ref="_outputEditor"
63+
Id="json-formatter-output"
64+
ConstructionOptions="@GetOutputEditorOptions"
65+
OnDidInit="OnOutputEditorInit" />
66+
</MudPaper>
67+
</MudStack>
68+
</MudItem>
69+
</MudGrid>
70+
</DialogContent>
71+
</MudDialog>
72+
73+
@code {
74+
[CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = default!;
75+
[Inject] private IJsApiService JsApiService { get; set; } = default!;
76+
[Inject] private ISnackbar Snackbar { get; set; } = default!;
77+
[Inject] private SolutionsState SolutionsState { get; set; } = default!;
78+
[Inject] private IDialogService DialogService { get; set; } = default!;
79+
80+
private StandaloneCodeEditor? _inputEditor;
81+
private StandaloneCodeEditor? _outputEditor;
82+
private bool _inputEditorReady;
83+
private bool _outputEditorReady;
84+
85+
private string _jsonInput = "";
86+
private string _formattedJson = "";
87+
private string? _error;
88+
89+
private StandaloneEditorConstructionOptions GetInputEditorOptions(StandaloneCodeEditor editor)
90+
{
91+
return new StandaloneEditorConstructionOptions
92+
{
93+
AutomaticLayout = true,
94+
Language = "json",
95+
Value = _jsonInput,
96+
FontSize = 14,
97+
LineHeight = 20,
98+
TabSize = 2,
99+
InsertSpaces = true,
100+
WordWrap = "off",
101+
Minimap = new EditorMinimapOptions { Enabled = false },
102+
};
103+
}
104+
105+
private StandaloneEditorConstructionOptions GetOutputEditorOptions(StandaloneCodeEditor editor)
106+
{
107+
return new StandaloneEditorConstructionOptions
108+
{
109+
AutomaticLayout = true,
110+
Language = "json",
111+
Value = _formattedJson,
112+
FontSize = 14,
113+
LineHeight = 20,
114+
TabSize = 2,
115+
InsertSpaces = true,
116+
WordWrap = "off",
117+
Minimap = new EditorMinimapOptions { Enabled = false },
118+
ReadOnly = true,
119+
};
120+
}
121+
122+
private async Task OnInputEditorInit()
123+
{
124+
_inputEditorReady = true;
125+
await Task.CompletedTask;
126+
}
127+
128+
private async Task OnOutputEditorInit()
129+
{
130+
_outputEditorReady = true;
131+
await Task.CompletedTask;
132+
}
133+
134+
private async Task OnInputEditorContentChanged(ModelContentChangedEvent evt)
135+
{
136+
if (!_inputEditorReady || _inputEditor == null)
137+
return;
138+
139+
_jsonInput = await _inputEditor.GetValue();
140+
await InvokeAsync(StateHasChanged);
141+
}
142+
143+
private async Task FormatJson()
144+
{
145+
_error = null;
146+
_formattedJson = "";
147+
148+
if (!_inputEditorReady || _inputEditor == null)
149+
return;
150+
151+
if (string.IsNullOrWhiteSpace(_jsonInput))
152+
{
153+
_jsonInput = await _inputEditor.GetValue();
154+
}
155+
156+
if (string.IsNullOrWhiteSpace(_jsonInput))
157+
{
158+
_error = "Input is empty";
159+
return;
160+
}
161+
162+
try
163+
{
164+
using var doc = System.Text.Json.JsonDocument.Parse(_jsonInput);
165+
_formattedJson = System.Text.Json.JsonSerializer.Serialize(doc, new System.Text.Json.JsonSerializerOptions
166+
{
167+
WriteIndented = true
168+
});
169+
170+
if (_outputEditorReady && _outputEditor != null)
171+
{
172+
await _outputEditor.SetValue(_formattedJson);
173+
}
174+
}
175+
catch (System.Text.Json.JsonException ex)
176+
{
177+
_error = $"Invalid JSON: {ex.Message}";
178+
}
179+
catch (Exception ex)
180+
{
181+
_error = $"Error formatting JSON: {ex.Message}";
182+
}
183+
}
184+
185+
private async Task CopyFormatted()
186+
{
187+
if (string.IsNullOrWhiteSpace(_formattedJson))
188+
return;
189+
190+
await JsApiService.CopyToClipboardAsync(_formattedJson);
191+
Snackbar.AddApolloNotification("Copied formatted JSON to clipboard!", Severity.Success);
192+
}
193+
194+
private async Task SaveToSolution()
195+
{
196+
if (string.IsNullOrWhiteSpace(_formattedJson))
197+
return;
198+
199+
var parameters = new DialogParameters
200+
{
201+
{ "DefaultFileName", "formatted.json" }
202+
};
203+
204+
var options = new DialogOptions
205+
{
206+
MaxWidth = MaxWidth.Medium,
207+
CloseOnEscapeKey = true
208+
};
209+
210+
var dialog = await DialogService.ShowAsync<SaveJsonFileDialog>("Save JSON File", parameters, options);
211+
var result = await dialog.Result;
212+
213+
if (!result.Canceled && result.Data is string fileName && !string.IsNullOrWhiteSpace(fileName))
214+
{
215+
var jsonFileName = fileName.EndsWith(".json", StringComparison.OrdinalIgnoreCase) ? fileName : $"{fileName}.json";
216+
SolutionsState.AddFile(jsonFileName, _formattedJson);
217+
Snackbar.AddApolloNotification($"Saved {jsonFileName} to solution!", Severity.Success);
218+
}
219+
}
220+
221+
private void Close() => MudDialog.Close();
222+
223+
public void Dispose()
224+
{
225+
}
226+
}
227+

0 commit comments

Comments
 (0)