Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d7a53da59f | |||
| 6a51a58224 |
@@ -152,7 +152,7 @@
|
|||||||
MinWidth="100" />
|
MinWidth="100" />
|
||||||
<Button Command="{Binding CancelCommand}"
|
<Button Command="{Binding CancelCommand}"
|
||||||
IsCancel="True"
|
IsCancel="True"
|
||||||
Content="ביטול"
|
Content="סגור"
|
||||||
Padding="20,5"
|
Padding="20,5"
|
||||||
MinWidth="100" />
|
MinWidth="100" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Threading;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using MarcusLaw.OutlookAddin.Core.Services;
|
using MarcusLaw.OutlookAddin.Core.Services;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
@@ -18,6 +19,7 @@ namespace MarcusLaw.OutlookAddin.UI.ViewModels
|
|||||||
{
|
{
|
||||||
private readonly IEspoCrmClient? _client;
|
private readonly IEspoCrmClient? _client;
|
||||||
private readonly ILogger? _logger;
|
private readonly ILogger? _logger;
|
||||||
|
private readonly Dispatcher? _dispatcher;
|
||||||
private bool _childrenLoaded;
|
private bool _childrenLoaded;
|
||||||
private bool _loadingStarted;
|
private bool _loadingStarted;
|
||||||
|
|
||||||
@@ -58,12 +60,14 @@ namespace MarcusLaw.OutlookAddin.UI.ViewModels
|
|||||||
public FolderNodeViewModel(
|
public FolderNodeViewModel(
|
||||||
IEspoCrmClient client,
|
IEspoCrmClient client,
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
|
Dispatcher dispatcher,
|
||||||
string name,
|
string name,
|
||||||
string fullPath,
|
string fullPath,
|
||||||
string? relativePath)
|
string? relativePath)
|
||||||
{
|
{
|
||||||
_client = client ?? throw new ArgumentNullException(nameof(client));
|
_client = client ?? throw new ArgumentNullException(nameof(client));
|
||||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
|
_dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
|
||||||
Name = name;
|
Name = name;
|
||||||
FullPath = fullPath;
|
FullPath = fullPath;
|
||||||
RelativePath = relativePath;
|
RelativePath = relativePath;
|
||||||
@@ -133,8 +137,15 @@ namespace MarcusLaw.OutlookAddin.UI.ViewModels
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
Children.Clear();
|
// Marshal onto the Dispatcher: once this node's TreeViewItem
|
||||||
foreach (var child in loaded) Children.Add(child);
|
// exists, Children is bound and must only be mutated on the UI
|
||||||
|
// thread (the network await above can resume on a thread-pool
|
||||||
|
// thread in this VSTO host).
|
||||||
|
OnUi(() =>
|
||||||
|
{
|
||||||
|
Children.Clear();
|
||||||
|
foreach (var child in loaded) Children.Add(child);
|
||||||
|
});
|
||||||
_childrenLoaded = true;
|
_childrenLoaded = true;
|
||||||
IsLoading = false;
|
IsLoading = false;
|
||||||
}
|
}
|
||||||
@@ -144,7 +155,14 @@ namespace MarcusLaw.OutlookAddin.UI.ViewModels
|
|||||||
{
|
{
|
||||||
var fullPath = string.IsNullOrEmpty(FullPath) ? name : FullPath + "/" + name;
|
var fullPath = string.IsNullOrEmpty(FullPath) ? name : FullPath + "/" + name;
|
||||||
var relativePath = IsRoot ? name : RelativePath + "/" + name;
|
var relativePath = IsRoot ? name : RelativePath + "/" + name;
|
||||||
return new FolderNodeViewModel(_client!, _logger!, name, fullPath, relativePath);
|
return new FolderNodeViewModel(_client!, _logger!, _dispatcher!, name, fullPath, relativePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnUi(Action action)
|
||||||
|
{
|
||||||
|
var d = _dispatcher;
|
||||||
|
if (d == null || d.CheckAccess()) action();
|
||||||
|
else d.Invoke(action);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,12 @@ namespace MarcusLaw.OutlookAddin.UI.ViewModels
|
|||||||
|
|
||||||
public sealed partial class ReadingPaneViewModel : ObservableObject
|
public sealed partial class ReadingPaneViewModel : ObservableObject
|
||||||
{
|
{
|
||||||
private readonly IMatchingService _matchingService;
|
// Resolved live on each lookup rather than captured once: the host
|
||||||
|
// rebuilds the CRM stack (new HttpClient + MatchingService) whenever
|
||||||
|
// settings are saved and disposes the old HttpClient. A captured
|
||||||
|
// snapshot would then call into a disposed HttpClient and throw
|
||||||
|
// ObjectDisposedException on every sidebar lookup.
|
||||||
|
private readonly Func<IMatchingService?> _matchingServiceProvider;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private CancellationTokenSource? _activeLookup;
|
private CancellationTokenSource? _activeLookup;
|
||||||
// Stashes the ambiguous MatchResult while the user is drilling into
|
// Stashes the ambiguous MatchResult while the user is drilling into
|
||||||
@@ -94,9 +99,9 @@ namespace MarcusLaw.OutlookAddin.UI.ViewModels
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<string>? OpenInBrowserRequested;
|
public event EventHandler<string>? OpenInBrowserRequested;
|
||||||
|
|
||||||
public ReadingPaneViewModel(IMatchingService matchingService, ILogger logger)
|
public ReadingPaneViewModel(Func<IMatchingService?> matchingServiceProvider, ILogger logger)
|
||||||
{
|
{
|
||||||
_matchingService = matchingService ?? throw new ArgumentNullException(nameof(matchingService));
|
_matchingServiceProvider = matchingServiceProvider ?? throw new ArgumentNullException(nameof(matchingServiceProvider));
|
||||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,12 +167,20 @@ namespace MarcusLaw.OutlookAddin.UI.ViewModels
|
|||||||
_ambiguousReturnTarget = null;
|
_ambiguousReturnTarget = null;
|
||||||
State = ReadingPaneState.Loading;
|
State = ReadingPaneState.Loading;
|
||||||
|
|
||||||
|
var svc = _matchingServiceProvider();
|
||||||
|
if (svc == null)
|
||||||
|
{
|
||||||
|
ErrorMessage = "התוסף עדיין לא מוגדר. עדכן בהגדרות.";
|
||||||
|
State = ReadingPaneState.Error;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var cts = new CancellationTokenSource();
|
var cts = new CancellationTokenSource();
|
||||||
_activeLookup = cts;
|
_activeLookup = cts;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = await _matchingService.LookupAsync(senderEmail, cts.Token).ConfigureAwait(true);
|
var result = await svc.LookupAsync(senderEmail, cts.Token).ConfigureAwait(true);
|
||||||
if (cts.IsCancellationRequested) return;
|
if (cts.IsCancellationRequested) return;
|
||||||
|
|
||||||
if (result == null)
|
if (result == null)
|
||||||
@@ -296,6 +309,14 @@ namespace MarcusLaw.OutlookAddin.UI.ViewModels
|
|||||||
{
|
{
|
||||||
if (candidate == null || string.IsNullOrWhiteSpace(candidate.Id)) return;
|
if (candidate == null || string.IsNullOrWhiteSpace(candidate.Id)) return;
|
||||||
|
|
||||||
|
var svc = _matchingServiceProvider();
|
||||||
|
if (svc == null)
|
||||||
|
{
|
||||||
|
ErrorMessage = "התוסף עדיין לא מוגדר. עדכן בהגדרות.";
|
||||||
|
State = ReadingPaneState.Error;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
CancelInFlight();
|
CancelInFlight();
|
||||||
var cts = new CancellationTokenSource();
|
var cts = new CancellationTokenSource();
|
||||||
_activeLookup = cts;
|
_activeLookup = cts;
|
||||||
@@ -310,7 +331,7 @@ namespace MarcusLaw.OutlookAddin.UI.ViewModels
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = await _matchingService.LookupContactAsync(candidate.Id, rememberedEmail, cts.Token).ConfigureAwait(true);
|
var result = await svc.LookupContactAsync(candidate.Id, rememberedEmail, cts.Token).ConfigureAwait(true);
|
||||||
if (cts.IsCancellationRequested) return;
|
if (cts.IsCancellationRequested) return;
|
||||||
|
|
||||||
if (result == null || result.Contact == null)
|
if (result == null || result.Contact == null)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Threading;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using MarcusLaw.OutlookAddin.Core.Models;
|
using MarcusLaw.OutlookAddin.Core.Models;
|
||||||
@@ -31,6 +32,7 @@ namespace MarcusLaw.OutlookAddin.UI.ViewModels
|
|||||||
|
|
||||||
private readonly IEspoCrmClient _client;
|
private readonly IEspoCrmClient _client;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
private readonly Dispatcher _dispatcher;
|
||||||
private CancellationTokenSource? _searchCts;
|
private CancellationTokenSource? _searchCts;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
@@ -109,6 +111,13 @@ namespace MarcusLaw.OutlookAddin.UI.ViewModels
|
|||||||
{
|
{
|
||||||
_client = client ?? throw new ArgumentNullException(nameof(client));
|
_client = client ?? throw new ArgumentNullException(nameof(client));
|
||||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
|
// Captured on the UI thread (the VM is constructed on the thread
|
||||||
|
// that shows the dialog). Used to marshal ObservableCollection
|
||||||
|
// mutations back onto the Dispatcher — in this VSTO host the WPF
|
||||||
|
// SynchronizationContext is not installed, so ConfigureAwait(true)
|
||||||
|
// resumes on a thread-pool thread and a bound-collection Add throws
|
||||||
|
// "changes ... from a thread different from the Dispatcher thread".
|
||||||
|
_dispatcher = Dispatcher.CurrentDispatcher;
|
||||||
|
|
||||||
ResetFolderTree();
|
ResetFolderTree();
|
||||||
|
|
||||||
@@ -150,10 +159,20 @@ namespace MarcusLaw.OutlookAddin.UI.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Runs <paramref name="action"/> on the UI/Dispatcher thread.</summary>
|
||||||
|
private void OnUi(Action action)
|
||||||
|
{
|
||||||
|
if (_dispatcher.CheckAccess()) action();
|
||||||
|
else _dispatcher.Invoke(action);
|
||||||
|
}
|
||||||
|
|
||||||
private void ResetFolderTree()
|
private void ResetFolderTree()
|
||||||
{
|
{
|
||||||
FolderTree.Clear();
|
OnUi(() =>
|
||||||
SelectedFolder = null;
|
{
|
||||||
|
FolderTree.Clear();
|
||||||
|
SelectedFolder = null;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void OnSearchTextChanged(string value)
|
partial void OnSearchTextChanged(string value)
|
||||||
@@ -234,10 +253,13 @@ namespace MarcusLaw.OutlookAddin.UI.ViewModels
|
|||||||
// default), expanded, and seeded with the default subfolders so
|
// default), expanded, and seeded with the default subfolders so
|
||||||
// they appear even before the share listing returns. Deeper levels
|
// they appear even before the share listing returns. Deeper levels
|
||||||
// load lazily as the user expands them.
|
// load lazily as the user expands them.
|
||||||
var root = new FolderNodeViewModel(_client, _logger, RootSentinel, folderPath, relativePath: null);
|
var root = new FolderNodeViewModel(_client, _logger, _dispatcher, RootSentinel, folderPath, relativePath: null);
|
||||||
FolderTree.Add(root);
|
OnUi(() =>
|
||||||
SelectedFolder = root;
|
{
|
||||||
root.IsSelected = true;
|
FolderTree.Add(root);
|
||||||
|
SelectedFolder = root;
|
||||||
|
root.IsSelected = true;
|
||||||
|
});
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -247,7 +269,7 @@ namespace MarcusLaw.OutlookAddin.UI.ViewModels
|
|||||||
{
|
{
|
||||||
_logger.Warning(ex, "Listing subfolders for case {CaseId} failed", c.Id);
|
_logger.Warning(ex, "Listing subfolders for case {CaseId} failed", c.Id);
|
||||||
}
|
}
|
||||||
root.IsExpanded = true;
|
OnUi(() => root.IsExpanded = true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -33,6 +33,6 @@ using System.Security;
|
|||||||
// You can specify all the values or you can default the Build and Revision Numbers
|
// You can specify all the values or you can default the Build and Revision Numbers
|
||||||
// by using the '*' as shown below:
|
// by using the '*' as shown below:
|
||||||
// [assembly: AssemblyVersion("1.0.*")]
|
// [assembly: AssemblyVersion("1.0.*")]
|
||||||
[assembly: AssemblyVersion("1.2.15.0")]
|
[assembly: AssemblyVersion("1.2.17.0")]
|
||||||
[assembly: AssemblyFileVersion("1.2.15.0")]
|
[assembly: AssemblyFileVersion("1.2.17.0")]
|
||||||
|
|
||||||
|
|||||||
@@ -367,7 +367,6 @@ namespace OutlookAddin.Services
|
|||||||
_sidebarController = new SidebarController(
|
_sidebarController = new SidebarController(
|
||||||
taskPanes,
|
taskPanes,
|
||||||
_outlookApp,
|
_outlookApp,
|
||||||
MatchingService!,
|
|
||||||
this,
|
this,
|
||||||
System.Windows.Threading.Dispatcher.CurrentDispatcher,
|
System.Windows.Threading.Dispatcher.CurrentDispatcher,
|
||||||
Logger);
|
Logger);
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ namespace OutlookAddin.Services
|
|||||||
|
|
||||||
private readonly CustomTaskPaneCollection _taskPanes;
|
private readonly CustomTaskPaneCollection _taskPanes;
|
||||||
private readonly Outlook.Application _app;
|
private readonly Outlook.Application _app;
|
||||||
private readonly IMatchingService _matchingService;
|
|
||||||
private readonly AddInHost _host;
|
private readonly AddInHost _host;
|
||||||
private readonly Dispatcher _dispatcher;
|
private readonly Dispatcher _dispatcher;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
@@ -38,14 +37,12 @@ namespace OutlookAddin.Services
|
|||||||
public SidebarController(
|
public SidebarController(
|
||||||
CustomTaskPaneCollection taskPanes,
|
CustomTaskPaneCollection taskPanes,
|
||||||
Outlook.Application app,
|
Outlook.Application app,
|
||||||
IMatchingService matchingService,
|
|
||||||
AddInHost host,
|
AddInHost host,
|
||||||
Dispatcher dispatcher,
|
Dispatcher dispatcher,
|
||||||
ILogger logger)
|
ILogger logger)
|
||||||
{
|
{
|
||||||
_taskPanes = taskPanes ?? throw new ArgumentNullException(nameof(taskPanes));
|
_taskPanes = taskPanes ?? throw new ArgumentNullException(nameof(taskPanes));
|
||||||
_app = app ?? throw new ArgumentNullException(nameof(app));
|
_app = app ?? throw new ArgumentNullException(nameof(app));
|
||||||
_matchingService = matchingService ?? throw new ArgumentNullException(nameof(matchingService));
|
|
||||||
_host = host ?? throw new ArgumentNullException(nameof(host));
|
_host = host ?? throw new ArgumentNullException(nameof(host));
|
||||||
_dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
|
_dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
|
||||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
@@ -83,7 +80,7 @@ namespace OutlookAddin.Services
|
|||||||
if (_bindings.ContainsKey(key)) return;
|
if (_bindings.ContainsKey(key)) return;
|
||||||
|
|
||||||
var view = new ReadingPaneView();
|
var view = new ReadingPaneView();
|
||||||
var vm = new ReadingPaneViewModel(_matchingService, _logger)
|
var vm = new ReadingPaneViewModel(() => _host.MatchingService, _logger)
|
||||||
{
|
{
|
||||||
EspoCrmBaseUrl = _host.Settings.EspoCrmUrl
|
EspoCrmBaseUrl = _host.Settings.EspoCrmUrl
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user