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

Enable the user to unpin/pin an external tool on the bar #2972

Merged
merged 3 commits into from
May 22, 2024

Conversation

andreww-msft
Copy link
Contributor

Summary of the pull request

Prior to this, when the user added an external tool we always pinned it to the bar. Now, while we always add it to the menu, we let the user choose whether to pin it to the bar (defaults to pinned). This meant adding an IsPinned property to ExternalTool, a ToggleSwitch in the Settings | AddTool UI, adding a FilteredExternalTools collection which is synchronized with changes to the "all tools" collection, and databinding the buttons collection to this instead. The ExternalTools menu uses the "all tools" collection.

Fixed related bugs

  • when you add an external tool to the bar, and reload the bar, the tool is duped.
  • external tool menu should be restricted to external tool buttons, and not show up on the other bar items.
  • we save the list of tools to a file when a tool is added/removed, but we also need to do this when a tool property is changed.

tools/PI/DevHome.PI/Helpers/ExternalTool.cs Outdated Show resolved Hide resolved
@@ -36,65 +68,161 @@ private ExternalToolsHelper()
localFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location) ?? string.Empty;
}

// The file should be in this location:
// %LocalAppData%\Packages\Microsoft.Windows.DevHome.Dev_8wekyb3d8bbwe\LocalState\externaltools.json
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest not using Dev package path

Suggested change
// %LocalAppData%\Packages\Microsoft.Windows.DevHome.Dev_8wekyb3d8bbwe\LocalState\externaltools.json
// %LocalAppData%\Packages\Microsoft.Windows.DevHome_8wekyb3d8bbwe\LocalState\externaltools.json

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2nd commit.

}
}
catch (Exception ex)
{
// TODO If we failed parsing the JSON file... should we just delete it?
// TODO If we failed to parse the JSON file, we should rename it (using DateTime.Now),
// create a new one, and report to the user.
_log.Error(ex, "Failed to parse {tool}", toolInfoFileName);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
_log.Error(ex, "Failed to parse {tool}", toolInfoFileName);
_log.Error(ex, $"Failed to parse {toolInfoFileName}");

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2nd commit.

private void ToolItem_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
// The user can change the IsPinned property of a tool, to pin or unpin it on the bar.
if (sender is ExternalTool tool && e.PropertyName == nameof(ExternalTool.IsPinned))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer string.Equals to ==

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2nd commit.

Grid.Row="4" Margin="0,12,0,0" HorizontalAlignment="Left" IsOn="True"/>

<StackPanel Orientation="Horizontal" Grid.Row="5" HorizontalAlignment="Right" Margin="0,12,0,0">
<Button x:Uid="AddToolsOkButton" x:Name="OkButton" Click="OkButton_Click" Width="100"/>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use MinWidth, otherwise you have a potential text scaling (or long language) accessibility bug.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2nd commit.

Grid.Row="4" Margin="0,12,0,0" HorizontalAlignment="Left" IsOn="True"/>

<StackPanel Orientation="Horizontal" Grid.Row="5" HorizontalAlignment="Right" Margin="0,12,0,0">
<Button x:Uid="AddToolsOkButton" x:Name="OkButton" Click="OkButton_Click" Width="100"/>
<Button x:Uid="AddToolsCancelButton" x:Name="CancelButton" Margin="6,0,0,0" Width="100"/>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest adding Spacing to the surrounding StackPanel instead of Margin to the Button.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2nd commit.

@andreww-msft andreww-msft self-assigned this May 21, 2024
@@ -292,7 +294,7 @@ private void CreateMenuItemFromTool(ExternalTool item)

private void ExternalToolItem_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (sender is ExternalTool item && e.PropertyName == nameof(ExternalTool.MenuIcon))
Copy link
Contributor

@timkur timkur May 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

==

We do this sort of comparison throughout a lot of the code. Should we be doing string.equals instead? #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it seems, and yes I fixed this one in the 2nd commit.

}

// The bar shows only the pinned tools.
public ObservableCollection<ExternalTool> FilteredExternalTools { get; private set; } = [];
Copy link
Contributor

@timkur timkur May 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ObservableCollection

Is this a ReadOnly collection? #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I thought about wrapping it, but the set is private, and I need to be able to change at least the IsPinned property on any of the items in the collection.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ReadonlyCollection only prevents you from adding/removing from the collection, not modifying the items in the collection. I think you want this as a ReadOnly collection, as you don't want other components adding/removing from this collection, correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3rd commit.


private void SynchronizeAllFilteredItems()
{
FilteredExternalTools.Clear();
Copy link
Contributor

@timkur timkur May 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FilteredExternalTools.Clear();

I wonder if it would make sense to build a collection first and then assign it to FilteredExternalTools at the end. Otherwise you'll be generated a bunch of changed events (no items, then 1 item, then 2 items, etc)... I bet the titlebar could potentially flicker based on system load? #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, that could only happen on first init, since the user is only adding/removing one tool at a time. If we reinstate the ability to bulk edit the json, then when we read it in, it would again be an init.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh... yeah, I misread that.

[JsonIgnore]

[ObservableProperty]
private bool _isPinned;
Copy link
Contributor

@timkur timkur May 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm struggling with this one a bit.... first we've got the pinned property, and then potientially an "order index in the bar"... is this a property of an ExternalTool, or how it's represented in the bar? Or is it ok to have them all in one data structure.

Not advocating for a particular change now, but just something to keep in mind in the future.
#Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IsPinned is rightfully a property of ExternalTool, same as arguments - both are PI-specific configurations for the tool that the user gets to choose. We use the IsPinned user setting to do X, and the Args user setting to do Y. Not sure where the "order index" comes from?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We chatted a bit about long term vision. I'm ok with this for now.

}

// The bar shows only the pinned tools.
public ObservableCollection<ExternalTool> FilteredExternalTools { get; private set; } = [];
Copy link
Contributor

@timkur timkur May 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should probably be in the BarWindow view model vs here... since the BarWindow would want a filtered view based on pinned, but other components using this (thinking about when we need input/output filters, extensions, etc) would want a different set of filtered tools. #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, and that's a can of worms I want to defer to next round. ExternalToolsHelper should be split into a BarViewModel and other stuff.


// Note the additional "property:" syntax to ensure the JsonIgnore is propagated to the generated property.
[ObservableProperty]
[property: JsonIgnore]
private SoftwareBitmapSource? _toolIcon;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_toolIcon

nit: can we use camelCase?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that would be my preferred style, but I'm seeing the leading underscore in other parts of DevHome. We should make an explicit style choice across the board, and do a code cleanup operation at some point.

Copy link
Contributor

@timkur timkur left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

Copy link
Contributor

@jaholme jaholme left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@andreww-msft andreww-msft merged commit 5aaa89d into main May 22, 2024
4 checks passed
@andreww-msft andreww-msft deleted the user/andreww/pinUnpin branch May 22, 2024 15:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants