Skip to content

Scheduler: Selection logic #6098

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 32 commits into from
May 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
8db2346
Selection logic
stsrki May 12, 2025
d090ea6
Listen on document level with JS to cancel selection
stsrki May 12, 2025
716eb95
Comment
stsrki May 12, 2025
e7ae055
Fix selecting css
stsrki May 12, 2025
7b58190
Better transaction handling
stsrki May 12, 2025
35c32da
Merge branch 'dev-scheduler' into dev-scheduler-selection
stsrki May 14, 2025
46fc1e1
Refactor transactions
stsrki May 14, 2025
206f7c0
SlotSelectionMode parameter
stsrki May 14, 2025
bfe4a53
Merge branch 'dev-scheduler' into dev-scheduler-selection
stsrki May 15, 2025
e80e577
Enable selection in demo
stsrki May 15, 2025
4b76759
Listen for document mouse only when selecting
stsrki May 15, 2025
0433da8
Fix SelectionEnded
stsrki May 15, 2025
343d651
Use InvokeSafeVoidAsync in JSModule
stsrki May 15, 2025
65e9c1a
Deselect when outside of valid day column
stsrki May 15, 2025
80da568
Add CSS file
stsrki May 15, 2025
5ae0ace
Optimize slot mouse events to only use them when needed
stsrki May 19, 2025
ddb5b8b
Add extension methods to RenderTreeBuilderExtensions
stsrki May 19, 2025
09d90ac
Manually build slot and items
stsrki May 19, 2025
3f54f06
Add more Attribute overloads
stsrki May 19, 2025
27359e0
Fix TW build error
stsrki May 19, 2025
e19a8ba
null-coalescing
stsrki May 20, 2025
75bea06
Refactor null selection dates
stsrki May 20, 2025
51932ed
Remove unused totalSlots var
stsrki May 20, 2025
b72de0d
One line style
stsrki May 20, 2025
7c96170
Merge branch 'dev-scheduler' into dev-scheduler-selection
stsrki May 20, 2025
e63a597
Add ThrottleDispatcher
stsrki May 20, 2025
788fc4c
Optimize loops with key
stsrki May 20, 2025
99782c9
Remove unused BackgroundColor
stsrki May 21, 2025
33bbeb3
DateUtils for Min and Max
stsrki May 21, 2025
9ba63e0
Unregister previous mouseUpHandler if exists
stsrki May 21, 2025
d1f39c8
Remove unused DraggableAttribute
stsrki May 21, 2025
4979b57
Clean
stsrki May 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Demos/Blazorise.Demo/Pages/Tests/SchedulerPage.razor
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
Data="@Appointments"
@bind-SelectedView="@selectedView"
Draggable
ItemStyling="@OnItemStyling">
ItemStyling="@OnItemStyling"
SlotSelectionMode="SchedulerSlotSelectionMode.Mouse">
<SchedulerToolbar />
<SchedulerViews>
<SchedulerDayView StartTime="@(new TimeOnly( 7, 0 ))" EndTime="@(new TimeOnly( 17, 0 ))" WorkDayStart="@(new TimeOnly( 8, 0 ))" WorkDayEnd="@(new TimeOnly( 16, 0 ))" />
Expand Down
2 changes: 1 addition & 1 deletion Source/Blazorise.Tailwind/Components/Button.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ protected override void BuildRenderTree( RenderTreeBuilder builder )
{
builder.OpenElement( "svg" );

builder.Attribute( "data-accordion-icon", null );
builder.Data( "accordion-icon", null );

builder.Class( CollapseVisible
? "w-6 h-6 shrink-0 rotate-180"
Expand Down
91 changes: 91 additions & 0 deletions Source/Blazorise/Extensions/RenderTreeBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,41 @@ public static RenderTreeBuilder Attribute( this RenderTreeBuilder builder, strin
return builder;
}

public static RenderTreeBuilder Attribute( this RenderTreeBuilder builder, string name, MulticastDelegate value, [CallerLineNumber] int line = 0 )
{
builder.AddAttribute( GetSequence( line ), name, value );

return builder;
}

public static RenderTreeBuilder Attribute( this RenderTreeBuilder builder, string name, bool value, [CallerLineNumber] int line = 0 )
{
builder.AddAttribute( GetSequence( line ), name, value );

return builder;
}

public static RenderTreeBuilder Attribute( this RenderTreeBuilder builder, string name, string value, [CallerLineNumber] int line = 0 )
{
builder.AddAttribute( GetSequence( line ), name, value );

return builder;
}

public static RenderTreeBuilder Attribute( this RenderTreeBuilder builder, string name, EventCallback value, [CallerLineNumber] int line = 0 )
{
builder.AddAttribute( GetSequence( line ), name, value );

return builder;
}

public static RenderTreeBuilder Attribute<TArgument>( this RenderTreeBuilder builder, string name, EventCallback<TArgument> value, [CallerLineNumber] int line = 0 )
{
builder.AddAttribute( GetSequence( line ), name, value );

return builder;
}

public static RenderTreeBuilder OnClick<T>( this RenderTreeBuilder builder, object receiver, EventCallback<T> callback, [CallerLineNumber] int line = 0 )
{
builder.AddAttribute( GetSequence( line ), "onclick", EventCallback.Factory.Create<T>( receiver, callback ) );
Expand All @@ -195,6 +230,62 @@ public static RenderTreeBuilder OnClickPreventDefault( this RenderTreeBuilder bu
return builder;
}

public static RenderTreeBuilder OnMouseEnter<T>( this RenderTreeBuilder builder, object receiver, EventCallback<T> callback, [CallerLineNumber] int line = 0 )
{
builder.AddAttribute( GetSequence( line ), "onmouseenter", EventCallback.Factory.Create<T>( receiver, callback ) );

return builder;
}

public static RenderTreeBuilder OnMouseLeave<T>( this RenderTreeBuilder builder, object receiver, EventCallback<T> callback, [CallerLineNumber] int line = 0 )
{
builder.AddAttribute( GetSequence( line ), "onmouseleave", EventCallback.Factory.Create<T>( receiver, callback ) );

return builder;
}

public static RenderTreeBuilder OnMouseDown<T>( this RenderTreeBuilder builder, object receiver, EventCallback<T> callback, [CallerLineNumber] int line = 0 )
{
builder.AddAttribute( GetSequence( line ), "onmousedown", EventCallback.Factory.Create<T>( receiver, callback ) );

return builder;
}

public static RenderTreeBuilder OnMouseMove<T>( this RenderTreeBuilder builder, object receiver, EventCallback<T> callback, [CallerLineNumber] int line = 0 )
{
builder.AddAttribute( GetSequence( line ), "onmousemove", EventCallback.Factory.Create<T>( receiver, callback ) );

return builder;
}

public static RenderTreeBuilder OnMouseUp<T>( this RenderTreeBuilder builder, object receiver, EventCallback<T> callback, [CallerLineNumber] int line = 0 )
{
builder.AddAttribute( GetSequence( line ), "onmouseup", EventCallback.Factory.Create<T>( receiver, callback ) );

return builder;
}

public static RenderTreeBuilder OnDragEnter<T>( this RenderTreeBuilder builder, object receiver, EventCallback<T> callback, [CallerLineNumber] int line = 0 )
{
builder.AddAttribute( GetSequence( line ), "ondragenter", EventCallback.Factory.Create<T>( receiver, callback ) );

return builder;
}

public static RenderTreeBuilder OnDragLeave<T>( this RenderTreeBuilder builder, object receiver, EventCallback<T> callback, [CallerLineNumber] int line = 0 )
{
builder.AddAttribute( GetSequence( line ), "ondragleave", EventCallback.Factory.Create<T>( receiver, callback ) );

return builder;
}

public static RenderTreeBuilder OnDrop<T>( this RenderTreeBuilder builder, object receiver, EventCallback<T> callback, [CallerLineNumber] int line = 0 )
{
builder.AddAttribute( GetSequence( line ), "ondrop", EventCallback.Factory.Create<T>( receiver, callback ) );

return builder;
}

public static RenderTreeBuilder Content( this RenderTreeBuilder builder, RenderFragment fragment, [CallerLineNumber] int line = 0 )
{
builder.AddContent( GetSequence( line ), fragment );
Expand Down
108 changes: 108 additions & 0 deletions Source/Blazorise/Utilities/Dispatchers/ThrottleDispatcher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#region Using directives
using System;
using System.Threading;
using System.Threading.Tasks;
#endregion

namespace Blazorise.Utilities;

/// <summary>
/// Utility class for throttling the execution of asynchronous tasks.
/// </summary>
public class ThrottleDispatcher
{
#region Members

private readonly int interval;
private readonly bool delayAfterExecution;
private readonly bool resetIntervalOnException;

private readonly object locker = new();

private bool isBusy;
private Task lastTask;
private DateTime? lastInvokeTime;

#endregion

#region Constructors

/// <summary>
/// Initializes a new instance of the <see cref="ThrottleDispatcher"/> class.
/// </summary>
/// <param name="interval">Minimum interval in milliseconds between invocations.</param>
/// <param name="delayAfterExecution">If true, interval starts after task completion; otherwise, at task start.</param>
/// <param name="resetIntervalOnException">If true, resets interval on task failure.</param>
public ThrottleDispatcher( int interval, bool delayAfterExecution = false, bool resetIntervalOnException = false )
{
this.interval = interval;
this.delayAfterExecution = delayAfterExecution;
this.resetIntervalOnException = resetIntervalOnException;
}

#endregion

#region Methods

/// <summary>
/// Throttles the execution of the provided asynchronous function.
/// </summary>
/// <param name="action">The function to execute.</param>
/// <param name="cancellationToken">Optional cancellation token.</param>
/// <returns>The task representing the throttled execution.</returns>
public Task ThrottleAsync( Func<Task> action, CancellationToken cancellationToken = default )
{
lock ( locker )
{
if ( lastTask is not null && ( isBusy || ShouldThrottle() ) )
return lastTask;

isBusy = true;
lastInvokeTime = DateTime.UtcNow;

lastTask = ExecuteAsync( action, cancellationToken );

return lastTask;
}
}

/// <summary>
/// Determines whether the operation should be throttled based on the time elapsed since the last invocation.
/// </summary>
/// <returns><see langword="true"/> if the time elapsed since the last invocation is less than the specified interval; otherwise, <see langword="false"/>.</returns>
private bool ShouldThrottle() =>
lastInvokeTime.HasValue && ( DateTime.UtcNow - lastInvokeTime.Value ).TotalMilliseconds < interval;

private async Task ExecuteAsync( Func<Task> action, CancellationToken cancellationToken )
{
try
{
await action.Invoke();

if ( delayAfterExecution )
lastInvokeTime = DateTime.UtcNow;
}
catch
{
if ( resetIntervalOnException )
{
lock ( locker )
{
lastInvokeTime = null;
lastTask = null;
}
}

throw;
}
finally
{
lock ( locker )
{
isBusy = false;
}
}
}

#endregion
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
var numberOfPreviousItems = Scheduler.CountOverlappingItemsBefore( AllDayItems, itemInfo );
var top = height * numberOfPreviousItems;

<Div Flex="FlexDefault" Style="@GetAllDayHeaderInnerStyle( totalDays, top, height )" @onclick:stopPropagation>
<_SchedulerDayAllDayItem TItem="TItem" Item="@itemInfo.Item" OverflowingFromStart="@itemInfo.OverflowingFromStart" OverflowingOnEnd="@itemInfo.OverflowingOnEnd" IsRecurring="@itemInfo.IsRecurring" Clicked="@OnItemClicked" DragSection="@DragSection" />
<Div @key="@itemInfo.Key" Flex="FlexDefault" Style="@GetAllDayHeaderInnerStyle( totalDays, top, height )" @onclick:stopPropagation>
<_SchedulerDayAllDayItem TItem="TItem" Item="@itemInfo.Item" OverflowingFromStart="@itemInfo.OverflowingFromStart" OverflowingOnEnd="@itemInfo.OverflowingOnEnd" IsRecurring="@itemInfo.IsRecurring" Clicked="@OnItemClicked" Section="@Section" />
</Div>
}
}
Expand Down Expand Up @@ -55,7 +55,7 @@
{
draggingOver = false;

return Scheduler.DropAllDayItem( Date, DragSection );
return Scheduler.DropAllDayItem( Date, Section );
}

string GetAllDayHeaderStyle()
Expand All @@ -68,7 +68,7 @@
return $"position: absolute; left: 0; top: {top}px; width: {totalDays * 100}%; height: {height}px; z-index: 1; cursor: pointer;";
}

private bool IsDraggingOver => draggingOver && DragSection == Scheduler.CurrentDragSection;
private bool IsDraggingOver => draggingOver && Section == Scheduler.CurrentDragSection;

private IFluentBorder BorderColor => IsDraggingOver ? BorderIs1Dark : BorderIs1OnBottomIs1OnStart;

Expand All @@ -88,5 +88,5 @@

[Parameter] public IEnumerable<SchedulerAllDayItemInfo<TItem>> AllDayItems { get; set; }

[Parameter] public SchedulerSection DragSection { get; set; }
[Parameter] public SchedulerSection Section { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ protected Task OnSlotClicked()
/// <param name="item">The item being dragged.</param>
protected Task OnItemDragStart( DragEventArgs e, TItem item )
{
return Scheduler.StartDrag( item, DragSection );
return Scheduler.StartDrag( item, Section );
}

private string GetItemClass( string customClass )
Expand Down Expand Up @@ -120,9 +120,9 @@ private SchedulerItemStyling GetItemStyling( TItem item )
[Parameter] public Func<TItem, Task> Clicked { get; set; }

/// <summary>
/// Gets or sets the drag area to be used when initiating drag operations.
/// Gets or sets the area to be used when initiating transactional operations.
/// </summary>
[Parameter] public SchedulerSection DragSection { get; set; }
[Parameter] public SchedulerSection Section { get; set; }

#endregion
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

var viewItems = GetSlotItemViewsInfo( slotStart, slotEnd ).ToList();

<_SchedulerSlot TItem="TItem" ViewItems="@viewItems" SlotStart="@slotStart" SlotEnd="@slotEnd" SlotClicked="@OnSlotClicked" EditItemClicked="@OnEditItemClicked" DeleteItemClicked="@OnDeleteItemClicked" LastSlot="@lastSlot" ItemCellHeight="@ItemCellHeight" DragSection="@DragSection" />
<_SchedulerSlot @key="@slotIndex" TItem="TItem" ViewItems="@viewItems" SlotStart="@slotStart" SlotEnd="@slotEnd" SlotClicked="@OnSlotClicked" EditItemClicked="@OnEditItemClicked" DeleteItemClicked="@OnDeleteItemClicked" LastSlot="@lastSlot" ItemCellHeight="@ItemCellHeight" Section="@Section" />
}
</Div>
</Div>
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,9 @@ protected TimeSpan GetTime( int slotIndex )
[Parameter] public IEnumerable<SchedulerItemViewInfo<TItem>> ViewItems { get; set; }

/// <summary>
/// Gets or sets the drag area used for appointment dragging.
/// Gets or sets the area to be used when initiating transactional operations.
/// </summary>
[Parameter] public SchedulerSection DragSection { get; set; }
[Parameter] public SchedulerSection Section { get; set; }

#endregion
}
}
Loading
Loading