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

feat: Tree node styling. #592

Merged
merged 10 commits into from
Oct 27, 2022
Merged

feat: Tree node styling. #592

merged 10 commits into from
Oct 27, 2022

Conversation

dhess
Copy link
Member

@dhess dhess commented Oct 26, 2022

This series of commits attempts to bring our current tree implementation closer to the UI design spec.

A few notes:

* Our Tailwind config inverts the blue primary and secondary colors
wrt. the document. (By my recollection, I think this was intentional
and had something to do with the corresponding hover colors.)

* Ann's document lists "red secondary" twice, once as #cd3764 and once
as #f1685e. I believe #cd3764 was added late in the process and
therefore I've called it "red tertiary" in this commit.

* Ditto for "grey secondary," but in this case, the hex RGB value
given for the 2nd occurrence of "grey secondary" does not match the
color swatch. The hex value is the same as the 1st occurrence of "grey
secondary" and therefore I've used the hex RGB value I measured by
sampling the swatch in the document (which is #d4d4d4). I've called
this "grey quaternary" in the Tailwind config.
This will become more useful in the next couple of commits.
@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Oct 26, 2022

Deploying with  Cloudflare Pages  Cloudflare Pages

Latest commit: 986024d
Status: ✅  Deploy successful!
Preview URL: https://0eae140b.primer-app.pages.dev
Branch Preview URL: https://dhess-style-nodes.primer-app.pages.dev

View logs

@dhess
Copy link
Member Author

dhess commented Oct 26, 2022

I'm not going overboard here because we are likely to toss the current tree implementation, but I'd like to get closer to the design spec for our upcoming testing.

We don't use all of these at the moment, but it's easy to forget these
are necessary, so we might as well define them in advance.
("Mostly" because some of these nodes should be filled a solid color
with white text.)

The summary commit message describes the only behavioral change in
this commit, but I've buried the lede: this commit also majorly
refactors how we assign classes to nodes & edges in the tree by using
Tailwind CSS classes, rather than injecting raw `style` CSS
attributes.

At the moment, this doesn't buy us much and instead feels a bit like a
violation of DRY, but it does give us a lot more flexibility in
styling, which the next few commits will take advantage of.
The following flavors aren't selectable, and therefore we don't show
hover for them:

* `with` in `match ... with`.
* Pattern boxes.
* Pattern constructors.
* Pattern applications.

(Notably, pattern binds are selectable because we can rename them.)

This implementation deviates from the design spec by making the width
of the outline 4px, rather than 2px. I think 2px is a bit too thin, at
least for our current overall style.
Per the Tailwind CSS docs, this is a 6px radius, which is what the UI
design spec calls for.
I had been wondering separately what was the difference between `ring`
and `outline` and finally stumbled across an explanation of why `ring`
was added in the first place:

tailwindlabs/tailwindcss#7649

It turns out that on Safari, outlines don't follow the border of the
element they're outlining: they are always squared off. Tailwind CSS
addressed this by adding `ring`, so for our purposes, this is what we
should be using.
There's still some work to be done here, but this is a good
approximation of the design spec.

Among the things that remain to be done are:

* We don't draw any labels on most syntax nodes.

* The size of the label and the contrast need some work.

* The single-glyph labels render like relatively flat ellipses rather
than nice circles like the 3-glyph labels do. We might be able to fake
this by padding out the single-glyph labels to 3 (spaces on either
side), or perhaps with a bit of min-width magic.
With this commit, we pretty closely match the UI design spec for tree
styling, with a few exceptions:

* Holes are not rendered as a circle.

* We don't have an `=` node for `let`s.

* Value constructor and function `apply` nodes read just `apply`
rather than `apply function`. We need to think about this a bit in the
context of patterns, where `apply` only ever appears with a value
constructor and we don't want students to get confused.

Note that there are a few node types that weren't specified in the UI
design spec (e.g., type constructors). At the moment, these are
rendered in black, and we'll need to choose some colors for these.

Also, nodes that appear in patterns are also rendered in black. I'm
guessing this is a leftover from a debate we had a few months ago
about whether patterns should be treated specially other than the box
drawn around them. This will be corrected in a subsequent commit.

Finally, the body content I've chosen here for some syntax
nodes (e.g., `function type`) should be considered just placeholders
for now. We should finalize the terms we use for these at a later
date. (One complication is that they should be reasonably short, so
that they don't take up too much space in the tree.)
@dhess
Copy link
Member Author

dhess commented Oct 27, 2022

This is pretty close to the final design for expression trees (ignoring the changes we've made to edges). Off the top of my head, we still need:

  1. Round hole nodes, but I would like to try implementing "shrink-to-fit" node rendering first.
  2. Decisions on what to render for syntax nodes like annotations and function type constructors.
  3. Color choices for node types that don't show up in Ann's UI design spec, such as type constructors and variables.

2 and 3 will probably require some bikeshedding and 1 will take me some time to figure out. Therefore, I think this is ready for review.

(And of course, this PR doesn't do anything about rendering type trees or top-level definition names.)

Note: yes, there is quite a bit of repetitive code in the Tailwind class functions. We should definitely take advantage of some default cases in the switch statements to DRY, but at the moment this is nice because it's easy to make one-off changes to special nodes. Once we have a reasonably final collection of class functions, I will take a pass at batching similar node styles together.

@dhess
Copy link
Member Author

dhess commented Oct 27, 2022

BTW, there are a few things that I don't like about the design spec now that we've implemented most of it in a real application. For example, I don't think the green selection ring works well. I will compile a list of these when I get a chance, and we can discuss some alternatives. I encourage everyone to do the same after you've had a chance to play with the changes in this PR.

@dhess dhess requested a review from a team October 27, 2022 02:12
@dhess
Copy link
Member Author

dhess commented Oct 27, 2022

In this PR, there's one notable deviation from the UI design spec. I've placed the "P" label for patterns at the midway point of the top edge, rather than in the upper right corner as the spec calls for.

This PR:

Screenshot 2022-10-27 at 10 40 52 AM

The spec:

Screenshot 2022-10-27 at 10 41 20 AM

I did that because other "syntax" nodes like match and apply have the centered label, and I thought it might be good to be consistent about that. However, patterns are different in that students can modify them. At the moment, we permit the student to rename a pattern variable, and someday we'll give them the ability to write their own patterns. So in that sense, they're not like the other syntax nodes, and an argument could be made for putting the "P" in the upper right-hand corner like other student-definable nodes such as names and bindings.

(Hmm, it now occurs to me that maybe we should treat constructors as syntax nodes?)

@@ -146,6 +146,7 @@ function flavorLabel(flavor: NodeFlavor): string {
}

const primerNodeTypeName = "primer";

Copy link
Contributor

Choose a reason for hiding this comment

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

FWIW, the lack of line breaks were intentional. This is all part of defining a "primer node", and I like to group related definitions (though maybe it's a bit odd that there is then a blank line within PrimerNode.

Anyway, it really doesn't matter.

Copy link
Member Author

Choose a reason for hiding this comment

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

I found it hard to read and follow, personally.

return "border-black-primary outline-black-primary";
return "border-black-primary outline-black-primary".concat(
commonHoverClasses
);
Copy link
Contributor

Choose a reason for hiding this comment

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

It's a shame we can't easily reflect that these are precisely the nodes with numeric IDs. But I can't see a better way to factor this.

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh that's a very good point that hadn't occurred to me. I will give it some thought, as we definitely need to do some kind of refactoring based on common traits/flavors. I don't think the code as it exists in this PR is long-term maintainable.

case "FlavorTHole":
return "{?}";
default:
return undefined;
Copy link
Contributor

Choose a reason for hiding this comment

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

This makes me think maybe we should split up flavours in to separate types depending on what kind of NodeBody they correspond to. This would make a few things more elegant. See e.g. the comment about assigning a text colour for FlavorPattern being "meaningless".

Of course, this change would have to be made in the backend.

@@ -454,8 +621,6 @@ const convertTree = (
source: id,
target,
className: flavorEdgeClasses(tree.flavor),
// We draw edges above nodes, so that they aren't hidden by nodes' solid backgrounds.
zIndex: 1,
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess this is because we've dropped the solid background for pattern boxes?

Copy link
Member Author

Choose a reason for hiding this comment

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

More like I dropped the solid background because I couldn't get this to work at all with Tailwind's own z-N classes. I'm not sure what was going on, but I could not get edges to show up inside pattern boxes, even if I bumped edges' Z-index to the highest value in the hierarchy.

We can revisit this later, but personally I don't mind that there's no background inside the pattern boxes, and I'm not sure we'll want to keep the dotted background in the long run, either, so maybe it won't matter.

const primerNodeTypeName = "primer";

type PrimerNodePropsNode = {
label?: string;
contents?: string;
contents: string | undefined;
Copy link
Contributor

Choose a reason for hiding this comment

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

The fact that these aren't trivially interchangeable is one of my two pet hates with TypeScript...

Copy link
Member Author

Choose a reason for hiding this comment

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

I think maybe this is due to strictNullChecks or another related TypeScript config setting?

Copy link
Contributor

Choose a reason for hiding this comment

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

Well, strictNullChecks would work around it, but it's a very big hammer.

There is a long-open proposal about improving the situation. I think the difficulty comes from the fact that they are distinguishable at runtime, e.g. by using Object.keys.

@georgefst
Copy link
Contributor

(Hmm, it now occurs to me that maybe we should treat constructors as syntax nodes?)

Maybe. I think we need to clarify what we mean by "syntax nodes". Certainly, I think we should make it more obvious which nodes are not selectable, which is currently just applications and constructors in patterns (and with, but that's a temporary crutch). Although this PR does at least help by introducing the hover ring.

@georgefst
Copy link
Contributor

BTW, there are a few things that I don't like about the design spec now that we've implemented most of it in a real application. For example, I don't think the green selection ring works well. I will compile a list of these when I get a chance, and we can discuss some alternatives. I encourage everyone to do the same after you've had a chance to play with the changes in this PR.

Yeah, I've already got a bit of a list.

For one thing, and I mention this now since it's one of the major changes in this PR, I feel quite strongly that syntax nodes shouldn't have the extra text on them. i.e. we should return NoBody nodes to being small circles, or similar. Yes, a beginner student might forget that a dark blue $ node means "apply", but they're just as likely to forget that a green V node means "value constructor". This extra detail on every node really clutters the tree, and seems like something that should be displayed on select or hover.

Anyway, let's save this for a future discussion.

@dhess
Copy link
Member Author

dhess commented Oct 27, 2022

BTW, there are a few things that I don't like about the design spec now that we've implemented most of it in a real application. For example, I don't think the green selection ring works well. I will compile a list of these when I get a chance, and we can discuss some alternatives. I encourage everyone to do the same after you've had a chance to play with the changes in this PR.

Yeah, I've already got a bit of a list.

For one thing, and I mention this now since it's one of the major changes in this PR, I feel quite strongly that syntax nodes shouldn't have the extra text on them. i.e. we should return NoBody nodes to being small circles, or similar. Yes, a beginner student might forget that a dark blue $ node means "apply", but they're just as likely to forget that a green V node means "value constructor". This extra detail on every node really clutters the tree, and seems like something that should be displayed on select or hover.

Anyway, let's save this for a future discussion.

Yeah we shouldn't get into it here, but personally I feel completely the opposite: most names like Zero and x are already pretty self-explanatory, at least if students are choosing good names for their types, constructors, and variables. And when you read them out loud, they mean what they say: they are names.

But syntax like $ and : is completely arbitrary, and if you're learning the language, you do not automatically think apply and annotate. So when you encounter one in the tree, you have to pause a think, what does this symbol mean? And then consider that you're also probably struggling with the idea of apply or whatever as well, so now you need to recall 2 things before you can figure out what that node means.

So despite the fact that they clutter the tree, I think the words are going to be really helpful to beginning students: they see apply and only have to consider what that means, rather than first needing to remember that $ means apply. And the fact that we show both apply (in the body) and $ (in the label) means that, once students are familiar with the concept, they can enable a mode that only shows the $ and de-clutters the tree, for when that's the right tradeoff. And now they've had plenty of opportunities to associate $ with apply in the verbose tree, so it should be second nature by that point, as it is for people like us.

tldr; I think we should only show $ in intermediate or higher mode, but not in beginner mode.

@georgefst
Copy link
Contributor

tldr; I think we should only show $ in intermediate or higher mode, but not in beginner mode.

That sounds like a good compromise.

@dhess dhess added the Ready to merge Ready to merge label Oct 27, 2022
mergify bot added a commit that referenced this pull request Oct 27, 2022
@mergify mergify bot merged commit 14da98d into main Oct 27, 2022
@mergify mergify bot deleted the dhess/style-nodes branch October 27, 2022 12:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Ready to merge Ready to merge
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants