Skip to content

Commit

Permalink
Merge pull request #16 from valincius/release-3.0.0
Browse files Browse the repository at this point in the history
Release 3.0.0 - API overhaul
  • Loading branch information
valincius committed Sep 27, 2021
2 parents f0b71c9 + 4655708 commit 5b3d5fd
Show file tree
Hide file tree
Showing 36 changed files with 763 additions and 406 deletions.
6 changes: 0 additions & 6 deletions BlazorScheduler.sln
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorScheduler", "BlazorSc
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DemoApp", "DemoApp\DemoApp.csproj", "{5FA9FE4D-C4C7-4A49-A10B-5B84E15D33E7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorScheduler.Core", "BlazorSchedulerCore\BlazorScheduler.Core.csproj", "{3F0AABE4-9274-4C6B-803A-748E80CEFAF1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -23,10 +21,6 @@ Global
{5FA9FE4D-C4C7-4A49-A10B-5B84E15D33E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5FA9FE4D-C4C7-4A49-A10B-5B84E15D33E7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5FA9FE4D-C4C7-4A49-A10B-5B84E15D33E7}.Release|Any CPU.Build.0 = Release|Any CPU
{3F0AABE4-9274-4C6B-803A-748E80CEFAF1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3F0AABE4-9274-4C6B-803A-748E80CEFAF1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3F0AABE4-9274-4C6B-803A-748E80CEFAF1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3F0AABE4-9274-4C6B-803A-748E80CEFAF1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
37 changes: 37 additions & 0 deletions BlazorScheduler/Components/Appointment.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using System;
using System.Threading.Tasks;

namespace BlazorScheduler
{
public partial class Appointment : ComponentBase, IDisposable
{
[CascadingParameter] public Scheduler Scheduler { get; set; }

[Parameter] public RenderFragment ChildContent { get; set; }

[Parameter] public Func<Task> OnClick { get; set; }

[Parameter] public DateTime Start { get; set; }
[Parameter] public DateTime End { get; set; }
[Parameter] public string Color { get; set; }

protected override void OnInitialized()
{
Scheduler.AddAppointment(this);
base.OnInitialized();
}

public void Click(MouseEventArgs e)
{
OnClick?.Invoke();
}

public void Dispose()
{
Scheduler.RemoveAppointment(this);
GC.SuppressFinalize(this);
}
}
}
71 changes: 47 additions & 24 deletions BlazorScheduler/Components/Scheduler.razor
Original file line number Diff line number Diff line change
@@ -1,43 +1,66 @@
@namespace BlazorScheduler
@typeparam T

@using Microsoft.JSInterop
@using BlazorScheduler.Internal
@using System.Globalization

@inject IJSRuntime jsRuntime

<div class="scheduler" style="--theme-color: @ThemeColor.ToRgbString(); --theme-alt-color: @ThemeColor.GetAltColor().ToRgbString();">
<div class="actions">
<button class="btn today" @onclick="() => ChangeMonth()">Today</button>
<div class="navigation">
<button class="btn icon-btn" @onclick="() => ChangeMonth(-1)">
<svg viewBox="0 0 24 24">
<path d="M24 0v24H0V0h24z" fill="none"/>
<path d="M14 7l-5 5 5 5V7z"/>
</svg>
</button>
<span class="selected-month">@MonthDisplay</span>
<button class="btn icon-btn" @onclick="() => ChangeMonth(+1)">
<svg viewBox="0 0 24 24">
<path d="M0 0h24v24H0V0z" fill="none"/>
<path d="M10 17l5-5-5-5v10z"/>
</svg>
</button>
</div>
<span></span>
<div class="scheduler">
<div class="header">
@if (HeaderTemplate != null)
{
@HeaderTemplate(this)
}
else
{
<div class="actions">
<button class="btn today" @onclick="() => ChangeMonth()">@Config.TodayButtonText</button>
<div class="navigation">
<button class="btn icon-btn" @onclick="() => ChangeMonth(-1)">
<svg viewBox="0 0 24 24">
<path d="M24 0v24H0V0h24z" fill="none"/>
<path d="M14 7l-5 5 5 5V7z"/>
</svg>
</button>
<span class="selected-month">@MonthDisplay</span>
<button class="btn icon-btn" @onclick="() => ChangeMonth(+1)">
<svg viewBox="0 0 24 24">
<path d="M0 0h24v24H0V0z" fill="none"/>
<path d="M10 17l5-5-5-5v10z"/>
</svg>
</button>
</div>
@if (_loading)
{
<div class="scheduler-loader" style="--size: 25px; --color1: #666; --color2: #ccc;"></div>
}
else
{
<span></span>
}
</div>
}
</div>
<div class="month">
<div class="week header">
@for (int d=0;d<7;d++)
{
<div class="day-of-month">@Enum.GetName(typeof(DayOfWeek), (DayOfWeek)((int)(StartDayOfWeek+d) % 7))</div>
int dayNumber = (int)(Config.StartDayOfWeek + d) % 7;
string dayName = CultureInfo.CurrentCulture.DateTimeFormat.DayNames[dayNumber];
<div class="day-of-month">
<span class="full-dayname">@dayName</span>
<span class="short-dayname">@dayName.Substring(0, 3)</span>
</div>
}
</div>
<CascadingValue Value="this">
@foreach (var week in GetDateRange().Chunk(7))
<CascadingValue IsFixed="true" Value="this">
@Appointments

@foreach (var week in GetDaysInRange().Chunk(7))
{
var (start, end) = (week.First(), week.Last());
<SchedulerWeek T="T" Start="start" End="end" Appointments="GetAppointments(start, end)" />
<SchedulerWeek Start="start" End="end" Appointments="GetAppointments(start, end)" />
}
</CascadingValue>
</div>
Expand Down
126 changes: 84 additions & 42 deletions BlazorScheduler/Components/Scheduler.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,28 @@
using System.Linq;
using System.Threading.Tasks;
using BlazorScheduler.Internal.Extensions;
using BlazorScheduler.Internal.Components;
using Microsoft.AspNetCore.Components.Web;
using BlazorScheduler.Core;
using System.Drawing;
using BlazorScheduler.Configuration;
using BlazorScheduler.Internal.Components;

namespace BlazorScheduler
{
public partial class Scheduler<T> where T : IAppointment, new()
public partial class Scheduler
{
[Parameter] public List<T> Appointments { get; set; }
[Parameter] public Func<T, Task> OnAddingNewAppointment { get; set; }
[Parameter] public Func<DateTime, Task> OnDayClick { get; set; }
[Parameter] public Func<T, MouseEventArgs, Task> OnAppointmentClick { get; set; }
[Parameter] public Func<IEnumerable<T>, MouseEventArgs, Task> OnOverflowAppointmentClick { get; set; }
[Parameter] public DayOfWeek StartDayOfWeek { get; set; } = DayOfWeek.Sunday;
[Parameter] public Color ThemeColor { get; set; } = Color.Aqua;
[Parameter] public RenderFragment Appointments { get; set; }
[Parameter] public RenderFragment<Scheduler> HeaderTemplate { get; set; }
[Parameter] public RenderFragment<DateTime> DayTemplate { get; set; }

[Parameter] public Func<DateTime, DateTime, Task> OnRequestNewData { get; set; }
[Parameter] public Func<DateTime, DateTime, Task> OnAddingNewAppointment { get; set; }
[Parameter] public Func<DateTime, Task> OnOverflowAppointmentClick { get; set; }

[Parameter] public Config Config { get; set; } = new();

private DotNetObjectReference<Scheduler<T>> ObjectReference;
private DateTime NewAppointmentAnchor;

public DateTime CurrentDate { get; private set; }
public Appointment NewAppointment { get; private set; }

internal event EventHandler OnInvalidate = delegate { };

private string MonthDisplay
{
get
Expand All @@ -41,15 +40,18 @@ private string MonthDisplay
}
}

public T NewAppointment { get; private set; }
private bool DoneDragging = false;
private readonly HashSet<Appointment> _appointments = new();
private DotNetObjectReference<Scheduler> _objReference;
private DateTime _draggingAppointmentAnchor;
private bool _doneDragging = false;
private bool _loading = false;

protected override void OnInitialized()
protected override async Task OnInitializedAsync()
{
ObjectReference = DotNetObjectReference.Create(this);
CurrentDate = DateTime.Today;
_objReference = DotNetObjectReference.Create(this);
await SetCurrentMonth(DateTime.Today);

base.OnInitialized();
await base.OnInitializedAsync();
}

protected override async Task OnAfterRenderAsync(bool firstRender)
Expand All @@ -61,29 +63,69 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
base.OnAfterRender(firstRender);
}

internal void AddAppointment(Appointment appointment)
{
_appointments.Add(appointment);
StateHasChanged();
}

internal void RemoveAppointment(Appointment appointment)
{
_appointments.Remove(appointment);
StateHasChanged();
}

public async Task SetCurrentMonth(DateTime date)
{
CurrentDate = date;
await AttachMouseHandler();
var (start, end) = GetDateRangeForCurrentMonth();
if (OnRequestNewData != null)
{
_loading = true;
StateHasChanged();
await OnRequestNewData(start, end);
_loading = false;
}
StateHasChanged();
}

public void Invalidate()
{
OnInvalidate(this, new EventArgs());
StateHasChanged();
}

private async Task AttachMouseHandler()
{
await jsRuntime.InvokeVoidAsync("attachSchedulerMouseEventsHandler", ObjectReference);
await jsRuntime.InvokeVoidAsync("attachSchedulerMouseEventsHandler", _objReference);
}

private async Task ChangeMonth(int months = 0)
{
CurrentDate = months == 0 ? DateTime.Today : CurrentDate.AddMonths(months);
await AttachMouseHandler();
await SetCurrentMonth(months == 0 ? DateTime.Today : CurrentDate.AddMonths(months));
}

private IEnumerable<DateTime> GetDateRange()
private (DateTime, DateTime) GetDateRangeForCurrentMonth()
{
var startDate = new DateTime(CurrentDate.Year, CurrentDate.Month, 1).GetPrevious(StartDayOfWeek);
var endDate = new DateTime(CurrentDate.Year, CurrentDate.Month, DateTime.DaysInMonth(CurrentDate.Year, CurrentDate.Month)).GetNext((DayOfWeek)((int)(StartDayOfWeek - 1 + 7) % 7));
var startDate = new DateTime(CurrentDate.Year, CurrentDate.Month, 1).GetPrevious(Config.StartDayOfWeek);
var endDate = new DateTime(CurrentDate.Year, CurrentDate.Month, DateTime.DaysInMonth(CurrentDate.Year, CurrentDate.Month))
.GetNext((DayOfWeek)((int)(Config.StartDayOfWeek - 1 + 7) % 7));

return Enumerable.Range(0, 1 + endDate.Subtract(startDate).Days)
.Select(offset => startDate.AddDays(offset));
return (startDate, endDate);
}

private IEnumerable<DateTime> GetDaysInRange()
{
var (start, end) = GetDateRangeForCurrentMonth();
return Enumerable
.Range(0, 1 + end.Subtract(start).Days)
.Select(offset => start.AddDays(offset));
}

private IEnumerable<T> GetAppointments(DateTime start, DateTime end)
private IEnumerable<Appointment> GetAppointments(DateTime start, DateTime end)
{
var appointmentsInTimeframe = Appointments.Where(x => (start, end).Overlaps((x.Start, x.End))).ToList();
var appointmentsInTimeframe = _appointments.Where(x => (start, end).Overlaps((x.Start, x.End))).ToList();
if (NewAppointment is not null && (start, end).Overlaps((NewAppointment.Start, NewAppointment.End)))
{
appointmentsInTimeframe.Add(NewAppointment);
Expand All @@ -94,18 +136,18 @@ private IEnumerable<T> GetAppointments(DateTime start, DateTime end)
.ThenByDescending(x => (x.End - x.Start).Days);
}

public void BeginDrag(SchedulerDay<T> day)
public void BeginDrag(SchedulerDay day)
{
NewAppointment = new T
NewAppointment = new Appointment
{
ChildContent = new RenderFragment(builder => builder.AddContent(0, "New appointment")),
Start = day.Day,
End = day.Day,
Title = "New Appointment",
Color = ThemeColor
Color = Config.ThemeColor
};
DoneDragging = false;
_doneDragging = false;

NewAppointmentAnchor = NewAppointment.Start;
_draggingAppointmentAnchor = NewAppointment.Start;
StateHasChanged();
}

Expand All @@ -114,10 +156,10 @@ public async Task OnMouseUp(int button)
{
if (button == 0)
{
if (NewAppointment is not null && !DoneDragging)
if (NewAppointment is not null && !_doneDragging)
{
DoneDragging = true;
await OnAddingNewAppointment?.Invoke(NewAppointment);
_doneDragging = true;
await OnAddingNewAppointment?.Invoke(NewAppointment.Start, NewAppointment.End);
NewAppointment = default;
StateHasChanged();
}
Expand All @@ -127,10 +169,10 @@ public async Task OnMouseUp(int button)
[JSInvokable]
public void OnMouseMove(string date)
{
if (NewAppointment is not null && !DoneDragging)
if (NewAppointment is not null && !_doneDragging)
{
var day = DateTime.ParseExact(date, "yyyyMMdd", null);
(NewAppointment.Start, NewAppointment.End) = day < NewAppointmentAnchor ? (day, NewAppointmentAnchor) : (NewAppointmentAnchor, day);
(NewAppointment.Start, NewAppointment.End) = day < _draggingAppointmentAnchor ? (day, _draggingAppointmentAnchor) : (_draggingAppointmentAnchor, day);
StateHasChanged();
}
}
Expand Down
8 changes: 7 additions & 1 deletion BlazorScheduler/Configuration/Config.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
namespace BlazorScheduler.Configuration
using System;

namespace BlazorScheduler.Configuration
{
public class Config
{
public bool AlwaysShowYear { get; set; } = true;
public int MaxVisibleAppointmentsPerDay { get; set; } = 5;
public bool DisableDragging { get; set; } = false;
public string ThemeColor { get; set; } = "aqua";
public DayOfWeek StartDayOfWeek { get; set; } = DayOfWeek.Sunday;
public string TodayButtonText { get; set; } = "Today";
public string PlusOthersText { get; set; } = "+ {n} others";
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@typeparam T
@namespace BlazorScheduler.Internal.Components

<div class="appointment @Classes.AsString()"
style="--start: @Start; --end: @End; --order: @Order; background-color: @BackgroundColor; color: @TextColor;"
@onclick="e => Scheduler.OnAppointmentClick(Appointment, e)">
@Appointment.Title
style="--start: @Start; --end: @End; --order: @Order; background-color: @Appointment.Color;"
@onclick="Appointment.Click">
@Appointment.ChildContent
</div>

0 comments on commit 5b3d5fd

Please sign in to comment.