-
-
Notifications
You must be signed in to change notification settings - Fork 267
Add BitFullCalendar extras component (#12449) #12450
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
base: develop
Are you sure you want to change the base?
Changes from 37 commits
b27f335
df593ab
fdc0e72
64e2d84
6b55009
0124500
b48fff7
fd3e6fb
4a9d1de
897f9cc
44ef70c
4339767
6551fb9
cc3d16b
692dd36
18435b9
f181910
b01f279
d790e39
1c23485
f498381
318c8d5
de8a32a
b7746a6
7087703
6a7360b
e8e16eb
41c0b78
955181c
2d5bbd1
dff228a
45b8421
345c4a7
eda294c
dc60be6
cd3294a
be8a448
1db96b8
184b89b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| @namespace Bit.BlazorUI | ||
| @implements IDisposable | ||
|
|
||
| <div class="bit-bfc-body" @key="(State.Mode, State.View)"> | ||
| @if (State.Mode == BitFullCalendarMode.Timeline) | ||
| { | ||
| switch (State.View) | ||
| { | ||
| case BitFullCalendarView.Day: | ||
| <BitFcTimelineDayView Events="_timelineEvents" EventTemplate="TimelineEventTemplate" /> | ||
| break; | ||
| case BitFullCalendarView.Week: | ||
| <BitFcTimelineWeekView Events="_timelineEvents" EventTemplate="TimelineEventTemplate" /> | ||
| break; | ||
| case BitFullCalendarView.Month: | ||
| <BitFcTimelineMonthView Events="_timelineEvents" EventTemplate="TimelineEventTemplate" /> | ||
| break; | ||
| default: | ||
| <BitFcTimelineWeekView Events="_timelineEvents" EventTemplate="TimelineEventTemplate" /> | ||
| break; | ||
| } | ||
| } | ||
| else | ||
| { | ||
| switch (State.View) | ||
| { | ||
| case BitFullCalendarView.Month: | ||
| <BitFcCalendarMonthView SingleDayEvents="_singleDayEvents" MultiDayEvents="_multiDayEvents" EventTemplate="MonthEventTemplate" /> | ||
| break; | ||
| case BitFullCalendarView.Week: | ||
| <BitFcCalendarWeekView SingleDayEvents="_singleDayEvents" MultiDayEvents="_multiDayEvents" EventTemplate="WeekEventTemplate" /> | ||
| break; | ||
| case BitFullCalendarView.Day: | ||
| <BitFcCalendarDayView SingleDayEvents="_singleDayEvents" MultiDayEvents="_multiDayEvents" EventTemplate="DayEventTemplate" /> | ||
| break; | ||
| case BitFullCalendarView.Year: | ||
| <BitFcCalendarYearView SingleDayEvents="_singleDayEvents" MultiDayEvents="_multiDayEvents" /> | ||
| break; | ||
| case BitFullCalendarView.Agenda: | ||
| <BitFcAgendaEvents /> | ||
| break; | ||
| } | ||
| } | ||
| </div> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| namespace Bit.BlazorUI; | ||
|
|
||
| public partial class BitFcCalendarBody | ||
| { | ||
| [CascadingParameter] public BitFullCalendarState State { get; set; } = default!; | ||
|
|
||
| [Parameter] public RenderFragment<BitFullCalendarEvent>? MonthEventTemplate { get; set; } | ||
| [Parameter] public RenderFragment<BitFullCalendarEvent>? WeekEventTemplate { get; set; } | ||
| [Parameter] public RenderFragment<BitFullCalendarEvent>? DayEventTemplate { get; set; } | ||
| [Parameter] public RenderFragment<BitFullCalendarEvent>? TimelineEventTemplate { get; set; } | ||
|
|
||
| private List<BitFullCalendarEvent> _singleDayEvents = []; | ||
| private List<BitFullCalendarEvent> _multiDayEvents = []; | ||
| private List<BitFullCalendarEvent> _timelineEvents = []; | ||
|
|
||
| protected override void OnInitialized() | ||
| { | ||
| State.OnStateChanged += Refresh; | ||
| ComputeEvents(); | ||
| } | ||
|
|
||
| private void Refresh() | ||
| { | ||
| ComputeEvents(); | ||
| InvokeAsync(StateHasChanged); | ||
| } | ||
|
|
||
| private void ComputeEvents() | ||
| { | ||
| _singleDayEvents = State.Events.Where(e => e.IsSingleDay).ToList(); | ||
| _multiDayEvents = State.Events.Where(e => e.IsMultiDay).ToList(); | ||
| _timelineEvents = State.Events.ToList(); | ||
| } | ||
|
|
||
| public void Dispose() => State.OnStateChanged -= Refresh; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| @namespace Bit.BlazorUI | ||
|
|
||
| <div class="bit-bfc-toast-container" aria-live="polite"> | ||
| @foreach (var toast in _toasts) | ||
| { | ||
| <div @key="toast.Id" class="bit-bfc-toast @(toast.IsError ? "error" : "success")" role="@(toast.IsError ? "alert" : "status")"> | ||
| @toast.Message | ||
| </div> | ||
|
msynk marked this conversation as resolved.
|
||
| } | ||
| </div> | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,104 @@ | ||||||||||||||||||||||||||||||||||||||||||||
| namespace Bit.BlazorUI; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| public partial class BitFcCalendarToast : IAsyncDisposable | ||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||
| private readonly List<ToastItem> _toasts = []; | ||||||||||||||||||||||||||||||||||||||||||||
| private readonly List<CancellationTokenSource> _removalTokens = []; | ||||||||||||||||||||||||||||||||||||||||||||
| private readonly object _removalTokensLock = new(); | ||||||||||||||||||||||||||||||||||||||||||||
| private int _nextId; | ||||||||||||||||||||||||||||||||||||||||||||
|
msynk marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| public void Show(string message, bool isError = false) | ||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||
| // Allocate the id atomically: Show can be invoked off the renderer thread (the list mutation | ||||||||||||||||||||||||||||||||||||||||||||
| // is marshalled below, but the id is assigned here on the caller's thread), so a plain | ||||||||||||||||||||||||||||||||||||||||||||
| // _nextId++ could hand out duplicate ids under concurrent calls and break RemoveAfterDelay's | ||||||||||||||||||||||||||||||||||||||||||||
| // per-id removal. | ||||||||||||||||||||||||||||||||||||||||||||
| var item = new ToastItem { Id = Interlocked.Increment(ref _nextId), Message = message, IsError = isError }; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| var cts = new CancellationTokenSource(); | ||||||||||||||||||||||||||||||||||||||||||||
|
msynk marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||||||||||
| lock (_removalTokensLock) | ||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||
| _removalTokens.Add(cts); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| // Marshal the list mutation and render onto the renderer's dispatcher so the whole toast | ||||||||||||||||||||||||||||||||||||||||||||
| // lifecycle (add here, remove in RemoveAfterDelay) stays dispatcher-safe even when Show is | ||||||||||||||||||||||||||||||||||||||||||||
| // invoked from a non-renderer thread. | ||||||||||||||||||||||||||||||||||||||||||||
| _ = InvokeAsync(() => | ||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||
| _toasts.Add(item); | ||||||||||||||||||||||||||||||||||||||||||||
| StateHasChanged(); | ||||||||||||||||||||||||||||||||||||||||||||
| // Start the expiration timer only after the toast has actually been queued into the UI, | ||||||||||||||||||||||||||||||||||||||||||||
| // so the 3s lifetime begins from when it becomes visible rather than from when Show was | ||||||||||||||||||||||||||||||||||||||||||||
| // scheduled (which may run on a non-renderer thread before the add is dispatched). | ||||||||||||||||||||||||||||||||||||||||||||
| _ = RemoveAfterDelay(item.Id, cts); | ||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+31
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win Skip the queued toast add after disposal.
Suggested fix _ = InvokeAsync(() =>
{
+ if (token.IsCancellationRequested)
+ return;
+
_toasts.Add(item);
StateHasChanged();
// Start the expiration timer only after the toast has actually been queued into the UI,📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| private async Task RemoveAfterDelay(int id, CancellationTokenSource cts) | ||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||
| try | ||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||
| try | ||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||
| await Task.Delay(3000, cts.Token); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
msynk marked this conversation as resolved.
Outdated
|
||||||||||||||||||||||||||||||||||||||||||||
| catch (OperationCanceledException) | ||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| // Mutate the toast list on the renderer's dispatcher to avoid racing the template's foreach. | ||||||||||||||||||||||||||||||||||||||||||||
| await InvokeAsync(() => | ||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||
| _toasts.RemoveAll(t => t.Id == id); | ||||||||||||||||||||||||||||||||||||||||||||
| StateHasChanged(); | ||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| finally | ||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||
| // Drop the token as soon as its timer finishes (or is cancelled) so _removalTokens | ||||||||||||||||||||||||||||||||||||||||||||
| // doesn't grow unbounded on long-lived pages that show many toasts. | ||||||||||||||||||||||||||||||||||||||||||||
| bool removed; | ||||||||||||||||||||||||||||||||||||||||||||
| lock (_removalTokensLock) | ||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||
| removed = _removalTokens.Remove(cts); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| if (removed) | ||||||||||||||||||||||||||||||||||||||||||||
| cts.Dispose(); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| public ValueTask DisposeAsync() | ||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||
| // Snapshot under the lock: RemoveAfterDelay also removes/disposes tokens as their timers | ||||||||||||||||||||||||||||||||||||||||||||
| // complete, so reading the live list here could race with that cleanup. | ||||||||||||||||||||||||||||||||||||||||||||
| CancellationTokenSource[] tokens; | ||||||||||||||||||||||||||||||||||||||||||||
| lock (_removalTokensLock) | ||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||
| tokens = _removalTokens.ToArray(); | ||||||||||||||||||||||||||||||||||||||||||||
| _removalTokens.Clear(); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| foreach (var cts in tokens) | ||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||
| try | ||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||
| cts.Cancel(); | ||||||||||||||||||||||||||||||||||||||||||||
| cts.Dispose(); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| catch (ObjectDisposedException) | ||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||
| // Already disposed by RemoveAfterDelay's cleanup; nothing to do. | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| return ValueTask.CompletedTask; | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
msynk marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| private class ToastItem | ||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||
| public int Id { get; set; } | ||||||||||||||||||||||||||||||||||||||||||||
| public string Message { get; set; } = ""; | ||||||||||||||||||||||||||||||||||||||||||||
| public bool IsError { get; set; } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| @namespace Bit.BlazorUI | ||
|
|
||
| <BitCascadingValueProvider ValueList="BuildCascadingValues()"> | ||
| <div class="bit-bfc" dir="@(State.IsRtl ? "rtl" : "ltr")"> | ||
| <BitFcCalendarHeader /> | ||
| <BitFcCalendarBody MonthEventTemplate="MonthEventTemplate" | ||
| WeekEventTemplate="WeekEventTemplate" | ||
| DayEventTemplate="DayEventTemplate" | ||
| TimelineEventTemplate="TimelineEventTemplate" /> | ||
| <BitFcCalendarToast /> | ||
| </div> | ||
| </BitCascadingValueProvider> |
Uh oh!
There was an error while loading. Please reload this page.