Skip to content
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

Binding to events? #33

Closed
dsyme opened this issue Feb 7, 2018 · 61 comments · Fixed by #126
Closed

Binding to events? #33

dsyme opened this issue Feb 7, 2018 · 61 comments · Fixed by #126

Comments

@dsyme
Copy link

dsyme commented Feb 7, 2018

Hi all

I'm seeing plenty of examples of Xaml where binding to events via code behind is needed, e.g. see below for a snippet of Xamarin code.

My question is - what's the right way to go about this in Elmish.WPF?

From what I've seen there doesn't seem much alternative to writing the code-behind event handler (apart from massive things like this with very intrusive Xaml) - but how would the event handler capture the Elmish dispatch routine?

Xaml:

        <ListView x:Name="ItemsListView" ItemsSource="{Binding [Items]}" ItemSelected="OnItemSelected" ....  >          

code behind:

    async void OnItemSelected(object sender, SelectedItemChangedEventArgs args)
    {
        ...
    }
@2sComplement
Copy link
Collaborator

I haven't yet needed to do this in Elmish.WPF, but in vanilla MVVM you would probably get a reference to the Control's DataContext (its ViewModel) in code-behind and do something with it there. In Elmish.WPF with the ViewModel being a DynamicObject, this should still be possible even if not the cleanest solution (isn't code-behind-to-view-model always a little dirty?).

Stepping back for a second though, in your example you could just hook up a two-way binding to the ListView's SelectedItem no?

@dsyme
Copy link
Author

dsyme commented Feb 7, 2018

@2sComplement Thanks!

Yes, it looks like I could use SelectedItem, but I'm trying to work out whether Elmish.XamarinForms is complete enough to do everything necessary. There are a lot of events in XamarinForms that don't have a corresponding 2-way binding.

@2sComplement
Copy link
Collaborator

@dsyme Ideally I try to keep code-behind limited to view-only types of interactions. I'd be curious to know what other events would be applicable here, where they would need some way of binding to the ViewModel.

@dsyme
Copy link
Author

dsyme commented Feb 7, 2018

@2sComplement Some of the samples may be sloppy Xaml. Another example:

	<ToolbarItem Text="Save" Clicked="Save_Clicked" />

should be:

	<ToolbarItem Text=Save Command= SaveCommand />

But I expect there is a lot of this kind of thing around in samples, including educational ones. Also, in the samples I'm translating I also see event hookup for ItemAppearing, which AFAIK is not accessible via command. More generally, to take an example, the list of events on Xamarin ListView is:

  • ItemAppearing | Occurs when the visual representation of an item is being added to the visual layout.
    -- | --
  • ItemDisappearing | Occurs when the visual representation of an item is being removed from the visual layout.
  • ItemSelected | Event that is raised when a new item is selected.
  • ItemTapped | Event that is raised when an item is tapped.
  • Refreshing | Event that is raised when the list view refreshes.

Both of these make me think that if there's some way to wire up events to commands (i.e. commands stored in the ViewModel) then it would be useful...

@2sComplement
Copy link
Collaborator

System.Windows.Interactivity seems to provide a way of doing this via the InvokeCommandAction class (taken from this blog post):

<Window x:Class="Mm.HandlingEventsMVVM.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
        Title="MainWindow" Height="350" Width="525">
    <StackPanel>
        <Rectangle Fill="Yellow" Stroke="Black" Width="100" Height="100">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="MouseEnter" >
                    <i:InvokeCommandAction Command="{Binding MouseEnterCommand}" />
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </Rectangle>
    </StackPanel>
</Window>

Does this help for Xamarin?

@dsyme
Copy link
Author

dsyme commented Feb 7, 2018

So much xaml :)

We might be able to get Xamarin.Forms to allow

                 ItemSelected=“{Binding [ItemSelected]}”

where the view model property ItemSelected returns a delegate of an appropriate type.

I'm discussing that with the XF people as a possible Xaml extension. I think the implementation might be very simple: just allow value to be a delegate here and just call eventInfo.AddEventHandler(value) directly in that case

@dsyme
Copy link
Author

dsyme commented Feb 7, 2018

@2sComplement If we use the SelectedItem bindable property we get the nasty:

"SelectedItem"|> Binding.twoWay (fun m -> null) (fun v m -> 
    if not (isNull v) then Device.OpenUri(Uri("http://fsharp.org"))
    Ignore ) 

The return of null seems harmless (if we don't do that we'd have to store the SelectedItem in the elmish model, which seems wrong when all we're trying to do is hook into the event to cause an external action (or a navigation action), i.e. a command)

Have you seen this pattern before, i.e. where a change in a property on a UI element is perceived as a command as far as the application is concerned? I suppose each time this happens it's an indication of a missing Command in the UI framework, e.g. Xamarin.Forms doesn't have an ItemSelectedCommand, just the event instead.

@2sComplement
Copy link
Collaborator

Lots of Xaml for sure, but it appears to be the best practice for this specific scenario.

In your example, the event of interest tells you that an item was selected, so to me it makes sense to store that item in your model, and incur any side effects as a result of the selection. If you want to trigger a command from a control inside a ListView, you'd probably create a custom control that has a button or a link or something that actually has a Command dependency property attached to it, and fire off your command from there.

I personally don't run in to this scenario very often; I find that, with a few exceptions, UI events that are not dependency properties are pertinent to view-side code.

@dsyme
Copy link
Author

dsyme commented Feb 7, 2018

Lots of Xaml for sure, but it appears to be the best practice for this specific scenario.

Yes, agreed. Though best practice in Xaml can also mean unusable :) In any case, the XF guys seem open to making this simpler in their variant of Xaml, either via ItemSelectedCommand or making eventing simpler as I mentioned above

you'd probably create a custom control that has a button or a link or something that actually has a Command dependency property attached to it

Yes, that would be a fair approach

@cmeeren
Copy link
Member

cmeeren commented May 8, 2019

I haven't found a good way of doing this, and the "official" approach is the System.Windows.Interactivity method described by @2sComplement in this comment, so I'm closing this.

@cmeeren cmeeren closed this as completed May 8, 2019
@TysonMN
Copy link
Member

TysonMN commented Sep 5, 2019

And the System.Windows.Interactivity DLL is in the Expression.Blend.Sdk NuGet package.

@cmeeren
Copy link
Member

cmeeren commented Sep 5, 2019

I have just installed the System.Windows.Interactivity.WPF package.

@TysonMN
Copy link
Member

TysonMN commented Sep 5, 2019

I tried that one first, but I didn't work for me (before I gave up after one minute of trying). Maybe I did something wrong.

This reminds me. I would like to add more samples, and this is one of the things for which I would like to add a sample. Then Elmish.WPF can have a completely official way to solve this problem :)

@cmeeren
Copy link
Member

cmeeren commented Sep 6, 2019

My bad, you also have to install MicrosoftExpressionInteractions. Check out the new EventBindingsAndBehaviors sample I just added. :)

@TysonMN
Copy link
Member

TysonMN commented Sep 6, 2019

Excellent! I didn't know anything about behaviors, so I learned something new there.

I put a breakpoint in OnDetaching but wasn't able to hit it.

Is there some way to cause OnDetaching to be called in this example?

@cmeeren
Copy link
Member

cmeeren commented Sep 6, 2019

You could use Visibility to hide/show it. You'd have to modify the example. (That might not be a bad idea, actually. Add a button or checkbox that toggles the visibility of the auto-focused textbox, to demonstrate the behaviour. Feel free to create a PR!)

@TysonMN
Copy link
Member

TysonMN commented Sep 7, 2019

I added a button to hide/show the text box. I agree; it is a nice addition. The text box regains focus each time it (re)appears. PR coming soon.

However, OnDetaching still isn't called. Seems like others are also unable to get OnDetaching called.

TysonMN pushed a commit to TysonMN/Elmish.WPF that referenced this issue Sep 7, 2019
@TysonMN
Copy link
Member

TysonMN commented Sep 10, 2019

I am not convinced about the use of both System.Windows.Interactivity.WPF and MicrosoftExpressionInteractions NuGet packages in order to obtain System.Windows.Interactivity.DLL.

When I build the sample in Visual Studio, I get the following build warning.

Found conflicts between different versions of "System.Windows.Interactivity" that could not be resolved. These reference conflicts are listed in the build log when log verbosity is set to detailed.

Here is the relevant section of the verbose output.

There was a conflict between "System.Windows.Interactivity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" and "System.Windows.Interactivity, Version=4.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35".
    "System.Windows.Interactivity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" was chosen because it was primary and "System.Windows.Interactivity, Version=4.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" was not.
    References which depend on "System.Windows.Interactivity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" [C:\Users\twilliams\.nuget\packages\system.windows.interactivity.wpf\2.0.20525\lib\net40\System.Windows.Interactivity.dll].
        C:\Users\twilliams\.nuget\packages\system.windows.interactivity.wpf\2.0.20525\lib\net40\System.Windows.Interactivity.dll
          Project file item includes which caused reference "C:\Users\twilliams\.nuget\packages\system.windows.interactivity.wpf\2.0.20525\lib\net40\System.Windows.Interactivity.dll".
            C:\Users\twilliams\.nuget\packages\system.windows.interactivity.wpf\2.0.20525\lib\net40\System.Windows.Interactivity.dll
    References which depend on "System.Windows.Interactivity, Version=4.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" [].
        C:\Code\ThirdParty\Elmish.WPF\src\Samples\EventBindingsAndBehaviors.Views\bin\Debug\net471\EventBindingsAndBehaviors.Views.dll
          Project file item includes which caused reference "C:\Code\ThirdParty\Elmish.WPF\src\Samples\EventBindingsAndBehaviors.Views\bin\Debug\net471\EventBindingsAndBehaviors.Views.dll".
            C:\Code\ThirdParty\Elmish.WPF\src\Samples\EventBindingsAndBehaviors.Views\bin\Debug\net471\EventBindingsAndBehaviors.Views.dll
        C:\Users\twilliams\.nuget\packages\microsoftexpressioninteractions\3.0.40218\lib\net45\Microsoft.Expression.Interactions.dll
          Project file item includes which caused reference "C:\Users\twilliams\.nuget\packages\microsoftexpressioninteractions\3.0.40218\lib\net45\Microsoft.Expression.Interactions.dll".
            C:\Users\twilliams\.nuget\packages\microsoftexpressioninteractions\3.0.40218\lib\net45\Microsoft.Expression.Interactions.dll

This problem persists even after I clear my NuGet cache.

If I use the Expression.Blend.Sdk NuGet package (as I mentioned above) instead of those two NuGet packages, then there are no build warnings and the sample still works. A disadvantage with depending on that NuGet package is that it is a third-party solution hosting Microsoft DLLs.

As of last year, it appears the correct solution is to depend on Microsoft.Xaml.Behaviors.Wpf. This blog post by Microsoft announcing the release of this NuGet package includes migration steps. I tried it, and it worked for me when adding the NuGet package dependency though Visual Studio and also building with Visual Studio. I would create a PR for this change, but I don't know how .paket works (yet).

@cmeeren
Copy link
Member

cmeeren commented Sep 10, 2019

Thanks! Fixed in 5df8514

@reinux
Copy link

reinux commented Sep 10, 2019

What about for binding to something like the Window.Closing event? The program has to be able to write to the event args to indicate that it doesn't want to close.

@cmeeren
Copy link
Member

cmeeren commented Sep 10, 2019

I haven't tested, but it seems that it should be possible using the same method. Have you tried?

@reinux
Copy link

reinux commented Sep 10, 2019

I've been trying to figure out how it might be done using this mechanism.

Apologies, I'm still new to Elmish.WPF, and I'm having trouble finding specific documentation.

@cmeeren
Copy link
Member

cmeeren commented Sep 10, 2019

Hm, actually, it won't work that way because the mechanism shown above is only for sending messages to the Elmish message loop. There's no way to write to the event args.

I don't have a good solution at the moment. Perhaps you could have the main window be invisible, and simply open a new window using Binding.subModelWin? Then you can control the closing of the window using the Elmish model/messages.

@cmeeren
Copy link
Member

cmeeren commented Sep 11, 2019

For reference, here's a memoization implementation I've used. Perfect for immutable data.

/// Memoizes the last return value by comparing the input using reference equality.
let memoizeOneRef (f: 'a -> 'b) =
  let mutable value = ValueNone
  fun x ->
    match value with
    | ValueSome (arg, ret) when Object.ReferenceEquals(arg, x) -> ret
    | _ ->
        let ret = f x
        value <- ValueSome (x, ret)
        ret

Usage:

// Original, non-memoized function
let myDomainFun x = x + 1

// Memoized version (must be declared in a stable location, i.e. not inline)
let myDomainFunMemoized = memoizeOneRef myDomainFun

// Note - do not do this, since memoizeOneRef will be applied separately
// each time it's called
// let myDomainFunMemoized x = memoizeOneRef myDomainFun x

// Use the memoized function just like you would the non-memoized one
let x = myDomainFunMemoized 2

If you need multiple parameters, either use tuples or write separate higher-arity memoization functions. Note however that memoization functions can be incorrectly used - you can pass a multi-parameter function to memoizeOneRef without compilation errors, and end up with a memoized function return value that memoizes based on the first argument, which for the most part would be incorrect. (In that case, the 'b in 'a -> 'b would simply be some function type.)

@reinux
Copy link

reinux commented Sep 12, 2019

@JDiLenarda

I think your problem is the way you use TaskCompletionSource. This works :

Ah ha! Thanks, that does indeed work.

Goes to show how little I know about Elmish still.

@reinux
Copy link

reinux commented Sep 22, 2019

Couple thoughts...

  • Might it be a good idea to make an overload for subModelWin that allows you to specify subscriptions? It could be useful to be able to subscribe to events when we create new windows.
  • Microsoft.Xaml.Behaviors has CallMethodAction, which could make more sense to bind to than Binding.cmdParam, since it's a lot more straightforward to receive event args. It would mean having to reference EventArgs in bindings, but it's already referencing Window.

@cmeeren
Copy link
Member

cmeeren commented Sep 22, 2019

@reinux Could you share some examples of how you imagine the usage to be according to your suggestions?

@reinux
Copy link

reinux commented Sep 22, 2019

For the first one:

"wstate" |> Binding.subModelWin (
    (fun m -> m.wstate),
    bindings,
    (fun dispatch ->    // Pass in a dispatch function -- can this be done?
        let w = Window ()
        w.MouseWheel.Add (fun e ->
            dispatch <| VolumeUpDown (e.Delta)
        )
        w
    )
)

Being able to see the model from that function right after init could be useful too, though I'm not sure if that might be too conceptually polluting.

The second one:

<b:Interaction.Triggers>
    <b:EventTrigger EventName="MouseWheel">
        <b:CallMethodAction MethodName="MouseWheel" />
    </b:EventTrigger>
</b:Interaction.Triggers>
let bindings () = [
    "MouseWheel" |> Binding.method (fun e m ->
        VolumeUpDown ((e :?> MouseWheelEventArgs).Delta)
]

The usage would be more or less the same as cmdParam and <b:InvokeCommandAction />, but it would allow the actual EventArgs to come through rather than something bound via CommandParameter.

Part of the reason I'm interested in this is that Program.withSubscription is easy enough to work with for the initial window, but after that, there isn't a very straightforward way to bind more handlers.

@cmeeren
Copy link
Member

cmeeren commented Sep 22, 2019

Great suggestions!

My immediate thought on having getWindow accept both dispatch and the current model is that it seems simple, both conceptually and from an implementation standpoint. I'll need to think on it some more.

As for the CallMethodAction stuff: It might be possible, but it would help to have more use-cases that can't be easily solved other ways. Do you have some (more) examples where you can't use e.g. cmdParam?

@reinux
Copy link

reinux commented Sep 22, 2019

Do you have some (more) examples where you can't use e.g. cmdParam?

If there's a way to bind the EventArgs of an event to CommandParameter in Microsoft.Xaml.Behaviors, preferably without having to tinker in code-behind, I think that would suffice. There's a good chance I'm just not familiar enough with WPF Commands to know, though I suspect it would at least be a convoluted solution.

Of course, if subModelWin can dispatch messages, that works just as well too, from a practical standpoint.

In general, I think any event that has numeric parameters that need to be processed along with the model (i.e. in update) can benefit from having the EventArgs being exposed to bindings -- so retrieving the position of a mouse click/tap would be another one, say, in a drawing app.

Will get back to you if I can think of more.

@cmeeren
Copy link
Member

cmeeren commented Sep 23, 2019

If there's a way to bind the EventArgs of an event to CommandParameter in Microsoft.Xaml.Behaviors, preferably without having to tinker in code-behind, I think that would suffice. There's a good chance I'm just not familiar enough with WPF Commands to know, though I suspect it would at least be a convoluted solution.

As far as some cursory searching reveals, there is no built-in way to do this, but it might be possible to create a reusable trigger that passes the event args as command parameters. See e.g. MVVM Light's EventToCommand.

A significant drawback of this method is that you'll have to do unsafe casting of the event args.

Of course, if subModelWin can dispatch messages, that works just as well too, from a practical standpoint.

Yes, I think that would work best, because everything would be strongly typed. Your windows could surface the controls using x:Name, and getWindow could subscribe to the relevant events and dispatch necessary messages.

@reinux
Copy link

reinux commented Sep 24, 2019

See e.g. MVVM Light's EventToCommand.

Cool, will check that out :D

For now, I've resorted to an unholy workaround, specifying the ID associated with a Window by passing it via a global mutable (with the assumption that init and bindings are both called from the UI thread), and having another global function that relays messages to Program.withSubscription.

Nasty, and a little precarious, but I think it'll do for now.

@cmeeren
Copy link
Member

cmeeren commented Sep 24, 2019

@reinux can you give the subModelWin-args branch a try? I have added model and dispatch as parameters to getWindow. Would be great to see if it solves your problems before I release anything.

@reinux
Copy link

reinux commented Sep 24, 2019

@cmeeren Works great! Thanks for taking the time to do this.

One thing I noticed: I haven't come across a situation where this is an issue yet, but Program.withSubscription doesn't have access to the initial model the way subModelWin does, so there's a bit of a disparity there. A simple workaround would be to memoize init and have that in higher scope, e.g. in main (since init is a function, it won't necessarily return the same instance).

Scratch that, not sure why I didn't notice withSubscription passes in a 'model.

By the way, this branch reacts slightly differently to what I think is a bug: with the latest nuget package, if you return a Cmd.ofMsg from init to update some stuff, the model reverts back to the initial state before getting updated again.

I've been trying to repro it with a simpler example, but I haven't been able to.

With this branch, trying to do the same thing causes a null reference exception trying to access Application.Current, which kind of makes more sense and is more informative, since I have some stuff in init that probably needs access to Application. Newing an Application first solved the problem.

@cmeeren
Copy link
Member

cmeeren commented Sep 25, 2019

What a strange bug. AFAIK there should be no difference in that regard between the current nuget and the branch. Particularly since nothing in the Program module hasn't been changed in a while. Have you tried thoroughly cleaning between trying the nuget and trying the branch? (Including deleting the .vs folder.)

Please let me know if you can arrive at a somewhat minimal repro, or figure out what it is.

Also, may I ask what you're doing in init that requires Application? Just curious.

@reinux
Copy link

reinux commented Sep 25, 2019

Have you tried thoroughly cleaning between trying the nuget and trying the branch?

Nope. Honestly, the "new" behavior is desirable, because it actually throws an exception!

Will let you know if it ever comes back.

Also, may I ask what you're doing in init that requires Application? Just curious.

I new up an ffmediaelement element and return a Cmd.ofSub (speaking of which, I should move that to subModelWin now). It actually shouldn't be referencing Application directly, so I'm not too sure what's going on, but actually, the only reason I suspect this is what was causing it is because something similar happened before, and was since fixed.

The only real question is why Elmish.WPF was silently going back to the init state, and why it stopped doing that.

In fairness, this is probably one of the unforeseen consequences of having complex mutable objects in the model. This is for a separate project from when I asked about mutable state before. I'm experimenting with multiple child windows in multiple subModelSeqs, so putting the element in the model was the best I could think to do.

I'm probably bending Elmish way out of shape...

@TysonMN
Copy link
Member

TysonMN commented Sep 25, 2019

Side note: Elmish contains a comment above Cmd.ofMsg that says

// Synonymous with OfFunc.result, may be removed in the future

The alternative is to call Cmd.OfFunc.result.

@TysonMN
Copy link
Member

TysonMN commented Sep 25, 2019

Another side note: I think Program.withSubscription is syntactic sugar for the Cmd<'msg> returned from init : 'a -> ('model, Cmd<'msg>) as I explain here.

@TysonMN
Copy link
Member

TysonMN commented Sep 25, 2019

...if you return a Cmd.ofMsg from init to update some stuff, the model reverts back to the initial state before getting updated again.
...
The only real question is why Elmish.WPF was silently going back to the init state, and why it stopped doing that.

In fairness, this is probably one of the unforeseen consequences of having complex mutable objects in the model.

Yes, that is my guess. I have looked at the Elmish dispatch loop before and just looked at it again now. I don't see how the bug you describe could be caused by anything in Elmish (and Elmish.WPF isn't involved in this part).

@reinux
Copy link

reinux commented Sep 26, 2019

Another side note: I think Program.withSubscription is syntactic sugar for the Cmd<'msg> returned from init : 'a -> ('model, Cmd<'msg>) as I explain here.

Thanks, good to know.

I'm going to keep exploring for ways to clean up the model. It was easier with the other one, since I only had one instance, and I didn't have any bindings between elements.

@cmeeren
Copy link
Member

cmeeren commented Sep 26, 2019

3.4.0 is in the pipeline and will be released shortly with the new feature.

@reinux
Copy link

reinux commented Sep 28, 2019

Okay, I think I'm getting better at this. I've managed to banish most mutable state from the model, save for OfFunc commands, if that counts.

For the most part, I can just pass commands in as parameters to the 'Msg cases and respond to events that way when I can't use bindings.

I guess there've always been ways around it, but the new subModelWin parameter seems to make things a lot tidier, since now I can shove all the WPF-specific stuff in the bindings function.

@TysonMN
Copy link
Member

TysonMN commented Oct 29, 2019

Quoting #33 (comment)...

What about for binding to something like the Window.Closing event? The program has to be able to write to the event args to indicate that it doesn't want to close.

...and #33 (comment)

If there's a way to bind the EventArgs of an event to CommandParameter in Microsoft.Xaml.Behaviors, preferably without having to tinker in code-behind, I think that would suffice.

There is! A friend just informed me that InvokeCommandAction has a Boolean property called PassEventArgsToCommand. Just set it to True.

https://github.com/microsoft/XamlBehaviorsWpf/blob/f1aa06daa9c42169d728d6172cd59986ab4a04a8/src/Microsoft.Xaml.Behaviors/InvokeCommandAction.cs#L100-L104

Maybe we could expand the EventBindingsAndBehaviors sample to include a use of this.

@cmeeren
Copy link
Member

cmeeren commented Oct 29, 2019

Maybe we could expand the EventBindingsAndBehaviors sample to include a use of this.

Sure, PR welcome! Though perhaps not the Window.Closing event, because the idiomatic way to handle windows is using Binding.subModelWin, so demoing other approaches might confuse users.

@reinux
Copy link

reinux commented Oct 29, 2019

Thanks @bender2k14 , good to know!

TysonMN pushed a commit to TysonMN/Elmish.WPF that referenced this issue Oct 29, 2019
TysonMN pushed a commit to TysonMN/Elmish.WPF that referenced this issue Oct 29, 2019
@cmeeren
Copy link
Member

cmeeren commented Oct 30, 2019

I think everything here is resolved now. I recommend posting new issues for new problems related to events. It makes it easier to keep track of what's resolved and unresolved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants