features/refactor
stm 2 years ago
parent c3b267dfc4
commit b178bbb9b0

@ -10,59 +10,61 @@ namespace Connected.Components;
public partial class Dialog : UIComponent
{
protected string ContentClass => new CssBuilder("dialog-content")
.AddClass($"dialog-no-side-padding", DisableSidePadding)
.AddClass(ClassContent)
.Build();
#region Variables
[CascadingParameter] private DialogInstance DialogInstance { get; set; }
[Inject] public IDialogService DialogService { get; set; }
private IDialogReference _reference;
#endregion
protected string ActionClass => new CssBuilder("dialog-actions")
.AddClass(ClassActions)
.Build();
#region Events
[Parameter]
[Category(CategoryTypes.Dialog.Behavior)]
public Action OnBackdropClick { get; set; }
[CascadingParameter] private DialogInstance DialogInstance { get; set; }
/// <summary>
/// Raised when the inline dialog's display status changes.
/// </summary>
[Parameter] public EventCallback<bool> IsVisibleChanged { get; set; }
#endregion
[Inject] public IDialogService DialogService { get; set; }
#region Content
/// <summary>
/// Define the dialog title as a renderfragment (overrides GlyphTitle)
/// </summary>
[Parameter]
[Category(CategoryTypes.Dialog.Behavior)]
public RenderFragment TitleContent { get; set; }
public DefaultFocus DefaultFocus { get; set; }
/// <summary>
/// Define the dialog body here
/// Define the dialog title as a renderfragment (overrides GlyphTitle)
/// </summary>
[Parameter]
[Category(CategoryTypes.Dialog.Behavior)]
public RenderFragment DialogContent { get; set; }
public RenderFragment TitleContent { get; set; }
/// <summary>
/// Define the action buttons here
/// Define the dialog body here
/// </summary>
[Parameter]
[Category(CategoryTypes.Dialog.Behavior)]
public RenderFragment DialogActions { get; set; }
public RenderFragment DialogContent { get; set; }
/// <summary>
/// Default options to pass to Show(), if none are explicitly provided.
/// Typically useful on inline dialogs.
/// Define the action buttons here
/// </summary>
[Parameter]
[Category(CategoryTypes.Dialog.Misc)] // Behavior and Appearance
public DialogOptions Options { get; set; }
[Parameter]
[Category(CategoryTypes.Dialog.Behavior)]
public Action OnBackdropClick { get; set; }
public RenderFragment DialogActions { get; set; }
#endregion
#region Styling
/// <summary>
/// No padding at the sides
/// </summary>
[Parameter]
[Category(CategoryTypes.Dialog.Appearance)]
public bool DisableSidePadding { get; set; }
/// <summary>
/// CSS class that will be applied to the dialog content
/// </summary>
@ -101,23 +103,56 @@ public partial class Dialog : UIComponent
}
}
private bool _isVisible;
protected string ContentClass => new CssBuilder("dialog-content")
.AddClass($"dialog-no-side-padding", DisableSidePadding)
.AddClass(ClassContent)
.Build();
protected string ActionClass => new CssBuilder("dialog-actions")
.AddClass(ClassActions)
.Build();
#endregion
#region Behavior
/// <summary>
/// Raised when the inline dialog's display status changes.
/// Used for forwarding state changes from inlined dialog to its instance
/// </summary>
[Parameter] public EventCallback<bool> IsVisibleChanged { get; set; }
internal void ForceUpdate()
{
StateHasChanged();
}
/// <summary>
/// Define the dialog title as a renderfragment (overrides GlyphTitle)
/// Default options to pass to Show(), if none are explicitly provided.
/// Typically useful on inline dialogs.
/// </summary>
[Parameter]
[Category(CategoryTypes.Dialog.Behavior)]
public DefaultFocus DefaultFocus { get; set; }
[Category(CategoryTypes.Dialog.Misc)] // Behavior and Appearance
public DialogOptions Options { get; set; }
/// <summary>
/// Close the currently open inlined dialog
/// </summary>
/// <param name="result"></param>
public void Close(DialogResult result = null)
{
if (!IsInline || _reference == null)
return;
_reference.Close(result);
_reference = null;
}
private bool IsInline => DialogInstance == null;
private IDialogReference _reference;
#endregion
#region Lifecycle
protected override void OnInitialized()
{
base.OnInitialized();
DialogInstance?.Register(this);
}
/// <summary>
/// Show this inlined dialog
@ -170,30 +205,6 @@ public partial class Dialog : UIComponent
}
base.OnAfterRender(firstRender);
}
#endregion
/// <summary>
/// Used for forwarding state changes from inlined dialog to its instance
/// </summary>
internal void ForceUpdate()
{
StateHasChanged();
}
/// <summary>
/// Close the currently open inlined dialog
/// </summary>
/// <param name="result"></param>
public void Close(DialogResult result = null)
{
if (!IsInline || _reference == null)
return;
_reference.Close(result);
_reference = null;
}
protected override void OnInitialized()
{
base.OnInitialized();
DialogInstance?.Register(this);
}
}

@ -12,29 +12,52 @@ namespace Connected.Components;
public partial class DialogInstance : UIComponent, IDisposable
{
#region Variables
private DialogOptions _options = new();
private string _elementId = "dialog_" + Guid.NewGuid().ToString().Substring(0, 8);
private IKeyInterceptor _keyInterceptor;
[Inject] private IKeyInterceptorFactory _keyInterceptorFactory { get; set; }
[CascadingParameter(Name = "RightToLeft")] public bool RightToLeft { get; set; }
[CascadingParameter] private DialogProvider Parent { get; set; }
[CascadingParameter] private DialogOptions GlobalDialogOptions { get; set; } = new DialogOptions();
private Dialog _dialog;
private bool _disposedValue;
#endregion
[Parameter]
[Category(CategoryTypes.Dialog.Misc)] // Behavior and Appearance
public DialogOptions Options
#region Events
private void HandleBackgroundClick()
{
get
if (DisableBackdropClick)
return;
if (_dialog?.OnBackdropClick == null)
{
if (_options == null)
_options = new DialogOptions();
return _options;
Cancel();
return;
}
set => _options = value;
_dialog?.OnBackdropClick.Invoke();
}
/// <summary>
/// Cancels all dialogs in dialog provider collection.
/// </summary>
public void CancelAll()
{
Parent?.DismissAll();
}
public void Register(Dialog dialog)
{
if (dialog == null)
return;
_dialog = dialog;
//AdditionalClassList = dialog.AdditionalClassList;
TitleContent = dialog.TitleContent;
StateHasChanged();
}
#endregion
#region Content
[Parameter]
[Category(CategoryTypes.Dialog.Behavior)]
public string Title { get; set; }
@ -46,10 +69,129 @@ public partial class DialogInstance : UIComponent, IDisposable
[Parameter]
[Category(CategoryTypes.Dialog.Behavior)]
public RenderFragment Content { get; set; }
[Parameter]
[Category(CategoryTypes.Dialog.Behavior)]
public Guid Id { get; set; }
#endregion
#region Styling
public void ForceRender()
{
StateHasChanged();
}
private string SetPosition()
{
DialogPosition position;
if (Options.Position.HasValue)
{
position = Options.Position.Value;
}
else if (GlobalDialogOptions.Position.HasValue)
{
position = GlobalDialogOptions.Position.Value;
}
else
{
position = DialogPosition.Center;
}
return $"dialog-{position.ToDescription()}";
}
private bool SetHideHeader()
{
if (Options.NoHeader.HasValue)
return Options.NoHeader.Value;
if (GlobalDialogOptions.NoHeader.HasValue)
return GlobalDialogOptions.NoHeader.Value;
return false;
}
private bool SetCloseButton()
{
if (Options.CloseButton.HasValue)
return Options.CloseButton.Value;
if (GlobalDialogOptions.CloseButton.HasValue)
return GlobalDialogOptions.CloseButton.Value;
return false;
}
private bool SetDisableBackdropClick()
{
if (Options.DisableBackdropClick.HasValue)
return Options.DisableBackdropClick.Value;
if (GlobalDialogOptions.DisableBackdropClick.HasValue)
return GlobalDialogOptions.DisableBackdropClick.Value;
return false;
}
private bool SetCloseOnEscapeKey()
{
if (Options.CloseOnEscapeKey.HasValue)
return Options.CloseOnEscapeKey.Value;
if (GlobalDialogOptions.CloseOnEscapeKey.HasValue)
return GlobalDialogOptions.CloseOnEscapeKey.Value;
return false;
}
private string SetMaxWidth()
{
MaxWidth maxWidth;
if (Options.MaxWidth.HasValue)
{
maxWidth = Options.MaxWidth.Value;
}
else if (GlobalDialogOptions.MaxWidth.HasValue)
{
maxWidth = GlobalDialogOptions.MaxWidth.Value;
}
else
{
maxWidth = MaxWidth.Small;
}
return $"dialog-width-{maxWidth.ToDescription()}";
}
private bool SetFullWidth()
{
if (Options.FullWidth.HasValue)
return Options.FullWidth.Value;
if (GlobalDialogOptions.FullWidth.HasValue)
return GlobalDialogOptions.FullWidth.Value;
return false;
}
private bool SetFulScreen()
{
if (Options.FullScreen.HasValue)
return Options.FullScreen.Value;
if (GlobalDialogOptions.FullScreen.HasValue)
return GlobalDialogOptions.FullScreen.Value;
return false;
}
protected string Classname =>
new CssBuilder("dialog")
.AddClass(DialogMaxWidth, !FullScreen)
.AddClass("dialog-width-full", FullWidth && !FullScreen)
.AddClass("dialog-fullscreen", FullScreen)
.AddClass("dialog-rtl", RightToLeft)
.AddClass(_dialog?.AdditionalClassList)
.Build();
/// <summary>
/// Custom close icon.
@ -66,35 +208,22 @@ public partial class DialogInstance : UIComponent, IDisposable
private bool CloseButton { get; set; }
private bool FullScreen { get; set; }
private bool FullWidth { get; set; }
protected override void OnInitialized()
{
ConfigureInstance();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
//Since CloseOnEscapeKey is the only thing to be handled, turn interceptor off
if (CloseOnEscapeKey)
[CascadingParameter(Name = "RightToLeft")] public bool RightToLeft { get; set; }
[Parameter]
[Category(CategoryTypes.Dialog.Misc)] // Behavior and Appearance
public DialogOptions Options
{
_keyInterceptor = _keyInterceptorFactory.Create();
await _keyInterceptor.Connect(_elementId, new KeyInterceptorOptions()
get
{
TargetClass = "dialog",
Keys = {
new KeyOptions { Key="Escape", SubscribeDown = true },
},
});
_keyInterceptor.KeyDown += HandleKeyDown;
}
if (_options == null)
_options = new DialogOptions();
return _options;
}
await base.OnAfterRenderAsync(firstRender);
set => _options = value;
}
#endregion
#region Behavior
internal void HandleKeyDown(KeyboardEventArgs args)
{
switch (args.Key)
@ -175,157 +304,34 @@ public partial class DialogInstance : UIComponent, IDisposable
//AdditionalClassList = Classname;
}
private string SetPosition()
{
DialogPosition position;
if (Options.Position.HasValue)
{
position = Options.Position.Value;
}
else if (GlobalDialogOptions.Position.HasValue)
{
position = GlobalDialogOptions.Position.Value;
}
else
{
position = DialogPosition.Center;
}
return $"dialog-{position.ToDescription()}";
}
private string SetMaxWidth()
{
MaxWidth maxWidth;
if (Options.MaxWidth.HasValue)
{
maxWidth = Options.MaxWidth.Value;
}
else if (GlobalDialogOptions.MaxWidth.HasValue)
{
maxWidth = GlobalDialogOptions.MaxWidth.Value;
}
else
{
maxWidth = MaxWidth.Small;
}
return $"dialog-width-{maxWidth.ToDescription()}";
}
private bool SetFullWidth()
{
if (Options.FullWidth.HasValue)
return Options.FullWidth.Value;
if (GlobalDialogOptions.FullWidth.HasValue)
return GlobalDialogOptions.FullWidth.Value;
return false;
}
private bool SetFulScreen()
{
if (Options.FullScreen.HasValue)
return Options.FullScreen.Value;
if (GlobalDialogOptions.FullScreen.HasValue)
return GlobalDialogOptions.FullScreen.Value;
return false;
}
protected string Classname =>
new CssBuilder("dialog")
.AddClass(DialogMaxWidth, !FullScreen)
.AddClass("dialog-width-full", FullWidth && !FullScreen)
.AddClass("dialog-fullscreen", FullScreen)
.AddClass("dialog-rtl", RightToLeft)
.AddClass(_dialog?.AdditionalClassList)
.Build();
private bool SetHideHeader()
{
if (Options.NoHeader.HasValue)
return Options.NoHeader.Value;
if (GlobalDialogOptions.NoHeader.HasValue)
return GlobalDialogOptions.NoHeader.Value;
return false;
}
private bool SetCloseButton()
{
if (Options.CloseButton.HasValue)
return Options.CloseButton.Value;
#endregion
if (GlobalDialogOptions.CloseButton.HasValue)
return GlobalDialogOptions.CloseButton.Value;
return false;
}
private bool SetDisableBackdropClick()
#region Lifecycle
protected override void OnInitialized()
{
if (Options.DisableBackdropClick.HasValue)
return Options.DisableBackdropClick.Value;
if (GlobalDialogOptions.DisableBackdropClick.HasValue)
return GlobalDialogOptions.DisableBackdropClick.Value;
return false;
ConfigureInstance();
}
private bool SetCloseOnEscapeKey()
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (Options.CloseOnEscapeKey.HasValue)
return Options.CloseOnEscapeKey.Value;
if (GlobalDialogOptions.CloseOnEscapeKey.HasValue)
return GlobalDialogOptions.CloseOnEscapeKey.Value;
return false;
}
private void HandleBackgroundClick()
if (firstRender)
{
if (DisableBackdropClick)
return;
if (_dialog?.OnBackdropClick == null)
//Since CloseOnEscapeKey is the only thing to be handled, turn interceptor off
if (CloseOnEscapeKey)
{
Cancel();
return;
}
_dialog?.OnBackdropClick.Invoke();
}
private Dialog _dialog;
private bool _disposedValue;
_keyInterceptor = _keyInterceptorFactory.Create();
public void Register(Dialog dialog)
await _keyInterceptor.Connect(_elementId, new KeyInterceptorOptions()
{
if (dialog == null)
return;
_dialog = dialog;
//AdditionalClassList = dialog.AdditionalClassList;
TitleContent = dialog.TitleContent;
StateHasChanged();
TargetClass = "dialog",
Keys = {
new KeyOptions { Key="Escape", SubscribeDown = true },
},
});
_keyInterceptor.KeyDown += HandleKeyDown;
}
public void ForceRender()
{
StateHasChanged();
}
/// <summary>
/// Cancels all dialogs in dialog provider collection.
/// </summary>
public void CancelAll()
{
Parent?.DismissAll();
await base.OnAfterRenderAsync(firstRender);
}
protected virtual void Dispose(bool disposing)
@ -350,4 +356,6 @@ public partial class DialogInstance : UIComponent, IDisposable
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
#endregion
}

@ -15,9 +15,10 @@ namespace Connected.Components;
public partial class DialogProvider : IDisposable
{
#region Variables
[Inject] private IDialogService DialogService { get; set; }
[Inject] private NavigationManager NavigationManager { get; set; }
[Parameter][Category(CategoryTypes.Dialog.Behavior)] public bool? NoHeader { get; set; }
[Parameter][Category(CategoryTypes.Dialog.Behavior)] public bool? CloseButton { get; set; }
[Parameter][Category(CategoryTypes.Dialog.Behavior)] public bool? DisableBackdropClick { get; set; }
@ -28,35 +29,14 @@ public partial class DialogProvider : IDisposable
private readonly Collection<IDialogReference> _dialogs = new();
private readonly DialogOptions _globalDialogOptions = new();
#endregion
protected override void OnInitialized()
{
DialogService.OnDialogInstanceAdded += AddInstance;
DialogService.OnDialogCloseRequested += DismissInstance;
NavigationManager.LocationChanged += LocationChanged;
_globalDialogOptions.DisableBackdropClick = DisableBackdropClick;
_globalDialogOptions.CloseOnEscapeKey = CloseOnEscapeKey;
_globalDialogOptions.CloseButton = CloseButton;
_globalDialogOptions.NoHeader = NoHeader;
_globalDialogOptions.Position = Position;
_globalDialogOptions.FullWidth = FullWidth;
_globalDialogOptions.MaxWidth = MaxWidth;
}
protected override Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender)
{
foreach (var dialogReference in _dialogs.Where(x => !x.Result.IsCompleted))
#region Events
private void AddInstance(IDialogReference dialog)
{
dialogReference.RenderCompleteTaskCompletionSource.TrySetResult(true);
}
}
return base.OnAfterRenderAsync(firstRender);
_dialogs.Add(dialog);
StateHasChanged();
}
internal void DismissInstance(Guid id, DialogResult result)
{
var reference = GetDialogReference(id);
@ -64,12 +44,6 @@ public partial class DialogProvider : IDisposable
DismissInstance(reference, result);
}
private void AddInstance(IDialogReference dialog)
{
_dialogs.Add(dialog);
StateHasChanged();
}
public void DismissAll()
{
_dialogs.ToList().ForEach(r => DismissInstance(r, DialogResult.Cancel()));
@ -93,6 +67,36 @@ public partial class DialogProvider : IDisposable
{
DismissAll();
}
#endregion
#region Lifecycle
protected override void OnInitialized()
{
DialogService.OnDialogInstanceAdded += AddInstance;
DialogService.OnDialogCloseRequested += DismissInstance;
NavigationManager.LocationChanged += LocationChanged;
_globalDialogOptions.DisableBackdropClick = DisableBackdropClick;
_globalDialogOptions.CloseOnEscapeKey = CloseOnEscapeKey;
_globalDialogOptions.CloseButton = CloseButton;
_globalDialogOptions.NoHeader = NoHeader;
_globalDialogOptions.Position = Position;
_globalDialogOptions.FullWidth = FullWidth;
_globalDialogOptions.MaxWidth = MaxWidth;
}
protected override Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender)
{
foreach (var dialogReference in _dialogs.Where(x => !x.Result.IsCompleted))
{
dialogReference.RenderCompleteTaskCompletionSource.TrySetResult(true);
}
}
return base.OnAfterRenderAsync(firstRender);
}
public void Dispose()
{
@ -105,4 +109,7 @@ public partial class DialogProvider : IDisposable
DialogService.OnDialogCloseRequested -= DismissInstance;
}
}
#endregion
}

@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Components;
namespace Connected.Components;
public partial class Divider : UIComponent
{
#region Styling
protected string Classname =>
new CssBuilder("divider")
.AddClass($"divider-absolute", Absolute)
@ -50,4 +51,6 @@ public partial class Divider : UIComponent
[Parameter]
[Category(CategoryTypes.Divider.Appearance)]
public DividerType DividerType { get; set; } = DividerType.FullWidth;
#endregion
}

@ -7,6 +7,7 @@ namespace Connected.Components;
public partial class Drawer : UIComponent, IDisposable, INavigationEventReceiver
{
#region Variables
private double _height;
private ElementReference _contentRef;
private DrawerClipMode _clipMode;
@ -21,8 +22,16 @@ public partial class Drawer : UIComponent, IDisposable, INavigationEventReceiver
private Breakpoint _screenBreakpoint = Breakpoint.None;
private Guid _breakpointListenerSubscriptionId;
#region EventCallbacks
#endregion
#region Events
private void CloseDrawer()
{
if (Open)
{
OpenChanged.InvokeAsync(false);
}
}
[Parameter] public EventCallback<bool> OpenChanged { get; set; }
@ -351,13 +360,7 @@ public partial class Drawer : UIComponent, IDisposable, INavigationEventReceiver
OpenChanged.InvokeAsync(_open);
}
}
private void CloseDrawer()
{
if (Open)
{
OpenChanged.InvokeAsync(false);
}
}
/// <summary>
/// Width of left/right drawer. Only for non-fixed drawers.

@ -6,16 +6,33 @@ namespace Connected.Components;
public partial class DrawerContainer : UIComponent
{
#region Variables
protected bool Fixed { get; set; } = false;
private List<Drawer> _drawers = new();
#endregion
#region Event callbacks
#region Events
internal void FireDrawersChanged() => StateHasChanged();
internal void Add(Drawer drawer)
{
if (Fixed && !drawer.Fixed)
return;
_drawers.Add(drawer);
StateHasChanged();
}
internal void Remove(Drawer drawer)
{
_drawers.Remove(drawer);
StateHasChanged();
}
#endregion
#region Content placeholders
#region Content
[CascadingParameter(Name = "RightToLeft")] public bool RightToLeft { get; set; }
@ -83,7 +100,7 @@ public partial class DrawerContainer : UIComponent
#endregion
#region Styling properties
#region Styling
protected virtual CssBuilder CompiledClassList
{
@ -142,18 +159,5 @@ public partial class DrawerContainer : UIComponent
#endregion
internal void Add(Drawer drawer)
{
if (Fixed && !drawer.Fixed)
return;
_drawers.Add(drawer);
StateHasChanged();
}
internal void Remove(Drawer drawer)
{
_drawers.Remove(drawer);
StateHasChanged();
}
}

@ -4,10 +4,8 @@ using Microsoft.AspNetCore.Components;
namespace Connected.Components;
public partial class DrawerHeader : UIComponent
{
#region Event callbacks
#endregion
#region Content placeholders
#region Content
/// <summary>
/// If true, compact padding will be used, same as the Appbar.
@ -29,7 +27,7 @@ public partial class DrawerHeader : UIComponent
#endregion
#region Styling properties
#region Styling
/// <summary>
/// A space separated list of class names, added on top of the default class list.
@ -37,11 +35,13 @@ public partial class DrawerHeader : UIComponent
[Parameter]
public string? ClassList { get; set; }
/*
/// <summary>
/// A space separated list of class names, added on top of the default class list.
/// </summary>
[Parameter]
public string? StyleList { get; set; }
*/
protected virtual CssBuilder CompiledClassList
{
@ -56,5 +56,4 @@ public partial class DrawerHeader : UIComponent
#endregion
}

@ -10,6 +10,13 @@ namespace Connected.Components;
public class DragAndDropIndexChangedEventArgs : EventArgs
{
#region Variables
public string ZoneIdentifier { get; }
public int Index { get; }
public string OldZoneIdentifier { get; }
#endregion
#region Events
public DragAndDropIndexChangedEventArgs(string zoneIdentifier, string oldZoneIdentifier, int index)
{
ZoneIdentifier = zoneIdentifier;
@ -17,41 +24,17 @@ public class DragAndDropIndexChangedEventArgs : EventArgs
OldZoneIdentifier = oldZoneIdentifier;
}
public string ZoneIdentifier { get; }
public int Index { get; }
public string OldZoneIdentifier { get; }
#endregion
}
/// <summary>
/// Used to encapsulate data for a drag and drop transaction
/// </summary>
/// <typeparam name="T"></typeparam>
public class DragAndDropItemTransaction<T>
{
#region Variables
private Func<Task> _commitCallback;
private Func<Task> _cancelCallback;
#endregion
/// <summary>
/// The Item that is dragged during the transaction
/// </summary>
public T Item { get; init; }
/// <summary>
/// The index of the item in the current drop zone
/// </summary>
public int Index { get; private set; }
/// <summary>
/// The index of the item when the transaction started
/// </summary>
public int SourceIndex { get; private set; }
/// <summary>
/// Identifier for drop zone where the transaction started
/// </summary>
public string SourceZoneIdentifier { get; init; }
public string CurrentZone { get; private set; }
#region Events
/// <summary>
/// create a new instance of a drag and drop transaction encapsulating the item and source
@ -101,8 +84,53 @@ public class DragAndDropItemTransaction<T>
Index = -1;
return true;
}
#endregion
#region Content
/// <summary>
/// The Item that is dragged during the transaction
/// </summary>
public T Item { get; init; }
/// <summary>
/// The index of the item in the current drop zone
/// </summary>
public int Index { get; private set; }
/// <summary>
/// The index of the item when the transaction started
/// </summary>
public int SourceIndex { get; private set; }
/// <summary>
/// Identifier for drop zone where the transaction started
/// </summary>
public string SourceZoneIdentifier { get; init; }
public string CurrentZone { get; private set; }
#endregion
#region Styling
#endregion
#region Behavior
#endregion
#region Lifecycle
#endregion
}
/// <summary>
/// Used to encapsulate data for a drag and drop transaction
/// </summary>
/// <typeparam name="T"></typeparam>
/// <summary>
/// Record encaplusalting data regaring a completed transaction
/// </summary>

Loading…
Cancel
Save