diff --git a/Wino.Calendar.ViewModels/AppShellViewModel.cs b/Wino.Calendar.ViewModels/AppShellViewModel.cs index a938c35c..48b0a88d 100644 --- a/Wino.Calendar.ViewModels/AppShellViewModel.cs +++ b/Wino.Calendar.ViewModels/AppShellViewModel.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Threading; using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Messaging; +using Serilog; using Wino.Calendar.ViewModels.Data; using Wino.Calendar.ViewModels.Interfaces; using Wino.Core.Domain.Collections; @@ -65,6 +67,9 @@ namespace Wino.Calendar.ViewModels public bool IsVerticalCalendar => StatePersistenceService.CalendarDisplayType == CalendarDisplayType.Month; + // For updating account calendars asynchronously. + private SemaphoreSlim _accountCalendarUpdateSemaphoreSlim = new(1); + public AppShellViewModel(IPreferencesService preferencesService, IStatePersistanceService statePersistanceService, IAccountService accountService, @@ -77,6 +82,9 @@ namespace Wino.Calendar.ViewModels _calendarService = calendarService; AccountCalendarStateService = accountCalendarStateService; + AccountCalendarStateService.AccountCalendarSelectionStateChanged += UpdateAccountCalendarRequested; + AccountCalendarStateService.CollectiveAccountGroupSelectionStateChanged += AccountCalendarStateCollectivelyChanged; + NavigationService = navigationService; ServerConnectionManager = serverConnectionManager; PreferencesService = preferencesService; @@ -104,16 +112,33 @@ namespace Wino.Calendar.ViewModels UpdateDateNavigationHeaderItems(); await InitializeAccountCalendarsAsync(); + + TodayClicked(); } - private void AddGroupedAccountCalendarViewModel(GroupedAccountCalendarViewModel groupedAccountCalendarViewModel) + private async void AccountCalendarStateCollectivelyChanged(object sender, GroupedAccountCalendarViewModel e) { - foreach (var calendarViewModel in groupedAccountCalendarViewModel.AccountCalendars) - { - calendarViewModel.CalendarSelectionStateChanged += UpdateAccountCalendarRequested; - } + // When using three-state checkbox, multiple accounts will be selected/unselected at the same time. + // Reporting all these changes one by one to the UI is not efficient and may cause problems in the future. - AccountCalendarStateService.GroupedAccountCalendars.Add(groupedAccountCalendarViewModel); + // Update all calendar states at once. + try + { + await _accountCalendarUpdateSemaphoreSlim.WaitAsync(); + + foreach (var calendar in e.AccountCalendars) + { + await _calendarService.UpdateAccountCalendarAsync(calendar.AccountCalendar).ConfigureAwait(false); + } + } + catch (Exception ex) + { + Log.Error(ex, "Error while waiting for account calendar update semaphore."); + } + finally + { + _accountCalendarUpdateSemaphoreSlim.Release(); + } } private async void UpdateAccountCalendarRequested(object sender, AccountCalendarViewModel e) @@ -121,7 +146,7 @@ namespace Wino.Calendar.ViewModels private async Task InitializeAccountCalendarsAsync() { - await Dispatcher.ExecuteOnUIThread(() => AccountCalendarStateService.GroupedAccountCalendars.Clear()); + await Dispatcher.ExecuteOnUIThread(() => AccountCalendarStateService.ClearGroupedAccountCalendar()); var accounts = await _accountService.GetAccountsAsync().ConfigureAwait(false); @@ -141,7 +166,7 @@ namespace Wino.Calendar.ViewModels await Dispatcher.ExecuteOnUIThread(() => { - AddGroupedAccountCalendarViewModel(groupedAccountCalendarViewModel); + AccountCalendarStateService.AddGroupedAccountCalendar(groupedAccountCalendarViewModel); }); } } @@ -233,12 +258,12 @@ namespace Wino.Calendar.ViewModels private readonly IAccountService _accountService; private readonly ICalendarService _calendarService; - public override void OnPageLoaded() - { - base.OnPageLoaded(); + //public override void OnPageLoaded() + //{ + // base.OnPageLoaded(); - TodayClicked(); - } + // TodayClicked(); + //} #region Commands diff --git a/Wino.Calendar.ViewModels/CalendarPageViewModel.cs b/Wino.Calendar.ViewModels/CalendarPageViewModel.cs index 77cc9e3b..2c9a9605 100644 --- a/Wino.Calendar.ViewModels/CalendarPageViewModel.cs +++ b/Wino.Calendar.ViewModels/CalendarPageViewModel.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using CommunityToolkit.Diagnostics; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Messaging; +using Wino.Calendar.ViewModels.Data; using Wino.Calendar.ViewModels.Interfaces; using Wino.Core.Domain.Collections; using Wino.Core.Domain.Enums; @@ -211,8 +212,6 @@ namespace Wino.Calendar.ViewModels } } - - // Create day ranges for each flip item until we reach the total days to load. int totalFlipItemCount = (int)Math.Ceiling((double)flipLoadRange.TotalDays / eachFlipItemCount); @@ -233,15 +232,7 @@ namespace Wino.Calendar.ViewModels foreach (var renderModel in renderModels) { - foreach (var day in renderModel.CalendarDays) - { - var events = await _calendarService.GetCalendarEventsAsync(Guid.Parse("9ead7613-dacb-4163-8d33-2e32e65008a1"), day.Period.Start, day.Period.End).ConfigureAwait(false); - - foreach (var calendarItem in events) - { - day.EventsCollection.Add(calendarItem); - } - } + await InitializeCalendarEventsAsync(renderModel).ConfigureAwait(false); } CalendarLoadDirection animationDirection = calendarLoadDirection; @@ -326,6 +317,42 @@ namespace Wino.Calendar.ViewModels } } + private async Task InitializeCalendarEventsAsync(DayRangeRenderModel dayRangeRenderModel) + { + // Load for each selected calendar from the state. + var checkedCalendarViewModels = _accountCalendarStateService.GroupedAccountCalendars + .SelectMany(a => a.AccountCalendars) + .Where(b => b.IsChecked); + + foreach (var calendarViewModel in checkedCalendarViewModels) + { + // Check all the events for the given date range and calendar. + // Then find the day representation for all the events returned, and add to the collection. + + var events = await _calendarService.GetCalendarEventsAsync(calendarViewModel, + dayRangeRenderModel.Period.Start, + dayRangeRenderModel.Period.End) + .ConfigureAwait(false); + + foreach (var calendarItem in events) + { + var calendarDayModel = dayRangeRenderModel.CalendarDays.FirstOrDefault(a => a.RepresentingDate.Date == calendarItem.StartTime.Date); + + if (calendarDayModel == null) continue; + + var calendarItemViewModel = new CalendarItemViewModel(calendarItem); + + await ExecuteUIThread(() => + { + // TODO: EventsCollection should not take CalendarItem, but CalendarItemViewModel. + // Enforce it later on. + + calendarDayModel.EventsCollection.Add(calendarItemViewModel); + }); + } + } + } + private async Task TryConsolidateItemsAsync() { // Check if trimming is necessary diff --git a/Wino.Calendar.ViewModels/Data/AccountCalendarViewModel.cs b/Wino.Calendar.ViewModels/Data/AccountCalendarViewModel.cs index 26f687d5..98903ed6 100644 --- a/Wino.Calendar.ViewModels/Data/AccountCalendarViewModel.cs +++ b/Wino.Calendar.ViewModels/Data/AccountCalendarViewModel.cs @@ -8,8 +8,6 @@ namespace Wino.Calendar.ViewModels.Data { public partial class AccountCalendarViewModel : ObservableObject, IAccountCalendar { - public event EventHandler CalendarSelectionStateChanged; - public MailAccount Account { get; } public AccountCalendar AccountCalendar { get; } @@ -24,11 +22,7 @@ namespace Wino.Calendar.ViewModels.Data [ObservableProperty] private bool _isChecked; - partial void OnIsCheckedChanged(bool value) - { - IsExtended = value; - CalendarSelectionStateChanged?.Invoke(this, this); - } + partial void OnIsCheckedChanged(bool value) => IsExtended = value; public string Name { @@ -71,5 +65,6 @@ namespace Wino.Calendar.ViewModels.Data get => AccountCalendar.RemoteCalendarId; set => SetProperty(AccountCalendar.RemoteCalendarId, value, AccountCalendar, (u, r) => u.RemoteCalendarId = r); } + public Guid Id { get => ((IAccountCalendar)AccountCalendar).Id; set => ((IAccountCalendar)AccountCalendar).Id = value; } } } diff --git a/Wino.Calendar.ViewModels/Data/CalendarItemViewModel.cs b/Wino.Calendar.ViewModels/Data/CalendarItemViewModel.cs index c6c2d489..fd150af7 100644 --- a/Wino.Calendar.ViewModels/Data/CalendarItemViewModel.cs +++ b/Wino.Calendar.ViewModels/Data/CalendarItemViewModel.cs @@ -1,13 +1,14 @@ using System; using CommunityToolkit.Mvvm.ComponentModel; using Itenso.TimePeriod; +using Wino.Core.Domain.Entities.Calendar; using Wino.Core.Domain.Interfaces; namespace Wino.Calendar.ViewModels.Data { - public partial class CalendarItemViewModel : ObservableObject, ICalendarItem + public partial class CalendarItemViewModel : ObservableObject, ICalendarItem, ICalendarItemViewModel { - public ICalendarItem CalendarItem { get; } + public CalendarItem CalendarItem { get; } public string Title => CalendarItem.Title; @@ -19,7 +20,9 @@ namespace Wino.Calendar.ViewModels.Data public TimeRange Period => CalendarItem.Period; - public CalendarItemViewModel(ICalendarItem calendarItem) + public IAccountCalendar AssignedCalendar => ((ICalendarItem)CalendarItem).AssignedCalendar; + + public CalendarItemViewModel(CalendarItem calendarItem) { CalendarItem = calendarItem; } diff --git a/Wino.Calendar.ViewModels/Data/GroupedAccountCalendarViewModel.cs b/Wino.Calendar.ViewModels/Data/GroupedAccountCalendarViewModel.cs index 6d13dc78..b789bdab 100644 --- a/Wino.Calendar.ViewModels/Data/GroupedAccountCalendarViewModel.cs +++ b/Wino.Calendar.ViewModels/Data/GroupedAccountCalendarViewModel.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; @@ -9,6 +10,9 @@ namespace Wino.Calendar.ViewModels.Data { public partial class GroupedAccountCalendarViewModel : ObservableObject { + public event EventHandler CollectiveSelectionStateChanged; + public event EventHandler CalendarSelectionStateChanged; + public MailAccount Account { get; } public ObservableCollection AccountCalendars { get; } @@ -59,6 +63,7 @@ namespace Wino.Calendar.ViewModels.Data if (e.PropertyName == nameof(AccountCalendarViewModel.IsChecked)) { ManageIsCheckedState(); + UpdateCalendarCheckedState(viewModel, viewModel.IsChecked, true); } } } @@ -73,6 +78,8 @@ namespace Wino.Calendar.ViewModels.Data private void ManageIsCheckedState() { + if (_isExternalPropChangeBlocked) return; + _isExternalPropChangeBlocked = true; if (AccountCalendars.All(c => c.IsChecked)) @@ -95,22 +102,45 @@ namespace Wino.Calendar.ViewModels.Data { if (_isExternalPropChangeBlocked) return; + // Update is triggered by user on the three-state checkbox. + // We should not report all changes one by one. + + _isExternalPropChangeBlocked = true; + if (newValue == null) { // Only primary calendars must be checked. foreach (var calendar in AccountCalendars) { - calendar.IsChecked = calendar.IsPrimary; + UpdateCalendarCheckedState(calendar, calendar.IsPrimary); } } else { foreach (var calendar in AccountCalendars) { - calendar.IsChecked = newValue.GetValueOrDefault(); + UpdateCalendarCheckedState(calendar, newValue.GetValueOrDefault()); } } + + _isExternalPropChangeBlocked = false; + + CollectiveSelectionStateChanged?.Invoke(this, EventArgs.Empty); + } + + private void UpdateCalendarCheckedState(AccountCalendarViewModel accountCalendarViewModel, bool newValue, bool ignoreValueCheck = false) + { + var currentValue = accountCalendarViewModel.IsChecked; + + if (currentValue == newValue && !ignoreValueCheck) return; + + accountCalendarViewModel.IsChecked = newValue; + + // No need to report. + if (_isExternalPropChangeBlocked == true) return; + + CalendarSelectionStateChanged?.Invoke(this, accountCalendarViewModel); } } } diff --git a/Wino.Calendar.ViewModels/Interfaces/IAccountCalendarStateService.cs b/Wino.Calendar.ViewModels/Interfaces/IAccountCalendarStateService.cs index d97d614b..43afa5c6 100644 --- a/Wino.Calendar.ViewModels/Interfaces/IAccountCalendarStateService.cs +++ b/Wino.Calendar.ViewModels/Interfaces/IAccountCalendarStateService.cs @@ -1,10 +1,21 @@ -using System.Collections.ObjectModel; +using System; +using System.Collections.ObjectModel; using Wino.Calendar.ViewModels.Data; namespace Wino.Calendar.ViewModels.Interfaces { public interface IAccountCalendarStateService { - ObservableCollection GroupedAccountCalendars { get; } + ReadOnlyObservableCollection GroupedAccountCalendars { get; } + + event EventHandler CollectiveAccountGroupSelectionStateChanged; + event EventHandler AccountCalendarSelectionStateChanged; + + public void AddGroupedAccountCalendar(GroupedAccountCalendarViewModel groupedAccountCalendar); + public void RemoveGroupedAccountCalendar(GroupedAccountCalendarViewModel groupedAccountCalendar); + public void ClearGroupedAccountCalendar(); + + public void AddAccountCalendar(AccountCalendarViewModel accountCalendar); + public void RemoveAccountCalendar(AccountCalendarViewModel accountCalendar); } } diff --git a/Wino.Calendar/Services/AccountCalendarStateService.cs b/Wino.Calendar/Services/AccountCalendarStateService.cs index 2520d947..10d211eb 100644 --- a/Wino.Calendar/Services/AccountCalendarStateService.cs +++ b/Wino.Calendar/Services/AccountCalendarStateService.cs @@ -1,13 +1,91 @@ -using System.Collections.ObjectModel; +using System; +using System.Collections.ObjectModel; +using System.Linq; using CommunityToolkit.Mvvm.ComponentModel; using Wino.Calendar.ViewModels.Data; using Wino.Calendar.ViewModels.Interfaces; namespace Wino.Calendar.Services { + /// + /// Encapsulated state manager for collectively managing the state of account calendars. + /// Callers must react to the events to update their state only from this service. + /// public partial class AccountCalendarStateService : ObservableObject, IAccountCalendarStateService { + public event EventHandler CollectiveAccountGroupSelectionStateChanged; + public event EventHandler AccountCalendarSelectionStateChanged; + [ObservableProperty] - private ObservableCollection _groupedAccountCalendars = new ObservableCollection(); + private ReadOnlyObservableCollection groupedAccountCalendars; + + private ObservableCollection _internalGroupedAccountCalendars = new ObservableCollection(); + + public AccountCalendarStateService() + { + GroupedAccountCalendars = new ReadOnlyObservableCollection(_internalGroupedAccountCalendars); + } + + private void SingleGroupCalendarCollectiveStateChanged(object sender, EventArgs e) + => CollectiveAccountGroupSelectionStateChanged?.Invoke(this, sender as GroupedAccountCalendarViewModel); + + private void SingleCalendarSelectionStateChanged(object sender, AccountCalendarViewModel e) + => AccountCalendarSelectionStateChanged?.Invoke(this, e); + + public void AddGroupedAccountCalendar(GroupedAccountCalendarViewModel groupedAccountCalendar) + { + groupedAccountCalendar.CalendarSelectionStateChanged += SingleCalendarSelectionStateChanged; + groupedAccountCalendar.CollectiveSelectionStateChanged += SingleGroupCalendarCollectiveStateChanged; + + _internalGroupedAccountCalendars.Add(groupedAccountCalendar); + } + + public void RemoveGroupedAccountCalendar(GroupedAccountCalendarViewModel groupedAccountCalendar) + { + groupedAccountCalendar.CalendarSelectionStateChanged -= SingleCalendarSelectionStateChanged; + groupedAccountCalendar.CollectiveSelectionStateChanged -= SingleGroupCalendarCollectiveStateChanged; + + _internalGroupedAccountCalendars.Remove(groupedAccountCalendar); + } + + public void ClearGroupedAccountCalendar() + { + foreach (var groupedAccountCalendar in _internalGroupedAccountCalendars) + { + RemoveGroupedAccountCalendar(groupedAccountCalendar); + } + } + + public void AddAccountCalendar(AccountCalendarViewModel accountCalendar) + { + // Find the group that this calendar belongs to. + var group = _internalGroupedAccountCalendars.FirstOrDefault(g => g.Account.Id == accountCalendar.Account.Id); + + if (group == null) + { + // If the group doesn't exist, create it. + group = new GroupedAccountCalendarViewModel(accountCalendar.Account, new[] { accountCalendar }); + AddGroupedAccountCalendar(group); + } + else + { + group.AccountCalendars.Add(accountCalendar); + } + } + + public void RemoveAccountCalendar(AccountCalendarViewModel accountCalendar) + { + var group = _internalGroupedAccountCalendars.FirstOrDefault(g => g.Account.Id == accountCalendar.Account.Id); + + // We don't expect but just in case. + if (group == null) return; + + group.AccountCalendars.Remove(accountCalendar); + + if (group.AccountCalendars.Count == 0) + { + RemoveGroupedAccountCalendar(group); + } + } } } diff --git a/Wino.Core.Domain/Collections/CalendarEventCollection.cs b/Wino.Core.Domain/Collections/CalendarEventCollection.cs index 98352062..42c826de 100644 --- a/Wino.Core.Domain/Collections/CalendarEventCollection.cs +++ b/Wino.Core.Domain/Collections/CalendarEventCollection.cs @@ -1,13 +1,18 @@ -using System.Collections.ObjectModel; +using System; +using System.Collections.ObjectModel; using Wino.Core.Domain.Interfaces; namespace Wino.Core.Domain.Collections { + // TODO: Could be read-only collection in the MVVM package. public class CalendarEventCollection : ObservableRangeCollection { public ObservableCollection AllDayEvents { get; } = new ObservableCollection(); public new void Add(ICalendarItem calendarItem) { + if (calendarItem is not ICalendarItemViewModel) + throw new ArgumentException("CalendarItem must be of type ICalendarItemViewModel", nameof(calendarItem)); + base.Add(calendarItem); if (calendarItem.Period.Duration.TotalMinutes == 1440) @@ -18,6 +23,9 @@ namespace Wino.Core.Domain.Collections public new void Remove(ICalendarItem calendarItem) { + if (calendarItem is not ICalendarItemViewModel) + throw new ArgumentException("CalendarItem must be of type ICalendarItemViewModel", nameof(calendarItem)); + base.Remove(calendarItem); if (calendarItem.Period.Duration.TotalMinutes == 1440) diff --git a/Wino.Core.Domain/Entities/Calendar/CalendarItem.cs b/Wino.Core.Domain/Entities/Calendar/CalendarItem.cs index 66c23f9e..c8168b57 100644 --- a/Wino.Core.Domain/Entities/Calendar/CalendarItem.cs +++ b/Wino.Core.Domain/Entities/Calendar/CalendarItem.cs @@ -24,5 +24,8 @@ namespace Wino.Core.Domain.Entities.Calendar [Ignore] public TimeRange Period => new TimeRange(StartTime.Date, StartTime.Date.AddMinutes(DurationInMinutes)); + + [Ignore] + public IAccountCalendar AssignedCalendar { get; set; } } } diff --git a/Wino.Core.Domain/Interfaces/IAccountCalendar.cs b/Wino.Core.Domain/Interfaces/IAccountCalendar.cs index e388fd63..32c908cb 100644 --- a/Wino.Core.Domain/Interfaces/IAccountCalendar.cs +++ b/Wino.Core.Domain/Interfaces/IAccountCalendar.cs @@ -11,5 +11,6 @@ namespace Wino.Core.Domain.Interfaces Guid AccountId { get; set; } string RemoteCalendarId { get; set; } bool IsExtended { get; set; } + Guid Id { get; set; } } } diff --git a/Wino.Core.Domain/Interfaces/ICalendarItem.cs b/Wino.Core.Domain/Interfaces/ICalendarItem.cs index 99d02e88..adb7b3ef 100644 --- a/Wino.Core.Domain/Interfaces/ICalendarItem.cs +++ b/Wino.Core.Domain/Interfaces/ICalendarItem.cs @@ -10,5 +10,6 @@ namespace Wino.Core.Domain.Interfaces DateTimeOffset StartTime { get; } int DurationInMinutes { get; } TimeRange Period { get; } + IAccountCalendar AssignedCalendar { get; } } } diff --git a/Wino.Core.Domain/Interfaces/ICalendarItemViewModel.cs b/Wino.Core.Domain/Interfaces/ICalendarItemViewModel.cs new file mode 100644 index 00000000..11a1c00f --- /dev/null +++ b/Wino.Core.Domain/Interfaces/ICalendarItemViewModel.cs @@ -0,0 +1,7 @@ +namespace Wino.Core.Domain.Interfaces +{ + /// + /// Temporarily to enforce CalendarItemViewModel. Used in CalendarEventCollection. + /// + public interface ICalendarItemViewModel { } +} diff --git a/Wino.Core.Domain/Interfaces/ICalendarService.cs b/Wino.Core.Domain/Interfaces/ICalendarService.cs index ff937fa0..ddcd9e7e 100644 --- a/Wino.Core.Domain/Interfaces/ICalendarService.cs +++ b/Wino.Core.Domain/Interfaces/ICalendarService.cs @@ -14,6 +14,6 @@ namespace Wino.Core.Domain.Interfaces Task InsertAccountCalendarAsync(AccountCalendar accountCalendar); Task UpdateAccountCalendarAsync(AccountCalendar accountCalendar); Task CreateNewCalendarItemAsync(CalendarItem calendarItem, List attendees); - Task> GetCalendarEventsAsync(Guid calendarId, DateTime rangeStart, DateTime rangeEnd); + Task> GetCalendarEventsAsync(IAccountCalendar calendar, DateTime rangeStart, DateTime rangeEnd); } } diff --git a/Wino.Core.Domain/Models/Calendar/CalendarDayModel.cs b/Wino.Core.Domain/Models/Calendar/CalendarDayModel.cs index 0d0a217c..030aeb3d 100644 --- a/Wino.Core.Domain/Models/Calendar/CalendarDayModel.cs +++ b/Wino.Core.Domain/Models/Calendar/CalendarDayModel.cs @@ -12,6 +12,7 @@ namespace Wino.Core.Domain.Models.Calendar { public TimeRange Period { get; } public CalendarEventCollection EventsCollection { get; } = new CalendarEventCollection(); + public CalendarDayModel(DateTime representingDate, CalendarRenderOptions calendarRenderOptions) { RepresentingDate = representingDate; diff --git a/Wino.Services/CalendarService.cs b/Wino.Services/CalendarService.cs index 8ed9dfa9..86ce4eb4 100644 --- a/Wino.Services/CalendarService.cs +++ b/Wino.Services/CalendarService.cs @@ -71,16 +71,18 @@ namespace Wino.Services }); } - public async Task> GetCalendarEventsAsync(Guid calendarId, DateTime rangeStart, DateTime rangeEnd) + public async Task> GetCalendarEventsAsync(IAccountCalendar calendar, DateTime rangeStart, DateTime rangeEnd) { // TODO: We might need to implement caching here. // I don't know how much of the events we'll have in total, but this logic scans all events every time. - var accountEvents = await Connection.Table().Where(x => x.CalendarId == calendarId).ToListAsync(); - var result = new List(); + var accountEvents = await Connection.Table().Where(x => x.CalendarId == calendar.Id).ToListAsync(); + var result = new List(); foreach (var ev in accountEvents) { + ev.AssignedCalendar = calendar; + // Parse recurrence rules var calendarEvent = new Ical.Net.CalendarComponents.CalendarEvent { diff --git a/Wino.SourceGenerators/Translator/TranslatorGenerator.cs b/Wino.SourceGenerators/Translator/TranslatorGenerator.cs index 54c95012..2d42194f 100644 --- a/Wino.SourceGenerators/Translator/TranslatorGenerator.cs +++ b/Wino.SourceGenerators/Translator/TranslatorGenerator.cs @@ -41,16 +41,17 @@ namespace Wino.Core.SourceGeneration.Translator predicate: static (node, _) => node is ClassDeclarationSyntax, transform: static (context, _) => (ClassDeclarationSyntax)context.TargetNode); - // Get the JSON schema + // Get the JSON schema and track changes var jsonSchema = context.AdditionalTextsProvider .Where(static file => file.Path.EndsWith("en_US\\resources.json")) .Select((text, _) => (text, text.GetText())) - .Collect(); + .Collect() + .WithTrackingName("JsonSchema"); // Combine the JSON schema with the marked classes var combined = classDeclarations.Combine(jsonSchema); - // Generate the source + // Generate the source only when the JSON schema changes context.RegisterSourceOutput(combined, static (spc, source) => Execute(source.Left, source.Right, spc)); }