// Copyright (c) MudBlazor 2021
// MudBlazor licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Connected.Annotations;
using Connected.Utilities;
using Microsoft.AspNetCore.Components;
namespace Connected.Components;
public class DragAndDropIndexChangedEventArgs : EventArgs
{
public DragAndDropIndexChangedEventArgs(string zoneIdentifier, string oldZoneIdentifier, int index)
{
ZoneIdentifier = zoneIdentifier;
Index = index;
OldZoneIdentifier = oldZoneIdentifier;
}
public string ZoneIdentifier { get; }
public int Index { get; }
public string OldZoneIdentifier { get; }
}
///
/// Used to encapsulate data for a drag and drop transaction
///
///
public class DragAndDropItemTransaction
{
private Func _commitCallback;
private Func _cancelCallback;
///
/// The Item that is dragged during the transaction
///
public T Item { get; init; }
///
/// The index of the item in the current drop zone
///
public int Index { get; private set; }
///
/// The index of the item when the transaction started
///
public int SourceIndex { get; private set; }
///
/// Identifier for drop zone where the transaction started
///
public string SourceZoneIdentifier { get; init; }
public string CurrentZone { get; private set; }
///
/// create a new instance of a drag and drop transaction encapsulating the item and source
///
/// The item of this transaction
/// The identifier of the drop zone, where the transaction started
/// The source index
/// A callback that is invokde when the transaction has been successful
/// A callback that is inviked when the transaction has been cancelled
public DragAndDropItemTransaction(T item, string identifier, int index, Func commitCallback, Func cancelCallback)
{
Item = item;
SourceZoneIdentifier = identifier;
CurrentZone = identifier;
Index = index;
SourceIndex = index;
_commitCallback = commitCallback;
_cancelCallback = cancelCallback;
}
///
/// Cancel the transaction
///
///
public async Task Cancel() => await _cancelCallback.Invoke();
///
/// Commit this transaction as succesful
///
///
public async Task Commit() => await _commitCallback.Invoke();
internal bool UpdateIndex(int index)
{
if (Index == index) { return false; }
Index = index;
return true;
}
internal bool UpdateZone(string idenfifer)
{
if (CurrentZone == idenfifer) { return false; }
CurrentZone = idenfifer;
Index = -1;
return true;
}
}
///
/// Record encaplusalting data regaring a completed transaction
///
/// Type of dragged item
/// The dragged item during the transaction
/// Identifier of the zone where the transaction started
/// The index of the item within in the dropzone
public record ItemDropInfo(T Item, string DropzoneIdentifier, int IndexInZone);
public class DragAndDropTransactionFinishedEventArgs : EventArgs
{
public DragAndDropTransactionFinishedEventArgs(DragAndDropItemTransaction transaction) :
this(string.Empty, false, transaction)
{
}
public DragAndDropTransactionFinishedEventArgs(string destinationDropzoneIdentifier, bool success, DragAndDropItemTransaction transaction)
{
Item = transaction.Item;
Success = success;
OriginatedDropzoneIdentifier = transaction.SourceZoneIdentifier;
DestinationDropzoneIdentifier = destinationDropzoneIdentifier;
OriginIndex = transaction.SourceIndex;
DestinationIndex = transaction.Index;
}
public T Item { get; }
public bool Success { get; }
public string OriginatedDropzoneIdentifier { get; }
public string DestinationDropzoneIdentifier { get; }
public int OriginIndex { get; }
public int DestinationIndex { get; }
}
///
/// The container of a drag and drop zones
///
/// Datetype of items
public partial class DropContainer : UIComponent
{
private DragAndDropItemTransaction _transaction;
protected string Classname =>
new CssBuilder("mud-drop-container")
.AddClass(Class)
.Build();
///
/// Child content of component. This should include the drop zones
///
[Parameter]
[Category(CategoryTypes.DropZone.Appearance)]
public RenderFragment ChildContent { get; set; }
///
/// The items that can be drag and dropped within the container
///
[Parameter]
[Category(CategoryTypes.DropZone.Items)]
public IEnumerable Items { get; set; }
///
/// The render fragment (template) that should be used to render the items within a drop zone
///
[Parameter]
[Category(CategoryTypes.DropZone.Items)]
public RenderFragment ItemRenderer { get; set; }
///
/// The method is used to determinate if an item can be dropped within a drop zone
///
[Parameter]
[Category(CategoryTypes.DropZone.Items)]
public Func ItemsSelector { get; set; }
///
/// Callback that indicates that an item has been dropped on a drop zone. Should be used to update the "status" of the data item
///
[Parameter]
[Category(CategoryTypes.DropZone.Items)]
public EventCallback> ItemDropped { get; set; }
///
/// The method is used to determinate if an item can be dropped within a drop zone
///
[Parameter]
[Category(CategoryTypes.DropZone.DropRules)]
public Func CanDrop { get; set; }
///
/// The CSS class(es), that is applied to drop zones that are a valid target for drag and drop transaction
///
[Parameter]
[Category(CategoryTypes.DropZone.DropRules)]
public string CanDropClass { get; set; }
///
/// The CSS class(es), that is applied to drop zones that are NOT valid target for drag and drop transaction
///
[Parameter]
[Category(CategoryTypes.DropZone.DropRules)]
public string NoDropClass { get; set; }
///
/// If true, drop classes CanDropClass or NoDropClass or applied as soon, as a transaction has started
///
[Parameter]
[Category(CategoryTypes.DropZone.DropRules)]
public bool ApplyDropClassesOnDragStarted { get; set; } = false;
///
/// The method is used to determinate if an item should be disabled for dragging. Defaults to allow all items
///
[Parameter]
[Category(CategoryTypes.DropZone.Disabled)]
public Func ItemIsDisabled { get; set; }
///
/// If a drop item is disabled (determinate by ). This class is applied to the element
///
[Parameter]
[Category(CategoryTypes.DropZone.Disabled)]
public string DisabledClass { get; set; } = "disabled";
///
/// An additional class that is applied to the drop zone where a drag operation started
///
[Parameter]
[Category(CategoryTypes.DropZone.DraggingClass)]
public string DraggingClass { get; set; }
///
/// An additional class that is applied to an drop item, when it is dragged
///
[Parameter]
[Category(CategoryTypes.DropZone.DraggingClass)]
public string ItemDraggingClass { get; set; }
public event EventHandler> TransactionStarted;
public event EventHandler TransactionIndexChanged;
public event EventHandler> TransactionEnded;
public event EventHandler RefreshRequested;
public void StartTransaction(T item, string identifier, int index, Func commitCallback, Func cancelCallback)
{
_transaction = new DragAndDropItemTransaction(item, identifier, index, commitCallback, cancelCallback);
TransactionStarted?.Invoke(this, _transaction);
}
public T GetTransactionItem() => _transaction.Item;
public bool TransactionInProgress() => _transaction != null;
public string GetTransactionOrignZoneIdentiifer() => _transaction?.SourceZoneIdentifier ?? string.Empty;
public string GetTransactionCurrentZoneIdentiifer() => _transaction?.CurrentZone ?? string.Empty;
public bool IsTransactionOriginatedFromInside(string identifier) => _transaction.SourceZoneIdentifier == identifier;
public int GetTransactionIndex() => _transaction?.Index ?? -1;
public bool IsItemMovedDownwards() => _transaction.Index > _transaction.SourceIndex;
public bool HasTransactionIndexChanged()
{
if (_transaction == null)
{
return false;
}
if (_transaction.CurrentZone != _transaction.SourceZoneIdentifier)
{
return true;
}
return _transaction.Index != _transaction.SourceIndex;
}
public bool IsOrign(int index, string identifier)
{
if (_transaction == null)
{
return false;
}
if (identifier != _transaction.SourceZoneIdentifier)
{
return false;
}
return _transaction.SourceIndex == index || _transaction.SourceIndex - 1 == index;
}
public async Task CommitTransaction(string dropzoneIdentifier, bool reorderIsAllowed)
{
await _transaction.Commit();
var index = -1;
if (reorderIsAllowed == true)
{
index = GetTransactionIndex() + 1;
if (_transaction.SourceZoneIdentifier == _transaction.CurrentZone && IsItemMovedDownwards() == true)
{
index -= 1;
}
}
await ItemDropped.InvokeAsync(new ItemDropInfo(_transaction.Item, dropzoneIdentifier, index));
var transactionFinishedEventArgs = new DragAndDropTransactionFinishedEventArgs(dropzoneIdentifier, true, _transaction);
_transaction = null;
TransactionEnded?.Invoke(this, transactionFinishedEventArgs);
}
public async Task CancelTransaction()
{
await _transaction.Cancel();
var transactionFinishedEventArgs = new DragAndDropTransactionFinishedEventArgs(_transaction);
_transaction = null;
TransactionEnded?.Invoke(this, transactionFinishedEventArgs);
}
public void UpdateTransactionIndex(int index)
{
var changed = _transaction.UpdateIndex(index);
if (changed == false) { return; }
TransactionIndexChanged?.Invoke(this, new DragAndDropIndexChangedEventArgs(_transaction.CurrentZone, _transaction.CurrentZone, _transaction.Index));
}
internal void UpdateTransactionZone(string identifier)
{
var oldValue = _transaction.CurrentZone;
var changed = _transaction.UpdateZone(identifier);
if (changed == false) { return; }
TransactionIndexChanged?.Invoke(this, new DragAndDropIndexChangedEventArgs(_transaction.CurrentZone, oldValue, _transaction.Index));
}
///
/// Refreshes the dropzone and all items within. This is neded in case of adding items to the collection or changed values of items
///
public void Refresh() => RefreshRequested?.Invoke(this, EventArgs.Empty);
}