You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Connected.Components/Components/DropZone/DropZone.razor.cs

436 lines
11 KiB

2 years ago
// 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;
using Microsoft.JSInterop;
namespace Connected.Components;
public partial class DropZone<T> : UIComponent, IDisposable
{
private bool _containerIsInitialized = false;
private bool _canDrop = false;
private bool _dragInProgress = false;
private bool _disposedValue = false;
private Guid _id = Guid.NewGuid();
private Dictionary<T, int> _indicies = new();
[Inject] private IJSRuntime JsRuntime { get; set; }
[CascadingParameter]
protected DropContainer<T> Container { get; set; }
/// <summary>
/// Child content of component
/// </summary>
[Parameter]
[Category(CategoryTypes.DropZone.Appearance)]
public RenderFragment ChildContent { get; set; }
/// <summary>
/// The unique identifier of this drop zone. It is used within transaction to
/// </summary>
[Parameter]
[Category(CategoryTypes.DropZone.Appearance)]
public string Identifier { get; set; }
/// <summary>
/// The render fragment (template) that should be used to render the items within a drop zone. Overrides value provided by drop container
/// </summary>
[Parameter]
[Category(CategoryTypes.DropZone.Items)]
public RenderFragment<T> ItemRenderer { get; set; }
/// <summary>
/// The method is used to determinate if an item can be dropped within a drop zone. Overrides value provided by drop container
/// </summary>
[Parameter]
[Category(CategoryTypes.DropZone.Items)]
public Func<T, bool> ItemsSelector { get; set; }
/// <summary>
/// The method is used to determinate if an item can be dropped within a drop zone. Overrides value provided by drop container
/// </summary>
[Parameter]
[Category(CategoryTypes.DropZone.DropRules)]
public Func<T, bool> CanDrop { get; set; }
/// <summary>
/// The CSS class(es), that is applied to drop zones that are a valid target for drag and drop transaction. Overrides value provided by drop container
/// </summary>
[Parameter]
[Category(CategoryTypes.DropZone.DropRules)]
public string CanDropClass { get; set; }
/// <summary>
/// The CSS class(es), that is applied to drop zones that are NOT valid target for drag and drop transaction. Overrides value provided by drop container
/// </summary>
[Parameter]
[Category(CategoryTypes.DropZone.DropRules)]
public string NoDropClass { get; set; }
/// <summary>
/// If true, drop classes CanDropClass <see cref="CanDropClass"/> or NoDropClass <see cref="NoDropClass"/> or applied as soon, as a transaction has started. Overrides value provided by drop container
/// </summary>
[Parameter]
[Category(CategoryTypes.DropZone.DropRules)]
public bool? ApplyDropClassesOnDragStarted { get; set; }
/// <summary>
/// The method is used to determinate if an item should be disabled for dragging. Defaults to allow all items. Overrides value provided by drop container
/// </summary>
[Parameter]
[Category(CategoryTypes.DropZone.Disabled)]
public Func<T, bool> ItemIsDisabled { get; set; }
/// <summary>
/// If a drop item is disabled (determinate by <see cref="ItemIsDisabled"/>). This class is applied to the element. Overrides value provided by drop container
/// </summary>
[Parameter]
[Category(CategoryTypes.DropZone.Disabled)]
public string DisabledClass { get; set; }
/// <summary>
/// An additional class that is applied to the drop zone where a drag operation started
/// </summary>
[Parameter]
[Category(CategoryTypes.DropZone.DraggingClass)]
public string DraggingClass { get; set; }
/// <summary>
/// An additional class that is applied to an drop item, when it is dragged
/// </summary>
[Parameter]
[Category(CategoryTypes.DropZone.DraggingClass)]
public string ItemDraggingClass { get; set; }
[Parameter]
[Category(CategoryTypes.DropZone.Behavior)]
public bool AllowReorder { get; set; }
/// <summary>
/// If true, will only act as a dropable zone and not render any items.
/// </summary>
[Parameter]
[Category(CategoryTypes.DropZone.Behavior)]
public bool OnlyZone { get; set; }
#region view helper
private int GetItemIndex(T item)
{
if (_indicies.ContainsKey(item) == false)
{
_indicies.Add(item, _indicies.Count);
}
return _indicies[item];
}
private IEnumerable<T> GetItems()
{
Func<T, bool> predicate = (item) => Container.ItemsSelector(item, Identifier ?? string.Empty);
if (ItemsSelector != null)
{
predicate = ItemsSelector;
}
return (Container?.Items ?? Array.Empty<T>()).Where(predicate).OrderBy(x => GetItemIndex(x)).ToArray();
}
private RenderFragment<T> GetItemTemplate() => ItemRenderer ?? Container?.ItemRenderer;
private string GetDragginClass()
{
if (string.IsNullOrEmpty(DraggingClass) == true)
{
return Container?.DraggingClass ?? string.Empty;
}
return DraggingClass;
}
private string GetItemDraggingClass()
{
if (string.IsNullOrEmpty(ItemDraggingClass) == false)
{
return ItemDraggingClass;
}
return Container?.ItemDraggingClass ?? string.Empty;
}
private bool GetApplyDropClassesOnDragStarted() => (ApplyDropClassesOnDragStarted ?? Container?.ApplyDropClassesOnDragStarted) ?? false;
private bool GetItemDisabledStatus(T item)
{
var result = false;
var predicate = ItemIsDisabled ?? Container?.ItemIsDisabled;
if (predicate != null)
{
result = predicate(item);
}
return result;
}
protected string Classname =>
new CssBuilder("mud-drop-zone")
//.AddClass("mud-drop-zone-drag-block", Container?.TransactionInProgress() == true && Container.GetTransactionOrignZoneIdentiifer() != Identifier)
.AddClass(CanDropClass ?? Container?.CanDropClass, Container?.TransactionInProgress() == true && Container.GetTransactionOrignZoneIdentiifer() != Identifier && _canDrop == true && (_dragCounter > 0 || GetApplyDropClassesOnDragStarted() == true))
.AddClass(NoDropClass ?? Container?.NoDropClass, Container?.TransactionInProgress() == true && Container.GetTransactionOrignZoneIdentiifer() != Identifier && _canDrop == false && (_dragCounter > 0 || GetApplyDropClassesOnDragStarted() == true))
.AddClass(GetDragginClass(), _dragInProgress == true)
.AddClass(Class)
.Build();
protected string PlaceholderClassname =>
new CssBuilder("border-2 mud-border-primary border-dashed mud-chip-text mud-chip-color-primary pa-4 mud-dropitem-placeholder")
.AddClass("d-none", AllowReorder == false || (Container?.TransactionInProgress() == false || Container.GetTransactionCurrentZoneIdentiifer() != Identifier))
.Build();
#endregion
#region helper
private (T, bool) ItemCanBeDropped()
{
if (Container == null || Container.TransactionInProgress() == false)
{
return (default(T), false);
}
var item = Container.GetTransactionItem();
var result = true;
if (CanDrop != null)
{
result = CanDrop(item);
}
else if (Container.CanDrop != null)
{
result = Container.CanDrop(item, Identifier);
}
return (item, result);
}
private bool IsOrign(int index) => Container.IsOrign(index, Identifier);
#endregion
#region container event handling
private void Container_TransactionEnded(object sender, DragAndDropTransactionFinishedEventArgs<T> e)
{
_dragCounter = 0;
if (GetApplyDropClassesOnDragStarted() == true)
{
_canDrop = false;
}
if (e.Success == true)
{
if (e.OriginatedDropzoneIdentifier == Identifier && e.DestinationDropzoneIdentifier != e.OriginatedDropzoneIdentifier)
{
_indicies.Remove(e.Item);
}
if (e.OriginatedDropzoneIdentifier == Identifier || e.DestinationDropzoneIdentifier == Identifier)
{
int index = 0;
foreach (var item in _indicies.OrderBy(x => x.Value).ToArray())
{
_indicies[item.Key] = index++;
}
}
}
StateHasChanged();
}
private void Container_TransactionStarted(object sender, DragAndDropItemTransaction<T> e)
{
if (GetApplyDropClassesOnDragStarted() == true)
{
var dropResult = ItemCanBeDropped();
_canDrop = dropResult.Item2;
}
StateHasChanged();
}
private void Container_RefreshRequested(object sender, EventArgs e)
{
_indicies.Clear();
InvokeAsync(StateHasChanged);
}
#endregion
#region handling event callbacks
private int _dragCounter = 0;
private void HandleDragEnter()
{
_dragCounter++;
var (context, isValidZone) = ItemCanBeDropped();
if (context == null)
{
return;
}
_canDrop = isValidZone;
Container.UpdateTransactionZone(Identifier);
}
private void HandleDragLeave()
{
_dragCounter--;
var (context, _) = ItemCanBeDropped();
if (context == null)
{
return;
}
}
private async Task HandleDrop()
{
var (context, isValidZone) = ItemCanBeDropped();
if (context == null)
{
return;
}
_dragCounter = 0;
if (isValidZone == false)
{
await Container.CancelTransaction();
return;
}
if (AllowReorder == true)
{
if (Container.HasTransactionIndexChanged() == true)
{
var newIndex = Container.GetTransactionIndex() + 1;
if (Container.IsTransactionOriginatedFromInside(this.Identifier) == true)
{
var oldIndex = _indicies[context];
if (Container.IsItemMovedDownwards() == true)
{
newIndex -= 1;
foreach (var item in _indicies.Where(x => x.Value >= oldIndex + 1 && x.Value <= newIndex).ToArray())
{
_indicies[item.Key] -= 1;
}
}
else
{
foreach (var item in _indicies.Where(x => x.Value >= newIndex && x.Value < oldIndex).ToArray())
{
_indicies[item.Key] += 1;
}
}
_indicies[context] = newIndex;
}
else
{
foreach (var item in _indicies.Where(x => x.Value >= newIndex).ToArray())
{
_indicies[item.Key] = item.Value + 1;
}
_indicies.Add(context, newIndex);
}
}
}
else
{
_indicies.Clear();
}
await Container.CommitTransaction(Identifier, AllowReorder);
}
private void FinishedDragOperation() => _dragInProgress = false;
private void DragOperationStarted() => _dragInProgress = true;
#endregion
#region life cycle
protected override void OnParametersSet()
{
if (Container != null && _containerIsInitialized == false)
{
_containerIsInitialized = true;
Container.TransactionStarted += Container_TransactionStarted;
Container.TransactionEnded += Container_TransactionEnded;
Container.RefreshRequested += Container_RefreshRequested;
Container.TransactionIndexChanged += Container_TransactionIndexChanged;
}
base.OnParametersSet();
}
private void Container_TransactionIndexChanged(object sender, DragAndDropIndexChangedEventArgs e)
{
if (e.ZoneIdentifier != Identifier && e.OldZoneIdentifier != Identifier) { return; }
StateHasChanged();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender == true)
{
await JsRuntime.InvokeVoidAsyncWithErrorHandling("mudDragAndDrop.initDropZone", _id.ToString());
}
await base.OnAfterRenderAsync(firstRender);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
if (Container != null)
{
Container.TransactionStarted -= Container_TransactionStarted;
Container.TransactionEnded -= Container_TransactionEnded;
Container.RefreshRequested -= Container_RefreshRequested;
Container.TransactionIndexChanged -= Container_TransactionIndexChanged;
}
}
_disposedValue = true;
}
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
#endregion
}