Skip to content

Commit

Permalink
Merge pull request #45 from neuroglia-io/feat-13-dashboard-projection…
Browse files Browse the repository at this point in the history
…-edit

Enable projection edition in the Dashboard
  • Loading branch information
JBBianchi authored Feb 18, 2025
2 parents 79392b8 + 4de093b commit a6c9a3e
Show file tree
Hide file tree
Showing 13 changed files with 494 additions and 51 deletions.
25 changes: 15 additions & 10 deletions src/CloudShapes.Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,22 @@
options.LowercaseUrls = true;
});
builder.Services.AddResponseCompression();
builder.Services.AddControllers().AddJsonOptions(options =>
{
var setup = JsonSerializer.DefaultOptionsConfiguration;
JsonSerializer.DefaultOptionsConfiguration = serializerOptions =>
builder.Services
.AddControllers(options =>
{
setup(serializerOptions);
serializerOptions.Converters.Insert(0, new Decimal128Converter());
};
JsonSerializer.DefaultOptionsConfiguration(options.JsonSerializerOptions);
options.JsonSerializerOptions.Converters.Add(new ObjectConverter());
});
options.Filters.Add<ProblemDetailsExceptionFilter>();
})
.AddJsonOptions(options =>
{
var setup = JsonSerializer.DefaultOptionsConfiguration;
JsonSerializer.DefaultOptionsConfiguration = serializerOptions =>
{
setup(serializerOptions);
serializerOptions.Converters.Insert(0, new Decimal128Converter());
};
JsonSerializer.DefaultOptionsConfiguration(options.JsonSerializerOptions);
options.JsonSerializerOptions.Converters.Add(new ObjectConverter());
});
builder.Services.AddSignalR();
builder.Services.AddOpenApi();
builder.Services.AddMediator(options =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,36 @@
See the License for the specific language governing permissions and
limitations under the License.
*@
@namespace CloudShapes.Dashboard.Components
@using CloudShapes.Dashboard.Components.MonacoEditorStateManagement
@namespace CloudShapes.Dashboard.Components
@inherits StatefulComponent<MonacoEditor, MonacoEditorStore, MonacoEditorState>

<div class="d-flex justify-content-between mb-2">
<div>
<div class="d-flex gap-2">
<Button Outline="true" Color="ButtonColor.Primary" Size="ButtonSize.Small" @onclick="Store.OnCopyToClipboard" TooltipTitle="Copy to clipboard">
<Icon Name="IconName.Clipboard" />
</Button>
@if (ExtraControls != null)
{
@ExtraControls
}
</div>
<PreferredLanguageSelector PreferredLanguageChange="Store.ToggleTextBasedEditorLanguageAsync" />
</div>
<StandaloneCodeEditor @ref="Store.TextEditor"
ConstructionOptions="Store.StandaloneEditorConstructionOptions"
OnDidInit="Store.OnTextBasedEditorInitAsync"
OnDidChangeModelContent="OnDidChangeModelContent"
CssClass="h-300-px" />
CssClass="@(ClassNames ?? "h-300-px")" />

@code {

protected string? ClassNames => Class;
/// <summary>
/// Gets/sets the additional css classes
/// </summary>
[Parameter] public string? Class { get; set; }

[Parameter] public bool IsReadOnly { get; set; } = false;

[Parameter] public object? Document { get; set; }
Expand All @@ -45,6 +55,8 @@

[Parameter] public string ModelName { get; set; } = string.Empty;

[Parameter] public RenderFragment? ExtraControls { get; set; }

protected override async Task OnParametersSetAsync()
{
await base.OnParametersSetAsync();
Expand Down
2 changes: 1 addition & 1 deletion src/CloudShapes.Dashboard/Components/Pager/Pager.razor
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
See the License for the specific language governing permissions and
limitations under the License.
*@
@namespace CloudShapes.Dashboard.Components
@using CloudShapes.Dashboard.Components.PagerStateManagement
@namespace CloudShapes.Dashboard.Components
@inherits StatefulComponent<Pager, PagerStore, PagerState>

<div class="pager d-flex justify-content-between align-items-center gap-3 my-3 @(pageCount == 0 ? "opacity-25" : "") @ClassNames">
Expand Down
25 changes: 4 additions & 21 deletions src/CloudShapes.Dashboard/Components/Pager/Store.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.

using BlazorMonaco.Editor;

namespace CloudShapes.Dashboard.Components.PagerStateManagement;

/// <summary>
/// Represents the <see cref="ComponentStore{TState}" /> of a <see cref="Pager"/>
/// </summary>
/// <param name="logger">The service used to perform logging</param>
public class PagerStore(ILogger<PagerStore> logger)
public class PagerStore()
: ComponentStore<PagerState>(new())
{

/// <summary>
/// Gets the service used to perform logging
/// </summary>
protected ILogger<PagerStore> Logger { get; } = logger;

#region Selectors
/// <summary>
/// Gets an <see cref="IObservable{T}"/> used to observe <see cref="PagerState.TotalLength"/> changes
Expand Down Expand Up @@ -89,7 +81,7 @@ public class PagerStore(ILogger<PagerStore> logger)
/// <param name="totalLength">The new value</param>
public void SetTotalLength(long totalLength)
{
this.Reduce(state => state with {
Reduce(state => state with {
TotalLength = totalLength
});
}
Expand All @@ -101,7 +93,7 @@ public void SetTotalLength(long totalLength)
public void SetPageIndex(int pageIndex)
{
var previousPageIndex = this.Get(state => state.PageIndex);
this.Reduce(state => state with
Reduce(state => state with
{
PageIndex = pageIndex,
PreviousPageIndex = previousPageIndex
Expand All @@ -115,21 +107,12 @@ public void SetPageIndex(int pageIndex)
public void SetPageSize(int pageSize)
{
var previousPageSize = this.Get(state => state.PageSize);
this.Reduce(state => state with
Reduce(state => state with
{
PageSize = pageSize,
PreviousPageSize = previousPageSize
});
}
#endregion

#region Actions
#endregion

/// <inheritdoc/>
public override Task InitializeAsync()
{
return base.InitializeAsync();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,108 @@
See the License for the specific language governing permissions and
limitations under the License.
*@
@using CloudShapes.Dashboard.Components.ProjectionDetailsStateManagement
@namespace CloudShapes.Dashboard.Components
@inject IJsonSerializer JsonSerializer
@inherits StatefulComponent<ProjectionDetails, ProjectionDetailsStore, ProjectionDetailsState>

<div class="monaco-editor-container-large">
<MonacoEditor Document="projection" />
<div class="flex-grow-1 d-flex flex-column gap-3">
@if (projection != null)
{
@if (!isEditing)
{
<MonacoEditor Document="projection" OnTextChanged="textEditorInput.OnNext" Class="flex-grow-1" IsReadOnly="true">
<ExtraControls>
@if (!readOnly)
{
<Button Outline="true" Color="ButtonColor.Primary" Size="ButtonSize.Small" @onclick="(_) => Store.SetIsEditing(true)" TooltipTitle="Edit projection">
<Icon Name="IconName.Pencil" />
</Button>
}
</ExtraControls>
</MonacoEditor>
}
else
{
<MonacoEditor Document="projection" OnTextChanged="textEditorInput.OnNext" Class="flex-grow-1">
<ExtraControls>
<Button Outline="true" Color="ButtonColor.Primary" Size="ButtonSize.Small" @onclick="(_) => Store.SetIsEditing(false)" TooltipTitle="View projection">
<Icon Name="IconName.Eye" />
</Button>
</ExtraControls>
</MonacoEditor>
}
}
@if (problemDetails != null)
{
<div class="problems px-3">
<Callout Color="CalloutColor.Danger" Heading="@problemDetails.Title" Class="position-relative m-0">
<Icon Name="IconName.X" Class="position-absolute" @onclick="() => Store.SetProblemDetails(null)" />
<p>@problemDetails.Detail</p>

@if (problemDetails.Errors != null && problemDetails.Errors.Any())
{
foreach (KeyValuePair<string, string[]> errorContainer in problemDetails.Errors)
{
<strong>@errorContainer.Key:</strong>
<ul>
@foreach (string error in errorContainer.Value)
{
<li>@error</li>
}
</ul>
}
}
</Callout>
</div>
}
@if (isEditing)
{
<Button Disabled="isSaving" Color="ButtonColor.Primary" Loading="isSaving" @onclick="async _ => await Store.SaveProjectionAsync()">
<Icon Name="IconName.Save" />
Save
</Button>
}
</div>

@code {

ProjectionType? projectionType;
IDictionary<string, object>? projection;
bool readOnly;
bool readOnly = false;
private ProblemDetails? problemDetails = null;
bool isSaving = false;
bool isEditing = false;
private Subject<string> textEditorInput = new Subject<string>();

[Parameter] public ProjectionType? ProjectionType { get; set; } = null!;

[Parameter] public IDictionary<string, object>? Projection { get; set; } = null!;

[Parameter] public bool ReadOnly { get; set; }
[Parameter] public bool ReadOnly { get; set; } = false;

protected override void OnInitialized()
{
base.OnInitialized();
Store.Projection.Subscribe(value => OnStateChanged(_ => projection = value), CancellationTokenSource.Token);
Store.ProjectionType.Subscribe(value => OnStateChanged(_ => projectionType = value), CancellationTokenSource.Token);
Store.IsSaving.Subscribe(value => OnStateChanged(_ => isSaving = value), CancellationTokenSource.Token);
Store.IsEditing.Subscribe(value => OnStateChanged(_ => isEditing = value), CancellationTokenSource.Token);
Store.ProblemDetails.Subscribe(value => OnStateChanged(_ => problemDetails = value), token: CancellationTokenSource.Token);
textEditorInput
.Throttle(TimeSpan.FromMilliseconds(300))
.DistinctUntilChanged()
.Subscribe(text => Store.SetProjectionValue(text));
}

/// <inheritdoc/>
protected override Task OnParametersSetAsync()
{
if (projectionType != ProjectionType) projectionType = ProjectionType;
if (projection != Projection) projection = Projection;
Store.SetIsSaving(false);
Store.SetIsEditing(false);
Store.SetProblemDetails(null);
if (projectionType != ProjectionType) Store.SetProjectionType(ProjectionType);
if (projection != Projection) Store.SetProjection(Projection);
if (readOnly != ReadOnly) readOnly = ReadOnly;
return base.OnParametersSetAsync();
}

}
70 changes: 70 additions & 0 deletions src/CloudShapes.Dashboard/Components/ProjectionDetails/State.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright © 2025-Present The Cloud Shapes Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"),
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace CloudShapes.Dashboard.Components.ProjectionDetailsStateManagement;

/// <summary>
/// Represents the state of a <see cref="ProjectionDetails"/>
/// </summary>
public record ProjectionDetailsState
{
/// <summary>
/// Gets/sets the projection
/// </summary>
public IDictionary<string, object>? Projection { get; set; } = null;

/// <summary>
/// Gets/sets the serialized projection
/// </summary>
public string SerializedProjection { get; set; } = string.Empty;

/// <summary>
/// Gets/sets the <see cref="ProjectionType"/> of the projection
/// </summary>
public ProjectionType? ProjectionType { get; set; } = null;

/// <summary>
/// Gets/sets a boolean indicating if the projection is being saved
/// </summary>
public bool IsSaving { get; set; } = false;

/// <summary>
/// Gets/sets a boolean indicating if the projection is being edited
/// </summary>
public bool IsEditing { get; set; } = false;

/// <summary>
/// Gets/sets the <see cref="ProblemDetails"/> type that occurred when trying to save the projection, if any
/// </summary>
public Uri? ProblemType { get; set; } = null;

/// <summary>
/// Gets/sets the <see cref="ProblemDetails"/> title that occurred when trying to save the projection, if any
/// </summary>
public string ProblemTitle { get; set; } = string.Empty;

/// <summary>
/// Gets/sets the <see cref="ProblemDetails"/> details that occurred when trying to save the projection, if any
/// </summary>
public string ProblemDetail { get; set; } = string.Empty;

/// <summary>
/// Gets/sets the <see cref="ProblemDetails"/> status that occurred when trying to save the projection, if any
/// </summary>
public int ProblemStatus { get; set; } = 0;

/// <summary>
/// Gets/sets the list of <see cref="ProblemDetails"/> errors that occurred when trying to save the projection, if any
/// </summary>
public IDictionary<string, string[]> ProblemErrors { get; set; } = new Dictionary<string, string[]>();
}
Loading

0 comments on commit a6c9a3e

Please sign in to comment.