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

Grid Support #210

Closed
wants to merge 51 commits into from
Closed

Grid Support #210

wants to merge 51 commits into from

Conversation

CharlesTaylor7
Copy link

@CharlesTaylor7 CharlesTaylor7 commented Jul 24, 2020

Resolves #176

Explicit support for all grid properties listed on mdn, except for a handful of shorthand properties:

The above shorthands have involved syntax which would make them a pain to support. The finer grained methods still encapsulate all the grid layout options.

I'm mostly happy with how this pr turned out. The main shortcoming for me is upcast. I tried a number of other things, but none of them worked very well. What would be nice here is Impredicative Polymorphism. I want to be able to create a list of type [forall a. Size a] for gridTemplateRows/gridTemplateColumns, but GHC won't oblige me. Maybe something to revisit when we get Quick Look?

@CharlesTaylor7 CharlesTaylor7 changed the title Grid support Grid Support Jul 24, 2020
@turion
Copy link
Collaborator

turion commented Jul 26, 2020

Exciting :) do assign me as a reviewer when you're ready.

@CharlesTaylor7 CharlesTaylor7 marked this pull request as ready for review July 26, 2020 19:01
@CharlesTaylor7
Copy link
Author

@turion Ready for review. I don't think have permissions for explicitly assigning you as reviewer.

Copy link
Collaborator

@turion turion left a comment

Choose a reason for hiding this comment

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

Thanks a lot for the extensive work so far! Here are some initial comments to get the review started. I'm going to have a second round later, when I understand your angle better.

clay.cabal Outdated
@@ -75,7 +75,8 @@ Library
Build-Depends:
base >= 4.11 && < 5,
mtl >= 1 && < 2.3,
text >= 0.11 && < 1.3
text >= 0.11 && < 1.3,
these < 1.2
Copy link
Collaborator

Choose a reason for hiding this comment

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

If you introduce an upper bound, shouldn't you also introduce a lower bound?

Copy link
Author

Choose a reason for hiding this comment

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

I looked at the earliest version of the package on hackage, and it has the constructurs I'm making use of.
But I can add a lower bound for completeness.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes that would be nice, then noone is going to ask themselves that question anymore.


-- | Upcast a size unit
upcast :: Size a -> Size AnyUnit
upcast = coerce
Copy link
Collaborator

Choose a reason for hiding this comment

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

Sorry, I think this is not a good idea. Can't you instead:

  1. Make Size into a GADT
  2. Add an Upcast :: Size a -> Size AnyUnit constructor

Copy link
Author

Choose a reason for hiding this comment

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

Can you elaborate?
What's the functional difference from the user perspective?
It's more work to embed it in a constructor, I now have to add cases to sizeToText & the Val instance that do nothing but unwrap.

Copy link
Collaborator

Choose a reason for hiding this comment

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

From the user perspective it's mostly the same I hope. But from the maintainer's perspective a huge burden is gone because noone has to worry about coerce breaking anything.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I have to admit that I completely misunderstood what you did here. I mistook coerce for unsafeCoerce, which is - as the name says - unsafe, whereas coerce is completely fine. It will never break anything because the type checker ensures that coercion only happens when the internal representations are indeed the same. If that was your goal, I'm sorry about the misdirection: feel free to disregard the stuff I said about GADTs, and let's make everyone's life easier by using coerce.

@@ -47,8 +42,6 @@ spec = do
sizeRepr (em 2 @+@ px 1) `shouldBe` "calc(2em + 1px)"
it "returns calc for nested sum" $
sizeRepr (em 2 @+@ pt 1 @+@ px 3) `shouldBe` "calc((2em + 1pt) + 3px)"
it "returns prefixed calc for simple sum" $
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is this removed?

@@ -1,8 +1,16 @@
module Common where
module Common
( shouldRenderFrom
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think there is no need to filter the exports. Or was there a name clash?

Copy link
Author

Choose a reason for hiding this comment

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

No name clash. I introduced a new top level binding of testRender & was preventing its export. It's not a big deal either way.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah, I'm fine if you don't restrict the exports in that case.

src/Clay/Grid.hs Outdated Show resolved Hide resolved
src/Clay/Grid.hs Outdated Show resolved Hide resolved
src/Clay/Grid.hs Outdated Show resolved Hide resolved
src/Clay/Grid.hs Outdated Show resolved Hide resolved
src/Clay/Grid.hs Outdated Show resolved Hide resolved
src/Clay/Grid.hs Outdated Show resolved Hide resolved
@CharlesTaylor7
Copy link
Author

@turion To be clear, you're very opposed to partial Num instances which exist just for number literals?

But are you open to partial IsList instances or no?
I will point out that NonEmpty has a partial fromList implementation for its IsList instance. I find it useful, but I recognize that its debatable and a matter of taste.

@turion
Copy link
Collaborator

turion commented Aug 3, 2020

@turion To be clear, you're very opposed to partial Num instances which exist just for number literals?

But are you open to partial IsList instances or no?
I will point out that NonEmpty has a partial fromList implementation for its IsList instance. I find it useful, but I recognize that its debatable and a matter of taste.

In my own libraries, I would not allow any partial functions that are reachable for library users. In clay, which I didn't author and only maintain so it is preserved for the community, there already are a lot of partial functions. They partly exist in places where I don't see their justification: For example Size Percentage has a Num instance without a (+) function, but with a refactoring of the code it would be possible to define (+) and all the other operations. Given enough time and dev effort, we can of course remove all of these warts, but time and effort are limited resources.

In particular for a CSS processor many people will take the stance that in a sense clay is always run "before runtime" in that CSS is generated before the webserver starts. Still these errors don't show up during type checking and are therefore not in the typical Haskell dev workflow. You'd think that your program will run when it compiles, but it doesn't necessarily here.

Since I don't want to undermine your efforts, I'd be willing to accept these partial functions if you really think they add value and cannot made be total with reasonable effort.

But if it's just for being able to write an integer literal I'm not so sure it's all that much added value. Consider shadow 23 42 vs shadow (px 23) (pct 42). The first expression is weirdly polymorphic in the size type in that you can later decide whether your 23 is percentage or pixels. But the idiomatic style is the second one anyways, which specifies the length units and types. It's clearer and not very long. The Num instance wasn't even used. I'd expect it to be similar in your applications, but of course you will be able to judge this better.

@ddssff
Copy link
Contributor

ddssff commented Jun 12, 2021

Would love to see this merged.

@turion
Copy link
Collaborator

turion commented Jun 15, 2021

Yes, that would be great :)

@CharlesTaylor7 do you want to continue? I hope my review so far wasn't to rigorous to demotivate. Reading back what I wrote it sounds quite harsh in my ears. If it did that for you as well, I'm really sorry, and I'll try to write in a friendlier tone :)

If you have any questions on how to proceed, feel free to ask.

@CharlesTaylor7
Copy link
Author

Yes, that would be great :)

@CharlesTaylor7 do you want to continue? I hope my review so far wasn't to rigorous to demotivate. Reading back what I wrote it sounds quite harsh in my ears. If it did that for you as well, I'm really sorry, and I'll try to write in a friendlier tone :)

If you have any questions on how to proceed, feel free to ask.

@turion I would like to complete this PR. I hate loose ends and do appreciate quality focused, thorough code reviews. I have no issue with you, just lost interest with this last year. I have the time and motivation to tackle this now.

@CharlesTaylor7
Copy link
Author

@turion #210 (comment)
Do you have a recommended naming scheme for handling complex data definitions?
I think it's a benefit, not a disadvantage, for the record type name, to have a similar name to the sum type constructor's name, this links the two clearly. Though I do agree there needs to be a convention to know which is which.

For sum types, I took the approach of prefixing constructor names with the name of type. This can be done with or without underscores. I removed the underscores, because they are at odds with the rest of the library which doesn't use underscores. (Though, I do think the underscores make it easier to visually parse).

-- https://developer.mozilla.org/en-US/docs/Web/CSS/grid-column-start
-- either grid index and/or named grid area is required, but span is optional
-- Note: constuctor is not exported, 'GridIndex, or 'gridLocationData to fill this info in
data GridLocationData = MkGridLocationData IsSpan (These Integer GridArea)
Copy link
Collaborator

Choose a reason for hiding this comment

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

The Constructor should have the same name as the type.

src/Clay/Grid.hs Outdated Show resolved Hide resolved
@turion
Copy link
Collaborator

turion commented Jan 9, 2022

I would like to complete this PR.

Fantastic, looking forward to reviewing further :)

@turion #210 (comment) Do you have a recommended naming scheme for handling complex data definitions? I think it's a benefit, not a disadvantage, for the record type name, to have a similar name to the sum type constructor's name, this links the two clearly. Though I do agree there needs to be a convention to know which is which.

For sum types, I took the approach of prefixing constructor names with the name of type. This can be done with or without underscores. I removed the underscores, because they are at odds with the rest of the library which doesn't use underscores. (Though, I do think the underscores make it easier to visually parse).

This is a tough problem that I also often have. A few rules of thumb that I have for these situations:

  • Newtypes & product types have to have the same constructor name as the type name (GridLocationData in this example)
  • There cannot be a constructor with the name of an existing, distinct type (because otherwise people will think that the previous rule applies)
  • In sum types, ideally the constructor name doesn't need to repeat the type name. If collisions occur, avoid them by first thinking about different naming, and then putting types into submodules.

Specifically for clay, I'd add another rule of thumb:

  • If some Haskell definition matches exactly some known CSS concept, the naming should exactly mirror the CSS naming (adapted to camelCase and UpperCamelCase).

In your case though, I'd simply remove the GridLocationData type and add the fields to the corresponding constructor of the GridLocation type, though. The GridLocationData type isn't used anywhere else, and the total size of instance code will not be higher I think.

data IsSpan = Span | NoSpan
deriving (Show, Eq)

data GridLocation
Copy link
Collaborator

Choose a reason for hiding this comment

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

Since this is not a known CSS concept (as far as I know), describe a bit what it is and what it is good for.


-------------------------------------------------------------------------------

gridRowStart :: GridLocation -> Css
Copy link
Collaborator

Choose a reason for hiding this comment

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

But I thought grid rows don't support spans? See https://www.w3schools.com/cssref/pr_grid-row-start.asp. Maybe GridLocation should be GridColumnLocation?

class Hidden a where hidden :: a
class Initial a where initial :: a
class Unset a where unset :: a
class MinContent a where minContent :: a
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is there any chance that some other types than Value will also be instances of your new classes?

, gridTemplateColumns
)
where
( gap
Copy link
Collaborator

Choose a reason for hiding this comment

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

Are there many definitions in this module that are not exported? If everything is exported, we can also simply not restrict the exports.


-- | Property sets the gaps (gutters) between rows and columns.
gridGap :: Size a -> Css
gridGap = key "grid-gap"
-- Sets both "gap" & "grid-gap"
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
-- Sets both "gap" & "grid-gap"
--
-- Sets both "gap" & "grid-gap"

gap = key "gap" <> key "grid-gap"

-- | Property sets the size of the gap (gutter) between an element's grid rows.
-- Sets both "row-gap" & "grid-row-gap"
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
-- Sets both "row-gap" & "grid-row-gap"
--
-- Sets both "row-gap" & "grid-row-gap"

At least I believe this will make a paragraph in haddock.

rowGap = key "row-gap" <> key "grid-row-gap"

-- | Property sets the size of the gap (gutter) between an element's grid columns.
-- Sets both "column-gap" & "grid-column-gap"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same here

gridTemplateColumns = key "grid-template-columns"

newtype GridTrackList a = GridTrackList Value
deriving (Val, None, Inherit, Initial, Unset)
Copy link
Collaborator

@turion turion Jan 9, 2022

Choose a reason for hiding this comment

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

Suggested change
deriving (Val, None, Inherit, Initial, Unset)
deriving (Val, None, Inherit, Initial, Unset, Auto, MaxContent, MinContent)

gridTemplateColumns :: GridTrackList a -> Css
gridTemplateColumns = key "grid-template-columns"

newtype GridTrackList a = GridTrackList Value
Copy link
Collaborator

Choose a reason for hiding this comment

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

Since Size is also an instance of None, Initial, etc., why not define it directly as a list of sizes?

Suggested change
newtype GridTrackList a = GridTrackList Value
newtype GridTrackList a = GridTrackList [Size a]

gridTemplateColumns :: GridTrackList a -> Css
gridTemplateColumns = key "grid-template-columns"

newtype GridTrackList a = GridTrackList Value
Copy link
Collaborator

Choose a reason for hiding this comment

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

My understanding now is that GridTrackList is a concept that has no corresponding CSS name, and it mirrors the possible values one can give to grid-template-columns and grid-template-rows. Maybe the naming can reflect this, and stay closer to known concepts? So for example GridTemplateSizes?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Or maybe these are called "tracks" in the community, and I just don't know?

Comment on lines +99 to +100
mkGridTrackList :: [Size a] -> GridTrackList a
mkGridTrackList = GridTrackList . noCommas
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
mkGridTrackList :: [Size a] -> GridTrackList a
mkGridTrackList = GridTrackList . noCommas
gridTrackList :: [Size a] -> GridTrackList a
gridTrackList = GridTrackList . noCommas

But see https://github.com/sebastiaanvisser/clay/pull/210/files#r780772464 first.

src/Clay/Grid.hs Outdated Show resolved Hide resolved
gridAutoColumns :: GridAutoTrackList a -> Css
gridAutoColumns = key "grid-auto-columns"

newtype GridAutoTrackList a = GridAutoTrackList Value
Copy link
Collaborator

Choose a reason for hiding this comment

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

The same comments as for GridTrackList apply.

Copy link
Collaborator

@turion turion left a comment

Choose a reason for hiding this comment

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

First superficial round done. I like where this is going!

CharlesTaylor7 and others added 2 commits January 9, 2022 16:59
Co-authored-by: Manuel Bärenz <[email protected]>
gridTemplateColumns none

describe "list of sizes" $ do
"{grid-template-columns:1em calc(20% + 1fr) auto}"
Copy link
Contributor

Choose a reason for hiding this comment

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

From the grid spec:

Note: <flex> values are not <length>s (nor are they compatible with <length>s, like some <percentage> values), so they cannot be represented in or combined with other unit types in calc() expressions.

@CharlesTaylor7
Copy link
Author

So I obviously lost steam to work on this, and there's a newer pull request in place, so withdrawing this PR. #250

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.

Support grid
5 participants