Closed zadjii-msft closed 1 day ago
OR
We could have a
[uuid("c78b9851-e76b-43ee-8f76-da5ba14e69a4")]
interface IContextItem {}
interface ICommandContextItem requires IContextItem {
ICommand Command { get; };
String Tooltip { get; };
Boolean IsCritical { get; };
}
interface ISubMenuContextItem requires IContextItem {
IContextItem[] MoreCommands{ get; };
// These two are from ICommand....
String Name;
IconDataType Icon;
// These ones are lightly reused from ICommandContextItem - should dedupe
String Tooltip { get; };
Boolean IsCritical { get; };
}
So then, if the user invokes a ISubMenuContextItem
, we just build a new menu there. And dismissing the menu in that state allows us to go back to the root context menu
Is ListItem.ContextMenu
also just a list of ILIstItem
s?
and if you hit enter on one, then we'd
ContextMenu
on that list item, we'd open a sub-menu with the default action and all context list itemsI think I finally understand. (alternatively, I haven't had enough sleep and this is ravings of a madman)
IListItem
is really a Command
the whole time. It's the primary currency everywhereICommand
is really nothing at all, it's just some shared root interface for all these thingsIPage
extend from a IListItem
, then
Title
and Icon
would just be IListItem.Title
, IListItem.Icon
what weird stuff on IListItem
wouldn't apply to a page?
IconDataType Icon
: Makes sense on a page, on a context menu item String Title
: Makes sense on a page, on a context menu item String Subtitle
: I guess makes sense on a page, hidden on contextICommand Command
: Well I mean, in this proposal, we're canning this thing entirelyIContextItem[] MoreCommands
: Makes sense on FormPage
and MarkdownPage
, but not on a ListPage, since a listpage's actions all come from the items. ITag[] Tags
: nope no senseIDetails Details
: Makes sense on FormPage
and MarkdownPage
, but not on a ListPage, since a listpage's actions all come from the items. IFallbackHandler FallbackHandler
: nope no sense. Only makes sense in a list (which I guess the context menu is, but still)String Section
: nope no sense. Only makes sense in a list (which I guess the context menu is, but still)Do we just:
Title
and Subtitle
into ICommand
, IListItem.Icon
(just always use the one on the command)ICommand.Name
to ICommand.ShortName
Cause THEN the TopLevelCommands that we'd stash as stubs is literally just an ICommand
- a {Id, ShortName, Title, Subtitle, Icon}
. Everything else either:
i'm a genius
ICommand: {Id, ShortName, Icon}
. IListItem: {Command, Icon, Title, Subtitle, MoreCommands, Tags, Details, FallbackHandler, Section}
ICommandContextItem: {Command, Icon, Title, Tooltip, IsCritical}
ISubMenuContextItem : {Icon, Title, MoreCommands, Tooltip}
IPage : {Title, Loading}
When displaying a list item:
IListItem.Icon ?? IListItem.Command.Icon
IListItem.Title ?? IListItem.Command.ShortName
IListItem.Subtitle
Command.ShortName
A stub list item is:
StubItem(){
Id = IListItem.Command.Id,
Icon = IListItem.Icon(),
Title = IListItem.Title(),
Subtitle = IListItem.Subtitle,
ShortName = IListItem.Command.ShortName,
}
(resolving icon and title as detailed above)
When displaying a command context menu item (not nested):
ICommandContextItem.Icon ?? ICommandContextItem.Command.Icon
ICommandContextItem.Title ?? ICommandContextItem.Command.ShortName
When displaying the default command context item, we'll make a
ICommandContextItem(){
Command = IListItem.Command,
Icon = IListItem.Command.Icon,
Title = IListItem.Command.ShortName,
Tooltip = IListItem.Title,
IsCritical = false,
}
When displaying a nested context menu item:
ISubMenuContextItem.Icon
(no fallback to the command, because there isn't one)ISubMenuContextItem.Title
(no fallback to the command, because there isn't one)When displaying a page:
IPage.Title ?? ICommand.Name
ICommand.Icon
And now that I've typed this all out - does it make sense for ICommand
to have an icon? If IListItem
, and ICommandContextItem
both have icons, why not just give IPage
one too, and remove all the icon fallback machinery?
Plan V5:
interface ICommand requires INotifyPropChanged{
String ShortName{ get; };
String Id{ get; };
IconDataType Icon{ get; };
}
interface IPage requires ICommand {
String Title { get; };
Boolean Loading { get; };
}
interface ICommandItem requires INotifyPropChanged {
ICommand Command{ get; };
IContextItem[] MoreCommands{ get; };
IconDataType Icon{ get; };
String Title{ get; };
String Subtitle{ get; };
}
interface IListItem requires ICommandItem {
ITag[] Tags{ get; };
IDetails Details{ get; };
IFallbackHandler FallbackHandler{ get; };
String Section { get; };
String TextToSuggest { get; };
}
interface ICommandContextItem requires ICommandItem, IContextItem {
Boolean IsCritical { get; }; // READ: "make this red"
}
interface IFallbackCommandItem requires ICommandItem {
IFallbackHandler FallbackHandler{ get; };
}
interface ICommandProvider requires Windows.Foundation.IClosable
{
// ...
ICommandItem[] TopLevelCommands();
IFallbackCommandItem[] FallbackCommands();
ICommand GetCommand(String id);
void InitializeWithHost(IExtensionHost host);
};
What if IListItem
and ICommandItem
shared the same base type? Basically, they're both a list of things which have
Title
which might replace their Command
's Name
, Icon
which might replace their Command
's Icon
, Subtitle
, which is visible on the list, and a tooltip for a context menuMoreCommands
:
If we structure it like this, then the TopLevelCommands
are just ICommandItem
s. That's the thing we can serialize to a stub.
AND by putting MoreCommands
on ICommandItem
, top level commands can have context menus (which are just more ICommandItem
)
All the bits of a list item which we can't have on a stub? They still live in IListItem
, which we don't use in the toplevel.
Fallback handlers have to move. They already felt weird on IListItem
itself. Instead, we'll stick them as a separate property on ICommandProvider
. This lets us keep fallbacks entirely separate from normal top-level items. It gives us a better abstraction for enabling/disabling fallbacks from a provider. It lets us clearly identify providers that need to be treated as fresh, never frozen, because of the presence of fallbacks. And if an extension's own list page wants to implement a similar fallback mechanism - it's free to use IDynamicListPage
to listen for changes to the query and have it's own ListItem it updates manually.
When displaying a list item:
ICommandItem.Icon ?? ICommandItem.Command.Icon
ICommandItem.Title ?? ICommandItem.Command.Name
ICommandItem.Subtitle
ICommandItem.Command.Name
When displaying a command context menu item:
ICommandItem.Icon ?? ICommandItem.Command.Icon
ICommandItem.Title ?? ICommandItem.Command.Name
ICommandItem.Subtitle
When displaying a IListItem
's default Command
as a context item, we'll make a new
ICommandContextItem(){
Command = ICommandItem.Command,
MoreCommands = null,
Icon = Command.Icon, // use icon from command, not list item
Title = Command.Name, // Use command's name, not list item
Subtitle = IListItem.Title, // Use the title of the list item as the tooltip on the context menu
IsCritical = false,
}
If a ICommandItem
in a context menu has MoreCommands
, then activating it will open a submenu with those items.
If a ICommandItem
in a context menu has MoreCommands
AND a non-null Command
, then activating it will open a submenu with the Command
first (following the same rules above for building a context item from a default Command
), followed by the items in MoreCommands
.
When displaying a page:
IPage.Title ?? ICommand.Name
ICommand.Icon
TL;DR: I'm gonna touch all the extensions, and make IListItem
and ICommandContextItem
share a root interface. TopLevelCommands()
won't return IListItem
s, but instead ICommandItem
s, and it's all gonna be simpler.
Alright so I'm planning one last largish, breaking-ish change to the API.
Right now we've got IListItem
s, which have a ICommand
, as well as a title/subtitle/icon. And those list items might have a context menu of MoreCommands
, which are IContextItem
s (which mostly just also have a ICommand
, title, icon, tooltip etc.)
We've also got a wacky situation where we want to store "stubs" for top-level commands, so that we don't always need to instantiate the extension process just to read top level commands from it. So some bits of IListItem
(like details, sections, tags, TextToSuggest, FallbackHandler)... they don't really make sense on TopLevelCommand ListItems. Only really the title,subtitle,icon,name matter.
Well ListViews and ContextMenus, those are also basically just the same thing - a list of commands to display. So I'm gonna consolidate classes slightly. Here's a side-by-side diff
Before | After |
---|---|
```diff -interface IListItem requires INotifyPropChanged { - IconDataType Icon{ get; }; - String Title{ get; }; - String Subtitle{ get; }; - ICommand Command{ get; }; - IContextItem[] MoreCommands{ get; }; ITag[] Tags{ get; }; IDetails Details{ get; }; - IFallbackHandler FallbackHandler{ get; }; String Section { get; }; String TextToSuggest { get; }; } interface IContextItem {} interface ISeparatorContextItem requires IContextItem {} -interface ICommandContextItem requires IContextItem { - ICommand Command { get; }; - IconDataType Icon{ get; }; - String Title{ get; }; - String Tooltip { get; }; Boolean IsCritical { get; }; // READ: "make this red" } -interface ISubMenuContextItem requires IContextItem { - IContextItem[] MoreCommands{ get; }; - IconDataType Icon{ get; }; - String Title{ get; }; - String Tooltip { get; }; - Boolean IsCritical { get; }; // READ: "make this red" -} interface ICommandProvider requires Windows.Foundation.IClosable { // ... - IListItem[] TopLevelCommands(); ICommand GetCommand(String id); void InitializeWithHost(IExtensionHost host); }; ``` | ```diff + interface ICommandItem requires INotifyPropChanged { + ICommand Command{ get; }; + IContextItem[] MoreCommands{ get; }; + IconDataType Icon{ get; }; + String Title{ get; }; + String Subtitle{ get; }; + } +interface IListItem requires ICommandItem { ITag[] Tags{ get; }; IDetails Details{ get; }; String Section { get; }; String TextToSuggest { get; }; } +interface ICommandContextItem requires ICommandItem, IContextItem { Boolean IsCritical { get; }; // READ: "make this red" } +interface IFallbackCommandItem requires ICommandItem { + IFallbackHandler FallbackHandler{ get; }; +} interface ICommandProvider requires Windows.Foundation.IClosable { // ... + ICommandItem[] TopLevelCommands(); + IFallbackCommandItem[] FallbackCommands(); ICommand GetCommand(String id); void InitializeWithHost(IExtensionHost host); }; ``` |
This makes the API way less repetitive. More of the currency of the app is now just ICommandItem
s throughout. It aligns what can be present on stub top-level items with what's actually there in the API.
Example:
Notes from teams - this was something we discussed on a Friday then never wrote down
other notes
In 3, it could return
StayOpen
, and then the parentListItem
raises aPropChanged(MoreCommands)
. Of course, the real problem here is that there's no existing way to tell theIListCommand
that the menu dismissed, so the app would need to build in another way to go back to the old menu.