-
Notifications
You must be signed in to change notification settings - Fork 323
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
Improve dropdown arrows placement #9909
Conversation
// Find the top-most PropertyAccess, and return its rhs id. | ||
// It will be used to place the dropdown arrow under the constructor name. | ||
let node = props.input.value | ||
while (node instanceof Ast.Ast) { | ||
if (node instanceof Ast.PropertyAccess) return node.rhs.id | ||
if (node instanceof Ast.App) node = node.function | ||
else break | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sometimes constructors have arguments, but call resolution happens only in WidgetFunction, which has a lower priority than WidgetSelection. I think this code will cover the majority of cases when we have a constructor call, but perhaps there are some common cases that I missed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sometimes constructors have arguments, but call resolution happens only in WidgetFunction, which has a lower priority than WidgetSelection. I think this code will cover the majority of cases when we have a constructor call, but perhaps there are some common cases that I missed.
It will also catch all normal function calls (for example, in node opreator1.select_columns [operator2.first]
the arrow will be under first
- I'm not sure if it's expected. On the other hand, it's rather an advanced usage).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, that’s the case. I don’t think we can solve it without expensive operations to determine if we have a constructor call or a function call. Perhaps we can analyze each node on a higher level, preparing all the necessary information in advance and only fetching it in WidgetFunction, but for now I suggest to leave it as it is.
<Teleport v-if="arrowLocation" :to="arrowLocation"> | ||
<SvgIcon v-if="isHovered" name="arrow_right_head_only" class="arrow" /> | ||
</Teleport> | ||
<SvgIcon v-else-if="isHovered" name="arrow_right_head_only" class="arrow" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You may wonder why to duplicate instead of using <Teleport :disabled='...'>
. The reason is that :to
should point at a valid mounted reference at the time of its mounting. Here, however, the destination would only be available after mount. In other words, you can’t have <Teleport :disabled="bla" :to="undefined">
, Vue will bail out. I choose not to state it in the code, as the comment will be copied to application html tree, which I find ugly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I choose not to state it in the code, as the comment will be copied to application html tree, which I find ugly.
In production builds, the comments are removed: https://vuejs.org/api/application.html#app-config-compileroptions-comments If you want, you may set the config to remove also in dev builds, but perhaps comments may be useful there?
In either case, please put here a comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, good to know, added a comment there.
@@ -205,6 +205,14 @@ export interface WidgetOptions<T extends WidgetInput> { | |||
// A list of widget kinds that will be prevented from being used on the same node as this widget, | |||
// once this widget is used. | |||
prevent?: WidgetComponent<any>[] | |||
/** Allow to select this widget as the leaf of the widget tree for particular widget input. | |||
* `true` by default. The widget marked with `false` *must* have a child, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The second sentence is kinda obvious, but I still want to write it down just in case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the implementation, but I think the docs/names should be improved.
// Find the top-most PropertyAccess, and return its rhs id. | ||
// It will be used to place the dropdown arrow under the constructor name. | ||
let node = props.input.value | ||
while (node instanceof Ast.Ast) { | ||
if (node instanceof Ast.PropertyAccess) return node.rhs.id | ||
if (node instanceof Ast.App) node = node.function | ||
else break | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sometimes constructors have arguments, but call resolution happens only in WidgetFunction, which has a lower priority than WidgetSelection. I think this code will cover the majority of cases when we have a constructor call, but perhaps there are some common cases that I missed.
It will also catch all normal function calls (for example, in node opreator1.select_columns [operator2.first]
the arrow will be under first
- I'm not sure if it's expected. On the other hand, it's rather an advanced usage).
<Teleport v-if="arrowLocation" :to="arrowLocation"> | ||
<SvgIcon v-if="isHovered" name="arrow_right_head_only" class="arrow" /> | ||
</Teleport> | ||
<SvgIcon v-else-if="isHovered" name="arrow_right_head_only" class="arrow" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I choose not to state it in the code, as the comment will be copied to application html tree, which I find ugly.
In production builds, the comments are removed: https://vuejs.org/api/application.html#app-config-compileroptions-comments If you want, you may set the config to remove also in dev builds, but perhaps comments may be useful there?
In either case, please put here a comment.
|
||
<template> | ||
<div ref="teleportTarget" class="WidgetSelectionArrow"> | ||
<NodeWidget :input="innerInput" allowEmpty /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we allowEmpty here, while also not allowing as leaf?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, an artifact from previous implementation
/** Allow to select this widget as the leaf of the widget tree for particular widget input. | ||
* `true` by default. The widget marked with `false` *must* have a child, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the terminology is not quite right here: in my understanding of child
term, a widget could actually have children, but still be discarded when allowAsLeaf
is false.
The best example is WidgetHierarchy - usually picked as last resort for AST tree. This tree may still have children, but those will make different input for widgets, so the Hierarchy's input will match no other widget - making it being discarded when allowAsLeaf will be false.
I think this could be renamed to wrapperOnly
or something like that, and the meaning is "if true, this widget will be matched only when at least one another widget is guaranteed to be matched for the same input". Or better :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think your understanding is correct, and so is my comment – the important words of the first sentence are for particular widget input
. I don’t think wrapperOnly
and inversion of boolean have a strong preference here, so I left it as it is. wrapperOnly
has the same problem – if widget is wrapperOnly
, it still can be selected as a non-wrapper if it has no children with the same input. However, I liked your explanation if true, this widget will be matched only when at least one another widget is guaranteed to be matched for the same input
; and reused it. (inversing the boolean)
// If we found a perfect match, we can return immediately, as there can be no better match. | ||
// If we found a leaf match, we can safely return – there will be another leaf later down the tree. | ||
// One caveat here is that if the widget marked with `allowAsLeaf = false` has no children. Then we | ||
// can pick it here, and it will effectively become leaf. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would somehow merge the first two sentences. And I have an impression, that the caveat
does not explain too much - after all, the allowAsLeaf
is about guarantees, the widget may do whatever it wants.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don’t think merging is possible without murdering the grammar, but I removed the part about caveat
and rephrased it.
Pull Request Description
Closes #9428
Hilariously tricky thing to implement. Besides placing arrows below constructor names, we also position them under values in argument placeholders.
We also added a new feature to the widget tree: the
allowAsLeaf
property. See provided documentation.working.arrow.placement.mp4
Checklist
Please ensure that the following checklist has been satisfied before submitting the PR:
Scala,
Java,
TypeScript,
and
Rust
style guides. In case you are using a language not listed above, follow the Rust style guide.