// 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); }