-
Notifications
You must be signed in to change notification settings - Fork 80
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(csharp/ExcelAddIn): ExcelAddIn demo v6: Respond to demo feedback (…
…#6030) Respond to demo feedback, make a variety of changes.
- Loading branch information
Showing
17 changed files
with
666 additions
and
311 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
122 changes: 47 additions & 75 deletions
122
csharp/ExcelAddIn/factories/ConnectionManagerDialogFactory.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,102 +1,74 @@ | ||
using Deephaven.ExcelAddIn.Viewmodels; | ||
using System.Collections.Concurrent; | ||
using Deephaven.ExcelAddIn.Managers; | ||
using Deephaven.ExcelAddIn.Viewmodels; | ||
using Deephaven.ExcelAddIn.ViewModels; | ||
using Deephaven.ExcelAddIn.Views; | ||
using System.Diagnostics; | ||
using Deephaven.ExcelAddIn.Models; | ||
using Deephaven.ExcelAddIn.Util; | ||
|
||
namespace Deephaven.ExcelAddIn.Factories; | ||
|
||
internal static class ConnectionManagerDialogFactory { | ||
public static void CreateAndShow(StateManager sm) { | ||
var rowToManager = new ConcurrentDictionary<ConnectionManagerDialogRow, ConnectionManagerDialogRowManager>(); | ||
|
||
// The "new" button creates a "New/Edit Credentials" dialog | ||
void OnNewButtonClicked() { | ||
var cvm = CredentialsDialogViewModel.OfEmpty(); | ||
var dialog = CredentialsDialogFactory.Create(sm, cvm); | ||
dialog.Show(); | ||
} | ||
|
||
var cmDialog = new ConnectionManagerDialog(OnNewButtonClicked); | ||
cmDialog.Show(); | ||
var cmso = new ConnectionManagerSessionObserver(sm, cmDialog); | ||
var disposer = sm.SubscribeToSessions(cmso); | ||
|
||
cmDialog.Closed += (_, _) => { | ||
disposer.Dispose(); | ||
cmso.Dispose(); | ||
}; | ||
} | ||
} | ||
|
||
internal class ConnectionManagerSessionObserver( | ||
StateManager stateManager, | ||
ConnectionManagerDialog cmDialog) : IObserver<AddOrRemove<EndpointId>>, IDisposable { | ||
private readonly List<IDisposable> _disposables = new(); | ||
|
||
public void OnNext(AddOrRemove<EndpointId> aor) { | ||
if (!aor.IsAdd) { | ||
// TODO(kosak) | ||
Debug.WriteLine("Remove is not handled"); | ||
return; | ||
void OnDeleteButtonClicked(ConnectionManagerDialogRow[] rows) { | ||
foreach (var row in rows) { | ||
if (!rowToManager.TryGetValue(row, out var manager)) { | ||
continue; | ||
} | ||
manager.DoDelete(); | ||
} | ||
} | ||
|
||
var endpointId = aor.Value; | ||
void OnReconnectButtonClicked(ConnectionManagerDialogRow[] rows) { | ||
foreach (var row in rows) { | ||
if (!rowToManager.TryGetValue(row, out var manager)) { | ||
continue; | ||
} | ||
manager.DoReconnect(); | ||
} | ||
} | ||
|
||
var statusRow = new ConnectionManagerDialogRow(endpointId.Id, stateManager); | ||
// We watch for session and credential state changes in our ID | ||
var sessDisposable = stateManager.SubscribeToSession(endpointId, statusRow); | ||
var credDisposable = stateManager.SubscribeToCredentials(endpointId, statusRow); | ||
void OnMakeDefaultButtonClicked(ConnectionManagerDialogRow[] rows) { | ||
// Make the last selected row the default | ||
if (rows.Length == 0) { | ||
return; | ||
} | ||
|
||
// And we also watch for credentials changes in the default session (just to keep | ||
// track of whether we are still the default) | ||
var dct = new DefaultCredentialsTracker(statusRow); | ||
var defaultCredDisposable = stateManager.SubscribeToDefaultCredentials(dct); | ||
var row = rows[^1]; | ||
if (!rowToManager.TryGetValue(row, out var manager)) { | ||
return; | ||
} | ||
|
||
// We'll do our AddRow on the GUI thread, and, while we're on the GUI thread, we'll add | ||
// our disposables to our saved disposables. | ||
cmDialog.Invoke(() => { | ||
_disposables.Add(sessDisposable); | ||
_disposables.Add(credDisposable); | ||
_disposables.Add(defaultCredDisposable); | ||
cmDialog.AddRow(statusRow); | ||
}); | ||
} | ||
manager.DoSetAsDefault(); | ||
} | ||
|
||
public void Dispose() { | ||
// Since the GUI thread is where we added these disposables, the GUI thread is where we will | ||
// access and dispose them. | ||
cmDialog.Invoke(() => { | ||
var temp = _disposables.ToArray(); | ||
_disposables.Clear(); | ||
foreach (var disposable in temp) { | ||
disposable.Dispose(); | ||
void OnEditButtonClicked(ConnectionManagerDialogRow[] rows) { | ||
foreach (var row in rows) { | ||
if (!rowToManager.TryGetValue(row, out var manager)) { | ||
continue; | ||
} | ||
manager.DoEdit(); | ||
} | ||
}); | ||
} | ||
} | ||
|
||
public void OnCompleted() { | ||
// TODO(kosak) | ||
throw new NotImplementedException(); | ||
} | ||
var cmDialog = new ConnectionManagerDialog(OnNewButtonClicked, OnDeleteButtonClicked, | ||
OnReconnectButtonClicked, OnMakeDefaultButtonClicked, OnEditButtonClicked); | ||
cmDialog.Show(); | ||
var dm = new ConnectionManagerDialogManager(cmDialog, rowToManager, sm); | ||
var disposer = sm.SubscribeToSessions(dm); | ||
|
||
public void OnError(Exception error) { | ||
// TODO(kosak) | ||
throw new NotImplementedException(); | ||
cmDialog.Closed += (_, _) => { | ||
disposer.Dispose(); | ||
dm.Dispose(); | ||
}; | ||
} | ||
} | ||
|
||
internal class DefaultCredentialsTracker(ConnectionManagerDialogRow statusRow) : IObserver<StatusOr<CredentialsBase>> { | ||
public void OnNext(StatusOr<CredentialsBase> value) { | ||
statusRow.SetDefaultCredentials(value); | ||
} | ||
|
||
public void OnCompleted() { | ||
// TODO(kosak) | ||
throw new NotImplementedException(); | ||
} | ||
|
||
public void OnError(Exception error) { | ||
// TODO(kosak) | ||
throw new NotImplementedException(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
66 changes: 66 additions & 0 deletions
66
csharp/ExcelAddIn/managers/ConnectionManagerDialogManager.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
using System.Collections.Concurrent; | ||
using Deephaven.ExcelAddIn.Models; | ||
using Deephaven.ExcelAddIn.Viewmodels; | ||
using Deephaven.ExcelAddIn.Views; | ||
using System.Diagnostics; | ||
using Deephaven.ExcelAddIn.Util; | ||
|
||
namespace Deephaven.ExcelAddIn.Managers; | ||
|
||
internal class ConnectionManagerDialogManager( | ||
ConnectionManagerDialog cmDialog, | ||
ConcurrentDictionary<ConnectionManagerDialogRow, ConnectionManagerDialogRowManager> rowToManager, | ||
StateManager stateManager) : IObserver<AddOrRemove<EndpointId>>, IDisposable { | ||
private readonly WorkerThread _workerThread = stateManager.WorkerThread; | ||
private readonly Dictionary<EndpointId, ConnectionManagerDialogRow> _idToRow = new(); | ||
private readonly List<IDisposable> _disposables = new(); | ||
|
||
public void OnNext(AddOrRemove<EndpointId> aor) { | ||
if (_workerThread.InvokeIfRequired(() => OnNext(aor))) { | ||
return; | ||
} | ||
|
||
if (aor.IsAdd) { | ||
var endpointId = aor.Value; | ||
var row = new ConnectionManagerDialogRow(endpointId.Id); | ||
var statusRowManager = ConnectionManagerDialogRowManager.Create(row, endpointId, stateManager); | ||
_ = rowToManager.TryAdd(row, statusRowManager); | ||
_idToRow.Add(endpointId, row); | ||
_disposables.Add(statusRowManager); | ||
|
||
cmDialog.AddRow(row); | ||
return; | ||
} | ||
|
||
// Remove! | ||
if (!_idToRow.Remove(aor.Value, out var rowToDelete) || | ||
!rowToManager.TryRemove(rowToDelete, out var rowManager)) { | ||
return; | ||
} | ||
|
||
cmDialog.RemoveRow(rowToDelete); | ||
rowManager.Dispose(); | ||
} | ||
|
||
public void Dispose() { | ||
// Since the GUI thread is where we added these disposables, the GUI thread is where we will | ||
// access and dispose them. | ||
cmDialog.Invoke(() => { | ||
var temp = _disposables.ToArray(); | ||
_disposables.Clear(); | ||
foreach (var disposable in temp) { | ||
disposable.Dispose(); | ||
} | ||
}); | ||
} | ||
|
||
public void OnCompleted() { | ||
// TODO(kosak) | ||
throw new NotImplementedException(); | ||
} | ||
|
||
public void OnError(Exception error) { | ||
// TODO(kosak) | ||
throw new NotImplementedException(); | ||
} | ||
} |
140 changes: 140 additions & 0 deletions
140
csharp/ExcelAddIn/managers/ConnectionManagerDialogRowManager.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
using Deephaven.ExcelAddIn.Factories; | ||
using Deephaven.ExcelAddIn.Models; | ||
using Deephaven.ExcelAddIn.Util; | ||
using Deephaven.ExcelAddIn.Viewmodels; | ||
using Deephaven.ExcelAddIn.ViewModels; | ||
|
||
namespace Deephaven.ExcelAddIn.Managers; | ||
|
||
public sealed class ConnectionManagerDialogRowManager : IObserver<StatusOr<CredentialsBase>>, | ||
IObserver<StatusOr<SessionBase>>, IObserver<ConnectionManagerDialogRowManager.MyWrappedSocb>, IDisposable { | ||
|
||
public static ConnectionManagerDialogRowManager Create(ConnectionManagerDialogRow row, | ||
EndpointId endpointId, StateManager stateManager) { | ||
var result = new ConnectionManagerDialogRowManager(row, endpointId, stateManager); | ||
result.Resubscribe(); | ||
return result; | ||
} | ||
|
||
private readonly ConnectionManagerDialogRow _row; | ||
private readonly EndpointId _endpointId; | ||
private readonly StateManager _stateManager; | ||
private readonly WorkerThread _workerThread; | ||
private readonly List<IDisposable> _disposables = new(); | ||
|
||
private ConnectionManagerDialogRowManager(ConnectionManagerDialogRow row, EndpointId endpointId, | ||
StateManager stateManager) { | ||
_row = row; | ||
_endpointId = endpointId; | ||
_stateManager = stateManager; | ||
_workerThread = stateManager.WorkerThread; | ||
} | ||
|
||
public void Dispose() { | ||
Unsubcribe(); | ||
} | ||
|
||
private void Resubscribe() { | ||
if (_workerThread.InvokeIfRequired(Resubscribe)) { | ||
return; | ||
} | ||
|
||
if (_disposables.Count != 0) { | ||
throw new Exception("State error: already subscribed"); | ||
} | ||
// We watch for session and credential state changes in our ID | ||
var d1 = _stateManager.SubscribeToSession(_endpointId, this); | ||
var d2 = _stateManager.SubscribeToCredentials(_endpointId, this); | ||
// Now we have a problem. We would also like to watch for credential | ||
// state changes in the default session. But the default session | ||
// has the same observable type (IObservable<StatusOr<SessionBase>>) | ||
// as the specific session we are watching. To work around this, | ||
// we create an Observer that translates StatusOr<SessionBase> to | ||
// MyWrappedSOSB and then we subscribe to that. | ||
var converter = ObservableConverter.Create( | ||
(StatusOr<CredentialsBase> socb) => new MyWrappedSocb(socb), _workerThread); | ||
var d3 = _stateManager.SubscribeToDefaultCredentials(converter); | ||
var d4 = converter.Subscribe(this); | ||
|
||
_disposables.AddRange(new[] { d1, d2, d3, d4 }); | ||
} | ||
|
||
private void Unsubcribe() { | ||
if (_workerThread.InvokeIfRequired(Unsubcribe)) { | ||
return; | ||
} | ||
var temp = _disposables.ToArray(); | ||
_disposables.Clear(); | ||
|
||
foreach (var disposable in temp) { | ||
disposable.Dispose(); | ||
} | ||
} | ||
|
||
public void OnNext(StatusOr<CredentialsBase> value) { | ||
_row.SetCredentialsSynced(value); | ||
} | ||
|
||
public void OnNext(StatusOr<SessionBase> value) { | ||
_row.SetSessionSynced(value); | ||
} | ||
|
||
public void OnNext(MyWrappedSocb value) { | ||
_row.SetDefaultCredentialsSynced(value.Value); | ||
} | ||
|
||
public void DoEdit() { | ||
var creds = _row.GetCredentialsSynced(); | ||
// If we have valid credentials, then make a populated viewmodel. | ||
// If we don't, then make an empty viewmodel with only Id populated. | ||
var cvm = creds.AcceptVisitor( | ||
crs => CredentialsDialogViewModel.OfIdAndCredentials(_endpointId.Id, crs), | ||
_ => CredentialsDialogViewModel.OfIdButOtherwiseEmpty(_endpointId.Id)); | ||
var cd = CredentialsDialogFactory.Create(_stateManager, cvm); | ||
cd.Show(); | ||
} | ||
|
||
public void DoDelete() { | ||
// Strategy: | ||
// 1. Unsubscribe to everything | ||
// 2. If it turns out that we were the last subscriber to the session, then great, the | ||
// delete can proceed. | ||
// 3. Otherwise (there is some other subscriber to the session), then the delete operation | ||
// should be denied. In that case we restore our state by resubscribing to everything. | ||
Unsubcribe(); | ||
|
||
_stateManager.SwitchOnEmpty(_endpointId, () => { }, Resubscribe); | ||
} | ||
|
||
public void DoReconnect() { | ||
_stateManager.Reconnect(_endpointId); | ||
} | ||
|
||
public void DoSetAsDefault() { | ||
// If the connection is already the default, do nothing. | ||
if (_row.IsDefault) { | ||
return; | ||
} | ||
|
||
// If we don't have credentials, then we can't make them the default. | ||
var credentials = _row.GetCredentialsSynced(); | ||
if (!credentials.GetValueOrStatus(out var creds, out _)) { | ||
return; | ||
} | ||
|
||
_stateManager.SetDefaultCredentials(creds); | ||
} | ||
|
||
public void OnCompleted() { | ||
// TODO(kosak) | ||
throw new NotImplementedException(); | ||
} | ||
|
||
public void OnError(Exception error) { | ||
// TODO(kosak) | ||
throw new NotImplementedException(); | ||
} | ||
|
||
public record MyWrappedSocb(StatusOr<CredentialsBase> Value) { | ||
} | ||
} |
Oops, something went wrong.