Skip to content

Commit c34852a

Browse files
authored
Merge branch 'main' into feature/remove-rendering-before--login
2 parents 73d42e8 + 91868f2 commit c34852a

File tree

25 files changed

+643
-256
lines changed

25 files changed

+643
-256
lines changed
Lines changed: 4 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
1-
@using Elsa.Studio.DomInterop.Contracts
2-
@using Elsa.Studio.Localization
3-
@using Elsa.Studio.Localization.Time.Components
4-
@using Elsa.Studio.Models
5-
61
<style>
72
.hover-row:hover .icon-on-hover {
83
visibility: visible;
@@ -21,7 +16,7 @@
2116
<tbody>
2217
@{
2318
var data = HideEmptyValues
24-
? Data.Where(x => !string.IsNullOrWhiteSpace(x.Text) || x.LabelComponent != null || x.ValueComponent != null).ToList()
19+
? Data.Where(x => !string.IsNullOrWhiteSpace(x.Text) || x.Value != null || x.LabelComponent != null || x.ValueComponentType != null || x.ValueTemplate != null).ToList()
2520
: Data;
2621
}
2722
@if (data.Any())
@@ -40,56 +35,11 @@
4035
}
4136
</td>
4237
<td>
43-
@if (item.ValueComponent != null)
44-
{
45-
@item.ValueComponent
46-
}
47-
else
48-
{
49-
<MudStack Row=true Spacing="1" AlignItems="AlignItems.Center">
50-
@if (!string.IsNullOrWhiteSpace(item.Text))
51-
{
52-
<MudTooltip Text="@Localizer["View"]" Delay="500">
53-
<MudIconButton Icon="@Icons.Material.Outlined.ManageSearch" Size="Size.Small" OnClick="@(() => OnViewClicked(item!))" Disabled="@(string.IsNullOrWhiteSpace(item.Text))" Class="icon-on-hover" />
54-
</MudTooltip>
55-
}
56-
@if (!string.IsNullOrWhiteSpace(item.Link))
57-
{
58-
<MudLink Typo="Typo.body2" Href="@item.Link">@item.Text</MudLink>
59-
}
60-
else if (item.OnClick != null)
61-
{
62-
<MudLink Typo="Typo.body2" OnClick="@item.OnClick">@item.Text</MudLink>
63-
}
64-
else
65-
{
66-
@if (item.Label == "Created" || item.Label == "Updated" || item.Label == "Finished")
67-
{
68-
<span><Timestamp Value="@Convert.ToDateTime(item.Text)"></Timestamp></span>
69-
}
70-
else
71-
{
72-
@if (item.Text?.Length > truncationLength)
73-
{
74-
<div>
75-
@item.Text.Substring(0, truncationLength)
76-
<MudTooltip Text="@Localizer["Show all"]" Delay="500">
77-
<MudLink OnClick="@(() => OnViewClicked(item!))"><small>[...]</small></MudLink>
78-
</MudTooltip>
79-
</div>
80-
}
81-
else
82-
{
83-
<span>@item.Text</span>
84-
}
85-
}
86-
}
87-
</MudStack>
88-
}
38+
@RenderValue(item)
8939
</td>
9040
<td style="width: 50px;">
9141
<MudTooltip Text="@Localizer["Copy"]" Delay="500">
92-
<MudIconButton Icon="@Icons.Material.Outlined.ContentCopy" Size="Size.Small" OnClick="@(x => OnCopyClicked(item!))" Disabled="@(string.IsNullOrWhiteSpace(item.Text))" />
42+
<MudIconButton Icon="@Icons.Material.Outlined.ContentCopy" Size="Size.Small" OnClick="@(x => OnCopyClicked(item!))" Disabled="@(string.IsNullOrWhiteSpace(GetFormattedValue(item)))" />
9343
</MudTooltip>
9444
</td>
9545
</tr>
@@ -109,65 +59,4 @@
10959
{
11060
<MudAlert Severity="Severity.Normal" Dense="true" Variant="Variant.Text">@(NoDataMessage)</MudAlert>
11161
}
112-
</div>
113-
114-
@code {
115-
private int truncationLength = 300;
116-
117-
/// <summary>
118-
/// The data to display.
119-
/// </summary>
120-
[Parameter] public DataPanelModel Data { get; set; } = new();
121-
122-
/// <summary>
123-
/// If true, empty values will be hidden.
124-
/// </summary>
125-
[Parameter] public bool HideEmptyValues { get; set; }
126-
127-
/// <summary>
128-
/// If true, a message will be displayed when there is no data.
129-
/// </summary>
130-
[Parameter] public bool ShowNoDataAlert { get; set; }
131-
132-
/// <summary>
133-
/// The title of the data panel
134-
/// </summary>
135-
[Parameter] public string Title { get; set; } = null!;
136-
137-
/// <summary>
138-
/// The message to display when there is no data.
139-
/// </summary>
140-
[Parameter] public string NoDataMessage { get; set; } = null!;
141-
142-
[Inject] private ILocalizer Localizer { get; set; } = null!;
143-
[Inject] private IClipboard Clipboard { get; set; } = null!;
144-
[Inject] private ISnackbar Snackbar { get; set; } = null!;
145-
[Inject] private IDialogService DialogService { get; set; } = null!;
146-
147-
protected override void OnInitialized()
148-
{
149-
NoDataMessage ??= Localizer["No data available."];
150-
}
151-
152-
private async Task OnCopyClicked(DataPanelItem item)
153-
{
154-
await Clipboard.CopyText(item.Text);
155-
Snackbar.Add(Localizer["{0} copied", item.Label], Severity.Success);
156-
}
157-
158-
private async Task OnViewClicked(DataPanelItem item)
159-
{
160-
var options = new DialogOptions
161-
{
162-
CloseOnEscapeKey = true,
163-
Position = DialogPosition.Center,
164-
FullWidth = true,
165-
MaxWidth = MaxWidth.Large
166-
};
167-
var parameters = new DialogParameters
168-
{
169-
{ nameof(DataPanelItem), item }
170-
};
171-
await DialogService.ShowAsync<ContentVisualizer>(Localizer["View content"], parameters, options);
172-
}
173-
}
62+
</div>
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
using System.Globalization;
2+
using System.Text.Json;
3+
using Elsa.Studio.Components.DataPanelRenderers;
4+
using Elsa.Studio.DomInterop.Contracts;
5+
using Elsa.Studio.Localization;
6+
using Elsa.Studio.Models;
7+
using Microsoft.AspNetCore.Components;
8+
using MudBlazor;
9+
10+
namespace Elsa.Studio.Components;
11+
12+
/// <summary>
13+
/// A component that displays a panel of data items in a tabular format.
14+
/// </summary>
15+
public partial class DataPanel : ComponentBase
16+
{
17+
/// <summary>
18+
/// The default maximum length for truncating displayed content in data panel components.
19+
/// </summary>
20+
public const int DefaultTruncationLength = 300;
21+
private static readonly JsonSerializerOptions JsonSerializerOptions = new() { WriteIndented = true };
22+
23+
/// <summary>
24+
/// The data to display.
25+
/// </summary>
26+
[Parameter] public DataPanelModel Data { get; set; } = new();
27+
28+
/// <summary>
29+
/// If true, empty values will be hidden.
30+
/// </summary>
31+
[Parameter] public bool HideEmptyValues { get; set; }
32+
33+
/// <summary>
34+
/// If true, a message will be displayed when there is no data.
35+
/// </summary>
36+
[Parameter] public bool ShowNoDataAlert { get; set; }
37+
38+
/// <summary>
39+
/// The title of the data panel
40+
/// </summary>
41+
[Parameter] public string Title { get; set; } = null!;
42+
43+
/// <summary>
44+
/// The message to display when there is no data.
45+
/// </summary>
46+
[Parameter] public string? NoDataMessage { get; set; }
47+
48+
[Inject] private ILocalizer Localizer { get; set; } = null!;
49+
[Inject] private IClipboard Clipboard { get; set; } = null!;
50+
[Inject] private ISnackbar Snackbar { get; set; } = null!;
51+
[Inject] private IDialogService DialogService { get; set; } = null!;
52+
53+
/// <inheritdoc />
54+
protected override void OnInitialized()
55+
{
56+
NoDataMessage ??= Localizer["No data available."];
57+
}
58+
59+
private RenderFragment RenderValue(DataPanelItem item) => builder =>
60+
{
61+
// Priority 1: ValueTemplate (custom render fragment with context)
62+
if (item.ValueTemplate != null)
63+
{
64+
var context = new DataPanelItemContext
65+
{
66+
Label = item.Label,
67+
Value = item.Value,
68+
Item = item
69+
};
70+
builder.AddContent(0, item.ValueTemplate(context));
71+
return;
72+
}
73+
74+
// Priority 2: ValueComponentType (reusable custom component)
75+
if (item.ValueComponentType != null)
76+
{
77+
builder.OpenComponent(0, item.ValueComponentType);
78+
builder.AddAttribute(1, "Item", item);
79+
builder.CloseComponent();
80+
return;
81+
}
82+
83+
// Priority 3: Format-based rendering (new system using dedicated components)
84+
var displayText = GetFormattedValue(item);
85+
builder.OpenComponent<DataPanelValueRenderer>(0);
86+
builder.AddAttribute(1, "Item", item);
87+
builder.AddAttribute(2, "DisplayText", displayText);
88+
builder.AddAttribute(3, "TruncationLength", DefaultTruncationLength);
89+
builder.AddAttribute(4, "OnViewClicked", EventCallback.Factory.Create(this, () => OnViewClicked(item)));
90+
builder.CloseComponent();
91+
};
92+
93+
/// <summary>
94+
/// Gets the formatted value for a data panel item.
95+
/// </summary>
96+
public string GetFormattedValue(DataPanelItem item)
97+
{
98+
// Use Text if explicitly provided (backward compatibility)
99+
if (!string.IsNullOrWhiteSpace(item.Text))
100+
return item.Text;
101+
102+
// No value to format
103+
if (item.Value == null)
104+
return string.Empty;
105+
106+
// Apply format based on Format property
107+
return item.Format switch
108+
{
109+
DataPanelItemFormat.Text => item.Value.ToString() ?? string.Empty,
110+
DataPanelItemFormat.Timestamp => FormatTimestamp(item.Value, item.FormatString),
111+
DataPanelItemFormat.Number => FormatNumber(item.Value),
112+
DataPanelItemFormat.Boolean => FormatBoolean(item.Value),
113+
DataPanelItemFormat.Json => FormatJson(item.Value),
114+
DataPanelItemFormat.Code => item.Value.ToString() ?? string.Empty,
115+
DataPanelItemFormat.Markdown => item.Value.ToString() ?? string.Empty,
116+
DataPanelItemFormat.Auto => FormatAuto(item.Value),
117+
_ => item.Value.ToString() ?? string.Empty
118+
};
119+
}
120+
121+
private string FormatTimestamp(object value, string? formatString)
122+
{
123+
return value switch
124+
{
125+
DateTime dt => string.IsNullOrWhiteSpace(formatString)
126+
? dt.ToLocalTime().ToString(CultureInfo.CurrentUICulture)
127+
: dt.ToLocalTime().ToString(formatString, CultureInfo.CurrentUICulture),
128+
DateTimeOffset dto => string.IsNullOrWhiteSpace(formatString)
129+
? dto.ToLocalTime().ToString(CultureInfo.CurrentUICulture)
130+
: dto.ToLocalTime().ToString(formatString, CultureInfo.CurrentUICulture),
131+
_ => value.ToString() ?? string.Empty
132+
};
133+
}
134+
135+
private string FormatNumber(object value)
136+
{
137+
return value switch
138+
{
139+
int or long or short or byte => $"{value:N0}",
140+
double or float or decimal => $"{value:N2}",
141+
_ => value.ToString() ?? string.Empty
142+
};
143+
}
144+
145+
private string FormatBoolean(object value)
146+
{
147+
return value is bool b ? (b ? Localizer["Yes"] : Localizer["No"]) : value.ToString() ?? string.Empty;
148+
}
149+
150+
private string FormatJson(object value)
151+
{
152+
return value as string ?? JsonSerializer.Serialize(value, JsonSerializerOptions);
153+
}
154+
155+
private string FormatAuto(object value)
156+
{
157+
// Auto-detect based on type
158+
return value switch
159+
{
160+
DateTime or DateTimeOffset => FormatTimestamp(value, null),
161+
bool => FormatBoolean(value),
162+
int or long or short or byte or double or float or decimal => FormatNumber(value),
163+
_ => value.ToString() ?? string.Empty
164+
};
165+
}
166+
167+
private async Task OnCopyClicked(DataPanelItem item)
168+
{
169+
var textToCopy = !string.IsNullOrWhiteSpace(item.Text) ? item.Text : GetFormattedValue(item);
170+
await Clipboard.CopyText(textToCopy);
171+
Snackbar.Add(Localizer["{0} copied", item.Label], Severity.Success);
172+
}
173+
174+
private async Task OnViewClicked(DataPanelItem item)
175+
{
176+
var options = new DialogOptions
177+
{
178+
CloseOnEscapeKey = true,
179+
Position = DialogPosition.Center,
180+
FullWidth = true,
181+
MaxWidth = MaxWidth.Large
182+
};
183+
184+
// Create a copy with the formatted text for viewing
185+
var itemToView = item with { Text = GetFormattedValue(item) };
186+
187+
var parameters = new DialogParameters
188+
{
189+
{ nameof(DataPanelItem), itemToView }
190+
};
191+
await DialogService.ShowAsync<ContentVisualizer>(Localizer["View content"], parameters, options);
192+
}
193+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
@if (!string.IsNullOrWhiteSpace(Code))
2+
{
3+
<pre style="background-color: var(--mud-palette-background-grey); padding: 8px; border-radius: 4px; overflow-x: auto; margin: 0;"><code>@Code</code></pre>
4+
}
5+
else
6+
{
7+
<span>-</span>
8+
}
9+
10+
@code {
11+
/// <summary>
12+
/// The code to display.
13+
/// </summary>
14+
[Parameter] public string? Code { get; set; }
15+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
@using Elsa.Studio.Localization
2+
@using Elsa.Studio.Models
3+
4+
@if (DisplayText?.Length > TruncationLength)
5+
{
6+
<div>
7+
@DisplayText.Substring(0, TruncationLength)
8+
<MudTooltip Text="@Localizer["Show all"]" Delay="500">
9+
<MudLink OnClick="@OnViewClicked"><small>[...]</small></MudLink>
10+
</MudTooltip>
11+
</div>
12+
}
13+
else
14+
{
15+
<span>@DisplayText</span>
16+
}
17+
18+
@code {
19+
/// <summary>
20+
/// The text to display.
21+
/// </summary>
22+
[Parameter] public string? DisplayText { get; set; }
23+
24+
/// <summary>
25+
/// The maximum length before truncation.
26+
/// </summary>
27+
[Parameter] public int TruncationLength { get; set; } = DataPanel.DefaultTruncationLength;
28+
29+
/// <summary>
30+
/// Callback invoked when the "show all" link is clicked.
31+
/// </summary>
32+
[Parameter] public EventCallback OnViewClicked { get; set; }
33+
34+
[Inject] private ILocalizer Localizer { get; set; } = null!;
35+
}

0 commit comments

Comments
 (0)