Elm Tree Workshop
- teaching
- Elm programming
- web development
A workshop that will give you a glimpse into the way software is created. It is intended for people with no prior experience in programming and doesn’t require any technical knowledge. Everybody is welcome! During this 5 days workshop (3 hours each day) you will learn to solve problems using a functional programming language. Together, we will build a program that simulates growth of a tree. In 2019 the workshop was presented to students of Utrecht University.
- Web development
- Education technology
- Elm programming
- Procedural animation
- Free / Open source software
Devlog
Commits: 1
Fix faux browser window buttons size
index 91c211c..00e067b 100644
--- a/src/BrowserWindow.elm
+++ b/src/BrowserWindow.elm
@@ -28,9 +28,6 @@ window attributes content =
= |> Element.text
= |> Element.el
= [ color |> Font.color
- , Font.size 35
- , Element.width (Element.px 20)
- , Element.height (Element.px 35)
= , Font.center
= ]
= in
Commits: 1
Update Fathom Analytics URL
index c5f325a..431d7a1 100644
--- a/container.html
+++ b/container.html
@@ -25,6 +25,6 @@
= <script>
= Elm.Main.init({ flags: { markup: null } })
= </script>
- <script src="https://hookworm.hornbook.io/script.js" site="ZCEAQSWL" spa="auto" defer></script>
+ <script src="https://cdn.usefathom.com/script.js" site="ZCEAQSWL" spa="auto" defer></script>
= </body>
=</html>
Commits: 1
Update Fathom analytics site ID
index 28d7815..c5f325a 100644
--- a/container.html
+++ b/container.html
@@ -25,6 +25,6 @@
= <script>
= Elm.Main.init({ flags: { markup: null } })
= </script>
- <script src="https://hookworm.hornbook.io/script.js" site="BGOCWZLP" spa="auto" defer></script>
+ <script src="https://hookworm.hornbook.io/script.js" site="ZCEAQSWL" spa="auto" defer></script>
= </body>
=</html>
Commits: 1
Adapt content to Software Garden being a consulting
This is now hosted at https://elm-tree.software.garden/. Content needs to reflect it.
Also our discourse server is dead for long time now. No point in keeping a link to it.
index f186b7b..b80119f 100644
--- a/content/index.txt
+++ b/content/index.txt
@@ -1,11 +1,11 @@
=| Title
- Software Garden
+ Growing an Elm Tree
=
=| Emphasize
= <>
=
= A Software Development Workshop for Non-Programmers
- by {Link|Hornbook Company|url=https://hornbook.io/}
+ by {Link|Software Garden|url=https://software.garden/}
= in Amsterdam and Utrecht, the Netherlands
=
=Hello! We are running a workshop that will give you a glimpse into the way software is created. We think {Link|it's important|url=/motivation.html}.
@@ -70,7 +70,7 @@ The price for individual participants is *500<>€* (inc. 21% VAT). Utrecht Univ
=| Subtitle
= What Do I Get?
=
-In addition to 15 hours of training you will get access to {Link|our discussion site|url=https://discuss.hornbook.io/} where you can ask questions. Upon completion of the course your name, program that you will have created and link to your website (or LinkedIn profile etc) will be posted {Link|here|url=/test-run.html}.
+You will get 15 hours of training. Upon completion of the course your name, program that you will have created and link to your website (or LinkedIn profile etc) will be posted {Link|here|url=/test-run.html}.
=
=Below is the material through which we will be going during the workshop. Only the first section is a required read before you come.
=
@@ -103,10 +103,10 @@ Below is the material through which we will be going during the workshop. Only t
=| Subtitle
= About us
=
-*Tad Lispy*: I'm going to be your teacher during the workshop. I've been working as a software developer for the best part of the last six years. Before that I've been a lawyer.
+*Tad Lispy*: I'm going to be your teacher during the workshop. I'm a co-founder of Software Garden and I've been working as a software developer for the best part of the last eight years. Before that I've been a lawyer.
=
=I love the creativity of the software development and hope to share that passion with you.
=
-*Fana* I'm a junior software developer and together with Tad a co-founder of {Link|Hornbook Company|url=https://hornbook.io/}. At the workshop I am going to be your teaching assistant. Previously I was teaching English in China and working as a marine biologist {Icon|name=anchor} in Eritrea, Italy, Finland, Azores and Spain.
+*Fana* I'm a Clojure developer at {Link|AdGoji|url=https://www.adgoji.com/}. At the workshop I am going to be your teaching assistant. Previously I was teaching English in China and working as a marine biologist {Icon|name=anchor} in Eritrea, Italy, Finland, Azores and Spain.
=
-*Sam* is a co-author of the workshop. He is an Elm developer at {Link|itravel|url=https://www.itravel.de/} in Cologne, Germany.
+*Sam Phillips* is a co-author of the workshop. He is an Elm developer at {Link|itravel|url=https://www.itravel.de/} in Cologne, Germany.
Commits: 1
Change contact e-mail address
index 3c9bc17..f186b7b 100644
--- a/content/index.txt
+++ b/content/index.txt
@@ -58,7 +58,7 @@ Currently we don't have a fixed date for the next round yet. If you are interest
=| Emphasize
= Say hello {Icon|name=phone} {Link|+31 638 216 166|url=tel:+31638216166}
=
- or drop us a note at {Link|fana@software.garden|url= mailto:fana@software.garden}
+ or drop us a note at {Link|software-garden@tad-lispy.com|url= mailto:software-garden@tad-lispy.com}
=
=
=| Subtitle
@@ -103,10 +103,10 @@ Below is the material through which we will be going during the workshop. Only t
=| Subtitle
= About us
=
-*Ted*: I'm going to be your teacher during the workshop. I've been working as a software developer for the best part of the last six years. Before that I've been a lawyer.
+*Tad Lispy*: I'm going to be your teacher during the workshop. I've been working as a software developer for the best part of the last six years. Before that I've been a lawyer.
=
=I love the creativity of the software development and hope to share that passion with you.
=
-*Fana* I'm a junior software developer and together with Ted a co-founder of {Link|Hornbook Company|url=https://hornbook.io/}. At the workshop I am going to be your teaching assistant. Previously I was teaching English in China and working as a marine biologist {Icon|name=anchor} in Eritrea, Italy, Finland, Azores and Spain.
+*Fana* I'm a junior software developer and together with Tad a co-founder of {Link|Hornbook Company|url=https://hornbook.io/}. At the workshop I am going to be your teaching assistant. Previously I was teaching English in China and working as a marine biologist {Icon|name=anchor} in Eritrea, Italy, Finland, Azores and Spain.
=
=*Sam* is a co-author of the workshop. He is an Elm developer at {Link|itravel|url=https://www.itravel.de/} in Cologne, Germany.index 89d29c0..29ec55e 100644
--- a/content/motivation.txt
+++ b/content/motivation.txt
@@ -8,6 +8,6 @@ Computer programs are moving things around us, they talk and listen to us and to
=We won't make you a professional software developer - that takes months to years of education and experience. But you don't have to be a professional novelist to read and write. Perhaps you work with IT professionals or considering becoming one. Perhaps you want to help your kids to learn programming. Maybe you just want to learn something about the world. Come, sit with us and see the world through the eyes of 21st century wizards (a friendly kind).
=
=| Emphasize
- You can ask Fana about the available dates!
+ You can ask us about the available dates!
=
- {Icon|name=phone} {Link|+31 638 216 166|url=tel:+31638216166} | {Icon|name=mail} {Link|fana@software.garden|url= mailto:fana@software.garden} | {Icon|name=home} {Link|Back|url=/}
+ {Icon|name=phone} {Link|+31 638 216 166|url=tel:+31638216166} | {Icon|name=mail} {Link|software-garden@tad-lispy.com|url= mailto:software-garden@tad-lispy.com} | {Icon|name=home} {Link|Back|url=/}index 41bf6c6..5a6dbce 100644
--- a/content/preparation.txt
+++ b/content/preparation.txt
@@ -59,4 +59,4 @@ Click {Icon|name=align-left} to make sure it's formatted nicely and then {Key|CO
=
= {Icon|name=help-circle}
=
- Please reach out to {Link|fana@software.garden|url= mailto:fana@software.garden}.
+ Please reach out to {Link|software-garden@tad-lispy.com|url= mailto:software-garden@tad-lispy.com}.index 63c686c..c2c4d70 100644
--- a/content/second-workshop.txt
+++ b/content/second-workshop.txt
@@ -12,4 +12,4 @@ In collaboration with {Link|Career Services|url=https://students.uu.nl/en/career
=
= If you have any questions don't hesitate to get in touch.
=
- {Icon|name=phone} {Link|+31 638 216 166|url=tel:+31638216166} | {Icon|name=mail} {Link|fana@software.garden|url= mailto:fana@software.garden} | {Icon|name=home} {Link|Back|url=/}
+ {Icon|name=phone} {Link|+31 638 216 166|url=tel:+31638216166} | {Icon|name=mail} {Link|software-garden@tad-lispy.com|url= mailto:software-garden@tad-lispy.com} | {Icon|name=home} {Link|Back|url=/}index 1df1597..7b96c51 100644
--- a/content/test-run.txt
+++ b/content/test-run.txt
@@ -52,4 +52,4 @@ See {Link|Anouck's program|url=https://ellie-app.com/4Yq4PR47CD5a1}
= If you are interested please get in touch
=
=
- {Icon|name=phone} {Link|+31 638 216 166|url=tel:+31638216166} | {Icon|name=mail} {Link|fana@software.garden|url= mailto:fana@software.garden} | {Icon|name=home} {Link|Back|url=/}
+ {Icon|name=phone} {Link|+31 638 216 166|url=tel:+31638216166} | {Icon|name=mail} {Link|software-garden@tad-lispy.com|url= mailto:software-garden@tad-lispy.com} | {Icon|name=home} {Link|Back|url=/}
Commits: 1
Fix mistake in day-4
index 6b632c1..21504b6 100644
--- a/content/day-4.txt
+++ b/content/day-4.txt
@@ -142,7 +142,7 @@ The tree is built from segments: a line and a dot. In this respect it is similar
= ]
= []
=
-Press {Key|COMPILE} on Ellie. There should be no difference at this point. Looking back at code I notice that each segment looks the same: it is a group with a dot and a line, where the {Code|group} and {Code|line} functions have the same arguments passed to them. This kind of repetition begs for a function. Let's call it {Code|segment} and define like this:
+Press {Key|COMPILE} on Ellie. There should be no difference at this point. Looking back at code I notice that each segment looks the same: it is a group with a dot and a line, where the {Code|dot} and {Code|line} functions have the same arguments passed to them. This kind of repetition begs for a function. Let's call it {Code|segment} and define like this:
=
=| Editor
= | Annotations
Commits: 1
Replace Matomo with Fathom
index 7620f1d..28d7815 100644
--- a/container.html
+++ b/container.html
@@ -18,24 +18,6 @@
=
= }
= </style>
-
-
- <!-- Matomo -->
- <script type="text/javascript">
- var _paq = window._paq || [];
- /* tracker methods like "setCustomDimension" should be called before "trackPageView" */
- _paq.push(['trackPageView']);
- _paq.push(['enableLinkTracking']);
- (function() {
- var u="https://hornbook.matomo.cloud/";
- _paq.push(['setTrackerUrl', u+'matomo.php']);
- _paq.push(['setSiteId', '4']);
- var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
- g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
- })();
- </script>
- <!-- End Matomo Code -->
-
= </head>
= <body>
= <!-- <div id="app-container"></div> -->
@@ -43,5 +25,6 @@
= <script>
= Elm.Main.init({ flags: { markup: null } })
= </script>
+ <script src="https://hookworm.hornbook.io/script.js" site="BGOCWZLP" spa="auto" defer></script>
= </body>
=</html>
Commits: 1
Fix cartesian coordinates example - label of the x slider
The label of the x-slider was diplaying the y value.
index 18f4a42..dd1df31 100644
--- a/src/Examples/CartesianCoordinates.elm
+++ b/src/Examples/CartesianCoordinates.elm
@@ -183,7 +183,7 @@ ui config model =
= { onChange = SetX
= , label =
= Input.labelBelow [ Element.centerX ] <|
- Element.text ("x = " ++ String.fromFloat (Spring.target model.y))
+ Element.text ("x = " ++ String.fromFloat (Spring.target model.x))
= , min = config.minX
= , max = config.maxX
= , value = Spring.target model.x
Commits: 2
Merge branch 'master' of gitlab.com:software-garden/software-garden.gitlab.io
Remove ACME challenge files
Since Let's Encrypt certificates are now managed by GitLab there is no need to keep those files anymore.
deleted file mode 100644
index bb6cf95..0000000
--- a/.well-known/acme-challenge/UBNiEIBLenAB3IqiiGcCtBr74O7hx65wX4DljVwQ0Mg
+++ /dev/null
@@ -1 +0,0 @@
-UBNiEIBLenAB3IqiiGcCtBr74O7hx65wX4DljVwQ0Mg.HvUI1nPJPHTnM--CR4G53bMPL5yhCFd2qGlsMDuIw14deleted file mode 100644
index a883d70..0000000
--- a/.well-known/acme-challenge/pdVc2jkKwuMJABabK3RmuCzmB-OgxynG3pPgaKnqmYE
+++ /dev/null
@@ -1 +0,0 @@
-pdVc2jkKwuMJABabK3RmuCzmB-OgxynG3pPgaKnqmYE.HvUI1nPJPHTnM--CR4G53bMPL5yhCFd2qGlsMDuIw14index c8408b6..3eee960 100755
--- a/scripts/build
+++ b/scripts/build
@@ -15,7 +15,6 @@ npx elm make src/Main.elm --output built/index.js --optimize
=cp -r content/ public/content/
=cp -r assets/ public/assets/
=cp -r built/ public/built/
-cp -r .well-known/ public/.well-known/
=
=
=mkdir -p built/captured/
Commits: 1
Update ECMA challenge file for new Let's Encrypt certificate
new file mode 100644
index 0000000..a883d70
--- /dev/null
+++ b/.well-known/acme-challenge/pdVc2jkKwuMJABabK3RmuCzmB-OgxynG3pPgaKnqmYE
@@ -0,0 +1 @@
+pdVc2jkKwuMJABabK3RmuCzmB-OgxynG3pPgaKnqmYE.HvUI1nPJPHTnM--CR4G53bMPL5yhCFd2qGlsMDuIw14
Commits: 5
Fix typo
index 533e942..b3b701b 100644
--- a/content/day-1.txt
+++ b/content/day-1.txt
@@ -312,7 +312,7 @@ Fortunately, we don't need to know the width and height of the {Code|svg} elemen
=
=You can imagine an SVG element as an infinite surface on which elements are placed. Which elements you see, depends on where you look. And where you look is called the *viewbox*. It has four properties: {Code|top}, {Code|left}, {Code|width} and {Code|height}.
=
-It works a little bit like a photo camera pointing directly at the surface. You can move it up, down (along the {Code|y} axis) and left and right (along the {Code|x} axis). You can also change its focal length to cover more or less area. Unlike a lens of the camera you set the width and height separately. If you set it so that the dot is in the middle of your frame, then no matter how big the print of the picture will be, the dot will always be in the center. Increasing the {Code|width} and {Code|height} of the viewbox will make more of the surrounding surface visible and the dot will become smaller, but it's position won't change. Let's see it in action:
+It works a little bit like a photo camera pointing directly at the surface. You can move it up, down (along the {Code|y} axis) and left and right (along the {Code|x} axis). You can also change its focal length to cover more or less area. Unlike a lens of the camera you set the width and height separately. If you set it so that the dot is in the middle of your frame, then no matter how big the print of the picture will be, the dot will always be in the center. Increasing the {Code|width} and {Code|height} of the viewbox will make more of the surrounding surface visible and the dot will become smaller, but its position won't change. Let's see it in action:
=
=| Row
= | ViewBoxindex 26c4ff9..3ff135b 100644
--- a/content/day-3.txt
+++ b/content/day-3.txt
@@ -51,7 +51,7 @@ Some value literals are simple, like numbers and strings. Some are complex. Prev
= > [ 1, 2, 5, 77, 2 ]
= [1,2,5,77.6,2] : List number
=
-First notice that a list literal starts with {Code|[} and ends with {Code|]}. Inside there are values called /elements of the list/. Each element is separated from others with a {Code|,} (comma). Then notice that each element is a value on its own, but the whole list is also a value.
+First notice that a list literal starts with {Code|[} and ends with {Code|]}. Inside there are values called /elements of the list/. Each element is separated from others with a {Code|,}(comma). Then notice that each element is a value on its own, but the whole list is also a value.
=
=| Note
= It can be called a /composite value/. It's like the box of eggs. Each egg is a thing, but the box of eggs is also a thing (composed of the box and the eggs).Correct grammar error
index fd705c2..1df1597 100644
--- a/content/test-run.txt
+++ b/content/test-run.txt
@@ -1,33 +1,33 @@
=| Title
= Former Students
=
-In collaboration with {Link|Career Services|url=https://students.uu.nl/en/careerservices} of the University of Utrecht we offered a one time, free workshop for 9 students from March 4 - March 8. Here are our students with their animated tree:
+In collaboration with {Link|Career Services|url=https://students.uu.nl/en/careerservices} of Utrecht University we offered a one time, free workshop for 9 students from March 4 - March 8. Here are our students with their animated tree:
=
=| Header
= Sarah Warnau
=
-Student of Environmental Sciences, University of Utrecht
+Student of Environmental Sciences, Utrecht University
=
=See {Link|Sarah's program|url=https://ellie-app.com/4WbyTFpZr6Da1} and {Link|LinkedIn profile|url=https://www.linkedin.com/in/sarah-warnau/}
=
=| Header
= Maarten Zwaal
=
-Student of Linguistics, University of Utrecht
+Student of Linguistics, Utrecht University
=
=See {Link|Maarten's program|url=https://ellie-app.com/4VN9F2KcRzVa1} and {Link|LinkedIn profile|url=https://www.linkedin.com/in/maarten-zwaal-b1aa16a3/}
=
=| Header
= Iris Bondoc
=
-Student of Business Informatics, University of Utrecht
+Student of Business Informatics, Utrecht University
=
=See {Link|Iris's program|url=https://ellie-app.com/4VNvz7HvNcCa1}
=
=| Header
= Daniel Kirk
=
-Student of Biology of Disease, University of Utrecht
+Student of Biology of Disease, Utrecht University
=
=See {Link|Dan's program|url=https://ellie-app.com/4XBfc9d87RLa1}
=
@@ -35,14 +35,14 @@ See {Link|Dan's program|url=https://ellie-app.com/4XBfc9d87RLa1}
=| Header
= Ishak Guelai
=
-Student of Pharmaceutical Sciences, University of Utrecht
+Student of Pharmaceutical Sciences, Utrecht University
=
=See {Link|Ishak's program|url=https://ellie-app.com/4X6QQjNV3xVa1}
=
=| Header
= Anouck Fietje
=
-Student of Psychology, University of Utrecht
+Student of Psychology, Utrecht University
=
=See {Link|Anouck's program|url=https://ellie-app.com/4Yq4PR47CD5a1}
=Remove signup form
index e85e578..b1e8cce 100644
--- a/content/index.txt
+++ b/content/index.txt
@@ -58,16 +58,10 @@ Our workshop is intended for people with no prior experience in programming and
=| Subtitle
= Next Round
=
-We are going to run the second workshop at the University of Utrecht from Monday, *13th of May* to Friday 17th. The price for participation is *500<>€* (inc. 21% VAT).
+The price for participation is *500<>€* (inc. 21% VAT). We have a special offer for Utrecht University students at a discounted price of *300<>€* (inc. 21% VAT). As soon as we have enough participants we will announce the date and place of workshop.
=
=In addition to 15 hours of training you will get access to {Link|our discussion site|url=https://discuss.hornbook.io/} where you can ask questions. Upon completion of the course your name, program that you will have created and link to your website (or LinkedIn profile etc) will be posted {Link|here|url=/test-run.html}.
=
-| Coupon
- icon = gift
- primary = True
- text = Special offer for University of Utrecht students
- href = /second-workshop.html
-
=Below is the material through which we will be going during the workshop. Only the first section is a required read before you come.
=
=| LinkRemove signup form
Add price for participation
index b1e8cce..3c9bc17 100644
--- a/content/index.txt
+++ b/content/index.txt
@@ -48,17 +48,27 @@ During this *5 days workshop* (3 hours each day) you will learn to solve problem
=
=Our workshop is intended for people with no prior experience in programming and doesn't require any technical knowledge. Everybody is welcome!
=
+
+| Subtitle
+ Next Round
+
+Currently we don't have a fixed date for the next round yet. If you are interested to participate please get in touch and we will arrange a suitable schedule.
+
+
=| Emphasize
= Say hello {Icon|name=phone} {Link|+31 638 216 166|url=tel:+31638216166}
=
= or drop us a note at {Link|fana@software.garden|url= mailto:fana@software.garden}
=
=
-
=| Subtitle
- Next Round
+ How Much Does It Cost?
+
+The price for individual participants is *500<>€* (inc. 21% VAT). Utrecht University students enjoy a special discounted price of *300<>€* (inc. 21% VAT). We can also offer a discount for groups.
=
-The price for participation is *500<>€* (inc. 21% VAT). We have a special offer for Utrecht University students at a discounted price of *300<>€* (inc. 21% VAT). As soon as we have enough participants we will announce the date and place of workshop.
+
+| Subtitle
+ What Do I Get?
=
=In addition to 15 hours of training you will get access to {Link|our discussion site|url=https://discuss.hornbook.io/} where you can ask questions. Upon completion of the course your name, program that you will have created and link to your website (or LinkedIn profile etc) will be posted {Link|here|url=/test-run.html}.
=Merge branch 'remove-signup-form' into 'master'
Remove signup form
See merge request software-garden/software-garden.gitlab.io!46
Commits: 1
Integrate Matomo analytics
index 6e062a5..7620f1d 100644
--- a/container.html
+++ b/container.html
@@ -18,6 +18,24 @@
=
= }
= </style>
+
+
+ <!-- Matomo -->
+ <script type="text/javascript">
+ var _paq = window._paq || [];
+ /* tracker methods like "setCustomDimension" should be called before "trackPageView" */
+ _paq.push(['trackPageView']);
+ _paq.push(['enableLinkTracking']);
+ (function() {
+ var u="https://hornbook.matomo.cloud/";
+ _paq.push(['setTrackerUrl', u+'matomo.php']);
+ _paq.push(['setSiteId', '4']);
+ var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
+ g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
+ })();
+ </script>
+ <!-- End Matomo Code -->
+
= </head>
= <body>
= <!-- <div id="app-container"></div> -->
Commits: 4
Link to hornbook.io and discuss.hornbook.io
Reshuffle the content on home page. Make subtitles bold.
index d41b8f2..909aaf5 100644
--- a/content/index.txt
+++ b/content/index.txt
@@ -1,17 +1,16 @@
=| Title
= Software Garden
=
-| Subtitle
- A software development workshop for non-programmers
-
-Hello! We are running a workshop that will give you a glimpse into the way software is created. Our workshop is intended for people with no prior experience in programming and doesn't require any technical knowledge. Everybody is welcome!
-
=| Emphasize
- Say hello {Icon|name=phone} {Link|+31 638 216 166|url=tel:+31638216166}
+ <>
=
- or drop us a note at {Link|fana@software.garden|url= mailto:fana@software.garden}
+ A software development workshop for non-programmers
+ by {Link|Hornbook company|url=https://hornbook.io/}
+ in Amsterdam and Utrecht, the Netherlands
+
+Hello! We are running a workshop that will give you a glimpse into the way software is created. We think {Link|it's important|url=/motivation.html}.
=
-During this *5 days workshop* (3 hours each day) you will learn to solve problems using a functional programming language. We think {Link|it's important|url=/motivation.html}. Together, we will build a program that simulates growth of a tree, like this:
+During this *5 days workshop* (3 hours each day) you will learn to solve problems using a functional programming language. Together, we will build a program that simulates growth of a tree, like this:
=
=| Window
= | AnimatedTree
@@ -47,7 +46,21 @@ During this *5 days workshop* (3 hours each day) you will learn to solve problem
= color = olive
= rotation = -20
=
-Upon completion of the course your name, program that you will have created, photo and link to your website (or LinkedIn profile etc) will be posted {Link|here|url=/test-run.html}. The price for participation is *500 €* (inc. 21% VAT).
+Our workshop is intended for people with no prior experience in programming and doesn't require any technical knowledge. Everybody is welcome!
+
+| Emphasize
+ Say hello {Icon|name=phone} {Link|+31 638 216 166|url=tel:+31638216166}
+
+ or drop us a note at {Link|fana@software.garden|url= mailto:fana@software.garden}
+
+
+
+| Subtitle
+ Next Round
+
+We are going to run the second workshop at the University of Utrecht from Monday, *13th of May* to Friday, 17th. The price for participation is *500<>€* (inc. 21% VAT).
+
+In addition to 15 hours of training you will get access to {Link|our discussion site|url=https://discuss.hornbook.io/} where you can ask questions. Upon completion of the course your name, program that you will have created and link to your website (or LinkedIn profile etc) will be posted {Link|here|url=/test-run.html}.
=
=| Coupon
= icon = giftindex 2c9e0ce..e3164ab 100644
--- a/src/Mark/Custom.elm
+++ b/src/Mark/Custom.elm
@@ -502,6 +502,7 @@ subtitle =
= , Element.paddingXY 0 10
= , Element.width Element.fill
= , Element.spacing 10
+ , Font.bold
= , content
= |> String.toLower
= |> String.wordsTitleize the subtitle of the home page
index 909aaf5..3a3471f 100644
--- a/content/index.txt
+++ b/content/index.txt
@@ -4,8 +4,8 @@
=| Emphasize
= <>
=
- A software development workshop for non-programmers
- by {Link|Hornbook company|url=https://hornbook.io/}
+ A Software Development Workshop for Non-Programmers
+ by {Link|Hornbook Company|url=https://hornbook.io/}
= in Amsterdam and Utrecht, the Netherlands
=
=Hello! We are running a workshop that will give you a glimpse into the way software is created. We think {Link|it's important|url=/motivation.html}.Correct grammar.
Update Fana's bio.
index 3a3471f..e85e578 100644
--- a/content/index.txt
+++ b/content/index.txt
@@ -58,7 +58,7 @@ Our workshop is intended for people with no prior experience in programming and
=| Subtitle
= Next Round
=
-We are going to run the second workshop at the University of Utrecht from Monday, *13th of May* to Friday, 17th. The price for participation is *500<>€* (inc. 21% VAT).
+We are going to run the second workshop at the University of Utrecht from Monday, *13th of May* to Friday 17th. The price for participation is *500<>€* (inc. 21% VAT).
=
=In addition to 15 hours of training you will get access to {Link|our discussion site|url=https://discuss.hornbook.io/} where you can ask questions. Upon completion of the course your name, program that you will have created and link to your website (or LinkedIn profile etc) will be posted {Link|here|url=/test-run.html}.
=
@@ -103,6 +103,6 @@ Below is the material through which we will be going during the workshop. Only t
=
=I love the creativity of the software development and hope to share that passion with you.
=
-*Fana* is a junior software developer (an ex marine biologist {Icon|name=anchor}) and coordinator of our project. She keeps us on the right track. Also she is taking care of our media presence.
+*Fana* I'm a junior software developer and together with Ted a co-founder of {Link|Hornbook Company|url=https://hornbook.io/}. At the workshop I am going to be your teaching assistant. Previously I was teaching English in China and working as a marine biologist {Icon|name=anchor} in Eritrea, Italy, Finland, Azores and Spain.
=
=*Sam* is a co-author of the workshop. He is an Elm developer at {Link|itravel|url=https://www.itravel.de/} in Cologne, Germany.index 0124dc1..63c686c 100644
--- a/content/second-workshop.txt
+++ b/content/second-workshop.txt
@@ -4,7 +4,7 @@
=In collaboration with {Link|Career Services|url=https://students.uu.nl/en/careerservices} of the University of Utrecht we are running a second workshop. For the first five students it is free. For the next 10 students, we have a discounted price of 300 € (inc. 21% VAT).
=
=| Note
- Monday 13 May - Friday 17, 2019 Marinus Ruppert room B Time 16:00 - 19:00
+ Monday 13 May - Friday 17 May, 2019 Marinus Ruppert room B Time 16:00 - 19:00
=
=| Emphasize
= {Link|Sign Up|url= https://fd21.formdesk.com/universiteitutrecht/CS-20190513-17-SoftwareGarden}Merge branch 'hornbook' into 'master'
Link to hornbook.io and discuss.hornbook.io
See merge request software-garden/software-garden.gitlab.io!45
Commits: 4
Add sign up, date and place of workshop.
index f1e1668..8ccd230 100644
--- a/content/second-workshop.txt
+++ b/content/second-workshop.txt
@@ -1,9 +1,15 @@
=| Title
= Special Offer for University of Utrecht Students
=
-In collaboration with {Link|Career Services|url=https://students.uu.nl/en/careerservices} of the University of Utrecht we are running a second workshop. For the first five students it is free. If the 5 spots are filled, we have discounted price of 300 € (inc. 21% VAT) for the next 10 students.
+In collaboration with {Link|Career Services|url=https://students.uu.nl/en/careerservices} of the University of Utrecht we are running a second workshop. For the first five students it is free. For the next 10 students, we a have discounted price of 300 € (inc. 21% VAT).
+
+| Note
+ Monday 13 May - Friday 17, 2019 Marinus Ruppert room B Time 16:00 - 19:00
=
=| Emphasize
- If you are interested please get in touch
+ {Link|Sign Up|url= https://fd21.formdesk.com/universiteitutrecht/CS-20190513-17-SoftwareGarden}
+ <>
+
+ If you have any questions don't hesitate to get in touch.
=
= {Icon|name=phone} {Link|+31 638 216 166|url=tel:+31638216166} | {Icon|name=mail} {Link|fana@software.garden|url= mailto:fana@software.garden} | {Icon|name=home} {Link|Back|url=/}Merge branch 'second-workshop' into 'master'
Add offer for the second-workshop.
See merge request software-garden/software-garden.gitlab.io!43
Fix typo.
index 8ccd230..0124dc1 100644
--- a/content/second-workshop.txt
+++ b/content/second-workshop.txt
@@ -1,7 +1,7 @@
=| Title
= Special Offer for University of Utrecht Students
=
-In collaboration with {Link|Career Services|url=https://students.uu.nl/en/careerservices} of the University of Utrecht we are running a second workshop. For the first five students it is free. For the next 10 students, we a have discounted price of 300 € (inc. 21% VAT).
+In collaboration with {Link|Career Services|url=https://students.uu.nl/en/careerservices} of the University of Utrecht we are running a second workshop. For the first five students it is free. For the next 10 students, we have a discounted price of 300 € (inc. 21% VAT).
=
=| Note
= Monday 13 May - Friday 17, 2019 Marinus Ruppert room B Time 16:00 - 19:00Merge branch 'second-workshop' into 'master'
Fix typo.
See merge request software-garden/software-garden.gitlab.io!44
Commits: 1
Add offer for the second-workshop.
Remove navigation bar from second- workshop page.
Change the workshop hours from 4 hrs a day to 3 hrs a day in index.
Change the title of the test-run page from special offer to former students.
index 42287fc..d41b8f2 100644
--- a/content/index.txt
+++ b/content/index.txt
@@ -11,7 +11,7 @@ Hello! We are running a workshop that will give you a glimpse into the way softw
=
= or drop us a note at {Link|fana@software.garden|url= mailto:fana@software.garden}
=
-During this *5 days workshop* (4 hours each day) you will learn to solve problems using a functional programming language. We think {Link|it's important|url=/motivation.html}. Together, we will build a program that simulates growth of a tree, like this:
+During this *5 days workshop* (3 hours each day) you will learn to solve problems using a functional programming language. We think {Link|it's important|url=/motivation.html}. Together, we will build a program that simulates growth of a tree, like this:
=
=| Window
= | AnimatedTree
@@ -47,13 +47,13 @@ During this *5 days workshop* (4 hours each day) you will learn to solve problem
= color = olive
= rotation = -20
=
-Upon completion of the course your name, program that you will have created, photo and link to your website (or LinkedIn profile etc) will be posted here. The price for participation is *500 €* (inc. 21% VAT).
+Upon completion of the course your name, program that you will have created, photo and link to your website (or LinkedIn profile etc) will be posted {Link|here|url=/test-run.html}. The price for participation is *500 €* (inc. 21% VAT).
=
=| Coupon
= icon = gift
= primary = True
= text = Special offer for University of Utrecht students
- href = /test-run.html
+ href = /second-workshop.html
=
=Below is the material through which we will be going during the workshop. Only the first section is a required read before you come.
=new file mode 100644
index 0000000..f1e1668
--- /dev/null
+++ b/content/second-workshop.txt
@@ -0,0 +1,9 @@
+| Title
+ Special Offer for University of Utrecht Students
+
+In collaboration with {Link|Career Services|url=https://students.uu.nl/en/careerservices} of the University of Utrecht we are running a second workshop. For the first five students it is free. If the 5 spots are filled, we have discounted price of 300 € (inc. 21% VAT) for the next 10 students.
+
+| Emphasize
+ If you are interested please get in touch
+
+ {Icon|name=phone} {Link|+31 638 216 166|url=tel:+31638216166} | {Icon|name=mail} {Link|fana@software.garden|url= mailto:fana@software.garden} | {Icon|name=home} {Link|Back|url=/}index 884dc05..fd705c2 100644
--- a/content/test-run.txt
+++ b/content/test-run.txt
@@ -1,5 +1,5 @@
=| Title
- Special Offer for University of Utrecht Students
+ Former Students
=
=In collaboration with {Link|Career Services|url=https://students.uu.nl/en/careerservices} of the University of Utrecht we offered a one time, free workshop for 9 students from March 4 - March 8. Here are our students with their animated tree:
=index 0ffec3a..4ca5c79 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -211,6 +211,9 @@ view model =
= Routes.TestRun ->
= Element.none
=
+ Routes.SecondWorkshop ->
+ Element.none
+
= Routes.Content _ ->
= contentNavigationBar model
=
@@ -517,6 +520,12 @@ loadContent route =
= , expect = Http.expectString ContentFetched
= }
=
+ Routes.SecondWorkshop ->
+ Http.get
+ { url = "/content/second-workshop.txt"
+ , expect = Http.expectString ContentFetched
+ }
+
= Routes.Content base ->
= Http.get
= { url = "/content/" ++ base ++ ".txt"index a1a6bbf..ec93cd7 100644
--- a/src/Routes.elm
+++ b/src/Routes.elm
@@ -13,6 +13,7 @@ type Route
= | Content String
= | Motivation
= | TestRun
+ | SecondWorkshop
= | NotFound
=
=
@@ -23,6 +24,7 @@ parser =
= , Parser.map Home (Parser.s "index.html")
= , Parser.map Motivation (Parser.s "motivation.html")
= , Parser.map TestRun (Parser.s "test-run.html")
+ , Parser.map SecondWorkshop (Parser.s "second-workshop.html")
= , Parser.custom "Content"
= (\path ->
= case String.split "." path of
Commits: 1
Update acme challenge file to renew the TLS certificate
deleted file mode 100644
index 44f3504..0000000
--- a/.well-known/acme-challenge/HVUehfup9PAAASIMOcJoCGvD9q9bzX_W_tBUSSI6RXI
+++ /dev/null
@@ -1 +0,0 @@
-HVUehfup9PAAASIMOcJoCGvD9q9bzX_W_tBUSSI6RXI.HvUI1nPJPHTnM--CR4G53bMPL5yhCFd2qGlsMDuIw14new file mode 100644
index 0000000..bb6cf95
--- /dev/null
+++ b/.well-known/acme-challenge/UBNiEIBLenAB3IqiiGcCtBr74O7hx65wX4DljVwQ0Mg
@@ -0,0 +1 @@
+UBNiEIBLenAB3IqiiGcCtBr74O7hx65wX4DljVwQ0Mg.HvUI1nPJPHTnM--CR4G53bMPL5yhCFd2qGlsMDuIw14
Commits: 4
Add Anouck's profile to the offer page.
index 7f3d1f5..884dc05 100644
--- a/content/test-run.txt
+++ b/content/test-run.txt
@@ -13,23 +13,24 @@ See {Link|Sarah's program|url=https://ellie-app.com/4WbyTFpZr6Da1} and {Link|Lin
=| Header
= Maarten Zwaal
=
-Student of Taalwetenschap, University of Utrecht
+Student of Linguistics, University of Utrecht
=
=See {Link|Maarten's program|url=https://ellie-app.com/4VN9F2KcRzVa1} and {Link|LinkedIn profile|url=https://www.linkedin.com/in/maarten-zwaal-b1aa16a3/}
=
=| Header
- Daniel Kirk
+ Iris Bondoc
=
-Student of Biology of Disease, University of Utrecht
+Student of Business Informatics, University of Utrecht
=
-See {Link|Dan's program|url=https://ellie-app.com/4XBfc9d87RLa1}
+See {Link|Iris's program|url=https://ellie-app.com/4VNvz7HvNcCa1}
=
=| Header
- Iris Bondoc
+ Daniel Kirk
=
-Student of Business Informatics, University of Utrecht
+Student of Biology of Disease, University of Utrecht
+
+See {Link|Dan's program|url=https://ellie-app.com/4XBfc9d87RLa1}
=
-See {Link|Iris's program|url=https://ellie-app.com/4VNvz7HvNcCa1}
=
=| Header
= Ishak Guelai
@@ -38,6 +39,13 @@ Student of Pharmaceutical Sciences, University of Utrecht
=
=See {Link|Ishak's program|url=https://ellie-app.com/4X6QQjNV3xVa1}
=
+| Header
+ Anouck Fietje
+
+Student of Psychology, University of Utrecht
+
+See {Link|Anouck's program|url=https://ellie-app.com/4Yq4PR47CD5a1}
+
=| Emphasize
= We are hoping to do it again.
=Add Anouck's profile to the student profiles.
index 7f3d1f5..884dc05 100644
--- a/content/test-run.txt
+++ b/content/test-run.txt
@@ -13,23 +13,24 @@ See {Link|Sarah's program|url=https://ellie-app.com/4WbyTFpZr6Da1} and {Link|Lin
=| Header
= Maarten Zwaal
=
-Student of Taalwetenschap, University of Utrecht
+Student of Linguistics, University of Utrecht
=
=See {Link|Maarten's program|url=https://ellie-app.com/4VN9F2KcRzVa1} and {Link|LinkedIn profile|url=https://www.linkedin.com/in/maarten-zwaal-b1aa16a3/}
=
=| Header
- Daniel Kirk
+ Iris Bondoc
=
-Student of Biology of Disease, University of Utrecht
+Student of Business Informatics, University of Utrecht
=
-See {Link|Dan's program|url=https://ellie-app.com/4XBfc9d87RLa1}
+See {Link|Iris's program|url=https://ellie-app.com/4VNvz7HvNcCa1}
=
=| Header
- Iris Bondoc
+ Daniel Kirk
=
-Student of Business Informatics, University of Utrecht
+Student of Biology of Disease, University of Utrecht
+
+See {Link|Dan's program|url=https://ellie-app.com/4XBfc9d87RLa1}
=
-See {Link|Iris's program|url=https://ellie-app.com/4VNvz7HvNcCa1}
=
=| Header
= Ishak Guelai
@@ -38,6 +39,13 @@ Student of Pharmaceutical Sciences, University of Utrecht
=
=See {Link|Ishak's program|url=https://ellie-app.com/4X6QQjNV3xVa1}
=
+| Header
+ Anouck Fietje
+
+Student of Psychology, University of Utrecht
+
+See {Link|Anouck's program|url=https://ellie-app.com/4Yq4PR47CD5a1}
+
=| Emphasize
= We are hoping to do it again.
=Merge branch 'alumni' of gitlab.com:software-garden/software-garden.gitlab.io into alumni
Merge branch 'alumni' into 'master'
Add Anouck's profile to the student profiles
See merge request software-garden/software-garden.gitlab.io!42
Commits: 3
Add student profiles
index 39ca6b4..7f3d1f5 100644
--- a/content/test-run.txt
+++ b/content/test-run.txt
@@ -1,20 +1,47 @@
=| Title
= Special Offer for University of Utrecht Students
=
-In collaboration with {Link|Career Services|url=https://students.uu.nl/en/careerservices} of the University of Utrecht we are offering a one time, free workshop for the first 12 students to sign up. The workshop will be shorter than usual - 3 hours a day.
+In collaboration with {Link|Career Services|url=https://students.uu.nl/en/careerservices} of the University of Utrecht we offered a one time, free workshop for 9 students from March 4 - March 8. Here are our students with their animated tree:
=
-| Note
- 04 March 2019: /Marinus Ruppert Room 029/ Time *16:00* - *19:00*
- 05 March 2019: /Marinus Ruppert Room 005/ Time *16:00* - *19:00*
- 06 March 2019: /Marinus Ruppert Room 031/ Time *16:00* - *19:00*
- 07 March 2019: /Marinus Langeveld Room G230/ Time *16:00* - *19:00*
- 08 March 2019: /Marinus Ruppert Room 121/ Time *16:00* - *19:00*
+| Header
+ Sarah Warnau
=
-The workshop slots are almost filled. If you can't register and would like to attend in the future please, reach out to us. We will try to arrange another workshop with Utrecht University.
+Student of Environmental Sciences, University of Utrecht
+
+See {Link|Sarah's program|url=https://ellie-app.com/4WbyTFpZr6Da1} and {Link|LinkedIn profile|url=https://www.linkedin.com/in/sarah-warnau/}
+
+| Header
+ Maarten Zwaal
+
+Student of Taalwetenschap, University of Utrecht
+
+See {Link|Maarten's program|url=https://ellie-app.com/4VN9F2KcRzVa1} and {Link|LinkedIn profile|url=https://www.linkedin.com/in/maarten-zwaal-b1aa16a3/}
+
+| Header
+ Daniel Kirk
+
+Student of Biology of Disease, University of Utrecht
+
+See {Link|Dan's program|url=https://ellie-app.com/4XBfc9d87RLa1}
+
+| Header
+ Iris Bondoc
+
+Student of Business Informatics, University of Utrecht
+
+See {Link|Iris's program|url=https://ellie-app.com/4VNvz7HvNcCa1}
+
+| Header
+ Ishak Guelai
+
+Student of Pharmaceutical Sciences, University of Utrecht
+
+See {Link|Ishak's program|url=https://ellie-app.com/4X6QQjNV3xVa1}
=
=| Emphasize
- {Link|Sign up now|url=https://fd21.formdesk.com/universiteitutrecht/CS-20190304-08-SoftwareGarden}
+ We are hoping to do it again.
+
+ If you are interested please get in touch
=
- <>
=
= {Icon|name=phone} {Link|+31 638 216 166|url=tel:+31638216166} | {Icon|name=mail} {Link|fana@software.garden|url= mailto:fana@software.garden} | {Icon|name=home} {Link|Back|url=/}Merge remote-tracking branch 'origin/master' into alumni
Merge branch 'alumni' into 'master'
Add student profiles
See merge request software-garden/software-garden.gitlab.io!41
Commits: 4
Fix typo of the venue for Thursday.
index 39ca6b4..a155ad2 100644
--- a/content/test-run.txt
+++ b/content/test-run.txt
@@ -7,7 +7,7 @@ In collaboration with {Link|Career Services|url=https://students.uu.nl/en/career
= 04 March 2019: /Marinus Ruppert Room 029/ Time *16:00* - *19:00*
= 05 March 2019: /Marinus Ruppert Room 005/ Time *16:00* - *19:00*
= 06 March 2019: /Marinus Ruppert Room 031/ Time *16:00* - *19:00*
- 07 March 2019: /Marinus Langeveld Room G230/ Time *16:00* - *19:00*
+ 07 March 2019: /Martinus J Langeveld Room G230/ Time *16:00* - *19:00*
= 08 March 2019: /Marinus Ruppert Room 121/ Time *16:00* - *19:00*
=
=The workshop slots are almost filled. If you can't register and would like to attend in the future please, reach out to us. We will try to arrange another workshop with Utrecht University.Merge branch 'venue-thursday' into 'master'
Fix typo of the venue for Thursday.
See merge request software-garden/software-garden.gitlab.io!39
Fix some mistakes on day 3
index 661c9b8..26c4ff9 100644
--- a/content/day-3.txt
+++ b/content/day-3.txt
@@ -953,8 +953,8 @@ Armed with our functional superpowers, let's make another function that draws a
= [ Svg.Attributes.strokeWidth "1"
= , Svg.Attributes.x1 "0"
= , Svg.Attributes.y1 "0"
- , Svg.Attributes.x1 "80"
- , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x2 "80"
+ , Svg.Attributes.y2 "0"
= , Svg.Attributes.stroke color
= , Svg.Attributes.transform
= (String.concat
@@ -1063,7 +1063,7 @@ Once you have the function defined, let's use it. For every dot, create one line
= ]
= []
=
-You should see something like this in on Ellie:
+You should see something like this on Ellie:
=
=| Window
= | ShapesMerge branch 'fix-content-day-3' into 'master'
Fix some mistakes on day 3
See merge request software-garden/software-garden.gitlab.io!40
Commits: 9
Use tad-lispy/springs
I've published it 😎
index a5f4ae6..f19e8dc 100644
--- a/elm.json
+++ b/elm.json
@@ -25,6 +25,7 @@
= "ianmackenzie/elm-geometry-svg": "1.0.2",
= "mdgriffith/elm-markup": "2.0.6",
= "mdgriffith/elm-ui": "1.1.0",
+ "tad-lispy/springs": "1.0.4",
= "turboMaCk/any-dict": "1.0.1"
= },
= "indirect": {index a8f1e95..18f4a42 100644
--- a/src/Examples/CartesianCoordinates.elm
+++ b/src/Examples/CartesianCoordinates.elm
@@ -69,8 +69,8 @@ type alias Flags =
=
=init : Flags -> ( Model, Cmd Msg )
=init () =
- ( { x = Spring.create 20 20
- , y = Spring.create 20 20
+ ( { x = Spring.create { strength = 20, dampness = 5 }
+ , y = Spring.create { strength = 20, dampness = 5 }
= }
= , Cmd.none
= )index eb30eb9..0ffec3a 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -138,7 +138,7 @@ init flags url key =
= , key = key
= , content = content
= , examples = examplesModel
- , scroll = Spring.create 20 20
+ , scroll = Spring.create { strength = 20, dampness = 4 }
= , viewport =
= { width = 800, height = 600 }
= }deleted file mode 100644
index 0b7bdc3..0000000
--- a/src/Spring.elm
+++ /dev/null
@@ -1,248 +0,0 @@
-module Spring exposing
- ( Spring
- , animate
- , atRest
- , create
- , jumpTo
- , setTarget
- , target
- , value
- )
-
-{-| A rough model of a mass attached to a spring, as described by [Hooke's law](https://en.wikipedia.org/wiki/Hooke's_law). Good for making smooth and organic looking animations or modeling oscilating values (e.g. emotions). High physical accuracy is not a priority - performance and API is more important.
--}
-
-
-{-| A model of mass attached to a spring. The spring is anchored to a target. The mass is constant (1).
-
-Value represents the current position of the mass. It is re-calculated (together with velocity) by animate function.
-
-Strength regulates how strongly the spring pulls toward target. It is also called the stiffness but I find the former term more intuitive.
-
-Dampness is how resistant the spring is to change in it's stretch (both stretching out and contracting in). If dumpness is low relative to strength, then the animation will end in long period of vibration around the target value - in other words lowering dumpness will increase wobbliness. Setting dumpness to 0 will result in something like a sine wave oscilator (but it's not advise to depend on it's accuracy).
-
-Target is the value toward which the mass is pulled. Typically the spring will start in an equilibrium position (i.e. value == target) and later on (due to an event) the target will be changed and the value will follow according to the strength and dampness of the spring.
-
-Value is where the mass is. It can be extracted from the spring using `value` function and set (with `setValue` function - rarely useful).
-
-Velocity is an internal property that cannot be directly modified or read.
-
-Let's say we are creating a program that animates the position of an element toward last click position.
-
- type alias Model =
- { x : Spring
- , y : Spring
- }
-
--}
-type Spring
- = Spring
- { strength : Float
- , dampness : Float
- , target : Float
- , value : Float
- , velocity : Float
- }
-
-
-{-| Create a spring in an equilibrium state.
-
-Let's say we are creating a program that animates the position of an element toward last click position.
-
- type alias Model =
- { x : Spring
- , y : Spring
- }
-
- init : Flags -> ( Model, Cmd msg )
- init flags =
- ( { x = Spring.create 20 20
- , y = Spring.create 20 20
- }
- , Cmd.none
- )
-
--}
-create : Float -> Float -> Spring
-create strength dampness =
- Spring
- { strength = strength
- , dampness = dampness
- , target = 0
- , value = 0
- , velocity = 0
- }
-
-
-{-| Set a new target (relaxed value) for the spring.
-
-The current value and it's velocity will remain the same. Typically you would set a target in response to an event, e.g.:
-
- update : Msg -> Model -> ( Model, Cmd Msg )
- update msg model =
- case msg of
- Click x y ->
- ( { model
- | x = Spring.setTarget x model.x
- , y = Spring.setTarget y model.y
- }
- , Cmd.none
- )
-
--}
-setTarget : Float -> Spring -> Spring
-setTarget target_ (Spring spring) =
- Spring
- { spring
- | target =
- target_
- }
-
-
-{-| Update the spring
-
-Typically you would do it in response to an animation frame message, like this:
-
- subscriptions : Model -> Sub Msg
- subscriptions model =
- Browser.Events.onAnimationFrameDelta Animate
-
- update : Msg -> Model -> ( Model, Cmd Msg )
- update msg model =
- case msg of
- Animate delta ->
- ( { model
- | x = Spring.animate delta model.x
- , y = Spring.animate delta model.y
- }
- , Cmd.none
- )
-
--}
-animate : Float -> Spring -> Spring
-animate delta ((Spring spring) as this) =
- if atRest this then
- {- If it's in equilibrium, then let's just skip the whole calculation. Be lazy. -}
- Spring spring
-
- else
- let
- time =
- {- Low sampling rates often result in nasty errors.
-
- With this cap on delta, if the frame rate is lower than 30fps the animation will slow down and good sampling rate will be preserved.
- -}
- min delta 32 / 1000
-
- stretch =
- spring.target - spring.value
-
- force =
- stretch * spring.strength
-
- dampening =
- spring.velocity * spring.dampness
-
- acceleration =
- time * (force - dampening)
- in
- if
- (abs (spring.value - spring.target) < 0.001)
- && (abs spring.velocity < 0.001)
- then
- {- In reality the spring never stops vibrating, but at some point the vibration is lost in the background noise (uncertainity principle). In our case it's also a wasted computation. Let's just say that it is at rest already. -} {- Snap to ideal equilibrium -}
- Spring
- { spring
- | value = spring.target
- , velocity = 0
- }
-
- else
- Spring
- { spring
- | value =
- spring.value + (spring.velocity * time)
- , velocity =
- spring.velocity + acceleration
- }
-
-
-{-| Measure the value of the spring.
-
-Typically you want to access it in the view function, like this:
-
- Element.el
- [ Element.width (Element.px 20)
- , Element.height (Element.px 20)
- , Font.size 30
- , model.x
- |> Spring.value
- |> Element.moveRight
- , model.y
- |> Spring.value
- |> Element.moveDown
- ]
- (Element.text "\u{1F991}")
-
-Above we use Elm UI Elements and Attributes, but it's not difficult to implement same behavior using CSS transformations. Spring value is just a `Float`.
-
--}
-value : Spring -> Float
-value (Spring spring) =
- spring.value
-
-
-{-| Get current target of a spring
-
-Can be useful to see where the spring is going. Maybe you want to display something there?
-
--}
-target : Spring -> Float
-target (Spring spring) =
- spring.target
-
-
-{-| Check if the spring is at rest
-
-It indicates that no animation is running. Maybe you want to unsubscribe from animation frames? Or remove an element?
-
- subscriptions : Model -> Sub Msg
- subscriptions model =
- if Spring.atRest model.x && Spring.atRest model.y then
- Sub.none
-
- else
- Browser.Events.onAnimationFrameDelta Animate
-
--}
-atRest : Spring -> Bool
-atRest (Spring spring) =
- spring.value == spring.target && spring.velocity == 0.0
-
-
-{-| Forcefully set the value and interrupt the animation.
-
-It is useful when you set the spring for the first time (e.g. in init function) or you want to reset the animation.
-
- init : Flags -> ( Model, Cmd msg )
- init flags =
- ( { x =
- Spring.create 20 20
- |> Spring.setTarget 200
- |> Spring.jumpTo 200
- , y =
- Spring.create 20 20
- |> Spring.setTarget 200
- |> Spring.jumpTo 200
- }
- , Cmd.none
- )
-
--}
-jumpTo : Float -> Spring -> Spring
-jumpTo value_ (Spring spring) =
- Spring
- { spring
- | value = value_
- , velocity = 0.0
- }Remove Examples/Spring
It lives at https://tad-lispy.gitlab.io/elm-springs/Squid.html now
deleted file mode 100644
index 690d5fb..0000000
--- a/src/Examples/Spring.elm
+++ /dev/null
@@ -1,139 +0,0 @@
-module Examples.Spring exposing
- ( Model
- , Msg
- , init
- , main
- , subscriptions
- , ui
- , update
- )
-
-import Browser
-import Browser.Events
-import Element exposing (Element)
-import Element.Background as Background
-import Element.Events as Events
-import Element.Font as Font
-import Element.Input as Input
-import Examples.Tree
-import FeatherIcons
-import Html exposing (Html)
-import Html.Events
-import Json.Decode as Decode exposing (Decoder)
-import Spring exposing (Spring)
-
-
-main : Program Flags Model Msg
-main =
- Browser.element
- { init = init
- , view = view
- , update = update
- , subscriptions = subscriptions
- }
-
-
-type alias Model =
- { x : Spring
- , y : Spring
- }
-
-
-type alias Flags =
- ()
-
-
-init : Flags -> ( Model, Cmd msg )
-init flags =
- let
- strength =
- 20
-
- dampness =
- 20
- in
- ( { x =
- Spring.create strength dampness
- |> Spring.setTarget 200
- , y =
- Spring.create strength dampness
- |> Spring.setTarget 200
- }
- , Cmd.none
- )
-
-
-ui : Model -> Element Msg
-ui model =
- Element.el
- [ Element.width (Element.px 20)
- , Element.height (Element.px 20)
- , Font.size 30
-
- -- , Background.color (Element.rgb 1 0 0)
- , model.x
- |> Spring.value
- |> Element.moveRight
- , model.y
- |> Spring.value
- |> Element.moveDown
- ]
- (Element.text "\u{1F991}")
-
-
-type Msg
- = Animate Float
- | Mouse Float Float
-
-
-view : Model -> Html Msg
-view model =
- model
- |> ui
- |> Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
- , mouseDecoder
- |> Html.Events.on "click"
- |> Element.htmlAttribute
- ]
-
-
-update : Msg -> Model -> ( Model, Cmd Msg )
-update msg model =
- case msg of
- Animate delta ->
- ( { model
- | x = Spring.animate delta model.x
- , y = Spring.animate delta model.y
- }
- , Cmd.none
- )
-
- Mouse x y ->
- ( { model
- | x = Spring.setTarget x model.x
- , y = Spring.setTarget y model.y
- }
- , Cmd.none
- )
-
-
-mouseDecoder : Decoder Msg
-mouseDecoder =
- Decode.map2 Mouse
- (Decode.field "clientX" Decode.float)
- (Decode.field "clientY" Decode.float)
- |> Decode.map (Debug.log "Msg")
-
-
-subscriptions : Model -> Sub Msg
-subscriptions model =
- [ if Spring.atRest model.x && Spring.atRest model.y then
- Sub.none
-
- else
- Browser.Events.onAnimationFrameDelta Animate
- , Browser.Events.onMouseMove mouseDecoder
- ]
- |> Sub.batchAdd a text on test-run.
Informing students who can't register to reach out to us.
index 9a06486..39ca6b4 100644
--- a/content/test-run.txt
+++ b/content/test-run.txt
@@ -10,6 +10,8 @@ In collaboration with {Link|Career Services|url=https://students.uu.nl/en/career
= 07 March 2019: /Marinus Langeveld Room G230/ Time *16:00* - *19:00*
= 08 March 2019: /Marinus Ruppert Room 121/ Time *16:00* - *19:00*
=
+The workshop slots are almost filled. If you can't register and would like to attend in the future please, reach out to us. We will try to arrange another workshop with Utrecht University.
+
=| Emphasize
= {Link|Sign up now|url=https://fd21.formdesk.com/universiteitutrecht/CS-20190304-08-SoftwareGarden}
=Merge branch 'reg-wait-list' into 'master'
Add a text on test-run.
See merge request software-garden/software-garden.gitlab.io!36
Merge branch 'springs' into 'master'
Use tad-lispy/strings for better performance
See merge request software-garden/software-garden.gitlab.io!35
Create flash cards for day 1
For now we can use markdown-preview-enhanced plugin for Atom to view them. Install the plugin, open the preview with:
Cmd-Shift-P > Markdown Preview Enhanced: Toggle
Then right click on the preview and chose:
Open in Browser
In the future we may implement something to build them automatically (based on Mume: https://github.com/shd101wyy/mume).
new file mode 100644
index 0000000..78a2ed5
--- /dev/null
+++ b/flash-cards.md
@@ -0,0 +1,235 @@
+---
+presentation:
+ enableSpeakerNotes: true
+ theme: solarized.css
+ height: 150%
+---
+
+<!-- slide -->
+
+# After day 1
+
+<!-- slide vertical -->
+
+On day one we have created a program that ...
+
+
+1. ... calculates the phase of the moon
+1. ... displays a dot in the middle of the screen <!-- .element: class="fragment grow" -->
+1. ... makes the screen pink
+
+
+<!-- slide vertical -->
+
+Consider the following code:
+
+```elm
+Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200" ]
+ [ Svg.circle
+ [ Svg.Attributes.cx "300"
+ , Svg.Attributes.cy "100"
+ , Svg.Attributes.r "10"
+ ]
+ []
+ ]
+```
+
+Where will the dot be?
+
+
+1. Visible on the screen
+1. To the left of the screen (invisible)
+1. To the right of the screen (invisible) <!-- .element: class="fragment grow" -->
+
+
+<!-- slide vertical -->
+
+What does it mean to format the code?
+
+
+1. To make it work when previously it was broken.
+1. To make it look good. <!-- .element: class="fragment grow" -->
+1. To write it from scratch.
+1. To take code created by somebody else and adapt it to our needs.
+
+
+<!-- slide vertical -->
+
+A way of describing a position of a point on flat a surface is called:
+
+1. the origin
+1. geography
+1. SVG
+1. cartesian coordinates system <!-- .element: class="fragment grow" -->
+1. X - Y system
+
+
+
+
+
+<!-- slide vertical -->
+
+In SVG, the greater the value of the y coordinate ...
+
+1. ... the lower the point is on the screen. <!-- .element: class="fragment grow" -->
+1. ... the higher the point is on the screen.
+1. ... the bigger the point is.
+
+
+<!-- slide vertical -->
+
+The value of `Svg.Attributes.r` affects:
+
+1. rotation of a circle
+1. roundness of a circle
+1. size of a circle <!-- .element: class="fragment grow" -->
+
+
+<!-- slide vertical -->
+
+The value of `Svg.Attributes.viewBox` controls:
+
+1. How big the screen is.
+1. At what fragment of a surface we are looking. <!-- .element: class="fragment grow" -->
+1. How big the SVG element is.
+1. How big the viewport is.
+
+
+<!-- slide vertical -->
+
+What is the origin?
+
+1. The first copy of a program.
+1. A number representing the size of a dot.
+1. A point in cartesian coordinates system. <!-- .element: class="fragment grow" -->
+
+
+<!-- slide vertical -->
+
+If the viewbox is
+
+```elm
+{ left = -200
+, top = -200
+, width = 300
+, height = 200
+}
+```
+
+then the point at the centre is:
+
+1. `{ x = 0, y = 0 }`
+1. `{ x = -50, y = -100 }` <!-- .element: class="fragment grow" -->
+1. `{ x = 150, y = 100 }`
+
+
+<!-- slide vertical -->
+
+To make sure that a given point is in the middle of the screen, we can:
+
+1. Move the viewport
+1. Move the viewbox <!-- .element: class="fragment grow" -->
+1. Move the screen
+1. Move the point
+
+
+<!-- slide vertical -->
+
+```elm
+main =
+ Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200" ]
+ [ Svg.rect
+ [ Svg.Attributes.x "-10"
+ , Svg.Attributes.y "-10"
+ , Svg.Attributes.width "20"
+ , Svg.Attributes.height "20"
+ ]
+ []
+ ]
+```
+
+Which output will this program produce?
+
+<div style="display: flex">
+ <svg viewbox="-100 -100 200 200" style=" background: white; border: solid 1px black; border-radius: 10px; margin: 30px;" class="fragment grow">
+ <rect x="-10" y="-10" width="20" height="20">
+ </svg>
+
+ <svg viewbox="-100 -100 200 200" style=" background: white; border: solid 1px black; border-radius: 10px; margin: 30px;">
+ <circle r="10">
+ </svg>
+
+ <svg viewbox="-100 -100 200 200" style=" background: white; border: solid 1px black; border-radius: 10px; margin: 30px;">
+ <rect x="-15" y="-10" width="30" height="20">
+ </svg>
+</div>
+
+
+<!-- slide vertical -->
+
+What is this code missing?
+
+```elm
+main =
+ Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200"
+ [ Svg.rect
+ [ x "-10"
+ , y "-10"
+ , width "20"
+ , height "20"
+ ]
+ []
+ ]
+```
+
+1. A closing quotation mark
+1. A closing square bracket <!-- .element: class="fragment grow" -->
+1. An equal sign
+
+
+<!-- slide vertical -->
+
+This fragment of code is broken:
+
+```elm
+Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200" ]
+ [ Svg.circle [ Svg.Attributes.r: "10" ] [] ]
+```
+
+Which one is the correct version?
+
+```elm
+Svg.svg
+ [ Svg.Attributes.viewBox = "-100 -100 200 200" ]
+ [ Svg.circle [ Svg.Attributes.r = "10" ] [] ]
+```
+
+```elm
+Svg.svg
+ [ Svg.Attributes.viewBox: "-100 -100 200 200" ]
+ [ Svg.circle [ Svg.Attributes.r: "10" ] [] ]
+```
+
+```elm { .fragment .grow}
+Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200" ]
+ [ Svg.circle [ Svg.Attributes.r "10" ] [] ]
+```
+
+<!-- slide vertical -->
+
+## You are the best 👑
+
+
+<!-- slide -->
+
+# After day 2
+
+
+<!-- slide vertical -->
+
+> TODO: Flashcards for day 2Merge branch 'flash-cards' into 'master'
Create flash cards for day 1
See merge request software-garden/software-garden.gitlab.io!37
Schedule a timeline for day 1
new file mode 100644
index 0000000..a15227e
--- /dev/null
+++ b/TIMELINE.md
@@ -0,0 +1,120 @@
+# Timeline for the workshop
+
+
+## Day 1
+
+### 00:00
+
+ - 👋 👋🏾 Introduce ourselves
+
+ - Ask to write names on badges
+
+ - 🙋 Who read the material?
+
+ - Organizational stuff:
+
+ 5 minute break after 1h
+
+ 20 minutes after 2h
+
+ - 💬 Any questions?
+
+
+### 00:05
+
+ - Introduce the problem (Ellie)
+
+ - Are there any questions?
+
+### 00:10
+
+ - 💻 Introduce SVG
+
+ Installation
+
+ Import
+
+ Make a dot with radius only
+
+ 🙋 Is everyone having it working
+
+### 00:15
+
+ - 💬 Where is the dot?
+
+ - 💻 SVG Element - let's make it pink
+
+ - 🙋 Do you have it? Is it more or less clear?
+
+### 00:20
+
+ - 💻 Fill the viewport
+
+ Install Elm UI
+
+ Import
+
+ `svg |> html |> layout`
+
+ - 🙋 Do you have it working?
+
+ It's a technicality, so let's not focus too much on that right now.
+
+### 00:30
+
+ - Break (5 minutes)
+
+### 00:40
+
+ - Cartesian coordinates system
+
+ 🙋 Who knows what it is?
+
+ 👨🏫 Explain on the whiteboard ( ⇄ 📏 2d ☩ )
+
+ - Change the code to move the dot ( `cx` `cy` )
+
+ 🕞 Take 3 minutes to play with it
+
+ - 💬 Questions?
+
+
+### 00:50
+
+ - Screen size 💥
+
+ - 👨🏫 The infinite canvas and the viewbox into it
+
+ We know the position of a point and we can set the viewbox any way we like. How can we make it in the middle?
+
+ - 💻 Code
+
+ - 💬 Questions?
+
+### 01:10
+
+ - Color
+
+ 💬 What else is there about our dot?
+
+ 💻 `fill "skyblue"`
+
+ 🙋 Who is done?
+
+ 💬 Questions
+
+### 01:20
+
+ - Break 20 minutes
+
+### 01:40
+
+ - 💬 How did you like it?
+
+ Is there anything we could do better?
+
+ New questions, ideas?
+
+ Stashed questions
+
+ See you tomorrow 👋🏾 👋Merge branch 'timeline' into 'master'
Schedule a timeline for day 1
See merge request software-garden/software-garden.gitlab.io!38
Commits: 1
In CartesianCoordinates do not subscribe to animation frames if springs are at rest
It leads to frequent garbage collection, which hits performance.
Also use translations instead of setting x and y attributes for SVG elements. Otherwise browsers are recalcuating the layout and frame rate drops.
index f45515a..a8f1e95 100644
--- a/src/Examples/CartesianCoordinates.elm
+++ b/src/Examples/CartesianCoordinates.elm
@@ -21,6 +21,7 @@ import Html exposing (Html)
=import Spring exposing (Spring)
=import Svg exposing (..)
=import Svg.Attributes exposing (..)
+import Transformations exposing (Transformation)
=
=
=type alias Config =
@@ -91,7 +92,7 @@ view model =
=
=update : Msg -> Model -> ( Model, Cmd Msg )
=update msg model =
- case Debug.log "CartesianCoordinatesMsg" msg of
+ case msg of
= SetX x ->
= ( { model | x = Spring.setTarget x model.x }
= , Cmd.none
@@ -112,7 +113,11 @@ update msg model =
=
=
=subscriptions model =
- Browser.Events.onAnimationFrameDelta Animate
+ if Spring.atRest model.x && Spring.atRest model.y then
+ Sub.none
+
+ else
+ Browser.Events.onAnimationFrameDelta Animate
=
=
=ui : Config -> Model -> Element Msg
@@ -120,19 +125,21 @@ ui config model =
= let
= graph =
= [ circle
- [ Spring.value model.x
- |> String.fromFloat
- |> cx
- , Spring.value model.y
- |> String.fromFloat
- |> cy
+ [ Transformations.Translate
+ (Spring.value model.x)
+ (Spring.value model.y)
+ |> Transformations.toString
+ |> Svg.Attributes.transform
= , r "2"
= , fill "magenta"
= ]
= []
= , text_
- [ x <| String.fromFloat (Spring.value model.x + 5)
- , y <| String.fromFloat (Spring.value model.y + 5)
+ [ Transformations.Translate
+ (5 + Spring.value model.x)
+ (-5 + Spring.value model.y)
+ |> Transformations.toString
+ |> Svg.Attributes.transform
= , fontSize "6"
= , fontFamily "Source Code Pro, monospace"
= , fill "gray"
Commits: 17
Move AnimatedTree lower in the home page
The reason is that on 13' screens (e.g. popular MacBooks) the AnimatedTree element was the last thing visible on the page without scrolling. This made it look like there is nothing else there. The concern was that some readers will think it's just a title and animation and they will miss the "call to action" elements.
index eb4b093..42287fc 100644
--- a/content/index.txt
+++ b/content/index.txt
@@ -4,39 +4,6 @@
=| Subtitle
= A software development workshop for non-programmers
=
-| AnimatedTree
- | Axiom
- color = olive
- rotation = -90
- age = 8
- growing = True
-
- | Rule
- parent = olive
-
- children =
- | Child
- color = olive
- rotation = 5.0
-
- | Child
- color = maroon
- rotation = 40
-
- | Child
- color = maroon
- rotation = -40
-
- | Rule
- parent = maroon
- children =
- | Child
- color = olive
- rotation = 20
- | Child
- color = olive
- rotation = -20
-
=Hello! We are running a workshop that will give you a glimpse into the way software is created. Our workshop is intended for people with no prior experience in programming and doesn't require any technical knowledge. Everybody is welcome!
=
=| Emphasize
@@ -44,7 +11,43 @@ Hello! We are running a workshop that will give you a glimpse into the way softw
=
= or drop us a note at {Link|fana@software.garden|url= mailto:fana@software.garden}
=
-During this *5 days workshop* (4 hours each day) you will learn to solve problems using a functional programming language. We think {Link|it's important|url=/motivation.html}. Upon completion of the course your name, photo and link to your website (or LinkedIn profile etc) will be posted here. The price for participation is *500 €* (inc. 21% VAT).
+During this *5 days workshop* (4 hours each day) you will learn to solve problems using a functional programming language. We think {Link|it's important|url=/motivation.html}. Together, we will build a program that simulates growth of a tree, like this:
+
+| Window
+ | AnimatedTree
+ | Axiom
+ color = olive
+ rotation = -90
+ age = 8
+ growing = True
+
+ | Rule
+ parent = olive
+
+ children =
+ | Child
+ color = olive
+ rotation = 5.0
+
+ | Child
+ color = maroon
+ rotation = 40
+
+ | Child
+ color = maroon
+ rotation = -40
+
+ | Rule
+ parent = maroon
+ children =
+ | Child
+ color = olive
+ rotation = 20
+ | Child
+ color = olive
+ rotation = -20
+
+Upon completion of the course your name, program that you will have created, photo and link to your website (or LinkedIn profile etc) will be posted here. The price for participation is *500 €* (inc. 21% VAT).
=
=| Coupon
= icon = giftMerge branch 'custom-animated-tree' into 'master'
Make Animated Tree customizable, show it on Home page
See merge request software-garden/software-garden.gitlab.io!30
Make AnimatedTree grow older and zoom in on it
index d13c1f8..c9288ce 100644
--- a/src/Examples/AnimatedTree.elm
+++ b/src/Examples/AnimatedTree.elm
@@ -32,7 +32,7 @@ minAge =
=
=
=maxAge =
- 6
+ 7
=
=
=type alias Model =index 1b26fc6..3de5d72 100644
--- a/src/Examples/Tree.elm
+++ b/src/Examples/Tree.elm
@@ -17,7 +17,8 @@ import Transformations
=
=defaults : Config
=defaults =
- { axiom =
+ { viewBox = "-400 -400 800 800"
+ , axiom =
= { color = "purple"
= , rotation = -90
= , age = 7
@@ -47,7 +48,8 @@ defaults =
=
=
=type alias Config =
- { axiom : Axiom
+ { viewBox : String
+ , axiom : Axiom
= , rules : List Rule
= }
=
@@ -191,7 +193,7 @@ ui config =
= [ Svg.Attributes.height "100%"
= , Svg.Attributes.width "100%"
= , Svg.Attributes.style "background: none"
- , Svg.Attributes.viewBox "-500 -500 1000 1000"
+ , Svg.Attributes.viewBox config.viewBox
= ]
= |> Element.html
=index 70dd2ff..386af8c 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -1133,7 +1133,7 @@ document =
= in
= Mark.block "Tree"
= render
- (Mark.startWith Examples.Tree.Config
+ (Mark.startWith (Examples.Tree.Config "-400 -400 800 800")
= axiom
= rules
= )
@@ -1179,7 +1179,7 @@ document =
= in
= Mark.block "AnimatedTree"
= render
- (Mark.startWith Examples.Tree.Config
+ (Mark.startWith (Examples.Tree.Config "-200 -300 400 400")
= axiom
= rules
= )Make few corrections to day 4
Wrong annotations, a typo and comment about the final tree lushness.
index db3ac4a..8190a9f 100644
--- a/content/day-4.txt
+++ b/content/day-4.txt
@@ -3301,7 +3301,7 @@ Since the size depends on the age, and the age is given for every segment, we ca
= []
=
=
-Next let's pass the age to the {Code|line} function and use it for both the thickness ({Code|strokWidth}) and length ({Code|x2}).
+Next let's pass the age to the {Code|line} function and use it for both the thickness ({Code|strokeWidth}) and length ({Code|x2}).
=
=
=| Editor
@@ -3312,25 +3312,31 @@ Next let's pass the age to the {Code|line} function and use it for both the thic
=
=
= | Fold
- start = 61
- length = 16
+ start = 59
+ length = 15
=
=
= | Highlight
- top = 79
- left = 4
+ top = 75
+ left = 5
= width = 5
= height = 1
=
= | Highlight
- top = 81
- left = 29
+ top = 77
+ left = 39
= width = 20
= height = 1
=
= | Highlight
- top = 58
- left = 18
+ top = 80
+ left = 30
+ width = 28
+ height = 1
+
+ | Highlight
+ top = 56
+ left = 19
= width = 5
= height = 1
=
@@ -3591,6 +3597,8 @@ Now the tree should look correctly, like this:
= color = red
= rotation = 50
=
+It doesn't look as lush as the one above. That's because the rules and age are different. I will leave it to you to play with the rules. Make your tree beautiful - tomorrow we will make it grow!
+
=| Emphasize
= Congratulations!
=Fix typo on day 4.
index 8190a9f..6b632c1 100644
--- a/content/day-4.txt
+++ b/content/day-4.txt
@@ -1263,7 +1263,7 @@ So now each segment has two sub segments: red and blue. The red one is rotated 1
= []
=
=
-We have added third parameter to the {Code|segment} function on line 35 (before the changes it was 64). The parameter is named {Code|children} and we use it in place of the list on line 46 (previously 75). The list itself was moved to line 11, where it is passed as a value for the parameter of the first segment. This move made all the lines shift down. All other segments will also need to get value for {Code|children} parameter, so let's just give each of them an empty list - meaning they have 0 children.
+We have added third parameter to the {Code|segment} function on line 34 (before the changes it was 64). The parameter is named {Code|children} and we use it in place of the list on line 45 (previously 75). The list itself was moved to line 11, where it is passed as a value for the parameter of the first segment. This move made all the lines shift down. All other segments will also need to get value for {Code|children} parameter, so let's just give each of them an empty list - meaning they have 0 children.
=
=Once all changes are applied, click {Key|COMPILE} and see that only first, skyblue segment has child segments.
=
@@ -1611,7 +1611,7 @@ A segment within a segment! Consider that every segment can have child segments.
=
= | Fold
= start = 30
- length = 60
+ length = 61
=
= | Code
= module Main exposing (main)Merge branch 'day-4-corrections' into 'master'
Make few corrections to day 4
See merge request software-garden/software-garden.gitlab.io!31
Merge branch 'custom-animated-tree' into 'master'
Make AnimatedTree grow older and zoom in on it
See merge request software-garden/software-garden.gitlab.io!32
Implement a Spring module for animated values
Use it to animate model.scroll - a value driving progress bar. The performance is better, esp. on mobile devices.
With this improvement the biggest performance bottleneck is layout calculation of the progress bar. Perhaps it should be done with a combination of gradient and css transformations, or with SVG.
The cute squid on a spring example is designed by Fana.
Co-Authored-By: Fana jewiet@outlook.com
index 64c0692..a5f4ae6 100644
--- a/elm.json
+++ b/elm.json
@@ -13,6 +13,7 @@
= "elm/json": "1.1.2",
= "elm/parser": "1.1.0",
= "elm/svg": "1.0.1",
+ "elm/time": "1.0.0",
= "elm/url": "1.0.0",
= "elm-community/basics-extra": "4.0.0",
= "elm-community/list-extra": "8.1.0",
@@ -29,7 +30,6 @@
= "indirect": {
= "elm/bytes": "1.0.7",
= "elm/file": "1.0.1",
- "elm/time": "1.0.0",
= "elm/virtual-dom": "1.0.2",
= "ianmackenzie/elm-float-extra": "1.0.1",
= "ianmackenzie/elm-interval": "1.0.1",
@@ -40,4 +40,4 @@
= "direct": {},
= "indirect": {}
= }
-}
+}
\ No newline at end of filenew file mode 100644
index 0000000..690d5fb
--- /dev/null
+++ b/src/Examples/Spring.elm
@@ -0,0 +1,139 @@
+module Examples.Spring exposing
+ ( Model
+ , Msg
+ , init
+ , main
+ , subscriptions
+ , ui
+ , update
+ )
+
+import Browser
+import Browser.Events
+import Element exposing (Element)
+import Element.Background as Background
+import Element.Events as Events
+import Element.Font as Font
+import Element.Input as Input
+import Examples.Tree
+import FeatherIcons
+import Html exposing (Html)
+import Html.Events
+import Json.Decode as Decode exposing (Decoder)
+import Spring exposing (Spring)
+
+
+main : Program Flags Model Msg
+main =
+ Browser.element
+ { init = init
+ , view = view
+ , update = update
+ , subscriptions = subscriptions
+ }
+
+
+type alias Model =
+ { x : Spring
+ , y : Spring
+ }
+
+
+type alias Flags =
+ ()
+
+
+init : Flags -> ( Model, Cmd msg )
+init flags =
+ let
+ strength =
+ 20
+
+ dampness =
+ 20
+ in
+ ( { x =
+ Spring.create strength dampness
+ |> Spring.setTarget 200
+ , y =
+ Spring.create strength dampness
+ |> Spring.setTarget 200
+ }
+ , Cmd.none
+ )
+
+
+ui : Model -> Element Msg
+ui model =
+ Element.el
+ [ Element.width (Element.px 20)
+ , Element.height (Element.px 20)
+ , Font.size 30
+
+ -- , Background.color (Element.rgb 1 0 0)
+ , model.x
+ |> Spring.value
+ |> Element.moveRight
+ , model.y
+ |> Spring.value
+ |> Element.moveDown
+ ]
+ (Element.text "\u{1F991}")
+
+
+type Msg
+ = Animate Float
+ | Mouse Float Float
+
+
+view : Model -> Html Msg
+view model =
+ model
+ |> ui
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ , mouseDecoder
+ |> Html.Events.on "click"
+ |> Element.htmlAttribute
+ ]
+
+
+update : Msg -> Model -> ( Model, Cmd Msg )
+update msg model =
+ case msg of
+ Animate delta ->
+ ( { model
+ | x = Spring.animate delta model.x
+ , y = Spring.animate delta model.y
+ }
+ , Cmd.none
+ )
+
+ Mouse x y ->
+ ( { model
+ | x = Spring.setTarget x model.x
+ , y = Spring.setTarget y model.y
+ }
+ , Cmd.none
+ )
+
+
+mouseDecoder : Decoder Msg
+mouseDecoder =
+ Decode.map2 Mouse
+ (Decode.field "clientX" Decode.float)
+ (Decode.field "clientY" Decode.float)
+ |> Decode.map (Debug.log "Msg")
+
+
+subscriptions : Model -> Sub Msg
+subscriptions model =
+ [ if Spring.atRest model.x && Spring.atRest model.y then
+ Sub.none
+
+ else
+ Browser.Events.onAnimationFrameDelta Animate
+ , Browser.Events.onMouseMove mouseDecoder
+ ]
+ |> Sub.batchindex 386af8c..eb30eb9 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -49,8 +49,10 @@ import Parser
=import Parser.Advanced
=import Result.Extra as Result
=import Routes exposing (Route)
+import Spring exposing (Spring)
=import Svg.Attributes
=import Task
+import Time
=import Transformations exposing (Transformation)
=import Url exposing (Url)
=
@@ -77,7 +79,7 @@ type alias Model =
= , key : Navigation.Key
= , content : Content
= , examples : Examples.Model
- , scroll : Float
+ , scroll : Spring
= , viewport : { width : Int, height : Int }
= }
=
@@ -91,6 +93,7 @@ type Msg
= | ExamplesMsg Examples.Msg
= | ContainerMeasured (Result Dom.Error Dom.Viewport)
= | ViewportMeasured { width : Int, height : Int }
+ | Tick Time.Posix
=
=
=type Content
@@ -135,7 +138,7 @@ init flags url key =
= , key = key
= , content = content
= , examples = examplesModel
- , scroll = 0
+ , scroll = Spring.create 20 20
= , viewport =
= { width = 800, height = 600 }
= }
@@ -393,7 +396,10 @@ update msg model =
= ( { model
= | location = url
= , content = Loading
- , scroll = 0
+ , scroll =
+ model.scroll
+ |> Spring.setTarget 0
+ |> Spring.jumpTo 0
= }
= , Cmd.batch
= [ url
@@ -432,9 +438,16 @@ update msg model =
= ContainerMeasured (Ok { viewport, scene }) ->
= let
= scroll =
- viewport.y / (scene.height - viewport.height)
+ if (scene.height - viewport.height) == 0 then
+ 0
+
+ else
+ viewport.y / (scene.height - viewport.height)
= in
- ( { model | scroll = scroll }
+ ( { model
+ | scroll =
+ Spring.setTarget scroll model.scroll
+ }
= , Cmd.none
= )
=
@@ -449,6 +462,15 @@ update msg model =
= )
=
= Animate delta ->
+ ( { model
+ | scroll =
+ model.scroll
+ |> Spring.animate delta
+ }
+ , Cmd.none
+ )
+
+ Tick time ->
= ( model
= , Dom.getViewport
= |> Task.attempt ContainerMeasured
@@ -465,7 +487,12 @@ subscriptions model =
= (\width height ->
= ViewportMeasured { width = width, height = height }
= )
- , Browser.Events.onAnimationFrameDelta Animate
+ , if Spring.atRest model.scroll then
+ Sub.none
+
+ else
+ Browser.Events.onAnimationFrameDelta Animate
+ , Time.every 250 Tick
= ]
=
=
@@ -717,7 +744,7 @@ contentNavigationBar { location, scroll, viewport } =
= , size = 1
= , blur = 3
= , color =
- if scroll == 0 then
+ if Spring.target scroll == 0 then
= Element.rgba 0.8 0.8 0.8 0
=
= else
@@ -726,12 +753,11 @@ contentNavigationBar { location, scroll, viewport } =
= , Element.htmlAttribute (Html.Attributes.id "navigation-bar")
= ]
= [ linksRow
- , progressBar
- (List.length links)
- -- index of current route
- past
- -- scroll position (0 - 1)
- scroll
+ , scroll
+ |> Spring.value
+ |> progressBar
+ (List.length links)
+ past
= ]
=
=new file mode 100644
index 0000000..0b7bdc3
--- /dev/null
+++ b/src/Spring.elm
@@ -0,0 +1,248 @@
+module Spring exposing
+ ( Spring
+ , animate
+ , atRest
+ , create
+ , jumpTo
+ , setTarget
+ , target
+ , value
+ )
+
+{-| A rough model of a mass attached to a spring, as described by [Hooke's law](https://en.wikipedia.org/wiki/Hooke's_law). Good for making smooth and organic looking animations or modeling oscilating values (e.g. emotions). High physical accuracy is not a priority - performance and API is more important.
+-}
+
+
+{-| A model of mass attached to a spring. The spring is anchored to a target. The mass is constant (1).
+
+Value represents the current position of the mass. It is re-calculated (together with velocity) by animate function.
+
+Strength regulates how strongly the spring pulls toward target. It is also called the stiffness but I find the former term more intuitive.
+
+Dampness is how resistant the spring is to change in it's stretch (both stretching out and contracting in). If dumpness is low relative to strength, then the animation will end in long period of vibration around the target value - in other words lowering dumpness will increase wobbliness. Setting dumpness to 0 will result in something like a sine wave oscilator (but it's not advise to depend on it's accuracy).
+
+Target is the value toward which the mass is pulled. Typically the spring will start in an equilibrium position (i.e. value == target) and later on (due to an event) the target will be changed and the value will follow according to the strength and dampness of the spring.
+
+Value is where the mass is. It can be extracted from the spring using `value` function and set (with `setValue` function - rarely useful).
+
+Velocity is an internal property that cannot be directly modified or read.
+
+Let's say we are creating a program that animates the position of an element toward last click position.
+
+ type alias Model =
+ { x : Spring
+ , y : Spring
+ }
+
+-}
+type Spring
+ = Spring
+ { strength : Float
+ , dampness : Float
+ , target : Float
+ , value : Float
+ , velocity : Float
+ }
+
+
+{-| Create a spring in an equilibrium state.
+
+Let's say we are creating a program that animates the position of an element toward last click position.
+
+ type alias Model =
+ { x : Spring
+ , y : Spring
+ }
+
+ init : Flags -> ( Model, Cmd msg )
+ init flags =
+ ( { x = Spring.create 20 20
+ , y = Spring.create 20 20
+ }
+ , Cmd.none
+ )
+
+-}
+create : Float -> Float -> Spring
+create strength dampness =
+ Spring
+ { strength = strength
+ , dampness = dampness
+ , target = 0
+ , value = 0
+ , velocity = 0
+ }
+
+
+{-| Set a new target (relaxed value) for the spring.
+
+The current value and it's velocity will remain the same. Typically you would set a target in response to an event, e.g.:
+
+ update : Msg -> Model -> ( Model, Cmd Msg )
+ update msg model =
+ case msg of
+ Click x y ->
+ ( { model
+ | x = Spring.setTarget x model.x
+ , y = Spring.setTarget y model.y
+ }
+ , Cmd.none
+ )
+
+-}
+setTarget : Float -> Spring -> Spring
+setTarget target_ (Spring spring) =
+ Spring
+ { spring
+ | target =
+ target_
+ }
+
+
+{-| Update the spring
+
+Typically you would do it in response to an animation frame message, like this:
+
+ subscriptions : Model -> Sub Msg
+ subscriptions model =
+ Browser.Events.onAnimationFrameDelta Animate
+
+ update : Msg -> Model -> ( Model, Cmd Msg )
+ update msg model =
+ case msg of
+ Animate delta ->
+ ( { model
+ | x = Spring.animate delta model.x
+ , y = Spring.animate delta model.y
+ }
+ , Cmd.none
+ )
+
+-}
+animate : Float -> Spring -> Spring
+animate delta ((Spring spring) as this) =
+ if atRest this then
+ {- If it's in equilibrium, then let's just skip the whole calculation. Be lazy. -}
+ Spring spring
+
+ else
+ let
+ time =
+ {- Low sampling rates often result in nasty errors.
+
+ With this cap on delta, if the frame rate is lower than 30fps the animation will slow down and good sampling rate will be preserved.
+ -}
+ min delta 32 / 1000
+
+ stretch =
+ spring.target - spring.value
+
+ force =
+ stretch * spring.strength
+
+ dampening =
+ spring.velocity * spring.dampness
+
+ acceleration =
+ time * (force - dampening)
+ in
+ if
+ (abs (spring.value - spring.target) < 0.001)
+ && (abs spring.velocity < 0.001)
+ then
+ {- In reality the spring never stops vibrating, but at some point the vibration is lost in the background noise (uncertainity principle). In our case it's also a wasted computation. Let's just say that it is at rest already. -} {- Snap to ideal equilibrium -}
+ Spring
+ { spring
+ | value = spring.target
+ , velocity = 0
+ }
+
+ else
+ Spring
+ { spring
+ | value =
+ spring.value + (spring.velocity * time)
+ , velocity =
+ spring.velocity + acceleration
+ }
+
+
+{-| Measure the value of the spring.
+
+Typically you want to access it in the view function, like this:
+
+ Element.el
+ [ Element.width (Element.px 20)
+ , Element.height (Element.px 20)
+ , Font.size 30
+ , model.x
+ |> Spring.value
+ |> Element.moveRight
+ , model.y
+ |> Spring.value
+ |> Element.moveDown
+ ]
+ (Element.text "\u{1F991}")
+
+Above we use Elm UI Elements and Attributes, but it's not difficult to implement same behavior using CSS transformations. Spring value is just a `Float`.
+
+-}
+value : Spring -> Float
+value (Spring spring) =
+ spring.value
+
+
+{-| Get current target of a spring
+
+Can be useful to see where the spring is going. Maybe you want to display something there?
+
+-}
+target : Spring -> Float
+target (Spring spring) =
+ spring.target
+
+
+{-| Check if the spring is at rest
+
+It indicates that no animation is running. Maybe you want to unsubscribe from animation frames? Or remove an element?
+
+ subscriptions : Model -> Sub Msg
+ subscriptions model =
+ if Spring.atRest model.x && Spring.atRest model.y then
+ Sub.none
+
+ else
+ Browser.Events.onAnimationFrameDelta Animate
+
+-}
+atRest : Spring -> Bool
+atRest (Spring spring) =
+ spring.value == spring.target && spring.velocity == 0.0
+
+
+{-| Forcefully set the value and interrupt the animation.
+
+It is useful when you set the spring for the first time (e.g. in init function) or you want to reset the animation.
+
+ init : Flags -> ( Model, Cmd msg )
+ init flags =
+ ( { x =
+ Spring.create 20 20
+ |> Spring.setTarget 200
+ |> Spring.jumpTo 200
+ , y =
+ Spring.create 20 20
+ |> Spring.setTarget 200
+ |> Spring.jumpTo 200
+ }
+ , Cmd.none
+ )
+
+-}
+jumpTo : Float -> Spring -> Spring
+jumpTo value_ (Spring spring) =
+ Spring
+ { spring
+ | value = value_
+ , velocity = 0.0
+ }Prevent scaling the viewport on mobile devices
Use real device width without 75% scaling.
index c0abf0b..5502e69 100644
--- a/container.html
+++ b/container.html
@@ -2,6 +2,7 @@
=<html lang="en" dir="ltr">
= <head>
= <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
= <title>Software Garden</title>
= <style>
= @import url('https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Source+Code+Pro:300,400,700');Merge branch 'fix-mobile-scaling' into 'master'
Prevent scaling the viewport on mobile devices
See merge request software-garden/software-garden.gitlab.io!33
Set mobile scale to 0.75
It was set to 1 by mistake.
index 5502e69..6e062a5 100644
--- a/container.html
+++ b/container.html
@@ -2,7 +2,7 @@
=<html lang="en" dir="ltr">
= <head>
= <meta charset="utf-8">
- <meta name="viewport" content="width=device-width, initial-scale=1">
+ <meta name="viewport" content="width=device-width, initial-scale=0.75">
= <title>Software Garden</title>
= <style>
= @import url('https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Source+Code+Pro:300,400,700');Merge branch 'fix-mobile-scaling' into 'master'
Set mobile scale to 0.75
See merge request software-garden/software-garden.gitlab.io!34
Make the text in Coupon element wrap
Otherwise it doesn't fit on small screens.
index 075de33..2c9e0ce 100644
--- a/src/Mark/Custom.elm
+++ b/src/Mark/Custom.elm
@@ -126,6 +126,8 @@ coupon =
= |> Maybe.withDefault Element.none
= , config.text
= |> Element.text
+ |> List.singleton
+ |> Element.paragraph [ Font.center ]
= ]
= |> Element.row
= [ Element.centerXMerge branch 'fix-mobile-scaling'
Do not build examples in develop screipt
There is no need for that. We can just run Elm Reactor side by side with it.
Also remove the debug flag from develop script. It is never realy useful and with animation frames subscription it significantly affects performance.
index d3e4036..b671dc2 100755
--- a/scripts/develop
+++ b/scripts/develop
@@ -2,17 +2,11 @@
=
=set -euo pipefail
=
-# Build examples
-for example in src/Examples/*.elm
-do
- echo "Compiling ${example} to public/${example}.html"
- npx elm make --output "public/${example}.html" "${example}"
-done
-
=npx elm-live src/Main.elm \
= --pushstate \
= --start-page container.html \
= --port 8001 \
= -- \
= --output built/index.js \
- --debug
+ # --optimize
+ # --debugUse springs to improve performance of CartesianCoordinates system
index 2492c7d..b8781f3 100644
--- a/src/Examples.elm
+++ b/src/Examples.elm
@@ -39,18 +39,24 @@ type Msg
=init : ( Model, Cmd Msg )
=init =
= let
- ( animatedTreeModel, animatedTreeMsg ) =
+ ( animatedTreeModel, animatedTreeCmd ) =
= Examples.AnimatedTree.init ()
+
+ ( cartesianCoordinatesModel, cartesianCoordinatesCmd ) =
+ Examples.CartesianCoordinates.init ()
= in
= ( { counter = Examples.Counter.init
= , transformations = Examples.Transformations.init
= , nestedTransformations = Examples.NestedTransformations.init
- , cartesianCoordinates = Examples.CartesianCoordinates.init
+ , cartesianCoordinates = cartesianCoordinatesModel
= , polarCoordinates = Examples.PolarCoordinates.init
= , viewBox = Examples.ViewBox.init
= , animatedTree = animatedTreeModel
= }
- , animatedTreeMsg
+ , [ animatedTreeCmd |> Cmd.map AnimatedTreeMsg
+ , cartesianCoordinatesCmd |> Cmd.map CartesianCoordinatesMsg
+ ]
+ |> Cmd.batch
= )
=
=
@@ -79,11 +85,16 @@ update msg model =
= )
=
= CartesianCoordinatesMsg m ->
+ let
+ ( cartesianCoordinatesModel, cartesianCoordinatesCmd ) =
+ Examples.CartesianCoordinates.update m model.cartesianCoordinates
+ in
= ( { model
= | cartesianCoordinates =
- Examples.CartesianCoordinates.update m model.cartesianCoordinates
+ cartesianCoordinatesModel
= }
- , Cmd.none
+ , cartesianCoordinatesCmd
+ |> Cmd.map CartesianCoordinatesMsg
= )
=
= PolarCoordinatesMsg m ->
@@ -109,6 +120,11 @@ update msg model =
=
=subscriptions : Model -> Sub Msg
=subscriptions model =
- model.animatedTree
+ [ model.animatedTree
= |> Examples.AnimatedTree.subscriptions
= |> Sub.map AnimatedTreeMsg
+ , model.cartesianCoordinates
+ |> Examples.CartesianCoordinates.subscriptions
+ |> Sub.map CartesianCoordinatesMsg
+ ]
+ |> Sub.batchindex b8b1723..f45515a 100644
--- a/src/Examples/CartesianCoordinates.elm
+++ b/src/Examples/CartesianCoordinates.elm
@@ -4,18 +4,21 @@ module Examples.CartesianCoordinates exposing
= , Msg
= , init
= , main
+ , subscriptions
= , ui
= , update
= , view
= )
=
=import Browser
+import Browser.Events
=import CartesianPlane exposing (graph)
=import Element exposing (Element)
=import Element.Background as Background
=import Element.Border as Border
=import Element.Input as Input
=import Html exposing (Html)
+import Spring exposing (Spring)
=import Svg exposing (..)
=import Svg.Attributes exposing (..)
=
@@ -39,27 +42,37 @@ defaults =
=
=main : Program () Model Msg
=main =
- Browser.sandbox
+ Browser.element
= { init = init
= , view = view
= , update = update
+ , subscriptions = subscriptions
= }
=
=
=type alias Model =
- { x : Float
- , y : Float
+ { x : Spring
+ , y : Spring
= }
=
=
=type Msg
= = SetX Float
= | SetY Float
+ | Animate Float
=
=
-init : Model
-init =
- { x = 0, y = 0 }
+type alias Flags =
+ ()
+
+
+init : Flags -> ( Model, Cmd Msg )
+init () =
+ ( { x = Spring.create 20 20
+ , y = Spring.create 20 20
+ }
+ , Cmd.none
+ )
=
=
=view : Model -> Html Msg
@@ -76,14 +89,30 @@ view model =
= ]
=
=
-update : Msg -> Model -> Model
+update : Msg -> Model -> ( Model, Cmd Msg )
=update msg model =
- case msg of
+ case Debug.log "CartesianCoordinatesMsg" msg of
= SetX x ->
- { model | x = x }
+ ( { model | x = Spring.setTarget x model.x }
+ , Cmd.none
+ )
=
= SetY y ->
- { model | y = y }
+ ( { model | y = Spring.setTarget y model.y }
+ , Cmd.none
+ )
+
+ Animate delta ->
+ ( { model
+ | x = Spring.jumpTo (Spring.target model.x) model.x
+ , y = Spring.jumpTo (Spring.target model.y) model.y
+ }
+ , Cmd.none
+ )
+
+
+subscriptions model =
+ Browser.Events.onAnimationFrameDelta Animate
=
=
=ui : Config -> Model -> Element Msg
@@ -91,15 +120,19 @@ ui config model =
= let
= graph =
= [ circle
- [ cx <| String.fromFloat model.x
- , cy <| String.fromFloat model.y
+ [ Spring.value model.x
+ |> String.fromFloat
+ |> cx
+ , Spring.value model.y
+ |> String.fromFloat
+ |> cy
= , r "2"
= , fill "magenta"
= ]
= []
= , text_
- [ x <| String.fromFloat (model.x + 5)
- , y <| String.fromFloat (model.y + 5)
+ [ x <| String.fromFloat (Spring.value model.x + 5)
+ , y <| String.fromFloat (Spring.value model.y + 5)
= , fontSize "6"
= , fontFamily "Source Code Pro, monospace"
= , fill "gray"
@@ -116,9 +149,9 @@ ui config model =
=
= coordinates =
= "{ x = "
- ++ String.fromFloat model.x
+ ++ (model.x |> Spring.target |> String.fromFloat)
= ++ ", y = "
- ++ String.fromFloat model.y
+ ++ (model.y |> Spring.target |> String.fromFloat)
= ++ "}"
= in
= Element.column
@@ -143,10 +176,10 @@ ui config model =
= { onChange = SetX
= , label =
= Input.labelBelow [ Element.centerX ] <|
- Element.text ("x = " ++ String.fromFloat model.x)
+ Element.text ("x = " ++ String.fromFloat (Spring.target model.y))
= , min = config.minX
= , max = config.maxX
- , value = model.x
+ , value = Spring.target model.x
= , thumb = Input.defaultThumb
= , step = Just 0.01
= }
@@ -165,10 +198,10 @@ ui config model =
= { onChange = SetY
= , label =
= Input.labelBelow [ Element.centerX ] <|
- Element.text ("y = " ++ String.fromFloat model.y)
+ Element.text ("y = " ++ String.fromFloat (Spring.target model.y))
= , min = config.minY
= , max = config.maxY
- , value = model.y
+ , value = Spring.target model.y
= , thumb = Input.defaultThumb
= , step = Just 0.01
= }Merge branch 'master' into springs
Commits: 4
Create a fancy coupon widget and use it for Utrecht test run on
We want the link to be more prominent.
index a010fa5..67672db 100644
--- a/content/index.txt
+++ b/content/index.txt
@@ -15,8 +15,11 @@ Hello! We are running a workshop that will give you a glimpse into the way softw
=
=During this *5 days workshop* (4 hours each day) you will learn to solve problems using a functional programming language. We think {Link|it's important|url=/motivation.html}. Upon completion of the course your name, photo and link to your website (or LinkedIn profile etc) will be posted here. The price for participation is *500 €* (inc. 21% VAT).
=
-| Note
- {Icon|name=gift} {Link|Special offer for University of Utrecht students|url=/test-run.html}.
+| Coupon
+ icon = gift
+ primary = True
+ text = Special offer for University of Utrecht students
+ href = /test-run.html
=
=Below is the material through which we will be going during the workshop. Only the first section is a required read before you come.
=index 41c32c3..2eac9c4 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -827,6 +827,7 @@ document =
= [ Mark.Custom.editor
= , Mark.Custom.elmRepl
= , Mark.Custom.note
+ , Mark.Custom.coupon
= ]
=
= special =index 88c3af4..1e547c3 100644
--- a/src/Mark/Custom.elm
+++ b/src/Mark/Custom.elm
@@ -1,5 +1,6 @@
=module Mark.Custom exposing
= ( colors
+ , coupon
= , editor
= , elmRepl
= , emphasize
@@ -103,6 +104,100 @@ link =
= (Mark.field "label" paragraph)
=
=
+coupon =
+ let
+ render :
+ { icon : String
+ , text : String
+ , primary : Bool
+ , href : String
+ }
+ -> model
+ -> Element msg
+ render config model =
+ { url = config.href
+ , label =
+ [ FeatherIcons.icons
+ |> Dict.get config.icon
+ |> Maybe.map (FeatherIcons.toHtml [])
+ |> Maybe.map Element.html
+ |> Maybe.map (Element.el [ Element.paddingXY 10 0 ])
+ |> Maybe.withDefault Element.none
+ , config.text
+ |> Element.text
+ ]
+ |> Element.row
+ [ Element.centerX
+ ]
+ }
+ |> Element.link
+ [ Element.paddingXY 0 20
+ , Font.size 24
+ , Font.bold
+ , Background.color colors.white
+ , Element.width Element.fill
+
+ -- , Background.color colors.charcoal
+ , Font.color
+ (if config.primary then
+ colors.green
+
+ else
+ colors.charcoal
+ )
+ , Border.rounded 9
+ , Border.width 1
+ , Border.color
+ (if config.primary then
+ colors.green
+
+ else
+ colors.charcoal
+ )
+ , Border.width 1
+ , Border.dashed
+ , Element.mouseOver
+ [ Border.shadow
+ { offset = ( 0, 2 )
+ , size = 0
+ , blur = 3
+ , color = colors.charcoal
+ }
+ , Element.moveDown 5
+ , Element.moveLeft 3
+ , Element.rotate (degrees 1)
+ ]
+ ]
+ |> Element.el
+ [ Background.color colors.gray
+ , Border.innerShadow
+ { offset = ( 0, 2 )
+ , size = 1
+ , blur = 3
+ , color = colors.charcoal
+ }
+ , Border.rounded 10
+ , Border.width 1
+ , Border.color
+ (if config.primary then
+ colors.green
+
+ else
+ colors.charcoal
+ )
+ , Border.width 1
+ , Border.dashed
+ ]
+ in
+ Mark.record4 "Coupon"
+ (\icon_ text_ primary href -> { icon = icon_, text = text_, primary = primary, href = href })
+ (Mark.field "icon" Mark.string)
+ (Mark.field "text" Mark.string)
+ (Mark.field "primary" Mark.bool)
+ (Mark.field "href" Mark.string)
+ |> Mark.map render
+
+
=monospace : Mark.Block (model -> Element msg)
=monospace =
= Mark.Default.monospaceMerge branch 'big-utrecht-button' into 'master'
Create a fancy coupon widget and use it for Utrecht test run
See merge request software-garden/software-garden.gitlab.io!29
Make AnimatedTree markup block customizable
It takes the same configuration as Tree block. The minimum and maximum age are hardcoded (0 - 6).
Also add a Subtitle block.
index 10a10f6..33c7a49 100644
--- a/content/day-5.txt
+++ b/content/day-5.txt
@@ -25,6 +25,38 @@ We have a nice picture of a tree, but ultimately it may have been easier to just
=
=| Window
= | AnimatedTree
+ | Axiom
+ color = olive
+ rotation = -90
+ age = 8
+ growing = True
+
+ | Rule
+ parent = olive
+
+ children =
+ | Child
+ color = olive
+ rotation = 5.0
+
+ | Child
+ color = maroon
+ rotation = 40
+
+ | Child
+ color = maroon
+ rotation = -40
+
+ | Rule
+ parent = maroon
+ children =
+ | Child
+ color = olive
+ rotation = 20
+ | Child
+ color = olive
+ rotation = -20
+
=
=| Header
= The Elm Architectureindex 67672db..7bae3a4 100644
--- a/content/index.txt
+++ b/content/index.txt
@@ -1,11 +1,42 @@
=| Title
= Software Garden
=
-| Emphasize
- ⚘
-
+| Subtitle
= A software development workshop for non-programmers
=
+| AnimatedTree
+ | Axiom
+ color = olive
+ rotation = -90
+ age = 8
+ growing = True
+
+ | Rule
+ parent = olive
+
+ children =
+ | Child
+ color = olive
+ rotation = 5.0
+
+ | Child
+ color = maroon
+ rotation = 40
+
+ | Child
+ color = maroon
+ rotation = -40
+
+ | Rule
+ parent = maroon
+ children =
+ | Child
+ color = olive
+ rotation = 20
+ | Child
+ color = olive
+ rotation = -20
+
=Hello! We are running a workshop that will give you a glimpse into the way software is created. Our workshop is intended for people with no prior experience in programming and doesn't require any technical knowledge. Everybody is welcome!
=
=| Emphasizeindex fe2d90a..d13c1f8 100644
--- a/src/Examples/AnimatedTree.elm
+++ b/src/Examples/AnimatedTree.elm
@@ -27,12 +27,17 @@ main =
= }
=
=
+minAge =
+ 0
+
+
+maxAge =
+ 6
+
+
=type alias Model =
= { age : Float
- , maxAge : Float
- , minAge : Float
= , play : Bool
- , config : Examples.Tree.Config
= }
=
=
@@ -43,27 +48,27 @@ type alias Flags =
=init : Flags -> ( Model, Cmd msg )
=init flags =
= ( { age = 0.0
- , maxAge = 6.0
- , minAge = 0
= , play = False
- , config = Examples.Tree.defaults
= }
= , Cmd.none
= )
=
=
-ui : Model -> Element Msg
-ui model =
+ui : Examples.Tree.Config -> Model -> Element Msg
+ui config model =
= let
= tree =
= { config | axiom = { axiom | age = model.age } }
=
+ axiom =
+ config.axiom
+
= controls =
= Element.row
= [ Element.spacing 12
= ]
= [ rewindButton
- , if model.age == model.maxAge then
+ , if model.age == maxAge then
= resetButton
=
= else if model.play then
@@ -74,12 +79,6 @@ ui model =
= , forwardButton
= ]
=
- config =
- model.config
-
- axiom =
- config.axiom
-
= playButton =
= Input.button []
= { onPress = Just Play
@@ -165,7 +164,7 @@ type Msg
=view : Model -> Html Msg
=view model =
= model
- |> ui
+ |> ui Examples.Tree.defaults
= |> Element.layout
= [ Element.width Element.fill
= , Element.height Element.fill
@@ -190,30 +189,42 @@ update msg model =
= progress =
= min 32 delta / 1000
= in
- ( { model | age = grow progress model }
+ ( { model
+ | age = grow progress model
+ , play = model.age < maxAge
+ }
= , Cmd.none
= )
=
= Reset ->
- ( { model | age = model.minAge }
+ ( { model
+ | age = minAge
+ , play = True
+ }
= , Cmd.none
= )
=
= Rewind ->
- ( { model | age = grow -0.2 model }
+ ( { model
+ | age = grow -0.2 model
+ , play = False
+ }
= , Cmd.none
= )
=
= Forward ->
- ( { model | age = grow 0.2 model }
+ ( { model
+ | age = grow 0.2 model
+ , play = False
+ }
= , Cmd.none
= )
=
=
=grow progress model =
= (model.age + progress)
- |> min model.maxAge
- |> max model.minAge
+ |> min maxAge
+ |> max minAge
=
=
=subscriptions : Model -> Sub Msgindex 2eac9c4..70dd2ff 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -819,6 +819,7 @@ document =
= , Mark.Custom.link
= , Mark.Custom.monospace
= , Mark.Custom.emphasize
+ , Mark.Custom.subtitle
= , Mark.Custom.list
= , Mark.Custom.image
= ]
@@ -1140,19 +1141,49 @@ document =
= animatedTree : Mark.Block (Examples.Model -> Element Msg)
= animatedTree =
= let
- render : Examples.Model -> Element Msg
- render model =
- Examples.AnimatedTree.ui model.animatedTree
+ render : Examples.Tree.Config -> Examples.Model -> Element Msg
+ render config model =
+ model.animatedTree
+ |> Examples.AnimatedTree.ui config
= |> Element.map Examples.AnimatedTreeMsg
= |> Element.map ExamplesMsg
+
+ axiom : Mark.Block Examples.Tree.Axiom
+ axiom =
+ Mark.record4 "Axiom"
+ Examples.Tree.Axiom
+ (Mark.field "color" Mark.string)
+ (Mark.field "rotation" Mark.float)
+ (Mark.field "age" Mark.float)
+ (Mark.field "growing" Mark.bool)
+
+ rules : Mark.Block (List Examples.Tree.Rule)
+ rules =
+ Mark.manyOf [ rule ]
+
+ rule =
+ Mark.record2 "Rule"
+ Tuple.pair
+ (Mark.field "parent" Mark.string)
+ (Mark.field "children" (Mark.manyOf [ child ]))
+
+ parent =
+ Mark.block "Parent" identity Mark.string
+
+ -- Mark.stub "Parent" "green"
+ child =
+ Mark.record2 "Child"
+ Examples.Tree.Segment
+ (Mark.field "color" Mark.string)
+ (Mark.field "rotation" Mark.float)
= in
- Mark.stub "AnimatedTree"
+ Mark.block "AnimatedTree"
= render
+ (Mark.startWith Examples.Tree.Config
+ axiom
+ rules
+ )
=
- -- Mark.Block Examples.Tree.Config
- -- result = Examples.Tree.Config
- -- start = Axiom
- -- rest = List Rule
= viewBox : Mark.Block (Examples.Model -> Element Msg)
= viewBox =
= letindex 1e547c3..e17b54e 100644
--- a/src/Mark/Custom.elm
+++ b/src/Mark/Custom.elm
@@ -13,6 +13,7 @@ module Mark.Custom exposing
= , note
= , paragraph
= , row
+ , subtitle
= , text
= , title
= , window
@@ -484,6 +485,26 @@ emphasize =
= (Mark.manyOf [ text ])
=
=
+subtitle =
+ let
+ render :
+ (model -> List (Element msg))
+ -> model
+ -> Element msg
+ render content model =
+ content model
+ |> Element.paragraph
+ [ Font.center
+ , Element.paddingXY 0 10
+ , Element.width Element.fill
+ , Element.spacing 10
+ ]
+ in
+ Mark.block "Subtitle"
+ render
+ text
+
+
=image : Mark.Block (model -> Element msg)
=image =
= Mark.Default.imageGive id attribute to each subtitle, so it can be linked directly
index 7bae3a4..eb4b093 100644
--- a/content/index.txt
+++ b/content/index.txt
@@ -80,7 +80,7 @@ Below is the material through which we will be going during the workshop. Only t
= label = Day 5 - Let's Make the Tree Grow
=
=
-| Header
+| Subtitle
= About us
=
=*Ted*: I'm going to be your teacher during the workshop. I've been working as a software developer for the best part of the last six years. Before that I've been a lawyer.index e17b54e..075de33 100644
--- a/src/Mark/Custom.elm
+++ b/src/Mark/Custom.elm
@@ -488,21 +488,29 @@ emphasize =
=subtitle =
= let
= render :
- (model -> List (Element msg))
+ String
= -> model
= -> Element msg
= render content model =
- content model
+ content
+ |> Element.text
+ |> List.singleton
= |> Element.paragraph
= [ Font.center
= , Element.paddingXY 0 10
= , Element.width Element.fill
= , Element.spacing 10
+ , content
+ |> String.toLower
+ |> String.words
+ |> String.join "-"
+ |> Html.Attributes.id
+ |> Element.htmlAttribute
= ]
= in
= Mark.block "Subtitle"
= render
- text
+ Mark.string
=
=
=image : Mark.Block (model -> Element msg)
Commits: 6
Parse /index.html URL into Home route
Otherwise it's treated as Content /index. The most visible effect is
that the navigation bar is displayed. On Home route it should not be.
index 8dbac32..a1a6bbf 100644
--- a/src/Routes.elm
+++ b/src/Routes.elm
@@ -20,6 +20,7 @@ parser : Parser (Route -> b) b
=parser =
= Parser.oneOf
= [ Parser.map Home Parser.top
+ , Parser.map Home (Parser.s "index.html")
= , Parser.map Motivation (Parser.s "motivation.html")
= , Parser.map TestRun (Parser.s "test-run.html")
= , Parser.custom "Content"Change port of the capture server to 8010
To avoid clashes with Elm Reactor.
index 1c73baa..ea95984 100644
--- a/src/capture.coffee
+++ b/src/capture.coffee
@@ -4,6 +4,8 @@ FS = require "fs"
=glob = require "glob"
=Path = require "path"
=
+port = 8010
+
=match = (pattern, options) =>
= new Promise (resolve, reject) =>
= glob pattern, options, (error, paths) =>
@@ -17,7 +19,7 @@ do () =>
= app.get '*', (req, res) ->
= res.sendFile "container.html", root: process.cwd ``
=
- server = app.listen 8000
+ server = app.listen port
=
= browser = await puppeteer.launch
= args: [
@@ -32,7 +34,7 @@ do () =>
= { dir, name } = Path.parse match
= base = if dir is "" then name else "#{dir}/#{name}"
=
- url = "http://localhost:8000/#{base}.html"
+ url = "http://localhost:#{port}/#{base}.html"
= path = "built/captured/#{base}.html"
=
= await page.goto url, waitUntil: "networkidle2"Crash the capture process if there is an error on page being captured
Also console log messages from the page to standard output. Some additional logging is commented out for future debugging.
index ea95984..c98f206 100644
--- a/src/capture.coffee
+++ b/src/capture.coffee
@@ -29,6 +29,17 @@ do () =>
=
= page = await browser.newPage ``
=
+ page.on "pageerror", (error) ->
+ console.error error
+ process.exit 1
+
+ # page.on "response", (response) ->
+ # console.log "#{response.status ``} #{response.url ``}"
+
+ page.on "console", (msg) ->
+ console.log "# #{page.url ``}: #{msg.text ``}"
+
+
= for match in await match "*.txt", cwd: "content/"
= await do (match) ->
= { dir, name } = Path.parse match
@@ -37,8 +48,11 @@ do () =>
= url = "http://localhost:#{port}/#{base}.html"
= path = "built/captured/#{base}.html"
=
+ # console.log ""
+ # console.log "<-- Catpuring #{url}"
= await page.goto url, waitUntil: "networkidle2"
=
+
= html =
= (await page.content ``)
= .replace /<p\b/gi, "<div"
@@ -51,6 +65,12 @@ do () =>
= </body>
= """
=
+ # console.log ""
+ # console.log html
+ # console.log ""
+
+ # console.log "--> Writing to #{path}"
+
= await new Promise (resolve, reject) ->
= FS.writeFile path, html, (error) ->
= if error then return reject errorFix #23: flash of Loading view
In capture.coffee the markup is now baked into captured HTML and when the page loads it is passed to the Elm program as flags. If it's correctly parsed, then the program starts in the Loaded state and there is no flash. Also there is no need to make additional request from init!
index b2d458d..c0abf0b 100644
--- a/container.html
+++ b/container.html
@@ -22,7 +22,7 @@
= <!-- <div id="app-container"></div> -->
= <script src="/built/index.js"></script>
= <script>
- Elm.Main.init()
+ Elm.Main.init({ flags: { markup: null } })
= </script>
= </body>
=</html>index e4b5db2..41c32c3 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -68,7 +68,8 @@ main =
=
=
=type alias Flags =
- ()
+ { markup : Maybe String
+ }
=
=
=type alias Model =
@@ -118,19 +119,34 @@ init flags url key =
= let
= ( examplesModel, examplesCmd ) =
= Examples.init
+
+ content =
+ case flags.markup of
+ Nothing ->
+ Loading
+
+ Just markup ->
+ markup
+ |> Mark.parse document
+ |> Result.map Loaded
+ |> Result.extract ParseError
= in
= ( { location = url
= , key = key
- , content = Loading
+ , content = content
= , examples = examplesModel
= , scroll = 0
= , viewport =
= { width = 800, height = 600 }
= }
= , Cmd.batch
- [ url
- |> Routes.parse
- |> loadContent
+ [ if content == Loading then
+ url
+ |> Routes.parse
+ |> loadContent
+
+ else
+ Cmd.none
= , Dom.getViewport
= |> Task.map
= (\{ viewport } ->index c98f206..1149557 100644
--- a/src/capture.coffee
+++ b/src/capture.coffee
@@ -42,6 +42,12 @@ do () =>
=
= for match in await match "*.txt", cwd: "content/"
= await do (match) ->
+ markup = await new Promise (resolve, reject) ->
+ FS.readFile "content/" + match, encoding: "utf-8", (error, content) ->
+ if error then return reject error
+
+ resolve content
+
= { dir, name } = Path.parse match
= base = if dir is "" then name else "#{dir}/#{name}"
=
@@ -60,7 +66,8 @@ do () =>
= .replace "</body>", """
= <script src="/built/index.js"></script>
= <script>
- Elm.Main.init()
+ var flags = { markup : #{ JSON.stringify markup } }
+ Elm.Main.init({ flags: flags })
= </script>
= </body>
= """Enable compiler optimization in build script
index b3c69f2..c8408b6 100755
--- a/scripts/build
+++ b/scripts/build
@@ -9,7 +9,7 @@ mkdir -p built/
=mkdir -p public/
=
=
-npx elm make src/Main.elm --output built/index.js
+npx elm make src/Main.elm --output built/index.js --optimize
=
=
=cp -r content/ public/content/Merge branch '23-there-is-a-flash-of-loading-content-right-after-opening-first-page' into 'master'
Resolve "There is a flash of "Loading content" right after opening first page"
Closes #23
See merge request software-garden/software-garden.gitlab.io!28
Commits: 17
Prevent rewind from going below minAge and forward from exceeding maxAge in Animated Tree.
Also make tree grow faster.
index 42a14cb..fc858b3 100644
--- a/src/Examples/AnimatedTree.elm
+++ b/src/Examples/AnimatedTree.elm
@@ -147,7 +147,7 @@ update msg model =
= Animate delta ->
= let
= progress =
- min 32 delta / 5000
+ min 32 delta / 1000
=
= age =
= (model.age + progress)
@@ -164,12 +164,24 @@ update msg model =
= )
=
= Rewind ->
- ( { model | age = model.age - 1 }
+ let
+ age =
+ (model.age - 1)
+ |> min model.maxAge
+ |> max model.minAge
+ in
+ ( { model | age = age }
= , Cmd.none
= )
=
= Forward ->
- ( { model | age = model.age + 1 }
+ let
+ age =
+ (model.age + 1)
+ |> min model.maxAge
+ |> max model.minAge
+ in
+ ( { model | age = age }
= , Cmd.none
= )
=Refactoring the code in rewind, forward and animate in animated tree.
index fc858b3..9ac54bf 100644
--- a/src/Examples/AnimatedTree.elm
+++ b/src/Examples/AnimatedTree.elm
@@ -148,13 +148,8 @@ update msg model =
= let
= progress =
= min 32 delta / 1000
-
- age =
- (model.age + progress)
- |> min model.maxAge
- |> max model.minAge
= in
- ( { model | age = age }
+ ( { model | age = grow progress model }
= , Cmd.none
= )
=
@@ -164,28 +159,22 @@ update msg model =
= )
=
= Rewind ->
- let
- age =
- (model.age - 1)
- |> min model.maxAge
- |> max model.minAge
- in
- ( { model | age = age }
+ ( { model | age = grow -1 model }
= , Cmd.none
= )
=
= Forward ->
- let
- age =
- (model.age + 1)
- |> min model.maxAge
- |> max model.minAge
- in
- ( { model | age = age }
+ ( { model | age = grow 1 model }
= , Cmd.none
= )
=
=
+grow progress model =
+ (model.age + progress)
+ |> min model.maxAge
+ |> max model.minAge
+
+
=subscriptions : Model -> Sub Msg
=subscriptions model =
= if model.play thenAdd spacing to the buttons in animated tree.
index 9ac54bf..8fb8ffd 100644
--- a/src/Examples/AnimatedTree.elm
+++ b/src/Examples/AnimatedTree.elm
@@ -58,7 +58,9 @@ ui model =
= { config | axiom = { axiom | age = model.age } }
=
= controls =
- Element.row []
+ Element.row
+ [ Element.spacing 12
+ ]
= [ if model.play then
= pauseButton
=Implement animated tree block in mark up.
index 12dfff2..aeb3dd8 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -22,6 +22,7 @@ import Element.Font as Font
=import Element.Input as Input
=import Element.Lazy
=import Examples
+import Examples.AnimatedTree
=import Examples.CartesianCoordinates
=import Examples.Circle
=import Examples.Counter
@@ -831,6 +832,7 @@ document =
= , rosette
= , spiral
= , tree
+ , animatedTree
= , viewBox
= ]
=
@@ -1118,6 +1120,18 @@ document =
= rules
= )
=
+ animatedTree : Mark.Block (Examples.Model -> Element Msg)
+ animatedTree =
+ let
+ render : Examples.Model -> Element Msg
+ render model =
+ Examples.AnimatedTree.ui model.animatedTree
+ |> Element.map Examples.AnimatedTreeMsg
+ |> Element.map ExamplesMsg
+ in
+ Mark.stub "AnimatedTree"
+ render
+
= -- Mark.Block Examples.Tree.Config
= -- result = Examples.Tree.Config
= -- start = AxiomFix animated tree controls not visible in the window element.
Limit the height of the animated tree to 400px. This is somewhat arbitrary, but works well with the Window element, and currently we are only displaying animated tree inside window.
Also make controls overlay and visible only when paused or mouse hover.
index 8fb8ffd..d82915d 100644
--- a/src/Examples/AnimatedTree.elm
+++ b/src/Examples/AnimatedTree.elm
@@ -107,11 +107,32 @@ ui model =
= , label = Element.text "Forward"
= }
= in
- Element.column
- [ Element.width Element.fill, Element.height Element.fill ]
- [ Examples.Tree.ui tree
- , controls
- ]
+ Examples.Tree.ui tree
+ |> Element.el
+ [ Element.height (Element.maximum 400 Element.fill)
+ , Element.width Element.fill
+ , Element.inFront
+ (Element.el
+ [ Element.alpha
+ (if model.play then
+ 0
+
+ else
+ 0.3
+ )
+ , Element.mouseOver [ Element.alpha 0.5 ]
+ , Element.width Element.fill
+ , Element.height Element.fill
+ ]
+ (Element.el
+ [ Element.centerX
+ , Element.alignBottom
+ , Element.padding 40
+ ]
+ controls
+ )
+ )
+ ]
=
=
=type MsgUse icons for animated tree controls.
index d82915d..15224c9 100644
--- a/src/Examples/AnimatedTree.elm
+++ b/src/Examples/AnimatedTree.elm
@@ -13,6 +13,7 @@ import Browser.Events
=import Element exposing (Element)
=import Element.Input as Input
=import Examples.Tree
+import FeatherIcons
=import Html exposing (Html)
=
=
@@ -61,13 +62,15 @@ ui model =
= Element.row
= [ Element.spacing 12
= ]
- [ if model.play then
+ [ rewindButton
+ , if model.age == model.maxAge then
+ resetButton
+
+ else if model.play then
= pauseButton
=
= else
= playButton
- , resetButton
- , rewindButton
= , forwardButton
= ]
=
@@ -80,31 +83,46 @@ ui model =
= playButton =
= Input.button []
= { onPress = Just Play
- , label = Element.text "Play"
+ , label =
+ FeatherIcons.play
+ |> FeatherIcons.toHtml []
+ |> Element.html
= }
=
= pauseButton =
= Input.button []
= { onPress = Just Pause
- , label = Element.text "Pause"
+ , label =
+ FeatherIcons.pause
+ |> FeatherIcons.toHtml []
+ |> Element.html
= }
=
= resetButton =
= Input.button []
= { onPress = Just Reset
- , label = Element.text "Reset"
+ , label =
+ FeatherIcons.refreshCw
+ |> FeatherIcons.toHtml []
+ |> Element.html
= }
=
= rewindButton =
= Input.button []
= { onPress = Just Rewind
- , label = Element.text "Rewind"
+ , label =
+ FeatherIcons.rewind
+ |> FeatherIcons.toHtml []
+ |> Element.html
= }
=
= forwardButton =
= Input.button []
= { onPress = Just Forward
- , label = Element.text "Forward"
+ , label =
+ FeatherIcons.fastForward
+ |> FeatherIcons.toHtml []
+ |> Element.html
= }
= in
= Examples.Tree.ui treeAdd animated tree block to day 5.
index fe7dc56..1a8e6b2 100644
--- a/content/day-5.txt
+++ b/content/day-5.txt
@@ -23,47 +23,8 @@
=
=We have a nice picture of a tree, but ultimately it may have been easier to just draw it using some graphics program. We promised you a growing tree, so let's make it grow like this:
=
-| Monospace
- TODO: Implement growing tree example
-
+| Window
= | AnimatedTree
- | Axiom
- color = brown
- rotation = -90
- minAge = 0
- maxAge = 10
-
- | Rule
- | Parent
- color = brown
-
- | Child
- color = "green"
- rotation = 0
-
- | Child
- color = "green"
- rotation = 20
-
- | Child
- color = "green"
- rotation = -30
-
- | Rule
- | Parent
- color = green
-
- | Child
- color = "red"
- rotation = -45
-
- | Child
- color = "red"
- rotation = -5
-
- | Child
- color = "red"
- rotation = 50
=
=| Header
= The Elm ArchitectureEdits to day 2
index 26e0ab6..7958918 100644
--- a/content/day-2.txt
+++ b/content/day-2.txt
@@ -29,8 +29,7 @@ Previously we have created a program that displays one dot in the center of the
=| Header
= Multiple Dots
=
-
-First obvious difference is that now we have multiple dots. The only dot we have created using {Code|Svg.circle} function on line 9 - 15, this code block:
+We will start from where we left off yesterday. In the browser go to your saved Ellie bookmark. If you run it you should notice first obvious difference. Currently we only have one dot. It's the one created using {Code|Svg.circle} function on lines 9 - 15, this code fragment:
=
=| Editor
= | Annotations
@@ -372,7 +371,7 @@ This time the radius will affect the size of an imaginary circle on which the do
=
=Ok, so this describes what it means to lay on a circle. But what are some efficient methods of putting things there? How about the following.
=
-You choose a center (anywhere, say {Code|(0, 0)}) and radius (any length you like, say 80).
+You choose a center (anywhere, say {Code|(0, 0)}) and radius (any length you like, say {Code|80}).
=
=Take a ruler and place it's one end (the one showing the value 0) at the center point. Then move the thing you want to place along the ruler the length you chose. Leave it there.
=
@@ -456,14 +455,14 @@ In mathematics there is a special operation to determine it. Perhaps you have he
=
=Maybe you can solve it in your head, but if not, why not use an online Elm REPL? Go to {Link|elmrepl.cuberoot.in|url=http://elmrepl.cuberoot.in/}.
=
-And then
+And then type
=
=| Monospace
= 360 / 5
=
-You should see:
+and press {Key|enter}. You should see:
=
-| Monospace
+| ElmRepl
= ---- Elm 0.19.0 ----------------------------------------------------------------
= Read <https://elm-lang.org/0.19.0/repl> to learn more: exit, help, imports, etc.
= --------------------------------------------------------------------------------
@@ -497,7 +496,7 @@ Now that we understand what it means to be placed on a circle and evenly distrib
= radius = 80
= scatter = False
=
-We will start from where we left off yesterday. Open Eliie in your browser and go to your saved bookmark. It should have the following code:
+Let's get back to our code in Ellie. It should look something like this:
=
=| Editor
= | Annotations
@@ -512,33 +511,64 @@ We will start from where we left off yesterday. Open Eliie in your browser and g
=
=
= main =
- Svg.svg
- [ Svg.Attributes.viewBox "-100 -100 200 200"
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "skyblue"
= ]
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- ]
- []
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "40"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "orange"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "80"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "red"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "120"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "lime"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "160"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "maroon"
+ ]
+ []
+ ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.viewBox "-300 -300 600 600"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
= ]
- |> Element.html
- |> Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
- ]
=
-It's a good starting point. We already have a single dot in the middle. Let's say this will be the center of our circle.
=
-Now it's time to introduce our protractor and ruler. These is a property of an SVG element called `transform`. You use it like that:
+| Note
+ At this point there may be some differences in colours and positions of the dots, but if you follow the instructions so far, the code should look familiar.
+
+It's a good starting point. We already have five dots, one of them in the center ({Code|cx} and {Code|cy} set to {Code|0}). Let's say this will also be the center of our circle. Move all the other dots there. Now it's time to introduce our protractor and ruler. These is a property of an SVG element called `transform`. You use it like that:
=
=| Monospace
= Svg.Attributes.transform "translate(80)"
=
=This will move the dot ("translate" is a fancy term for "move") 80 units.
=
-Also, let's change the color of the dot. In the end it should look like this:
-
=
=| Editor
= | Annotations
@@ -549,8 +579,8 @@ Also, let's change the color of the dot. In the end it should look like this:
= length = 11
=
= | Fold
- start = 21
- length = 7
+ start = 27
+ length = 50
=
= | Code
= module Main exposing (main)
@@ -572,6 +602,34 @@ Also, let's change the color of the dot. In the end it should look like this:
= , Svg.Attributes.fill "skyblue"
= ]
= []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "orange"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "red"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "lime"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "maroon"
+ ]
+ []
= ]
= |> Element.html
= |> Element.layout
@@ -582,18 +640,57 @@ Also, let's change the color of the dot. In the end it should look like this:
=With this change, click {Key|COMPILE}. You should see something like this:
=
=| Window
- | Circle
- dots = 1
- circle = 0 1
- protractor = False
- center = none
- radi = 0 1
- radius = 80
- scatter = False
+ | Shapes
+ | Container
+ background = none
+ fill = True
+ viewBox = -100 -100 200 200
+
+ | Dot
+ radius = 10
+ color = skyblue
+ transformations =
+ | Translate
+ x = 80
+ y = 0
+
+ | Dot
+ radius = 10
+ color = orange
+ transformations =
+ | Translate
+ x = 0
+ y = 0
+
+ | Dot
+ radius = 10
+ color = red
+ transformations =
+ | Translate
+ x = 0
+ y = 0
+
+
+ | Dot
+ radius = 10
+ color = lime
+ transformations =
+ | Translate
+ x = 0
+ y = 0
+
+
+ | Dot
+ radius = 10
+ color = maroon
+ transformations =
+ | Translate
+ x = 0
+ y = 0
=
-As you can see the dot is no longer in the center - it has moved!
+Looks like two dots, but we are not going to fall for this again. We know that there are four of them stacked in the center (with the maroon one on top). What's interesting is the blue one. It's moved!
=
-Now we need a second dot. If you remember the discussion about lists from previous day, you know what to do. Just duplicate the first SVG circle and put it after the first one, with a coma in between. Like this:
+Now let's move the second dot.
=
=| Editor
= | Annotations
@@ -604,8 +701,14 @@ Now we need a second dot. If you remember the discussion about lists from previo
= length = 11
=
= | Fold
- start = 29
- length = 8
+ start = 27
+ length = 50
+
+ | Highlight
+ top = 24
+ left = 14
+ width = 40
+ height = 1
=
= | Code
= module Main exposing (main)
@@ -631,8 +734,29 @@ Now we need a second dot. If you remember the discussion about lists from previo
= [ Svg.Attributes.r "10"
= , Svg.Attributes.cx "0"
= , Svg.Attributes.cy "0"
- , Svg.Attributes.fill "orange"
= , Svg.Attributes.transform "translate(80)"
+ , Svg.Attributes.fill "orange"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "red"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "lime"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "maroon"
= ]
= []
= ]
@@ -642,16 +766,15 @@ Now we need a second dot. If you remember the discussion about lists from previo
= , Element.height Element.fill
= ]
=
-Now let's focus on the second one. We gave it a different color, so we can distinguish between them, but now we don't see the first one! That's because it's exactly in the same spot as the second one, which covers it. They are stacked in a way. We need to move the second dot in a different direction, so it's on a circle, but 72 degree apart from the first one.
=
-For that we need to add another transformation before the move. First we will rotate, then translate:
+Of course it will end up on top of the blue one. That's not what we want. We need to change the direction of the movement. Let's rotate it 72 degree before we translate, like this:
=
=| Editor
= | Annotations
= | Highlight
= top = 25
- left = 14
- width = 51
+ left = 40
+ width = 12
= height = 1
=
= | Fold
@@ -660,7 +783,7 @@ For that we need to add another transformation before the move. First we will ro
=
= | Fold
= start = 29
- length = 8
+ length = 50
=
= | Code
= module Main exposing (main)
@@ -690,67 +813,25 @@ For that we need to add another transformation before the move. First we will ro
= , Svg.Attributes.transform "rotate(72) translate(80)"
= ]
= []
- ]
- |> Element.html
- |> Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
- ]
-
-This will rotate the element by 72 degrees. On its own it wouldn't make any difference, because the dot is round - it doesn't matter how you rotate it, it always looks the same. But It also rotates its internal coordinates system. So the translation will move it in a different direction then the first dot.
-
-Now it's time to add the third, red dot. Duplicate the second one, change fill to `"red"` and put `144` for rotate function, like this:
-
-| Editor
- | Annotations
- | Highlight
- top = 33
- left = 14
- width = 54
- height = 1
-
- | Fold
- start = 1
- length = 27
-
- | Fold
- start = 37
- length = 7
-
- | Code
- module Main exposing (main)
-
- import Element
- import Svg
- import Svg.Attributes
-
-
- main =
- Svg.svg
- [ Svg.Attributes.viewBox "-100 -100 200 200"
- ]
- [ Svg.circle
+ , Svg.circle
= [ Svg.Attributes.r "10"
= , Svg.Attributes.cx "0"
= , Svg.Attributes.cy "0"
- , Svg.Attributes.transform "translate(80)"
- , Svg.Attributes.fill "skyblue"
+ , Svg.Attributes.fill "red"
= ]
= []
= , Svg.circle
= [ Svg.Attributes.r "10"
= , Svg.Attributes.cx "0"
= , Svg.Attributes.cy "0"
- , Svg.Attributes.fill "orange"
- , Svg.Attributes.transform "rotate(72) translate(80)"
+ , Svg.Attributes.fill "lime"
= ]
= []
= , Svg.circle
= [ Svg.Attributes.r "10"
= , Svg.Attributes.cx "0"
= , Svg.Attributes.cy "0"
- , Svg.Attributes.fill "red"
- , Svg.Attributes.transform "rotate(144) translate(80)"
+ , Svg.Attributes.fill "maroon"
= ]
= []
= ]
@@ -760,34 +841,66 @@ Now it's time to add the third, red dot. Duplicate the second one, change fill t
= , Element.height Element.fill
= ]
=
-Why 144? It's because 72 + 72 is 144! So to be 72 degree apart from a dot that's at 72 degree, you need to be at 144 (or 0, but the first dot is already there).
+This will rotate the element by 72 degrees. On its own it wouldn't make any difference, because the dot is round - it doesn't matter how you rotate it, it always looks the same. But It also rotates its internal coordinates system. So the translation will move it in a different direction then the first dot. It's like rotating the ruler before moving the dot along it's edge. Here is the expected result:
=
-This makes the last two dots trivial. Just rotate them to 216 and 288 and give them lime and maroon colors. Note that 288 + 72 = 360, so the distance between the first and last dot is correct.
+| Window
+ | Shapes
+ | Container
+ background = none
+ fill = True
+ viewBox = -100 -100 200 200
=
+ | Dot
+ radius = 10
+ color = skyblue
+ transformations =
+ | Translate
+ x = 80
+ y = 0
=
-The complete program should look like this:
+ | Dot
+ radius = 10
+ color = orange
+ transformations =
+ | Rotate
+ 72
+ | Translate
+ x = 80
+ y = 0
=
-| Editor
- | Annotations
- | Highlight
- top = 41
- left = 12
- width = 54
- height = 1
+ | Dot
+ radius = 10
+ color = red
+ transformations =
+ | Translate
+ x = 0
+ y = 0
=
- | Highlight
- top = 49
- left = 12
- width = 54
- height = 1
=
- | Fold
- start = 1
- length = 35
+ | Dot
+ radius = 10
+ color = lime
+ transformations =
+ | Translate
+ x = 0
+ y = 0
=
- | Fold
- start = 53
- length = 7
+
+ | Dot
+ radius = 10
+ color = maroon
+ transformations =
+ | Translate
+ x = 0
+ y = 0
+
+Now it's time to move the fird and the rest of the dots. Red one gets rotated {Code|144} degree, next one {Code|216} and finally {Code|288}. Why {Code|144}? It's because {Code|72 + 72 is 144}! So to be {Code|72} degree apart from a dot that's at {Code|72} degree, you need to be at {Code|144} (or {Code|0}, but the blue dot is already there). Note that {Code|288 + 72 = 360}, so the distance between the first and last dot is correct. The beauty of math 🤓
+
+The complete program should look like this:
+
+| Editor
+ | Annotations
+ | None
=
= | Code
= module Main exposing (main)Edits to day 3
index 7389e27..661c9b8 100644
--- a/content/day-3.txt
+++ b/content/day-3.txt
@@ -4,11 +4,6 @@
=| Emphasize
= Connecting the Dots
=
-| Note
- *We are still working on this content.*
-
- Stay tuned {Icon|name=radio}
-
=| Note
= So far we have been writing the code of our program without thinking too much about what it means. Today we are going to learn about
=
@@ -48,7 +43,7 @@ Let's start with values. The simplest way to create a value is to literally type
= "Hello!" : String
= >
=
-These expressions ({Code|2} and {Code|"Hello!"}) are called /literals/, because their values are literally what they look like. The value of {Code|2} is simply {Code|2}! When you enter them, the REPL evaluates and prints their value (which in this case is very easy) and type (the text after {Code|:}). We will get to types later.
+These expressions ({Code|2} and {Code|"Hello!"}) are called /literals/, because their values are literally what they look like. The value of {Code|2} is simply {Code|2}! When you enter them, the REPL evaluates and prints their *value* (which in this case is very easy) and *type* (the text after {Code|:}). We will get to types later.
=
=Some value literals are simple, like numbers and strings. Some are complex. Previously we have touched the subject of lists. You can also type them literally, like this:
=
@@ -133,7 +128,7 @@ Every value in Elm has a type. You can see the type in the REPL - after evaluati
= > kittens
= 2 : number
=
-Above {Code|2} is the value and `number` is the type. Similar here:
+Above {Code|2} is the value and {Code|number} is the type. Similar here:
=
=| ElmRepl
= > fun "Learning Elm"
@@ -183,7 +178,7 @@ What's the type of an empty list? Let's see:
= > []
= [] : List a
=
-A list of {Code|a}. Obviously {Code|a} is not a type. This means that the type of the elements cannot be determined yet. This is signaled by the lowercase term where the type should be: {Code|a} instead of the capitalized {Code|String}.
+A list of {Code|a}. Obviously {Code|a} is not a type. This means that the type of the elements cannot be determined yet. This is signaled by the lowercase term where the type should be: lowercase {Code|a} in contrast with the capitalized {Code|String}.
=
=| Note
= Notice that the {Code|number} (type of value {Code|15} for example) is also lowercase. That's because it's not a concrete type! Elm has two concrete types for numbers: {Code|Int} that can represent an integer number (1, 2, 3, 0, -122 etc.) and {Code|Float} that can represent a number with a fraction, (like 1.2, -3.5, 44.2). Because fraction part can be 0 (e.g. 1.0, 2.0, 0.0, -122.0 - so called whole numbers), it's not possible to tell if {Code|2} is an {Code|Int} or a {Code|Float}. We will see some of the implications of this later.
@@ -239,6 +234,9 @@ But what are functions good for? They are very good for making similar values th
=
=We don't have to create all the functions that we need. There are thousands of them already defined and we can use them by importing modules.
=
+| Header
+ Exercise
+
=Let's look at our code and try to recognize some literal values, lists, names and functions. Here is how it should look after day 2:
=
=| Editor
@@ -653,7 +651,7 @@ This way we declared that when calling a {Code|dot} name you will provide two va
=
=Let's reload again and see that there is still only one, skyblue dot visible. That's because we did not tell Elm what to do with the values of the two parameters we provide for the {Code|dot} function. We gave them names ({Code|color} and {Code|rotation}), but never called them.
=
-First, let's use the {Code|color} parameter. If you want to change the color of the dot, you need to pass a different value to the {Code|Svg.Attributes.fill} function on line {Code|12}. Currently the value is a literal string {Code|"skyblue"}. Instead we want to give it whatever value was given for the {Code|color} parameter. We do it by simply calling the name of the parameter in place of a literal value, like this:
+First, let's use the {Code|color} parameter. If you want to change the color of the dot, you need to pass a different value to the {Code|Svg.Attributes.fill} function on line {Code|30}. Currently the value is a literal string {Code|"skyblue"}. Instead we want to give it whatever value was given for the {Code|color} parameter. We do it by simply calling the name of the parameter in place of a literal value, like this:
=
=| Editor
= | Annotations
@@ -753,7 +751,12 @@ We can use the {Code|++} operator to glue the strings together. First part is co
= ]
= []
=
-But Elm will complain about this. First it looks like we gave 5 arguments to the {Code|Svg.Attributes.transform} function, and it only takes one. We can fix it by putting a parentheses around the {Code|"rotate(" ++ rotation ++ ") translate(80)" }, basically telling Elm that this is a single expression and it should evaluate its value before passing it to the {Code|Svg.Attributes.transform} function. Now it should look like this:
+But Elm will complain about this. First it looks like we gave 5 arguments to the {Code|Svg.Attributes.transform} function, and it only takes one. We can fix it by putting a parentheses around this part:
+
+| Monospace
+ "rotate(" ++ rotation ++ ") translate(80)" }
+
+basically telling Elm that this is a single expression and it should evaluate it before passing its value to the {Code|Svg.Attributes.transform} function. Now it should look like this:
=
=| Editor
= | Annotations
@@ -886,16 +889,50 @@ That's what we need! Let's pass the value of {Code|rotation} through this functi
= , Svg.Attributes.cx "0"
= , Svg.Attributes.cy "0"
= , Svg.Attributes.fill color
- , Svg.Attributes.transform ("rotate(" ++ (String.fromFloat rotation) ++ ") translate(80)")
+ , Svg.Attributes.transform ("rotate("
+ ++ (String.fromFloat rotation)
+ ++ ") translate(80)"
+ )
= ]
= []
=
+This should work! Ellie should now present the dots as intended and I hope you agree that the code is more readable now.
+
+| Header
+ Lines to Connect the Dots
+
+Armed with our functional superpowers, let's make another function that draws a line. Let me first show you a complete code and then we can discuss it.
+
=| Editor
= | Annotations
- | None
+ | Fold
+ start = 1
+ length = 36
=
= | Code
- IDEA: What if we use String.concat instead?
+ module Main exposing (main)
+
+ import Element
+ import Svg
+ import Svg.Attributes
+
+
+ main =
+ Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200"
+ ]
+ [ dot "skyblue" 0
+ , dot "orange" 72
+ , dot "red" 144
+ , dot "lime" 216
+ , dot "maroon" 288
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
=
= dot color rotation =
= Svg.circle
@@ -903,27 +940,14 @@ That's what we need! Let's pass the value of {Code|rotation} through this functi
= , Svg.Attributes.cx "0"
= , Svg.Attributes.cy "0"
= , Svg.Attributes.fill color
- , Svg.Attributes.transform (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ") translate(80)"
- ]
+ , Svg.Attributes.transform ("rotate("
+ ++ (String.fromFloat rotation)
+ ++ ") translate(80)"
= )
= ]
= []
=
-This should work! Ellie should now present the dots as intended and I hope you agree that the code is more readable now.
-
-| Header
- Lines to Connect the Dots
-
-Armed with our functional superpowers, let's make another function that draws a line. Let me first show you a complete code and then we can discuss it.
-
-| Editor
- | Annotations
- | None
=
- | Code
= line color rotation =
= Svg.line
= [ Svg.Attributes.strokeWidth "1"
@@ -987,9 +1011,9 @@ Once you have the function defined, let's use it. For every dot, create one line
= , dot "red" 144
= , line "red" 144
= , dot "lime" 216
- , line " lime" 216
+ , line "lime" 216
= , dot "maroon" 288
- , line " maroon" 288
+ , line "maroon" 288
= ]
= |> Svg.svg
= [ Svg.Attributes.height "100%"
@@ -1134,8 +1158,6 @@ You should see something like this in on Ellie:
=| Emphasize
= Congratulations!
=
-| Emphasize
= {Icon|name=award}
=
-| Emphasize
- You are ready for {Link|Day 4|url=/day-4.html}!
+ This was the toughest day of the workshop. Now you are ready for {Link|Day 4|url=/day-4.html}!Edits to day 4
index 497fe17..db3ac4a 100644
--- a/content/day-4.txt
+++ b/content/day-4.txt
@@ -4,11 +4,6 @@
=| Emphasize
= Let's Make a Tree
=
-| Note
- *We are still working on this content.*
-
- Stay tuned {Icon|name=radio}
-
=| Note
= Last day we have learned about basic building blocks of an Elm program: *value*, *name* and *type*. Today we are going to use this blocks to compose more complex structures. On the way we are going to learn about:
=
@@ -147,20 +142,7 @@ The tree is built from segments: a line and a dot. In this respect it is similar
= ]
= []
=
-{Key|COMPILE} on Ellie there should be no difference at this point. Looking back at code I notice that each segment looks the same: it is a group with a dot and a line, where the {Code|group} and {Code|line} functions have the same arguments passed to them. This kind of repetition begs for a function. Let's call it {Code|segment} and define like this:
-
-| Editor
- | Annotations
- | None
-
- | Code
- segment color rotation =
- Svg.g []
- [ dot color rotation
- , line color rotation
- ]
-
-Type this code at the bottom of the file and then replace main with the following:
+Press {Key|COMPILE} on Ellie. There should be no difference at this point. Looking back at code I notice that each segment looks the same: it is a group with a dot and a line, where the {Code|group} and {Code|line} functions have the same arguments passed to them. This kind of repetition begs for a function. Let's call it {Code|segment} and define like this:
=
=| Editor
= | Annotations
@@ -261,7 +243,7 @@ Click {Key|COMPILE}. There should still be no visible difference in the behavior
=
=The big difference between our program and the one we are trying to build is that ours have only one level of segments, whereas in the example segments grow from the tip of other segments. You can think of it as segments having child segments: the green segment has two child red segments and one green segment. Each red segment has two green child segments.
=
-It's similar to how the way an SVG group has child elements. But group itself (together with its children) is an element, so we can put a group within a group. Let's do that! Change the definition of {Code|segment} function as follows:
+It's similar to the way an SVG group has child elements. But group itself (together with its children) is an element, so we can put a group within a group. Let's do that! Change the definition of {Code|segment} function as follows:
=
=| Editor
= | Annotations
@@ -340,24 +322,24 @@ It's similar to how the way an SVG group has child elements. But group itself (t
=
=
= segment color rotation =
- Svg.g []
- [ dot color rotation
- , line color rotation
- , Svg.g
- [ Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ") translate(80)"
- ]
- )
- ]
- [ dot "red" 15
- , line "red" 15
- , dot "blue" -15
- , line "blue" -15
+ Svg.g []
+ [ dot color rotation
+ , line color rotation
+ , Svg.g
+ [ Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ [ dot "red" 15
+ , line "red" 15
+ , dot "blue" -15
+ , line "blue" -15
+ ]
= ]
- ]
=
=Now the program should display something like this:
=
@@ -707,7 +689,7 @@ Now the program should display something like this:
= x = 80
= y = 0
=
-It already starts to look interesting, but there are two problems with it. First it doesn't fit on screen. This is easy to fix using {Code|viewBox}. We need to zoom out to see more of a picture. Change the value passed to {Code|viewBox} on line 19 to {Code|"-500 -500 1000 1000"}, effectively zooming out 5x but preserving keeping the origin in the center of our viewbox.
+It already starts to look interesting, but there are two problems with it. First it doesn't fit on screen. This is easy to fix using {Code|viewBox}. We need to zoom out to see more of a picture. Change the value passed to {Code|viewBox} on line 19 to {Code|"-500 -500 1000 1000"}, effectively zooming out 5x but keeping the origin in the center.
=
=Then the lines look ugly displayed on top of the dots of different color. This is also easy to fix. In SVG several siblings (children of the same parent element) lay one on top of another. The "younger" sibling is laying on top of the older, so in our case the group containing the blue and red dots and lines lays on top of the dot and line. Let's change the order of siblings by moving the group to the beginning of the list, like this:
=
@@ -719,12 +701,6 @@ Then the lines look ugly displayed on top of the dots of different color. This i
= width = 46
= height = 1
=
- | Highlight
- top = 74
- left = 10
- width = 21
- height = 8
-
= | Fold
= start = 1
= length = 7
@@ -1182,20 +1158,20 @@ So now each segment has two sub segments: red and blue. The red one is rotated 1
=
= | Fold
= start = 21
- length = 13
+ length = 12
=
= | Fold
= start = 50
= length = 39
=
= | Highlight
- top = 35
- left = 1
- width = 31
+ top = 34
+ left = 23
+ width = 10
= height = 1
=
= | Highlight
- top = 46
+ top = 45
= left = 13
= width = 8
= height = 1
@@ -1234,7 +1210,6 @@ So now each segment has two sub segments: red and blue. The red one is rotated 1
= ]
=
=
- segment : String -> Float -> List (Svg msg) -> Svg.Svg msg
= segment color rotation children =
= Svg.g []
= [ Svg.g
@@ -2201,7 +2176,7 @@ The tree following these rules will look like this:
= y = 0
=
=
-To represent this in code we will need to learn few new concepts: *dictionary*, *record*, *type alias* and *mapping over list*.
+To represent this in code we will need to learn few new concepts: *dictionary*, *record*, *type alias* and *mapping over a list*.
=
=Let's start with a record. Currently our {Code|segment} function takes three arguments first two are {Code|color : String} and {Code|rotation : Float}. The last one is a list of child segments. Instead we can merge the first two into one argument of type {Code|\{ color : String, rotation : Float \}}. It will look like this:
=
@@ -2836,7 +2811,7 @@ There are three new things here:
=
=The first one is pretty simple. Previously we have been inserting entries into the dictionary. Each entry has a key (color of the segment) and value (list of child segments). Now we are getting these values back.
=
-But what if the entry for a given key is not there? For example we have not inserted the entry for the {Code|"red"} key? Well, then we get {Code|Nothing}. But our {Code|segment} function cannot work with nothing - it has to get a record with color and rotation. So when we have nothing, we can't call the {Code|segment} function. To understand this, consider the following.
+But what if the entry for a given key is not there? For example we have not inserted the entry for the {Code|"red"} key? Well, then we get {Code|Nothing}. But our {Code|segment} function cannot work with nothing - it has to get a list of records with colors and rotations. So when we have nothing, we can't call the {Code|segment} function. To understand this, consider the following.
=
=If there is an entry, we will get a list of records. We want a list of segments. Each of the records can be passed to the {Code|segment} function to produce one segment. That's where {Code|List.map} comes in. Given a list and a function it will call the function with each of the elements in the list and give back the list of produced values. Let's see a simpler example in the REPL:
=
@@ -3204,26 +3179,20 @@ Since the size depends on the age, and the age is given for every segment, we ca
= | Annotations
= | Highlight
= top = 62
- left = 1
- width = 22
+ left = 4
+ width = 5
= height = 1
=
= | Highlight
= top = 64
- left = 10
- width = 39
- height = 1
-
- | Highlight
- top = 53
- left = 30
- width = 17
+ left = 29
+ width = 20
= height = 1
=
= | Highlight
- top = 72
+ top = 57
= left = 18
- width = 17
+ width = 5
= height = 1
=
= | Fold
@@ -3334,32 +3303,142 @@ Since the size depends on the age, and the age is given for every segment, we ca
=
=Next let's pass the age to the {Code|line} function and use it for both the thickness ({Code|strokWidth}) and length ({Code|x2}).
=
-If you click {Key|COMPILE} now, the tree will look as if it exploded. It's funny. The reason is that we did not adjust the translation of the child groups nor the dots. It's always 80, even though the length of the lines is variable - see lines {Code|53} and {Code|72}. Let's change it like that:
=
=| Editor
= | Annotations
+ | Fold
+ start = 1
+ length = 36
+
+
+ | Fold
+ start = 61
+ length = 16
+
+
= | Highlight
- top = 88
- left = 10
- width = 47
+ top = 79
+ left = 4
+ width = 5
= height = 1
=
= | Highlight
- top = 91
- left = 10
- width = 49
+ top = 81
+ left = 29
+ width = 20
= height = 1
=
= | Highlight
- top = 75
+ top = 58
= left = 18
- width = 27
+ width = 5
+ height = 1
+
+ | Code
+ module Main exposing (main)
+
+ import Dict
+ import Element
+ import Svg exposing (Svg)
+ import Svg.Attributes
+
+
+ main =
+ [ segment 4 { color = "brown", rotation = -90 } ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: none"
+ , Svg.Attributes.viewBox "-500 -500 1000 1000"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+
+ rules =
+ Dict.empty
+ |> Dict.insert "brown"
+ [ { color = "brown", rotation = 0 }
+ , { color = "green", rotation = 20 }
+ , { color = "green", rotation = -30 }
+ ]
+ |> Dict.insert "green"
+ [ { color = "red", rotation = -45 }
+ , { color = "red", rotation = -5 }
+ , { color = "red", rotation = 50 }
+ ]
+
+
+ segment age { color, rotation } =
+ if age <= 0 then
+ Svg.g [] []
+
+ else
+ Svg.g []
+ [ rules
+ |> Dict.get color
+ |> Maybe.withDefault []
+ |> List.map (segment (age - 1))
+ |> Svg.g
+ [ Svg.Attributes.transform
+ ("rotate("
+ ++ String.fromFloat rotation
+ ++ ") translate(80)"
+ )
+ ]
+ , dot age color rotation
+ , line age color rotation
+ ]
+
+
+ dot age color rotation =
+ Svg.circle
+ [ Svg.Attributes.r (String.fromFloat age)
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform
+ ("rotate("
+ ++ String.fromFloat rotation
+ + ") translate(80)"
+ )
+ ]
+ []
+
+
+ line age color rotation =
+ Svg.line
+ [ Svg.Attributes.strokeWidth (String.fromFloat age)
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x1 (String.fromFloat (age * 10))
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.stroke color
+ , Svg.Attributes.transform
+ ( "rotate("
+ ++ String.fromFloat rotation
+ ++ ")"
+ )
+ ]
+ []
+
+If you click {Key|COMPILE} now, the tree will look as if it exploded. It's funny. The reason is that we did not adjust the translation of the child groups nor the dots. It's always 80, even though the length of the lines is variable - see lines {Code|53} and {Code|72}. Let's change it like that:
+
+| Editor
+ | Annotations
+ | Highlight
+ top = 72
+ left = 21
+ width = 25
= height = 1
=
= | Highlight
- top = 54
- left = 30
- width = 27
+ top = 53
+ left = 33
+ width = 26
= height = 1
=
= | Fold
@@ -3370,6 +3449,10 @@ If you click {Key|COMPILE} now, the tree will look as if it exploded. It's funny
= start = 1
= length = 37
=
+ | Fold
+ start = 78
+ length = 100
+
= | Code
= module Main exposing (main)
=
@@ -3420,13 +3503,11 @@ If you click {Key|COMPILE} now, the tree will look as if it exploded. It's funny
= |> List.map (segment (age - 1))
= |> Svg.g
= [ Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ") translate("
- , String.fromFloat (age * 10)
- , ")"
- ]
+ ("rotate("
+ ++ String.fromFloat rotation
+ ++ ") translate("
+ ++ String.fromFloat (age * 10)
+ ++ ")"
= )
= ]
= , dot age color rotation
@@ -3441,13 +3522,11 @@ If you click {Key|COMPILE} now, the tree will look as if it exploded. It's funny
= , Svg.Attributes.cy "0"
= , Svg.Attributes.fill color
= , Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ") translate("
- , String.fromFloat (age * 10)
- , ")"
- ]
+ ("rotate("
+ ++ String.fromFloat rotation
+ ++ ") translate("
+ ++ String.fromFloat (age * 10)
+ ++ ")"
= )
= ]
= []
@@ -3455,23 +3534,21 @@ If you click {Key|COMPILE} now, the tree will look as if it exploded. It's funny
=
= line age color rotation =
= Svg.line
- [ Svg.Attributes.strokeWidth "1"
+ [ Svg.Attributes.strokeWidth (String.fromFloat age)
= , Svg.Attributes.x1 "0"
= , Svg.Attributes.y1 "0"
= , Svg.Attributes.x1 (String.fromFloat (age * 10))
= , Svg.Attributes.y1 "0"
= , Svg.Attributes.stroke color
- , Svg.Attributes.strokeWidth (String.fromFloat age)
= , Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ")"
- ]
+ ("rotate("
+ ++ String.fromFloat rotation
+ ++ ")"
= )
= ]
= []
=
+
=Now the tree should look correctly, like this:
=
=| Window
@@ -3517,8 +3594,6 @@ Now the tree should look correctly, like this:
=| Emphasize
= Congratulations!
=
-| Emphasize
= {Icon|name=award}
=
-| Emphasize
= You are ready for {Link|the final day|url=/day-5.html}!Merge branch 'master' into animated-tree
Merge remote-tracking branch 'origin/master' into animated-tree
Make rewind and fast-forward more granullar in AnimatedTree example
index 15224c9..fe2d90a 100644
--- a/src/Examples/AnimatedTree.elm
+++ b/src/Examples/AnimatedTree.elm
@@ -200,12 +200,12 @@ update msg model =
= )
=
= Rewind ->
- ( { model | age = grow -1 model }
+ ( { model | age = grow -0.2 model }
= , Cmd.none
= )
=
= Forward ->
- ( { model | age = grow 1 model }
+ ( { model | age = grow 0.2 model }
= , Cmd.none
= )
=Merge branch 'animated-tree' into 'master'
Animated tree
See merge request software-garden/software-garden.gitlab.io!26
Merge remote-tracking branch 'origin/master' into rewrite-content-ellie
Edits to day 5
index a19cee5..10a10f6 100644
--- a/content/day-5.txt
+++ b/content/day-5.txt
@@ -183,7 +183,7 @@ First let's change the value of {Code|main} and create the {Code|view} function,
= ]
= []
=
-Notice that the {Code|view} takes an argument called {Code|age} and passes it to the first (brown) segment. The value of {Code|age} is our state. In some programs the type of state can be very complex, but in our program it's simply a {Code|Float} number. It will indicate how much time in milliseconds have passed from the start of the program. Because the time is counted in milliseconds, to get correct results we need to divide it by 5000 (so the tree will grows one generation of segments per five second).
+Notice that the {Code|view} takes an argument called {Code|age} and passes it to the first (brown) segment. The value of {Code|age} is our state. In some programs the type of state can be very complex, but in our program it's simply a {Code|Float} number. It will indicate how much time in milliseconds have passed from the start of the program. Because the time is counted in milliseconds, to get correct results we need to divide it by 5000 (so the tree will grow one generation of segments per five second).
=
=Now let's provide the init function. It takes an argument sometimes called flags, but we will ignore it. Let's just say that it will always receive {Code|()} - an empty value called unit. It needs to return two values bound together in a structure called /tuple/.
=
@@ -229,17 +229,7 @@ To use it we first need to import the {Code|Browser.Events} module. The {Code|Br
=| Note
= Perhaps you have noticed that we use a lot of functions. Functions are passed to functions that sometimes return functions 🤕 That's why Elm is called a *functional programming language*.
=
-The whole {Code|subscriptions} looks like that:
-
-| Editor
- | Annotations
- | None
-
- | Code
- subscriptions age =
- Browser.Events.onAnimationFrameDelta identity
-
-And the complete code like this:
+The complete code like this:
=
=| Editor
= | Annotations
@@ -255,21 +245,12 @@ And the complete code like this:
= width = 31
= height = 7
=
- | Fold
- start = 1
- length = 18
-
= | Highlight
= top = 48
= left = 1
= width = 49
= height = 2
=
- | Fold
- start = 51
- length = 81
-
-
= | Code
= module Main exposing (main)
=
@@ -404,10 +385,8 @@ And the complete code like this:
=| Emphasize
= That's it!
=
-| Emphasize
= {Icon|name=flag}
=
-| Emphasize
= Your tree is growing over time!
=
=We hope you had good time learning programming with us. If we still have some time left, we can play with the code.Merge branch 'rewrite-content-ellie' into 'master'
Use Ellie for writing and running the code
See merge request software-garden/software-garden.gitlab.io!27
Commits: 11
Remove config parameter from the ui function of Examples.AnimatedTree.
The model parameter already contains config. No need to pass it as a separate parameter to ui.
Co-Authored-By: Tadeusz Łazurski tadeusz@lazurski.pl
index f6efba1..cdfa56d 100644
--- a/src/Examples/AnimatedTree.elm
+++ b/src/Examples/AnimatedTree.elm
@@ -51,8 +51,8 @@ init flags =
= )
=
=
-ui : Examples.Tree.Config -> Model -> Element Msg
-ui config model =
+ui : Model -> Element Msg
+ui model =
= let
= tree =
= { config | axiom = { axiom | age = model.age } }
@@ -64,6 +64,9 @@ ui config model =
= else
= playButton
=
+ config =
+ model.config
+
= axiom =
= config.axiom
=
@@ -95,7 +98,7 @@ type Msg
=view : Model -> Html Msg
=view model =
= model
- |> ui Examples.Tree.defaults
+ |> ui
= |> Element.layout
= [ Element.width Element.fill
= , Element.height Element.fillMerge branch 'master' into rewrite-content-ellie
Add resetButton to Animated tree.
index cdfa56d..c315a72 100644
--- a/src/Examples/AnimatedTree.elm
+++ b/src/Examples/AnimatedTree.elm
@@ -58,11 +58,14 @@ ui model =
= { config | axiom = { axiom | age = model.age } }
=
= controls =
- if model.play then
- pauseButton
+ Element.row []
+ [ if model.play then
+ pauseButton
=
- else
- playButton
+ else
+ playButton
+ , resetButton
+ ]
=
= config =
= model.config
@@ -81,6 +84,12 @@ ui model =
= { onPress = Just Pause
= , label = Element.text "Pause"
= }
+
+ resetButton =
+ Input.button []
+ { onPress = Just Reset
+ , label = Element.text "Reset"
+ }
= in
= Element.column
= [ Element.width Element.fill, Element.height Element.fill ]
@@ -93,6 +102,7 @@ type Msg
= = Play
= | Pause
= | Animate Float
+ | Reset
=
=
=view : Model -> Html Msg
@@ -132,6 +142,11 @@ update msg model =
= , Cmd.none
= )
=
+ Reset ->
+ ( { model | age = model.minAge }
+ , Cmd.none
+ )
+
=
=subscriptions : Model -> Sub Msg
=subscriptions model =Change default Editor path to Elm
This is how it's presented in Ellie.
index a25c922..a9a249a 100644
--- a/src/Editor.elm
+++ b/src/Editor.elm
@@ -38,7 +38,7 @@ type alias Colors =
=
=defaults : Config
=defaults =
- { path = "src/Main.elm"
+ { path = "Elm"
= , colors =
= { primary = Element.rgb 0 0 0
= , secondary = Element.rgb 0.8 0.8 0.8Edits to preparation page
index e2d25b1..41bf6c6 100644
--- a/content/preparation.txt
+++ b/content/preparation.txt
@@ -1,14 +1,31 @@
=| Title
= Before the course begins
=
-During the workshop you will learn how to write and publish a computer program running in a web browser. Computer Programs are represented as text, so the text editor is the most fundamental tool of a programmer. We are going to use the Elm live editor called Ellie {Link|ellie-app.com|url=https://ellie-app.com} to write our code. When you open Ellie there will be a code, but you don't need that and please delete it. On Ellie, you write your codes on the left side (black screen) and the result is displayed on the right side (white screen). On the top left side of the black screen there are two buttons. {Icon|name=package} is for installing packages and {Icon|name=settings} is for adjusting settings. On the top right side of the black screen there is {Icon|name=align-left} we will use to format and make our code look pretty. After finishing writing a code we click {Key|COMPILE} (on the top right side of the white screen) to see the output.
+Hello! We are glad you are here. Let's get you set up for the workshop.
+
+| Header
+ Setup
+
+You will need
+
+| List
+ - a laptop computer;
+ - a web browser: {Link|Mozilla Firefox|url=https://www.mozilla.org/en-US/firefox/} or {Link|Google Chrome|url=https://www.google.com/chrome/}.
+
+During the workshop you will learn how to write and publish a computer program running in a web browser. Computer Programs are represented as text, so the text editor is the most fundamental tool of a programmer. We are going to use the Elm live editor called Ellie. It's free to use and requires no registration. All you need to do to write and run our code is open this address: {Link|ellie-app.com|url=https://ellie-app.com}.
+
+On Ellie, you write your codes on the left side (in the dark area of the screen) and the result is displayed on the right side (white area of the screen). The dark area is divided in two parts: *Elm* and *HTML*. During the workshop we will only modify the Elm code. You can use small downward arrow {Icon|name=chevron-down} to hide the HTML part and get some more space for your Elm code.
+
+When you open Ellie there will already be some code in Elm part. You don't need it so please delete it. We will write our own code together.
+
+On the top left side of the screen there are two buttons: {Icon|name=package} is for installing packages and {Icon|name=settings} is for adjusting settings. On the top right side of the dark area of the screen there is {Icon|name=align-left} button. We can use to format our code (make it look pretty). After finishing writing a code we click {Key|COMPILE} (on the top right side of the white area). This will run and display our program.
=
=Go to {Icon|name=settings} to give your project a name. Insert any name you wish. To save your work first click {Key|SAVE} and then bookmark it in your browser.
=
=| Header
= Our First Program!
=
-We are going to write "Hello Tree", but first let's install html package. Go to {Icon|name=package} and install html. Write the code as shown below.
+First we are going to create a very simple program that just writes "Hello Tree" on the screen. Write the code as shown below in the Elm part of the Ellie app.
=
=| Editor
= | Annotations
@@ -23,22 +40,23 @@ We are going to write "Hello Tree", but first let's install html package. Go to
= main =
= Html.text "Hello, Tree"
=
-Click {Icon|name=align-left} and then {Key|COMPILE} you should see something like this:
+Click {Icon|name=align-left} to make sure it's formatted nicely and then {Key|COMPILE}. You should see something like this:
=
=| Window
= Hello, Tree!
=
-This was a warmup. Let's try something more challenging on day 1.
=
-| Header
- Setup
=
-You will need
+| Emphasize
+ Did it work? Congratulations!
=
-| List
- - A Laptop
- - Mozilla firefox or Chrome browser
- - Internet connection
+ {Icon|name=check-circle}
+
+ You are all set and ready for {Link|Day 1|url=/day-1.html}!
=
=| Emphasize
- Congratulations! We are all set and you are ready for {Link|Day 1|url=/day-1.html}!
+ Got questions? Got Stuck?
+
+ {Icon|name=help-circle}
+
+ Please reach out to {Link|fana@software.garden|url= mailto:fana@software.garden}.Remove setting up an Elm program from the list of things to be learned on Day 1
With Ellie it's not necessary.
index 3ddc2f7..21102a2 100644
--- a/content/day-1.txt
+++ b/content/day-1.txt
@@ -9,7 +9,6 @@
= Today we are going to learn about
=
= | List
- -> Setting up an Elm program
= -> Scalable Vector Graphics
= -> Cartesian coordinates systems
= -> Layouts with Elm UIAdd rewind and forward button to Animated Tree.
index c315a72..42a14cb 100644
--- a/src/Examples/AnimatedTree.elm
+++ b/src/Examples/AnimatedTree.elm
@@ -65,6 +65,8 @@ ui model =
= else
= playButton
= , resetButton
+ , rewindButton
+ , forwardButton
= ]
=
= config =
@@ -90,6 +92,18 @@ ui model =
= { onPress = Just Reset
= , label = Element.text "Reset"
= }
+
+ rewindButton =
+ Input.button []
+ { onPress = Just Rewind
+ , label = Element.text "Rewind"
+ }
+
+ forwardButton =
+ Input.button []
+ { onPress = Just Forward
+ , label = Element.text "Forward"
+ }
= in
= Element.column
= [ Element.width Element.fill, Element.height Element.fill ]
@@ -103,6 +117,8 @@ type Msg
= | Pause
= | Animate Float
= | Reset
+ | Rewind
+ | Forward
=
=
=view : Model -> Html Msg
@@ -147,6 +163,16 @@ update msg model =
= , Cmd.none
= )
=
+ Rewind ->
+ ( { model | age = model.age - 1 }
+ , Cmd.none
+ )
+
+ Forward ->
+ ( { model | age = model.age + 1 }
+ , Cmd.none
+ )
+
=
=subscriptions : Model -> Sub Msg
=subscriptions model =Correct the complete code on day 1
index 21102a2..2a8aba1 100644
--- a/content/day-1.txt
+++ b/content/day-1.txt
@@ -51,13 +51,13 @@ Below is the complete code to draw a dot like this, but don't type it in your ed
=
= main =
= Svg.svg
- [ Svg.Attributes.viewBox "-100 -100 200 200"
+ [ Svg.Attributes.viewBox "-300 -300 600 600"
= ]
= [ Svg.circle
= [ Svg.Attributes.r "10"
= , Svg.Attributes.cx "0"
= , Svg.Attributes.cy "0"
- , Svg.Attributes.color "skyblue"
+ , Svg.Attributes.fill "skyblue"
= ]
= []
= ]Elaborate on the installation of elm/svg
index 2a8aba1..a984b66 100644
--- a/content/day-1.txt
+++ b/content/day-1.txt
@@ -67,7 +67,7 @@ Below is the complete code to draw a dot like this, but don't type it in your ed
= , Element.height Element.fill
= ]
=
-We are going to use a technology called *SVG (Scalable Vector Graphics)*. It's all about drawing shapes on the screen. Let's install an Elm Package to help us work with SVG. Go to {Icon|name=package} to install svg.
+We are going to use a technology called *SVG (Scalable Vector Graphics)*. It's all about drawing shapes on the screen. Let's install an Elm Package to help us work with SVG. Go to {Icon|name=package} and type {Code|elm//svg} in the search box. After a short while a list of suggested packages should show up. Install the one called simply {Code|svg}.
=
=Now that we have {Code|elm//svg} installed, we can write the code that will draw a dot for us. Change the code to look like this:
=Edits to Day 1
index a984b66..2dce2cb 100644
--- a/content/day-1.txt
+++ b/content/day-1.txt
@@ -225,7 +225,7 @@ One method is to use the *cartesian coordinates system*. To get some intuition a
=| Note
= TODO: Image of the table, the flower pot and the villain
=
-However, if you had a ruler, you could say: "it's 63.3cm from the top edge and 27.1cm from the left edge". To do that you would have previously agree which edge is left and which is top - not so obvious in case of a table. Fortunately in SVG we have this sort of agreement made for us. We always measure from the top and left edge of the viewport.
+However, if you had a ruler, you could say: "it's 63.3cm from the top edge and 27.1cm from the left edge". To do that you would need to previously agree which edge is left and which is top - not so obvious in case of a table. Fortunately in SVG we have this sort of agreement made for us. We always measure from the top and left edge of the viewport.
=
=The distance from the left edge is called {Code|x} and the distance from the top is called {Code|y}. The bigger the {Code|x} the more to the right goes the element. The bigger the {Code|y} the more it goes down. Try it out:
=
@@ -238,7 +238,7 @@ The distance from the left edge is called {Code|x} and the distance from the top
=
=Adjust the sliders below to change the {Code|x} and {Code|y} coordinates of the dot. It's like moving the flower pot on the table by describing how far left, right, up or down it should be.
=
-Initially the center of the dot is at a point called /the origin/ or {Code|\{ x = 0, y = 0\}}. The origin is in the top left corner of the viewport. That's why only a quarter of the dot is visible. If we want to help our dot get out of hiding, we'll have to place the center of the dot at a point with positive {Code|x} and {Code|y} values.
+Initially the center of the dot is at a point called /the origin/: {Code|\{ x = 0, y = 0\}}. The origin is in the top left corner of the viewport. That's why only a quarter of the dot is visible. If we want to help our dot get out of hiding, we'll have to place the center of the dot at a point with positive {Code|x} and {Code|y} values.
=
=To change the position of the dot, we can use the {Code|cx} and {Code|cy} attributes of the {Code|circle} element, like this:
=
@@ -405,7 +405,7 @@ You can also remove line 19 to remove the pink background, or set it to some oth
=| Header
= Color
=
-Speaking of colors, now that we've centered our dot, it's time to give it a color! We do it by adding a {Code|fill} attribute to the {Code|circle} element, like that:
+Speaking of colors, now that we've centered our dot, it's time to give it a color! We do it by adding a {Code|fill} attribute to the {Code|circle} element, like this:
=
=| Editor
= | Annotations
@@ -474,6 +474,8 @@ The only change is on line 13. Reload the browser and see this:
=
=That's exactly what we were trying to achieve. Scroll up to the problem section to compare.
=
+Please remember to save your code using the {Key|save} button in Ellie. When saved, copy the address and send it to us.
+
=| Emphasize
= Congratulations!
=Restyle the ending of Day 1
index 2dce2cb..533e942 100644
--- a/content/day-1.txt
+++ b/content/day-1.txt
@@ -468,19 +468,15 @@ The only change is on line 13. Reload the browser and see this:
= x = 0
= y = 0
=
-| Emphasize
- Wow!
-
-
=That's exactly what we were trying to achieve. Scroll up to the problem section to compare.
=
-Please remember to save your code using the {Key|save} button in Ellie. When saved, copy the address and send it to us.
+| Note
+ Please remember to save your code using the {Key|save} button in Ellie. When saved, copy the address and send it to us.
+
=
=| Emphasize
= Congratulations!
=
-| Emphasize
= {Icon|name=award}
=
-| Emphasize
= We really hope you enjoyed this first day of our workshop. See you at {Link|Day 2|url=/day-2.html}!
Commits: 2
Add attribution to the Elm architecture.
index fe7dc56..88c1007 100644
--- a/content/day-5.txt
+++ b/content/day-5.txt
@@ -87,8 +87,8 @@ State is sometimes called model. There are also commands, but we will not use th
= src = /assets/Elm MVU architecture.jpg
= description = "Model View Update - The Elm Architecture"
=
-| Monospace
- TODO: Attribution
+| Note
+ "The Elm Architecture" by {Link|Kolja Wilcke|url=https://twitter.com/01k} is licenced under {Link|CC BY 4.0|url=https://creativecommons.org/licenses/by/4.0/}
=
=First let's change the value of {Code|main} and create the {Code|view} function, like this:
=Merge branch 'tea-picture-attribution' into 'master'
Add attribution to the Elm architecture picture.
See merge request software-garden/software-garden.gitlab.io!25
Commits: 3
Merge branch 'rewrite-content-ellie' of gitlab.com:software-garden/software-garden.gitlab.io into rewrite-content-ellie
Change code Terminal to code Elm Repl on day 3 and day 4.
Put Fana's bio before Sam.
Change widget terminal to elmRepl in Mark.Custom.
Change terminal to elmRepl in Mark/Custom so we can use the online Elm Repl.
index 19b53ef..e6dd358 100644
--- a/content/day-3.txt
+++ b/content/day-3.txt
@@ -28,7 +28,7 @@ For efficiency we are going to play with these concepts in REPL (the Read - Eval
=
=It should show something like this
=
-| Terminal
+| ElmRepl
= ---- Elm 0.19.0 ----------------------------------------------------------------
= Read <https://elm-lang.org/0.19.0/repl> to learn more: exit, help, imports, etc.
= --------------------------------------------------------------------------------
@@ -41,7 +41,7 @@ Here you can enter snippets of Elm code and see it evaluated.
=
=Let's start with values. The simplest way to create a value is to literally type it in our code. Try it (type things after {Code|>} and expect to see what follows):
=
-| Terminal
+| ElmRepl
= > 2
= 2 : number
= > "Hello"
@@ -52,7 +52,7 @@ These expressions ({Code|2} and {Code|"Hello!"}) are called /literals/, because
=
=Some value literals are simple, like numbers and strings. Some are complex. Previously we have touched the subject of lists. You can also type them literally, like this:
=
-| Terminal
+| ElmRepl
= > [ 1, 2, 5, 77, 2 ]
= [1,2,5,77.6,2] : List number
=
@@ -63,13 +63,13 @@ First notice that a list literal starts with {Code|[} and ends with {Code|]}. In
=
=List can be empty:
=
-| Terminal
+| ElmRepl
= > []
= [] : List a
=
=or contain only one element:
=
-| Terminal
+| ElmRepl
= > [ "Hello" ]
= ["Hello"] : List String
=
@@ -78,7 +78,7 @@ or contain only one element:
=
=Now let's focus our attention to *names*. If you have a value, you can give it a name.
=
-| Terminal
+| ElmRepl
= > kittens = 2
= 2 : number
= > greeting = "Hello!"
@@ -87,7 +87,7 @@ Now let's focus our attention to *names*. If you have a value, you can give it a
=
=From now on you can refer to this value either literally or by its name:
=
-| Terminal
+| ElmRepl
= > kittens
= 2 : number
= > 2
@@ -95,19 +95,19 @@ From now on you can refer to this value either literally or by its name:
=
=The effect will be exactly the same - you will get a value back. So we see that there are at least two ways of getting values: by literally expressing them or referring to them by names given to them previously. In a composite value of a list, you can mix the two ways together:
=
-| Terminal
+| ElmRepl
= > [ 1, kittens, 3 ]
= [1,2,3] : List number
=
=A *function* is a special kind of a value. Let's create a named function like this:
=
-| Terminal
+| ElmRepl
= > fun something = something ++ " is fun!"
= <function> : String -> String
=
=Here we gave a name {Code|fun} to a function, that given {Code|something} will produce a string saying that this {Code|something} is fun! Let's see how you can use it:
=
-| Terminal
+| ElmRepl
= > fun "Learning Elm"
= "Learning Elm is fun!" : String
=
@@ -129,13 +129,13 @@ Not every function can be applied to any value. You can add two numbers (like {C
=
=Every value in Elm has a type. You can see the type in the REPL - after evaluating the expression it shows its value and the type, like here:
=
-| Terminal
+| ElmRepl
= > kittens
= 2 : number
=
=Above {Code|2} is the value and `number` is the type. Similar here:
=
-| Terminal
+| ElmRepl
= > fun "Learning Elm"
= "Learning Elm is fun!" : String
=
@@ -143,19 +143,19 @@ The value is {Code|"Learning Elm is fun!"} and the type is {Code|String}.
=
=Composite values like lists have composite types. For example here the type is {Code|List String}.
=
-| Terminal
+| ElmRepl
= > [ "Hello", "Good bye" ]
= ["Hello","Good bye"] : List String
=
=This means that it's a list of strings. All the elements of this list are of type {Code|String}. When talking about lists you have to tell that it's a list and what is the type of its elements. Only this can be considered a fully specified type. Compare:
=
-| Terminal
+| ElmRepl
= > [ 1, 2, 3 ]
= [1,2,3] : List number
=
=This is a list of numbers. It's easy to see - all the elements are numbers, just as previously they were strings. All the elements of a list must be of the same type. If you try mixing the types, Elm will refuse to build your program. Try:
=
-| Terminal
+| ElmRepl
= > [ 1, 2, 3, "Carrot" ]
= -- TYPE MISMATCH ----------------------------------------------------------- elm
=
@@ -179,7 +179,7 @@ This is a list of numbers. It's easy to see - all the elements are numbers, just
=
=What's the type of an empty list? Let's see:
=
-| Terminal
+| ElmRepl
= > []
= [] : List a
=
@@ -190,13 +190,13 @@ A list of {Code|a}. Obviously {Code|a} is not a type. This means that the type o
=
=Above I said that every value has a type and also that a function is a value on its own. Let's try the following in REPL:
=
-| Terminal
+| ElmRepl
= > fun
= <function> : String -> String
=
=We have asked the REPL to tell as the value referenced by name {Code|fun}. In response the REPL display the value as {Code|<function>}. That's because there is no simple way to represent the value of a function as text. Let's focus our attention on the type (part after the colon {Code|:}). It is {Code|String -> String}. This means that the type is a function (we recognize it by the arrow symbol). What's before the arrow is a type of the value that goes into the function (called the /argument of the function/). The type of the argument is {Code|String}. That shouldn't be a surprise. Remember how we have used the function before to produce the value:
=
-| Terminal
+| ElmRepl
= > fun "Learning Elm"
= "Learning Elm is fun!" : String
=
@@ -206,19 +206,19 @@ The part after the arrow symbol is the type that the function returns. It is als
=
=Not every function returns the same type as it takes. For example the function {Code|String.fromInt} takes a value of type {Code|Int} and returns a value of type {Code|String}. Its type is therefore {Code|Int -> String}. We read it as: /type of String.fromInt is a function from Int to String/. Let's see it in action:
=
-| Terminal
+| ElmRepl
= > String.fromInt 10
= "10" : String
=
=That's right! The value {Code|10} is an {Code|Int}, the value {Code|"10"} is a {Code|String}.
=
-| Terminal
+| ElmRepl
= > String.fromInt
= <function> : Int -> String
=
=But what if we want to say that a certain number (say, {Code|232}) is fun? We have our {Code|fun} function, but it takes a {Code|String} and what we have is an {Code|Int}. We can use the very same {Code|String.fromInt} to first convert it into the {Code|String} and then pass the value to the {Code|fun} function, like this:
=
-| Terminal
+| ElmRepl
= > fun (String.fromInt 232)
= "232 is fun!" : String
=
@@ -226,7 +226,7 @@ We had to put the {Code|String.fromInt 232} expression in parentheses, so that E
=
=Alternatively we can use the pipe operator which looks like this: {Code|\|>}. It has the advantage that the transformations that happen first are on the left side and ones that happen later on the right. We can read our code left to right instead of inside out. Here is how it would look like:
=
-| Terminal
+| ElmRepl
= > 232 |> String.fromInt |> fun
= "232 is fun!" : String
=
@@ -239,12 +239,12 @@ But what are functions good for? They are very good for making similar values th
=
=We don't have to create all the functions that we need. There are thousands of them already defined and we can use them by importing modules. We already did that. First you import a module like this:
=
-| Terminal
+| ElmRepl
= > import String
=
=And then use any name that it exposes, like this
=
-| Terminal
+| ElmRepl
= > String.fromInt 10
= "10" : String
=
@@ -845,7 +845,7 @@ and another is:
=
=Elm won't have that. Fortunately there is an easy way to satisfy the type system. The {Code|String.fromFloat} takes a {Code|Float} and gives a {Code|String}. Try it in the REPL:
=
-| Terminal
+| ElmRepl
= > String.fromFloat
= <function> : Float -> String
=index 5cf6d1a..497fe17 100644
--- a/content/day-4.txt
+++ b/content/day-4.txt
@@ -2823,7 +2823,7 @@ Nice, but what's going on here:
= )
= ]
= []
-
+
=
=There are three new things here:
=
@@ -2840,7 +2840,7 @@ But what if the entry for a given key is not there? For example we have not inse
=
=If there is an entry, we will get a list of records. We want a list of segments. Each of the records can be passed to the {Code|segment} function to produce one segment. That's where {Code|List.map} comes in. Given a list and a function it will call the function with each of the elements in the list and give back the list of produced values. Let's see a simpler example in the REPL:
=
-| Terminal
+| ElmRepl
= > fun something = something ++ " is fun!"
= <function> : String -> String
= > fun "Learning Elm"index 79e54bd..527240c 100644
--- a/content/index.txt
+++ b/content/index.txt
@@ -48,6 +48,6 @@ You can learn more about {Link|our motivation|url=/motivation.html}. Below is ma
=
=I love the creativity of the software development and hope to share that passion with you.
=
-*Sam* is a co-author of the workshop. He is an Elm developer at {Link|itravel|url=https://www.itravel.de/} in Cologne, Germany.
-
=*Fana* is a junior software developer (an ex marine biologist {Icon|name=anchor}) and coordinator of our project. She keeps us on the right track. Also she is taking care of our media presence.
+
+*Sam* is a co-author of the workshop. He is an Elm developer at {Link|itravel|url=https://www.itravel.de/} in Cologne, Germany.index 499cdc9..e4c7f4c 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -799,7 +799,7 @@ document =
=
= widgets =
= [ Mark.Custom.editor
- , Mark.Custom.terminal
+ , Mark.Custom.elmRepl
= , Mark.Custom.note
= ]
=index 745e5af..88c3af4 100644
--- a/src/Mark/Custom.elm
+++ b/src/Mark/Custom.elm
@@ -1,6 +1,7 @@
=module Mark.Custom exposing
= ( colors
= , editor
+ , elmRepl
= , emphasize
= , header
= , icon
@@ -11,7 +12,6 @@ module Mark.Custom exposing
= , note
= , paragraph
= , row
- , terminal
= , text
= , title
= , window
@@ -215,8 +215,8 @@ editor =
= )
=
=
-terminal : Mark.Block (a -> Element msg)
-terminal =
+elmRepl : Mark.Block (a -> Element msg)
+elmRepl =
= let
= render content model =
= Element.column
@@ -250,7 +250,7 @@ terminal =
= , Font.monospace
= ]
= ]
- (Element.text "terminal")
+ (Element.text "Elm Repl")
= ]
= , content
= |> String.split "\n"
@@ -276,7 +276,7 @@ terminal =
= ]
= ]
= in
- Mark.block "Terminal"
+ Mark.block "ElmRepl"
= render
= Mark.multiline
=Remove import String from Elm Repl on day 3.
index e6dd358..7389e27 100644
--- a/content/day-3.txt
+++ b/content/day-3.txt
@@ -237,18 +237,7 @@ But what are functions good for? They are very good for making similar values th
=| Note
= Just like machines for painting eggs are useful if you have a lot of eggs to paint. If you only need one or two painted eggs, then it's probably better to just get them painted. But if you have thousands, maybe it's worth to build a machine for it.
=
-We don't have to create all the functions that we need. There are thousands of them already defined and we can use them by importing modules. We already did that. First you import a module like this:
-
-| ElmRepl
- > import String
-
-And then use any name that it exposes, like this
-
-| ElmRepl
- > String.fromInt 10
- "10" : String
-
-If the name comes from an imported module, you need to prefix it with the name of the module, like we did above.
+We don't have to create all the functions that we need. There are thousands of them already defined and we can use them by importing modules.
=
=Let's look at our code and try to recognize some literal values, lists, names and functions. Here is how it should look after day 2:
=
Commits: 7
Initial implementation of Examples.AnimatedTree
No integration with markup yet. Works only in Elm Reactor.
new file mode 100644
index 0000000..a61064c
--- /dev/null
+++ b/src/Examples/AnimatedTree.elm
@@ -0,0 +1,134 @@
+module Examples.AnimatedTree exposing
+ ( main
+ , ui
+ )
+
+import Browser
+import Browser.Events
+import Element exposing (Element)
+import Element.Input as Input
+import Examples.Tree
+import Html exposing (Html)
+
+
+main : Program Flags Model Msg
+main =
+ Browser.element
+ { init = init
+ , view = view
+ , update = update
+ , subscriptions = subscriptions
+ }
+
+
+type alias Model =
+ { age : Float
+ , maxAge : Float
+ , minAge : Float
+ , play : Bool
+ , config : Examples.Tree.Config
+ }
+
+
+type alias Flags =
+ ()
+
+
+init : Flags -> ( Model, Cmd msg )
+init flags =
+ ( { age = 0.0
+ , maxAge = 6.0
+ , minAge = 0
+ , play = False
+ , config = Examples.Tree.defaults
+ }
+ , Cmd.none
+ )
+
+
+ui : Examples.Tree.Config -> Model -> Element Msg
+ui config model =
+ let
+ tree =
+ { config | axiom = { axiom | age = model.age } }
+
+ controls =
+ if model.play then
+ pauseButton
+
+ else
+ playButton
+
+ axiom =
+ config.axiom
+
+ playButton =
+ Input.button []
+ { onPress = Just Play
+ , label = Element.text "Play"
+ }
+
+ pauseButton =
+ Input.button []
+ { onPress = Just Pause
+ , label = Element.text "Pause"
+ }
+ in
+ Element.column
+ [ Element.width Element.fill, Element.height Element.fill ]
+ [ Examples.Tree.ui tree
+ , controls
+ ]
+
+
+type Msg
+ = Play
+ | Pause
+ | Animate Float
+
+
+view : Model -> Html Msg
+view model =
+ model
+ |> ui Examples.Tree.defaults
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+
+update : Msg -> Model -> ( Model, Cmd Msg )
+update msg model =
+ case msg of
+ Play ->
+ ( { model | play = True }
+ , Cmd.none
+ )
+
+ Pause ->
+ ( { model | play = False }
+ , Cmd.none
+ )
+
+ Animate delta ->
+ let
+ progress =
+ min 32 delta / 5000
+
+ age =
+ (model.age + progress)
+ |> min model.maxAge
+ |> max model.minAge
+ in
+ ( { model | age = age }
+ , Cmd.none
+ )
+
+
+subscriptions : Model -> Sub Msg
+subscriptions model =
+ if model.play then
+ Browser.Events.onAnimationFrameDelta Animate
+
+ else
+ Sub.noneIntegrate Examples.AnimatedTree with Examples module
No integration with markup yet.
index 4db325b..2492c7d 100644
--- a/src/Examples.elm
+++ b/src/Examples.elm
@@ -1,5 +1,6 @@
=module Examples exposing (Model, Msg(..), init, subscriptions, update)
=
+import Examples.AnimatedTree
=import Examples.CartesianCoordinates
=import Examples.Circle
=import Examples.Counter
@@ -21,6 +22,7 @@ type alias Model =
= , cartesianCoordinates : Examples.CartesianCoordinates.Model
= , polarCoordinates : Examples.PolarCoordinates.Model
= , viewBox : Examples.ViewBox.Model
+ , animatedTree : Examples.AnimatedTree.Model
= }
=
=
@@ -31,18 +33,24 @@ type Msg
= | CartesianCoordinatesMsg Examples.CartesianCoordinates.Msg
= | PolarCoordinatesMsg Examples.PolarCoordinates.Msg
= | ViewBoxMsg Examples.ViewBox.Msg
+ | AnimatedTreeMsg Examples.AnimatedTree.Msg
=
=
=init : ( Model, Cmd Msg )
=init =
+ let
+ ( animatedTreeModel, animatedTreeMsg ) =
+ Examples.AnimatedTree.init ()
+ in
= ( { counter = Examples.Counter.init
= , transformations = Examples.Transformations.init
= , nestedTransformations = Examples.NestedTransformations.init
= , cartesianCoordinates = Examples.CartesianCoordinates.init
= , polarCoordinates = Examples.PolarCoordinates.init
= , viewBox = Examples.ViewBox.init
+ , animatedTree = animatedTreeModel
= }
- , Cmd.none
+ , animatedTreeMsg
= )
=
=
@@ -89,7 +97,18 @@ update msg model =
= ViewBoxMsg m ->
= ( { model | viewBox = Examples.ViewBox.update m model.viewBox }, Cmd.none )
=
+ AnimatedTreeMsg m ->
+ let
+ ( animatedTreeModel, animatedTreeMsg ) =
+ Examples.AnimatedTree.update m model.animatedTree
+ in
+ ( { model | animatedTree = animatedTreeModel }
+ , animatedTreeMsg |> Cmd.map AnimatedTreeMsg
+ )
+
=
=subscriptions : Model -> Sub Msg
=subscriptions model =
- Sub.none
+ model.animatedTree
+ |> Examples.AnimatedTree.subscriptions
+ |> Sub.map AnimatedTreeMsgindex a61064c..f6efba1 100644
--- a/src/Examples/AnimatedTree.elm
+++ b/src/Examples/AnimatedTree.elm
@@ -1,6 +1,11 @@
=module Examples.AnimatedTree exposing
- ( main
+ ( Model
+ , Msg
+ , init
+ , main
+ , subscriptions
= , ui
+ , update
= )
=
=import BrowserRewrite content to work on Ellie.
I have removed contents from day 1 and moved it to preparation.
index 6b759b8..3ddc2f7 100644
--- a/content/day-1.txt
+++ b/content/day-1.txt
@@ -15,100 +15,6 @@
= -> Layouts with Elm UI
= -> Lists
=
-
-| Header
- Our First Program!
-
-As mentioned before, programs are represented as text (called the /source code/). The source code is stored in files {Icon|name=file} and files are organized in directories {Icon|name=folder}.
-
-
-So the first step is to create a directory for our new program. Let's call it {Code|software-garden}.
-
-In the terminal type:
-
-| Terminal
- mkdir software-garden/
-
-press {Key|enter} key. Then type:
-
-| Terminal
- cd software-garden/
-
-| Note
- Press {Key|enter} again. Generally when we ask you to type something in a terminal, we imply that you press enter afterwards. That's how you signal that you are done typing and expect computer to process your command. Alternatively we call it entering a command.
-
- In this case the first command ({Code|mkdir}) will *m*a*k*e our new *dir*ectory and the second ({Code|cd}) will set it as the *c*urrent *d*irectory, so that subsequent commands will be executed in the context of {Code|software-garden//} directory.
-
- Don't worry about the details too much. Just remember that if you close your terminal and then open it again, you will need to enter:
-
- {Code|cd software-garden//}
-
- to set the current directory again.
-
-We can easily create a new program by entering a following command:
-
-| Terminal
- elm init
-
-You should see something similar to this:
-
-| Terminal
- Hello! Elm projects always start with an elm.json file. I can create them!
-
- Now you may be wondering, what will be in this file? How do I add Elm files to
- my project? How do I see it in the browser? How will my code grow? Do I need
- more directories? What about tests? Etc.
-
- Check out <https://elm-lang.org/0.19.0/init> for all the answers!
-
- Knowing all that, would you like me to create an elm.json file now? [Y/n]:
-
-
-Just press {Key|enter} once again to proceed. Then to create the file which will contain our source code, type:
-
-| Terminal
- atom src/Main.elm
-
-| Note
- Notice that the word {Code|Main} is capitalized. It's important! You can enter uppercase letter {Code|M} with {Key|shift} + {Key|m}, which means:
-
- | List
- - Press and hold {Key|shift}
- - While holding press the {Key|m} button once.
- - Finally release the {Key|shift}.
-
-It may take few seconds, but eventually this command should open a new Atom editor window with an empty text file. If you prefer to use different editor, feel free to do so. Once the editor is ready, type the code below and save the file.
-
-| Editor
- | Annotations
- | None
-
- | Code
- module Main exposing (main)
-
-
- import Html
-
- main =
- Html.text "Hello, Tree"
-
-Then switch back to the terminal and enter the following command:
-
-| Terminal
- elm reactor
-
-This command will start the Elm Reactor, which will allow you to run your program in the web browser. Note that it won't give you the command prompt back - it will run in the terminal until you stop it. Leave it running and open the following address in the web browser:
-
-| Emphasize
- {Link|http:////localhost:8000//src//Main.elm|url=http://localhost:8000/src/Main.elm}
-
-You should see something like this:
-
-| Window
- Hello, Tree!
-
-This was a warmup. Let's try something more challenging.
-
=| Header
= The problem
=
@@ -162,30 +68,9 @@ Below is the complete code to draw a dot like this, but don't type it in your ed
= , Element.height Element.fill
= ]
=
-We are going to use a technology called *SVG (Scalable Vector Graphics)*. It's all about drawing shapes on the screen. Let's install an Elm Package to help us work with SVG. Stop the reactor running in the terminal by pressing {Key|ctrl} + {Key|c} and then enter the following command:
+We are going to use a technology called *SVG (Scalable Vector Graphics)*. It's all about drawing shapes on the screen. Let's install an Elm Package to help us work with SVG. Go to {Icon|name=package} to install svg.
=
-| Terminal
- elm install elm/svg
-
-| Note
- To press {Key|ctrl} + {Key|c} means:
-
- | List
- - Press and hold {Key|ctrl} button (on some keyboards it's labeled as {Key|control}).
- - While holding press the {Key|c} button once.
- - Finally release the {Key|ctrl}.
-
-In response you should see something like this:
-
-| Terminal
- Here is my plan:
-
- Add:
- elm/svg 1.0.1
-
- Would you like me to update your elm.json accordingly? [Y/n]:
-
-Press {Key|enter} to proceed with the installation. Now that we have {Code|elm//svg} installed, we can write the code that will draw a dot for us. Change the code to look like this:
+Now that we have {Code|elm//svg} installed, we can write the code that will draw a dot for us. Change the code to look like this:
=
=| Editor
= | Annotations
@@ -204,15 +89,8 @@ Press {Key|enter} to proceed with the installation. Now that we have {Code|elm//
= [ Svg.Attributes.r "10" ] []
= ]
=
-Start the reactor again by entering:
=
-| Terminal
- elm reactor
-
-| Note
- You can press the up arrow {Key|▲} on the keyboard to retrieve commands entered previously.
-
-Reload the browser. You should see something like this:
+Click {Key|COMPILE}, you should see something like this:
=
=| Window
= | Shapes
@@ -253,7 +131,8 @@ First we have to realize that the dot is inside an {Code|svg} element that itsel
= []
= ]
=
-Reloading the browser should reveal something like this:
+
+Click {Key|COMPILE} and it should reveal something like this:
=
=| Window
= | Shapes
@@ -272,16 +151,17 @@ Reloading the browser should reveal something like this:
=
=Since the dot is inside the {Code|svg} element, and the {Code|svg} element doesn't cover the center of the screen, it's impossible to place the dot at the center. Before anything else, let's make the {Code|svg} fill entire {Definition|term=viewport|definiens=the area of the browser window where the content is displayed}.
=
-To do this we will use a package called {Code|mdgriffith//elm-ui}. It makes laying out elements relatively easy. Install it with the terminal. Press {Key|ctrl} + {Key|c} to stop the elm reactor and enter the command
+To do this we will use a package called {Code|mdgriffith//elm-ui}. It makes laying out elements relatively easy. Go to {Icon|name=package} and install elm-ui.
=
-| Terminal
- elm install mdgriffith/elm-ui
-
-As before, press {Key|enter} to follow the plan. Now let's use the package. Change the code to look like this:
+Now let's use the package. Change the code to look like this:
=
=| Editor
= | Annotations
- | None
+ | Highlight
+ top = 3
+ left = 1
+ width = 14
+ height = 1
=
= | Code
= module Main exposing (main)
@@ -308,7 +188,6 @@ As before, press {Key|enter} to follow the plan. Now let's use the package. Chan
= , Element.height Element.fill
= ]
=
-
=| Note
= Don't worry if it all looks like black magic now. We will discuss what it all means on {Link|Day 3|url=day-3.html} of the workshop.
=
@@ -318,7 +197,7 @@ As before, press {Key|enter} to follow the plan. Now let's use the package. Chan
=
= Then there are some new things on lines 19 - 23. First is the pipe operator ({Code|\|>}). We use it to pass the {Code|svg} element together with the {Code|circle} inside it into `Element.html` function (we need to convert it into an {Code|Element}) and then to pass this {Code|Element} into an {Code|Element.layout} which will fill the viewport with its contents. Again, more about the pipe and functions on day 3.
=
-Reload the browser. The SVG space now fills the entire viewport. Even the small white margin is gone!
+Click {Icon|name=align-left} to format your code and then click {Key|COMPILE}. The SVG space now fills the entire viewport. Even the small white margin is gone!
=
=| Window
= | Shapes
@@ -335,7 +214,7 @@ Reload the browser. The SVG space now fills the entire viewport. Even the small
= x = 0
= y = 0
=
-It should now be possible to place our dot in the center of the screen. Currently the dot is in the top left corner of the viewport, but at least the parent {Code|svg} element is covering it, so the center of the viewport is somewher within the {Code|svg} element.
+It should now be possible to place our dot in the center of the screen. Currently the dot is in the top left corner of the viewport, but at least the parent {Code|svg} element is covering it, so the center of the viewport is somewhere within the {Code|svg} element.
=
=| Header
= The Coordinates
@@ -399,6 +278,7 @@ To change the position of the dot, we can use the {Code|cx} and {Code|cy} attrib
= , Element.height Element.fill
= ]
=
+
=| Window
= | Shapes
= | Containerindex afa080b..f2fbb4c 100644
--- a/content/day-2.txt
+++ b/content/day-2.txt
@@ -32,14 +32,47 @@ Previously we have created a program that displays one dot in the center of the
=
=First obvious difference is that now we have multiple dots. The only dot we have created using {Code|Svg.circle} function on line 9 - 15, this code block:
=
-| Monospace
- Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill "skyblue"
- ]
- []
+| Editor
+ | Annotations
+ | None
+
+ | Fold
+ start = 1
+ length = 8
+
+ | Fold
+ start = 17
+ length = 12
+
+
+ | Code
+ module Main exposing (main)
+
+ import Element
+ import Svg
+ import Svg.Attributes
+
+
+ main =
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "skyblue"
+ ]
+ []
+ ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.viewBox "-300 -300 600 600"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
=
=If you look closely in your source code, the block above is surrounded by {Code|[} and {Code|]} characters. That's a list. We will talk about lists during the next day. For now it's enough to say that a list can contain zero, one or more items of the same type. In this case it contains one item of type {Code|Svg msg} - our lonely, blue dot. Let's add a second one like this:
=
@@ -90,6 +123,7 @@ If you look closely in your source code, the block above is surrounded by {Code|
= , Element.height Element.fill
= ]
=
+
=Now the list spans from lines 9 to 23 and contains two items: skyblue and orange dots. The result should be:
=
=| Window
@@ -420,10 +454,7 @@ In mathematics there is a special operation to determine it. Perhaps you have he
=| Monospace
= 360 / 5
=
-Maybe you can solve it in your head, but if not, why not use Elm REPL? Go to the terminal, stop the reactor (if it's running) and type:
-
-| Monospace
- elm repl
+Maybe you can solve it in your head, but if not, why not use an online Elm REPL? Go to {Link|elmrepl.cuberoot.in|url=http://elmrepl.cuberoot.in/}.
=
=And then
=
@@ -440,8 +471,6 @@ You should see:
= 72 : Float
= >
=
-Type {Code|:exit} to close the REPL.
-
=Turns out that the result is 72. That's the number of degrees we have to turn our ruler around the center each time we place a new dot. If you have a protractor like the one pictured above, you can use it to measure the turn. Then measure the distance from the center along the ruler (a length called radius) and place the dot there.
=
=| Circle
@@ -468,7 +497,7 @@ Now that we understand what it means to be placed on a circle and evenly distrib
= radius = 80
= scatter = False
=
-We will start from where we left off yesterday. Open {Code|src//Main.elm} in your editor. It should have the following code:
+We will start from where we left off yesterday. Open Eliie in your browser and go to your saved bookmark. It should have the following code:
=
=| Editor
= | Annotations
@@ -515,23 +544,42 @@ Also, let's change the color of the dot. In the end it should look like this:
= | Annotations
= | None
=
+ | Fold
+ start = 1
+ length = 11
+
+ | Fold
+ start = 21
+ length = 7
+
= | Code
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.transform "translate(80)"
- , Svg.Attributes.fill "skyblue"
- ]
- []
- ]
+ module Main exposing (main)
=
-With this change, start the reactor by typing in your terminal:
+ import Element
+ import Svg
+ import Svg.Attributes
=
-| Monospace
- elm reactor
=
-and {Link|open the program in your browser|url=http://localhost/src/Main.elm}. You should see something like this:
+ main =
+ Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200"
+ ]
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.transform "translate(80)"
+ , Svg.Attributes.fill "skyblue"
+ ]
+ []
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+With this change, click {Key|COMPILE}. You should see something like this:
=
=| Window
= | Circle
@@ -551,24 +599,48 @@ Now we need a second dot. If you remember the discussion about lists from previo
= | Annotations
= | None
=
+ | Fold
+ start = 1
+ length = 11
+
+ | Fold
+ start = 29
+ length = 8
+
= | Code
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.transform "translate(80)"
- , Svg.Attributes.fill "skyblue"
- ]
- []
- , Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill "orange"
- , Svg.Attributes.transform "translate(80)"
- ]
- []
- ]
+ module Main exposing (main)
+
+ import Element
+ import Svg
+ import Svg.Attributes
+
+
+ main =
+ Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200"
+ ]
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.transform "translate(80)"
+ , Svg.Attributes.fill "skyblue"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "orange"
+ , Svg.Attributes.transform "translate(80)"
+ ]
+ []
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
=
=Now let's focus on the second one. We gave it a different color, so we can distinguish between them, but now we don't see the first one! That's because it's exactly in the same spot as the second one, which covers it. They are stacked in a way. We need to move the second dot in a different direction, so it's on a circle, but 72 degree apart from the first one.
=
@@ -577,72 +649,116 @@ For that we need to add another transformation before the move. First we will ro
=| Editor
= | Annotations
= | Highlight
- top = 14
- left = 6
- width = 53
+ top = 25
+ left = 14
+ width = 51
= height = 1
=
+ | Fold
+ start = 1
+ length = 11
+
+ | Fold
+ start = 29
+ length = 8
+
= | Code
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.transform "translate(80)"
- , Svg.Attributes.fill "skyblue"
- ]
- []
- , Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill "orange"
- , Svg.Attributes.transform "rotate(72) translate(80)"
- ]
- []
- ]
+ module Main exposing (main)
+
+ import Element
+ import Svg
+ import Svg.Attributes
=
-This will rotate the element by 72 degrees. On its own it wouldn't make any difference, because the dot is round - it doesn't matter how you rotate it, it always looks the same. But It also rotates it's internal coordinates system. So the translation will move it in a different direction then the first dot.
+
+ main =
+ Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200"
+ ]
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.transform "translate(80)"
+ , Svg.Attributes.fill "skyblue"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "orange"
+ , Svg.Attributes.transform "rotate(72) translate(80)"
+ ]
+ []
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+This will rotate the element by 72 degrees. On its own it wouldn't make any difference, because the dot is round - it doesn't matter how you rotate it, it always looks the same. But It also rotates its internal coordinates system. So the translation will move it in a different direction then the first dot.
=
=Now it's time to add the third, red dot. Duplicate the second one, change fill to `"red"` and put `144` for rotate function, like this:
=
=| Editor
= | Annotations
= | Highlight
- top = 22
- left = 6
+ top = 33
+ left = 14
= width = 54
= height = 1
=
= | Fold
= start = 1
- length = 16
+ length = 27
+
+ | Fold
+ start = 37
+ length = 7
=
= | Code
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.transform "translate(80)"
- , Svg.Attributes.fill "skyblue"
- ]
- []
- , Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill "orange"
- , Svg.Attributes.transform "rotate(72) translate(80)"
- ]
- []
- , Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill "red"
- , Svg.Attributes.transform "rotate(144) translate(80)"
- ]
- []
- ]
+ module Main exposing (main)
+
+ import Element
+ import Svg
+ import Svg.Attributes
+
+
+ main =
+ Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200"
+ ]
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.transform "translate(80)"
+ , Svg.Attributes.fill "skyblue"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "orange"
+ , Svg.Attributes.transform "rotate(72) translate(80)"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "red"
+ , Svg.Attributes.transform "rotate(144) translate(80)"
+ ]
+ []
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
=
=Why 144? It's because 72 + 72 is 144! So to be 72 degree apart from a dot that's at 72 degree, you need to be at 144 (or 0, but the first dot is already there).
=
@@ -732,7 +848,7 @@ The complete program should look like this:
= , Element.height Element.fill
= ]
=
-In the browser it should look exactly as we planned:
+In Ellie it should look exactly as we planned:
=
=| Window
= | Circleindex f39b2ad..19b53ef 100644
--- a/content/day-3.txt
+++ b/content/day-3.txt
@@ -24,10 +24,7 @@ Before we begin, let's reflect on the following.
=| Emphasize
= Every Elm program is composed of three basic building blocks: {Code|values}, {Code|names} and {Code|types}.
=
-For efficiency we are going to play with these concepts in REPL (the Read - Evaluate - Print Loop). Go to the terminal. If you have Elm reactor running, then stop it with {Key|ctrl} + {Key|c}. Then start the REPL by entering
-
-| Terminal
- elm repl
+For efficiency we are going to play with these concepts in REPL (the Read - Evaluate - Print Loop). Go to online Elm REPL {Link|elmrepl.cuberoot.in|url=http://elmrepl.cuberoot.in/}.
=
=It should show something like this
=
@@ -525,7 +522,7 @@ Move your cursor to the end of the file and type {Code|dot =}, press {Key|enter}
= ]
= []
=
-Reload the browser. Everything should work exactly the same as before. One of the dots is now a named value, but for our program it makes no difference. What is important is its value, not where it comes from.
+Click {Key|COMPILE}. Everything should work exactly the same as before. One of the dots is now a named value, but for our program it makes no difference. What is important is its value, not where it comes from.
=
=Of course the goal is to have our list of dots looking like this:
=
@@ -551,7 +548,7 @@ The dots need to have some parameters so they can be different. We already ident
=
= | Fold
= start = 13
- length = 38
+ length = 39
=
= | Code
= module Main exposing (main)
@@ -565,7 +562,7 @@ The dots need to have some parameters so they can be different. We already ident
= Svg.svg
= [ Svg.Attributes.viewBox "-100 -100 200 200"
= ]
- [ dot color rotation
+ [ dot "skyblue" 0
= , Svg.circle
= [ Svg.Attributes.r "10"
= , Svg.Attributes.cx "0"
@@ -605,6 +602,7 @@ The dots need to have some parameters so they can be different. We already ident
= , Element.height Element.fill
= ]
=
+
= dot color rotation =
= Svg.circle
= [ Svg.Attributes.r "10"
@@ -627,7 +625,7 @@ This way we declared that when calling a {Code|dot} name you will provide two va
=
= | Fold
= start = 18
- length = 5
+ length = 6
=
= | Code
= module Main exposing (main)
@@ -653,6 +651,7 @@ This way we declared that when calling a {Code|dot} name you will provide two va
= , Element.height Element.fill
= ]
=
+
= dot color rotation =
= Svg.circle
= [ Svg.Attributes.r "10"
@@ -670,7 +669,7 @@ First, let's use the {Code|color} parameter. If you want to change the color of
=| Editor
= | Annotations
= | Highlight
- top = 29
+ top = 30
= left = 31
= width = 5
= height = 1
@@ -681,7 +680,7 @@ First, let's use the {Code|color} parameter. If you want to change the color of
=
= | Fold
= start = 18
- length = 5
+ length = 6
=
= | Code
= module Main exposing (main)
@@ -707,6 +706,7 @@ First, let's use the {Code|color} parameter. If you want to change the color of
= , Element.height Element.fill
= ]
=
+
= dot color rotation =
= Svg.circle
= [ Svg.Attributes.r "10"
@@ -753,6 +753,7 @@ We can use the {Code|++} operator to glue the strings together. First part is co
= , Element.height Element.fill
= ]
=
+
= dot color rotation =
= Svg.circle
= [ Svg.Attributes.r "10"
@@ -797,6 +798,7 @@ But Elm will complain about this. First it looks like we gave 5 arguments to the
= , Element.height Element.fill
= ]
=
+
= dot color rotation =
= Svg.circle
= [ Svg.Attributes.r "10"
@@ -921,7 +923,7 @@ That's what we need! Let's pass the value of {Code|rotation} through this functi
= ]
= []
=
-This should work! The browser should now present the dots as intended and I hope you agree that the code is more readable now.
+This should work! Ellie should now present the dots as intended and I hope you agree that the code is more readable now.
=
=| Header
= Lines to Connect the Dots
@@ -1048,7 +1050,7 @@ Once you have the function defined, let's use it. For every dot, create one line
= ]
= []
=
-You should see something like this in the browser:
+You should see something like this in on Ellie:
=
=| Window
= | Shapesindex 5b8499f..5cf6d1a 100644
--- a/content/day-4.txt
+++ b/content/day-4.txt
@@ -63,11 +63,11 @@ The tree is built from segments: a line and a dot. In this respect it is similar
=
= | Fold
= start = 1
- length = 6
+ length = 7
=
= | Fold
- start = 29
- length = 48
+ start = 30
+ length = 49
=
= | Code
= module Main exposing (main)
@@ -76,6 +76,7 @@ The tree is built from segments: a line and a dot. In this respect it is similar
= import Svg
= import Svg.Attributes
=
+
= main =
= [ Svg.g []
= [ dot "skyblue" 0
@@ -110,6 +111,7 @@ The tree is built from segments: a line and a dot. In this respect it is similar
= , Element.height Element.fill
= ]
=
+
= dot color rotation =
= Svg.circle
= [ Svg.Attributes.r "10"
@@ -145,7 +147,7 @@ The tree is built from segments: a line and a dot. In this respect it is similar
= ]
= []
=
-Reload the browser there should be no difference at this point. Looking back at code I notice that each segment looks the same: it is a group with a dot and a line, where the {Code|group} and {Code|line} functions have the same arguments passed to them. This kind of repetition begs for a function. Let's call it {Code|segment} and define like this:
+{Key|COMPILE} on Ellie there should be no difference at this point. Looking back at code I notice that each segment looks the same: it is a group with a dot and a line, where the {Code|group} and {Code|line} functions have the same arguments passed to them. This kind of repetition begs for a function. Let's call it {Code|segment} and define like this:
=
=| Editor
= | Annotations
@@ -163,24 +165,24 @@ Type this code at the bottom of the file and then replace main with the followin
=| Editor
= | Annotations
= | Highlight
- top = 61
+ top = 64
= left = 1
= width = 29
= height = 5
=
= | Highlight
- top = 8
+ top = 9
= left = 4
= width = 22
= height = 6
=
= | Fold
= start = 1
- length = 6
+ length = 7
=
= | Fold
- start = 26
- length = 34
+ start = 27
+ length = 36
=
= | Code
= module Main exposing (main)
@@ -189,6 +191,7 @@ Type this code at the bottom of the file and then replace main with the followin
= import Svg
= import Svg.Attributes
=
+
= main =
= [ segment "skyblue" 0
= , segment "orange" 72
@@ -208,6 +211,7 @@ Type this code at the bottom of the file and then replace main with the followin
= , Element.height Element.fill
= ]
=
+
= dot color rotation =
= Svg.circle
= [ Svg.Attributes.r "10"
@@ -243,6 +247,7 @@ Type this code at the bottom of the file and then replace main with the followin
= ]
= []
=
+
= segment color rotation =
= Svg.g []
= [ dot color rotation
@@ -252,7 +257,7 @@ Type this code at the bottom of the file and then replace main with the followin
=| Note
= I hope you can see the pattern in what we are doing. We are taking repetitive blocks of code and turning them into named functions. Parameters help us deal with variability in the repeated code (like color and rotation that is different for each segment).
=
-Reload the browser. There should still be no visible difference in the behavior of the program, but our source code is getting more readable. That's good.
+Click {Key|COMPILE}. There should still be no visible difference in the behavior of the program, but our source code is getting more readable. That's good.
=
=The big difference between our program and the one we are trying to build is that ours have only one level of segments, whereas in the example segments grow from the tip of other segments. You can think of it as segments having child segments: the green segment has two child red segments and one green segment. Each red segment has two green child segments.
=
@@ -264,10 +269,10 @@ It's similar to how the way an SVG group has child elements. But group itself (t
=
= | Fold
= start = 1
- length = 6
+ length = 7
=
= | Fold
- start = 26
+ start = 28
= length = 35
=
= | Code
@@ -277,6 +282,7 @@ It's similar to how the way an SVG group has child elements. But group itself (t
= import Svg
= import Svg.Attributes
=
+
= main =
= [ segment "skyblue" 0
= , segment "orange" 72
@@ -296,6 +302,7 @@ It's similar to how the way an SVG group has child elements. But group itself (t
= , Element.height Element.fill
= ]
=
+
= dot color rotation =
= Svg.circle
= [ Svg.Attributes.r "10"
@@ -331,6 +338,7 @@ It's similar to how the way an SVG group has child elements. But group itself (t
= ]
= []
=
+
= segment color rotation =
= Svg.g []
= [ dot color rotation
@@ -699,30 +707,30 @@ Now the program should display something like this:
= x = 80
= y = 0
=
-It's already starts to look interesting, but there are two problems with it. First it doesn't fit on screen. This is easy to fix using {Code|viewBox}. We need to zoom out to see more of a picture. Change the value passed to {Code|viewBox} on line 19 to {Code|"-500 -500 1000 1000"}, effectively zooming out 5x but preserving keeping the origin in the center of our viewbox.
+It already starts to look interesting, but there are two problems with it. First it doesn't fit on screen. This is easy to fix using {Code|viewBox}. We need to zoom out to see more of a picture. Change the value passed to {Code|viewBox} on line 19 to {Code|"-500 -500 1000 1000"}, effectively zooming out 5x but preserving keeping the origin in the center of our viewbox.
=
=Then the lines look ugly displayed on top of the dots of different color. This is also easy to fix. In SVG several siblings (children of the same parent element) lay one on top of another. The "younger" sibling is laying on top of the older, so in our case the group containing the blue and red dots and lines lays on top of the dot and line. Let's change the order of siblings by moving the group to the beginning of the list, like this:
=
=| Editor
= | Annotations
= | Highlight
- top = 18
+ top = 19
= left = 12
= width = 46
= height = 1
=
= | Highlight
- top = 73
+ top = 74
= left = 10
= width = 21
= height = 8
=
= | Fold
= start = 1
- length = 6
+ length = 7
=
= | Fold
- start = 20
+ start = 21
= length = 43
=
= | Code
@@ -732,6 +740,7 @@ Then the lines look ugly displayed on top of the dots of different color. This i
= import Svg
= import Svg.Attributes
=
+
= main =
= [ segment "skyblue" 0
= , segment "orange" 72
@@ -751,6 +760,7 @@ Then the lines look ugly displayed on top of the dots of different color. This i
= , Element.height Element.fill
= ]
=
+
= dot color rotation =
= Svg.circle
= [ Svg.Attributes.r "10"
@@ -807,6 +817,7 @@ Then the lines look ugly displayed on top of the dots of different color. This i
= , line color rotation
= ]
=
+
=With this two changes applied you should see something like this in the browser:
=
=| Window
@@ -1277,9 +1288,9 @@ So now each segment has two sub segments: red and blue. The red one is rotated 1
= []
=
=
-We have added third parameter to {Code|segment} function on line 35 (before the changes it was 28). The parameter is named {Code|children} and we use it in place of the list on line 46 (previously 39). The list itself was moved to line 11, where it is passed as a value for the parameter of the first segment. This move made all the lines shift down. All other segments will also need to get value for {Code|children} parameter, so let's just give each of them an empty list - meaning they have 0 children.
+We have added third parameter to the {Code|segment} function on line 35 (before the changes it was 64). The parameter is named {Code|children} and we use it in place of the list on line 46 (previously 75). The list itself was moved to line 11, where it is passed as a value for the parameter of the first segment. This move made all the lines shift down. All other segments will also need to get value for {Code|children} parameter, so let's just give each of them an empty list - meaning they have 0 children.
=
-Once all changes are applied, reload the browser and see that only first, skyblue segment has child segments.
+Once all changes are applied, click {Key|COMPILE} and see that only first, skyblue segment has child segments.
=
=| Window
= | Shapes
@@ -1424,7 +1435,7 @@ Before we continue, let's notice that on lines 11 - 15 we have a nice opportunit
=
= | Fold
= start = 16
- length = 72
+ length = 73
=
= | Code
= module Main exposing (main)
@@ -1513,6 +1524,7 @@ Before we continue, let's notice that on lines 11 - 15 we have a nice opportunit
= ]
= []
=
+
=with:
=| Editor
= | Annotations
@@ -1715,6 +1727,7 @@ A segment within a segment! Consider that every segment can have child segments.
= ]
= []
=
+
=| Emphasize
= Time to play
=
@@ -1938,6 +1951,7 @@ The tree following these rules will look like this:
= y = 0
= | Rotate
= -45
+
= | Dot
= radius = 10
= color = red
@@ -1974,6 +1988,7 @@ The tree following these rules will look like this:
= y = 0
= | Rotate
= -5
+
= | Dot
= radius = 10
= color = red
@@ -2030,6 +2045,7 @@ The tree following these rules will look like this:
= | Translate
= x = 80
= y = 0
+
= | Line
= length = 80
= color = red
@@ -2199,7 +2215,7 @@ Let's start with a record. Currently our {Code|segment} function takes three arg
=
= | Fold
= start = 22
- length = 66
+ length = 67
=
= | Code
= module Main exposing (main)
@@ -2288,6 +2304,7 @@ Let's start with a record. Currently our {Code|segment} function takes three arg
= ]
= []
=
+
=Not much changed. Instead of passing two separate values for {Code|color} and {Code|rotation} we pass one value with two named fields. That's a record!
=
=Why did I do it? That way I can store complete information about a segment in one value. I need this for the *dictionary* of rules.
@@ -2308,7 +2325,7 @@ Dictionary let's us associate one value (let's say color) with another value (le
=
= | Fold
= start = 48
- length = 55
+ length = 56
=
= | Code
= module Main exposing (main)
@@ -2412,6 +2429,7 @@ Dictionary let's us associate one value (let's say color) with another value (le
= ]
= []
=
+
=We have added an import statement for {Code|Dict} module and created a dictionary with two entries: one for {Code|"brown"} and one for {Code|"green"}. The values associated with these keys are a list of segment records. The meaning of this is following:
=
=| List
@@ -2428,11 +2446,11 @@ Here it is:
=
= | Fold
= start = 1
- length = 47
+ length = 48
=
= | Fold
- start = 67
- length = 36
+ start = 69
+ length = 38
=
= | Code
= module Main exposing (main)
@@ -2469,6 +2487,7 @@ Here it is:
= , Element.height Element.fill
= ]
=
+
= rules =
= Dict.empty
= |> Dict.insert "brown"
@@ -2482,6 +2501,7 @@ Here it is:
= , { color = "red", rotation = 50 }
= ]
=
+
= segment { color, rotation } =
= Svg.g []
= [ rules
@@ -2501,6 +2521,7 @@ Here it is:
= , line color rotation
= ]
=
+
= dot color rotation =
= Svg.circle
= [ Svg.Attributes.r "10"
@@ -2517,6 +2538,7 @@ Here it is:
= ]
= []
=
+
= line color rotation =
= Svg.line
= [ Svg.Attributes.strokeWidth "1"
@@ -2541,18 +2563,18 @@ We also have to change the definition of {Code|main}. Segment no longer takes tw
=| Editor
= | Annotations
= | Highlight
- top = 9
+ top = 10
= left = 4
= width = 47
= height = 1
=
= | Fold
= start = 1
- length = 5
+ length = 7
=
= | Fold
- start = 20
- length = 70
+ start = 23
+ length = 72
=
= | Code
= module Main exposing (main)
@@ -2562,6 +2584,7 @@ We also have to change the definition of {Code|main}. Segment no longer takes tw
= import Svg exposing (Svg)
= import Svg.Attributes
=
+
= main =
= [ segment { color = "brown", rotation = -90 } ]
= |> Svg.svg
@@ -2576,6 +2599,7 @@ We also have to change the definition of {Code|main}. Segment no longer takes tw
= , Element.height Element.fill
= ]
=
+
= rules =
= Dict.empty
= |> Dict.insert "brown"
@@ -2589,6 +2613,7 @@ We also have to change the definition of {Code|main}. Segment no longer takes tw
= , { color = "red", rotation = 50 }
= ]
=
+
= segment { color, rotation } =
= Svg.g []
= [ rules
@@ -2608,6 +2633,7 @@ We also have to change the definition of {Code|main}. Segment no longer takes tw
= , line color rotation
= ]
=
+
= dot color rotation =
= Svg.circle
= [ Svg.Attributes.r "10"
@@ -2624,6 +2650,7 @@ We also have to change the definition of {Code|main}. Segment no longer takes tw
= ]
= []
=
+
= line color rotation =
= Svg.line
= [ Svg.Attributes.strokeWidth "1"
@@ -2670,6 +2697,7 @@ The result should be exactly like we wanted it to be:
= color = green
= rotation = -30
=
+
= | Rule
= parent = green
= children =
@@ -2683,23 +2711,24 @@ The result should be exactly like we wanted it to be:
= color = red
= rotation = 50
=
+
=Nice, but what's going on here:
=
=| Editor
= | Annotations
= | Highlight
- top = 38
+ top = 41
= left = 12
= width = 23
= height = 3
=
= | Fold
= start = 1
- length = 34
+ length = 36
=
= | Fold
- start = 53
- length = 36
+ start = 57
+ length = 38
=
=
= | Code
@@ -2710,6 +2739,7 @@ Nice, but what's going on here:
= import Svg exposing (Svg)
= import Svg.Attributes
=
+
= main =
= [ segment { color = "brown", rotation = -90 } ]
= |> Svg.svg
@@ -2724,6 +2754,7 @@ Nice, but what's going on here:
= , Element.height Element.fill
= ]
=
+
= rules =
= Dict.empty
= |> Dict.insert "brown"
@@ -2737,6 +2768,7 @@ Nice, but what's going on here:
= , { color = "red", rotation = 50 }
= ]
=
+
= segment { color, rotation } =
= Svg.g []
= [ rules
@@ -2756,6 +2788,7 @@ Nice, but what's going on here:
= , line color rotation
= ]
=
+
= dot color rotation =
= Svg.circle
= [ Svg.Attributes.r "10"
@@ -2772,6 +2805,7 @@ Nice, but what's going on here:
= ]
= []
=
+
= line color rotation =
= Svg.line
= [ Svg.Attributes.strokeWidth "1"
@@ -2789,6 +2823,7 @@ Nice, but what's going on here:
= )
= ]
= []
+
=
=There are three new things here:
=
@@ -2825,18 +2860,18 @@ Take some time to grasp it and then compare with our code.
=| Editor
= | Annotations
= | Highlight
- top = 37
+ top = 40
= left = 12
= width = 41
= height = 13
=
= | Fold
= start = 1
- length = 35
+ length = 36
=
= | Fold
- start = 53
- length = 36
+ start = 56
+ length = 39
=
= | Code
= module Main exposing (main)
@@ -2846,6 +2881,7 @@ Take some time to grasp it and then compare with our code.
= import Svg exposing (Svg)
= import Svg.Attributes
=
+
= main =
= [ segment { color = "brown", rotation = -90 } ]
= |> Svg.svg
@@ -2860,6 +2896,7 @@ Take some time to grasp it and then compare with our code.
= , Element.height Element.fill
= ]
=
+
= rules =
= Dict.empty
= |> Dict.insert "brown"
@@ -2873,6 +2910,7 @@ Take some time to grasp it and then compare with our code.
= , { color = "red", rotation = 50 }
= ]
=
+
= segment { color, rotation } =
= Svg.g []
= [ rules
@@ -2892,6 +2930,7 @@ Take some time to grasp it and then compare with our code.
= , line color rotation
= ]
=
+
= dot color rotation =
= Svg.circle
= [ Svg.Attributes.r "10"
@@ -2908,6 +2947,7 @@ Take some time to grasp it and then compare with our code.
= ]
= []
=
+
= line color rotation =
= Svg.line
= [ Svg.Attributes.strokeWidth "1"
@@ -2926,6 +2966,7 @@ Take some time to grasp it and then compare with our code.
= ]
= []
=
+
=Let's analyze it line by line:
=
=| List
@@ -2989,7 +3030,7 @@ We can do it like this. When we create our first segment (the brown one), we wil
=
= | Fold
= start = 60
- length = 38
+ length = 39
=
= | Fold
= start = 22
@@ -3092,6 +3133,7 @@ We can do it like this. When we create our first segment (the brown one), we wil
= ]
= []
=
+
=On line 10 we pass the age to the {Code|segment} function as the first argument. The record is a second parameter now. The reason is that it makes mapping on line 47 easier. Age is the same for all the children (age of the parent minus 1).
=
=On line 39 we see something new. It's the {Code|if ... then ... else ...} expression. You can probably guess how it works: first we give it a condition (in this case {Code|age <= 0} - meaning that age is zero or less). That's our halting condition.
@@ -3190,7 +3232,7 @@ Since the size depends on the age, and the age is given for every segment, we ca
=
= | Fold
= start = 77
- length = 20
+ length = 22
=
= | Code
= module Main exposing (main)
@@ -3270,6 +3312,7 @@ Since the size depends on the age, and the age is given for every segment, we ca
= ]
= []
=
+
= line color rotation =
= Svg.line
= [ Svg.Attributes.strokeWidth "1"
@@ -3288,9 +3331,10 @@ Since the size depends on the age, and the age is given for every segment, we ca
= ]
= []
=
+
=Next let's pass the age to the {Code|line} function and use it for both the thickness ({Code|strokWidth}) and length ({Code|x2}).
=
-If you reload now, the tree will look as if it exploded. It's funny. The reason is that we did not adjust the translation of the child groups nor the dots. It's always 80, even though the length of the lines is variable - see lines {Code|53} and {Code|72}. Let's change it like that:
+If you click {Key|COMPILE} now, the tree will look as if it exploded. It's funny. The reason is that we did not adjust the translation of the child groups nor the dots. It's always 80, even though the length of the lines is variable - see lines {Code|53} and {Code|72}. Let's change it like that:
=
=| Editor
= | Annotationsindex fe7dc56..389479e 100644
--- a/content/day-5.txt
+++ b/content/day-5.txt
@@ -95,19 +95,19 @@ First let's change the value of {Code|main} and create the {Code|view} function,
=| Editor
= | Annotations
= | Highlight
- top = 10
+ top = 11
= left = 4
= width = 35
= height = 6
=
= | Highlight
- top = 18
+ top = 19
= left = 1
= width = 8
= height = 1
=
= | Fold
- start = 31
+ start = 32
= length = 81
=
= | Code
@@ -119,6 +119,7 @@ First let's change the value of {Code|main} and create the {Code|view} function,
= import Svg exposing (Svg)
= import Svg.Attributes
=
+
= main =
= Browser.element
= { init = init
@@ -302,6 +303,7 @@ And the complete code like this:
= left = 1
= width = 49
= height = 2
+
= | Fold
= start = 51
= length = 81index 4c3c26d..e2d25b1 100644
--- a/content/preparation.txt
+++ b/content/preparation.txt
@@ -1,109 +1,44 @@
=| Title
= Before the course begins
=
-During the workshop you will learn how to write and publish a computer program running in a web browser. Please follow this instructions before we start, so that we have more time to focus on programming. It shouldn't take you more than 30 minutes {Icon|name=watch} and can save us several hours together.
+During the workshop you will learn how to write and publish a computer program running in a web browser. Computer Programs are represented as text, so the text editor is the most fundamental tool of a programmer. We are going to use the Elm live editor called Ellie {Link|ellie-app.com|url=https://ellie-app.com} to write our code. When you open Ellie there will be a code, but you don't need that and please delete it. On Ellie, you write your codes on the left side (black screen) and the result is displayed on the right side (white screen). On the top left side of the black screen there are two buttons. {Icon|name=package} is for installing packages and {Icon|name=settings} is for adjusting settings. On the top right side of the black screen there is {Icon|name=align-left} we will use to format and make our code look pretty. After finishing writing a code we click {Key|COMPILE} (on the top right side of the white screen) to see the output.
=
-| Note
- This setup instructions are based on an assumption that you are using a Mac.
-
- If you are using Linux or BSD, then feel free to ask us for help.
-
- If you are using anything else (like Windows), then we are sorry, but we do not support your operating system. A Mac, Linux or BSD are required for the workshop. If you don't have one, we can advise you on getting and setting up a Linux machine.
-
-| Header
- Setup
-
-You will need
-
-| List
- - a Mac or Linux laptop,
- - a text editor (I use Atom)
- - and the Elm programming language
+Go to {Icon|name=settings} to give your project a name. Insert any name you wish. To save your work first click {Key|SAVE} and then bookmark it in your browser.
=
=| Header
- Installing Elm
-
-
-To install the Elm programming language, go to it's website:
-
-| Emphasize
- {Link|elm-lang.org|url=http://elm-lang.org/}
-
-
-and follow the installation instructions.
-
-Some of the tools we use with Elm require Node.js. Go to the {Link|Node.js|url=https://nodejs.org/en/} website and install the current version (the green button on the right)
-
-We will need to use the {Definition|term=terminal|definiens=a program that let's you enter commands for your computer} {Icon|name=terminal} a little bit. Open Launchpad (the icon with a rocket) and type {Code|terminal}, like this:
+ Our First Program!
=
-| Image
- src = /assets/mac-launchpad-terminal.png
- description = Here is a picture of terminal
+We are going to write "Hello Tree", but first let's install html package. Go to {Icon|name=package} and install html. Write the code as shown below.
=
+| Editor
+ | Annotations
+ | None
=
-Press {Key|enter} key or click the black icon with {Icon|name=terminal} sign. You should see a window similar to this:
+ | Code
+ module Main exposing (main)
=
=
-| Image
- src = /assets/mac-terminal-window.png
- description = A picture of Mac Terminal
+ import Html
=
-Type the following in the terminal.
+ main =
+ Html.text "Hello, Tree"
=
+Click {Icon|name=align-left} and then {Key|COMPILE} you should see something like this:
=
-| Terminal
- elm repl
+| Window
+ Hello, Tree!
=
-Then press the {Key|enter} key. Note that the command line changes. You should see something like this:
-
-| Terminal
- ---- Elm 0.19.0 --------------------------------------------------
- Read <https://elm-lang.org/0.19.0/repl> to learn more: exit, help
- ------------------------------------------------------------------
- >
-
-It's the Elm REPL (Read-Evaluate-Print Loop). Inside the REPL type.
-
-| Terminal
- 2+2
-
-| Note
- Press {Key|enter} again. Generally when we ask you to type something in a terminal, we imply that you press enter afterwards. That's how you signal that you are done typing and expect computer to process your command. Alternatively we call it entering a command.
-
-You should see:
-
-| Terminal
- ---- Elm 0.19.0 ----------------
- Read <https://elm-lang.org/0.19.0/repl> to learn more: exit, help
- --------------------------------
- > 2 + 2
- 4 : number
- >
-
-Easy, huh? We will learn more about REPL later. For now enter the following command to close it.
-
-| Terminal
- :exit
-
-The command line should get back to its initial state.
+This was a warmup. Let's try something more challenging on day 1.
=
=| Header
- Install Atom Text Editor
-
-Computer Programs are represented as text, so the text editor is the most fundamental tool of a programmer. There is a lot of good text editors, but to get you started, we will use Atom here.
-
-| Note
- If you are already using a text editor that you like, feel free to continue using it during the workshop.
-
-Go to {Link|atom.io|url=https://atom.io} and download the editor. After installation, open it and from {Code|Atom} menu choose {Code|Install Shell Commands}.
-
-One last thing we need is the Elm support for the Atom editor. You can install it by entering the following command in the terminal:
+ Setup
=
-| Terminal
- apm install language-elm
+You will need
=
-| Note
- APM is the Atom Package Manager, but don't pay much attention to it. We won't be using it again.
+| List
+ - A Laptop
+ - Mozilla firefox or Chrome browser
+ - Internet connection
=
=| Emphasize
= Congratulations! We are all set and you are ready for {Link|Day 1|url=/day-1.html}!Edited Fold block on day 2.
index f2fbb4c..26e0ab6 100644
--- a/content/day-2.txt
+++ b/content/day-2.txt
@@ -42,7 +42,7 @@ First obvious difference is that now we have multiple dots. The only dot we have
=
= | Fold
= start = 17
- length = 12
+ length = 13
=
=
= | Code
@@ -86,7 +86,7 @@ If you look closely in your source code, the block above is surrounded by {Code|
=
= | Fold
= start = 24
- length = 12
+ length = 13
=
= | Code
= module Main exposing (main)Edit Fold block on day 2.
index f2fbb4c..26e0ab6 100644
--- a/content/day-2.txt
+++ b/content/day-2.txt
@@ -42,7 +42,7 @@ First obvious difference is that now we have multiple dots. The only dot we have
=
= | Fold
= start = 17
- length = 12
+ length = 13
=
=
= | Code
@@ -86,7 +86,7 @@ If you look closely in your source code, the block above is surrounded by {Code|
=
= | Fold
= start = 24
- length = 12
+ length = 13
=
= | Code
= module Main exposing (main)Add workshop dates to test-run.
index 00ea641..9a06486 100644
--- a/content/test-run.txt
+++ b/content/test-run.txt
@@ -3,6 +3,13 @@
=
=In collaboration with {Link|Career Services|url=https://students.uu.nl/en/careerservices} of the University of Utrecht we are offering a one time, free workshop for the first 12 students to sign up. The workshop will be shorter than usual - 3 hours a day.
=
+| Note
+ 04 March 2019: /Marinus Ruppert Room 029/ Time *16:00* - *19:00*
+ 05 March 2019: /Marinus Ruppert Room 005/ Time *16:00* - *19:00*
+ 06 March 2019: /Marinus Ruppert Room 031/ Time *16:00* - *19:00*
+ 07 March 2019: /Marinus Langeveld Room G230/ Time *16:00* - *19:00*
+ 08 March 2019: /Marinus Ruppert Room 121/ Time *16:00* - *19:00*
+
=| Emphasize
= {Link|Sign up now|url=https://fd21.formdesk.com/universiteitutrecht/CS-20190304-08-SoftwareGarden}
=Merge branch 'workshop-dates' into 'master'
Add workshop dates to test-run.
See merge request software-garden/software-garden.gitlab.io!24
Commits: 4
Create a test run page about the UU4U collaboration
index 79e54bd..36da1e7 100644
--- a/content/index.txt
+++ b/content/index.txt
@@ -13,7 +13,12 @@ Hello! We are running a workshop that will give you a glimpse into the way softw
=
= or drop us a note at {Link|fana@software.garden|url= mailto:fana@software.garden}
=
-You can learn more about {Link|our motivation|url=/motivation.html}. Below is material through which we will be going during the workshop. Only the first section is a required read before you come, but feel free to read more to get more prepared.
+During this *5 days workshop* (4 hours each day) you will learn to solve problems using a functional programming language. We think {Link|it's important|url=/motivation.html}. Upon completion of the course your name, photo and link to your website (or LinkedIn profile etc) will be posted here. The price for participation is *500 €* (inc. 21% VAT).
+
+| Note
+ {Icon|name=gift} {Link|Special offer for University of Utrecht students|url=/test-run.html}.
+
+Below is the material through which we will be going during the workshop. Only the first section is a required read before you come.
=
=| Link
= url = /preparation.htmlnew file mode 100644
index 0000000..a0c1120
--- /dev/null
+++ b/content/test-run.txt
@@ -0,0 +1,11 @@
+| Title
+ Special Offer for University of Utrecht Students
+
+In collaboration with {Link|Career Services|url=https://students.uu.nl/en/careerservices} of the University of Utrecht we are offering a one time, free workshop for first 12 students to sign up. The workshop will be shorter than usual (3 hours a day) because you are so smart 🤓
+
+| Emphasize
+ {Link|Sign up!|url=https://fd21.formdesk.com/universiteitutrecht/CS-20190304-08-SoftwareGarden}
+
+ <>
+
+ {Icon|name=phone} {Link|+31 638 216 166|url=tel:+31638216166} | {Icon|name=mail} {Link|fana@software.garden|url= mailto:fana@software.garden} | {Icon|name=home} {Link|Back|url=/}index 499cdc9..12dfff2 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -188,6 +188,9 @@ view model =
= Routes.Motivation ->
= Element.none
=
+ Routes.TestRun ->
+ Element.none
+
= Routes.Content _ ->
= contentNavigationBar model
=
@@ -464,6 +467,12 @@ loadContent route =
= , expect = Http.expectString ContentFetched
= }
=
+ Routes.TestRun ->
+ Http.get
+ { url = "/content/test-run.txt"
+ , expect = Http.expectString ContentFetched
+ }
+
= Routes.Content base ->
= Http.get
= { url = "/content/" ++ base ++ ".txt"index 29893c6..8dbac32 100644
--- a/src/Routes.elm
+++ b/src/Routes.elm
@@ -12,6 +12,7 @@ type Route
= = Home
= | Content String
= | Motivation
+ | TestRun
= | NotFound
=
=
@@ -20,6 +21,7 @@ parser =
= Parser.oneOf
= [ Parser.map Home Parser.top
= , Parser.map Motivation (Parser.s "motivation.html")
+ , Parser.map TestRun (Parser.s "test-run.html")
= , Parser.custom "Content"
= (\path ->
= case String.split "." path ofMerge branch 'pricing' into 'master'
Create a test run page about the UU4U collaboration
See merge request software-garden/software-garden.gitlab.io!22
Reedit test run page
index a0c1120..00ea641 100644
--- a/content/test-run.txt
+++ b/content/test-run.txt
@@ -1,10 +1,10 @@
=| Title
= Special Offer for University of Utrecht Students
=
-In collaboration with {Link|Career Services|url=https://students.uu.nl/en/careerservices} of the University of Utrecht we are offering a one time, free workshop for first 12 students to sign up. The workshop will be shorter than usual (3 hours a day) because you are so smart 🤓
+In collaboration with {Link|Career Services|url=https://students.uu.nl/en/careerservices} of the University of Utrecht we are offering a one time, free workshop for the first 12 students to sign up. The workshop will be shorter than usual - 3 hours a day.
=
=| Emphasize
- {Link|Sign up!|url=https://fd21.formdesk.com/universiteitutrecht/CS-20190304-08-SoftwareGarden}
+ {Link|Sign up now|url=https://fd21.formdesk.com/universiteitutrecht/CS-20190304-08-SoftwareGarden}
=
= <>
=Merge branch 'pricing' into 'master'
Reedit test run page
See merge request software-garden/software-garden.gitlab.io!23
Commits: 7
Add shapes block to day 1, day 2 and day 3.
index b8bce0e..6b759b8 100644
--- a/content/day-1.txt
+++ b/content/day-1.txt
@@ -123,10 +123,12 @@ We want to display a dot at the center of the screen, like this:
=
=
= | Dot
- cx = 0
- cy = 0
= color = skyblue
= radius = 10
+ transformations =
+ | Translate
+ x = 0
+ y = 0
=
=Below is the complete code to draw a dot like this, but don't type it in your editor yet! We are going to recreate it together, step by step.
=
@@ -221,9 +223,11 @@ Reload the browser. You should see something like this:
=
= | Dot
= radius = 10
- cx = 0
- cy = 0
= color = black
+ transformations =
+ | Translate
+ x = 0
+ y = 0
=
=At this point we have a dot on the screen - it's at the top left corner of the viewport (the area of the browser window where the content is displayed). We can see only a quarter of it - the rest is outside of the viewport.
=
@@ -260,9 +264,11 @@ Reloading the browser should reveal something like this:
=
= | Dot
= radius = 10
- cx = 0
- cy = 0
= color = black
+ transformations =
+ | Translate
+ x = 0
+ y = 0
=
=Since the dot is inside the {Code|svg} element, and the {Code|svg} element doesn't cover the center of the screen, it's impossible to place the dot at the center. Before anything else, let's make the {Code|svg} fill entire {Definition|term=viewport|definiens=the area of the browser window where the content is displayed}.
=
@@ -323,9 +329,11 @@ Reload the browser. The SVG space now fills the entire viewport. Even the small
=
= | Dot
= radius = 10
- cx = 0
- cy = 0
= color = black
+ transformations =
+ | Translate
+ x = 0
+ y = 0
=
=It should now be possible to place our dot in the center of the screen. Currently the dot is in the top left corner of the viewport, but at least the parent {Code|svg} element is covering it, so the center of the viewport is somewher within the {Code|svg} element.
=
@@ -400,9 +408,11 @@ To change the position of the dot, we can use the {Code|cx} and {Code|cy} attrib
=
= | Dot
= radius = 10
- cx = 100
- cy = 50
= color = black
+ transformations =
+ | Translate
+ x = 100
+ y = 50
=
=| Note
= The {Code|cx} stands for /center x/ and {Code|cy} for /center y/, where center refers to the circle. We will discuss what the center of the circle is in more exact terms on day 2, but you probably get the concept.
@@ -505,9 +515,11 @@ There are only two changes here. First we reset the {Code|cx} and {Code|cy} attr
=
= | Dot
= radius = 10
- cx = 0
- cy = 0
= color = black
+ transformations =
+ | Translate
+ x = 0
+ y = 0
=
=You can also remove line 19 to remove the pink background, or set it to some other color.
=
@@ -571,9 +583,11 @@ The only change is on line 13. Reload the browser and see this:
=
= | Dot
= radius = 10
- cx = 0
- cy = 0
= color = skyblue
+ transformations =
+ | Translate
+ x = 0
+ y = 0
=
=| Emphasize
= Wow!index 5a1bac3..afa080b 100644
--- a/content/day-2.txt
+++ b/content/day-2.txt
@@ -101,15 +101,19 @@ Now the list spans from lines 9 to 23 and contains two items: skyblue and orange
=
= | Dot
= radius = 10
- cx = 0
- cy = 0
= color = skyblue
+ transformations =
+ | Translate
+ x = 0
+ y = 0
=
= | Dot
= radius = 10
- cx = 0
- cy = 0
= color = orange
+ transformations =
+ | Translate
+ x = 0
+ y = 0
=
=Oh oh! The list contains two dots, but we can only see the orange one on the screen. Where is the blue one? It's behind the orange. Recall how the cartesian coordinates work: if {Code|x} and {Code|y} of the center are the same, then the dots are in the same place. If we want to see both dots, let's move one of them to a different place, like this:
=
@@ -175,15 +179,19 @@ Now you should see this:
=
= | Dot
= radius = 10
- cx = 0
- cy = 0
= color = skyblue
+ transformations =
+ | Translate
+ x = 0
+ y = 0
=
= | Dot
= radius = 10
- cx = 50
- cy = 0
= color = orange
+ transformations =
+ | Translate
+ x = 50
+ y = 0
=
=We can add more dots, by simply multiplying the code blocks inside the list. Each block must be separated from others with a {Code|,} (comma). Each time the {Code|cx} and {Code|cy} coordinates need to be different. We can also give dots different colors. Make five of them.
=
@@ -263,9 +271,11 @@ The {Code|r} attribute stands for radius. Then it was basically dictating the si
=
= | Dot
= radius = 40
- cx = 0
- cy = 0
= color = skyblue
+ transformations =
+ | Translate
+ x = 0
+ y = 0
=
= | Shapes
= | Container
@@ -275,9 +285,11 @@ The {Code|r} attribute stands for radius. Then it was basically dictating the si
=
= | Dot
= radius = 60
- cx = 0
- cy = 0
= color = skyblue
+ transformations =
+ | Translate
+ x = 0
+ y = 0
=
= | Shapes
= | Container
@@ -287,9 +299,11 @@ The {Code|r} attribute stands for radius. Then it was basically dictating the si
=
= | Dot
= radius = 80
- cx = 0
- cy = 0
= color = skyblue
+ transformations =
+ | Translate
+ x = 0
+ y = 0
=
=This time the radius will affect the size of an imaginary circle on which the dots are laying.
=index 0046d76..f39b2ad 100644
--- a/content/day-3.txt
+++ b/content/day-3.txt
@@ -1057,95 +1057,88 @@ You should see something like this in the browser:
= fill = True
= viewBox = -100 -100 200 200
=
- | Group
- x = 0
- y = 0
- rotation = 0
- children =
- | Line
- x1 = 0
- y1 = 0
- x2 = 80
- y2 = 0
- color = skyblue
-
- | Dot
- cx = 80
- cy = 0
- radius = 10
- color = skyblue
-
- | Group
- x = 0
- y = 0
- rotation = 72
- children =
- | Line
- x1 = 0
- y1 = 0
- x2 = 80
- y2 = 0
- color = orange
-
- | Dot
- cx = 80
- cy = 0
- radius = 10
- color = orange
-
- | Group
- x = 0
- y = 0
- rotation = 144
- children =
- | Line
- x1 = 0
- y1 = 0
- x2 = 80
- y2 = 0
- color = red
-
- | Dot
- cx = 80
- cy = 0
- radius = 10
- color = red
-
- | Group
- x = 0
- y = 0
- rotation = 216
- children =
- | Line
- x1 = 0
- y1 = 0
- x2 = 80
- y2 = 0
- color = lime
-
- | Dot
- cx = 80
- cy = 0
- radius = 10
- color = lime
-
- | Group
- x = 0
- y = 0
- rotation = 288
- children =
- | Line
- x1 = 0
- y1 = 0
- x2 = 80
- y2 = 0
- color = maroon
-
- | Dot
- cx = 80
- cy = 0
- radius = 10
- color = maroon
+ | Line
+ length = 80
+ color = skyblue
+ transformations =
+ | Rotate
+ 0
+ | Dot
+ radius = 10
+ color = skyblue
+ transformations =
+ | Translate
+ x = 80
+ y = 0
+
+ | Line
+ length = 80
+ color = orange
+ transformations =
+ | Rotate
+ 72
+ | Dot
+ radius = 10
+ color = orange
+ transformations =
+ | Rotate
+ 72
+ | Translate
+ x = 80
+ y = 0
+
+ | Line
+ length = 80
+ color = red
+ transformations =
+ | Rotate
+ 144
+
+ | Dot
+ radius = 10
+ color = red
+ transformations =
+ | Rotate
+ 144
+ | Translate
+ x = 80
+ y = 0
+
+ | Line
+ length = 80
+ color = lime
+ transformations =
+ | Rotate
+ 216
+
+ | Dot
+ radius = 10
+ color = lime
+ transformations =
+ | Rotate
+ 216
+ | Translate
+ x = 80
+ y = 0
+
+
+ | Line
+ length = 80
+ color = maroon
+ transformations =
+ | Rotate
+ 288
+
+ | Dot
+ radius = 10
+ color = maroon
+ transformations =
+ | Rotate
+ 288
+ | Translate
+ x = 80
+ y = 0
+
=
=| Emphasize
= Congratulations!Merge branch 'shapes-example' of gitlab.com:software-garden/software-garden.gitlab.io into shapes-example
Merge remote-tracking branch 'origin/master' into shapes-example
Merge branch 'growing-tree' into 'master'
Add a growing option to the tree example.
See merge request software-garden/software-garden.gitlab.io!21
Merge remote-tracking branch 'origin/master' into shapes-example
Add shapes code block.
Changed code monospace to code window.
index 50a54e8..5b8499f 100644
--- a/content/day-4.txt
+++ b/content/day-4.txt
@@ -2645,7 +2645,7 @@ We also have to change the definition of {Code|main}. Segment no longer takes tw
=
=The result should be exactly like we wanted it to be:
=
-| Monospace
+| Window
= | Tree
= | Axiom
= color = brownMerge branch 'shapes-example' into 'master'
Shapes example
See merge request software-garden/software-garden.gitlab.io!18
Commits: 12
Add a little dot at the end of the progress bar
index 3f0ebba..4051617 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -613,23 +613,41 @@ contentNavigationBar { location, scroll, viewport } =
= progressBar total done progress =
= Element.row [ Element.width Element.fill ]
= [ Element.el
+ -- Previous days
= [ Element.width (Element.fillPortion done)
= , Element.height (Element.px 4)
= , Background.color Mark.Custom.colors.green
= ]
= Element.none
= , Element.row
+ -- Current day
= [ Element.width (Element.fillPortion 1)
= , Element.height (Element.px 4)
= ]
= [ Element.el
+ -- Done
= [ Element.width
= (Element.fillPortion (round (progress * 1000)))
= , Element.height (Element.px 4)
= , Background.color Mark.Custom.colors.green
+ , Element.onRight
+ (Element.el
+ -- A dot at the end of the progress bar
+ [ Element.width (Element.px 10)
+ , Element.height (Element.px 10)
+ , Element.moveUp 3
+ , Border.rounded 10
+ , Border.color Mark.Custom.colors.green
+ , Border.width 3
+ , Element.moveLeft 5
+ , Background.color Mark.Custom.colors.white
+ ]
+ Element.none
+ )
= ]
= Element.none
= , Element.el
+ -- Ahead
= [ Element.width
= (Element.fillPortion (1000 - round (progress * 1000)))
= , Element.height (Element.px 4)
@@ -637,6 +655,7 @@ contentNavigationBar { location, scroll, viewport } =
= Element.none
= ]
= , Element.el
+ -- Days ahead
= [ Element.width (Element.fillPortion (total - done - 1))
= , Element.height (Element.px 4)
= ]Create Motivation page
index 732e725..79e54bd 100644
--- a/content/index.txt
+++ b/content/index.txt
@@ -13,7 +13,7 @@ Hello! We are running a workshop that will give you a glimpse into the way softw
=
= or drop us a note at {Link|fana@software.garden|url= mailto:fana@software.garden}
=
-Below is material through which we will be going during the workshop. Only the first section is a required read before you come, but feel free to read more to get more prepared.
+You can learn more about {Link|our motivation|url=/motivation.html}. Below is material through which we will be going during the workshop. Only the first section is a required read before you come, but feel free to read more to get more prepared.
=
=| Link
= url = /preparation.htmlnew file mode 100644
index 0000000..d982bad
--- /dev/null
+++ b/content/motivation.txt
@@ -0,0 +1,13 @@
+| Title
+ Why are we doing this?
+
+More and more of the world around us is driven by computer programs. Software is in our houses, offices, phones, cars, factories, cities. When you ride a bus, there is software helping the driver stay on track and on time. When you ride the elevator, the software controls it. One day soon perhaps software will control the bus without the driver, just like it already controls the elevator without a human operator.
+
+Computer programs are moving things around us, they talk and listen to us and to other programs. Perhaps it all seems like magic to you. It should! It is magical. We are creating living things by uttering words in an arcane languages. At Software Garden we want to show you how it's done.
+
+We won't make you a professional software developer - that takes months to years of education and experience. But you don't have to be a professional novelist to read and write. Perhaps you work with IT professionals or considering becoming one. Perhaps you want to help your kids to learn programming. Maybe you just want to learn something about the world. Come, sit with us and see the world through the eyes of 21st century wizards (a friendly kind).
+
+| Emphasize
+ You can ask Fana about the available dates!
+
+ {Icon|name=phone} {Link|+31 638 216 166|url=tel:+31638216166} | {Icon|name=mail} {Link|fana@software.garden|url= mailto:fana@software.garden} | {Icon|name=home} {Link|Back|url=/}index 3f0ebba..18bcc2e 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -185,6 +185,9 @@ view model =
= -- homeNavigationBar
= Element.none
=
+ Routes.Motivation ->
+ Element.none
+
= Routes.Content _ ->
= contentNavigationBar model
=
@@ -455,6 +458,12 @@ loadContent route =
= , expect = Http.expectString ContentFetched
= }
=
+ Routes.Motivation ->
+ Http.get
+ { url = "/content/motivation.txt"
+ , expect = Http.expectString ContentFetched
+ }
+
= Routes.Content base ->
= Http.get
= { url = "/content/" ++ base ++ ".txt"index f76545a..29893c6 100644
--- a/src/Routes.elm
+++ b/src/Routes.elm
@@ -11,6 +11,7 @@ import Url.Parser as Parser exposing (..)
=type Route
= = Home
= | Content String
+ | Motivation
= | NotFound
=
=
@@ -18,6 +19,7 @@ parser : Parser (Route -> b) b
=parser =
= Parser.oneOf
= [ Parser.map Home Parser.top
+ , Parser.map Motivation (Parser.s "motivation.html")
= , Parser.custom "Content"
= (\path ->
= case String.split "." path ofMerge branch 'progress-bar-dot' into 'master'
Progress bar dot
See merge request software-garden/software-garden.gitlab.io!19
Use shapes block on day 4.
index 6281453..9928187 100644
--- a/content/day-4.txt
+++ b/content/day-4.txt
@@ -23,39 +23,36 @@ We want to have a tree that looks like this:
=| Window
= | Tree
= | Axiom
- color = green
+ color = olive
= rotation = -90
= age = 8
=
=
= | Rule
- parent = green
+ parent = olive
=
= children =
= | Child
- color = brown
- rotation = 0.0
+ color = olive
+ rotation = 5.0
=
= | Child
- color = brown
- rotation = 30
+ color = maroon
+ rotation = 40
=
= | Child
- color = brown
- rotation = -30
+ color = maroon
+ rotation = -40
=
= | Rule
- parent = brown
+ parent = maroon
= children =
= | Child
- color = green
- rotation = 0.0
- | Child
- color = green
- rotation = 30
+ color = olive
+ rotation = 20
= | Child
- color = green
- rotation = -30
+ color = olive
+ rotation = -20
=
=
=The tree is built from segments: a line and a dot. In this respect it is similar to the connected dots we created yesterday. If we group the dot and a line, we will have the building block for our tree. We can do it with {Code|Svg.g} function ({Code|g} is an abbreviation of "group"). Just like {Code|Svg.svg}, it takes list of attributes and list of children. We can use it like that:
@@ -65,10 +62,20 @@ The tree is built from segments: a line and a dot. In this respect it is similar
= | None
=
= | Fold
- start = 23
- length = 13
+ start = 1
+ length = 6
+
+ | Fold
+ start = 29
+ length = 48
=
= | Code
+ module Main exposing (main)
+
+ import Element
+ import Svg
+ import Svg.Attributes
+
= main =
= [ Svg.g []
= [ dot "skyblue" 0
@@ -103,6 +110,41 @@ The tree is built from segments: a line and a dot. In this respect it is similar
= , Element.height Element.fill
= ]
=
+ dot color rotation =
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ []
+
+
+ line color rotation =
+ Svg.line
+ [ Svg.Attributes.strokeWidth "1"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x1 "80"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.stroke color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ")"
+ ]
+ )
+ ]
+ []
+
=Reload the browser there should be no difference at this point. Looking back at code I notice that each segment looks the same: it is a group with a dot and a line, where the {Code|group} and {Code|line} functions have the same arguments passed to them. This kind of repetition begs for a function. Let's call it {Code|segment} and define like this:
=
=| Editor
@@ -120,13 +162,33 @@ Type this code at the bottom of the file and then replace main with the followin
=
=| Editor
= | Annotations
- | None
+ | Highlight
+ top = 61
+ left = 1
+ width = 29
+ height = 5
+
+ | Highlight
+ top = 8
+ left = 4
+ width = 22
+ height = 6
=
= | Fold
- start = 8
- length = 13
+ start = 1
+ length = 6
+
+ | Fold
+ start = 26
+ length = 34
=
= | Code
+ module Main exposing (main)
+
+ import Element
+ import Svg
+ import Svg.Attributes
+
= main =
= [ segment "skyblue" 0
= , segment "orange" 72
@@ -146,15 +208,53 @@ Type this code at the bottom of the file and then replace main with the followin
= , Element.height Element.fill
= ]
=
+ dot color rotation =
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ []
+
+
+ line color rotation =
+ Svg.line
+ [ Svg.Attributes.strokeWidth "1"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x1 "80"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.stroke color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ")"
+ ]
+ )
+ ]
+ []
+
+ segment color rotation =
+ Svg.g []
+ [ dot color rotation
+ , line color rotation
+ ]
+
=| Note
= I hope you can see the pattern in what we are doing. We are taking repetitive blocks of code and turning them into named functions. Parameters help us deal with variability in the repeated code (like color and rotation that is different for each segment).
=
=Reload the browser. There should still be no visible difference in the behavior of the program, but our source code is getting more readable. That's good.
=
-The big difference between our program and the one we are trying to build is that ours have only one level of segments, whereas in the example segments grow from the tip of other segments. You can think of it as segments having child segments: the green segment has two child brown segments. Each brown segment has three red child segments
-
-| Note
- TODO: Make sure the description matches the example
+The big difference between our program and the one we are trying to build is that ours have only one level of segments, whereas in the example segments grow from the tip of other segments. You can think of it as segments having child segments: the green segment has two child red segments and one green segment. Each red segment has two green child segments.
=
=It's similar to how the way an SVG group has child elements. But group itself (together with its children) is an element, so we can put a group within a group. Let's do that! Change the definition of {Code|segment} function as follows:
=
@@ -162,31 +262,442 @@ It's similar to how the way an SVG group has child elements. But group itself (t
= | Annotations
= | None
=
+ | Fold
+ start = 1
+ length = 6
+
+ | Fold
+ start = 26
+ length = 35
+
= | Code
- segment color rotation =
- Svg.g []
- [ dot color rotation
- , line color rotation
- , Svg.g
- [ Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ") translate(80)"
- ]
- )
+ module Main exposing (main)
+
+ import Element
+ import Svg
+ import Svg.Attributes
+
+ main =
+ [ segment "skyblue" 0
+ , segment "orange" 72
+ , segment "red" 144
+ , segment "lime" 216
+ , segment "maroon" 288
+ ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: none"
+ , Svg.Attributes.viewBox "-100 -100 200 200"
= ]
- [ dot "red" 15
- , line "red" 15
- , dot "blue" -15
- , line "blue" -15
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
= ]
+
+ dot color rotation =
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ []
+
+
+ line color rotation =
+ Svg.line
+ [ Svg.Attributes.strokeWidth "1"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x1 "80"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.stroke color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ")"
+ ]
+ )
+ ]
+ []
+
+ segment color rotation =
+ Svg.g []
+ [ dot color rotation
+ , line color rotation
+ , Svg.g
+ [ Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
= ]
+ [ dot "red" 15
+ , line "red" 15
+ , dot "blue" -15
+ , line "blue" -15
+ ]
+ ]
=
=Now the program should display something like this:
=
-| Note
- TODO: Example
+| Window
+ | Shapes
+ | Container
+ background = none
+ fill = True
+ viewBox = -100 -100 200 200
+
+ | Line
+ length= 80
+ color = skyblue
+ transformations =
+ | Rotate
+ 0
+
+ | Dot
+ radius = 10
+ color = skyblue
+ transformations =
+ | Translate
+ x = 80
+ y = 0
+ | Line
+ length= 80
+ color = red
+ transformations =
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 15
+
+ | Dot
+ radius = 10
+ color = red
+ transformations =
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 15
+ | Translate
+ x = 80
+ y = 0
+ | Line
+ length= 80
+ color = blue
+ transformations =
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -15
+
+ | Dot
+ radius = 10
+ color = blue
+ transformations =
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -15
+ | Translate
+ x = 80
+ y = 0
+ | Line
+ length= 80
+ color = orange
+ transformations =
+ | Rotate
+ 72
+
+ | Dot
+ radius = 10
+ color = orange
+ transformations =
+ | Rotate
+ 72
+ | Translate
+ x = 80
+ y = 0
+
+ | Line
+ length= 80
+ color = red
+ transformations =
+ | Rotate
+ 72
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 15
+
+
+ | Dot
+ radius = 10
+ color = red
+ transformations =
+ | Rotate
+ 72
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 15
+ | Translate
+ x = 80
+ y = 0
+ | Line
+ length= 80
+ color = blue
+ transformations =
+ | Rotate
+ 72
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -15
+
+ | Dot
+ radius = 10
+ color = blue
+ transformations =
+ | Rotate
+ 72
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -15
+ | Translate
+ x = 80
+ y = 0
+ | Line
+ length= 80
+ color = red
+ transformations =
+ | Rotate
+ 144
+
+ | Dot
+ radius = 10
+ color = red
+ transformations =
+ | Rotate
+ 144
+ | Translate
+ x = 80
+ y = 0
+
+ | Line
+ length= 80
+ color = red
+ transformations =
+ | Rotate
+ 144
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 15
+
+ | Dot
+ radius = 10
+ color = red
+ transformations =
+ | Rotate
+ 144
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 15
+ | Translate
+ x = 80
+ y = 0
+
+ | Line
+ length= 80
+ color = blue
+ transformations =
+ | Rotate
+ 144
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -15
+
+ | Dot
+ radius = 10
+ color = blue
+ transformations =
+ | Rotate
+ 144
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -15
+ | Translate
+ x = 80
+ y = 0
+
+ | Line
+ length= 80
+ color = lime
+ transformations =
+ | Rotate
+ 216
+
+ | Dot
+ radius = 10
+ color = lime
+ transformations =
+ | Rotate
+ 216
+ | Translate
+ x = 80
+ y = 0
+ | Line
+ length= 80
+ color = red
+ transformations =
+ | Rotate
+ 216
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 15
+
+ | Dot
+ radius = 10
+ color = red
+ transformations =
+ | Rotate
+ 216
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 15
+ | Translate
+ x = 80
+ y = 0
+
+ | Line
+ length= 80
+ color = blue
+ transformations =
+ | Rotate
+ 216
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -15
+
+ | Dot
+ radius = 10
+ color = blue
+ transformations =
+ | Rotate
+ 216
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -15
+ | Translate
+ x = 80
+ y = 0
+ | Line
+ length= 80
+ color = maroon
+ transformations =
+ | Rotate
+ 288
+
+ | Dot
+ radius = 10
+ color = maroon
+ transformations =
+ | Rotate
+ 288
+ | Translate
+ x = 80
+ y = 0
+
+ | Line
+ length= 80
+ color = red
+ transformations =
+ | Rotate
+ 288
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 15
+
+ | Dot
+ radius = 10
+ color = red
+ transformations =
+ | Rotate
+ 288
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 15
+ | Translate
+ x = 80
+ y = 0
+
+ | Line
+ length= 80
+ color = blue
+ transformations =
+ | Rotate
+ 288
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -15
+
+ | Dot
+ radius = 10
+ color = blue
+ transformations =
+ | Rotate
+ 288
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -15
+ | Translate
+ x = 80
+ y = 0
=
=It's already starts to look interesting, but there are two problems with it. First it doesn't fit on screen. This is easy to fix using {Code|viewBox}. We need to zoom out to see more of a picture. Change the value passed to {Code|viewBox} on line 19 to {Code|"-500 -500 1000 1000"}, effectively zooming out 5x but preserving keeping the origin in the center of our viewbox.
=
@@ -194,9 +705,88 @@ Then the lines look ugly displayed on top of the dots of different color. This i
=
=| Editor
= | Annotations
- | None
+ | Highlight
+ top = 18
+ left = 12
+ width = 46
+ height = 1
+
+ | Highlight
+ top = 73
+ left = 10
+ width = 21
+ height = 8
+
+ | Fold
+ start = 1
+ length = 6
+
+ | Fold
+ start = 20
+ length = 43
=
= | Code
+ module Main exposing (main)
+
+ import Element
+ import Svg
+ import Svg.Attributes
+
+ main =
+ [ segment "skyblue" 0
+ , segment "orange" 72
+ , segment "red" 144
+ , segment "lime" 216
+ , segment "maroon" 288
+ ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: none"
+ , Svg.Attributes.viewBox "-500 -500 1000 1000"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+ dot color rotation =
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ []
+
+
+ line color rotation =
+ Svg.line
+ [ Svg.Attributes.strokeWidth "1"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x1 "80"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.stroke color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ")"
+ ]
+ )
+ ]
+ []
+
+
= segment color rotation =
= Svg.g []
= [ Svg.g
@@ -219,30 +809,373 @@ Then the lines look ugly displayed on top of the dots of different color. This i
=
=With this two changes applied you should see something like this in the browser:
=
-| Note
- TODO: Example
+| Window
+ | Shapes
+ | Container
+ background = none
+ fill = True
+ viewBox = -500 -500 1000 1000
+
+ | Line
+ length = 80
+ color = skyblue
+ transformations =
+ | Rotate
+ 0
+
+ | Line
+ length = 80
+ color = red
+ transformations =
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 15
+
+ | Dot
+ radius = 10
+ color = red
+ transformations =
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 15
+ | Translate
+ x = 80
+ y = 0
+ | Line
+ length = 80
+ color = blue
+ transformations =
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -15
+ | Dot
+ radius = 10
+ color = skyblue
+ transformations =
+ | Translate
+ x = 80
+ y = 0
+ | Dot
+ radius = 10
+ color = blue
+ transformations =
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -15
+ | Translate
+ x = 80
+ y = 0
+ | Line
+ length = 80
+ color = orange
+ transformations =
+ | Rotate
+ 72
+
+ | Line
+ length = 80
+ color = red
+ transformations =
+ | Rotate
+ 72
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 15
+
+
+ | Dot
+ radius = 10
+ color = red
+ transformations =
+ | Rotate
+ 72
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 15
+ | Translate
+ x = 80
+ y = 0
+ | Line
+ length = 80
+ color = blue
+ transformations =
+ | Rotate
+ 72
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -15
=
-So now each segment has two sub segments: red and blue. The red one is rotate 15 degree clockwise, the blue one is rotated 15 degree counterclockwise. But they are look the same. Our goal is to make them variable. This means that we need one more parameter to the segment: list of children. Let's do it:
+ | Dot
+ radius = 10
+ color = blue
+ transformations =
+ | Rotate
+ 72
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -15
+ | Translate
+ x = 80
+ y = 0
+ | Dot
+ radius = 10
+ color = orange
+ transformations =
+ | Rotate
+ 72
+ | Translate
+ x = 80
+ y = 0
+ | Line
+ length = 80
+ color = red
+ transformations =
+ | Rotate
+ 144
+
+
+ | Line
+ length = 80
+ color = red
+ transformations =
+ | Rotate
+ 144
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 15
+
+ | Dot
+ radius = 10
+ color = red
+ transformations =
+ | Rotate
+ 144
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 15
+ | Translate
+ x = 80
+ y = 0
+
+ | Line
+ length = 80
+ color = blue
+ transformations =
+ | Rotate
+ 144
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -15
+
+ | Dot
+ radius = 10
+ color = blue
+ transformations =
+ | Rotate
+ 144
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -15
+ | Translate
+ x = 80
+ y = 0
+
+ | Dot
+ radius = 10
+ color = red
+ transformations =
+ | Rotate
+ 144
+ | Translate
+ x = 80
+ y = 0
+ | Line
+ length = 80
+ color = lime
+ transformations =
+ | Rotate
+ 216
+
+ | Line
+ length = 80
+ color = red
+ transformations =
+ | Rotate
+ 216
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 15
+
+ | Dot
+ radius = 10
+ color = red
+ transformations =
+ | Rotate
+ 216
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 15
+ | Translate
+ x = 80
+ y = 0
+
+ | Line
+ length = 80
+ color = blue
+ transformations =
+ | Rotate
+ 216
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -15
+
+ | Dot
+ radius = 10
+ color = blue
+ transformations =
+ | Rotate
+ 216
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -15
+ | Translate
+ x = 80
+ y = 0
+
+ | Dot
+ radius = 10
+ color = lime
+ transformations =
+ | Rotate
+ 216
+ | Translate
+ x = 80
+ y = 0
+
+ | Line
+ length = 80
+ color = maroon
+ transformations =
+ | Rotate
+ 288
+
+ | Line
+ length = 80
+ color = red
+ transformations =
+ | Rotate
+ 288
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 15
+
+ | Dot
+ radius = 10
+ color = red
+ transformations =
+ | Rotate
+ 288
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 15
+ | Translate
+ x = 80
+ y = 0
+
+ | Line
+ length = 80
+ color = blue
+ transformations =
+ | Rotate
+ 288
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -15
+
+ | Dot
+ radius = 10
+ color = blue
+ transformations =
+ | Rotate
+ 288
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -15
+ | Translate
+ x = 80
+ y = 0
+
+ | Dot
+ radius = 10
+ color = maroon
+ transformations =
+ | Rotate
+ 288
+ | Translate
+ x = 80
+ y = 0
+
+So now each segment has two sub segments: red and blue. The red one is rotated 15 degree clockwise, the blue one is rotated 15 degree counterclockwise. But they look the same. Our goal is to make them variable. This means that we need one more parameter to the segment: list of children. Let's do it:
=
=| Editor
= | Annotations
= | Highlight
- top = 25
- left = 14
- width = 44
- height = 1
+ top = 11
+ left = 8
+ width = 17
+ height = 5
=
= | Fold
= start = 1
- length = 7
+ length = 6
=
= | Fold
- start = 27
- length = 7
+ start = 21
+ length = 13
=
= | Fold
= start = 50
- length = 38
+ length = 39
=
= | Highlight
= top = 35
@@ -344,12 +1277,140 @@ So now each segment has two sub segments: red and blue. The red one is rotate 15
= []
=
=
-We have added third parameter to {Code|segment} function on line 34 (before the changes it was 28). The parameter is named {Code|children} and we use it in place of the list on line 45 (previously 39). The list itself was moved to line 11, where it is passed as a value for the parameter of the first segment. This move made all the lines shift down. All other segments will also need to get value for {Code|children} parameter, so let's just give each of them an empty list - meaning they have 0 children.
+We have added third parameter to {Code|segment} function on line 35 (before the changes it was 28). The parameter is named {Code|children} and we use it in place of the list on line 46 (previously 39). The list itself was moved to line 11, where it is passed as a value for the parameter of the first segment. This move made all the lines shift down. All other segments will also need to get value for {Code|children} parameter, so let's just give each of them an empty list - meaning they have 0 children.
=
=Once all changes are applied, reload the browser and see that only first, skyblue segment has child segments.
=
-| Note
- TODO: Example
+| Window
+ | Shapes
+ | Container
+ background = none
+ fill = True
+ viewBox = -500 -500 1000 1000
+
+ | Line
+ length = 80
+ color = skyblue
+ transformations =
+ | Rotate
+ 0
+ | Line
+ length = 80
+ color = red
+ transformations =
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 15
+
+ | Dot
+ radius = 10
+ color = red
+ transformations =
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 15
+ | Translate
+ x = 80
+ y = 0
+ | Line
+ length = 80
+ color = blue
+ transformations =
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -15
+ | Dot
+ radius = 10
+ color = skyblue
+ transformations =
+ | Translate
+ x = 80
+ y = 0
+ | Dot
+ radius = 10
+ color = blue
+ transformations =
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -15
+ | Translate
+ x = 80
+ y = 0
+ | Line
+ length = 80
+ color = orange
+ transformations =
+ | Rotate
+ 72
+
+ | Dot
+ radius = 10
+ color = orange
+ transformations =
+ | Rotate
+ 72
+ | Translate
+ x = 80
+ y = 0
+ | Line
+ length = 80
+ color = red
+ transformations =
+ | Rotate
+ 144
+
+
+ | Dot
+ radius = 10
+ color = red
+ transformations =
+ | Rotate
+ 144
+ | Translate
+ x = 80
+ y = 0
+ | Line
+ length = 80
+ color = lime
+ transformations =
+ | Rotate
+ 216
+
+ | Dot
+ radius = 10
+ color = lime
+ transformations =
+ | Rotate
+ 216
+ | Translate
+ x = 80
+ y = 0
+
+ | Line
+ length = 80
+ color = maroon
+ transformations =
+ | Rotate
+ 288
+
+ | Dot
+ radius = 10
+ color = maroon
+ transformations =
+ | Rotate
+ 288
+ | Translate
+ x = 80
+ y = 0
+
=
=Before we continue, let's notice that on lines 11 - 15 we have a nice opportunity to remove some repetition. Consider that dot and a line with same rotation and color are just a segment. We can replace:
=
@@ -357,33 +1418,205 @@ Before we continue, let's notice that on lines 11 - 15 we have a nice opportunit
= | Annotations
= | None
=
+ | Fold
+ start = 1
+ length = 10
+
+ | Fold
+ start = 16
+ length = 72
+
= | Code
- [ dot "red" 15
- , line "red" 15
- , dot "blue" -15
- , line "blue" -15
- ]
+ module Main exposing (main)
+
+ import Element
+ import Svg exposing (Svg)
+ import Svg.Attributes
+
+
+ main =
+ [ segment "skyblue"
+ 0
+ [ dot "red" 15
+ , line "red" 15
+ , dot "blue" -15
+ , line "blue" -15
+ ]
+ , segment "orange" 72 []
+ , segment "red" 144 []
+ , segment "lime" 216 []
+ , segment "maroon" 288 []
+ ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: none"
+ , Svg.Attributes.viewBox "-500 -500 1000 1000"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+
+ segment : String -> Float -> List (Svg msg) -> Svg.Svg msg
+ segment color rotation children =
+ Svg.g []
+ [ Svg.g
+ [ Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ children
+ , dot color rotation
+ , line color rotation
+ ]
+
+
+ dot color rotation =
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ []
=
-with:
=
+ line color rotation =
+ Svg.line
+ [ Svg.Attributes.strokeWidth "1"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x1 "80"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.stroke color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ")"
+ ]
+ )
+ ]
+ []
+
+with:
=| Editor
= | Annotations
= | None
=
- | Code
- [ segment "red" 15 []
- , segment "blue" -15 []
- ]
+ | Fold
+ start = 1
+ length = 10
+
+ | Fold
+ start = 14
+ length = 73
+
+ | Code
+ module Main exposing (main)
+
+ import Element
+ import Svg exposing (Svg)
+ import Svg.Attributes
+
+
+ main =
+ [ segment "skyblue"
+ 0
+ [ segment "red" 15 []
+ , segment "blue" -15 []
+ ]
+ , segment "orange" 72 []
+ , segment "red" 144 []
+ , segment "lime" 216 []
+ , segment "maroon" 288 []
+ ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: none"
+ , Svg.Attributes.viewBox "-500 -500 1000 1000"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+
+ segment : String -> Float -> List (Svg msg) -> Svg.Svg msg
+ segment color rotation children =
+ Svg.g []
+ [ Svg.g
+ [ Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ children
+ , dot color rotation
+ , line color rotation
+ ]
+
+
+ dot color rotation =
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ []
+
+
+ line color rotation =
+ Svg.line
+ [ Svg.Attributes.strokeWidth "1"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x1 "80"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.stroke color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ")"
+ ]
+ )
+ ]
+ []
+
=
=A segment within a segment! Consider that every segment can have child segments. This way we can build a tree as complex as we want. Try adding more children:
=
=| Editor
= | Annotations
- | Highlight
- top = 28
- left = 12
- width = 50
- height = 1
+ | None
=
= | Fold
= start = 1
@@ -515,8 +1748,440 @@ Let's say we have three types of segments: brown, green and red. The child segme
=
=The tree following these rules will look like this:
=
-| Note
- TODO: Example
+| Window
+ | Shapes
+ | Container
+ background = none
+ fill = True
+ viewBox = -500 -500 1000 1000
+
+ | Line
+ length = 80
+ color = brown
+ transformations =
+ | Rotate
+ -90
+
+ | Line
+ length = 80
+ color = green
+ transformations =
+ | Rotate
+ -90
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 0
+
+ | Line
+ length = 80
+ color = green
+ transformations =
+ | Rotate
+ -90
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 20
+
+ | Line
+ length = 80
+ color = green
+ transformations =
+ | Rotate
+ -90
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -30
+
+ | Dot
+ radius = 10
+ color = brown
+ transformations =
+ | Rotate
+ -90
+ | Translate
+ x = 80
+ y = 0
+
+ | Line
+ length = 80
+ color = red
+ transformations =
+ | Rotate
+ -90
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 0
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -45
+
+ | Dot
+ radius = 10
+ color = red
+ transformations =
+ | Rotate
+ -90
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 0
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -45
+ | Translate
+ x = 80
+ y = 0
+
+ | Line
+ length = 80
+ color = red
+ transformations =
+ | Rotate
+ -90
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 0
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -5
+
+ | Dot
+ radius = 10
+ color = red
+ transformations =
+ | Rotate
+ -90
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 0
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -5
+ | Translate
+ x = 80
+ y = 0
+
+
+ | Line
+ length = 80
+ color = red
+ transformations =
+ | Rotate
+ -90
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 0
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 50
+
+ | Dot
+ radius = 10
+ color = red
+ transformations =
+ | Rotate
+ -90
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 0
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 50
+ | Translate
+ x = 80
+ y = 0
+
+ | Line
+ length = 80
+ color = red
+ transformations =
+ | Rotate
+ -90
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 20
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -45
+ | Dot
+ radius = 10
+ color = red
+ transformations =
+ | Rotate
+ -90
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 20
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -45
+ | Translate
+ x = 80
+ y = 0
+
+ | Line
+ length = 80
+ color = red
+ transformations =
+ | Rotate
+ -90
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 20
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -5
+ | Dot
+ radius = 10
+ color = red
+ transformations =
+ | Rotate
+ -90
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 20
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -5
+ | Translate
+ x = 80
+ y = 0
+
+ | Line
+ length = 80
+ color = red
+ transformations =
+ | Rotate
+ -90
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 20
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 50
+
+ | Dot
+ radius = 10
+ color = red
+ transformations =
+ | Rotate
+ -90
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 20
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 50
+ | Translate
+ x = 80
+ y = 0
+ | Line
+ length = 80
+ color = red
+ transformations =
+ | Rotate
+ -90
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -30
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -45
+
+ | Dot
+ radius = 10
+ color = red
+ transformations =
+ | Rotate
+ -90
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -30
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -45
+ | Translate
+ x = 80
+ y = 0
+
+ | Line
+ length = 80
+ color = red
+ transformations =
+ | Rotate
+ -90
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -30
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -5
+
+ | Dot
+ radius = 10
+ color = red
+ transformations =
+ | Rotate
+ -90
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -30
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -5
+ | Translate
+ x = 80
+ y = 0
+
+ | Line
+ length = 80
+ color = red
+ transformations =
+ | Rotate
+ -90
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -30
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 50
+
+ | Dot
+ radius = 10
+ color = red
+ transformations =
+ | Rotate
+ -90
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -30
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 50
+ | Translate
+ x = 80
+ y = 0
+
+ | Dot
+ radius = 10
+ color = green
+ transformations =
+ | Rotate
+ -90
+ | Translate
+ x = 80
+ y = 0
+ | Translate
+ x = 80
+ y = 0
+
+ | Dot
+ radius = 10
+ color = green
+ transformations =
+ | Rotate
+ -90
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ 20
+ | Translate
+ x = 80
+ y = 0
+
+ | Dot
+ radius = 10
+ color = green
+ transformations =
+ | Rotate
+ -90
+ | Translate
+ x = 80
+ y = 0
+ | Rotate
+ -30
+ | Translate
+ x = 80
+ y = 0
+
=
=To represent this in code we will need to learn few new concepts: *dictionary*, *record*, *type alias* and *mapping over list*.
=
@@ -643,7 +2308,6 @@ Dictionary let's us associate one value (let's say color) with another value (le
= start = 48
= length = 55
=
-
= | Code
= module Main exposing (main)
=
@@ -760,7 +2424,62 @@ Here it is:
= | Annotations
= | None
=
+ | Fold
+ start = 1
+ length = 47
+
+ | Fold
+ start = 67
+ length = 36
+
= | Code
+ module Main exposing (main)
+
+ import Dict
+ import Element
+ import Svg exposing (Svg)
+ import Svg.Attributes
+
+
+ main =
+ [ segment { color = "skyblue", rotation = 0 }
+ [ segment { color = "red", rotation = 15 } []
+ , segment { color = "blue", rotation = -15 }
+ [ segment { color = "yellow", rotation = 45 } []
+ , segment { color = "pink", rotation = -20 } []
+ , segment { color = "green", rotation = -90 } []
+ ]
+ ]
+ , segment { color = "orange", rotation = 72 } []
+ , segment { color = "red", rotation = 144 } []
+ , segment { color = "lime", rotation = 216 } []
+ , segment { color = "maroon", rotation = 288 } []
+ ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: none"
+ , Svg.Attributes.viewBox "-500 -500 1000 1000"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+ rules =
+ Dict.empty
+ |> Dict.insert "brown"
+ [ { color = "green", rotation = 0 }
+ , { color = "green", rotation = 20 }
+ , { color = "green", rotation = -30 }
+ ]
+ |> Dict.insert "green"
+ [ { color = "red", rotation = -45 }
+ , { color = "red", rotation = -5 }
+ , { color = "red", rotation = 50 }
+ ]
+
= segment { color, rotation } =
= Svg.g []
= [ rules
@@ -780,13 +2499,67 @@ Here it is:
= , line color rotation
= ]
=
+ dot color rotation =
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ []
+
+ line color rotation =
+ Svg.line
+ [ Svg.Attributes.strokeWidth "1"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x1 "80"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.stroke color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ")"
+ ]
+ )
+ ]
+ []
+
+
=We also have to change the definition of {Code|main}. Segment no longer takes two arguments, and its children are calculated according to rules, so we can just add one brown segment and rotate it upward (-90 degree)
=
=| Editor
= | Annotations
- | None
+ | Highlight
+ top = 9
+ left = 4
+ width = 47
+ height = 1
+
+ | Fold
+ start = 1
+ length = 5
+
+ | Fold
+ start = 20
+ length = 70
=
= | Code
+ module Main exposing (main)
+
+ import Dict
+ import Element
+ import Svg exposing (Svg)
+ import Svg.Attributes
+
= main =
= [ segment { color = "brown", rotation = -90 } ]
= |> Svg.svg
@@ -801,57 +2574,167 @@ We also have to change the definition of {Code|main}. Segment no longer takes tw
= , Element.height Element.fill
= ]
=
-The result should be exactly like we wanted it to be:
+ rules =
+ Dict.empty
+ |> Dict.insert "brown"
+ [ { color = "green", rotation = 0 }
+ , { color = "green", rotation = 20 }
+ , { color = "green", rotation = -30 }
+ ]
+ |> Dict.insert "green"
+ [ { color = "red", rotation = -45 }
+ , { color = "red", rotation = -5 }
+ , { color = "red", rotation = 50 }
+ ]
+
+ segment { color, rotation } =
+ Svg.g []
+ [ rules
+ |> Dict.get color
+ |> Maybe.withDefault []
+ |> List.map segment
+ |> Svg.g
+ [ Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ , dot color rotation
+ , line color rotation
+ ]
+
+ dot color rotation =
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ []
+
+ line color rotation =
+ Svg.line
+ [ Svg.Attributes.strokeWidth "1"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x1 "80"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.stroke color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ")"
+ ]
+ )
+ ]
+ []
=
-| Monospace
- TODO: Implement tree example
=
+The result should be exactly like we wanted it to be:
+
+| Window
= | Tree
= | Axiom
= color = brown
= rotation = -90
- age = -1
+ age = 8
= growing = False
=
+
= | Rule
- | Parent
- color = brown
+ parent = brown
=
- | Child
- color = "green"
- rotation = 0
+ children =
+ | Child
+ color = green
+ rotation = 0
=
- | Child
- color = "green"
- rotation = 20
+ | Child
+ color = green
+ rotation = 20
=
- | Child
- color = "green"
- rotation = -30
+ | Child
+ color = green
+ rotation = -30
=
= | Rule
- | Parent
- color = green
+ parent = green
+ children =
+ | Child
+ color = red
+ rotation = -45
+ | Child
+ color = red
+ rotation = -5
+ | Child
+ color = red
+ rotation = 50
=
- | Child
- color = "red"
- rotation = -45
+Nice, but what's going on here:
=
- | Child
- color = "red"
- rotation = -5
+| Editor
+ | Annotations
+ | Highlight
+ top = 38
+ left = 12
+ width = 23
+ height = 3
=
- | Child
- color = "red"
- rotation = 50
+ | Fold
+ start = 1
+ length = 34
=
-Nice, but what's going on here:
+ | Fold
+ start = 53
+ length = 36
+
+
+ | Code
+ module Main exposing (main)
+
+ import Dict
+ import Element
+ import Svg exposing (Svg)
+ import Svg.Attributes
+
+ main =
+ [ segment { color = "brown", rotation = -90 } ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: none"
+ , Svg.Attributes.viewBox "-500 -500 1000 1000"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
=
-| Editor
- | Annotations
- | None
+ rules =
+ Dict.empty
+ |> Dict.insert "brown"
+ [ { color = "green", rotation = 0 }
+ , { color = "green", rotation = 20 }
+ , { color = "green", rotation = -30 }
+ ]
+ |> Dict.insert "green"
+ [ { color = "red", rotation = -45 }
+ , { color = "red", rotation = -5 }
+ , { color = "red", rotation = 50 }
+ ]
=
- | Code
= segment { color, rotation } =
= Svg.g []
= [ rules
@@ -871,6 +2754,40 @@ Nice, but what's going on here:
= , line color rotation
= ]
=
+ dot color rotation =
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ []
+
+ line color rotation =
+ Svg.line
+ [ Svg.Attributes.strokeWidth "1"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x1 "80"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.stroke color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ")"
+ ]
+ )
+ ]
+ []
+
=There are three new things here:
=
=| List
@@ -903,20 +2820,109 @@ If there is an entry, we will get a list of records. We want a list of segments.
=
=Take some time to grasp it and then compare with our code.
=
-| Monospace
- rules
- |> Dict.get color
- |> Maybe.withDefault []
- |> List.map segment
- |> Svg.g
- [ Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ") translate(80)"
+| Editor
+ | Annotations
+ | Highlight
+ top = 37
+ left = 12
+ width = 41
+ height = 13
+
+ | Fold
+ start = 1
+ length = 35
+
+ | Fold
+ start = 53
+ length = 36
+
+ | Code
+ module Main exposing (main)
+
+ import Dict
+ import Element
+ import Svg exposing (Svg)
+ import Svg.Attributes
+
+ main =
+ [ segment { color = "brown", rotation = -90 } ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: none"
+ , Svg.Attributes.viewBox "-500 -500 1000 1000"
= ]
- )
- ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+ rules =
+ Dict.empty
+ |> Dict.insert "brown"
+ [ { color = "green", rotation = 0 }
+ , { color = "green", rotation = 20 }
+ , { color = "green", rotation = -30 }
+ ]
+ |> Dict.insert "green"
+ [ { color = "red", rotation = -45 }
+ , { color = "red", rotation = -5 }
+ , { color = "red", rotation = 50 }
+ ]
+
+ segment { color, rotation } =
+ Svg.g []
+ [ rules
+ |> Dict.get color
+ |> Maybe.withDefault []
+ |> List.map segment
+ |> Svg.g
+ [ Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ , dot color rotation
+ , line color rotation
+ ]
+
+ dot color rotation =
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ []
+
+ line color rotation =
+ Svg.line
+ [ Svg.Attributes.strokeWidth "1"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x1 "80"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.stroke color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ")"
+ ]
+ )
+ ]
+ []
=
=Let's analyze it line by line:
=
@@ -956,8 +2962,12 @@ We can do it like this. When we create our first segment (the brown one), we wil
= length = 8
=
= | Fold
- start = 62
- length = 36
+ start = 60
+ length = 38
+
+ | Fold
+ start = 22
+ length = 16
=
= | Code
= module Main exposing (main)
@@ -1128,9 +3138,76 @@ Since the size depends on the age, and the age is given for every segment, we ca
=
=| Editor
= | Annotations
- | None
+ | Highlight
+ top = 62
+ left = 1
+ width = 22
+ height = 1
+
+ | Highlight
+ top = 64
+ left = 10
+ width = 39
+ height = 1
+
+ | Highlight
+ top = 53
+ left = 30
+ width = 17
+ height = 1
+
+ | Highlight
+ top = 72
+ left = 18
+ width = 17
+ height = 1
+
+ | Fold
+ start = 1
+ length = 37
+
+ | Fold
+ start = 77
+ length = 20
=
= | Code
+ module Main exposing (main)
+
+ import Dict
+ import Element
+ import Svg exposing (Svg)
+ import Svg.Attributes
+
+
+ main =
+ [ segment 4 { color = "brown", rotation = -90 } ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: none"
+ , Svg.Attributes.viewBox "-500 -500 1000 1000"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+
+ rules =
+ Dict.empty
+ |> Dict.insert "brown"
+ [ { color = "brown", rotation = 0 }
+ , { color = "green", rotation = 20 }
+ , { color = "green", rotation = -30 }
+ ]
+ |> Dict.insert "green"
+ [ { color = "red", rotation = -45 }
+ , { color = "red", rotation = -5 }
+ , { color = "red", rotation = 50 }
+ ]
+
+
= segment age { color, rotation } =
= if age <= 0 then
= Svg.g [] []
@@ -1171,16 +3248,59 @@ Since the size depends on the age, and the age is given for every segment, we ca
= ]
= []
=
-| Monospace
- TODO: Describe the change to code
+ line color rotation =
+ Svg.line
+ [ Svg.Attributes.strokeWidth "1"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x1 "80"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.stroke color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ")"
+ ]
+ )
+ ]
+ []
=
-Next let's pass the age to the {Code|line} function and use it for both the thickness {Code|strokWidth} and length {Code|x2}.
+We passed age to the {Code|dot} function and use it for radius. Our radius now is affected by age.
=
-If you reload now, the tree will look as if it exploded. It's funny. The reason is that we did not adjust the translation of the child groups nor the dots. It's always 80, even though the length of the lines is variable - see lines {Code|TODO: line of group translation} and {Code|TODO: line of dot translation}. Let's change it like that:
+Next let's pass the age to the {Code|line} function and use it for both the thickness {Code|strokeWidth} and length {Code|x2}.
+
+If you reload now, the tree will look as if it exploded. It's funny. The reason is that we did not adjust the translation of the child groups nor the dots. It's always 80, even though the length of the lines is variable - see lines {Code|53} and {Code|72}. Let's change it like that:
=
=| Editor
= | Annotations
- | None
+ | Highlight
+ top = 88
+ left = 10
+ width = 47
+ height = 1
+
+ | Highlight
+ top = 91
+ left = 10
+ width = 49
+ height = 1
+
+ | Highlight
+ top = 75
+ left = 18
+ width = 27
+ height = 1
+
+ | Highlight
+ top = 54
+ left = 30
+ width = 27
+ height = 1
+
+ | Fold
+ start = 1
+ length = 37
=
= | Fold
= start = 1Add a growing option to the tree example.
If growing is true then the tree behaves as before (parents are bigger than children). If it's false, then they are the same, fixed size (length is 80 radius is 10).
Co-Authored-By: Tadeusz Łazurski tadeusz@lazurski.pl
index 6281453..adc7f1f 100644
--- a/content/day-4.txt
+++ b/content/day-4.txt
@@ -26,7 +26,7 @@ We want to have a tree that looks like this:
= color = green
= rotation = -90
= age = 8
-
+ growing = True
=
= | Rule
= parent = green
@@ -1068,9 +1068,7 @@ The {Code|else} value is almost the same as the whole {Code|segment} function be
=
=The result should be like this:
=
-| Monospace
- TODO: Implement tree example
-
+| Window
= | Tree
= | Axiom
= color = brown
@@ -1079,36 +1077,36 @@ The result should be like this:
= growing = False
=
= | Rule
- | Parent
- color = brown
+ parent = brown
=
- | Child
- color = "green"
- rotation = 0
+ children =
+ | Child
+ color = brown
+ rotation = 0
=
- | Child
- color = "green"
- rotation = 20
+ | Child
+ color = green
+ rotation = 20
=
- | Child
- color = "green"
- rotation = -30
+ | Child
+ color = green
+ rotation = -30
=
= | Rule
- | Parent
- color = green
+ parent = green
=
- | Child
- color = "red"
- rotation = -45
+ children =
+ | Child
+ color = red
+ rotation = -45
=
- | Child
- color = "red"
- rotation = -5
+ | Child
+ color = red
+ rotation = -5
=
- | Child
- color = "red"
- rotation = 50
+ | Child
+ color = red
+ rotation = 50
=
=| Emphasize
= Go ahead and play with the rules and age.
@@ -1290,9 +1288,7 @@ If you reload now, the tree will look as if it exploded. It's funny. The reason
=
=Now the tree should look correctly, like this:
=
-| Monospace
- TODO: Implement tree example
-
+| Window
= | Tree
= | Axiom
= color = brown
@@ -1301,36 +1297,36 @@ Now the tree should look correctly, like this:
= growing = True
=
= | Rule
- | Parent
- color = brown
+ parent = brown
=
- | Child
- color = "green"
- rotation = 0
+ children =
+ | Child
+ color = brown
+ rotation = 0
=
- | Child
- color = "green"
- rotation = 20
+ | Child
+ color = green
+ rotation = 20
=
- | Child
- color = "green"
- rotation = -30
+ | Child
+ color = green
+ rotation = -30
=
= | Rule
- | Parent
- color = green
+ parent = green
=
- | Child
- color = "red"
- rotation = -45
+ children =
+ | Child
+ color = red
+ rotation = -45
=
- | Child
- color = "red"
- rotation = -5
+ | Child
+ color = red
+ rotation = -5
=
- | Child
- color = "red"
- rotation = 50
+ | Child
+ color = red
+ rotation = 50
=
=| Emphasize
= Congratulations!index 8fee380..1b26fc6 100644
--- a/src/Examples/Tree.elm
+++ b/src/Examples/Tree.elm
@@ -12,6 +12,7 @@ import Dict exposing (Dict)
=import Element exposing (Element)
=import Svg exposing (Svg)
=import Svg.Attributes
+import Transformations
=
=
=defaults : Config
@@ -20,6 +21,7 @@ defaults =
= { color = "purple"
= , rotation = -90
= , age = 7
+ , growing = True
= }
= , rules =
= [ ( "purple"
@@ -54,6 +56,7 @@ type alias Axiom =
= { color : String
= , rotation : Float
= , age : Float
+ , growing : Bool
= }
=
=
@@ -70,19 +73,31 @@ ui config =
= let
= dot age color rotation =
= Svg.circle
- [ Svg.Attributes.r (String.fromFloat age)
+ [ Svg.Attributes.r
+ (String.fromFloat
+ (if config.axiom.growing then
+ age
+
+ else
+ 10
+ )
+ )
= , Svg.Attributes.cx "0"
= , Svg.Attributes.cy "0"
= , Svg.Attributes.fill color
- , Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ") translate("
- , String.fromFloat (age * 10)
- , ")"
- ]
- )
+ , [ Transformations.Rotate rotation
+ , Transformations.Translate
+ (if config.axiom.growing then
+ age * 10
+
+ else
+ 80
+ )
+ 0
+ ]
+ |> List.map Transformations.toString
+ |> String.join " "
+ |> Svg.Attributes.transform
= ]
= []
=
@@ -91,17 +106,29 @@ ui config =
= [ Svg.Attributes.strokeWidth "1"
= , Svg.Attributes.x1 "0"
= , Svg.Attributes.y1 "0"
- , Svg.Attributes.x2 (String.fromFloat (age * 10))
+ , Svg.Attributes.x2
+ (String.fromFloat
+ (if config.axiom.growing then
+ age * 10
+
+ else
+ 80
+ )
+ )
= , Svg.Attributes.y2 "0"
= , Svg.Attributes.stroke color
- , Svg.Attributes.strokeWidth (String.fromFloat age)
- , Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ")"
- ]
+ , Svg.Attributes.strokeWidth
+ (String.fromFloat
+ (if config.axiom.growing then
+ age
+
+ else
+ 1
+ )
= )
+ , Transformations.Rotate rotation
+ |> Transformations.toString
+ |> Svg.Attributes.transform
= ]
= []
=
@@ -137,15 +164,19 @@ ui config =
= |> Maybe.withDefault []
= |> List.map (segment (age - 1))
= |> Svg.g
- [ Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ") translate("
- , String.fromFloat (age * 10)
- , ")"
- ]
- )
+ [ [ Transformations.Rotate rotation
+ , Transformations.Translate
+ (if config.axiom.growing then
+ age * 10
+
+ else
+ 80
+ )
+ 0
+ ]
+ |> List.map Transformations.toString
+ |> String.join " "
+ |> Svg.Attributes.transform
= ]
= , dot age color rotation
= , line age color rotationindex 4051617..24a7f89 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -1055,11 +1055,12 @@ document =
=
= axiom : Mark.Block Examples.Tree.Axiom
= axiom =
- Mark.record3 "Axiom"
+ Mark.record4 "Axiom"
= Examples.Tree.Axiom
= (Mark.field "color" Mark.string)
= (Mark.field "rotation" Mark.float)
= (Mark.field "age" Mark.float)
+ (Mark.field "growing" Mark.bool)
=
= rules : Mark.Block (List Examples.Tree.Rule)
= rules =Fix style
index d982bad..89d29c0 100644
--- a/content/motivation.txt
+++ b/content/motivation.txt
@@ -1,7 +1,7 @@
=| Title
= Why are we doing this?
=
-More and more of the world around us is driven by computer programs. Software is in our houses, offices, phones, cars, factories, cities. When you ride a bus, there is software helping the driver stay on track and on time. When you ride the elevator, the software controls it. One day soon perhaps software will control the bus without the driver, just like it already controls the elevator without a human operator.
+More and more of the world around us is driven by computer programs. Software is in our houses, offices, phones, cars, factories, cities and so on. When you ride a bus, there is software helping the driver stay on track and on time. When you ride the elevator, the software controls it. One day soon perhaps software will control the bus without the driver, just like it already controls the elevator without a human operator.
=
=Computer programs are moving things around us, they talk and listen to us and to other programs. Perhaps it all seems like magic to you. It should! It is magical. We are creating living things by uttering words in an arcane languages. At Software Garden we want to show you how it's done.
=Merge branch 'motivation' into 'master'
Create Motivation page
See merge request software-garden/software-garden.gitlab.io!20
Restyle emphasize block on day 4
index 6281453..4d42c62 100644
--- a/content/day-4.txt
+++ b/content/day-4.txt
@@ -483,10 +483,12 @@ A segment within a segment! Consider that every segment can have child segments.
= []
=
=| Emphasize
- Time to play 🌳🌲🌴🎄
+ Time to play
+
+ 🌳 🌲 🌴 🎄
+
+ Try building your own tree. Play with colors and sizes.
=
-| Emphasize
- Try building your own tree this way. Play with colors and sizes.
=
=| Note
= Hint: if you add {Code|Svg.attributes.strokeWidth "2"} attribute to the line, you will get thicker branches. Try different combinations of stroke width and radius of a dot.Add more highlights and a fold to Editor block that introduces halting
index 4d42c62..03212fa 100644
--- a/content/day-4.txt
+++ b/content/day-4.txt
@@ -953,10 +953,38 @@ We can do it like this. When we create our first segment (the brown one), we wil
= width = 49
= height = 1
=
+ | Highlight
+ top = 38
+ left = 8
+ width = 5
+ height = 1
+
+ | Highlight
+ top = 38
+ left = 8
+ width = 5
+ height = 1
+
+ | Highlight
+ top = 39
+ left = 4
+ width = 18
+ height = 2
+
+ | Highlight
+ top = 47
+ left = 38
+ width = 9
+ height = 1
+
= | Fold
= start = 1
= length = 8
=
+ | Fold
+ start = 23
+ length = 14
+
= | Fold
= start = 62
= length = 36Reformat Emphasize block on day 4
index 03212fa..c3f9c78 100644
--- a/content/day-4.txt
+++ b/content/day-4.txt
@@ -1143,10 +1143,8 @@ The result should be like this:
=| Emphasize
= Go ahead and play with the rules and age.
=
-| Emphasize
- 😺🎾
+ 😺 🎾
=
-| Emphasize
= It's safe - the tree will always stop growing after a given number of generations.
=
=| HeaderAdd parens on day 4
index c3f9c78..454ee16 100644
--- a/content/day-4.txt
+++ b/content/day-4.txt
@@ -1202,7 +1202,7 @@ Since the size depends on the age, and the age is given for every segment, we ca
=| Monospace
= TODO: Describe the change to code
=
-Next let's pass the age to the {Code|line} function and use it for both the thickness {Code|strokWidth} and length {Code|x2}.
+Next let's pass the age to the {Code|line} function and use it for both the thickness ({Code|strokWidth}) and length ({Code|x2}).
=
=If you reload now, the tree will look as if it exploded. It's funny. The reason is that we did not adjust the translation of the child groups nor the dots. It's always 80, even though the length of the lines is variable - see lines {Code|TODO: line of group translation} and {Code|TODO: line of dot translation}. Let's change it like that:
=Merge branch 'shapes-example' of gitlab.com:software-garden/website into shapes-example
I have commited changes before pulling.
Commits: 3
Remove Transformations.apply, expose toString instead
Apply was just mapping the list with toString and joining with a " ". In our opinion it's easy enough to do it in consuming code and it doesn't require a separate function.
Co-Authored-By: Fana jewiet@outlook.com
index 6e99a6f..b934bac 100644
--- a/src/Examples/Circle.elm
+++ b/src/Examples/Circle.elm
@@ -154,11 +154,12 @@ ui config =
= , Svg.Attributes.cx "0"
= , Svg.Attributes.cy "0"
= , Svg.Attributes.fill fill
- , Svg.Attributes.transform <|
- Transformations.apply
- [ Rotate angle
- , Translate config.radius 0
- ]
+ , [ Rotate angle
+ , Translate config.radius 0
+ ]
+ |> List.map Transformations.toString
+ |> String.join " "
+ |> Svg.Attributes.transform
= ]
= []
=
@@ -177,10 +178,9 @@ ui config =
= , Svg.Attributes.y2 "0"
= , Svg.Attributes.stroke "pink"
= , Svg.Attributes.strokeDasharray config.radi
- , Svg.Attributes.transform <|
- Transformations.apply
- [ Rotate angle
- ]
+ , Rotate angle
+ |> Transformations.toString
+ |> Svg.Attributes.transform
= ]
= []
= inindex f4a9dbf..fee3a3d 100644
--- a/src/Examples/NestedTransformations.elm
+++ b/src/Examples/NestedTransformations.elm
@@ -128,17 +128,18 @@ ui model =
= nestTransformationsGroup : Group -> Array Transformation -> Svg Msg -> Svg Msg
= nestTransformationsGroup group transformations item =
= let
- transformation =
- transformations
- |> Array.toList
- |> Transformations.apply
-
= color =
= group
= |> groupName
= |> String.toLower
= in
- g [ transform transformation ]
+ g
+ [ transformations
+ |> Array.toList
+ |> List.map Transformations.toString
+ |> String.join " "
+ |> transform
+ ]
= [ line
= [ x1 "0"
= , x2 "100"index 15d42cf..d08ff63 100644
--- a/src/Examples/Transformations.elm
+++ b/src/Examples/Transformations.elm
@@ -93,7 +93,12 @@ ui model =
= ]
=
= shape =
- g [ transform (Transformations.apply transformations) ]
+ g
+ [ transformations
+ |> List.map Transformations.toString
+ |> String.join " "
+ |> transform
+ ]
= [ line
= [ x1 "0"
= , x2 "100"
@@ -267,8 +272,7 @@ transformationUI index transformation =
= ]
= [ Element.row [ Element.width Element.fill ]
= [ transformation
- |> List.singleton
- |> Transformations.apply
+ |> Transformations.toString
= |> Element.text
= |> Element.el [ Element.width Element.fill ]
= , Input.button []index d72965f..5b78597 100644
--- a/src/Transformations.elm
+++ b/src/Transformations.elm
@@ -1,6 +1,6 @@
=module Transformations exposing
= ( Transformation(..)
- , apply
+ , toString
= )
=
=
@@ -11,34 +11,27 @@ type Transformation
= | Rotate Float
=
=
-apply : List Transformation -> String
-apply transformations =
- let
- toString : Transformation -> String
- toString transformation =
- case transformation of
- Identity ->
- ""
+toString : Transformation -> String
+toString transformation =
+ case transformation of
+ Identity ->
+ ""
=
- Scale x y ->
- "scale("
- ++ String.fromFloat x
- ++ ", "
- ++ String.fromFloat y
- ++ ")"
+ Scale x y ->
+ "scale("
+ ++ String.fromFloat x
+ ++ ", "
+ ++ String.fromFloat y
+ ++ ")"
=
- Translate x y ->
- "translate("
- ++ String.fromFloat x
- ++ ", "
- ++ String.fromFloat y
- ++ ")"
+ Translate x y ->
+ "translate("
+ ++ String.fromFloat x
+ ++ ", "
+ ++ String.fromFloat y
+ ++ ")"
=
- Rotate angle ->
- "rotate("
- ++ String.fromFloat angle
- ++ ")"
- in
- transformations
- |> List.map toString
- |> String.join " "
+ Rotate angle ->
+ "rotate("
+ ++ String.fromFloat angle
+ ++ ")"Make each shape in Examples.Shapes take a list of transformations
We cannot nest groups due to Elm Markup limitation, so the next best thing is to repeat the transformations and simulate nesting that way.
Co-Authored-By: Fana jewiet@outlook.com
index 76067a3..24cc5df 100644
--- a/src/Examples/Shapes.elm
+++ b/src/Examples/Shapes.elm
@@ -6,6 +6,8 @@ module Examples.Shapes exposing
= , ui
= )
=
+import Axis2d
+import Basics.Extra exposing (inDegrees)
=import Circle2d
=import Element exposing (Element)
=import Geometry.Svg
@@ -15,7 +17,7 @@ import LineSegment2d
=import Point2d exposing (Point2d)
=import Svg exposing (Attribute, Svg)
=import Svg.Attributes
-import Transformations
+import Transformations exposing (Transformation(..))
=
=
=type alias Config =
@@ -33,22 +35,17 @@ type alias Container =
=
=type Shape
= = Dot
- { cx : Float
- , cy : Float
+ { transformations : List Transformation
= , radius : Float
= , color : String
= }
= | Line
- { x1 : Float
- , y1 : Float
- , x2 : Float
- , y2 : Float
+ { length : Float
+ , transformations : List Transformation
= , color : String
= }
= | Group
- { rotation : Float
- , x : Float
- , y : Float
+ { transformations : List Transformation
= }
= (List Shape)
=
@@ -62,43 +59,44 @@ defaults =
= }
= , shapes =
= [ Dot
- { cx = 0
- , cy = 0
+ { transformations = []
= , radius = 10
= , color = "skyblue"
= }
= , Line
- { x1 = 0.0
- , y1 = 0.0
- , x2 = 20.0
- , y2 = 30.0
+ { length = sqrt ((20 ^ 2) + (30 ^ 2))
+ , transformations =
+ [ atan2 20 30
+ |> inDegrees
+ |> Rotate
+ ]
= , color = "green"
= }
- , Group { rotation = -45.0, x = 0.0, y = 0.0 }
+ , Group
+ { transformations =
+ [ Rotate -45.0
+ ]
+ }
= [ Line
- { x1 = 20.0
- , y1 = 0.0
- , x2 = 70.0
- , y2 = 0.0
+ { transformations = [ Translate 20 0 ]
+ , length = 50
= , color = "orange"
= }
= , Dot
- { cx = 100
- , cy = 0.0
+ { transformations = [ Translate 100 0 ]
= , radius = 20.0
= , color = "red"
= }
- , Group { rotation = -45.0, x = 100.0, y = 0.0 }
+ , Group
+ { transformations = [ Rotate -45, Translate 100 0 ]
+ }
= [ Line
- { x1 = 20.0
- , y1 = 0.0
- , x2 = 70.0
- , y2 = 0.0
+ { length = 50
+ , transformations = [ Translate 20 0 ]
= , color = "orange"
= }
= , Dot
- { cx = 100
- , cy = 0.0
+ { transformations = [ Translate 100 0 ]
= , radius = 20.0
= , color = "red"
= }
@@ -123,34 +121,35 @@ ui { container, shapes } =
= shape : Shape -> Svg msg
= shape s =
= case s of
- Dot { cx, cy, color, radius } ->
- ( cx, cy )
- |> Point2d.fromCoordinates
+ Dot { transformations, color, radius } ->
+ Point2d.origin
= |> Circle2d.withRadius radius
- |> Geometry.Svg.circle2d [ Svg.Attributes.fill color ]
-
- Line { x1, y1, x2, y2, color } ->
- let
- a =
- Point2d.fromCoordinates ( x1, y1 )
+ |> Geometry.Svg.circle2d
+ [ Svg.Attributes.fill color
+ , transformations
+ |> List.map Transformations.toString
+ |> String.join " "
+ |> Svg.Attributes.transform
+ ]
=
- b =
- Point2d.fromCoordinates ( x2, y2 )
- in
- LineSegment2d.from a b
+ Line { length, transformations, color } ->
+ LineSegment2d.along Axis2d.x 0 length
= |> Geometry.Svg.lineSegment2d
= [ Svg.Attributes.stroke color
= , Svg.Attributes.strokeWidth "1"
+ , transformations
+ |> List.map Transformations.toString
+ |> String.join " "
+ |> Svg.Attributes.transform
= ]
=
- Group { rotation, x, y } ss ->
+ Group { transformations } ss ->
= ss
= |> List.map shape
= |> Svg.g
- [ [ Transformations.Rotate rotation
- , Transformations.Translate x y
- ]
- |> Transformations.apply
+ [ transformations
+ |> List.map Transformations.toString
+ |> String.join " "
= |> Svg.Attributes.transform
= ]
=index af1a472..e8c7294 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -49,7 +49,7 @@ import Result.Extra as Result
=import Routes exposing (Route)
=import Svg.Attributes
=import Task
-import Transformations
+import Transformations exposing (Transformation)
=import Url exposing (Url)
=
=
@@ -745,7 +745,6 @@ document =
= , circle
= , protractor
= , gradient
- , transformations
= , nestedTransformations
= , cartesianCoordinates
= , polarCoordinates
@@ -810,54 +809,80 @@ document =
=
= dot : Mark.Block Examples.Shapes.Shape
= dot =
- Mark.record4 "Dot"
- (\cx cy radius color ->
+ Mark.record3 "Dot"
+ (\radius color transformations ->
= Examples.Shapes.Dot
- { cx = cx
- , cy = cy
+ { transformations = transformations
= , radius = radius
= , color = color
= }
= )
- (Mark.field "cx" Mark.float)
- (Mark.field "cy" Mark.float)
= (Mark.field "radius" Mark.float)
= (Mark.field "color" Mark.string)
+ (Mark.field "transformations"
+ (Mark.manyOf
+ [ identity
+ , translate
+ , rotate
+ ]
+ )
+ )
=
= line : Mark.Block Examples.Shapes.Shape
= line =
- Mark.record5 "Line"
- (\x1 y1 x2 y2 color ->
+ Mark.record3 "Line"
+ (\color length transformations ->
= Examples.Shapes.Line
- { x1 = x1
- , y1 = y1
- , x2 = x2
- , y2 = y2
+ { transformations = transformations
= , color = color
+ , length = length
= }
= )
- (Mark.field "x1" Mark.float)
- (Mark.field "y1" Mark.float)
- (Mark.field "x2" Mark.float)
- (Mark.field "y2" Mark.float)
= (Mark.field "color" Mark.string)
+ (Mark.field "length" Mark.float)
+ (Mark.field "transformations"
+ (Mark.manyOf
+ [ identity
+ , translate
+ , rotate
+ ]
+ )
+ )
=
= -- TODO: Implement a recursive group block (that can contain nested groups)
= group : Mark.Block Examples.Shapes.Shape
= group =
- Mark.record4 "Group"
- (\x y rotation children ->
+ Mark.record2 "Group"
+ (\children transformations ->
= Examples.Shapes.Group
- { x = x
- , y = y
- , rotation = rotation
+ { transformations = transformations
= }
= children
= )
+ (Mark.field "children" (Mark.manyOf [ dot, line ]))
+ (Mark.field "transformations"
+ (Mark.manyOf
+ [ identity
+ , translate
+ , rotate
+ ]
+ )
+ )
+
+ identity : Mark.Block Transformation
+ identity =
+ Mark.stub "Identity" Transformations.Identity
+
+ translate : Mark.Block Transformation
+ translate =
+ Mark.record2 "Translate"
+ Transformations.Translate
= (Mark.field "x" Mark.float)
= (Mark.field "y" Mark.float)
- (Mark.field "rotation" Mark.float)
- (Mark.field "children" (Mark.manyOf [ dot, line ]))
+
+ rotate : Mark.Block Transformation
+ rotate =
+ Mark.block "Rotate" Transformations.Rotate Mark.float
= in
= Mark.block "Shapes"
= render
@@ -897,20 +922,6 @@ document =
= in
= Mark.stub "Gradient" render
=
- transformations : Mark.Block (Examples.Model -> Element Msg)
- transformations =
- let
- render model =
- model.transformations
- |> Examples.Transformations.ui
- |> Element.el
- [ Element.width Element.fill
- ]
- |> Element.map Examples.TransformationsMsg
- |> Element.map ExamplesMsg
- in
- Mark.stub "Transformations" render
-
= nestedTransformations : Mark.Block (Examples.Model -> Element Msg)
= nestedTransformations =
= letMerge remote-tracking branch 'origin/master' into shapes-example
Commits: 10
Use Element.classifyDevice to choose between icons and labels in nav bar
Also tweak font (and icons) size on small screens.
Also break some long lines in code.
index e75ff71..bb9aaa1 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -503,14 +503,16 @@ contentNavigationBar { location, scroll, viewport } =
= , label = "Home"
= , icon =
= FeatherIcons.home
- |> FeatherIcons.toHtml []
+ |> FeatherIcons.toHtml
+ [ Html.Attributes.style "height" "1em" ]
= |> Element.html
= }
= , { url = "/preparation.html"
= , label = "Get ready"
= , icon =
= FeatherIcons.bookOpen
- |> FeatherIcons.toHtml []
+ |> FeatherIcons.toHtml
+ [ Html.Attributes.style "height" "1em" ]
= |> Element.html
= }
= , { url = "/day-1.html"
@@ -563,11 +565,25 @@ contentNavigationBar { location, scroll, viewport } =
= )
= { url = link.url
= , label =
- if viewport.width < 600 then
- Element.el [ Element.centerX ] link.icon
+ Element.el [ Element.centerX ]
+ (case ( device.class, device.orientation ) of
+ ( Element.Phone, Element.Portrait ) ->
+ Element.el
+ [ Font.size 24
+ , Element.paddingXY 0 20
+ ]
+ link.icon
+
+ ( Element.Tablet, Element.Portrait ) ->
+ Element.el
+ [ Font.size 24
+ , Element.paddingXY 0 20
+ ]
+ link.icon
=
- else
- Element.el [ Element.centerX ] (Element.text link.label)
+ _ ->
+ Element.text link.label
+ )
= }
=
= linksRow =
@@ -585,6 +601,9 @@ contentNavigationBar { location, scroll, viewport } =
= , Font.size 18
= ]
=
+ device =
+ Element.classifyDevice viewport
+
= progressBar : Int -> Int -> Float -> Element Msg
= progressBar total done progress =
= Element.row [ Element.width Element.fill ]
@@ -599,13 +618,15 @@ contentNavigationBar { location, scroll, viewport } =
= , Element.height (Element.px 4)
= ]
= [ Element.el
- [ Element.width (Element.fillPortion (round (progress * 1000)))
+ [ Element.width
+ (Element.fillPortion (round (progress * 1000)))
= , Element.height (Element.px 4)
- , Background.color (Element.rgb 0 0.6 0)
+ , Background.color Mark.Custom.colors.badass
= ]
= Element.none
= , Element.el
- [ Element.width (Element.fillPortion (1000 - round (progress * 1000)))
+ [ Element.width
+ (Element.fillPortion (1000 - round (progress * 1000)))
= , Element.height (Element.px 4)
= ]
= Element.noneDisable font-size-adjusting on mobile devices
index af1a472..c08e7b3 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -17,6 +17,7 @@ import Element exposing (Element)
=import Element.Background as Background
=import Element.Border as Border
=import Element.Events
+import Element.Extra as Element
=import Element.Font as Font
=import Element.Input as Input
=import Element.Lazy
@@ -335,6 +336,10 @@ view model =
= Element.htmlAttribute (Html.Attributes.id "app-container")
= , Element.htmlAttribute (Html.Attributes.lang "en")
= , Font.family [ Font.typeface "Montserrat", Font.sansSerif ]
+ , Element.css "text-size-adjust" "none"
+ , Element.css "-webkit-text-size-adjust" "none"
+ , Element.css "-moz-text-size-adjust" "none"
+ , Element.css "-ms-text-size-adjust" "none"
= , Element.inFront navigationBar
= ]
= ]Merge branch 'fix-mobile-font-size-adjustment' into 'master'
Disable font-size-adjusting on mobile devices
See merge request software-garden/software-garden.gitlab.io!13
Merge branch 'home-page-links' into 'master'
Use Element.classifyDevice to choose between icons and labels in nav bar
See merge request software-garden/software-garden.gitlab.io!14
Revert "Merge branch 'home-page-links' into 'master'"
This reverts merge request !14
index f0910c0..104eefa 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -508,16 +508,14 @@ contentNavigationBar { location, scroll, viewport } =
= , label = "Home"
= , icon =
= FeatherIcons.home
- |> FeatherIcons.toHtml
- [ Html.Attributes.style "height" "1em" ]
+ |> FeatherIcons.toHtml []
= |> Element.html
= }
= , { url = "/preparation.html"
= , label = "Get ready"
= , icon =
= FeatherIcons.bookOpen
- |> FeatherIcons.toHtml
- [ Html.Attributes.style "height" "1em" ]
+ |> FeatherIcons.toHtml []
= |> Element.html
= }
= , { url = "/day-1.html"
@@ -570,25 +568,11 @@ contentNavigationBar { location, scroll, viewport } =
= )
= { url = link.url
= , label =
- Element.el [ Element.centerX ]
- (case ( device.class, device.orientation ) of
- ( Element.Phone, Element.Portrait ) ->
- Element.el
- [ Font.size 24
- , Element.paddingXY 0 20
- ]
- link.icon
-
- ( Element.Tablet, Element.Portrait ) ->
- Element.el
- [ Font.size 24
- , Element.paddingXY 0 20
- ]
- link.icon
+ if viewport.width < 600 then
+ Element.el [ Element.centerX ] link.icon
=
- _ ->
- Element.text link.label
- )
+ else
+ Element.el [ Element.centerX ] (Element.text link.label)
= }
=
= linksRow =
@@ -606,9 +590,6 @@ contentNavigationBar { location, scroll, viewport } =
= , Font.size 18
= ]
=
- device =
- Element.classifyDevice viewport
-
= progressBar : Int -> Int -> Float -> Element Msg
= progressBar total done progress =
= Element.row [ Element.width Element.fill ]
@@ -623,15 +604,13 @@ contentNavigationBar { location, scroll, viewport } =
= , Element.height (Element.px 4)
= ]
= [ Element.el
- [ Element.width
- (Element.fillPortion (round (progress * 1000)))
+ [ Element.width (Element.fillPortion (round (progress * 1000)))
= , Element.height (Element.px 4)
- , Background.color Mark.Custom.colors.badass
+ , Background.color (Element.rgb 0 0.6 0)
= ]
= Element.none
= , Element.el
- [ Element.width
- (Element.fillPortion (1000 - round (progress * 1000)))
+ [ Element.width (Element.fillPortion (1000 - round (progress * 1000)))
= , Element.height (Element.px 4)
= ]
= Element.noneMerge branch 'revert-9a34ba76' into 'master'
Revert home-page-links from master
See merge request software-garden/software-garden.gitlab.io!15
Use Element.classifyDevice to choose between icons and labels in nav bar
Also tweak font (and icons) size on small screens.
Also break some long lines in code.
index 104eefa..f0910c0 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -508,14 +508,16 @@ contentNavigationBar { location, scroll, viewport } =
= , label = "Home"
= , icon =
= FeatherIcons.home
- |> FeatherIcons.toHtml []
+ |> FeatherIcons.toHtml
+ [ Html.Attributes.style "height" "1em" ]
= |> Element.html
= }
= , { url = "/preparation.html"
= , label = "Get ready"
= , icon =
= FeatherIcons.bookOpen
- |> FeatherIcons.toHtml []
+ |> FeatherIcons.toHtml
+ [ Html.Attributes.style "height" "1em" ]
= |> Element.html
= }
= , { url = "/day-1.html"
@@ -568,11 +570,25 @@ contentNavigationBar { location, scroll, viewport } =
= )
= { url = link.url
= , label =
- if viewport.width < 600 then
- Element.el [ Element.centerX ] link.icon
+ Element.el [ Element.centerX ]
+ (case ( device.class, device.orientation ) of
+ ( Element.Phone, Element.Portrait ) ->
+ Element.el
+ [ Font.size 24
+ , Element.paddingXY 0 20
+ ]
+ link.icon
+
+ ( Element.Tablet, Element.Portrait ) ->
+ Element.el
+ [ Font.size 24
+ , Element.paddingXY 0 20
+ ]
+ link.icon
=
- else
- Element.el [ Element.centerX ] (Element.text link.label)
+ _ ->
+ Element.text link.label
+ )
= }
=
= linksRow =
@@ -590,6 +606,9 @@ contentNavigationBar { location, scroll, viewport } =
= , Font.size 18
= ]
=
+ device =
+ Element.classifyDevice viewport
+
= progressBar : Int -> Int -> Float -> Element Msg
= progressBar total done progress =
= Element.row [ Element.width Element.fill ]
@@ -604,13 +623,15 @@ contentNavigationBar { location, scroll, viewport } =
= , Element.height (Element.px 4)
= ]
= [ Element.el
- [ Element.width (Element.fillPortion (round (progress * 1000)))
+ [ Element.width
+ (Element.fillPortion (round (progress * 1000)))
= , Element.height (Element.px 4)
- , Background.color (Element.rgb 0 0.6 0)
+ , Background.color Mark.Custom.colors.badass
= ]
= Element.none
= , Element.el
- [ Element.width (Element.fillPortion (1000 - round (progress * 1000)))
+ [ Element.width
+ (Element.fillPortion (1000 - round (progress * 1000)))
= , Element.height (Element.px 4)
= ]
= Element.noneMerge branch 'home-page-links' into 'master'
Home page links
See merge request software-garden/software-garden.gitlab.io!16
In progress bar replace badass color with green
index f0910c0..3f0ebba 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -615,7 +615,7 @@ contentNavigationBar { location, scroll, viewport } =
= [ Element.el
= [ Element.width (Element.fillPortion done)
= , Element.height (Element.px 4)
- , Background.color (Element.rgb 0 0.6 0)
+ , Background.color Mark.Custom.colors.green
= ]
= Element.none
= , Element.row
@@ -626,7 +626,7 @@ contentNavigationBar { location, scroll, viewport } =
= [ Element.width
= (Element.fillPortion (round (progress * 1000)))
= , Element.height (Element.px 4)
- , Background.color Mark.Custom.colors.badass
+ , Background.color Mark.Custom.colors.green
= ]
= Element.none
= , Element.elindex ae6f285..745e5af 100644
--- a/src/Mark/Custom.elm
+++ b/src/Mark/Custom.elm
@@ -555,6 +555,6 @@ colors =
= , pink = Element.rgb 1 0.6 0.6
= , white = Element.rgb 1 1 1
= , black = Element.rgb 0 0 0
- , badass = Element.rgb255 0xBA 0xDA 0x55
+ , green = Element.rgb 0 0.6 0
= , blue = Element.rgb 0.07 0.52 0.81
= }Merge branch 'green-progress-bar' into 'master'
In progress bar replace badass color with green
See merge request software-garden/software-garden.gitlab.io!17
Commits: 8
Change Highlight from None in Code block day 4.
correct a typo.
index 9d4ca1c..3a09790 100644
--- a/content/day-4.txt
+++ b/content/day-4.txt
@@ -189,15 +189,35 @@ So now each segment has two sub segments: red and blue. The red one is rotate 15
=
=| Editor
= | Annotations
- | None
+ | Highlight
+ top = 25
+ left = 14
+ width = 44
+ height = 1
=
= | Fold
= start = 1
= length = 7
=
= | Fold
- start = 21
- length = 12
+ start = 27
+ length = 7
+
+ | Fold
+ start = 50
+ length = 38
+
+ | Highlight
+ top = 35
+ left = 1
+ width = 31
+ height = 1
+
+ | Highlight
+ top = 46
+ left = 13
+ width = 8
+ height = 1
=
= | Code
= module Main exposing (main)
@@ -877,7 +897,7 @@ It's a lot to absorb, so take your time to think about it.
=
=Our tree is very cool but rather simple. Wouldn't it be nice if it could grow bigger and bigger? What if we could say that a brown segment produces two green (left and right) and a brown (straight)? Then the brown child would have two green and a brown too. But brown gives more brown /ad infinitum/ (pardon my latin).
=
-You can try that, but don't expect to see anything interesting in the browser. Your computer will trying very hard to draw a tree for you, but after going through hundreds of thousands of segments' generations it will give up, perhaps saying "too much recursion". According to the rules you gave there is no stopping to this tree.
+You can try that, but don't expect to see anything interesting in the browser. Your computer is trying very hard to draw a tree for you, but after going through hundreds of thousands of segments' generations it will give up, perhaps saying "too much recursion". According to the rules you gave there is no stopping to this tree.
=
=| Note
= A funny fact about computers is that they generally can't tell if the task you give them can be finished or not. It's called {Link|the halting problem|url=https://en.wikipedia.org/wiki/Halting_problem}. Best they can do is give up after certain time. That's what happens here.Merge branch 'content' into editor-block
Merge branch 'content' into 'master'
Content
See merge request software-garden/software-garden.gitlab.io!8
Merge branch 'master' into shapes-example
Merge branch 'shapes-example' into 'master'
Shapes example
See merge request software-garden/software-garden.gitlab.io!11
Change the list on home page into block links
index 84b1c24..732e725 100644
--- a/content/index.txt
+++ b/content/index.txt
@@ -15,13 +15,30 @@ Hello! We are running a workshop that will give you a glimpse into the way softw
=
=Below is material through which we will be going during the workshop. Only the first section is a required read before you come, but feel free to read more to get more prepared.
=
-| List
- # {Link|Before the course begins|url=/preparation.html}
- # {Link|Day 1 - Let's Make a Dot|url=/day-1.html}
- # {Link|Day 2 - Let's Place the Dots in a Circle|url=/day-2.html}
- # {Link|Day 3 - Connecting the Dots|url=/day-3.html}
- # {Link|Day 4 - Let's Make a Tree|url=/day-4.html}
- # {Link|Day 5 - Let's Make the Tree Grow|url=/day-5.html}
+| Link
+ url = /preparation.html
+ label = Before the course begins
+
+| Link
+ url = /day-1.html
+ label = Day 1 - Let's Make a Dot
+
+
+| Link
+ url = /day-2.html
+ label = Day 2 - Let's Place the Dots in a Circle
+
+| Link
+ url = /day-3.html
+ label = Day 3 - Connecting the Dots
+
+| Link
+ url = /day-4.html
+ label = Day 4 - Let's Make a Tree
+
+| Link
+ url = /day-5.html
+ label = Day 5 - Let's Make the Tree Grow
=
=
=| Headerindex af1a472..f778ba6 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -720,6 +720,7 @@ document =
= typography =
= [ Mark.Custom.header
= , Mark.Custom.paragraph
+ , Mark.Custom.link
= , Mark.Custom.monospace
= , Mark.Custom.emphasize
= , Mark.Custom.listindex 9fe4d6d..ae6f285 100644
--- a/src/Mark/Custom.elm
+++ b/src/Mark/Custom.elm
@@ -5,6 +5,7 @@ module Mark.Custom exposing
= , header
= , icon
= , image
+ , link
= , list
= , monospace
= , note
@@ -67,6 +68,41 @@ paragraph =
= text
=
=
+link =
+ let
+ render :
+ String
+ -> (model -> Element msg)
+ -> model
+ -> Element msg
+ render url label model =
+ Element.link
+ [ Element.paddingXY 20 0
+ , Font.size 24
+
+ -- , Font.bold
+ -- , Background.color colors.charcoal
+ , Font.color colors.blue
+ , Border.rounded 2
+ , Element.mouseDown
+ [ Border.shadow
+ { offset = ( 0, 1 )
+ , size = 0
+ , blur = 3
+ , color = colors.gray
+ }
+ ]
+ ]
+ { url = url
+ , label = label model
+ }
+ in
+ Mark.record2 "Link"
+ render
+ (Mark.field "url" Mark.string)
+ (Mark.field "label" paragraph)
+
+
=monospace : Mark.Block (model -> Element msg)
=monospace =
= Mark.Default.monospace
@@ -520,4 +556,5 @@ colors =
= , white = Element.rgb 1 1 1
= , black = Element.rgb 0 0 0
= , badass = Element.rgb255 0xBA 0xDA 0x55
+ , blue = Element.rgb 0.07 0.52 0.81
= }Restyle navigation bar
Make it visually less significant and make current item stand out more.
index f778ba6..e75ff71 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -543,14 +543,24 @@ contentNavigationBar { location, scroll, viewport } =
= -> Element Msg
= linkElement link =
= Element.link
- [ Element.width Element.fill
- , Element.paddingEach
- { top = 40
+ ([ Element.width Element.fill
+ , Font.size 14
+ , Font.light
+ , Element.paddingEach
+ { top = 20
= , right = 5
- , bottom = 20
+ , bottom = 10
= , left = 5
= }
- ]
+ , Font.color Mark.Custom.colors.gray
+ ]
+ ++ (if location.path == link.url then
+ currentAttributes
+
+ else
+ []
+ )
+ )
= { url = link.url
= , label =
= if viewport.width < 600 then
@@ -569,6 +579,12 @@ contentNavigationBar { location, scroll, viewport } =
= , Font.bold
= ]
=
+ currentAttributes =
+ [ Font.medium
+ , Font.color Mark.Custom.colors.blue
+ , Font.size 18
+ ]
+
= progressBar : Int -> Int -> Float -> Element Msg
= progressBar total done progress =
= Element.row [ Element.width Element.fill ]Merge branch 'home-page-links' into 'master'
Home page links and restyled navigation bar
See merge request software-garden/software-garden.gitlab.io!12
Commits: 1
Improve performance when scrolling
Through combination of animation frame and lazy rendering.
index 4739a1a..0cf07d4 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -19,6 +19,7 @@ import Element.Border as Border
=import Element.Events
=import Element.Font as Font
=import Element.Input as Input
+import Element.Lazy
=import Examples
=import Examples.CartesianCoordinates
=import Examples.Circle
@@ -81,11 +82,12 @@ type alias Model =
=
=type Msg
= = NoOp
+ | Animate Float
= | UrlRequested Browser.UrlRequest
= | UrlChanged Url
= | ContentFetched (Result Http.Error String)
= | ExamplesMsg Examples.Msg
- | Scroll Float
+ | ContainerMeasured (Result Dom.Error Dom.Viewport)
= | ViewportMeasured { width : Int, height : Int }
=
=
@@ -325,19 +327,6 @@ view model =
= ++ ":"
= ++ String.fromInt col
= )
-
- scrollDecoder : Decoder Msg
- scrollDecoder =
- Json.Decode.field "target"
- (Json.Decode.map3
- (\top height clientHeight ->
- top / (height - clientHeight)
- )
- (Json.Decode.field "scrollTop" Json.Decode.float)
- (Json.Decode.field "scrollHeight" Json.Decode.float)
- (Json.Decode.field "clientHeight" Json.Decode.float)
- )
- |> Json.Decode.map Scroll
= in
= { title = "Software Garden : " ++ content.title
= , body =
@@ -348,9 +337,6 @@ view model =
= , Element.htmlAttribute (Html.Attributes.lang "en")
= , Font.family [ Font.typeface "Montserrat", Font.sansSerif ]
= , Element.inFront navigationBar
- , Element.height Element.fill
- , Element.scrollbars
- , Element.htmlAttribute (Html.Events.on "scroll" scrollDecoder)
= ]
= ]
= }
@@ -416,8 +402,17 @@ update msg model =
= , Cmd.map ExamplesMsg examplesCmd
= )
=
- Scroll position ->
- ( { model | scroll = position }
+ ContainerMeasured (Ok { viewport, scene }) ->
+ let
+ scroll =
+ viewport.y / (scene.height - viewport.height)
+ in
+ ( { model | scroll = scroll }
+ , Cmd.none
+ )
+
+ ContainerMeasured (Err error) ->
+ ( model
= , Cmd.none
= )
=
@@ -426,6 +421,12 @@ update msg model =
= , Cmd.none
= )
=
+ Animate delta ->
+ ( model
+ , Dom.getViewport
+ |> Task.attempt ContainerMeasured
+ )
+
=
=subscriptions : Model -> Sub Msg
=subscriptions model =
@@ -437,6 +438,7 @@ subscriptions model =
= (\width height ->
= ViewportMeasured { width = width, height = height }
= )
+ , Browser.Events.onAnimationFrameDelta Animate
= ]
=
=
@@ -648,10 +650,13 @@ document =
= -> Model
= -> View
= page { title, children } model =
+ View title (Element.Lazy.lazy3 body children model.examples model.viewport)
+
+ body children model viewport =
= children
- |> List.map (\child -> child model.examples)
+ |> List.map (\child -> child model)
= |> Element.textColumn
- (model.viewport
+ (viewport
= |> Element.classifyDevice
= |> responsiveAttributes
= |> (++)
@@ -659,7 +664,6 @@ document =
= , Element.spacing 20
= ]
= )
- |> View title
=
= responsiveAttributes { class, orientation } =
= case ( class, orientation ) of
Commits: 8
Merge Dots and Line into more general Shapes example
index d67a0ae..4659dcb 100644
--- a/content/day-1.txt
+++ b/content/day-1.txt
@@ -111,7 +111,7 @@ This was a warmup. Let's try something more challenging.
=We want to display a dot at the center of the screen, like this:
=
=| Window
- | Dots
+ | Shapes
= | Container
= background = none
= viewBox = -300 -300 600 600
@@ -201,7 +201,7 @@ Start the reactor again by entering:
=Reload the browser. You should see something like this:
=
=| Window
- | Dots
+ | Shapes
= | Container
= background = none
= fill = False
@@ -236,7 +236,7 @@ First we have to realize that the dot is inside an {Code|svg} element that itsel
=Reloading the browser should reveal something like this:
=
=| Window
- | Dots
+ | Shapes
= | Container
= background = pink
= fill = False
@@ -295,7 +295,7 @@ As before, press {Key|enter} to follow the plan. Now let's use the package. Chan
=Reload the browser. The SVG space now fills the entire viewport. Even the small white margin is gone!
=
=| Window
- | Dots
+ | Shapes
= | Container
= background = pink
= fill = True
@@ -364,7 +364,7 @@ To change the position of the dot, we can use the {Code|cx} and {Code|cy} attrib
= ]
=
=| Window
- | Dots
+ | Shapes
= | Container
= background = none
= fill = True
@@ -451,7 +451,7 @@ In the code we set the viewbox as an attribute of the {Code|svg} element, like t
=There are only two changes here. First we reset the {Code|cx} and {Code|cy} attributes of the {Code|circle} back to {Code|"0"} (on lines 11 and 12). We want the dot to be back at origin. Then we set up the position of the viewbox on line 20. Try experimenting with different values. If you set them right and refresh the browser the dot should be in the center of the screen.
=
=| Window
- | Dots
+ | Shapes
= | Container
= background = pink
= fill = True
@@ -501,7 +501,7 @@ Speaking of colors, now that we've centered our dot, it's time to give it a colo
=The only change is on line 13. Reload the browser and see this:
=
=| Window
- | Dots
+ | Shapes
= | Container
= background = none
= fill = Trueindex d0a4ad1..1073983 100644
--- a/content/day-2.txt
+++ b/content/day-2.txt
@@ -81,7 +81,7 @@ If you look closely in your source code, the block above is surrounded by {Code|
=Now the list spans from lines 9 to 23 and contains two items: skyblue and orange dots. The result should be:
=
=| Window
- | Dots
+ | Shapes
= | Container
= background = none
= fill = True
@@ -139,7 +139,7 @@ Oh oh! The list contains two dots, but we can only see the orange one on the scr
=Now you should see this:
=
=| Window
- | Dots
+ | Shapes
= | Container
= background = none
= fill = True
@@ -223,7 +223,7 @@ The {Code|r} attribute stands for radius. Then it was basically dictating the si
= radius = 80
=
=| Row
- | Dots
+ | Shapes
= | Container
= background = none
= fill = True
@@ -235,7 +235,7 @@ The {Code|r} attribute stands for radius. Then it was basically dictating the si
= cy = 0
= color = skyblue
=
- | Dots
+ | Shapes
= | Container
= background = none
= fill = True
@@ -247,7 +247,7 @@ The {Code|r} attribute stands for radius. Then it was basically dictating the si
= cy = 0
= color = skyblue
=
- | Dots
+ | Shapes
= | Container
= background = none
= fill = Trueindex 94e0a3f..4db325b 100644
--- a/src/Examples.elm
+++ b/src/Examples.elm
@@ -3,12 +3,11 @@ module Examples exposing (Model, Msg(..), init, subscriptions, update)
=import Examples.CartesianCoordinates
=import Examples.Circle
=import Examples.Counter
-import Examples.Dots
=import Examples.Gradient
-import Examples.Line
=import Examples.NestedTransformations
=import Examples.PolarCoordinates
=import Examples.RosetteTypedTransformations
+import Examples.Shapes
=import Examples.Spiral
=import Examples.Transformations
=import Examples.Treedeleted file mode 100644
index 4285cb8..0000000
--- a/src/Examples/Dots.elm
+++ /dev/null
@@ -1,106 +0,0 @@
-module Examples.Dots exposing
- ( Config
- , Container
- , Dot
- , main
- , ui
- )
-
-import Circle2d
-import Element exposing (Element)
-import Geometry.Svg
-import Html exposing (Html)
-import Html.Attributes
-import Point2d exposing (Point2d)
-import Svg exposing (Attribute, Svg)
-import Svg.Attributes
-
-
-type alias Config =
- { container : Container
- , dots : List Dot
- }
-
-
-type alias Container =
- { background : String
- , viewBox : String
- , fill : Bool
- }
-
-
-type alias Dot =
- { cx : Float
- , cy : Float
- , radius : Float
- , color : String
- }
-
-
-defaults : Config
-defaults =
- { container =
- { background = "none"
- , viewBox = "-300 -300 600 600"
- , fill = True
- }
- , dots =
- [ { cx = 0
- , cy = 0
- , radius = 10
- , color = "skyblue"
- }
- ]
- }
-
-
-main : Html msg
-main =
- ui defaults
- |> Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
- ]
-
-
-ui : Config -> Element msg
-ui { container, dots } =
- let
- dot : Dot -> Svg msg
- dot { cx, cy, color, radius } =
- ( cx, cy )
- |> Point2d.fromCoordinates
- |> Circle2d.withRadius radius
- |> Geometry.Svg.circle2d [ Svg.Attributes.fill color ]
-
- width =
- if container.fill then
- "100%"
-
- else
- "300"
-
- height =
- if container.fill then
- "100%"
-
- else
- "150"
-
- padding =
- if container.fill then
- "0"
-
- else
- "5px"
- in
- dots
- |> List.map dot
- |> Svg.svg
- [ Svg.Attributes.viewBox container.viewBox
- , Html.Attributes.style "background" container.background
- , Svg.Attributes.width width
- , Svg.Attributes.height height
- , Html.Attributes.style "margin" padding
- ]
- |> Element.htmldeleted file mode 100644
index d105ef5..0000000
--- a/src/Examples/Line.elm
+++ /dev/null
@@ -1,29 +0,0 @@
-module Examples.Line exposing (main, ui)
-
-import Element
-import Svg
-import Svg.Attributes
-
-
-main =
- Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
- ]
- ui
-
-
-ui =
- Element.html
- (Svg.svg
- [ Svg.Attributes.height "200"
- , Svg.Attributes.viewBox "-10 -25 100 100"
- ]
- [ Svg.line
- [ Svg.Attributes.x2 "1"
- , Svg.Attributes.stroke "black"
- , Svg.Attributes.transform "rotate(30) scale(100, 1)"
- ]
- []
- ]
- )new file mode 100644
index 0000000..485e44b
--- /dev/null
+++ b/src/Examples/Shapes.elm
@@ -0,0 +1,187 @@
+module Examples.Shapes exposing
+ ( Config
+ , Container
+ , Shape(..)
+ , main
+ , ui
+ )
+
+import Circle2d
+import Element exposing (Element)
+import Geometry.Svg
+import Html exposing (Html)
+import Html.Attributes
+import LineSegment2d
+import Point2d exposing (Point2d)
+import Svg exposing (Attribute, Svg)
+import Svg.Attributes
+import Transformations
+
+
+type alias Config =
+ { container : Container
+ , shapes : List Shape
+ }
+
+
+type alias Container =
+ { background : String
+ , viewBox : String
+ , fill : Bool
+ }
+
+
+type Shape
+ = Dot
+ { cx : Float
+ , cy : Float
+ , radius : Float
+ , color : String
+ }
+ | Line
+ { x1 : Float
+ , y1 : Float
+ , x2 : Float
+ , y2 : Float
+ , color : String
+ }
+ | Group
+ { rotation : Float
+ , x : Float
+ , y : Float
+ }
+ (List Shape)
+
+
+defaults : Config
+defaults =
+ { container =
+ { background = "none"
+ , viewBox = "-300 -300 600 600"
+ , fill = True
+ }
+ , shapes =
+ [ Dot
+ { cx = 0
+ , cy = 0
+ , radius = 10
+ , color = "skyblue"
+ }
+ , Line
+ { x1 = 0.0
+ , y1 = 0.0
+ , x2 = 20.0
+ , y2 = 30.0
+ , color = "green"
+ }
+ , Group { rotation = -45.0, x = 0.0, y = 0.0 }
+ [ Line
+ { x1 = 20.0
+ , y1 = 0.0
+ , x2 = 70.0
+ , y2 = 0.0
+ , color = "orange"
+ }
+ , Dot
+ { cx = 100
+ , cy = 0.0
+ , radius = 20.0
+ , color = "red"
+ }
+ , Group { rotation = -45.0, x = 100.0, y = 0.0 }
+ [ Line
+ { x1 = 20.0
+ , y1 = 0.0
+ , x2 = 70.0
+ , y2 = 0.0
+ , color = "orange"
+ }
+ , Dot
+ { cx = 100
+ , cy = 0.0
+ , radius = 20.0
+ , color = "red"
+ }
+ ]
+ ]
+ ]
+ }
+
+
+main : Html msg
+main =
+ ui defaults
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+
+ui : Config -> Element msg
+ui { container, shapes } =
+ let
+ shape : Shape -> Svg msg
+ shape s =
+ case s of
+ Dot { cx, cy, color, radius } ->
+ ( cx, cy )
+ |> Point2d.fromCoordinates
+ |> Circle2d.withRadius radius
+ |> Geometry.Svg.circle2d [ Svg.Attributes.fill color ]
+
+ Line { x1, y1, x2, y2, color } ->
+ let
+ a =
+ Point2d.fromCoordinates ( x1, y1 )
+
+ b =
+ Point2d.fromCoordinates ( x2, y2 )
+ in
+ LineSegment2d.from a b
+ |> Geometry.Svg.lineSegment2d
+ [ Svg.Attributes.stroke color
+ , Svg.Attributes.strokeWidth "3"
+ ]
+
+ Group { rotation, x, y } ss ->
+ ss
+ |> List.map shape
+ |> Svg.g
+ [ [ Transformations.Rotate rotation
+ , Transformations.Translate x y
+ ]
+ |> Transformations.apply
+ |> Svg.Attributes.transform
+ ]
+
+ width =
+ if container.fill then
+ "100%"
+
+ else
+ "300"
+
+ height =
+ if container.fill then
+ "100%"
+
+ else
+ "150"
+
+ padding =
+ if container.fill then
+ "0"
+
+ else
+ "5px"
+ in
+ shapes
+ |> List.map shape
+ |> Svg.svg
+ [ Svg.Attributes.viewBox container.viewBox
+ , Html.Attributes.style "background" container.background
+ , Svg.Attributes.width width
+ , Svg.Attributes.height height
+ , Html.Attributes.style "margin" padding
+ ]
+ |> Element.htmlindex bf183c7..ec3938a 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -23,13 +23,12 @@ import Examples
=import Examples.CartesianCoordinates
=import Examples.Circle
=import Examples.Counter
-import Examples.Dots
=import Examples.Gradient
-import Examples.Line
=import Examples.NestedTransformations
=import Examples.PolarCoordinates
=import Examples.Protractor
=import Examples.RosetteTypedTransformations
+import Examples.Shapes
=import Examples.Spiral
=import Examples.Transformations
=import Examples.Tree
@@ -681,10 +680,9 @@ document =
=
= examples =
= [ counter
- , dots
+ , shapes
= , circle
= , protractor
- , line
= , gradient
= , transformations
= , nestedTransformations
@@ -731,39 +729,81 @@ document =
= in
= Mark.stub "Counter" render
=
- dots : Mark.Block (Examples.Model -> Element Msg)
- dots =
+ shapes : Mark.Block (Examples.Model -> Element Msg)
+ shapes =
= let
= render :
- Examples.Dots.Config
+ Examples.Shapes.Config
= -> Examples.Model
= -> Element Msg
= render config model =
- Examples.Dots.ui config
+ Examples.Shapes.ui config
=
- container : Mark.Block Examples.Dots.Container
+ container : Mark.Block Examples.Shapes.Container
= container =
= Mark.record3 "Container"
- Examples.Dots.Container
+ Examples.Shapes.Container
= (Mark.field "background" Mark.string)
= (Mark.field "viewBox" Mark.string)
= (Mark.field "fill" Mark.bool)
=
- dot : Mark.Block Examples.Dots.Dot
+ dot : Mark.Block Examples.Shapes.Shape
= dot =
= Mark.record4 "Dot"
- Examples.Dots.Dot
+ (\cx cy radius color ->
+ Examples.Shapes.Dot
+ { cx = cx
+ , cy = cy
+ , radius = radius
+ , color = color
+ }
+ )
= (Mark.field "cx" Mark.float)
= (Mark.field "cy" Mark.float)
= (Mark.field "radius" Mark.float)
= (Mark.field "color" Mark.string)
+
+ line : Mark.Block Examples.Shapes.Shape
+ line =
+ Mark.record5 "Line"
+ (\x1 y1 x2 y2 color ->
+ Examples.Shapes.Line
+ { x1 = x1
+ , y1 = y1
+ , x2 = x2
+ , y2 = y2
+ , color = color
+ }
+ )
+ (Mark.field "x1" Mark.float)
+ (Mark.field "y1" Mark.float)
+ (Mark.field "x2" Mark.float)
+ (Mark.field "y2" Mark.float)
+ (Mark.field "color" Mark.string)
+
+ -- TODO: Implement a recursive group block (that can contain nested groups)
+ group : Mark.Block Examples.Shapes.Shape
+ group =
+ Mark.record4 "Group"
+ (\x y rotation children ->
+ Examples.Shapes.Group
+ { x = x
+ , y = y
+ , rotation = rotation
+ }
+ children
+ )
+ (Mark.field "x" Mark.float)
+ (Mark.field "y" Mark.float)
+ (Mark.field "rotation" Mark.float)
+ (Mark.field "children" (Mark.manyOf [ dot, line ]))
= in
- Mark.block "Dots"
+ Mark.block "Shapes"
= render
= (Mark.startWith
- Examples.Dots.Config
+ Examples.Shapes.Config
= container
- (Mark.manyOf [ dot ])
+ (Mark.manyOf [ dot, line, group ])
= )
=
= circle : Mark.Block (Examples.Model -> Element Msg)
@@ -784,18 +824,6 @@ document =
= (Mark.field "scatter" Mark.bool)
= |> Mark.map render
=
- line : Mark.Block (Examples.Model -> Element Msg)
- line =
- let
- render model =
- Examples.Line.ui
- |> Element.el
- [ Element.height (Element.px 400)
- , Element.width Element.fill
- ]
- in
- Mark.stub "Line" render
-
= gradient : Mark.Block (Examples.Model -> Element Msg)
= gradient =
= letProvide a connected dots example on day 3
index 155a00b..8975942 100644
--- a/content/day-3.txt
+++ b/content/day-3.txt
@@ -621,8 +621,102 @@ Once you have the function defined, let's use it. For every dot, create one line
=
=You should see something like this in the browser:
=
-| Monospace
- TODO: Example showing the connected dots
+| Window
+ | Shapes
+ | Container
+ background = none
+ fill = True
+ viewBox = -100 -100 200 200
+
+ | Group
+ x = 0
+ y = 0
+ rotation = 0
+ children =
+ | Line
+ x1 = 0
+ y1 = 0
+ x2 = 80
+ y2 = 0
+ color = skyblue
+
+ | Dot
+ cx = 80
+ cy = 0
+ radius = 10
+ color = skyblue
+
+ | Group
+ x = 0
+ y = 0
+ rotation = 72
+ children =
+ | Line
+ x1 = 0
+ y1 = 0
+ x2 = 80
+ y2 = 0
+ color = orange
+
+ | Dot
+ cx = 80
+ cy = 0
+ radius = 10
+ color = orange
+
+ | Group
+ x = 0
+ y = 0
+ rotation = 144
+ children =
+ | Line
+ x1 = 0
+ y1 = 0
+ x2 = 80
+ y2 = 0
+ color = red
+
+ | Dot
+ cx = 80
+ cy = 0
+ radius = 10
+ color = red
+
+ | Group
+ x = 0
+ y = 0
+ rotation = 216
+ children =
+ | Line
+ x1 = 0
+ y1 = 0
+ x2 = 80
+ y2 = 0
+ color = lime
+
+ | Dot
+ cx = 80
+ cy = 0
+ radius = 10
+ color = lime
+
+ | Group
+ x = 0
+ y = 0
+ rotation = 288
+ children =
+ | Line
+ x1 = 0
+ y1 = 0
+ x2 = 80
+ y2 = 0
+ color = maroon
+
+ | Dot
+ cx = 80
+ cy = 0
+ radius = 10
+ color = maroon
=
=| Emphasize
= Congratulations!index 485e44b..76067a3 100644
--- a/src/Examples/Shapes.elm
+++ b/src/Examples/Shapes.elm
@@ -140,7 +140,7 @@ ui { container, shapes } =
= LineSegment2d.from a b
= |> Geometry.Svg.lineSegment2d
= [ Svg.Attributes.stroke color
- , Svg.Attributes.strokeWidth "3"
+ , Svg.Attributes.strokeWidth "1"
= ]
=
= Group { rotation, x, y } ss ->WIP add Editor block to Code block.
Correct typos.
index d67a0ae..dbba549 100644
--- a/content/day-1.txt
+++ b/content/day-1.txt
@@ -79,14 +79,18 @@ Just press {Key|enter} once again to proceed. Then to create the file which will
=
=It may take few seconds, but eventually this command should open a new Atom editor window with an empty text file. If you prefer to use different editor, feel free to do so. Once the editor is ready, type the code below and save the file.
=
-| Code
- module Main exposing (main)
+| Editor
+ | Annotations
+ | None
=
+ | Code
+ module Main exposing (main)
=
- import Html
=
- main =
- Html.text "Hello, Tree"
+ import Html
+
+ main =
+ Html.text "Hello, Tree"
=
=Then switch back to the terminal and enter the following command:
=
@@ -126,31 +130,35 @@ We want to display a dot at the center of the screen, like this:
=
=Below is the complete code to draw a dot like this, but don't type it in your editor yet! We are going to recreate it together, step by step.
=
-| Code
- module Main exposing (main)
+| Editor
+ | Annotations
+ | None
=
- import Element
- import Svg
- import Svg.Attributes
+ | Code
+ module Main exposing (main)
=
+ import Element
+ import Svg
+ import Svg.Attributes
=
- main =
- Svg.svg
- [ Svg.Attributes.viewBox "-100 -100 200 200"
- ]
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.color "skyblue"
+
+ main =
+ Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200"
= ]
- []
- ]
- |> Element.html
- |> Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.color "skyblue"
+ ]
+ []
= ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
=
=We are going to use a technology called *SVG (Scalable Vector Graphics)*. It's all about drawing shapes on the screen. Let's install an Elm Package to help us work with SVG. Stop the reactor running in the terminal by pressing {Key|ctrl} + {Key|c} and then enter the following command:
=
@@ -177,18 +185,22 @@ In response you should see something like this:
=
=Press {Key|enter} to proceed with the installation. Now that we have {Code|elm//svg} installed, we can write the code that will draw a dot for us. Change the code to look like this:
=
-| Code
- module Main exposing (main)
+| Editor
+ | Annotations
+ | None
=
- import Svg
- import Svg.Attributes
+ | Code
+ module Main exposing (main)
=
+ import Svg
+ import Svg.Attributes
=
- main =
- Svg.svg []
- [ Svg.circle
- [ Svg.Attributes.r "10" ] []
- ]
+
+ main =
+ Svg.svg []
+ [ Svg.circle
+ [ Svg.Attributes.r "10" ] []
+ ]
=
=Start the reactor again by entering:
=
@@ -219,19 +231,23 @@ It's a good start, but the dot is not in the center of the screen yet. Let's try
=
=First we have to realize that the dot is inside an {Code|svg} element that itself has a position, width and height. We can make the background of the {Code|svg} element pink to see where it is:
=
-| Code
- module Main exposing (main)
+| Editor
+ | Annotations
+ | None
=
- import Svg
- import Svg.Attributes
+ | Code
+ module Main exposing (main)
=
+ import Svg
+ import Svg.Attributes
=
- main =
- Svg.svg [ Svg.Attributes.style "background: pink" ]
- [ Svg.circle
- [ Svg.Attributes.r "10" ]
- []
- ]
+
+ main =
+ Svg.svg [ Svg.Attributes.style "background: pink" ]
+ [ Svg.circle
+ [ Svg.Attributes.r "10" ]
+ []
+ ]
=
=Reloading the browser should reveal something like this:
=
@@ -257,30 +273,34 @@ To do this we will use a package called {Code|mdgriffith//elm-ui}. It makes layi
=
=As before, press {Key|enter} to follow the plan. Now let's use the package. Change the code to look like this:
=
-| Code
- module Main exposing (main)
+| Editor
+ | Annotations
+ | None
=
- import Element
- import Svg
- import Svg.Attributes
+ | Code
+ module Main exposing (main)
=
+ import Element
+ import Svg
+ import Svg.Attributes
=
- main =
- Svg.svg
- [ Svg.Attributes.style "background: pink"
- , Svg.Attributes.height "100%"
- , Svg.Attributes.width "100%"
- ]
- [ Svg.circle
- [ Svg.Attributes.r "10"
+
+ main =
+ Svg.svg
+ [ Svg.Attributes.style "background: pink"
+ , Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
= ]
- []
- ]
- |> Element.html
- |> Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ ]
+ []
= ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
=
=
=| Note
@@ -336,32 +356,40 @@ Initially the center of the dot is at a point called /the origin/ or {Code|\{ x
=
=To change the position of the dot, we can use the {Code|cx} and {Code|cy} attributes of the {Code|circle} element, like this:
=
-| Code
- module Main exposing (main)
+| Editor
+ | Annotations
+ | Highlight
+ top = 11
+ left = 8
+ width = 25
+ height = 2
=
- import Element
- import Svg
- import Svg.Attributes
+ | Code
+ module Main exposing (main)
=
+ import Element
+ import Svg
+ import Svg.Attributes
=
- main =
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "100"
- , Svg.Attributes.cy "50"
- ]
- []
- ]
- |> Svg.svg
- [ Svg.Attributes.height "100%"
- , Svg.Attributes.width "100%"
- , Svg.Attributes.style "background: pink"
- ]
- |> Element.html
- |> Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
+
+ main =
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "100"
+ , Svg.Attributes.cy "50"
+ ]
+ []
= ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: pink"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
=
=| Window
= | Dots
@@ -395,7 +423,7 @@ Fortunately, we don't need to know the width and height of the {Code|svg} elemen
=
=You can imagine an SVG element as an infinite surface on which elements are placed. Which elements you see, depends on where you look. And where you look is called the *viewbox*. It has four properties: {Code|top}, {Code|left}, {Code|width} and {Code|height}.
=
-It works a little bit like a photo camera pointing directly at the surface. You can move it up, down (along the {Code|y} axis) and left and right (along the {Code|x} axis). You can also change it's focal length to cover more or less area. Unlike a lens of the camera you set the width and height separately. If you set it so that the dot is in the middle of your frame, then no matter how big the print of the picture will be, the dot will always be in the center. Increasing the {Code|width} and {Code|height} of the viewbox will make more of the surrounding surface visible and the dot will become smaller, but it's position won't change. Let's see it in action:
+It works a little bit like a photo camera pointing directly at the surface. You can move it up, down (along the {Code|y} axis) and left and right (along the {Code|x} axis). You can also change its focal length to cover more or less area. Unlike a lens of the camera you set the width and height separately. If you set it so that the dot is in the middle of your frame, then no matter how big the print of the picture will be, the dot will always be in the center. Increasing the {Code|width} and {Code|height} of the viewbox will make more of the surrounding surface visible and the dot will become smaller, but it's position won't change. Let's see it in action:
=
=| Row
= | ViewBox
@@ -420,33 +448,51 @@ So instead of moving the dot in the SVG plane, we change where we look by moving
=
=In the code we set the viewbox as an attribute of the {Code|svg} element, like this:
=
-| Code
- module Main exposing (main)
+| Editor
+ | Annotations
+ | Highlight
+ top = 20
+ left = 8
+ width = 44
+ height = 1
=
- import Element
- import Svg
- import Svg.Attributes
+ | Highlight
+ top = 11
+ left = 8
+ width = 23
+ height = 2
=
+ | Fold
+ start = 1
+ length = 7
=
- main =
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- ]
- []
- ]
- |> Svg.svg
- [ Svg.Attributes.height "100%"
- , Svg.Attributes.width "100%"
- , Svg.Attributes.style "background: pink"
- , Svg.Attributes.viewBox "-300 -300 600 600"
- ]
- |> Element.html
- |> Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
+ | Code
+ module Main exposing (main)
+
+ import Element
+ import Svg
+ import Svg.Attributes
+
+
+ main =
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ ]
+ []
= ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: pink"
+ , Svg.Attributes.viewBox "-300 -300 600 600"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
=
=There are only two changes here. First we reset the {Code|cx} and {Code|cy} attributes of the {Code|circle} back to {Code|"0"} (on lines 11 and 12). We want the dot to be back at origin. Then we set up the position of the viewbox on line 20. Try experimenting with different values. If you set them right and refresh the browser the dot should be in the center of the screen.
=
@@ -470,33 +516,49 @@ You can also remove line 19 to remove the pink background, or set it to some oth
=
=Speaking of colors, now that we've centered our dot, it's time to give it a color! We do it by adding a {Code|fill} attribute to the {Code|circle} element, like that:
=
-| Code
- module Main exposing (main)
+| Editor
+ | Annotations
+ | Highlight
+ top = 13
+ left = 8
+ width = 31
+ height = 1
=
- import Element
- import Svg
- import Svg.Attributes
+ | Fold
+ start = 1
+ length = 7
=
+ | Fold
+ start = 17
+ length = 12
=
- main =
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill "skyblue"
- ]
- []
- ]
- |> Svg.svg
- [ Svg.Attributes.height "100%"
- , Svg.Attributes.width "100%"
- , Svg.Attributes.viewBox "-300 -300 600 600"
- ]
- |> Element.html
- |> Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
+ | Code
+ module Main exposing (main)
+
+ import Element
+ import Svg
+ import Svg.Attributes
+
+
+ main =
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "skyblue"
+ ]
+ []
= ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.viewBox "-300 -300 600 600"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
=
=The only change is on line 13. Reload the browser and see this:
=index d0a4ad1..9c0d915 100644
--- a/content/day-2.txt
+++ b/content/day-2.txt
@@ -43,40 +43,52 @@ First obvious difference is that now we have multiple dots. The only dot we have
=
=If you look closely in your source code, the block above is surrounded by {Code|[} and {Code|]} characters. That's a list. We will talk about lists during the next day. For now it's enough to say that a list can contain zero, one or more items of the same type. In this case it contains one item of type {Code|Svg msg} - our lonely, blue dot. Let's add a second one like this:
=
-| Code
- module Main exposing (main)
+| Editor
+ | Annotations
+ | None
=
- import Element
- import Svg
- import Svg.Attributes
+ | Fold
+ start = 1
+ length = 7
=
+ | Fold
+ start = 24
+ length = 12
=
- main =
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill "skyblue"
- ]
- []
- , Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill "orange"
- ]
- []
- ]
- |> Svg.svg
- [ Svg.Attributes.height "100%"
- , Svg.Attributes.width "100%"
- , Svg.Attributes.viewBox "-300 -300 600 600"
- ]
- |> Element.html
- |> Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
+ | Code
+ module Main exposing (main)
+
+ import Element
+ import Svg
+ import Svg.Attributes
+
+
+ main =
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "skyblue"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "orange"
+ ]
+ []
= ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.viewBox "-300 -300 600 600"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
=
=Now the list spans from lines 9 to 23 and contains two items: skyblue and orange dots. The result should be:
=
@@ -101,40 +113,56 @@ Now the list spans from lines 9 to 23 and contains two items: skyblue and orange
=
=Oh oh! The list contains two dots, but we can only see the orange one on the screen. Where is the blue one? It's behind the orange. Recall how the cartesian coordinates work: if {Code|x} and {Code|y} of the center are the same, then the dots are in the same place. If we want to see both dots, let's move one of them to a different place, like this:
=
-| Code
- module Main exposing (main)
+| Editor
+ | Annotations
+ | Highlight
+ top = 18
+ left = 8
+ width = 24
+ height = 1
=
- import Element
- import Svg
- import Svg.Attributes
+ | Fold
+ start = 1
+ length = 7
=
+ | Fold
+ start = 24
+ length = 12
=
- main =
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill "skyblue"
- ]
- []
- , Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "50"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill "orange"
- ]
- []
- ]
- |> Svg.svg
- [ Svg.Attributes.height "100%"
- , Svg.Attributes.width "100%"
- , Svg.Attributes.viewBox "-300 -300 600 600"
- ]
- |> Element.html
- |> Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
+ | Code
+ module Main exposing (main)
+
+ import Element
+ import Svg
+ import Svg.Attributes
+
+
+ main =
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "skyblue"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "50"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "orange"
+ ]
+ []
= ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.viewBox "-300 -300 600 600"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
=
=Now you should see this:
=
@@ -205,10 +233,14 @@ We can say that dots lay on a circle, if the distance to the center is the same
=
=We call this distance /a *radius* of a circle/. We already saw radius used as an attribute of a circle when we were making our first dot. Remember:
=
-| Code
- Svg.circle
- [ Svg.Attributes.r "10"]
- []
+| Editor
+ | Annotations
+ | None
+
+ | Code
+ Svg.circle
+ [ Svg.Attributes.r "10"]
+ []
=
=The {Code|r} attribute stands for radius. Then it was basically dictating the size of the dot. The larger the radius the bigger the circle. A dot is just a filled circle!
=
@@ -424,30 +456,34 @@ Now that we understand what it means to be placed on a circle and evenly distrib
=
=We will start from where we left off yesterday. Open {Code|src//Main.elm} in your editor. It should have the following code:
=
-| Code
- module Main exposing (main)
+| Editor
+ | Annotations
+ | None
=
- import Element
- import Svg
- import Svg.Attributes
+ | Code
+ module Main exposing (main)
=
+ import Element
+ import Svg
+ import Svg.Attributes
=
- main =
- Svg.svg
- [ Svg.Attributes.viewBox "-100 -100 200 200"
- ]
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
+
+ main =
+ Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200"
= ]
- []
- ]
- |> Element.html
- |> Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ ]
+ []
= ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
=
=It's a good starting point. We already have a single dot in the middle. Let's say this will be the center of our circle.
=
@@ -461,16 +497,20 @@ This will move the dot ("translate" is a fancy term for "move") 80 units.
=Also, let's change the color of the dot. In the end it should look like this:
=
=
-| Code
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.transform "translate(80)"
- , Svg.Attributes.fill "skyblue"
+| Editor
+ | Annotations
+ | None
+
+ | Code
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.transform "translate(80)"
+ , Svg.Attributes.fill "skyblue"
+ ]
+ []
= ]
- []
- ]
=
=With this change, start the reactor by typing in your terminal:
=
@@ -493,78 +533,102 @@ As you can see the dot is no longer in the center - it has moved!
=
=Now we need a second dot. If you remember the discussion about lists from previous day, you know what to do. Just duplicate the first SVG circle and put it after the first one, with a coma in between. Like this:
=
-| Code
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.transform "translate(80)"
- , Svg.Attributes.fill "skyblue"
- ]
- []
- , Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill "orange"
- , Svg.Attributes.transform "translate(80)"
+| Editor
+ | Annotations
+ | None
+
+ | Code
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.transform "translate(80)"
+ , Svg.Attributes.fill "skyblue"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "orange"
+ , Svg.Attributes.transform "translate(80)"
+ ]
+ []
= ]
- []
- ]
=
=Now let's focus on the second one. We gave it a different color, so we can distinguish between them, but now we don't see the first one! That's because it's exactly in the same spot as the second one, which covers it. They are stacked in a way. We need to move the second dot in a different direction, so it's on a circle, but 72 degree apart from the first one.
=
=For that we need to add another transformation before the move. First we will rotate, then translate:
=
-| Code
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.transform "translate(80)"
- , Svg.Attributes.fill "skyblue"
- ]
- []
- , Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill "orange"
- , Svg.Attributes.transform "rotate(72) translate(80)"
+| Editor
+ | Annotations
+ | Highlight
+ top = 14
+ left = 6
+ width = 53
+ height = 1
+
+ | Code
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.transform "translate(80)"
+ , Svg.Attributes.fill "skyblue"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "orange"
+ , Svg.Attributes.transform "rotate(72) translate(80)"
+ ]
+ []
= ]
- []
- ]
=
=This will rotate the element by 72 degrees. On its own it wouldn't make any difference, because the dot is round - it doesn't matter how you rotate it, it always looks the same. But It also rotates it's internal coordinates system. So the translation will move it in a different direction then the first dot.
=
=Now it's time to add the third, red dot. Duplicate the second one, change fill to `"red"` and put `144` for rotate function, like this:
=
-| Code
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.transform "translate(80)"
- , Svg.Attributes.fill "skyblue"
- ]
- []
- , Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill "orange"
- , Svg.Attributes.transform "rotate(72) translate(80)"
- ]
- []
- , Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill "red"
- , Svg.Attributes.transform "rotate(144) translate(80)"
+| Editor
+ | Annotations
+ | Highlight
+ top = 22
+ left = 6
+ width = 54
+ height = 1
+
+ | Fold
+ start = 1
+ length = 16
+
+ | Code
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.transform "translate(80)"
+ , Svg.Attributes.fill "skyblue"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "orange"
+ , Svg.Attributes.transform "rotate(72) translate(80)"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "red"
+ , Svg.Attributes.transform "rotate(144) translate(80)"
+ ]
+ []
= ]
- []
- ]
=
=Why 144? It's because 72 + 72 is 144! So to be 72 degree apart from a dot that's at 72 degree, you need to be at 144 (or 0, but the first dot is already there).
=
@@ -573,64 +637,86 @@ This makes the last two dots trivial. Just rotate them to 216 and 288 and give t
=
=The complete program should look like this:
=
-| Code
- module Main exposing (main)
+| Editor
+ | Annotations
+ | Highlight
+ top = 41
+ left = 12
+ width = 54
+ height = 1
=
- import Element
- import Svg
- import Svg.Attributes
+ | Highlight
+ top = 49
+ left = 12
+ width = 54
+ height = 1
=
+ | Fold
+ start = 1
+ length = 35
=
- main =
- Svg.svg
- [ Svg.Attributes.viewBox "-100 -100 200 200"
- ]
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.transform "translate(80)"
- , Svg.Attributes.fill "skyblue"
- ]
- []
- , Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill "orange"
- , Svg.Attributes.transform "rotate(72) translate(80)"
- ]
- []
- , Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill "red"
- , Svg.Attributes.transform "rotate(144) translate(80)"
- ]
- []
- , Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill "lime"
- , Svg.Attributes.transform "rotate(216) translate(80)"
- ]
- []
- , Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill "maroon"
- , Svg.Attributes.transform "rotate(288) translate(80)"
+ | Fold
+ start = 53
+ length = 7
+
+ | Code
+ module Main exposing (main)
+
+ import Element
+ import Svg
+ import Svg.Attributes
+
+
+ main =
+ Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200"
= ]
- []
- ]
- |> Element.html
- |> Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.transform "translate(80)"
+ , Svg.Attributes.fill "skyblue"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "orange"
+ , Svg.Attributes.transform "rotate(72) translate(80)"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "red"
+ , Svg.Attributes.transform "rotate(144) translate(80)"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "lime"
+ , Svg.Attributes.transform "rotate(216) translate(80)"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "maroon"
+ , Svg.Attributes.transform "rotate(288) translate(80)"
+ ]
+ []
= ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
=
=In the browser it should look exactly as we planned:
=index 155a00b..5e97ebd 100644
--- a/content/day-3.txt
+++ b/content/day-3.txt
@@ -255,64 +255,84 @@ If the name comes from an imported module, you need to prefix it with the name o
=
=Let's look at our code and try to recognize some literal values, lists, names and functions. Here is how it should look after day 2:
=
-| Code
- module Main exposing (main)
-
- import Element
- import Svg
- import Svg.Attributes
-
-
- main =
- Svg.svg
- [ Svg.Attributes.viewBox "-100 -100 200 200"
- ]
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.transform "translate(80)"
- , Svg.Attributes.fill "skyblue"
+| Editor
+ | Annotations
+ | Highlight
+ top = 3
+ left = 1
+ width = 21
+ height = 3
+
+ | Highlight
+ top = 10
+ left = 34
+ width = 19
+ height = 1
+
+ | Highlight
+ top = 8
+ left = 1
+ width = 6
+ height = 1
+
+ | Code
+ module Main exposing (main)
+
+ import Element
+ import Svg
+ import Svg.Attributes
+
+
+ main =
+ Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200"
= ]
- []
- , Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill "orange"
- , Svg.Attributes.transform "rotate(72) translate(80)"
- ]
- []
- , Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill "red"
- , Svg.Attributes.transform "rotate(144) translate(80)"
- ]
- []
- , Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill "lime"
- , Svg.Attributes.transform "rotate(216) translate(80)"
- ]
- []
- , Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill "maroon"
- , Svg.Attributes.transform "rotate(288) translate(80)"
- ]
- []
- ]
- |> Element.html
- |> Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.transform "translate(80)"
+ , Svg.Attributes.fill "skyblue"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "orange"
+ , Svg.Attributes.transform "rotate(72) translate(80)"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "red"
+ , Svg.Attributes.transform "rotate(144) translate(80)"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "lime"
+ , Svg.Attributes.transform "rotate(216) translate(80)"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "maroon"
+ , Svg.Attributes.transform "rotate(288) translate(80)"
+ ]
+ []
= ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
=
=Take your time. Here are some hints to help you:
=
@@ -348,17 +368,162 @@ This block of code is repeated five times in our code - once for each dot. Now I
=
=First let's make them even more uniform by adding {Code|rotate(0)} to the first block. It will help us later. The code should look like this:
=
-| Code
- TODO
+| Editor
+ | Annotations
+ | Highlight
+ top = 16
+ left = 12
+ width = 52
+ height = 1
+
+ | Fold
+ start = 1
+ length = 7
=
+ | Fold
+ start = 20
+ length = 42
=
+ | Code
+ module Main exposing (main)
+
+ import Element
+ import Svg
+ import Svg.Attributes
+
+
+ main =
+ Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200"
+ ]
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.transform "rotate(0) translate(80)"
+ , Svg.Attributes.fill "skyblue"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "orange"
+ , Svg.Attributes.transform "rotate(72) translate(80)"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "red"
+ , Svg.Attributes.transform "rotate(144) translate(80)"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "lime"
+ , Svg.Attributes.transform "rotate(216) translate(80)"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "maroon"
+ , Svg.Attributes.transform "rotate(288) translate(80)"
+ ]
+ []
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
=
=Then let's give the value produced by this block a name. We can call it the {Code|dot}. Select the first block that you have just modified, everything between {Code|[} and the {Code|,} on line 20 and cut it with {Key|command} + {Key|x}. Then in the empty space left type {Code|dot}.
=
=Move your cursor to the end of the file and type {Code|dot =}, press {Key|enter} and paste the code you cut before with {Key|command} + {Key|v}. The code should now look like this:
=
-| Code
- TODO
+| Editor
+ | Annotations
+ | Highlight
+ top = 12
+ left = 11
+ width = 3
+ height = 1
+
+ | Fold
+ start = 1
+ length = 7
+
+ | Fold
+ start = 21
+ length = 30
+
+ | Code
+ module Main exposing (main)
+
+ import Element
+ import Svg
+ import Svg.Attributes
+
+
+ main =
+ Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200"
+ ]
+ [ dot
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "orange"
+ , Svg.Attributes.transform "rotate(72) translate(80)"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "red"
+ , Svg.Attributes.transform "rotate(144) translate(80)"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "lime"
+ , Svg.Attributes.transform "rotate(216) translate(80)"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "maroon"
+ , Svg.Attributes.transform "rotate(288) translate(80)"
+ ]
+ []
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+
+ dot = Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.transform "rotate(0) translate(80)"
+ , Svg.Attributes.fill "skyblue"
+ ]
+ []
=
=Reload the browser. Everything should work exactly the same as before. One of the dots is now a named value, but for our program it makes no difference. What is important is its value, not where it comes from.
=
@@ -374,74 +539,265 @@ Of course the goal is to have our list of dots looking like this:
=
=Let's try it. The effect will be that all the dots will be in the same place, one on top of another. Also they will all have the same color. So on the screen you should see only one dot, somewhat to the right of center.
=
-The dots need to have some parameters so they can be different. We already identified them: color and rotation. Above I said that a function is a parametrized value. Let's turn our dot definition on line {Code|TODO} into a function with two parameters like this:
+The dots need to have some parameters so they can be different. We already identified them: color and rotation. Above I said that a function is a parametrized value. Let's turn our dot definition on line {Code|52} into a function with two parameters like this:
=
-| Code
- TODO: Rest of the code
+| Editor
+ | Annotations
+ | None
=
- dot color rotation =
- Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill "skyblue"
- , Svg.Attributes.transform "rotate(0) translate(80)"
- ]
- []
+ | Fold
+ start = 1
+ length = 7
=
-This way we declared that when calling a {Code|dot} name you will provide two values: first one for {Code|color} and second for {Code|rotation}. Let's stay true to our word and provide it. Change the list on lines {Code|todo} to look like this:
+ | Fold
+ start = 13
+ length = 38
=
-| Code
- TODO: Rest of the code
+ | Code
+ module Main exposing (main)
=
- [ dot "skyblue" 0
- , dot "orange" 72
- , dot "red" 144
- , dot "lime" 216
- , dot "maroon" 288
- ]
+ import Element
+ import Svg
+ import Svg.Attributes
+
+
+ main =
+ Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200"
+ ]
+ [ dot color rotation
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "orange"
+ , Svg.Attributes.transform "rotate(72) translate(80)"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "red"
+ , Svg.Attributes.transform "rotate(144) translate(80)"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "lime"
+ , Svg.Attributes.transform "rotate(216) translate(80)"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "maroon"
+ , Svg.Attributes.transform "rotate(288) translate(80)"
+ ]
+ []
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+ dot color rotation =
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "skyblue"
+ , Svg.Attributes.transform "rotate(0) translate(80)"
+ ]
+ []
+
+This way we declared that when calling a {Code|dot} name you will provide two values: first one for {Code|color} and second for {Code|rotation}. Let's stay true to our word and provide it. Change the list on lines {Code|12 - 16} to look like this:
+
+| Editor
+ | Annotations
+ | None
+
+ | Fold
+ start = 1
+ length = 7
+
+ | Fold
+ start = 18
+ length = 5
+
+ | Code
+ module Main exposing (main)
+
+ import Element
+ import Svg
+ import Svg.Attributes
+
+
+ main =
+ Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200"
+ ]
+ [ dot "skyblue" 0
+ , dot "orange" 72
+ , dot "red" 144
+ , dot "lime" 216
+ , dot "maroon" 288
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+ dot color rotation =
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "skyblue"
+ , Svg.Attributes.transform "rotate(0) translate(80)"
+ ]
+ []
=
=Let's reload again and see that there is still only one, skyblue dot visible. That's because we did not tell Elm what to do with the values of the two parameters we provide for the {Code|dot} function. We gave them names ({Code|color} and {Code|rotation}), but never called them.
=
-First, let's use the {Code|color} parameter. If you want to change the color of the dot, you need to pass a different value to the {Code|Svg.Attributes.fill} function on line {Code|TODO}. Currently the value is a literal string {Code|"skyblue"}. Instead we want to give it whatever value was given for the {Code|color} parameter. We do it by simply calling the name of the parameter in place of a literal value, like this:
+First, let's use the {Code|color} parameter. If you want to change the color of the dot, you need to pass a different value to the {Code|Svg.Attributes.fill} function on line {Code|12}. Currently the value is a literal string {Code|"skyblue"}. Instead we want to give it whatever value was given for the {Code|color} parameter. We do it by simply calling the name of the parameter in place of a literal value, like this:
=
+| Editor
+ | Annotations
+ | Highlight
+ top = 29
+ left = 31
+ width = 5
+ height = 1
=
-| Code
- TODO: Rest of the code
+ | Fold
+ start = 1
+ length = 7
=
- dot color rotation =
- Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill color
- , Svg.Attributes.transform "rotate(0) translate(80)"
- ]
- []
+ | Fold
+ start = 18
+ length = 5
+
+ | Code
+ module Main exposing (main)
+
+ import Element
+ import Svg
+ import Svg.Attributes
+
+
+ main =
+ Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200"
+ ]
+ [ dot "skyblue" 0
+ , dot "orange" 72
+ , dot "red" 144
+ , dot "lime" 216
+ , dot "maroon" 288
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+ dot color rotation =
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform "rotate(0) translate(80)"
+ ]
+ []
=
=Now let's do the same for rotation. This one is more complex, because we cannot just pass {Code|rotation} value to the {Code|Svg.Attributes.transform} function. The {Code|rotation} is just a number, while the function expects a string formatted like this: {Code|"rotate(...) translate(80)"}.
=
=We can use the {Code|++} operator to glue the strings together. First part is constant, so we can just give it a literal value of {Code|"rotate("}. After that we put the {Code|++} and our variable part: {Code|rotation}. Finally the rest of the string is also constant: {Code|" translate(80)"} (notice the space before {Code|translate}!). All together we have:
=
-| Code
- TODO: Rest of the code
+| Editor
+ | Annotations
+ | None
=
- dot color rotation =
- Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill color
- , Svg.Attributes.transform "rotate(" ++ rotation ++ ") translate(80)"
- ]
- []
+ | Fold
+ start = 1
+ length = 23
+
+ | Code
+ module Main exposing (main)
+
+ import Element
+ import Svg
+ import Svg.Attributes
+
+
+ main =
+ Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200"
+ ]
+ [ dot "skyblue" 0
+ , dot "orange" 72
+ , dot "red" 144
+ , dot "lime" 216
+ , dot "maroon" 288
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+ dot color rotation =
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform "rotate(" ++ rotation ++ ") translate(80)"
+ ]
+ []
=
=But Elm will complain about this. First it looks like we gave 5 arguments to the {Code|Svg.Attributes.transform} function, and it only takes one. We can fix it by putting a parentheses around the {Code|"rotate(" ++ rotation ++ ") translate(80)" }, basically telling Elm that this is a single expression and it should evaluate its value before passing it to the {Code|Svg.Attributes.transform} function. Now it should look like this:
=
-| Code
- TODO: Rest of the code
+| Editor
+ | Annotations
+ | None
+
+ | Fold
+ start = 1
+ length = 23
+
+ | Code
+ module Main exposing (main)
=
- dot color rotation =
+ import Element
+ import Svg
+ import Svg.Attributes
+
+
+ main =
+ Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200"
+ ]
+ [ dot "skyblue" 0
+ , dot "orange" 72
+ , dot "red" 144
+ , dot "lime" 216
+ , dot "maroon" 288
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+ dot color rotation =
= Svg.circle
= [ Svg.Attributes.r "10"
= , Svg.Attributes.cx "0"
@@ -454,7 +810,26 @@ But Elm will complain about this. First it looks like we gave 5 arguments to the
=Here we face another problem. Elm complains with the following:
=
=| Monospace
- TODO: Provide error message. Make sure the line and column numbers are correct.
+ -- TYPE MISMATCH ------------------------------------------------ src/Main.elm
+
+ The 2nd argument to `dot` is not what I expect:
+
+ 12| [ dot "skyblue" 0
+ ^
+ This argument is a number of type:
+
+ number
+
+ But `dot` needs the 2nd argument to be:
+
+ String
+
+ Hint: I always figure out the argument types from left to right. If an argument
+ is acceptable, I assume it is "correct" and move on. So the problem may actually
+ be in one of the previous arguments!
+
+ Hint: Try using String.fromInt to convert it to a string?
+
=
=It's the type system in action. We can only use {Code|++} function with both arguments being the same type. Here one argument is:
=
@@ -480,36 +855,71 @@ Elm won't have that. Fortunately there is an easy way to satisfy the type system
=
=That's what we need! Let's pass the value of {Code|rotation} through this function like this:
=
-| Code
- TODO: Rest of the code
+| Editor
+ | Annotations
+ | None
=
- dot color rotation =
- Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill color
- , Svg.Attributes.transform ("rotate(" ++ (String.fromFloat rotation) ++ ") translate(80)")
- ]
- []
+ | Fold
+ start = 1
+ length = 23
=
-| Code
- IDEA: What if we use String.concat instead?
=
- dot color rotation =
- Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill color
- , Svg.Attributes.transform (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ") translate(80)"
+ | Code
+ module Main exposing (main)
+
+ import Element
+ import Svg
+ import Svg.Attributes
+
+
+ main =
+ Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200"
= ]
- )
- ]
- []
+ [ dot "skyblue" 0
+ , dot "orange" 72
+ , dot "red" 144
+ , dot "lime" 216
+ , dot "maroon" 288
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+
+ dot color rotation =
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform ("rotate(" ++ (String.fromFloat rotation) ++ ") translate(80)")
+ ]
+ []
+
+| Editor
+ | Annotations
+ | None
+
+ | Code
+ IDEA: What if we use String.concat instead?
+
+ dot color rotation =
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ []
=
=This should work! The browser should now present the dots as intended and I hope you agree that the code is more readable now.
=
@@ -518,24 +928,28 @@ This should work! The browser should now present the dots as intended and I hope
=
=Armed with our functional superpowers, let's make another function that draws a line. Let me first show you a complete code and then we can discuss it.
=
-| Code
- line color rotation =
- Svg.line
- [ Svg.Attributes.strokeWidth "1"
- , Svg.Attributes.x1 "0"
- , Svg.Attributes.y1 "0"
- , Svg.Attributes.x1 "80"
- , Svg.Attributes.y1 "0"
- , Svg.Attributes.stroke color
- , Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ")"
- ]
- )
- ]
- []
+| Editor
+ | Annotations
+ | None
+
+ | Code
+ line color rotation =
+ Svg.line
+ [ Svg.Attributes.strokeWidth "1"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x1 "80"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.stroke color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ")"
+ ]
+ )
+ ]
+ []
=
=It works similar to the {Code|dot} function so you can just copy and paste it. Then apply the following changes:
=
@@ -554,70 +968,85 @@ It works similar to the {Code|dot} function so you can just copy and paste it. T
=
=Once you have the function defined, let's use it. For every dot, create one line, like so:
=
-| Code
- module Main exposing (main)
-
- import Element
- import Svg
- import Svg.Attributes
+| Editor
+ | Annotations
+ | None
+
+ | Fold
+ start = 1
+ length = 7
+
+ | Fold
+ start = 20
+ length = 49
+
+ | Code
+ module Main exposing (main)
+
+ import Element
+ import Svg
+ import Svg.Attributes
+
+
+ main =
+ [ dot "skyblue" 0
+ , line "skyblue" 0
+ , dot "orange" 72
+ , line "orange" 72
+ , dot "red" 144
+ , line "red" 144
+ , dot "lime" 216
+ , line " lime" 216
+ , dot "maroon" 288
+ , line " maroon" 288
+ ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: none"
+ , Svg.Attributes.viewBox "-100 -100 200 200"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
=
=
- main =
- [ dot "skyblue" 0
- , line "skyblue" 0
- , dot "orange" 72
- , line "orange" 72
- , dot "red" 144
- , dot "lime" 216
- , dot "maroon" 288
- ]
- |> Svg.svg
- [ Svg.Attributes.height "100%"
- , Svg.Attributes.width "100%"
- , Svg.Attributes.style "background: none"
- , Svg.Attributes.viewBox "-100 -100 200 200"
- ]
- |> Element.html
- |> Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
+ dot color rotation =
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
= ]
+ []
=
=
- dot color rotation =
- Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill color
- , Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ") translate(80)"
- ]
- )
- ]
- []
-
-
- line color rotation =
- Svg.line
- [ Svg.Attributes.strokeWidth "1"
- , Svg.Attributes.x1 "0"
- , Svg.Attributes.y1 "0"
- , Svg.Attributes.x1 "80"
- , Svg.Attributes.y1 "0"
- , Svg.Attributes.stroke color
- , Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ")"
- ]
- )
- ]
- []
+ line color rotation =
+ Svg.line
+ [ Svg.Attributes.strokeWidth "1"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x1 "80"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.stroke color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ")"
+ ]
+ )
+ ]
+ []
=
=You should see something like this in the browser:
=index 03acab1..9d4ca1c 100644
--- a/content/day-4.txt
+++ b/content/day-4.txt
@@ -23,104 +23,128 @@
=
=The tree is built from segments: a line and a dot. In this respect it is similar to the connected dots we created yesterday. If we group the dot and a line, we will have the building block for our tree. We can do it with {Code|Svg.g} function ({Code|g} is an abbreviation of "group"). Just like {Code|Svg.svg}, it takes list of attributes and list of children. We can use it like that:
=
-| Code
- main =
- [ Svg.g []
- [ dot "skyblue" 0
- , line "skyblue" 0
- ]
- , Svg.g []
- [ dot "orange" 72
- , line "orange" 72
- ]
- , Svg.g []
- [ dot "red" 144
- , line "red" 144
- ]
- , Svg.g []
- [ dot "lime" 216
- , line "lime" 216
- ]
- , Svg.g []
- [ dot "maroon" 288
- , line "maroon" 288
- ]
- ]
- |> Svg.svg
- [ Svg.Attributes.height "100%"
- , Svg.Attributes.width "100%"
- , Svg.Attributes.style "background: none"
- , Svg.Attributes.viewBox "-100 -100 200 200"
+| Editor
+ | Annotations
+ | None
+
+ | Fold
+ start = 23
+ length = 13
+
+ | Code
+ main =
+ [ Svg.g []
+ [ dot "skyblue" 0
+ , line "skyblue" 0
+ ]
+ , Svg.g []
+ [ dot "orange" 72
+ , line "orange" 72
= ]
- |> Element.html
- |> Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
+ , Svg.g []
+ [ dot "red" 144
+ , line "red" 144
= ]
+ , Svg.g []
+ [ dot "lime" 216
+ , line "lime" 216
+ ]
+ , Svg.g []
+ [ dot "maroon" 288
+ , line "maroon" 288
+ ]
+ ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: none"
+ , Svg.Attributes.viewBox "-100 -100 200 200"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
=
=Reload the browser there should be no difference at this point. Looking back at code I notice that each segment looks the same: it is a group with a dot and a line, where the {Code|group} and {Code|line} functions have the same arguments passed to them. This kind of repetition begs for a function. Let's call it {Code|segment} and define like this:
=
-| Code
- segment color rotation =
- Svg.g []
- [ dot color rotation
- , line color rotation
- ]
+| Editor
+ | Annotations
+ | None
+
+ | Code
+ segment color rotation =
+ Svg.g []
+ [ dot color rotation
+ , line color rotation
+ ]
=
=Type this code at the bottom of the file and then replace main with the following:
=
-| Code
- main =
- [ segment "skyblue" 0
- , segment "orange" 72
- , segment "red" 144
- , segment "lime" 216
- , segment "maroon" 288
- ]
- |> Svg.svg
- [ Svg.Attributes.height "100%"
- , Svg.Attributes.width "100%"
- , Svg.Attributes.style "background: none"
- , Svg.Attributes.viewBox "-100 -100 200 200"
- ]
- |> Element.html
- |> Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
- ]
+| Editor
+ | Annotations
+ | None
+
+ | Fold
+ start = 8
+ length = 13
+
+ | Code
+ main =
+ [ segment "skyblue" 0
+ , segment "orange" 72
+ , segment "red" 144
+ , segment "lime" 216
+ , segment "maroon" 288
+ ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: none"
+ , Svg.Attributes.viewBox "-100 -100 200 200"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
=
=| Note
= I hope you can see the pattern in what we are doing. We are taking repetitive blocks of code and turning them into named functions. Parameters help us deal with variability in the repeated code (like color and rotation that is different for each segment).
=
=Reload the browser. There should still be no visible difference in the behavior of the program, but our source code is getting more readable. That's good.
=
-The big difference between our program and the one we are trying to build is that our have only one level of segments, whereas in the example segments grow from the tip of other segments. You can think of it as segments having child segments: the green segment has two child brown segments. Each brown segment has three red child segments
+The big difference between our program and the one we are trying to build is that ours have only one level of segments, whereas in the example segments grow from the tip of other segments. You can think of it as segments having child segments: the green segment has two child brown segments. Each brown segment has three red child segments
=
=| Note
= TODO: Make sure the description matches the example
=
-It's similar to how the way an SVG group has child elements. But group itself (together with it's children) is an element, so we can put a group within a group. Let's do that! Change the definition of {Code|segment} function as follows:
+It's similar to how the way an SVG group has child elements. But group itself (together with its children) is an element, so we can put a group within a group. Let's do that! Change the definition of {Code|segment} function as follows:
=
-| Code
- segment color rotation =
- Svg.g []
- [ dot color rotation
- , line color rotation
- , Svg.g
- [ Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ") translate(80)"
- ]
- )
- ]
- [ dot "red" 15
- , line "red" 15
- , dot "blue" -15
- , line "blue" -15
+| Editor
+ | Annotations
+ | None
+
+ | Code
+ segment color rotation =
+ Svg.g []
+ [ dot color rotation
+ , line color rotation
+ , Svg.g
+ [ Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ [ dot "red" 15
+ , line "red" 15
+ , dot "blue" -15
+ , line "blue" -15
+ ]
= ]
- ]
=
=Now the program should display something like this:
=
@@ -131,26 +155,30 @@ It's already starts to look interesting, but there are two problems with it. Fir
=
=Then the lines look ugly displayed on top of the dots of different color. This is also easy to fix. In SVG several siblings (children of the same parent element) lay one on top of another. The "younger" sibling is laying on top of the older, so in our case the group containing the blue and red dots and lines lays on top of the dot and line. Let's change the order of siblings by moving the group to the beginning of the list, like this:
=
-| Code
- segment color rotation =
- Svg.g []
- [ Svg.g
- [ Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ") translate(80)"
- ]
- )
- ]
- [ dot "red" 15
- , line "red" 15
- , dot "blue" -15
- , line "blue" -15
+| Editor
+ | Annotations
+ | None
+
+ | Code
+ segment color rotation =
+ Svg.g []
+ [ Svg.g
+ [ Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ [ dot "red" 15
+ , line "red" 15
+ , dot "blue" -15
+ , line "blue" -15
+ ]
+ , dot color rotation
+ , line color rotation
= ]
- , dot color rotation
- , line color rotation
- ]
=
=With this two changes applied you should see something like this in the browser:
=
@@ -159,45 +187,77 @@ With this two changes applied you should see something like this in the browser:
=
=So now each segment has two sub segments: red and blue. The red one is rotate 15 degree clockwise, the blue one is rotated 15 degree counterclockwise. But they are look the same. Our goal is to make them variable. This means that we need one more parameter to the segment: list of children. Let's do it:
=
-| Code
- module Main exposing (main)
+| Editor
+ | Annotations
+ | None
=
- import Element
- import Svg exposing (Svg)
- import Svg.Attributes
+ | Fold
+ start = 1
+ length = 7
=
+ | Fold
+ start = 21
+ length = 12
=
- main =
- [ segment "skyblue"
- 0
- [ dot "red" 15
- , line "red" 15
- , dot "blue" -15
- , line "blue" -15
- ]
- , segment "orange" 72 []
- , segment "red" 144 []
- , segment "lime" 216 []
- , segment "maroon" 288 []
- ]
- |> Svg.svg
- [ Svg.Attributes.height "100%"
- , Svg.Attributes.width "100%"
- , Svg.Attributes.style "background: none"
- , Svg.Attributes.viewBox "-500 -500 1000 1000"
+ | Code
+ module Main exposing (main)
+
+ import Element
+ import Svg exposing (Svg)
+ import Svg.Attributes
+
+
+ main =
+ [ segment "skyblue"
+ 0
+ [ dot "red" 15
+ , line "red" 15
+ , dot "blue" -15
+ , line "blue" -15
= ]
- |> Element.html
- |> Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
+ , segment "orange" 72 []
+ , segment "red" 144 []
+ , segment "lime" 216 []
+ , segment "maroon" 288 []
+ ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: none"
+ , Svg.Attributes.viewBox "-500 -500 1000 1000"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+
+ segment : String -> Float -> List (Svg msg) -> Svg.Svg msg
+ segment color rotation children =
+ Svg.g []
+ [ Svg.g
+ [ Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ children
+ , dot color rotation
+ , line color rotation
= ]
=
=
- segment : String -> Float -> List (Svg msg) -> Svg.Svg msg
- segment color rotation children =
- Svg.g []
- [ Svg.g
- [ Svg.Attributes.transform
+ dot color rotation =
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform
= (String.concat
= [ "rotate("
= , String.fromFloat rotation
@@ -205,46 +265,26 @@ So now each segment has two sub segments: red and blue. The red one is rotate 15
= ]
= )
= ]
- children
- , dot color rotation
- , line color rotation
- ]
-
-
- dot color rotation =
- Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill color
- , Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ") translate(80)"
- ]
- )
- ]
- []
-
-
- line color rotation =
- Svg.line
- [ Svg.Attributes.strokeWidth "1"
- , Svg.Attributes.x1 "0"
- , Svg.Attributes.y1 "0"
- , Svg.Attributes.x1 "80"
- , Svg.Attributes.y1 "0"
- , Svg.Attributes.stroke color
- , Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ")"
- ]
- )
- ]
- []
+ []
+
+
+ line color rotation =
+ Svg.line
+ [ Svg.Attributes.strokeWidth "1"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x1 "80"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.stroke color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ")"
+ ]
+ )
+ ]
+ []
=
=
=We have added third parameter to {Code|segment} function on line 34 (before the changes it was 28). The parameter is named {Code|children} and we use it in place of the list on line 45 (previously 39). The list itself was moved to line 11, where it is passed as a value for the parameter of the first segment. This move made all the lines shift down. All other segments will also need to get value for {Code|children} parameter, so let's just give each of them an empty list - meaning they have 0 children.
@@ -256,63 +296,107 @@ Once all changes are applied, reload the browser and see that only first, skyblu
=
=Before we continue, let's notice that on lines 11 - 15 we have a nice opportunity to remove some repetition. Consider that dot and a line with same rotation and color are just a segment. We can replace:
=
-| Code
- [ dot "red" 15
- , line "red" 15
- , dot "blue" -15
- , line "blue" -15
- ]
+| Editor
+ | Annotations
+ | None
=
-with:
-
-| Code
- [ segment "red" 15 []
- , segment "blue" -15 []
- ]
+ | Code
+ [ dot "red" 15
+ , line "red" 15
+ , dot "blue" -15
+ , line "blue" -15
+ ]
=
-A segment within a segment! Consider that every segment can have child segments. This way we can build a tree as complex as we want. Try adding more children:
+with:
=
-| Code
- module Main exposing (main)
+| Editor
+ | Annotations
+ | None
=
- import Element
- import Svg exposing (Svg)
- import Svg.Attributes
+ | Code
+ [ segment "red" 15 []
+ , segment "blue" -15 []
+ ]
=
+A segment within a segment! Consider that every segment can have child segments. This way we can build a tree as complex as we want. Try adding more children:
=
- main =
- [ segment "skyblue"
- 0
- [ segment "red" 15 []
- , segment "blue"
- -15
- [ segment "yellow" 45 []
- , segment "pink" -20 []
- , segment "green" -90 []
+| Editor
+ | Annotations
+ | Highlight
+ top = 28
+ left = 12
+ width = 50
+ height = 1
+
+ | Fold
+ start = 1
+ length = 7
+
+ | Fold
+ start = 30
+ length = 60
+
+ | Code
+ module Main exposing (main)
+
+ import Element
+ import Svg exposing (Svg)
+ import Svg.Attributes
+
+
+ main =
+ [ segment "skyblue"
+ 0
+ [ segment "red" 15 []
+ , segment "blue"
+ -15
+ [ segment "yellow" 45 []
+ , segment "pink" -20 []
+ , segment "green" -90 []
+ ]
= ]
+ , segment "orange" 72 []
+ , segment "red" 144 []
+ , segment "lime" 216 []
+ , segment "maroon" 288 []
= ]
- , segment "orange" 72 []
- , segment "red" 144 []
- , segment "lime" 216 []
- , segment "maroon" 288 []
- ]
- |> Svg.svg
- [ Svg.Attributes.height "100%"
- , Svg.Attributes.width "100%"
- , Svg.Attributes.style "background: none"
- , Svg.Attributes.viewBox "-500 -500 1000 1000"
- ]
- |> Element.html
- |> Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: none"
+ , Svg.Attributes.viewBox "-500 -500 1000 1000"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+
+ segment color rotation children =
+ Svg.g []
+ [ Svg.g
+ [ Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ children
+ , dot color rotation
+ , line color rotation
= ]
=
=
- segment color rotation children =
- Svg.g []
- [ Svg.g
- [ Svg.Attributes.transform
+ dot color rotation =
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform
= (String.concat
= [ "rotate("
= , String.fromFloat rotation
@@ -320,46 +404,26 @@ A segment within a segment! Consider that every segment can have child segments.
= ]
= )
= ]
- children
- , dot color rotation
- , line color rotation
- ]
-
-
- dot color rotation =
- Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill color
- , Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ") translate(80)"
- ]
- )
- ]
- []
-
-
- line color rotation =
- Svg.line
- [ Svg.Attributes.strokeWidth "1"
- , Svg.Attributes.x1 "0"
- , Svg.Attributes.y1 "0"
- , Svg.Attributes.x1 "80"
- , Svg.Attributes.y1 "0"
- , Svg.Attributes.stroke color
- , Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ")"
- ]
- )
- ]
- []
+ []
+
+
+ line color rotation =
+ Svg.line
+ [ Svg.Attributes.strokeWidth "1"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x1 "80"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.stroke color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ")"
+ ]
+ )
+ ]
+ []
=
=| Emphasize
= Time to play 🌳🌲🌴🎄
@@ -401,45 +465,77 @@ To represent this in code we will need to learn few new concepts: *dictionary*,
=
=Let's start with a record. Currently our {Code|segment} function takes three arguments first two are {Code|color : String} and {Code|rotation : Float}. The last one is a list of child segments. Instead we can merge the first two into one argument of type {Code|\{ color : String, rotation : Float \}}. It will look like this:
=
-| Code
- module Main exposing (main)
+| Editor
+ | Annotations
+ | None
+
+ | Fold
+ start = 1
+ length = 7
+
+ | Fold
+ start = 22
+ length = 66
=
- import Element
- import Svg exposing (Svg)
- import Svg.Attributes
+ | Code
+ module Main exposing (main)
=
+ import Element
+ import Svg exposing (Svg)
+ import Svg.Attributes
=
- main =
- [ segment { color = "skyblue", rotation = 0 }
- [ segment { color = "red", rotation = 15 } []
- , segment { color = "blue", rotation = -15 }
- [ segment { color = "yellow", rotation = 45 } []
- , segment { color = "pink", rotation = -20 } []
- , segment { color = "green", rotation = -90 } []
+
+ main =
+ [ segment { color = "skyblue", rotation = 0 }
+ [ segment { color = "red", rotation = 15 } []
+ , segment { color = "blue", rotation = -15 }
+ [ segment { color = "yellow", rotation = 45 } []
+ , segment { color = "pink", rotation = -20 } []
+ , segment { color = "green", rotation = -90 } []
+ ]
= ]
+ , segment { color = "orange", rotation = 72 } []
+ , segment { color = "red", rotation = 144 } []
+ , segment { color = "lime", rotation = 216 } []
+ , segment { color = "maroon", rotation = 288 } []
= ]
- , segment { color = "orange", rotation = 72 } []
- , segment { color = "red", rotation = 144 } []
- , segment { color = "lime", rotation = 216 } []
- , segment { color = "maroon", rotation = 288 } []
- ]
- |> Svg.svg
- [ Svg.Attributes.height "100%"
- , Svg.Attributes.width "100%"
- , Svg.Attributes.style "background: none"
- , Svg.Attributes.viewBox "-500 -500 1000 1000"
- ]
- |> Element.html
- |> Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: none"
+ , Svg.Attributes.viewBox "-500 -500 1000 1000"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+
+ segment { color, rotation } children =
+ Svg.g []
+ [ Svg.g
+ [ Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ children
+ , dot color rotation
+ , line color rotation
= ]
=
=
- segment { color, rotation } children =
- Svg.g []
- [ Svg.g
- [ Svg.Attributes.transform
+ dot color rotation =
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform
= (String.concat
= [ "rotate("
= , String.fromFloat rotation
@@ -447,46 +543,26 @@ Let's start with a record. Currently our {Code|segment} function takes three arg
= ]
= )
= ]
- children
- , dot color rotation
- , line color rotation
- ]
-
-
- dot color rotation =
- Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill color
- , Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ") translate(80)"
- ]
- )
- ]
- []
-
-
- line color rotation =
- Svg.line
- [ Svg.Attributes.strokeWidth "1"
- , Svg.Attributes.x1 "0"
- , Svg.Attributes.y1 "0"
- , Svg.Attributes.x1 "80"
- , Svg.Attributes.y1 "0"
- , Svg.Attributes.stroke color
- , Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ")"
- ]
- )
- ]
- []
+ []
+
+
+ line color rotation =
+ Svg.line
+ [ Svg.Attributes.strokeWidth "1"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x1 "80"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.stroke color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ")"
+ ]
+ )
+ ]
+ []
=
=Not much changed. Instead of passing two separate values for {Code|color} and {Code|rotation} we pass one value with two named fields. That's a record!
=
@@ -494,60 +570,97 @@ Why did I do it? That way I can store complete information about a segment in on
=
=Dictionary let's us associate one value (let's say color) with another value (let's say a list of segments). You can have as many entries in your dictionary as you want. That's perfect for storing our rules. We can start with an empty dictionary and then add our two rules: one for brown and one for green segments. We do it like this:
=
-| Code
- module Main exposing (main)
+| Editor
+ | Annotations
+ | Highlight
+ top = 3
+ left = 1
+ width = 11
+ height = 1
+
+ | Fold
+ start = 8
+ length = 27
+
+ | Fold
+ start = 48
+ length = 55
=
- import Dict
- import Element
- import Svg exposing (Svg)
- import Svg.Attributes
=
+ | Code
+ module Main exposing (main)
=
- main =
- [ segment { color = "skyblue", rotation = 0 }
- [ segment { color = "red", rotation = 15 } []
- , segment { color = "blue", rotation = -15 }
- [ segment { color = "yellow", rotation = 45 } []
- , segment { color = "pink", rotation = -20 } []
- , segment { color = "green", rotation = -90 } []
+ import Dict
+ import Element
+ import Svg exposing (Svg)
+ import Svg.Attributes
+
+
+ main =
+ [ segment { color = "skyblue", rotation = 0 }
+ [ segment { color = "red", rotation = 15 } []
+ , segment { color = "blue", rotation = -15 }
+ [ segment { color = "yellow", rotation = 45 } []
+ , segment { color = "pink", rotation = -20 } []
+ , segment { color = "green", rotation = -90 } []
+ ]
= ]
+ , segment { color = "orange", rotation = 72 } []
+ , segment { color = "red", rotation = 144 } []
+ , segment { color = "lime", rotation = 216 } []
+ , segment { color = "maroon", rotation = 288 } []
= ]
- , segment { color = "orange", rotation = 72 } []
- , segment { color = "red", rotation = 144 } []
- , segment { color = "lime", rotation = 216 } []
- , segment { color = "maroon", rotation = 288 } []
- ]
- |> Svg.svg
- [ Svg.Attributes.height "100%"
- , Svg.Attributes.width "100%"
- , Svg.Attributes.style "background: none"
- , Svg.Attributes.viewBox "-500 -500 1000 1000"
- ]
- |> Element.html
- |> Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
- ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: none"
+ , Svg.Attributes.viewBox "-500 -500 1000 1000"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
=
=
- rules =
- Dict.empty
- |> Dict.insert "brown"
- [ { color = "green", rotation = 0 }
- , { color = "green", rotation = 20 }
- , { color = "green", rotation = -30 }
- ]
- |> Dict.insert "green"
- [ { color = "red", rotation = -45 }
- , { color = "red", rotation = -5 }
- , { color = "red", rotation = 50 }
+ rules =
+ Dict.empty
+ |> Dict.insert "brown"
+ [ { color = "green", rotation = 0 }
+ , { color = "green", rotation = 20 }
+ , { color = "green", rotation = -30 }
+ ]
+ |> Dict.insert "green"
+ [ { color = "red", rotation = -45 }
+ , { color = "red", rotation = -5 }
+ , { color = "red", rotation = 50 }
+ ]
+
+
+ segment { color, rotation } children =
+ Svg.g []
+ [ Svg.g
+ [ Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ children
+ , dot color rotation
+ , line color rotation
= ]
=
=
- segment { color, rotation } children =
- Svg.g []
- [ Svg.g
- [ Svg.Attributes.transform
+ dot color rotation =
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform
= (String.concat
= [ "rotate("
= , String.fromFloat rotation
@@ -555,46 +668,26 @@ Dictionary let's us associate one value (let's say color) with another value (le
= ]
= )
= ]
- children
- , dot color rotation
- , line color rotation
- ]
-
-
- dot color rotation =
- Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill color
- , Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ") translate(80)"
- ]
- )
- ]
- []
-
-
- line color rotation =
- Svg.line
- [ Svg.Attributes.strokeWidth "1"
- , Svg.Attributes.x1 "0"
- , Svg.Attributes.y1 "0"
- , Svg.Attributes.x1 "80"
- , Svg.Attributes.y1 "0"
- , Svg.Attributes.stroke color
- , Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ")"
- ]
- )
- ]
- []
+ []
+
+
+ line color rotation =
+ Svg.line
+ [ Svg.Attributes.strokeWidth "1"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x1 "80"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.stroke color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ")"
+ ]
+ )
+ ]
+ []
=
=We have added an import statement for {Code|Dict} module and created a dictionary with two entries: one for {Code|"brown"} and one for {Code|"green"}. The values associated with these keys are a list of segment records. The meaning of this is following:
=
@@ -606,43 +699,51 @@ There is no rule for red segments, so they will have no children. Now we will ap
=
=Here it is:
=
-| Code
- segment { color, rotation } =
- Svg.g []
- [ rules
- |> Dict.get color
- |> Maybe.withDefault []
- |> List.map segment
- |> Svg.g
- [ Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ") translate(80)"
- ]
- )
- ]
- , dot color rotation
- , line color rotation
- ]
-
-We also have to change the definition of {Code|main}. Segment no longer takes two arguments, and it's children are calculated according to rules, so we can just add one brown segment and rotate it upward (-90 degree)
+| Editor
+ | Annotations
+ | None
=
-| Code
- main =
- [ segment { color = "brown", rotation = -90 } ]
- |> Svg.svg
- [ Svg.Attributes.height "100%"
- , Svg.Attributes.width "100%"
- , Svg.Attributes.style "background: none"
- , Svg.Attributes.viewBox "-500 -500 1000 1000"
- ]
- |> Element.html
- |> Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
+ | Code
+ segment { color, rotation } =
+ Svg.g []
+ [ rules
+ |> Dict.get color
+ |> Maybe.withDefault []
+ |> List.map segment
+ |> Svg.g
+ [ Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ , dot color rotation
+ , line color rotation
= ]
=
+We also have to change the definition of {Code|main}. Segment no longer takes two arguments, and its children are calculated according to rules, so we can just add one brown segment and rotate it upward (-90 degree)
+
+| Editor
+ | Annotations
+ | None
+
+ | Code
+ main =
+ [ segment { color = "brown", rotation = -90 } ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: none"
+ , Svg.Attributes.viewBox "-500 -500 1000 1000"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
=The result should be exactly like we wanted it to be:
=
=| Monospace
@@ -689,25 +790,29 @@ The result should be exactly like we wanted it to be:
=
=Nice, but what's going on here:
=
-| Code
- segment { color, rotation } =
- Svg.g []
- [ rules
- |> Dict.get color
- |> Maybe.withDefault []
- |> List.map segment
- |> Svg.g
- [ Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ") translate(80)"
- ]
- )
- ]
- , dot color rotation
- , line color rotation
- ]
+| Editor
+ | Annotations
+ | None
+
+ | Code
+ segment { color, rotation } =
+ Svg.g []
+ [ rules
+ |> Dict.get color
+ |> Maybe.withDefault []
+ |> List.map segment
+ |> Svg.g
+ [ Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ , dot color rotation
+ , line color rotation
+ ]
=
=There are three new things here:
=
@@ -781,102 +886,118 @@ So that won't work. But it would be nice to have arbitrarily complex trees with
=
=We can do it like this. When we create our first segment (the brown one), we will pass it a second argument (let's call it {Code|age}). It will be an integer. The age of a parent will be decremented and passed to all it's children (so every child is younger than it's parent by one generation). Eventually the program will reach segments with {Code|age} being {Code|0}. This segment will have no children, thus stopping the whole process. Here is how we can implement it:
=
-| Code
- module Main exposing (main)
-
- import Dict
- import Element
- import Svg exposing (Svg)
- import Svg.Attributes
-
+| Editor
+ | Annotations
+ | Highlight
+ top = 10
+ left = 4
+ width = 49
+ height = 1
+
+ | Fold
+ start = 1
+ length = 8
+
+ | Fold
+ start = 62
+ length = 36
+
+ | Code
+ module Main exposing (main)
+
+ import Dict
+ import Element
+ import Svg exposing (Svg)
+ import Svg.Attributes
+
+
+ main =
+ [ segment 4 { color = "brown", rotation = -90 } ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: none"
+ , Svg.Attributes.viewBox "-500 -500 1000 1000"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
=
- main =
- [ segment 4 { color = "brown", rotation = -90 } ]
- |> Svg.svg
- [ Svg.Attributes.height "100%"
- , Svg.Attributes.width "100%"
- , Svg.Attributes.style "background: none"
- , Svg.Attributes.viewBox "-500 -500 1000 1000"
- ]
- |> Element.html
- |> Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
- ]
=
+ rules =
+ Dict.empty
+ |> Dict.insert "brown"
+ [ { color = "brown", rotation = 0 }
+ , { color = "green", rotation = 20 }
+ , { color = "green", rotation = -30 }
+ ]
+ |> Dict.insert "green"
+ [ { color = "red", rotation = -45 }
+ , { color = "red", rotation = -5 }
+ , { color = "red", rotation = 50 }
+ ]
=
- rules =
- Dict.empty
- |> Dict.insert "brown"
- [ { color = "brown", rotation = 0 }
- , { color = "green", rotation = 20 }
- , { color = "green", rotation = -30 }
- ]
- |> Dict.insert "green"
- [ { color = "red", rotation = -45 }
- , { color = "red", rotation = -5 }
- , { color = "red", rotation = 50 }
- ]
=
+ segment age { color, rotation } =
+ if age <= 0 then
+ Svg.g [] []
+
+ else
+ Svg.g []
+ [ rules
+ |> Dict.get color
+ |> Maybe.withDefault []
+ |> List.map (segment (age - 1))
+ |> Svg.g
+ [ Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ , dot color rotation
+ , line color rotation
+ ]
=
- segment age { color, rotation } =
- if age <= 0 then
- Svg.g [] []
=
- else
- Svg.g []
- [ rules
- |> Dict.get color
- |> Maybe.withDefault []
- |> List.map (segment (age - 1))
- |> Svg.g
- [ Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ") translate(80)"
- ]
- )
+ dot color rotation =
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
= ]
- , dot color rotation
- , line color rotation
+ )
= ]
-
-
- dot color rotation =
- Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill color
- , Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ") translate(80)"
- ]
- )
- ]
- []
-
-
- line color rotation =
- Svg.line
- [ Svg.Attributes.strokeWidth "1"
- , Svg.Attributes.x1 "0"
- , Svg.Attributes.y1 "0"
- , Svg.Attributes.x1 "80"
- , Svg.Attributes.y1 "0"
- , Svg.Attributes.stroke color
- , Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ")"
- ]
- )
- ]
- []
+ []
+
+
+ line color rotation =
+ Svg.line
+ [ Svg.Attributes.strokeWidth "1"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x1 "80"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.stroke color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ")"
+ ]
+ )
+ ]
+ []
=
=On line 10 we pass the age to the {Code|segment} function as the first argument. The record is a second parameter now. The reason is that it makes mapping on line 47 easier. Age is the same for all the children (age of the parent minus 1).
=
@@ -948,46 +1069,50 @@ I hope you had fun playing with the tree. You can make it really interesting and
=
=Since the size depends on the age, and the age is given for every segment, we can fix that relatively easy. Let's start by making the dots smaller or bigger depending on the age of the segment, like this:
=
-| Code
- segment age { color, rotation } =
- if age <= 0 then
- Svg.g [] []
+| Editor
+ | Annotations
+ | None
+
+ | Code
+ segment age { color, rotation } =
+ if age <= 0 then
+ Svg.g [] []
+
+ else
+ Svg.g []
+ [ rules
+ |> Dict.get color
+ |> Maybe.withDefault []
+ |> List.map (segment (age - 1))
+ |> Svg.g
+ [ Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ , dot age color rotation
+ , line color rotation
+ ]
=
- else
- Svg.g []
- [ rules
- |> Dict.get color
- |> Maybe.withDefault []
- |> List.map (segment (age - 1))
- |> Svg.g
- [ Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ") translate(80)"
- ]
- )
+
+ dot age color rotation =
+ Svg.circle
+ [ Svg.Attributes.r (String.fromFloat age)
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
= ]
- , dot age color rotation
- , line color rotation
+ )
= ]
-
-
- dot age color rotation =
- Svg.circle
- [ Svg.Attributes.r (String.fromFloat age)
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill color
- , Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ") translate(80)"
- ]
- )
- ]
- []
+ []
=
=| Monospace
= TODO: Describe the change to code
@@ -996,107 +1121,115 @@ Next let's pass the age to the {Code|line} function and use it for both the thic
=
=If you reload now, the tree will look as if it exploded. It's funny. The reason is that we did not adjust the translation of the child groups nor the dots. It's always 80, even though the length of the lines is variable - see lines {Code|TODO: line of group translation} and {Code|TODO: line of dot translation}. Let's change it like that:
=
-| Code
- module Main exposing (main)
+| Editor
+ | Annotations
+ | None
=
- import Dict
- import Element
- import Svg exposing (Svg)
- import Svg.Attributes
+ | Fold
+ start = 1
+ length = 37
=
+ | Code
+ module Main exposing (main)
=
- main =
- [ segment 4 { color = "brown", rotation = -90 } ]
- |> Svg.svg
- [ Svg.Attributes.height "100%"
- , Svg.Attributes.width "100%"
- , Svg.Attributes.style "background: none"
- , Svg.Attributes.viewBox "-500 -500 1000 1000"
- ]
- |> Element.html
- |> Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
- ]
+ import Dict
+ import Element
+ import Svg exposing (Svg)
+ import Svg.Attributes
=
=
- rules =
- Dict.empty
- |> Dict.insert "brown"
- [ { color = "brown", rotation = 0 }
- , { color = "green", rotation = 20 }
- , { color = "green", rotation = -30 }
- ]
- |> Dict.insert "green"
- [ { color = "red", rotation = -45 }
- , { color = "red", rotation = -5 }
- , { color = "red", rotation = 50 }
- ]
-
+ main =
+ [ segment 4 { color = "brown", rotation = -90 } ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: none"
+ , Svg.Attributes.viewBox "-500 -500 1000 1000"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
=
- segment age { color, rotation } =
- if age <= 0 then
- Svg.g [] []
=
- else
- Svg.g []
- [ rules
- |> Dict.get color
- |> Maybe.withDefault []
- |> List.map (segment (age - 1))
- |> Svg.g
- [ Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ") translate("
- , String.fromFloat (age * 10)
- , ")"
- ]
- )
- ]
- , dot age color rotation
- , line age color rotation
- ]
+ rules =
+ Dict.empty
+ |> Dict.insert "brown"
+ [ { color = "brown", rotation = 0 }
+ , { color = "green", rotation = 20 }
+ , { color = "green", rotation = -30 }
+ ]
+ |> Dict.insert "green"
+ [ { color = "red", rotation = -45 }
+ , { color = "red", rotation = -5 }
+ , { color = "red", rotation = 50 }
+ ]
=
=
- dot age color rotation =
- Svg.circle
- [ Svg.Attributes.r (String.fromFloat age)
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill color
- , Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ") translate("
- , String.fromFloat (age * 10)
- , ")"
- ]
- )
- ]
- []
-
-
- line age color rotation =
- Svg.line
- [ Svg.Attributes.strokeWidth "1"
- , Svg.Attributes.x1 "0"
- , Svg.Attributes.y1 "0"
- , Svg.Attributes.x1 (String.fromFloat (age * 10))
- , Svg.Attributes.y1 "0"
- , Svg.Attributes.stroke color
- , Svg.Attributes.strokeWidth (String.fromFloat age)
- , Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ")"
+ segment age { color, rotation } =
+ if age <= 0 then
+ Svg.g [] []
+
+ else
+ Svg.g []
+ [ rules
+ |> Dict.get color
+ |> Maybe.withDefault []
+ |> List.map (segment (age - 1))
+ |> Svg.g
+ [ Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate("
+ , String.fromFloat (age * 10)
+ , ")"
+ ]
+ )
+ ]
+ , dot age color rotation
+ , line age color rotation
= ]
- )
- ]
- []
+
+
+ dot age color rotation =
+ Svg.circle
+ [ Svg.Attributes.r (String.fromFloat age)
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate("
+ , String.fromFloat (age * 10)
+ , ")"
+ ]
+ )
+ ]
+ []
+
+
+ line age color rotation =
+ Svg.line
+ [ Svg.Attributes.strokeWidth "1"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x1 (String.fromFloat (age * 10))
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.stroke color
+ , Svg.Attributes.strokeWidth (String.fromFloat age)
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ")"
+ ]
+ )
+ ]
+ []
=
=Now the tree should look correctly, like this:
=index c073ebc..fe7dc56 100644
--- a/content/day-5.txt
+++ b/content/day-5.txt
@@ -93,6 +93,23 @@ State is sometimes called model. There are also commands, but we will not use th
=First let's change the value of {Code|main} and create the {Code|view} function, like this:
=
=| Editor
+ | Annotations
+ | Highlight
+ top = 10
+ left = 4
+ width = 35
+ height = 6
+
+ | Highlight
+ top = 18
+ left = 1
+ width = 8
+ height = 1
+
+ | Fold
+ start = 31
+ length = 81
+
= | Code
= module Main exposing (main)
=
@@ -102,7 +119,6 @@ First let's change the value of {Code|main} and create the {Code|view} function,
= import Svg exposing (Svg)
= import Svg.Attributes
=
-
= main =
= Browser.element
= { init = init
@@ -205,24 +221,6 @@ First let's change the value of {Code|main} and create the {Code|view} function,
= ]
= []
=
- | Highlight
- from = 11
- to = 16
- offset = 4
- width = 35
-
- | Highlight
- from = 19
- to = 19
- offset = 0
- width = 10
-
- | Highlight
- from = 20
- to = 20
- offset = 15
- width = 10
-
=Notice that the {Code|view} takes an argument called {Code|age} and passes it to the first (brown) segment. The value of {Code|age} is our state. In some programs the type of state can be very complex, but in our program it's simply a {Code|Float} number. It will indicate how much time in milliseconds have passed from the start of the program. Because the time is counted in milliseconds, to get correct results we need to divide it by 5000 (so the tree will grows one generation of segments per five second).
=
=Now let's provide the init function. It takes an argument sometimes called flags, but we will ignore it. Let's just say that it will always receive {Code|()} - an empty value called unit. It needs to return two values bound together in a structure called /tuple/.
@@ -237,16 +235,13 @@ First value is more important. This is the initial age of the tree, right after
=That's our init:
=
=| Editor
+ | Annotations
+ | None
+
= | Code
= init () =
= ( 0, Cmd.none )
=
- | Highlight
- from = 0
- to = 0
- offset = 0
- width = 10
-
=Time for the {Code|update} function. It will get two arguments: an incoming message and the current state. Incoming message will always be a duration of time since previous frame, so let's simply call it {Code|duration}. The state is the current age of the tree, so again we can call it {Code|age}. In return we must produce a tuple with:
=
=| List
@@ -256,18 +251,15 @@ Time for the {Code|update} function. It will get two arguments: an incoming mess
=It looks like this:
=
=| Editor
+ | Annotations
+ | None
+
= | Code
= update duration age =
= ( age + duration
= , Cmd.none
= )
=
- | Highlight
- from = 0
- to = 0
- offset = 0
- width = 10
-
=Finally let's subscribe to the events marking the passage of time. We can do it using {Code|Browser.Events.onAnimationFrameDelta}. Here is how it works. Whenever the browser is ready for the next frame it will send us a message containing the number of milliseconds that have passed since previous frame.
=
=To use it we first need to import the {Code|Browser.Events} module. The {Code|Browser.Events.onAnimationFrameDelta} function expects us to give it a function that will get a duration and return a message. But our message is simply a duration! We don't need to do anything with it, just take it as it is. So we need a function that just returns whatever it gets. This function is called {Code|identity}.
@@ -278,19 +270,43 @@ To use it we first need to import the {Code|Browser.Events} module. The {Code|Br
=The whole {Code|subscriptions} looks like that:
=
=| Editor
+ | Annotations
+ | None
+
= | Code
= subscriptions age =
= Browser.Events.onAnimationFrameDelta identity
=
- | Highlight
- from = 0
- to = 0
- offset = 0
- width = 10
-
=And the complete code like this:
=
=| Editor
+ | Annotations
+ | Highlight
+ top = 20
+ left = 1
+ width = 19
+ height = 2
+
+ | Highlight
+ top = 39
+ left = 1
+ width = 31
+ height = 7
+
+ | Fold
+ start = 1
+ length = 18
+
+ | Highlight
+ top = 48
+ left = 1
+ width = 49
+ height = 2
+ | Fold
+ start = 51
+ length = 81
+
+
= | Code
= module Main exposing (main)
=
@@ -421,12 +437,6 @@ And the complete code like this:
= ]
= []
=
- | Highlight
- from = 0
- to = 0
- offset = 0
- width = 10
-
=
=| Emphasize
= That's it!Fix fold taking one line too many.
index 07cf136..16a919a 100644
--- a/src/Mark/Custom.elm
+++ b/src/Mark/Custom.elm
@@ -137,7 +137,7 @@ editor =
= extractAnnotations
= { extracted
= | folded =
- (start + length)
+ (start + length - 1)
= |> List.range start
= |> Set.fromList
= |> Set.union foldedProvide contact information in index document.
Improve Emphasize block to allow multiple text blocks. All of them are centered but only the first one is big.
Co-Authored-By: Tadeusz Łazurski tadeusz@lazurski.pl
index 2547076..84b1c24 100644
--- a/content/index.txt
+++ b/content/index.txt
@@ -4,11 +4,15 @@
=| Emphasize
= ⚘
=
-| Emphasize
= A software development workshop for non-programmers
=
=Hello! We are running a workshop that will give you a glimpse into the way software is created. Our workshop is intended for people with no prior experience in programming and doesn't require any technical knowledge. Everybody is welcome!
=
+| Emphasize
+ Say hello {Icon|name=phone} {Link|+31 638 216 166|url=tel:+31638216166}
+
+ or drop us a note at {Link|fana@software.garden|url= mailto:fana@software.garden}
+
=Below is material through which we will be going during the workshop. Only the first section is a required read before you come, but feel free to read more to get more prepared.
=
=| Listindex ddbec4e..1a9f28e 100644
--- a/src/Mark/Custom.elm
+++ b/src/Mark/Custom.elm
@@ -315,22 +315,41 @@ note =
=emphasize : Mark.Block (model -> Element msg)
=emphasize =
= let
- render elements model =
- model
- |> elements
- |> Element.paragraph []
- |> Element.el
- [ Element.spacing 30
- , Font.bold
- , Font.size 30
- , Font.center
+ render texts model =
+ let
+ elements =
+ texts
+ |> List.map (\fn -> fn model)
+
+ heading =
+ List.head elements
+ |> Maybe.withDefault [ Element.none ]
+ |> Element.paragraph
+ [ Element.width Element.fill
+ , Font.bold
+ , Font.size 30
+ ]
+
+ subheadings =
+ elements
+ |> List.drop 1
+ |> List.map
+ (Element.paragraph
+ [ Element.width Element.fill
+ ]
+ )
+ in
+ (heading :: subheadings)
+ |> Element.textColumn
+ [ Font.center
= , Element.paddingXY 0 40
= , Element.width Element.fill
+ , Element.spacing 10
= ]
= in
= Mark.block "Emphasize"
= render
- text
+ (Mark.manyOf [ text ])
=
=
=image : Mark.Block (model -> Element msg)Merge remote-tracking branch 'origin/contact' into content
Make the width and padding of the main column respond to device class
On mobile devices in portrait orientation fill the screen with a fixed padding. Otherwise try to set width to maximum of 960 (but see https://github.com/mdgriffith/elm-ui/issues/42).
index bf183c7..d01b51a 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -619,11 +619,36 @@ document =
= children
= |> List.map (\child -> child model.examples)
= |> Element.textColumn
- [ Element.centerX
- , Element.spacing 20
+ (model.viewport
+ |> Element.classifyDevice
+ |> responsiveAttributes
+ |> (++)
+ [ Element.centerX
+ , Element.spacing 20
+ ]
+ )
+ |> View title
+
+ responsiveAttributes { class, orientation } =
+ case ( class, orientation ) of
+ ( Element.Phone, Element.Portrait ) ->
+ [ Element.width Element.fill
+ , Element.paddingXY 10 80
+ ]
+
+ ( Element.Tablet, Element.Portrait ) ->
+ [ Element.width Element.fill
+ , Element.paddingXY 20 80
+ ]
+
+ _ ->
+ [ Element.width
+ (Element.fill
+ |> Element.maximum 960
+ |> Element.minimum 500
+ )
= , Element.paddingXY 0 80
= ]
- |> View title
=
= {- The document has to start with a Title block containing a String (i.e. single line of unforamtted text). This String will be used in two ways:
=Add subtle material-style shadow to navigation bar
index d01b51a..4739a1a 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -491,15 +491,26 @@ homeNavigationBar =
=contentNavigationBar : Model -> Element Msg
=contentNavigationBar { location, scroll, viewport } =
= let
- links : List { url : String, label : String, icon : Element msg }
+ links :
+ List
+ { url : String
+ , label : String
+ , icon : Element msg
+ }
= links =
= [ { url = "/"
= , label = "Home"
- , icon = FeatherIcons.home |> FeatherIcons.toHtml [] |> Element.html
+ , icon =
+ FeatherIcons.home
+ |> FeatherIcons.toHtml []
+ |> Element.html
= }
= , { url = "/preparation.html"
= , label = "Get ready"
- , icon = FeatherIcons.bookOpen |> FeatherIcons.toHtml [] |> Element.html
+ , icon =
+ FeatherIcons.bookOpen
+ |> FeatherIcons.toHtml []
+ |> Element.html
= }
= , { url = "/day-1.html"
= , label = "Day 1"
@@ -523,11 +534,21 @@ contentNavigationBar { location, scroll, viewport } =
= }
= ]
=
- linkElement : { url : String, label : String, icon : Element Msg } -> Element Msg
+ linkElement :
+ { url : String
+ , label : String
+ , icon : Element Msg
+ }
+ -> Element Msg
= linkElement link =
= Element.link
= [ Element.width Element.fill
- , Element.paddingEach { top = 40, right = 5, bottom = 20, left = 5 }
+ , Element.paddingEach
+ { top = 40
+ , right = 5
+ , bottom = 20
+ , left = 5
+ }
= ]
= { url = link.url
= , label =
@@ -594,6 +615,17 @@ contentNavigationBar { location, scroll, viewport } =
= Element.column
= [ Element.width Element.fill
= , Background.color (Element.rgb 1 1 1)
+ , Border.shadow
+ { offset = ( 0, 0 )
+ , size = 1
+ , blur = 3
+ , color =
+ if scroll == 0 then
+ Element.rgba 0.8 0.8 0.8 0
+
+ else
+ Element.rgb 0.8 0.8 0.8
+ }
= , Element.htmlAttribute (Html.Attributes.id "navigation-bar")
= ]
= [ linksRow
Commits: 2
Merge branch 'content' into tree-example
Fix the tree example markup
With a workaround for parser breaking on startWith + manyOf.
index 9ef5249..6110a50 100644
--- a/content/day-4.txt
+++ b/content/day-4.txt
@@ -18,62 +18,44 @@
=| Header
= The Problem
=
-| Note
- FIXME: The tree example is broken.
+We want to have a tree that looks like this:
=
=| Window
= | Tree
= | Axiom
= color = green
- rotation = 0
- age = 9
-
- | Rule
- | Parent
- purple
-
- | Child
- color = green
- rotation = 0.0
-
- | Child
- color = green
- rotation = 30
+ rotation = -90
+ age = 8
=
- | Child
- color = green
- rotation = -30
=
= | Rule
- | Parent
- green
+ parent = green
=
- | Child
- color = brown
- rotation = 0.0
+ children =
+ | Child
+ color = brown
+ rotation = 0.0
=
- | Child
- color = brown
- rotation = 30
+ | Child
+ color = brown
+ rotation = 30
=
- | Child
- color = brown
- rotation = -30
+ | Child
+ color = brown
+ rotation = -30
=
= | Rule
-
- | Parent
- brown
-
- | Child
- color = green
- rotation = 0.0
- | Child
- color = green
- rotation = 30
- | Child
- color = green
- rotation = -30
+ parent = brown
+ children =
+ | Child
+ color = green
+ rotation = 0.0
+ | Child
+ color = green
+ rotation = 30
+ | Child
+ color = green
+ rotation = -30
=
=
=The tree is built from segments: a line and a dot. In this respect it is similar to the connected dots we created yesterday. If we group the dot and a line, we will have the building block for our tree. We can do it with {Code|Svg.g} function ({Code|g} is an abbreviation of "group"). Just like {Code|Svg.svg}, it takes list of attributes and list of children. We can use it like that:index d73c74e..bf183c7 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -915,14 +915,11 @@ document =
= Mark.manyOf [ rule ]
=
= rule =
- Mark.startWith Tuple.pair
- parent
- (Mark.manyOf [ child ])
-
- -- FIXME: Parser breaks with empty list of dead ends
- -- Mark.stub "Rule" ( "green", [ Examples.Tree.Segment "green" 30 ] )
- -- Mark.manyOf [ child ]
- -- |> Mark.map (Tuple.pair "green")
+ Mark.record2 "Rule"
+ Tuple.pair
+ (Mark.field "parent" Mark.string)
+ (Mark.field "children" (Mark.manyOf [ child ]))
+
= parent =
= Mark.block "Parent" identity Mark.string
=
Commits: 10
Show navigation bar in content only. WIP to show different navigation bar in home.
Co-Authored-By: Tadeusz Łazurski tadeusz@lazurski.pl
index d7e3485..9cc4789 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -166,8 +166,9 @@ view model =
= navigationBar =
= case Routes.parse model.location of
= Routes.Home ->
- -- TODO: Implement different navigation bar for home
- contentNavigationBar model
+ -- TODO: Implement fragment identifier navigation
+ -- homeNavigationBar
+ Element.none
=
= Routes.Content _ ->
= contentNavigationBar model
@@ -434,6 +435,35 @@ loadContent route =
= Cmd.none
=
=
+homeNavigationBar : Element Msg
+homeNavigationBar =
+ Element.row
+ [ Element.width Element.fill
+ , Element.padding 40
+ , Element.spacing 80
+ , Font.bold
+ , Background.color (Element.rgb 1 1 1)
+ ]
+ [ Element.link
+ [ Element.alignLeft
+ ]
+ { url = "/"
+ , label = Element.text "Home"
+ }
+ , Element.link
+ [ Element.alignRight
+ ]
+ { url = "#about-us"
+ , label = Element.text "About Us"
+ }
+ , Element.link
+ []
+ { url = "#contact"
+ , label = Element.text "Contact"
+ }
+ ]
+
+
=contentNavigationBar : Model -> Element Msg
=contentNavigationBar { location, scroll } =
= let
@@ -466,7 +496,7 @@ contentNavigationBar { location, scroll } =
= linkElement link =
= Element.link
= [ Element.width Element.fill
- , Element.padding 20
+ , Element.paddingEach { top = 40, right = 5, bottom = 20, left = 5 }
= ]
= { url = link.url
= , label = Element.el [ Element.centerX ] (Element.text link.label)Add support for Fold annotation to Editor
index 2553eb0..6c4cc3c 100644
--- a/src/Editor.elm
+++ b/src/Editor.elm
@@ -1,5 +1,6 @@
-module Editor exposing (Config, Highlight, defaults, editor)
+module Editor exposing (Annotation(..), Config, Region, defaults, editor)
=
+import Basics.Extra exposing (uncurry)
=import Browser
=import Dict exposing (Dict)
=import Element exposing (Element)
@@ -7,9 +8,11 @@ import Element.Background as Background
=import Element.Border as Border
=import Element.Extra as Element
=import Element.Font as Font
+import Element.Input as Input
=import FeatherIcons exposing (icons)
=import Html exposing (Html)
=import List.Extra as List
+import Set exposing (Set)
=import Svg exposing (Svg)
=import Svg.Attributes
=
@@ -41,34 +44,49 @@ defaults =
= }
=
=
-type alias Highlight =
- { from : Int
- , to : Int
- , offset : Int
+type Annotation
+ = None -- A dummy annotation to satisfy manyOf block. See https://github.com/mdgriffith/elm-markup/issues/12
+ | Highlight Region
+ | Fold Int Int
+
+
+type alias Region =
+ { top : Int
+ , left : Int
= , width : Int
+ , height : Int
= }
=
=
-highlight : Highlight -> Element msg
-highlight { from, to, offset, width } =
+highlight : Region -> Element msg
+highlight { top, left, width, height } =
= Element.row []
= [ " "
- |> String.repeat offset
+ |> String.repeat left
= |> Element.text
= , " "
= |> String.repeat width
- |> List.repeat (to - from + 1)
+ |> List.repeat height
= |> List.map Element.text
- |> List.map (Element.el [ Element.padding 10 ])
+ |> List.map (Element.el [ Element.paddingXY 0 10 ])
= |> Element.column
- [ Element.inFront
+ [ Element.behindContent
= (Element.el
= [ Element.width Element.fill
= , Element.height Element.fill
= , Border.color (Element.rgba 1 0 0 0.8)
- , Border.rounded 10
- , Border.width 2
- , Border.dashed
+ , -- Hand-drawn style borders. See https://codepen.io/tmrDevelops/pen/VeRvKX/
+ [ width, height ]
+ |> List.map ((*) 2)
+ |> List.map String.fromInt
+ |> List.map (\num -> num ++ "px")
+ |> List.repeat 2
+ |> List.concat
+ |> (\list -> [ list, List.reverse list ])
+ |> List.map (String.join " ")
+ |> String.join " / "
+ |> Element.css "border-radius"
+ , Border.width 3
= , Element.scale 1.1
= ]
= Element.none
@@ -84,91 +102,298 @@ highlight { from, to, offset, width } =
= ]
=
=
-editor : Config -> List Highlight -> String -> Element msg
-editor { path, offset, colors } highlights contents =
- let
- highlighted : Dict Int Highlight
- highlighted =
- highlights
- |> List.foldl extract Dict.empty
+type Fragment
+ = Folded Int
+ | Unfolded (List String)
=
- extract item memo =
- Dict.insert item.from item memo
- in
- Element.column
- [ Border.width 3
- , Border.rounded 5
- , Border.color colors.window
- , Element.css "page-break-inside" "avoid"
- , Font.family
- [ Font.typeface "Source Code Pro"
- , Font.monospace
- ]
- , Element.css "page-break-inside" "avoid"
- , Element.width Element.fill
- ]
- [ Element.row
- [ Element.width Element.fill
- , Background.color colors.window
- , Font.color colors.secondary
- ]
- [ FeatherIcons.fileText
- |> FeatherIcons.toHtml []
- |> Element.html
- |> Element.el
- [ Element.height (Element.px 35)
- , Element.padding 8
- ]
- , Element.el
+
+editor : Config -> List Annotation -> String -> Element msg
+editor { path, offset, colors } annotations code =
+ let
+ topBar =
+ Element.row
= [ Element.width Element.fill
- , Font.size 16
- , Font.family
- [ Font.typeface "Source Code Pro"
- , Font.monospace
+ , Background.color colors.window
+ , Font.color colors.secondary
+ ]
+ [ FeatherIcons.fileText
+ |> FeatherIcons.toHtml []
+ |> Element.html
+ |> Element.el
+ [ Element.height (Element.px 35)
+ , Element.padding 8
+ ]
+ , Element.el
+ [ Element.width Element.fill
+ , Font.size 16
+ , Font.family
+ [ Font.typeface "Source Code Pro"
+ , Font.monospace
+ ]
= ]
+ (Element.text path)
= ]
- (Element.text "src/Main.elm")
- ]
- , contents
- |> String.lines
- |> List.indexedMap
- (\n loc ->
- Element.row []
- [ Element.el
- [ Font.color colors.secondary
- , Font.extraLight
- , Element.width (Element.px 40)
- , Element.padding 10
- , Font.alignRight
- , Element.css "user-select" "none"
- , Element.css "-webkit-user-select" "none"
- , Element.css "-ms-user-select" "none"
- , Element.css "-webkit-touch-callout" "none"
- , Element.css "-o-user-select" "none"
- , Element.css "-moz-user-select" "none"
- ]
- ((n + 1)
- |> String.fromInt
+
+ contents : Element msg
+ contents =
+ code
+ |> String.lines
+ |> List.indexedMap Tuple.pair
+ |> extractFragments []
+ |> renderFragments 1 []
+ |> Element.column [ Element.width Element.fill ]
+
+ renderFragments :
+ Int
+ -> List (Element msg)
+ -> List Fragment
+ -> List (Element msg)
+ renderFragments start rendered fragments =
+ case fragments of
+ [] ->
+ -- We are done
+ List.reverse rendered
+
+ (Folded length) :: rest ->
+ let
+ lineNumbers =
+ [ start, start + length ]
+ |> List.map String.fromInt
+ |> String.join " - "
= |> Element.text
- )
- , Element.el
- [ Element.width Element.fill
- , Element.padding 10
- , highlighted
- |> Dict.get (n + 1)
- |> Maybe.map highlight
- |> Maybe.withDefault Element.none
- |> Element.inFront
- ]
- (Element.text loc)
- ]
- )
- |> Element.column
- [ Element.width Element.fill
- , Font.size 16
- , Element.scrollbarY
+ |> Element.el
+ [ Font.color colors.secondary
+ , Font.size 14
+ ]
+
+ button =
+ Input.button
+ [ Font.size 10
+ , Element.paddingXY 10 5
+ , Background.color colors.secondary
+ , Font.color colors.background
+ , Border.rounded 3
+ , Font.variant Font.smallCaps
+ ]
+ { onPress = Nothing
+ , label = Element.text "unfold"
+ }
+
+ shadow =
+ Element.el
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ , Border.innerShadow
+ { offset = ( 0, 3 )
+ , size = -6
+ , blur = 12
+ , color = colors.window
+ }
+ ]
+ Element.none
+
+ fold =
+ [ lineNumbers, button ]
+ |> Element.row
+ [ Element.spacing 20
+ , Element.padding 10
+ , Background.color colors.background
+ , Element.width Element.fill
+ ]
+ |> Element.el
+ [ Element.paddingXY 5 10
+ , Element.width Element.fill
+ , Element.paddingEach
+ { top = 0
+ , right = 1
+ , bottom = 0
+ , left = 1
+ }
+ , Background.color colors.window
+ , Element.inFront shadow
+ ]
+ in
+ fold
+ :: renderFragments (start + length) rendered rest
+
+ (Unfolded lines) :: rest ->
+ let
+ unfolded =
+ lines
+ |> List.indexedMap
+ (\n loc ->
+ Element.row []
+ [ Element.el
+ [ Font.color colors.secondary
+ , Font.extraLight
+ , Element.width (Element.px 40)
+ , Element.padding 10
+ , Font.alignRight
+ , Element.css "user-select" "none"
+ , Element.css "-webkit-user-select" "none"
+ , Element.css "-ms-user-select" "none"
+ , Element.css "-webkit-touch-callout" "none"
+ , Element.css "-o-user-select" "none"
+ , Element.css "-moz-user-select" "none"
+ ]
+ ((n + start)
+ |> String.fromInt
+ |> Element.text
+ )
+ , Element.el
+ [ Element.width Element.fill
+ , Element.padding 10
+ , highlighted
+ |> Dict.get (n + start)
+ |> Maybe.map highlight
+ |> Maybe.withDefault Element.none
+ |> Element.behindContent
+ ]
+ (Element.text loc)
+ ]
+ )
+ |> Element.column
+ [ Element.width Element.fill
+ , Font.size 16
+ , Element.scrollbarY
+ ]
+ in
+ unfolded
+ :: renderFragments (start + List.length lines) rendered rest
+
+ folded : Set Int
+ folded =
+ annotations
+ |> List.foldl folds []
+ |> List.map (uncurry List.range)
+ |> List.concat
+ |> Set.fromList
+
+ folds : Annotation -> List ( Int, Int ) -> List ( Int, Int )
+ folds item memo =
+ case item of
+ Fold start size ->
+ ( start, start + size ) :: memo
+
+ _ ->
+ memo
+
+ extractFragments :
+ List Fragment
+ -> List ( Int, String )
+ -> List Fragment
+ extractFragments accumulated lines =
+ case lines of
+ [] ->
+ -- We have reached the end of the code
+ case accumulated of
+ [] ->
+ -- It was empty. Add one empty line.
+ [ Unfolded [ "" ] ]
+
+ (Folded n) :: _ ->
+ List.reverse accumulated
+
+ (Unfolded linesReversed) :: previous ->
+ List.reverse
+ (Unfolded (List.reverse linesReversed)
+ :: previous
+ )
+
+ ( n, line ) :: rest ->
+ let
+ isFolded =
+ Set.member (n + 1) folded
+ in
+ case accumulated of
+ [] ->
+ -- It's the first fragment. Check if it's folded or not.
+ if isFolded then
+ extractFragments [ Folded 1 ] rest
+
+ else
+ extractFragments [ Unfolded [ line ] ] rest
+
+ (Folded length) :: previous ->
+ if isFolded then
+ -- Continue the fold
+ let
+ this =
+ Folded (length + 1)
+ in
+ extractFragments
+ (this :: previous)
+ rest
+
+ else
+ -- Fold is finished. Start a new unfolded fragment.
+ let
+ this =
+ Folded length
+
+ next =
+ Unfolded [ line ]
+ in
+ extractFragments
+ (next :: this :: previous)
+ rest
+
+ (Unfolded linesReversed) :: previous ->
+ if isFolded then
+ -- Unfolded fragment is finished. Reverse the lines and start a new fold.
+ let
+ this =
+ Unfolded (List.reverse linesReversed)
+
+ next =
+ Folded 1
+ in
+ extractFragments
+ (next
+ :: this
+ :: previous
+ )
+ rest
+
+ else
+ -- Continue the unfolded fragment
+ let
+ this =
+ Unfolded (line :: linesReversed)
+ in
+ extractFragments
+ (this :: previous)
+ rest
+
+ highlighted : Dict Int Region
+ highlighted =
+ annotations
+ |> List.foldl regions Dict.empty
+
+ regions : Annotation -> Dict Int Region -> Dict Int Region
+ regions item memo =
+ case item of
+ Highlight region ->
+ Dict.insert region.top region memo
+
+ _ ->
+ memo
+ in
+ [ topBar
+ , contents
+ ]
+ |> Element.column
+ [ Border.width 3
+ , Border.rounded 5
+ , Border.color colors.window
+ , Element.css "page-break-inside" "avoid"
+ , Font.family
+ [ Font.typeface "Source Code Pro"
+ , Font.monospace
= ]
- ]
+ , Element.css "page-break-inside" "avoid"
+ , Element.width Element.fill
+ ]
=
=
=main : Html msg
@@ -285,10 +510,11 @@ dot age color rotation =
= []
="""
= |> editor defaults
- [ Highlight 10 16 0 38
- , Highlight 3 3 0 15
- , Highlight 19 19 0 10
- , Highlight 20 20 15 10
+ [ Highlight { top = 10, left = 0, width = 38, height = 7 }
+ , Highlight { top = 3, left = 0, width = 15, height = 1 }
+ , Highlight { top = 19, left = 0, width = 10, height = 1 }
+ , Highlight { top = 20, left = 15, width = 10, height = 1 }
+ , Fold 35 11
= ]
= |> Element.layout
= [ Element.width Element.fillindex a96ce20..7185019 100644
--- a/src/Mark/Custom.elm
+++ b/src/Mark/Custom.elm
@@ -85,8 +85,14 @@ monospace =
=editor : Mark.Block (a -> Element msg)
=editor =
= let
- render contents highlights model =
- Editor.editor Editor.defaults highlights contents
+ render : List Editor.Annotation -> String -> model -> Element msg
+ render annotations_ contents model =
+ Editor.editor Editor.defaults annotations_ contents
+
+ annotations =
+ Mark.block "Annotations"
+ identity
+ (Mark.manyOf [ none, highlight, fold ])
=
= code : Mark.Block String
= code =
@@ -94,20 +100,32 @@ editor =
= identity
= Mark.multiline
=
- highlight : Mark.Block Editor.Highlight
+ none : Mark.Block Editor.Annotation
+ none =
+ Mark.stub "None" Editor.None
+
+ highlight : Mark.Block Editor.Annotation
= highlight =
= Mark.record4 "Highlight"
- Editor.Highlight
- (Mark.field "from" Mark.int)
- (Mark.field "to" Mark.int)
- (Mark.field "offset" Mark.int)
+ Editor.Region
+ (Mark.field "top" Mark.int)
+ (Mark.field "left" Mark.int)
= (Mark.field "width" Mark.int)
+ (Mark.field "height" Mark.int)
+ |> Mark.map Editor.Highlight
+
+ fold : Mark.Block Editor.Annotation
+ fold =
+ Mark.record2 "Fold"
+ Editor.Fold
+ (Mark.field "start" Mark.int)
+ (Mark.field "length" Mark.int)
= in
= Mark.block "Editor"
= identity
= (Mark.startWith render
+ annotations
= code
- (Mark.manyOf [ highlight ])
= )
=
=Make navigation bar respond to viewport width changes.
Use icons when viewport is < 600px.
Co-Authored-By: Tadeusz Łazurski tadeusz@lazurski.pl
index 9cc4789..87fee45 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -8,7 +8,8 @@ It fetches the Elm Markup file at /index.txt and renders it. There are a number
=
=import Basics.Extra exposing (curry)
=import Browser
-import Browser.Dom as Dom
+import Browser.Dom as Dom exposing (Viewport)
+import Browser.Events
=import Browser.Navigation as Navigation
=import BrowserWindow
=import Dict
@@ -74,6 +75,7 @@ type alias Model =
= , content : Content
= , examples : Examples.Model
= , scroll : Float
+ , viewport : { width : Int, height : Int }
= }
=
=
@@ -84,6 +86,7 @@ type Msg
= | ContentFetched (Result Http.Error String)
= | ExamplesMsg Examples.Msg
= | Scroll Float
+ | ViewportMeasured { width : Int, height : Int }
=
=
=type Content
@@ -117,12 +120,22 @@ init flags url key =
= , key = key
= , content = Loading
= , examples = examplesModel
- , scroll = 1
+ , scroll = 0
+ , viewport =
+ { width = 800, height = 600 }
= }
= , Cmd.batch
= [ url
= |> Routes.parse
= |> loadContent
+ , Dom.getViewport
+ |> Task.map
+ (\{ viewport } ->
+ { width = round viewport.width
+ , height = round viewport.height
+ }
+ )
+ |> Task.perform ViewportMeasured
= , Cmd.map ExamplesMsg examplesCmd
= ]
= )
@@ -408,12 +421,23 @@ update msg model =
= , Cmd.none
= )
=
+ ViewportMeasured viewport ->
+ ( { model | viewport = viewport }
+ , Cmd.none
+ )
+
=
=subscriptions : Model -> Sub Msg
=subscriptions model =
- model.examples
- |> Examples.subscriptions
- |> Sub.map ExamplesMsg
+ Sub.batch
+ [ model.examples
+ |> Examples.subscriptions
+ |> Sub.map ExamplesMsg
+ , Browser.Events.onResize
+ (\width height ->
+ ViewportMeasured { width = width, height = height }
+ )
+ ]
=
=
=loadContent : Route -> Cmd Msg
@@ -465,41 +489,53 @@ homeNavigationBar =
=
=
=contentNavigationBar : Model -> Element Msg
-contentNavigationBar { location, scroll } =
+contentNavigationBar { location, scroll, viewport } =
= let
- links : List { url : String, label : String }
+ links : List { url : String, label : String, icon : Element msg }
= links =
= [ { url = "http://localhost:8001"
= , label = "Home"
+ , icon = FeatherIcons.home |> FeatherIcons.toHtml [] |> Element.html
= }
= , { url = "/preparation.html"
= , label = "Get ready"
+ , icon = FeatherIcons.bookOpen |> FeatherIcons.toHtml [] |> Element.html
= }
= , { url = "/day-1.html"
= , label = "Day 1"
+ , icon = Element.text "1"
= }
= , { url = "/day-2.html"
= , label = "Day 2"
+ , icon = Element.text "2"
= }
= , { url = "/day-3.html"
= , label = "Day 3"
+ , icon = Element.text "3"
= }
= , { url = "/day-4.html"
= , label = "Day 4"
+ , icon = Element.text "4"
= }
= , { url = "/day-5.html"
= , label = "Day 5"
+ , icon = Element.text "5"
= }
= ]
=
- linkElement : { url : String, label : String } -> Element Msg
+ linkElement : { url : String, label : String, icon : Element Msg } -> Element Msg
= linkElement link =
= Element.link
= [ Element.width Element.fill
= , Element.paddingEach { top = 40, right = 5, bottom = 20, left = 5 }
= ]
= { url = link.url
- , label = Element.el [ Element.centerX ] (Element.text link.label)
+ , label =
+ if viewport.width < 600 then
+ Element.el [ Element.centerX ] link.icon
+
+ else
+ Element.el [ Element.centerX ] (Element.text link.label)
= }
=
= linksRow =Split the code from Editor module into Examples.Editor and Mark.Custom
index 6c4cc3c..a25c922 100644
--- a/src/Editor.elm
+++ b/src/Editor.elm
@@ -1,7 +1,13 @@
-module Editor exposing (Annotation(..), Config, Region, defaults, editor)
+module Editor exposing
+ ( Annotations
+ , Colors
+ , Config
+ , Region
+ , defaults
+ , editor
+ )
=
=import Basics.Extra exposing (uncurry)
-import Browser
=import Dict exposing (Dict)
=import Element exposing (Element)
=import Element.Background as Background
@@ -10,46 +16,39 @@ import Element.Extra as Element
=import Element.Font as Font
=import Element.Input as Input
=import FeatherIcons exposing (icons)
-import Html exposing (Html)
=import List.Extra as List
=import Set exposing (Set)
-import Svg exposing (Svg)
=import Svg.Attributes
=
=
=type alias Config =
= { path : String
- , offset : Int
- , colors :
- { annotations : Element.Color
- , background : Element.Color
- , primary : Element.Color
- , secondary : Element.Color
- , window : Element.Color
- }
+ , colors : Colors
+ }
+
+
+type alias Colors =
+ { annotations : Element.Color
+ , background : Element.Color
+ , primary : Element.Color
+ , secondary : Element.Color
+ , window : Element.Color
= }
=
=
=defaults : Config
=defaults =
= { path = "src/Main.elm"
- , offset = 1
= , colors =
= { primary = Element.rgb 0 0 0
= , secondary = Element.rgb 0.8 0.8 0.8
- , annotations = Element.rgb 1 0.6 0.6
+ , annotations = Element.rgb 0.7 0 0
= , background = Element.rgb 1 1 1
= , window = Element.rgb 0.2 0.2 0.2
= }
= }
=
=
-type Annotation
- = None -- A dummy annotation to satisfy manyOf block. See https://github.com/mdgriffith/elm-markup/issues/12
- | Highlight Region
- | Fold Int Int
-
-
=type alias Region =
= { top : Int
= , left : Int
@@ -58,57 +57,19 @@ type alias Region =
= }
=
=
-highlight : Region -> Element msg
-highlight { top, left, width, height } =
- Element.row []
- [ " "
- |> String.repeat left
- |> Element.text
- , " "
- |> String.repeat width
- |> List.repeat height
- |> List.map Element.text
- |> List.map (Element.el [ Element.paddingXY 0 10 ])
- |> Element.column
- [ Element.behindContent
- (Element.el
- [ Element.width Element.fill
- , Element.height Element.fill
- , Border.color (Element.rgba 1 0 0 0.8)
- , -- Hand-drawn style borders. See https://codepen.io/tmrDevelops/pen/VeRvKX/
- [ width, height ]
- |> List.map ((*) 2)
- |> List.map String.fromInt
- |> List.map (\num -> num ++ "px")
- |> List.repeat 2
- |> List.concat
- |> (\list -> [ list, List.reverse list ])
- |> List.map (String.join " ")
- |> String.join " / "
- |> Element.css "border-radius"
- , Border.width 3
- , Element.scale 1.1
- ]
- Element.none
- )
- , Element.css "pointer-events" "none"
- , Element.css "user-select" "none"
- , Element.css "-webkit-user-select" "none"
- , Element.css "-ms-user-select" "none"
- , Element.css "-webkit-touch-callout" "none"
- , Element.css "-o-user-select" "none"
- , Element.css "-moz-user-select" "none"
- ]
- ]
-
-
=type Fragment
= = Folded Int
= | Unfolded (List String)
=
=
-editor : Config -> List Annotation -> String -> Element msg
-editor { path, offset, colors } annotations code =
+type alias Annotations =
+ { highlights : List Region
+ , folded : Set Int
+ }
+
+
+editor : Config -> Annotations -> String -> Element msg
+editor { path, colors } { highlights, folded } code =
= let
= topBar =
= Element.row
@@ -139,9 +100,12 @@ editor { path, offset, colors } annotations code =
= code
= |> String.lines
= |> List.indexedMap Tuple.pair
- |> extractFragments []
+ |> extractFragments folded []
= |> renderFragments 1 []
- |> Element.column [ Element.width Element.fill ]
+ |> Element.column
+ [ Element.width Element.fill
+ , Element.scrollbarX
+ ]
=
= renderFragments :
= Int
@@ -155,229 +119,17 @@ editor { path, offset, colors } annotations code =
= List.reverse rendered
=
= (Folded length) :: rest ->
- let
- lineNumbers =
- [ start, start + length ]
- |> List.map String.fromInt
- |> String.join " - "
- |> Element.text
- |> Element.el
- [ Font.color colors.secondary
- , Font.size 14
- ]
-
- button =
- Input.button
- [ Font.size 10
- , Element.paddingXY 10 5
- , Background.color colors.secondary
- , Font.color colors.background
- , Border.rounded 3
- , Font.variant Font.smallCaps
- ]
- { onPress = Nothing
- , label = Element.text "unfold"
- }
-
- shadow =
- Element.el
- [ Element.width Element.fill
- , Element.height Element.fill
- , Border.innerShadow
- { offset = ( 0, 3 )
- , size = -6
- , blur = 12
- , color = colors.window
- }
- ]
- Element.none
-
- fold =
- [ lineNumbers, button ]
- |> Element.row
- [ Element.spacing 20
- , Element.padding 10
- , Background.color colors.background
- , Element.width Element.fill
- ]
- |> Element.el
- [ Element.paddingXY 5 10
- , Element.width Element.fill
- , Element.paddingEach
- { top = 0
- , right = 1
- , bottom = 0
- , left = 1
- }
- , Background.color colors.window
- , Element.inFront shadow
- ]
- in
- fold
+ fold colors start (length + start - 1)
= :: renderFragments (start + length) rendered rest
=
= (Unfolded lines) :: rest ->
- let
- unfolded =
- lines
- |> List.indexedMap
- (\n loc ->
- Element.row []
- [ Element.el
- [ Font.color colors.secondary
- , Font.extraLight
- , Element.width (Element.px 40)
- , Element.padding 10
- , Font.alignRight
- , Element.css "user-select" "none"
- , Element.css "-webkit-user-select" "none"
- , Element.css "-ms-user-select" "none"
- , Element.css "-webkit-touch-callout" "none"
- , Element.css "-o-user-select" "none"
- , Element.css "-moz-user-select" "none"
- ]
- ((n + start)
- |> String.fromInt
- |> Element.text
- )
- , Element.el
- [ Element.width Element.fill
- , Element.padding 10
- , highlighted
- |> Dict.get (n + start)
- |> Maybe.map highlight
- |> Maybe.withDefault Element.none
- |> Element.behindContent
- ]
- (Element.text loc)
- ]
- )
- |> Element.column
- [ Element.width Element.fill
- , Font.size 16
- , Element.scrollbarY
- ]
- in
- unfolded
+ unfolded colors highlighted start lines
= :: renderFragments (start + List.length lines) rendered rest
=
- folded : Set Int
- folded =
- annotations
- |> List.foldl folds []
- |> List.map (uncurry List.range)
- |> List.concat
- |> Set.fromList
-
- folds : Annotation -> List ( Int, Int ) -> List ( Int, Int )
- folds item memo =
- case item of
- Fold start size ->
- ( start, start + size ) :: memo
-
- _ ->
- memo
-
- extractFragments :
- List Fragment
- -> List ( Int, String )
- -> List Fragment
- extractFragments accumulated lines =
- case lines of
- [] ->
- -- We have reached the end of the code
- case accumulated of
- [] ->
- -- It was empty. Add one empty line.
- [ Unfolded [ "" ] ]
-
- (Folded n) :: _ ->
- List.reverse accumulated
-
- (Unfolded linesReversed) :: previous ->
- List.reverse
- (Unfolded (List.reverse linesReversed)
- :: previous
- )
-
- ( n, line ) :: rest ->
- let
- isFolded =
- Set.member (n + 1) folded
- in
- case accumulated of
- [] ->
- -- It's the first fragment. Check if it's folded or not.
- if isFolded then
- extractFragments [ Folded 1 ] rest
-
- else
- extractFragments [ Unfolded [ line ] ] rest
-
- (Folded length) :: previous ->
- if isFolded then
- -- Continue the fold
- let
- this =
- Folded (length + 1)
- in
- extractFragments
- (this :: previous)
- rest
-
- else
- -- Fold is finished. Start a new unfolded fragment.
- let
- this =
- Folded length
-
- next =
- Unfolded [ line ]
- in
- extractFragments
- (next :: this :: previous)
- rest
-
- (Unfolded linesReversed) :: previous ->
- if isFolded then
- -- Unfolded fragment is finished. Reverse the lines and start a new fold.
- let
- this =
- Unfolded (List.reverse linesReversed)
-
- next =
- Folded 1
- in
- extractFragments
- (next
- :: this
- :: previous
- )
- rest
-
- else
- -- Continue the unfolded fragment
- let
- this =
- Unfolded (line :: linesReversed)
- in
- extractFragments
- (this :: previous)
- rest
-
- highlighted : Dict Int Region
= highlighted =
- annotations
- |> List.foldl regions Dict.empty
-
- regions : Annotation -> Dict Int Region -> Dict Int Region
- regions item memo =
- case item of
- Highlight region ->
- Dict.insert region.top region memo
-
- _ ->
- memo
+ highlights
+ |> List.zip (List.map .top highlights)
+ |> Dict.fromList
= in
= [ topBar
= , contents
@@ -396,127 +148,240 @@ editor { path, offset, colors } annotations code =
= ]
=
=
-main : Html msg
-main =
- """module Main exposing (main)
-
-import Browser
-import Dict
-import Element
-import Svg exposing (Svg)
-import Svg.Attributes
-
-
-main =
- Browser.element
- { init = init
- , view = view
- , update = update
- , subscriptions = subscriptions
- }
+highlight : Element.Color -> Region -> Element msg
+highlight color { top, left, width, height } =
+ Element.row []
+ [ " "
+ |> String.repeat left
+ |> Element.text
+ , " "
+ |> String.repeat width
+ |> List.repeat height
+ |> List.map Element.text
+ |> List.map (Element.el [ Element.paddingXY 0 10 ])
+ |> Element.column
+ [ Element.behindContent
+ (Element.el
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ , Border.color color
+ , -- Hand-drawn style borders. See https://codepen.io/tmrDevelops/pen/VeRvKX/
+ [ width, height ]
+ |> List.map ((*) 2)
+ |> List.map String.fromInt
+ |> List.map (\num -> num ++ "px")
+ |> List.repeat 2
+ |> List.concat
+ |> (\list -> [ list, List.reverse list ])
+ |> List.map (String.join " ")
+ |> String.join " / "
+ |> Element.css "border-radius"
+ , Border.width 5
+ , Element.scale 1.1
+ , Element.alpha 0.7
+ ]
+ Element.none
+ )
+ , Element.css "pointer-events" "none"
+ , Element.css "user-select" "none"
+ , Element.css "-webkit-user-select" "none"
+ , Element.css "-ms-user-select" "none"
+ , Element.css "-webkit-touch-callout" "none"
+ , Element.css "-o-user-select" "none"
+ , Element.css "-moz-user-select" "none"
+ ]
+ ]
=
=
-view age =
- [ segment (age / 5000) { color = "brown", rotation = -90 } ]
- |> Svg.svg
- [ Svg.Attributes.height "100%"
- , Svg.Attributes.width "100%"
- , Svg.Attributes.style "background: none"
- , Svg.Attributes.viewBox "-500 -500 1000 1000"
- ]
- |> Element.html
- |> Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
- ]
+fold : Colors -> Int -> Int -> Element msg
+fold colors start end =
+ let
+ lineNumbers =
+ [ start, end ]
+ |> List.map String.fromInt
+ |> String.join " - "
+ |> Element.text
+ |> Element.el
+ [ Font.color colors.secondary
+ , Font.size 14
+ ]
=
+ button =
+ Input.button
+ [ Font.size 10
+ , Element.paddingXY 10 5
+ , Background.color colors.secondary
+ , Font.color colors.background
+ , Border.rounded 3
+ , Font.variant Font.smallCaps
+ ]
+ { onPress = Nothing
+ , label = Element.text "unfold"
+ }
=
-rules =
- Dict.empty
- |> Dict.insert "brown"
- [ { color = "brown", rotation = 0 }
- , { color = "green", rotation = 20 }
- , { color = "green", rotation = -30 }
+ shadow =
+ Element.el
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ , Border.innerShadow
+ { offset = ( 0, 3 )
+ , size = -6
+ , blur = 12
+ , color = colors.window
+ }
+ ]
+ Element.none
+ in
+ [ lineNumbers, button ]
+ |> Element.row
+ [ Element.spacing 20
+ , Element.padding 10
+ , Background.color colors.background
+ , Element.width Element.fill
= ]
- |> Dict.insert "green"
- [ { color = "red", rotation = -45 }
- , { color = "red", rotation = -5 }
- , { color = "red", rotation = 50 }
+ |> Element.el
+ [ Element.paddingXY 5 10
+ , Element.width Element.fill
+ , Element.paddingEach
+ { top = 0
+ , right = 1
+ , bottom = 0
+ , left = 1
+ }
+ , Background.color colors.window
+ , Element.inFront shadow
= ]
=
=
-segment age { color, rotation } =
- if age <= 0 then
- Svg.g [] []
-
- else
- Svg.g []
- [ rules
- |> Dict.get color
- |> Maybe.withDefault []
- |> List.map (segment (age - 1))
- |> Svg.g
- [ Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ") translate("
- , String.fromFloat (age * 10)
- , ")"
- ]
+unfolded : Colors -> Dict Int Region -> Int -> List String -> Element msg
+unfolded colors highlighted start lines =
+ lines
+ |> List.indexedMap
+ (\n loc ->
+ Element.row []
+ [ Element.el
+ [ Font.color colors.secondary
+ , Font.extraLight
+ , Element.width (Element.px 40)
+ , Element.padding 10
+ , Font.alignRight
+ , Element.css "user-select" "none"
+ , Element.css "-webkit-user-select" "none"
+ , Element.css "-ms-user-select" "none"
+ , Element.css "-webkit-touch-callout" "none"
+ , Element.css "-o-user-select" "none"
+ , Element.css "-moz-user-select" "none"
+ ]
+ ((n + start)
+ |> String.fromInt
+ |> Element.text
= )
+ , Element.el
+ [ Element.width Element.fill
+ , Element.padding 10
+ , highlighted
+ |> Dict.get (n + start)
+ |> Maybe.map (highlight colors.annotations)
+ |> Maybe.withDefault Element.none
+ |> Element.behindContent
+ ]
+ (Element.text loc)
= ]
- , dot age color rotation
- , line age color rotation
- ]
-
-
-dot age color rotation =
- Svg.circle
- [ Svg.Attributes.r (String.fromFloat age)
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill color
- , Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ") translate("
- , String.fromFloat (age * 10)
- , ")"
- ]
- )
- ]
- []
-
-
- line age color rotation =
- Svg.line
- [ Svg.Attributes.strokeWidth "1"
- , Svg.Attributes.x1 "0"
- , Svg.Attributes.y1 "0"
- , Svg.Attributes.x1 (String.fromFloat (age * 10))
- , Svg.Attributes.y1 "0"
- , Svg.Attributes.stroke color
- , Svg.Attributes.strokeWidth (String.fromFloat age)
- , Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ")"
- ]
= )
- ]
- []
-"""
- |> editor defaults
- [ Highlight { top = 10, left = 0, width = 38, height = 7 }
- , Highlight { top = 3, left = 0, width = 15, height = 1 }
- , Highlight { top = 19, left = 0, width = 10, height = 1 }
- , Highlight { top = 20, left = 15, width = 10, height = 1 }
- , Fold 35 11
- ]
- |> Element.layout
+ |> Element.column
= [ Element.width Element.fill
- , Element.height Element.fill
+ , Font.size 16
+ , Element.clipY
= ]
+
+
+extractFragments :
+ Set Int
+ -> List Fragment
+ -> List ( Int, String )
+ -> List Fragment
+extractFragments folded accumulated lines =
+ case lines of
+ [] ->
+ -- We have reached the end of the code
+ case accumulated of
+ [] ->
+ -- It was empty. Add one empty line.
+ [ Unfolded [ "" ] ]
+
+ (Folded n) :: _ ->
+ List.reverse accumulated
+
+ (Unfolded linesReversed) :: previous ->
+ List.reverse
+ (Unfolded (List.reverse linesReversed)
+ :: previous
+ )
+
+ ( n, line ) :: rest ->
+ let
+ isFolded =
+ Set.member (n + 1) folded
+ in
+ case accumulated of
+ [] ->
+ -- It's the first fragment. Check if it's folded or not.
+ if isFolded then
+ extractFragments folded
+ [ Folded 1 ]
+ rest
+
+ else
+ extractFragments folded
+ [ Unfolded [ line ] ]
+ rest
+
+ (Folded length) :: previous ->
+ if isFolded then
+ -- Continue the fold
+ let
+ this =
+ Folded (length + 1)
+ in
+ extractFragments folded
+ (this :: previous)
+ rest
+
+ else
+ -- Fold is finished. Start a new unfolded fragment.
+ let
+ this =
+ Folded length
+
+ next =
+ Unfolded [ line ]
+ in
+ extractFragments folded
+ (next :: this :: previous)
+ rest
+
+ (Unfolded linesReversed) :: previous ->
+ if isFolded then
+ -- Unfolded fragment is finished. Reverse the lines and start a new fold.
+ let
+ this =
+ Unfolded (List.reverse linesReversed)
+
+ next =
+ Folded 1
+ in
+ extractFragments
+ folded
+ (next :: this :: previous)
+ rest
+
+ else
+ -- Continue the unfolded fragment
+ let
+ this =
+ Unfolded (line :: linesReversed)
+ in
+ extractFragments
+ folded
+ (this :: previous)
+ restnew file mode 100644
index 0000000..51fdac0
--- /dev/null
+++ b/src/Examples/Editor.elm
@@ -0,0 +1,64 @@
+module Examples.Editor exposing (main)
+
+import Editor exposing (editor)
+import Element exposing (Element)
+import Html exposing (Html)
+import Set exposing (Set)
+
+
+main : Html msg
+main =
+ """This is an example of how Editor module can be used.
+
+It supports highligs. Lines can be folded, like this
+
+This code is folded
+
+So is this.
+
+Highlights can span over multiple lines. Try:
+
+"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor
+incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
+nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
+Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
+fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
+culpa qui officia deserunt mollit anim id est laborum."
+ |> editor Editor.defaults
+ { highlights =
+ [ { top = 10, left = 1, width = 40, height = 7 }
+ , { top = 3, left = 1, width = 15, height = 1 }
+ , { top = 19, left = 1, width = 10, height = 1 }
+ , { top = 20, left = 15, width = 12, height = 1 }
+ ]
+ , folded =
+ [ List.range 4 6
+ , List.range 74 91
+ ]
+ |> List.concat
+ |> Set.fromList
+ }
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+
+"""
+ |> editor Editor.defaults
+ { highlights =
+ [ { top = 3, left = 13, width = 8, height = 1 }
+ , { top = 19, left = 15, width = 45, height = 4 }
+ ]
+ , folded =
+ [ List.range 5 7
+ , List.range 74 91
+ , List.range 20 21
+ ]
+ |> List.concat
+ |> Set.fromList
+ }
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]index 7185019..07cf136 100644
--- a/src/Mark/Custom.elm
+++ b/src/Mark/Custom.elm
@@ -29,6 +29,7 @@ import Html exposing (Html)
=import Html.Attributes
=import Mark
=import Mark.Default
+import Set
=
=
=title : Mark.Block String
@@ -82,42 +83,91 @@ monospace =
= ]
=
=
+
+-- TODO: Separate to Mark.Editor ?
+
+
+type Annotation
+ = None
+ | Highlight Int Int Int Int
+ | Fold Int Int
+
+
=editor : Mark.Block (a -> Element msg)
=editor =
= let
- render : List Editor.Annotation -> String -> model -> Element msg
+ render : Editor.Annotations -> String -> model -> Element msg
= render annotations_ contents model =
= Editor.editor Editor.defaults annotations_ contents
=
= annotations =
= Mark.block "Annotations"
- identity
+ (extractAnnotations { highlights = [], folded = Set.empty })
= (Mark.manyOf [ none, highlight, fold ])
=
+ extractAnnotations : Editor.Annotations -> List Annotation -> Editor.Annotations
+ extractAnnotations ({ highlights, folded } as extracted) remaining =
+ case remaining of
+ [] ->
+ { extracted
+ | highlights = List.reverse highlights
+ }
+
+ None :: rest ->
+ extractAnnotations
+ { highlights = highlights
+ , folded = folded
+ }
+ rest
+
+ (Highlight top left width height) :: rest ->
+ extractAnnotations
+ { extracted
+ | highlights =
+ { top = top
+ , left = left
+ , width = width
+ , height = height
+ }
+ :: highlights
+ }
+ rest
+
+ (Fold start length) :: rest ->
+ extractAnnotations
+ { extracted
+ | folded =
+ (start + length)
+ |> List.range start
+ |> Set.fromList
+ |> Set.union folded
+ }
+ rest
+
= code : Mark.Block String
= code =
= Mark.block "Code"
= identity
= Mark.multiline
=
- none : Mark.Block Editor.Annotation
+ none : Mark.Block Annotation
= none =
- Mark.stub "None" Editor.None
+ -- A dummy annotation to satisfy manyOf block. See https://github.com/mdgriffith/elm-markup/issues/12
+ Mark.stub "None" None
=
- highlight : Mark.Block Editor.Annotation
+ highlight : Mark.Block Annotation
= highlight =
= Mark.record4 "Highlight"
- Editor.Region
+ Highlight
= (Mark.field "top" Mark.int)
= (Mark.field "left" Mark.int)
= (Mark.field "width" Mark.int)
= (Mark.field "height" Mark.int)
- |> Mark.map Editor.Highlight
=
- fold : Mark.Block Editor.Annotation
+ fold : Mark.Block Annotation
= fold =
= Mark.record2 "Fold"
- Editor.Fold
+ Fold
= (Mark.field "start" Mark.int)
= (Mark.field "length" Mark.int)
= inFix the fold over highlight in the Editor example
index 51fdac0..bbfb394 100644
--- a/src/Examples/Editor.elm
+++ b/src/Examples/Editor.elm
@@ -52,8 +52,7 @@ culpa qui officia deserunt mollit anim id est laborum."
= ]
= , folded =
= [ List.range 5 7
- , List.range 74 91
- , List.range 20 21
+ , List.range 12 14
= ]
= |> List.concat
= |> Set.fromListDisable CI when not on master branch
index 8d347ad..4472935 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -13,5 +13,5 @@ pages:
= paths:
= - public
=
- # only:
- # - master
+ only:
+ - masterMerge branch 'content' into about-fana
Merge branch 'content' into navigation
Add anchor icon to Fana's bio.
index c2939da..2547076 100644
--- a/content/index.txt
+++ b/content/index.txt
@@ -29,4 +29,4 @@ I love the creativity of the software development and hope to share that passion
=
=*Sam* is a co-author of the workshop. He is an Elm developer at {Link|itravel|url=https://www.itravel.de/} in Cologne, Germany.
=
-*Fana* is a junior software developer (an ex marine biologist) and coordinator of our project. She keeps us on the right track. Also she is taking care of our media presence.
+*Fana* is a junior software developer (an ex marine biologist {Icon|name=anchor}) and coordinator of our project. She keeps us on the right track. Also she is taking care of our media presence.Fix home link in content navigation bar
index 87fee45..471756e 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -493,7 +493,7 @@ contentNavigationBar { location, scroll, viewport } =
= let
= links : List { url : String, label : String, icon : Element msg }
= links =
- [ { url = "http://localhost:8001"
+ [ { url = "/"
= , label = "Home"
= , icon = FeatherIcons.home |> FeatherIcons.toHtml [] |> Element.html
= }
Commits: 3
Make navigation bar fixed
Move navigationBar from document to view.
Add background color to contentNavigationBar.
Co-Authored-By: Tadeusz Łazurski tadeusz@lazurski.pl
index 5d2609b..64223f3 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -158,6 +158,18 @@ view model =
= Loaded documentView ->
= documentView model
=
+ navigationBar =
+ case Routes.parse model.location of
+ Routes.Home ->
+ -- TODO: Implement different navigation bar for home
+ contentNavigationBar model.location
+
+ Routes.Content _ ->
+ contentNavigationBar model.location
+
+ Routes.NotFound ->
+ contentNavigationBar model.location
+
= loadingView : View
= loadingView =
= { title = "Loading Content"
@@ -297,13 +309,14 @@ view model =
= in
= { title = "Software Garden : " ++ content.title
= , body =
- [ Element.layout
- [ -- Below is a hack that enables static site generation
- Element.htmlAttribute (Html.Attributes.id "app-container")
- , Element.htmlAttribute (Html.Attributes.lang "en")
- , Font.family [ Font.typeface "Montserrat", Font.sansSerif ]
- ]
- content.body
+ [ content.body
+ |> Element.layout
+ [ -- Below is a hack that enables static site generation
+ Element.htmlAttribute (Html.Attributes.id "app-container")
+ , Element.htmlAttribute (Html.Attributes.lang "en")
+ , Font.family [ Font.typeface "Montserrat", Font.sansSerif ]
+ , Element.inFront navigationBar
+ ]
= ]
= }
=
@@ -485,7 +498,10 @@ contentNavigationBar location =
= |> List.elemIndex route
= |> Maybe.withDefault 0
= in
- Element.column [ Element.width Element.fill ]
+ Element.column
+ [ Element.width Element.fill
+ , Background.color (Element.rgb 1 1 1)
+ ]
= [ linksRow
= , progressBar
= (List.length links)
@@ -506,33 +522,13 @@ document =
= -> Model
= -> View
= page { title, children } model =
- let
- navigationBar =
- case Routes.parse model.location of
- Routes.Home ->
- -- TODO: Implement different navigation bar for home
- contentNavigationBar model.location
-
- Routes.Content _ ->
- contentNavigationBar model.location
-
- Routes.NotFound ->
- contentNavigationBar model.location
-
- content =
- children
- |> List.map (\child -> child model.examples)
- |> Element.textColumn
- [ Element.centerX
- , Element.spacing 20
- , Element.paddingXY 0 80
- ]
- in
- [ navigationBar
- , content
- ]
- |> Element.column
- [ Element.width Element.fill ]
+ children
+ |> List.map (\child -> child model.examples)
+ |> Element.textColumn
+ [ Element.centerX
+ , Element.spacing 20
+ , Element.paddingXY 0 80
+ ]
= |> View title
=
= {- The document has to start with a Title block containing a String (i.e. single line of unforamtted text). This String will be used in two ways:Make progress bar respond to scroll position.
Co-Authored-By: Tadeusz Łazurski tadeusz@lazurski.pl
index 64223f3..d7e3485 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -36,7 +36,9 @@ import Examples.ViewBox
=import FeatherIcons exposing (icons)
=import Html exposing (Html)
=import Html.Attributes
+import Html.Events
=import Http
+import Json.Decode exposing (Decoder)
=import List.Extra as List
=import Mark
=import Mark.Custom
@@ -71,6 +73,7 @@ type alias Model =
= , key : Navigation.Key
= , content : Content
= , examples : Examples.Model
+ , scroll : Float
= }
=
=
@@ -80,6 +83,7 @@ type Msg
= | UrlChanged Url
= | ContentFetched (Result Http.Error String)
= | ExamplesMsg Examples.Msg
+ | Scroll Float
=
=
=type Content
@@ -113,6 +117,7 @@ init flags url key =
= , key = key
= , content = Loading
= , examples = examplesModel
+ , scroll = 1
= }
= , Cmd.batch
= [ url
@@ -162,13 +167,13 @@ view model =
= case Routes.parse model.location of
= Routes.Home ->
= -- TODO: Implement different navigation bar for home
- contentNavigationBar model.location
+ contentNavigationBar model
=
= Routes.Content _ ->
- contentNavigationBar model.location
+ contentNavigationBar model
=
= Routes.NotFound ->
- contentNavigationBar model.location
+ contentNavigationBar model
=
= loadingView : View
= loadingView =
@@ -306,6 +311,19 @@ view model =
= ++ ":"
= ++ String.fromInt col
= )
+
+ scrollDecoder : Decoder Msg
+ scrollDecoder =
+ Json.Decode.field "target"
+ (Json.Decode.map3
+ (\top height clientHeight ->
+ top / (height - clientHeight)
+ )
+ (Json.Decode.field "scrollTop" Json.Decode.float)
+ (Json.Decode.field "scrollHeight" Json.Decode.float)
+ (Json.Decode.field "clientHeight" Json.Decode.float)
+ )
+ |> Json.Decode.map Scroll
= in
= { title = "Software Garden : " ++ content.title
= , body =
@@ -316,6 +334,9 @@ view model =
= , Element.htmlAttribute (Html.Attributes.lang "en")
= , Font.family [ Font.typeface "Montserrat", Font.sansSerif ]
= , Element.inFront navigationBar
+ , Element.height Element.fill
+ , Element.scrollbars
+ , Element.htmlAttribute (Html.Events.on "scroll" scrollDecoder)
= ]
= ]
= }
@@ -345,6 +366,7 @@ update msg model =
= ( { model
= | location = url
= , content = Loading
+ , scroll = 0
= }
= , Cmd.batch
= [ url
@@ -380,6 +402,11 @@ update msg model =
= , Cmd.map ExamplesMsg examplesCmd
= )
=
+ Scroll position ->
+ ( { model | scroll = position }
+ , Cmd.none
+ )
+
=
=subscriptions : Model -> Sub Msg
=subscriptions model =
@@ -407,8 +434,8 @@ loadContent route =
= Cmd.none
=
=
-contentNavigationBar : Url -> Element Msg
-contentNavigationBar location =
+contentNavigationBar : Model -> Element Msg
+contentNavigationBar { location, scroll } =
= let
= links : List { url : String, label : String }
= links =
@@ -501,6 +528,7 @@ contentNavigationBar location =
= Element.column
= [ Element.width Element.fill
= , Background.color (Element.rgb 1 1 1)
+ , Element.htmlAttribute (Html.Attributes.id "navigation-bar")
= ]
= [ linksRow
= , progressBar
@@ -508,7 +536,7 @@ contentNavigationBar location =
= -- index of current route
= past
= -- scroll position (0 - 1)
- 0.5
+ scroll
= ]
=
=Hide navigation bar when printing.
Co-Authored-By: Tadeusz Łazurski tadeusz@lazurski.pl
index 1f43711..b2d458d 100644
--- a/container.html
+++ b/container.html
@@ -5,6 +5,17 @@
= <title>Software Garden</title>
= <style>
= @import url('https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Source+Code+Pro:300,400,700');
+
+ @media print {
+ #app-container {
+ height: auto;
+ }
+
+ #navigation-bar {
+ display: none;
+ }
+
+ }
= </style>
= </head>
= <body>
Commits: 1
Create a navigation bar
index a0c8d8f..5d2609b 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -37,6 +37,7 @@ import FeatherIcons exposing (icons)
=import Html exposing (Html)
=import Html.Attributes
=import Http
+import List.Extra as List
=import Mark
=import Mark.Custom
=import Parser
@@ -66,7 +67,7 @@ type alias Flags =
=
=
=type alias Model =
- { url : Url
+ { location : Url
= , key : Navigation.Key
= , content : Content
= , examples : Examples.Model
@@ -85,7 +86,7 @@ type Content
= = Loading
= | FetchError Http.Error
= | ParseError (List DeadEnd)
- | Loaded (Examples.Model -> View)
+ | Loaded (Model -> View)
=
=
=type alias DeadEnd =
@@ -99,7 +100,7 @@ type alias View =
=
=
=type alias Document =
- Mark.Document (Examples.Model -> View)
+ Mark.Document (Model -> View)
=
=
=init : Flags -> Url -> Navigation.Key -> ( Model, Cmd Msg )
@@ -108,7 +109,7 @@ init flags url key =
= ( examplesModel, examplesCmd ) =
= Examples.init
= in
- ( { url = url
+ ( { location = url
= , key = key
= , content = Loading
= , examples = examplesModel
@@ -155,7 +156,7 @@ view model =
= deadEndsView deadEnds
=
= Loaded documentView ->
- documentView model.examples
+ documentView model
=
= loadingView : View
= loadingView =
@@ -329,7 +330,7 @@ update msg model =
=
= UrlChanged url ->
= ( { model
- | url = url
+ | location = url
= , content = Loading
= }
= , Cmd.batch
@@ -393,23 +394,145 @@ loadContent route =
= Cmd.none
=
=
+contentNavigationBar : Url -> Element Msg
+contentNavigationBar location =
+ let
+ links : List { url : String, label : String }
+ links =
+ [ { url = "http://localhost:8001"
+ , label = "Home"
+ }
+ , { url = "/preparation.html"
+ , label = "Get ready"
+ }
+ , { url = "/day-1.html"
+ , label = "Day 1"
+ }
+ , { url = "/day-2.html"
+ , label = "Day 2"
+ }
+ , { url = "/day-3.html"
+ , label = "Day 3"
+ }
+ , { url = "/day-4.html"
+ , label = "Day 4"
+ }
+ , { url = "/day-5.html"
+ , label = "Day 5"
+ }
+ ]
+
+ linkElement : { url : String, label : String } -> Element Msg
+ linkElement link =
+ Element.link
+ [ Element.width Element.fill
+ , Element.padding 20
+ ]
+ { url = link.url
+ , label = Element.el [ Element.centerX ] (Element.text link.label)
+ }
+
+ linksRow =
+ links
+ |> List.map linkElement
+ |> Element.row
+ [ Element.width Element.fill
+ , Element.spaceEvenly
+ , Font.bold
+ ]
+
+ progressBar : Int -> Int -> Float -> Element Msg
+ progressBar total done progress =
+ Element.row [ Element.width Element.fill ]
+ [ Element.el
+ [ Element.width (Element.fillPortion done)
+ , Element.height (Element.px 4)
+ , Background.color (Element.rgb 0 0.6 0)
+ ]
+ Element.none
+ , Element.row
+ [ Element.width (Element.fillPortion 1)
+ , Element.height (Element.px 4)
+ ]
+ [ Element.el
+ [ Element.width (Element.fillPortion (round (progress * 1000)))
+ , Element.height (Element.px 4)
+ , Background.color (Element.rgb 0 0.6 0)
+ ]
+ Element.none
+ , Element.el
+ [ Element.width (Element.fillPortion (1000 - round (progress * 1000)))
+ , Element.height (Element.px 4)
+ ]
+ Element.none
+ ]
+ , Element.el
+ [ Element.width (Element.fillPortion (total - done - 1))
+ , Element.height (Element.px 4)
+ ]
+ Element.none
+ ]
+
+ route =
+ Routes.parse location
+
+ past : Int
+ past =
+ links
+ |> List.map .url
+ |> List.map (\newPath -> { location | path = newPath })
+ |> List.map Routes.parse
+ |> List.elemIndex route
+ |> Maybe.withDefault 0
+ in
+ Element.column [ Element.width Element.fill ]
+ [ linksRow
+ , progressBar
+ (List.length links)
+ -- index of current route
+ past
+ -- scroll position (0 - 1)
+ 0.5
+ ]
+
+
=document : Document
=document =
= let
- content :
+ page :
= { title : String
= , children : List (Examples.Model -> Element Msg)
= }
- -> Examples.Model
+ -> Model
= -> View
- content { title, children } model =
- children
- |> List.map (\child -> child model)
- |> Element.textColumn
- [ Element.centerX
- , Element.spacing 20
- , Element.paddingXY 0 80
- ]
+ page { title, children } model =
+ let
+ navigationBar =
+ case Routes.parse model.location of
+ Routes.Home ->
+ -- TODO: Implement different navigation bar for home
+ contentNavigationBar model.location
+
+ Routes.Content _ ->
+ contentNavigationBar model.location
+
+ Routes.NotFound ->
+ contentNavigationBar model.location
+
+ content =
+ children
+ |> List.map (\child -> child model.examples)
+ |> Element.textColumn
+ [ Element.centerX
+ , Element.spacing 20
+ , Element.paddingXY 0 80
+ ]
+ in
+ [ navigationBar
+ , content
+ ]
+ |> Element.column
+ [ Element.width Element.fill ]
= |> View title
=
= {- The document has to start with a Title block containing a String (i.e. single line of unforamtted text). This String will be used in two ways:
@@ -714,5 +837,5 @@ document =
= Mark.stub "ViewBox" render
= in
= Mark.document
- content
+ page
= structure
Commits: 1
Replace Code block with Editor in day 5
index ce02674..c073ebc 100644
--- a/content/day-5.txt
+++ b/content/day-5.txt
@@ -92,117 +92,136 @@ State is sometimes called model. There are also commands, but we will not use th
=
=First let's change the value of {Code|main} and create the {Code|view} function, like this:
=
-| Code
- module Main exposing (main)
-
- import Browser
- import Dict
- import Element
- import Svg exposing (Svg)
- import Svg.Attributes
-
-
- main =
- Browser.element
- { init = init
- , view = view
- , update = update
- , subscriptions = subscriptions
- }
-
-
- view age =
- [ segment (age / 5000) { color = "brown", rotation = -90 } ]
- |> Svg.svg
- [ Svg.Attributes.height "100%"
- , Svg.Attributes.width "100%"
- , Svg.Attributes.style "background: none"
- , Svg.Attributes.viewBox "-500 -500 1000 1000"
- ]
- |> Element.html
- |> Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
- ]
+| Editor
+ | Code
+ module Main exposing (main)
+
+ import Browser
+ import Dict
+ import Element
+ import Svg exposing (Svg)
+ import Svg.Attributes
+
+
+ main =
+ Browser.element
+ { init = init
+ , view = view
+ , update = update
+ , subscriptions = subscriptions
+ }
+
+
+ view age =
+ [ segment (age / 5000) { color = "brown", rotation = -90 } ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: none"
+ , Svg.Attributes.viewBox "-500 -500 1000 1000"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
=
=
- rules =
- Dict.empty
- |> Dict.insert "brown"
- [ { color = "brown", rotation = 0 }
- , { color = "green", rotation = 20 }
- , { color = "green", rotation = -30 }
- ]
- |> Dict.insert "green"
- [ { color = "red", rotation = -45 }
- , { color = "red", rotation = -5 }
- , { color = "red", rotation = 50 }
- ]
+ rules =
+ Dict.empty
+ |> Dict.insert "brown"
+ [ { color = "brown", rotation = 0 }
+ , { color = "green", rotation = 20 }
+ , { color = "green", rotation = -30 }
+ ]
+ |> Dict.insert "green"
+ [ { color = "red", rotation = -45 }
+ , { color = "red", rotation = -5 }
+ , { color = "red", rotation = 50 }
+ ]
=
=
- segment age { color, rotation } =
- if age <= 0 then
- Svg.g [] []
-
- else
- Svg.g []
- [ rules
- |> Dict.get color
- |> Maybe.withDefault []
- |> List.map (segment (age - 1))
- |> Svg.g
- [ Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ") translate("
- , String.fromFloat (age * 10)
- , ")"
- ]
- )
- ]
- , dot age color rotation
- , line age color rotation
- ]
+ segment age { color, rotation } =
+ if age <= 0 then
+ Svg.g [] []
+
+ else
+ Svg.g []
+ [ rules
+ |> Dict.get color
+ |> Maybe.withDefault []
+ |> List.map (segment (age - 1))
+ |> Svg.g
+ [ Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate("
+ , String.fromFloat (age * 10)
+ , ")"
+ ]
+ )
+ ]
+ , dot age color rotation
+ , line age color rotation
+ ]
=
=
- dot age color rotation =
- Svg.circle
- [ Svg.Attributes.r (String.fromFloat age)
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill color
- , Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ") translate("
- , String.fromFloat (age * 10)
- , ")"
- ]
- )
- ]
- []
-
-
- line age color rotation =
- Svg.line
- [ Svg.Attributes.strokeWidth "1"
- , Svg.Attributes.x1 "0"
- , Svg.Attributes.y1 "0"
- , Svg.Attributes.x1 (String.fromFloat (age * 10))
- , Svg.Attributes.y1 "0"
- , Svg.Attributes.stroke color
- , Svg.Attributes.strokeWidth (String.fromFloat age)
- , Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ")"
- ]
- )
- ]
- []
+ dot age color rotation =
+ Svg.circle
+ [ Svg.Attributes.r (String.fromFloat age)
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate("
+ , String.fromFloat (age * 10)
+ , ")"
+ ]
+ )
+ ]
+ []
+
+
+ line age color rotation =
+ Svg.line
+ [ Svg.Attributes.strokeWidth "1"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x1 (String.fromFloat (age * 10))
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.stroke color
+ , Svg.Attributes.strokeWidth (String.fromFloat age)
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ")"
+ ]
+ )
+ ]
+ []
+
+ | Highlight
+ from = 11
+ to = 16
+ offset = 4
+ width = 35
+
+ | Highlight
+ from = 19
+ to = 19
+ offset = 0
+ width = 10
+
+ | Highlight
+ from = 20
+ to = 20
+ offset = 15
+ width = 10
=
=Notice that the {Code|view} takes an argument called {Code|age} and passes it to the first (brown) segment. The value of {Code|age} is our state. In some programs the type of state can be very complex, but in our program it's simply a {Code|Float} number. It will indicate how much time in milliseconds have passed from the start of the program. Because the time is counted in milliseconds, to get correct results we need to divide it by 5000 (so the tree will grows one generation of segments per five second).
=
@@ -217,9 +236,16 @@ First value is more important. This is the initial age of the tree, right after
=
=That's our init:
=
-| Code
- init () =
- ( 0, Cmd.none )
+| Editor
+ | Code
+ init () =
+ ( 0, Cmd.none )
+
+ | Highlight
+ from = 0
+ to = 0
+ offset = 0
+ width = 10
=
=Time for the {Code|update} function. It will get two arguments: an incoming message and the current state. Incoming message will always be a duration of time since previous frame, so let's simply call it {Code|duration}. The state is the current age of the tree, so again we can call it {Code|age}. In return we must produce a tuple with:
=
@@ -229,11 +255,18 @@ Time for the {Code|update} function. It will get two arguments: an incoming mess
=
=It looks like this:
=
-| Code
- update duration age =
- ( age + duration
- , Cmd.none
- )
+| Editor
+ | Code
+ update duration age =
+ ( age + duration
+ , Cmd.none
+ )
+
+ | Highlight
+ from = 0
+ to = 0
+ offset = 0
+ width = 10
=
=Finally let's subscribe to the events marking the passage of time. We can do it using {Code|Browser.Events.onAnimationFrameDelta}. Here is how it works. Whenever the browser is ready for the next frame it will send us a message containing the number of milliseconds that have passed since previous frame.
=
@@ -244,141 +277,156 @@ To use it we first need to import the {Code|Browser.Events} module. The {Code|Br
=
=The whole {Code|subscriptions} looks like that:
=
-| Code
- subscriptions age =
- Browser.Events.onAnimationFrameDelta identity
+| Editor
+ | Code
+ subscriptions age =
+ Browser.Events.onAnimationFrameDelta identity
+
+ | Highlight
+ from = 0
+ to = 0
+ offset = 0
+ width = 10
=
=And the complete code like this:
=
-| Code
- module Main exposing (main)
+| Editor
+ | Code
+ module Main exposing (main)
=
- import Browser
- import Browser.Events
- import Dict
- import Element
- import Svg exposing (Svg)
- import Svg.Attributes
+ import Browser
+ import Browser.Events
+ import Dict
+ import Element
+ import Svg exposing (Svg)
+ import Svg.Attributes
=
=
- main =
- Browser.element
- { init = init
- , view = view
- , update = update
- , subscriptions = subscriptions
- }
+ main =
+ Browser.element
+ { init = init
+ , view = view
+ , update = update
+ , subscriptions = subscriptions
+ }
=
=
- init () =
- ( 0, Cmd.none )
+ init () =
+ ( 0, Cmd.none )
=
=
- view age =
- [ segment (age / 5000) { color = "brown", rotation = -90 } ]
- |> Svg.svg
- [ Svg.Attributes.height "100%"
- , Svg.Attributes.width "100%"
- , Svg.Attributes.style "background: none"
- , Svg.Attributes.viewBox "-500 -500 1000 1000"
- ]
- |> Element.html
- |> Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
- ]
+ view age =
+ [ segment (age / 5000) { color = "brown", rotation = -90 } ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: none"
+ , Svg.Attributes.viewBox "-500 -500 1000 1000"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
=
=
- update duration age =
- ( duration
- |> Debug.log "Duration"
- |> (+) age
- |> Debug.log "Age"
- , Cmd.none
- )
+ update duration age =
+ ( duration
+ |> Debug.log "Duration"
+ |> (+) age
+ |> Debug.log "Age"
+ , Cmd.none
+ )
=
=
- subscriptions age =
- Browser.Events.onAnimationFrameDelta identity
+ subscriptions age =
+ Browser.Events.onAnimationFrameDelta identity
=
=
- rules =
- Dict.empty
- |> Dict.insert "brown"
- [ { color = "brown", rotation = 0 }
- , { color = "green", rotation = 20 }
- , { color = "green", rotation = -30 }
- ]
- |> Dict.insert "green"
- [ { color = "red", rotation = -45 }
- , { color = "red", rotation = -5 }
- , { color = "red", rotation = 50 }
- ]
+ rules =
+ Dict.empty
+ |> Dict.insert "brown"
+ [ { color = "brown", rotation = 0 }
+ , { color = "green", rotation = 20 }
+ , { color = "green", rotation = -30 }
+ ]
+ |> Dict.insert "green"
+ [ { color = "red", rotation = -45 }
+ , { color = "red", rotation = -5 }
+ , { color = "red", rotation = 50 }
+ ]
+
+
+ segment age { color, rotation } =
+ if age <= 0 then
+ Svg.g [] []
+
+ else
+ Svg.g []
+ [ rules
+ |> Dict.get color
+ |> Maybe.withDefault []
+ |> List.map (segment (age - 1))
+ |> Svg.g
+ [ Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate("
+ , String.fromFloat (age * 10)
+ , ")"
+ ]
+ )
+ ]
+ , dot age color rotation
+ , line age color rotation
+ ]
=
=
- segment age { color, rotation } =
- if age <= 0 then
- Svg.g [] []
-
- else
- Svg.g []
- [ rules
- |> Dict.get color
- |> Maybe.withDefault []
- |> List.map (segment (age - 1))
- |> Svg.g
- [ Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ") translate("
- , String.fromFloat (age * 10)
- , ")"
- ]
- )
+ dot age color rotation =
+ Svg.circle
+ [ Svg.Attributes.r (String.fromFloat age)
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate("
+ , String.fromFloat (age * 10)
+ , ")"
= ]
- , dot age color rotation
- , line age color rotation
+ )
= ]
+ []
+
+
+ line age color rotation =
+ Svg.line
+ [ Svg.Attributes.strokeWidth "1"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x1 (String.fromFloat (age * 10))
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.stroke color
+ , Svg.Attributes.strokeWidth (String.fromFloat age)
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ")"
+ ]
+ )
+ ]
+ []
=
+ | Highlight
+ from = 0
+ to = 0
+ offset = 0
+ width = 10
=
- dot age color rotation =
- Svg.circle
- [ Svg.Attributes.r (String.fromFloat age)
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill color
- , Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ") translate("
- , String.fromFloat (age * 10)
- , ")"
- ]
- )
- ]
- []
-
-
- line age color rotation =
- Svg.line
- [ Svg.Attributes.strokeWidth "1"
- , Svg.Attributes.x1 "0"
- , Svg.Attributes.y1 "0"
- , Svg.Attributes.x1 (String.fromFloat (age * 10))
- , Svg.Attributes.y1 "0"
- , Svg.Attributes.stroke color
- , Svg.Attributes.strokeWidth (String.fromFloat age)
- , Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ")"
- ]
- )
- ]
- []
=
=| Emphasize
= That's it!
Commits: 4
Remove tree from the examples model, messages and subscriptions.
The tree is static now.
index e4d98a5..94e0a3f 100644
--- a/src/Examples.elm
+++ b/src/Examples.elm
@@ -21,7 +21,6 @@ type alias Model =
= , nestedTransformations : Examples.NestedTransformations.Model
= , cartesianCoordinates : Examples.CartesianCoordinates.Model
= , polarCoordinates : Examples.PolarCoordinates.Model
- , tree : Maybe Examples.Tree.Model
= , viewBox : Examples.ViewBox.Model
= }
=
@@ -32,8 +31,6 @@ type Msg
= | NestedTransformationsMsg Examples.NestedTransformations.Msg
= | CartesianCoordinatesMsg Examples.CartesianCoordinates.Msg
= | PolarCoordinatesMsg Examples.PolarCoordinates.Msg
- | ToggleTree (Maybe Examples.Tree.Model)
- | TreeMsg Examples.Tree.Msg
= | ViewBoxMsg Examples.ViewBox.Msg
=
=
@@ -44,7 +41,6 @@ init =
= , nestedTransformations = Examples.NestedTransformations.init
= , cartesianCoordinates = Examples.CartesianCoordinates.init
= , polarCoordinates = Examples.PolarCoordinates.init
- , tree = Nothing
= , viewBox = Examples.ViewBox.init
= }
= , Cmd.none
@@ -91,32 +87,10 @@ update msg model =
= , Cmd.none
= )
=
- ToggleTree tree ->
- ( { model | tree = tree }, Cmd.none )
-
- TreeMsg m ->
- case model.tree of
- Just tree ->
- Examples.Tree.update m tree
- |> (\( newTree, treeCmd ) ->
- ( { model | tree = Just newTree }
- , Cmd.map TreeMsg treeCmd
- )
- )
-
- Nothing ->
- ( model, Cmd.none )
-
= ViewBoxMsg m ->
= ( { model | viewBox = Examples.ViewBox.update m model.viewBox }, Cmd.none )
=
=
=subscriptions : Model -> Sub Msg
=subscriptions model =
- case model.tree of
- Nothing ->
- Sub.none
-
- Just tree ->
- Examples.Tree.subscriptions tree
- |> Sub.map TreeMsg
+ Sub.noneExpose Rule and defaults from Examples.Tree module
index 26d559e..88234ae 100644
--- a/src/Examples/Tree.elm
+++ b/src/Examples/Tree.elm
@@ -1,7 +1,9 @@
=module Examples.Tree exposing
= ( Axiom
= , Config
+ , Rule
= , Segment
+ , defaults
= , main
= , ui
= )Make the default tree more beautiful.
index 88234ae..8fee380 100644
--- a/src/Examples/Tree.elm
+++ b/src/Examples/Tree.elm
@@ -17,45 +17,27 @@ import Svg.Attributes
=defaults : Config
=defaults =
= { axiom =
- { color = "brown"
+ { color = "purple"
= , rotation = -90
- , age = 8
+ , age = 7
= }
= , rules =
- [ ( "brown"
- , [ { color = "green"
- , rotation = 45
- }
- , { color = "green"
- , rotation = 20
- }
- , { color = "green"
- , rotation = -20
- }
+ [ ( "purple"
+ , [ { color = "green", rotation = 0.0 }
+ , { color = "green", rotation = 30 }
+ , { color = "green", rotation = -30 }
= ]
= )
= , ( "green"
- , [ { color = "purple"
- , rotation = 0
- }
- , { color = "purple"
- , rotation = -90
- }
- , { color = "purple"
- , rotation = -20
- }
+ , [ { color = "brown", rotation = 0.0 }
+ , { color = "brown", rotation = 30 }
+ , { color = "brown", rotation = -30 }
= ]
= )
- , ( "purple"
- , [ { color = "orange"
- , rotation = 0
- }
- , { color = "orange"
- , rotation = 60
- }
- , { color = "brown"
- , rotation = -20
- }
+ , ( "brown"
+ , [ { color = "green", rotation = 0.0 }
+ , { color = "green", rotation = 30 }
+ , { color = "green", rotation = -30 }
= ]
= )
= ]WIP tree block for our markup
We believe that our code is correct but there is a bug in the mdgriffith/elm-markup package.
index 03acab1..17c4bf3 100644
--- a/content/day-4.txt
+++ b/content/day-4.txt
@@ -18,8 +18,63 @@
=| Header
= The Problem
=
-| Monospace
- TODO: Static tree example
+| Note
+ FIXME: The tree example is broken.
+
+| Window
+ | Tree
+ | Axiom
+ color = green
+ rotation = 0
+ age = 9
+
+ | Rule
+ | Parent
+ purple
+
+ | Child
+ color = green
+ rotation = 0.0
+
+ | Child
+ color = green
+ rotation = 30
+
+ | Child
+ color = green
+ rotation = -30
+
+ | Rule
+ | Parent
+ green
+
+ | Child
+ color = brown
+ rotation = 0.0
+
+ | Child
+ color = brown
+ rotation = 30
+
+ | Child
+ color = brown
+ rotation = -30
+
+ | Rule
+
+ | Parent
+ brown
+
+ | Child
+ color = green
+ rotation = 0.0
+ | Child
+ color = green
+ rotation = 30
+ | Child
+ color = green
+ rotation = -30
+
=
=The tree is built from segments: a line and a dot. In this respect it is similar to the connected dots we created yesterday. If we group the dot and a line, we will have the building block for our tree. We can do it with {Code|Svg.g} function ({Code|g} is an abbreviation of "group"). Just like {Code|Svg.svg}, it takes list of attributes and list of children. We can use it like that:
=index a0c8d8f..2b7617c 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -685,20 +685,52 @@ document =
= tree : Mark.Block (Examples.Model -> Element Msg)
= tree =
= let
- render model =
- model.tree
- |> Maybe.map Examples.Tree.ui
- |> Maybe.withDefault Element.none
- |> Element.el
- [ Element.height (Element.px 600)
- , Element.width Element.fill
- ]
- |> BrowserWindow.window []
- |> Element.map Examples.TreeMsg
- |> Element.map ExamplesMsg
+ render : Examples.Tree.Config -> Examples.Model -> Element Msg
+ render config model =
+ Examples.Tree.ui config
+
+ axiom : Mark.Block Examples.Tree.Axiom
+ axiom =
+ Mark.record3 "Axiom"
+ Examples.Tree.Axiom
+ (Mark.field "color" Mark.string)
+ (Mark.field "rotation" Mark.float)
+ (Mark.field "age" Mark.float)
+
+ rules : Mark.Block (List Examples.Tree.Rule)
+ rules =
+ Mark.manyOf [ rule ]
+
+ rule =
+ Mark.startWith Tuple.pair
+ parent
+ (Mark.manyOf [ child ])
+
+ -- FIXME: Parser breaks with empty list of dead ends
+ -- Mark.stub "Rule" ( "green", [ Examples.Tree.Segment "green" 30 ] )
+ -- Mark.manyOf [ child ]
+ -- |> Mark.map (Tuple.pair "green")
+ parent =
+ Mark.block "Parent" identity Mark.string
+
+ -- Mark.stub "Parent" "green"
+ child =
+ Mark.record2 "Child"
+ Examples.Tree.Segment
+ (Mark.field "color" Mark.string)
+ (Mark.field "rotation" Mark.float)
= in
- Mark.stub "Tree" render
+ Mark.block "Tree"
+ render
+ (Mark.startWith Examples.Tree.Config
+ axiom
+ rules
+ )
=
+ -- Mark.Block Examples.Tree.Config
+ -- result = Examples.Tree.Config
+ -- start = Axiom
+ -- rest = List Rule
= viewBox : Mark.Block (Examples.Model -> Element Msg)
= viewBox =
= let
Commits: 2
Replaced old tree example with new tree
index eaed7b3..26d559e 100644
--- a/src/Examples/Tree.elm
+++ b/src/Examples/Tree.elm
@@ -1,291 +1,189 @@
=module Examples.Tree exposing
- ( Model
- , Msg
- , init
+ ( Axiom
+ , Config
+ , Segment
= , main
- , subscriptions
= , ui
- , update
= )
=
-import Browser
-import Browser.Events
=import Dict exposing (Dict)
=import Element exposing (Element)
-import Html exposing (Html)
-import Json.Decode exposing (Decoder)
-import List.Extra as List
-import Svg exposing (..)
-import Svg.Attributes exposing (..)
+import Svg exposing (Svg)
+import Svg.Attributes
=
=
-main : Program Flags Model Msg
-main =
- Browser.element
- { init = init
- , view = view
- , update = update
- , subscriptions = subscriptions
+defaults : Config
+defaults =
+ { axiom =
+ { color = "brown"
+ , rotation = -90
+ , age = 8
= }
+ , rules =
+ [ ( "brown"
+ , [ { color = "green"
+ , rotation = 45
+ }
+ , { color = "green"
+ , rotation = 20
+ }
+ , { color = "green"
+ , rotation = -20
+ }
+ ]
+ )
+ , ( "green"
+ , [ { color = "purple"
+ , rotation = 0
+ }
+ , { color = "purple"
+ , rotation = -90
+ }
+ , { color = "purple"
+ , rotation = -20
+ }
+ ]
+ )
+ , ( "purple"
+ , [ { color = "orange"
+ , rotation = 0
+ }
+ , { color = "orange"
+ , rotation = 60
+ }
+ , { color = "brown"
+ , rotation = -20
+ }
+ ]
+ )
+ ]
+ }
=
=
-type alias Flags =
- ()
-
-
-type alias Model =
- { time : Float
- , rules : Rules
+type alias Config =
+ { axiom : Axiom
+ , rules : List Rule
= }
=
=
-type Msg
- = Progress Float
- | Regress Float
+type alias Axiom =
+ { color : String
+ , rotation : Float
+ , age : Float
+ }
=
=
-type alias Color =
- String
+type alias Rule =
+ ( String, List Segment )
=
=
=type alias Segment =
- { color : Color
- , angle : Float
- , length : Float
- }
-
+ { color : String, rotation : Float }
=
-type alias Rules =
- Dict Color (List Segment)
=
-
-init : Flags -> ( Model, Cmd Msg )
-init () =
- ( { time = 0
- , rules =
- Dict.fromList
- [ ( "brown"
- , [ Segment "green" -115 1
- , Segment "green" -65 1
- , Segment "saddlebrown" 90 2
+ui : Config -> Element msg
+ui config =
+ let
+ dot age color rotation =
+ Svg.circle
+ [ Svg.Attributes.r (String.fromFloat age)
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate("
+ , String.fromFloat (age * 10)
+ , ")"
+ ]
+ )
+ ]
+ []
+
+ line age color rotation =
+ Svg.line
+ [ Svg.Attributes.strokeWidth "1"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x2 (String.fromFloat (age * 10))
+ , Svg.Attributes.y2 "0"
+ , Svg.Attributes.stroke color
+ , Svg.Attributes.strokeWidth (String.fromFloat age)
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ")"
+ ]
+ )
+ ]
+ []
+
+ rules : Dict String (List Segment)
+ rules =
+ Dict.empty
+ |> Dict.insert "brown"
+ [ { color = "green", rotation = 20 }
+ , { color = "green", rotation = -20 }
+ , { color = "brown", rotation = 0 }
= ]
- )
- , ( "green"
- , [ Segment "green" 20 3
- , Segment "green" -20 3
- , Segment "red" 90 1
- , Segment "red" -90 1
+ |> Dict.insert "green"
+ [ { color = "blue", rotation = -25 }
+ , { color = "blue", rotation = -5 }
+ , { color = "blue", rotation = 15 }
= ]
- )
- , ( "saddlebrown"
- , [ Segment "saddlebrown" 20 3
- , Segment "saddlebrown" -20 3
+ |> Dict.insert "blue"
+ [ { color = "brown", rotation = -25 }
+ , { color = "purple", rotation = -5 }
+ , { color = "purple", rotation = 25 }
= ]
- )
- ]
- }
- , Cmd.none
- )
-
-
-view : Model -> Html Msg
-view model =
- Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
- ]
- (ui model)
-
=
-ui : Model -> Element Msg
-ui model =
- let
- gradients : List (Svg Msg)
- gradients =
- model.rules
- |> Dict.map
- (\parentColor children ->
- children
- |> List.map (\child -> ( parentColor, child.color ))
- )
- |> Dict.values
- |> List.concat
- |> List.unique
- |> List.map
- (\(( start, end ) as colors) ->
- linearGradient
- [ id (gradientId colors)
- , x1 "0"
- , y1 "0"
- , x2 "1"
- , y2 "0"
- , gradientUnits "userSpaceOnUse"
+ segment : Float -> Segment -> Svg msg
+ segment age { color, rotation } =
+ if age <= 0 then
+ Svg.g [] []
+
+ else
+ Svg.g []
+ [ config.rules
+ |> Dict.fromList
+ |> Dict.get color
+ |> Maybe.withDefault []
+ |> List.map (segment (age - 1))
+ |> Svg.g
+ [ Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate("
+ , String.fromFloat (age * 10)
+ , ")"
+ ]
+ )
= ]
- [ stop
- [ stopColor start, offset "0" ]
- []
- , stop
- [ stopColor end, offset "1" ]
- []
- ]
- )
-
- tree =
- "brown"
- |> svgTree model.rules (model.time / 5000)
- |> g
- [ transform
- ("scale("
- ++ String.fromFloat (model.time / 1000)
- ++ ")"
- )
+ , dot age color rotation
+ , line age color rotation
= ]
= in
- Element.html <|
- svg
- [ viewBox "-1000 -1000 2000 2000"
- , height "100%"
- , width "100%"
- ]
- [ defs [] gradients
- , tree
+ [ segment config.axiom.age
+ { color = config.axiom.color
+ , rotation = config.axiom.rotation
+ }
+ ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: none"
+ , Svg.Attributes.viewBox "-500 -500 1000 1000"
= ]
+ |> Element.html
=
=
-update : Msg -> Model -> ( Model, Cmd Msg )
-update msg model =
- case msg of
- Progress delta ->
- ( { model | time = model.time + delta }
- , Cmd.none
- )
-
- Regress delta ->
- ( { model | time = model.time - delta }
- , Cmd.none
- )
-
-
-subscriptions : Model -> Sub Msg
-subscriptions model =
- let
- handleKeyPress : Decoder Msg
- handleKeyPress =
- Json.Decode.field "key" Json.Decode.string
- |> Json.Decode.andThen
- (\key ->
- case key of
- "," ->
- Json.Decode.succeed (Regress 10)
-
- "." ->
- Json.Decode.succeed (Progress 10)
-
- "<" ->
- Json.Decode.succeed (Regress 1000)
-
- ">" ->
- Json.Decode.succeed (Progress 1000)
-
- _ ->
- Json.Decode.fail "Unknown key press"
- )
- in
- Sub.batch
- [ Browser.Events.onKeyPress handleKeyPress
- , Browser.Events.onAnimationFrameDelta Progress
- ]
-
-
-svgTree : Rules -> Float -> Color -> List (Svg Msg)
-svgTree rules age color =
- if age <= 0 then
- []
-
- else
- let
- subtrees =
- rules
- |> Dict.get color
- |> Maybe.withDefault []
- |> List.map subtree
-
- subtree segment =
- let
- gradient =
- "url(#" ++ gradientId ( color, segment.color ) ++ ")"
-
- scale =
- age / (age + 1)
- in
- g
- [ applyTransformations
- [ Rotate segment.angle
- , Scale scale scale
- , Translate segment.length 0
- ]
- ]
- (line
- [ stroke gradient
- , x1 "0"
- , x2 "1"
- , y1 "0"
- , y2 "0"
- , strokeWidth "0.2"
- , strokeLinecap "round"
- , applyTransformations
- [ Translate (0 - segment.length) 0
- , Scale segment.length 1
- ]
- ]
- []
- :: svgTree rules (age - 1) segment.color
- )
- in
- subtrees
-
-
-type Transformation
- = Identity
- | Scale Float Float
- | Translate Float Float
- | Rotate Float
-
-
-applyTransformations : List Transformation -> Svg.Attribute Msg
-applyTransformations transformations =
- let
- toString : Transformation -> String
- toString transformation =
- case transformation of
- Identity ->
- ""
-
- Scale x y ->
- "scale("
- ++ String.fromFloat x
- ++ ", "
- ++ String.fromFloat y
- ++ ")"
-
- Translate x y ->
- "translate("
- ++ String.fromFloat x
- ++ ", "
- ++ String.fromFloat y
- ++ ")"
-
- Rotate angle ->
- "rotate("
- ++ String.fromFloat angle
- ++ ")"
- in
- transformations
- |> List.map toString
- |> String.join " "
- |> transform
-
-
-gradientId : ( Color, Color ) -> String
-gradientId ( start, end ) =
- "connection-" ++ start ++ "-" ++ end
+main =
+ ui defaults
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]Replace Code block with a new Edito block supporting highlits
On the way I separated the css function from the Mark.Custom to the Element.Extra module.
new file mode 100644
index 0000000..2553eb0
--- /dev/null
+++ b/src/Editor.elm
@@ -0,0 +1,296 @@
+module Editor exposing (Config, Highlight, defaults, editor)
+
+import Browser
+import Dict exposing (Dict)
+import Element exposing (Element)
+import Element.Background as Background
+import Element.Border as Border
+import Element.Extra as Element
+import Element.Font as Font
+import FeatherIcons exposing (icons)
+import Html exposing (Html)
+import List.Extra as List
+import Svg exposing (Svg)
+import Svg.Attributes
+
+
+type alias Config =
+ { path : String
+ , offset : Int
+ , colors :
+ { annotations : Element.Color
+ , background : Element.Color
+ , primary : Element.Color
+ , secondary : Element.Color
+ , window : Element.Color
+ }
+ }
+
+
+defaults : Config
+defaults =
+ { path = "src/Main.elm"
+ , offset = 1
+ , colors =
+ { primary = Element.rgb 0 0 0
+ , secondary = Element.rgb 0.8 0.8 0.8
+ , annotations = Element.rgb 1 0.6 0.6
+ , background = Element.rgb 1 1 1
+ , window = Element.rgb 0.2 0.2 0.2
+ }
+ }
+
+
+type alias Highlight =
+ { from : Int
+ , to : Int
+ , offset : Int
+ , width : Int
+ }
+
+
+highlight : Highlight -> Element msg
+highlight { from, to, offset, width } =
+ Element.row []
+ [ " "
+ |> String.repeat offset
+ |> Element.text
+ , " "
+ |> String.repeat width
+ |> List.repeat (to - from + 1)
+ |> List.map Element.text
+ |> List.map (Element.el [ Element.padding 10 ])
+ |> Element.column
+ [ Element.inFront
+ (Element.el
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ , Border.color (Element.rgba 1 0 0 0.8)
+ , Border.rounded 10
+ , Border.width 2
+ , Border.dashed
+ , Element.scale 1.1
+ ]
+ Element.none
+ )
+ , Element.css "pointer-events" "none"
+ , Element.css "user-select" "none"
+ , Element.css "-webkit-user-select" "none"
+ , Element.css "-ms-user-select" "none"
+ , Element.css "-webkit-touch-callout" "none"
+ , Element.css "-o-user-select" "none"
+ , Element.css "-moz-user-select" "none"
+ ]
+ ]
+
+
+editor : Config -> List Highlight -> String -> Element msg
+editor { path, offset, colors } highlights contents =
+ let
+ highlighted : Dict Int Highlight
+ highlighted =
+ highlights
+ |> List.foldl extract Dict.empty
+
+ extract item memo =
+ Dict.insert item.from item memo
+ in
+ Element.column
+ [ Border.width 3
+ , Border.rounded 5
+ , Border.color colors.window
+ , Element.css "page-break-inside" "avoid"
+ , Font.family
+ [ Font.typeface "Source Code Pro"
+ , Font.monospace
+ ]
+ , Element.css "page-break-inside" "avoid"
+ , Element.width Element.fill
+ ]
+ [ Element.row
+ [ Element.width Element.fill
+ , Background.color colors.window
+ , Font.color colors.secondary
+ ]
+ [ FeatherIcons.fileText
+ |> FeatherIcons.toHtml []
+ |> Element.html
+ |> Element.el
+ [ Element.height (Element.px 35)
+ , Element.padding 8
+ ]
+ , Element.el
+ [ Element.width Element.fill
+ , Font.size 16
+ , Font.family
+ [ Font.typeface "Source Code Pro"
+ , Font.monospace
+ ]
+ ]
+ (Element.text "src/Main.elm")
+ ]
+ , contents
+ |> String.lines
+ |> List.indexedMap
+ (\n loc ->
+ Element.row []
+ [ Element.el
+ [ Font.color colors.secondary
+ , Font.extraLight
+ , Element.width (Element.px 40)
+ , Element.padding 10
+ , Font.alignRight
+ , Element.css "user-select" "none"
+ , Element.css "-webkit-user-select" "none"
+ , Element.css "-ms-user-select" "none"
+ , Element.css "-webkit-touch-callout" "none"
+ , Element.css "-o-user-select" "none"
+ , Element.css "-moz-user-select" "none"
+ ]
+ ((n + 1)
+ |> String.fromInt
+ |> Element.text
+ )
+ , Element.el
+ [ Element.width Element.fill
+ , Element.padding 10
+ , highlighted
+ |> Dict.get (n + 1)
+ |> Maybe.map highlight
+ |> Maybe.withDefault Element.none
+ |> Element.inFront
+ ]
+ (Element.text loc)
+ ]
+ )
+ |> Element.column
+ [ Element.width Element.fill
+ , Font.size 16
+ , Element.scrollbarY
+ ]
+ ]
+
+
+main : Html msg
+main =
+ """module Main exposing (main)
+
+import Browser
+import Dict
+import Element
+import Svg exposing (Svg)
+import Svg.Attributes
+
+
+main =
+ Browser.element
+ { init = init
+ , view = view
+ , update = update
+ , subscriptions = subscriptions
+ }
+
+
+view age =
+ [ segment (age / 5000) { color = "brown", rotation = -90 } ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: none"
+ , Svg.Attributes.viewBox "-500 -500 1000 1000"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+
+rules =
+ Dict.empty
+ |> Dict.insert "brown"
+ [ { color = "brown", rotation = 0 }
+ , { color = "green", rotation = 20 }
+ , { color = "green", rotation = -30 }
+ ]
+ |> Dict.insert "green"
+ [ { color = "red", rotation = -45 }
+ , { color = "red", rotation = -5 }
+ , { color = "red", rotation = 50 }
+ ]
+
+
+segment age { color, rotation } =
+ if age <= 0 then
+ Svg.g [] []
+
+ else
+ Svg.g []
+ [ rules
+ |> Dict.get color
+ |> Maybe.withDefault []
+ |> List.map (segment (age - 1))
+ |> Svg.g
+ [ Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate("
+ , String.fromFloat (age * 10)
+ , ")"
+ ]
+ )
+ ]
+ , dot age color rotation
+ , line age color rotation
+ ]
+
+
+dot age color rotation =
+ Svg.circle
+ [ Svg.Attributes.r (String.fromFloat age)
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate("
+ , String.fromFloat (age * 10)
+ , ")"
+ ]
+ )
+ ]
+ []
+
+
+ line age color rotation =
+ Svg.line
+ [ Svg.Attributes.strokeWidth "1"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x1 (String.fromFloat (age * 10))
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.stroke color
+ , Svg.Attributes.strokeWidth (String.fromFloat age)
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ")"
+ ]
+ )
+ ]
+ []
+"""
+ |> editor defaults
+ [ Highlight 10 16 0 38
+ , Highlight 3 3 0 15
+ , Highlight 19 19 0 10
+ , Highlight 20 20 15 10
+ ]
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]new file mode 100644
index 0000000..784c614
--- /dev/null
+++ b/src/Element/Extra.elm
@@ -0,0 +1,17 @@
+module Element.Extra exposing (css)
+
+import Element exposing (Element)
+import Element.Background as Background
+import Element.Border as Border
+import Element.Font as Font
+import Html exposing (Html)
+import Html.Attributes
+
+
+css : String -> String -> Element.Attribute msg
+css property value =
+ Element.htmlAttribute
+ (Html.Attributes.style
+ property
+ value
+ )index a0c8d8f..3354f14 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -454,7 +454,7 @@ document =
= ]
=
= widgets =
- [ Mark.Custom.code
+ [ Mark.Custom.editor
= , Mark.Custom.terminal
= , Mark.Custom.note
= ]index ddbec4e..a96ce20 100644
--- a/src/Mark/Custom.elm
+++ b/src/Mark/Custom.elm
@@ -1,7 +1,6 @@
=module Mark.Custom exposing
- ( code
- , colors
- , css
+ ( colors
+ , editor
= , emphasize
= , header
= , icon
@@ -19,9 +18,11 @@ module Mark.Custom exposing
=
=import BrowserWindow
=import Dict
+import Editor
=import Element exposing (Element)
=import Element.Background as Background
=import Element.Border as Border
+import Element.Extra as Element
=import Element.Font as Font
=import FeatherIcons exposing (icons)
=import Html exposing (Html)
@@ -54,8 +55,8 @@ paragraph =
= Element.paragraph
= [ Element.paddingXY 0 10
= , Element.spacing 12
- , css "hyphens" "auto"
- , css "orphans" "3"
+ , Element.css "hyphens" "auto"
+ , Element.css "orphans" "3"
= , Font.justify
= ]
= (content model)
@@ -77,105 +78,37 @@ monospace =
= , Font.monospace
= ]
= , Element.scrollbarY
- , css "page-break-inside" "avoid"
+ , Element.css "page-break-inside" "avoid"
= ]
=
=
-type alias File =
- { path : String
- , line : Int
- }
-
-
-code : Mark.Block (a -> Element msg)
-code =
+editor : Mark.Block (a -> Element msg)
+editor =
= let
- render { path, line } contents model =
- Element.column
- [ Border.width 3
- , Border.rounded 5
- , Border.color colors.charcoal
- , css "page-break-inside" "avoid"
- , Font.family
- [ Font.typeface "Source Code Pro"
- , Font.monospace
- ]
- , css "page-break-inside" "avoid"
- ]
- [ Element.row
- [ Element.width Element.fill
- , Background.color colors.charcoal
- , Font.color colors.gray
- ]
- [ FeatherIcons.fileText
- |> FeatherIcons.toHtml []
- |> Element.html
- |> Element.el
- [ Element.height (Element.px 35)
- , Element.padding 8
- ]
- , Element.el
- [ Element.width Element.fill
- , Font.size 16
- , Font.family
- [ Font.typeface "Source Code Pro"
- , Font.monospace
- ]
- ]
- (Element.text path)
- ]
- , contents
- |> String.split "\n"
- |> List.indexedMap
- (\n loc ->
- Element.row []
- [ Element.el
- [ Font.color colors.gray
- , Font.extraLight
- , Element.width (Element.px 40)
- , Element.padding 10
- , Font.alignRight
- , css "user-select" "none"
- , css "-webkit-user-select" "none"
- , css "-ms-user-select" "none"
- , css "-webkit-touch-callout" "none"
- , css "-o-user-select" "none"
- , css "-moz-user-select" "none"
- ]
- (n
- |> (+) line
- |> String.fromInt
- |> Element.text
- )
- , Element.el
- [ Element.width Element.fill
- , Element.padding 10
- ]
- (Element.text loc)
- ]
- )
- |> Element.column
- [ Element.width Element.fill
- , Font.size 16
- , Element.scrollbarY
- ]
- ]
+ render contents highlights model =
+ Editor.editor Editor.defaults highlights contents
+
+ code : Mark.Block String
+ code =
+ Mark.block "Code"
+ identity
+ Mark.multiline
+
+ highlight : Mark.Block Editor.Highlight
+ highlight =
+ Mark.record4 "Highlight"
+ Editor.Highlight
+ (Mark.field "from" Mark.int)
+ (Mark.field "to" Mark.int)
+ (Mark.field "offset" Mark.int)
+ (Mark.field "width" Mark.int)
= in
- -- FIXME: There is a weird bug where 1st line of contents gets rendered indented 4 spaces
- -- Mark.block "Code"
- -- identity
- -- (Mark.startWith
- -- render
- -- (Mark.record2 "File"
- -- File
- -- (Mark.field "path" Mark.string)
- -- (Mark.field "line" Mark.int)
- -- )
- -- Mark.multiline
- -- )
- Mark.block "Code"
- (render (File "src/Main.elm" 1))
- Mark.multiline
+ Mark.block "Editor"
+ identity
+ (Mark.startWith render
+ code
+ (Mark.manyOf [ highlight ])
+ )
=
=
=terminal : Mark.Block (a -> Element msg)
@@ -187,12 +120,12 @@ terminal =
= , Border.rounded 5
= , Border.color colors.charcoal
= , Background.color colors.charcoal
- , css "page-break-inside" "avoid"
+ , Element.css "page-break-inside" "avoid"
= , Font.family
= [ Font.typeface "Source Code Pro"
= , Font.monospace
= ]
- , css "page-break-inside" "avoid"
+ , Element.css "page-break-inside" "avoid"
= ]
= [ Element.row
= [ Element.width Element.fill
@@ -255,7 +188,7 @@ window block =
= [ Element.height (Element.px 400)
= , Element.width Element.fill
= ]
- |> BrowserWindow.window [ css "page-break-inside" "avoid" ]
+ |> BrowserWindow.window [ Element.css "page-break-inside" "avoid" ]
= in
= Mark.block "Window"
= render
@@ -413,7 +346,7 @@ icon =
= )
= |> Element.el
= [ Element.padding 4
- , css "vertical-align" "middle"
+ , Element.css "vertical-align" "middle"
= ]
= )
= |> Mark.inlineString "name"
@@ -492,19 +425,6 @@ definition =
= |> Mark.inlineString "definiens"
=
=
-
--- Helpers
-
-
-css : String -> String -> Element.Attribute msg
-css property value =
- Element.htmlAttribute
- (Html.Attributes.style
- property
- value
- )
-
-
=colors =
= { maroon = Element.rgb 0.7 0 0
= , gray = Element.rgb 0.8 0.8 0.8
Commits: 1
Improve Fana's bio.
Also fixed some grammar
index e5d8848..9f75900 100644
--- a/content/day-4.txt
+++ b/content/day-4.txt
@@ -94,12 +94,12 @@ Type this code at the bottom of the file and then replace main with the followin
=
=Reload the browser. There should still be no visible difference in the behavior of the program, but our source code is getting more readable. That's good.
=
-The big difference between our program and the one we are trying to build is that our have only one level of segments, whereas in the example segments grow from the tip of other segments. You can think of it as segments having child segments: the green segment has two child brown segments. Each brown segment has three red child segments
+The big difference between our program and the one we are trying to build is that ours have only one level of segments, whereas in the example segments grow from the tip of other segments. You can think of it as segments having child segments: the green segment has two child brown segments. Each brown segment has three red child segments
=
=| Note
= TODO: Make sure the description matches the example
=
-It's similar to how the way an SVG group has child elements. But group itself (together with it's children) is an element, so we can put a group within a group. Let's do that! Change the definition of {Code|segment} function as follows:
+It's similar to how the way an SVG group has child elements. But group itself (together with its children) is an element, so we can put a group within a group. Let's do that! Change the definition of {Code|segment} function as follows:
=
=| Code
= segment color rotation =
@@ -764,4 +764,3 @@ Let's analyze it line by line:
= #. Finally we pass the list to the {Code|Svg.g} function, producing a single group (again, possibly empty).
=
=It's a lot to absorb, so take your time to think about it.
-index f181d18..c2939da 100644
--- a/content/index.txt
+++ b/content/index.txt
@@ -29,4 +29,4 @@ I love the creativity of the software development and hope to share that passion
=
=*Sam* is a co-author of the workshop. He is an Elm developer at {Link|itravel|url=https://www.itravel.de/} in Cologne, Germany.
=
-*Fana* is a coordinator of our project. She keeps us on the right track. Also she is taking care of our media presence.
+*Fana* is a junior software developer (an ex marine biologist) and coordinator of our project. She keeps us on the right track. Also she is taking care of our media presence.
Commits: 13
Make the Dot example work, add fill property to Container
Day 1 is updated to work with Dots. Day 2 needs to get updated still.
index d1fc7a3..680cd91 100644
--- a/content/day-1.txt
+++ b/content/day-1.txt
@@ -111,10 +111,18 @@ This was a warmup. Let's try something more challenging.
=We want to display a dot at the center of the screen, like this:
=
=| Window
- | DotWithViewBox
- background=none
- fill=skyblue
- viewBox=-300 -300 600 600
+ | Dots
+ | Container
+ background = none
+ viewBox = -300 -300 600 600
+ fill = True
+
+
+ | Dot
+ cx = 0
+ cy = 0
+ color = skyblue
+ radius = 10
=
=Below is the complete code to draw a dot like this, but don't type it in your editor yet! We are going to recreate it together, step by step.
=
@@ -193,9 +201,17 @@ Start the reactor again by entering:
=Reload the browser. You should see something like this:
=
=| Window
- | Dot
- background=none
- fill=black
+ | Dots
+ | Container
+ background = none
+ fill = False
+ viewBox = auto
+
+ | Dot
+ radius = 10
+ cx = 0
+ cy = 0
+ color = black
=
=At this point we have a dot on the screen - it's at the top left corner of the viewport (the area of the browser window where the content is displayed). We can see only a quarter of it - the rest is outside of the viewport.
=
@@ -220,9 +236,17 @@ First we have to realize that the dot is inside an {Code|svg} element that itsel
=Reloading the browser should reveal something like this:
=
=| Window
- | Dot
- background=pink
- fill=black
+ | Dots
+ | Container
+ background = pink
+ fill = False
+ viewBox = auto
+
+ | Dot
+ radius = 10
+ cx = 0
+ cy = 0
+ color = black
=
=Since the dot is inside the {Code|svg} element, and the {Code|svg} element doesn't cover the center of the screen, it's impossible to place the dot at the center. Before anything else, let's make the {Code|svg} fill entire {Definition|term=viewport|definiens=the area of the browser window where the content is displayed}.
=
@@ -271,11 +295,17 @@ As before, press {Key|enter} to follow the plan. Now let's use the package. Chan
=Reload the browser. The SVG space now fills the entire viewport. Even the small white margin is gone!
=
=| Window
- | DotInElement
- background=pink
- fill=black
- cx=0
- cy=0
+ | Dots
+ | Container
+ background = pink
+ fill = True
+ viewBox = auto
+
+ | Dot
+ radius = 10
+ cx = 0
+ cy = 0
+ color = black
=
=It should now be possible to place our dot in the center of the screen. Currently the dot is in the top left corner of the viewport, but at least the parent {Code|svg} element is covering it, so the center of the viewport is somewher within the {Code|svg} element.
=
@@ -334,11 +364,17 @@ To change the position of the dot, we can use the {Code|cx} and {Code|cy} attrib
= ]
=
=| Window
- | DotInElement
- background=pink
- fill=black
- cx=100
- cy=50
+ | Dots
+ | Container
+ background = none
+ fill = True
+ viewBox = auto
+
+ | Dot
+ radius = 10
+ cx = 100
+ cy = 50
+ color = black
=
=| Note
= The {Code|cx} stands for /center x/ and {Code|cy} for /center y/, where center refers to the circle. We will discuss what the center of the circle is in more exact terms on day 2, but you probably get the concept.
@@ -415,10 +451,17 @@ In the code we set the viewbox as an attribute of the {Code|svg} element, like t
=There are only two changes here. First we reset the {Code|cx} and {Code|cy} attributes of the {Code|circle} back to {Code|"0"} (on lines 11 and 12). We want the dot to be back at origin. Then we set up the position of the viewbox on line 20. Try experimenting with different values. If you set them right and refresh the broswer the dot should be in the center of the screen.
=
=| Window
- | DotWithViewBox
- background=pink
- fill=black
- viewBox=-300 -300 600 600
+ | Dots
+ | Container
+ background = pink
+ fill = True
+ viewBox = -300 -300 600 600
+
+ | Dot
+ radius = 10
+ cx = 0
+ cy = 0
+ color = black
=
=You can also remove line 19 to remove the pink background, or set it to some other color.
=
@@ -458,10 +501,17 @@ Speaking of colors, now that we've centered our dot, it's time to give it a colo
=The only change is on line 13. Reload the browser and see this:
=
=| Window
- | DotWithViewBox
- background=none
- fill=skyblue
- viewBox=-300 -300 600 600
+ | Dots
+ | Container
+ background = none
+ fill = True
+ viewBox = -300 -300 600 600
+
+ | Dot
+ radius = 10
+ cx = 0
+ cy = 0
+ color = skyblue
=
=| Emphasize
= Wow!index 4ebd806..e4d98a5 100644
--- a/src/Examples.elm
+++ b/src/Examples.elm
@@ -3,12 +3,9 @@ module Examples exposing (Model, Msg(..), init, subscriptions, update)
=import Examples.CartesianCoordinates
=import Examples.Circle
=import Examples.Counter
-import Examples.Dot as Dot
-import Examples.DotInElement as DotInElement
-import Examples.DotWithViewBox as DotWithViewBox
+import Examples.Dots
=import Examples.Gradient
=import Examples.Line
-import Examples.MultipleDots as MultipleDots
=import Examples.NestedTransformations
=import Examples.PolarCoordinates
=import Examples.RosetteTypedTransformationsdeleted file mode 100644
index 5581546..0000000
--- a/src/Examples/Dot.elm
+++ /dev/null
@@ -1,32 +0,0 @@
-module Examples.Dot exposing (Config, defaults, ui)
-
-import Html exposing (Html)
-import Svg
-import Svg.Attributes
-
-
-type alias Config =
- { background : String
- , fill : String
- }
-
-
-defaults : Config
-defaults =
- Config "none" "black"
-
-
-main : Html msg
-main =
- ui defaults
-
-
-ui : Config -> Html msg
-ui { background, fill } =
- Svg.svg [ Svg.Attributes.style <| "background: " ++ background ]
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.fill fill
- ]
- []
- ]deleted file mode 100644
index ee127b3..0000000
--- a/src/Examples/DotInElement.elm
+++ /dev/null
@@ -1,44 +0,0 @@
-module Examples.DotInElement exposing (Config, main, ui)
-
-import Element exposing (Element)
-import Svg
-import Svg.Attributes
-
-
-type alias Config =
- { background : String
- , fill : String
- , cx : String
- , cy : String
- }
-
-
-defaults : Config
-defaults =
- Config "pink" "black" "0" "0"
-
-
-main =
- Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
- ]
- (ui defaults)
-
-
-ui : Config -> Element msg
-ui { background, fill, cx, cy } =
- Svg.svg
- [ Svg.Attributes.height "100%"
- , Svg.Attributes.width "100%"
- , Svg.Attributes.style <| "background: " ++ background
- ]
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.fill fill
- , Svg.Attributes.cx cx
- , Svg.Attributes.cy cy
- ]
- []
- ]
- |> Element.htmldeleted file mode 100644
index 6f91dc6..0000000
--- a/src/Examples/DotWithViewBox.elm
+++ /dev/null
@@ -1,57 +0,0 @@
-module Examples.DotWithViewBox exposing (Config, main, ui)
-
-import Element
-import Svg
-import Svg.Attributes
-
-
-{-| This program demonstrates how to center an element within an SVG viewport. The idea is to make sure that (0, 0) point (so called origin) should be in the middle of the ViewBox. We can achive that, by making the top-left corner of the ViewBox same distance from the (0, 0) as the bottom right.
-
-The viewBox property takes a string consisting of four numbers:
-
- - position of the top edge
- - position of the left edge
- - width
- - and height
-
-If we set the width to 200 and then set the left edge to -100, that will place the right edge at +100. Both edges are 100 away from the 0!
-
-The same way you can control the bottom edge by tweaking the position of the top edge and the height.
-
-In fact it doesnt matter how wide and high the viewBox is. As long as the top will be -1/2 of the width and left will be -1/2 of the width.
-
-That's the trick to position an object (like a dot) exactly in the center of the SVG viewport
-
--}
-type alias Config =
- { background : String
- , fill : String
- , viewBox : String
- }
-
-
-defaults : Config
-defaults =
- Config "white" "black" "-500 -500 1000 1000"
-
-
-main =
- Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
- ]
- (ui defaults)
-
-
-ui { background, fill, viewBox } =
- Svg.svg
- [ Svg.Attributes.viewBox viewBox
- , Svg.Attributes.style <| "background: " ++ background
- ]
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.fill fill
- ]
- []
- ]
- |> Element.htmlindex 5944489..4285cb8 100644
--- a/src/Examples/Dots.elm
+++ b/src/Examples/Dots.elm
@@ -10,6 +10,7 @@ import Circle2d
=import Element exposing (Element)
=import Geometry.Svg
=import Html exposing (Html)
+import Html.Attributes
=import Point2d exposing (Point2d)
=import Svg exposing (Attribute, Svg)
=import Svg.Attributes
@@ -24,6 +25,7 @@ type alias Config =
=type alias Container =
= { background : String
= , viewBox : String
+ , fill : Bool
= }
=
=
@@ -40,6 +42,7 @@ defaults =
= { container =
= { background = "none"
= , viewBox = "-300 -300 600 600"
+ , fill = True
= }
= , dots =
= [ { cx = 0
@@ -69,13 +72,35 @@ ui { container, dots } =
= |> Point2d.fromCoordinates
= |> Circle2d.withRadius radius
= |> Geometry.Svg.circle2d [ Svg.Attributes.fill color ]
+
+ width =
+ if container.fill then
+ "100%"
+
+ else
+ "300"
+
+ height =
+ if container.fill then
+ "100%"
+
+ else
+ "150"
+
+ padding =
+ if container.fill then
+ "0"
+
+ else
+ "5px"
= in
= dots
= |> List.map dot
= |> Svg.svg
= [ Svg.Attributes.viewBox container.viewBox
- , Svg.Attributes.style <| "background: " ++ container.background
- , Svg.Attributes.width "100%"
- , Svg.Attributes.height "100%"
+ , Html.Attributes.style "background" container.background
+ , Svg.Attributes.width width
+ , Svg.Attributes.height height
+ , Html.Attributes.style "margin" padding
= ]
= |> Element.htmldeleted file mode 100644
index b0625da..0000000
--- a/src/Examples/MultipleDots.elm
+++ /dev/null
@@ -1,41 +0,0 @@
-module Examples.MultipleDots exposing (Config, main, ui)
-
-import Element
-import Svg exposing (Attribute)
-import Svg.Attributes
-
-
-type alias Config msg =
- { background : String
- , viewBox : String
- , dotsAttributes : List (List (Attribute msg))
- }
-
-
-defaults : Config msg
-defaults =
- Config "white" "-500 -500 1000 1000" [ [ Svg.Attributes.r "10" ] ]
-
-
-main =
- Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
- ]
- (ui defaults)
-
-
-ui { background, viewBox, dotsAttributes } =
- Svg.svg
- [ Svg.Attributes.viewBox viewBox
- , Svg.Attributes.style <| "background: " ++ background
- ]
- (dotsAttributes
- |> List.map
- (\attributes ->
- Svg.circle
- attributes
- []
- )
- )
- |> Element.htmlindex eae8b16..b2da4d8 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -22,7 +22,6 @@ import Examples
=import Examples.CartesianCoordinates
=import Examples.Circle
=import Examples.Counter
-import Examples.DotInElement
=import Examples.Dots
=import Examples.Gradient
=import Examples.Line
@@ -459,7 +458,6 @@ document =
=
= examples =
= [ counter
- , dotInElement
= , dots
= , circle
= , line
@@ -490,20 +488,6 @@ document =
= in
= Mark.stub "Counter" render
=
- dotInElement : Mark.Block (Examples.Model -> Element Msg)
- dotInElement =
- let
- render config model =
- Examples.DotInElement.ui config
- in
- Mark.record4 "DotInElement"
- Examples.DotInElement.Config
- (Mark.field "background" Mark.string)
- (Mark.field "fill" Mark.string)
- (Mark.field "cx" Mark.string)
- (Mark.field "cy" Mark.string)
- |> Mark.map render
-
= dots : Mark.Block (Examples.Model -> Element Msg)
= dots =
= let
@@ -516,10 +500,11 @@ document =
=
= container : Mark.Block Examples.Dots.Container
= container =
- Mark.record2 "Container"
+ Mark.record3 "Container"
= Examples.Dots.Container
= (Mark.field "background" Mark.string)
= (Mark.field "viewBox" Mark.string)
+ (Mark.field "fill" Mark.bool)
=
= dot : Mark.Block Examples.Dots.Dot
= dot =
@@ -530,48 +515,13 @@ document =
= (Mark.field "radius" Mark.float)
= (Mark.field "color" Mark.string)
= in
- {- FIXME: This should really be:
-
- Mark.block "Dots"
- render
- (Mark.startWith
- Examples.Dots.Config
- container
- (Mark.manyOf [ dot ])
- )
-
- and then markup:
-
- | Dots
- | Container
- background = brown
- viewBox = -200 -200 400 400
-
- | Dot
- cx = 10
- cy = 20
- color = blue
- radius = 33
-
- | Dot
- ...
-
- but it produces parsing errors:
-
- End at 36:1
- Expecting newline at 36:1
- Block start at 36:1
-
- TODO: Create a minimal reproducible example on Ellie and report issue at github.com/mdgriffith/elm-markup
- -}
= Mark.block "Dots"
- (Examples.Dots.Config
- { background = "none"
- , viewBox = "-300 -300 600 600"
- }
- >> render
+ render
+ (Mark.startWith
+ Examples.Dots.Config
+ container
+ (Mark.manyOf [ dot ])
= )
- (Mark.manyOf [ dot ])
=
= circle : Mark.Block (Examples.Model -> Element Msg)
= circle =Merge branch 'content' into wip-dots-example
Add a little spacing between lines in the Terminal block
It's easier to read that way.
index 8430329..ddbec4e 100644
--- a/src/Mark/Custom.elm
+++ b/src/Mark/Custom.elm
@@ -235,6 +235,7 @@ terminal =
= ]
= , Font.color colors.gray
= , Element.scrollbarY
+ , Element.spacing 6
= ]
= ]
= inWrite the section about rules based tree on day 4
index 3dbc52a..e5d8848 100644
--- a/content/day-4.txt
+++ b/content/day-4.txt
@@ -365,7 +365,6 @@ A segment within a segment! Consider that every segment can have child segments.
= Time to play 🌳🌲🌴🎄
=
=| Emphasize
-
= Try building your own tree this way. Play with colors and sizes.
=
=| Note
@@ -397,3 +396,372 @@ The tree following these rules will look like this:
=
=| Note
= TODO: Example
+
+To represent this in code we will need to learn few new concepts: *dictionary*, *record*, *type alias* and *mapping over list*.
+
+Let's start with a record. Currently our {Code|segment} function takes three arguments first two are {Code|color : String} and {Code|rotation : Float}. The last one is a list of child segments. Instead we can merge the first two into one argument of type {Code|\{ color : String, rotation : Float \}}. It will look like this:
+
+| Code
+ module Main exposing (main)
+
+ import Element
+ import Svg exposing (Svg)
+ import Svg.Attributes
+
+
+ main =
+ [ segment { color = "skyblue", rotation = 0 }
+ [ segment { color = "red", rotation = 15 } []
+ , segment { color = "blue", rotation = -15 }
+ [ segment { color = "yellow", rotation = 45 } []
+ , segment { color = "pink", rotation = -20 } []
+ , segment { color = "green", rotation = -90 } []
+ ]
+ ]
+ , segment { color = "orange", rotation = 72 } []
+ , segment { color = "red", rotation = 144 } []
+ , segment { color = "lime", rotation = 216 } []
+ , segment { color = "maroon", rotation = 288 } []
+ ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: none"
+ , Svg.Attributes.viewBox "-500 -500 1000 1000"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+
+ segment { color, rotation } children =
+ Svg.g []
+ [ Svg.g
+ [ Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ children
+ , dot color rotation
+ , line color rotation
+ ]
+
+
+ dot color rotation =
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ []
+
+
+ line color rotation =
+ Svg.line
+ [ Svg.Attributes.strokeWidth "1"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x1 "80"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.stroke color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ")"
+ ]
+ )
+ ]
+ []
+
+Not much changed. Instead of passing two separate values for {Code|color} and {Code|rotation} we pass one value with two named fields. That's a record!
+
+Why did I do it? That way I can store complete information about a segment in one value. I need this for the *dictionary* of rules.
+
+Dictionary let's us associate one value (let's say color) with another value (let's say a list of segments). You can have as many entries in your dictionary as you want. That's perfect for storing our rules. We can start with an empty dictionary and then add our two rules: one for brown and one for green segments. We do it like this:
+
+| Code
+ module Main exposing (main)
+
+ import Dict
+ import Element
+ import Svg exposing (Svg)
+ import Svg.Attributes
+
+
+ main =
+ [ segment { color = "skyblue", rotation = 0 }
+ [ segment { color = "red", rotation = 15 } []
+ , segment { color = "blue", rotation = -15 }
+ [ segment { color = "yellow", rotation = 45 } []
+ , segment { color = "pink", rotation = -20 } []
+ , segment { color = "green", rotation = -90 } []
+ ]
+ ]
+ , segment { color = "orange", rotation = 72 } []
+ , segment { color = "red", rotation = 144 } []
+ , segment { color = "lime", rotation = 216 } []
+ , segment { color = "maroon", rotation = 288 } []
+ ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: none"
+ , Svg.Attributes.viewBox "-500 -500 1000 1000"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+
+ rules =
+ Dict.empty
+ |> Dict.insert "brown"
+ [ { color = "green", rotation = 0 }
+ , { color = "green", rotation = 20 }
+ , { color = "green", rotation = -30 }
+ ]
+ |> Dict.insert "green"
+ [ { color = "red", rotation = -45 }
+ , { color = "red", rotation = -5 }
+ , { color = "red", rotation = 50 }
+ ]
+
+
+ segment { color, rotation } children =
+ Svg.g []
+ [ Svg.g
+ [ Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ children
+ , dot color rotation
+ , line color rotation
+ ]
+
+
+ dot color rotation =
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ []
+
+
+ line color rotation =
+ Svg.line
+ [ Svg.Attributes.strokeWidth "1"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x1 "80"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.stroke color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ")"
+ ]
+ )
+ ]
+ []
+
+We have added an import statement for {Code|Dict} module and created a dictionary with two entries: one for {Code|"brown"} and one for {Code|"green"}. The values associated with these keys are a list of segment records. The meaning of this is following:
+
+| List
+ - Whenever a brown segment is created, also create three green children
+ - Whenever a green segment is created, also create three red children
+
+There is no rule for red segments, so they will have no children. Now we will apply the rules. We have to change the definition of {Code|segment} function. It will no longer take the second argument - children will be added according to rules, not arbitrarily.
+
+Here it is:
+
+| Code
+ segment { color, rotation } =
+ Svg.g []
+ [ rules
+ |> Dict.get color
+ |> Maybe.withDefault []
+ |> List.map segment
+ |> Svg.g
+ [ Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ , dot color rotation
+ , line color rotation
+ ]
+
+We also have to change the definition of {Code|main}. Segment no longer takes two arguments, and it's children are calculated according to rules, so we can just add one brown segment and rotate it upward (-90 degree)
+
+| Code
+ main =
+ [ segment { color = "brown", rotation = -90 } ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: none"
+ , Svg.Attributes.viewBox "-500 -500 1000 1000"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+The result should be exactly like we wanted it to be:
+
+| Monospace
+ TODO: Implement tree example
+
+ | Tree
+ | Axiom
+ color = brown
+ rotation = -90
+
+ | Rule
+ | Parent
+ color = brown
+
+ | Child
+ color = "green"
+ rotation = 0
+
+ | Child
+ color = "green"
+ rotation = 20
+
+ | Child
+ color = "green"
+ rotation = -30
+
+ | Rule
+ | Parent
+ color = green
+
+ | Child
+ color = "red"
+ rotation = -45
+
+ | Child
+ color = "red"
+ rotation = -5
+
+ | Child
+ color = "red"
+ rotation = 50
+
+Nice, but what's going on here:
+
+| Code
+ segment { color, rotation } =
+ Svg.g []
+ [ rules
+ |> Dict.get color
+ |> Maybe.withDefault []
+ |> List.map segment
+ |> Svg.g
+ [ Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ , dot color rotation
+ , line color rotation
+ ]
+
+There are three new things here:
+
+| List
+ #. {Code|Dict.get}
+
+ #. {Code|Maybe.withDefault}
+
+ #. {Code|List.map}
+
+The first one is pretty simple. Previously we have been inserting entries into the dictionary. Each entry has a key (color of the segment) and value (list of child segments). Now we are getting these values back.
+
+But what if the entry for a given key is not there? For example we have not inserted the entry for the {Code|"red"} key? Well, then we get {Code|Nothing}. But our {Code|segment} function cannot work with nothing - it has to get a record with color and rotation. So when we have nothing, we can't call the {Code|segment} function. To understand this, consider the following.
+
+If there is an entry, we will get a list of records. We want a list of segments. Each of the records can be passed to the {Code|segment} function to produce one segment. That's where {Code|List.map} comes in. Given a list and a function it will call the function with each of the elements in the list and give back the list of produced values. Let's see a simpler example in the REPL:
+
+| Terminal
+ > fun something = something ++ " is fun!"
+ <function> : String -> String
+ > fun "Learning Elm"
+ "Learning Elm is fun!" : String
+ > fun "Tree"
+ "Tree is fun!" : String
+ > fun "Software"
+ "Software is fun!" : String
+ > things = [ "Elm", "Software", "Tree" ]
+ ["Elm","Software","Tree"] : List String
+ > List.map fun things
+ ["Elm is fun!","Software is fun!","Tree is fun!"] : List String
+ >
+
+Take some time to grasp it and then compare with our code.
+
+| Monospace
+ rules
+ |> Dict.get color
+ |> Maybe.withDefault []
+ |> List.map segment
+ |> Svg.g
+ [ Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+
+Let's analyze it line by line:
+
+| List
+ #. First line evaluates to a dictionary of rules.
+ #. Second takes that dictionary and gets a list of records for a given color (it maybe nothing if there is no entry, for example in case of {Code|"red"}).
+ #. Third gives an empty list if the result of second line was nothing.
+ #. Fourth maps this list of records with {Code|segment} function, producing a list of segments (the result can be an empty list if the input was empty)
+ #. Finally we pass the list to the {Code|Svg.g} function, producing a single group (again, possibly empty).
+
+It's a lot to absorb, so take your time to think about it.
+Update day-2 to use new Dots block.
index 5c27c5f..d0a4ad1 100644
--- a/content/day-2.txt
+++ b/content/day-2.txt
@@ -26,34 +26,11 @@ Previously we have created a program that displays one dot in the center of the
= radius = 80
= scatter = False
=
-
-| Window
- | Dots
- | Dot
- cx = 0
- cy = 0
- color = skyblue
- radius = 10
-
- | Dot
- cx = -50
- cy = 0
- color = pink
- radius = 10
-
- | Dot
- cx = 50
- cy = 0
- color = lime
- radius = 10
-
=| Header
= Multiple Dots
=
-| Note
- See the FIXME comment in {Code|Main.elm} on {Code|wip-dots-example} branch.
=
-First obvious difference is that now we have multiple dots. The only dot we have is created using {Code|Svg.circle} function on line 9 - 15, this code block:
+First obvious difference is that now we have multiple dots. The only dot we have created using {Code|Svg.circle} function on line 9 - 15, this code block:
=
=| Monospace
= Svg.circle
@@ -103,20 +80,24 @@ If you look closely in your source code, the block above is surrounded by {Code|
=
=Now the list spans from lines 9 to 23 and contains two items: skyblue and orange dots. The result should be:
=
-| Monospace
- | Window
- | Dots
- | Dot
- cx = 0
- cy = 0
- color = skyblue
- radius = 10
-
- | Dot
- cx = 0
- cy = 0
- color = orange
- radius = 10
+| Window
+ | Dots
+ | Container
+ background = none
+ fill = True
+ viewBox = -300 -300 600 600
+
+ | Dot
+ radius = 10
+ cx = 0
+ cy = 0
+ color = skyblue
+
+ | Dot
+ radius = 10
+ cx = 0
+ cy = 0
+ color = orange
=
=Oh oh! The list contains two dots, but we can only see the orange one on the screen. Where is the blue one? It's behind the orange. Recall how the cartesian coordinates work: if {Code|x} and {Code|y} of the center are the same, then the dots are in the same place. If we want to see both dots, let's move one of them to a different place, like this:
=
@@ -157,20 +138,24 @@ Oh oh! The list contains two dots, but we can only see the orange one on the scr
=
=Now you should see this:
=
-| Monospace
- | Window
- | Dots
- | Dot
- cx = 0
- cy = 0
- color = skyblue
- radius = 10
-
- | Dot
- cx = 50
- cy = 0
- color = orange
- radius = 10
+| Window
+ | Dots
+ | Container
+ background = none
+ fill = True
+ viewBox = -300 -300 600 600
+
+ | Dot
+ radius = 10
+ cx = 0
+ cy = 0
+ color = skyblue
+
+ | Dot
+ radius = 10
+ cx = 50
+ cy = 0
+ color = orange
=
=We can add more dots, by simply multiplying the code blocks inside the list. Each block must be separated from others with a {Code|,} (comma). Each time the {Code|cx} and {Code|cy} coordinates need to be different. We can also give dots different colors. Make five of them.
=
@@ -238,47 +223,41 @@ The {Code|r} attribute stands for radius. Then it was basically dictating the si
= radius = 80
=
=| Row
- | Monospace
- TODO: Dots example
- | Dots
- | Container
- viewBox = -100 -100 200 200
- background = none
- fill = True
-
- | Dot
- radius = 40
- cx = 0
- cy = 0
- color = skyblue
+ | Dots
+ | Container
+ background = none
+ fill = True
+ viewBox = -100 -100 200 200
=
- | Monospace
- TODO: Dots example
- | Dots
- | Container
- viewBox = -100 -100 200 200
- background = none
- fill = True
-
- | Dot
- radius = 60
- cx = 0
- cy = 0
- color = skyblue
+ | Dot
+ radius = 40
+ cx = 0
+ cy = 0
+ color = skyblue
=
- | Monospace
- TODO: Dots example
- | Dots
- | Container
- viewBox = -100 -100 200 200
- background = none
- fill = True
-
- | Dot
- radius = 80
- cx = 0
- cy = 0
- color = skyblue
+ | Dots
+ | Container
+ background = none
+ fill = True
+ viewBox = -100 -100 200 200
+
+ | Dot
+ radius = 60
+ cx = 0
+ cy = 0
+ color = skyblue
+
+ | Dots
+ | Container
+ background = none
+ fill = True
+ viewBox = -100 -100 200 200
+
+ | Dot
+ radius = 80
+ cx = 0
+ cy = 0
+ color = skyblue
=
=This time the radius will affect the size of an imaginary circle on which the dots are laying.
=
@@ -387,8 +366,6 @@ In physical world we often use a tool called protractor to measure angles. It ty
= radius = 400
= strokeWidth = 2
=
-| Note
- TODO: Modify the image to make marks 0 - 360. We can convert it to Elm code (using https:////level.app//svg-to-elm) and then reuse it in our examples.
=
=Now if we make the turns of the ruler equal between placing each dot, then the dots will be placed evenly. Equal turns mean that the number of degrees covered by each turn will be the same, including the turn you would need to make to go from the last dot back to the first. But how many degrees shall it be?
=
@@ -558,7 +535,7 @@ For that we need to add another transformation before the move. First we will ro
= []
= ]
=
-This will rotate the element by 72 degrees. On its own it wouldn't make any difference, because the dot is round - it doesn't matter how you rotate it, it always looks the same. But It also rotates its internal coordinates system. So the translation will move it in a different direction then the first dot.
+This will rotate the element by 72 degrees. On its own it wouldn't make any difference, because the dot is round - it doesn't matter how you rotate it, it always looks the same. But It also rotates it's internal coordinates system. So the translation will move it in a different direction then the first dot.
=
=Now it's time to add the third, red dot. Duplicate the second one, change fill to `"red"` and put `144` for rotate function, like this:
=Merge remote-tracking branch 'origin/wip-dots-example' into content
Write section about halting on day 4
index e5d8848..9617799 100644
--- a/content/day-4.txt
+++ b/content/day-4.txt
@@ -652,6 +652,8 @@ The result should be exactly like we wanted it to be:
= | Axiom
= color = brown
= rotation = -90
+ age = -1
+ growing = False
=
= | Rule
= | Parent
@@ -765,3 +767,176 @@ Let's analyze it line by line:
=
=It's a lot to absorb, so take your time to think about it.
=
+| Header
+ Towards Infinity
+
+Our tree is very cool but rather simple. Wouldn't it be nice if it could grow bigger and bigger? What if we could say that a brown segment produces two green (left and right) and a brown (straight)? Then the brown child would have two green and a brown too. But brown gives more brown /ad infinitum/ (pardon my latin).
+
+You can try that, but don't expect to see anything interesting in the browser. Your computer will trying very hard to draw a tree for you, but after going through hundreds of thousands of segments' generations it will give up, perhaps saying "too much recursion". According to the rules you gave there is no stopping to this tree.
+
+| Note
+ A funny fact about computers is that they generally can't tell if the task you give them can be finished or not. It's called {Link|the halting problem|url=https://en.wikipedia.org/wiki/Halting_problem}. Best they can do is give up after certain time. That's what happens here.
+
+So that won't work. But it would be nice to have arbitrarily complex trees with repeating patterns - that's how real trees grow. Since our problem is not ever stopping, maybe we can tell the program to simply stop after certain number of generations.
+
+We can do it like this. When we create our first segment (the brown one), we will pass it a second argument (let's call it {Code|age}). It will be an integer. The age of a parent will be decremented and passed to all it's children (so every child is younger than it's parent by one generation). Eventually the program will reach segments with {Code|age} being {Code|0}. This segment will have no children, thus stopping the whole process. Here is how we can implement it:
+
+| Code
+ module Main exposing (main)
+
+ import Dict
+ import Element
+ import Svg exposing (Svg)
+ import Svg.Attributes
+
+
+ main =
+ [ segment 4 { color = "brown", rotation = -90 } ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: none"
+ , Svg.Attributes.viewBox "-500 -500 1000 1000"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+
+ rules =
+ Dict.empty
+ |> Dict.insert "brown"
+ [ { color = "brown", rotation = 0 }
+ , { color = "green", rotation = 20 }
+ , { color = "green", rotation = -30 }
+ ]
+ |> Dict.insert "green"
+ [ { color = "red", rotation = -45 }
+ , { color = "red", rotation = -5 }
+ , { color = "red", rotation = 50 }
+ ]
+
+
+ segment age { color, rotation } =
+ if age == 0 then
+ Svg.g [] []
+
+ else
+ Svg.g []
+ [ rules
+ |> Dict.get color
+ |> Maybe.withDefault []
+ |> List.map (segment (age - 1))
+ |> Svg.g
+ [ Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ , dot color rotation
+ , line color rotation
+ ]
+
+
+ dot color rotation =
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ []
+
+
+ line color rotation =
+ Svg.line
+ [ Svg.Attributes.strokeWidth "1"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x1 "80"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.stroke color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ")"
+ ]
+ )
+ ]
+ []
+
+On line 10 we pass the age to the {Code|segment} function as the first argument. The record is a second parameter now. The reason is that it makes mapping on line 47 easier. Age is the same for all the children (age of the parent minus 1).
+
+On line 39 we see something new. It's the {Code|if ... then ... else ...} expression. You can probably guess how it works: first we give it a condition (in this case {Code|age == 0} - notice the double {Code|=}). That's our halting condition.
+
+In the {Code|then} block we put the value to produce if the condition is met (here an empty SVG group). This will effectively stop the growth of the tree - no more children from this segment.
+
+The {Code|else} block contains the value to be produced when the condition is not met. In our case it means that the segment is old enough to have children.
+
+The {Code|else} value is almost the same as the whole {Code|segment} function before the change. The only difference is that on line 47 we pass the {Code|age - 1} to the {Code|segment} function used in {Code|List.map}.
+
+The result should be like this:
+
+| Monospace
+ TODO: Implement tree example
+
+ | Tree
+ | Axiom
+ color = brown
+ rotation = -90
+ age = 4
+ growing = False
+
+ | Rule
+ | Parent
+ color = brown
+
+ | Child
+ color = "green"
+ rotation = 0
+
+ | Child
+ color = "green"
+ rotation = 20
+
+ | Child
+ color = "green"
+ rotation = -30
+
+ | Rule
+ | Parent
+ color = green
+
+ | Child
+ color = "red"
+ rotation = -45
+
+ | Child
+ color = "red"
+ rotation = -5
+
+ | Child
+ color = "red"
+ rotation = 50
+
+| Emphasize
+ Go ahead and play with the rules and age.
+
+| Emphasize
+ 😺🎾
+
+| Emphasize
+ It's safe - the tree will always stop growing after a given number of generations.Write section about segments growing on day 4
index 9617799..f731a33 100644
--- a/content/day-4.txt
+++ b/content/day-4.txt
@@ -940,3 +940,205 @@ The result should be like this:
=
=| Emphasize
= It's safe - the tree will always stop growing after a given number of generations.
+
+| Header
+ Every Branch was Once a Twig
+
+I hope you had fun playing with the tree. You can make it really interesting and complicated now. But it doesn't look very much like a real, organic tree. One problem is that with a real tree old branches are thick and long, while young are thin and short.
+
+Since the size depends on the age, and the age is given for every segment, we can fix that relatively easy. Let's start by making the dots smaller or bigger depending on the age of the segment, like this:
+
+| Code
+ segment age { color, rotation } =
+ if age == 0 then
+ Svg.g [] []
+
+ else
+ Svg.g []
+ [ rules
+ |> Dict.get color
+ |> Maybe.withDefault []
+ |> List.map (segment (age - 1))
+ |> Svg.g
+ [ Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ , dot age color rotation
+ , line color rotation
+ ]
+
+
+ dot age color rotation =
+ Svg.circle
+ [ Svg.Attributes.r (String.fromFloat age)
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ []
+
+| Monospace
+ TODO: Describe the change to code
+
+Next let's pass the age to the {Code|line} function and use it for both the thickness {Code|strokWidth} and length {Code|x2}.
+
+If you reload now, the tree will look as if it exploded. It's funny. The reason is that we did not adjust the translation of the child groups nor the dots. It's always 80, even though the length of the lines is variable - see lines {Code|TODO: line of group translation} and {Code|TODO: line of dot translation}. Let's change it like that:
+
+| Code
+ module Main exposing (main)
+
+ import Dict
+ import Element
+ import Svg exposing (Svg)
+ import Svg.Attributes
+
+
+ main =
+ [ segment 4 { color = "brown", rotation = -90 } ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: none"
+ , Svg.Attributes.viewBox "-500 -500 1000 1000"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+
+ rules =
+ Dict.empty
+ |> Dict.insert "brown"
+ [ { color = "brown", rotation = 0 }
+ , { color = "green", rotation = 20 }
+ , { color = "green", rotation = -30 }
+ ]
+ |> Dict.insert "green"
+ [ { color = "red", rotation = -45 }
+ , { color = "red", rotation = -5 }
+ , { color = "red", rotation = 50 }
+ ]
+
+
+ segment age { color, rotation } =
+ if age == 0 then
+ Svg.g [] []
+
+ else
+ Svg.g []
+ [ rules
+ |> Dict.get color
+ |> Maybe.withDefault []
+ |> List.map (segment (age - 1))
+ |> Svg.g
+ [ Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate("
+ , String.fromFloat (age * 10)
+ , ")"
+ ]
+ )
+ ]
+ , dot age color rotation
+ , line age color rotation
+ ]
+
+
+ dot age color rotation =
+ Svg.circle
+ [ Svg.Attributes.r (String.fromFloat age)
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate("
+ , String.fromFloat (age * 10)
+ , ")"
+ ]
+ )
+ ]
+ []
+
+
+ line age color rotation =
+ Svg.line
+ [ Svg.Attributes.strokeWidth "1"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x1 (String.fromFloat (age * 10))
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.stroke color
+ , Svg.Attributes.strokeWidth (String.fromFloat age)
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ")"
+ ]
+ )
+ ]
+ []
+
+Now the tree should look correctly, like this:
+
+| Monospace
+ TODO: Implement tree example
+
+ | Tree
+ | Axiom
+ color = brown
+ rotation = -90
+ age = 4
+ growing = True
+
+ | Rule
+ | Parent
+ color = brown
+
+ | Child
+ color = "green"
+ rotation = 0
+
+ | Child
+ color = "green"
+ rotation = 20
+
+ | Child
+ color = "green"
+ rotation = -30
+
+ | Rule
+ | Parent
+ color = green
+
+ | Child
+ color = "red"
+ rotation = -45
+
+ | Child
+ color = "red"
+ rotation = -5
+
+ | Child
+ color = "red"
+ rotation = 50
+Add congratulations block to day 4
index f731a33..98e45c9 100644
--- a/content/day-4.txt
+++ b/content/day-4.txt
@@ -1142,3 +1142,11 @@ Now the tree should look correctly, like this:
= color = "red"
= rotation = 50
=
+| Emphasize
+ Congratulations!
+
+| Emphasize
+ {Icon|name=award}
+
+| Emphasize
+ You are ready for {Link|the final day|url=/day-4.html}!Fix mistakes on day 5 header
index e54b4b9..b7f1ebe 100644
--- a/content/day-5.txt
+++ b/content/day-5.txt
@@ -1,8 +1,14 @@
=| Title
- Day 3
+ Day 5
=
=| Emphasize
- Let's Make the Tree Grow
+ Let the Tree Grow
+
+
+| Note
+ *We are still working on this content.*
+
+ Stay tuned {Icon|name=radio}
=
=
=| Note
@@ -15,7 +21,4 @@
=| Header
= The Problem
=
-| Note
- *We are still working on this content.*
=
- Stay tuned {Icon|name=radio}Fix bug in halting condition of segment (day 4)
index 98e45c9..1816461 100644
--- a/content/day-4.txt
+++ b/content/day-4.txt
@@ -820,7 +820,7 @@ We can do it like this. When we create our first segment (the brown one), we wil
=
=
= segment age { color, rotation } =
- if age == 0 then
+ if age <= 0 then
= Svg.g [] []
=
= else
@@ -880,7 +880,7 @@ We can do it like this. When we create our first segment (the brown one), we wil
=
=On line 10 we pass the age to the {Code|segment} function as the first argument. The record is a second parameter now. The reason is that it makes mapping on line 47 easier. Age is the same for all the children (age of the parent minus 1).
=
-On line 39 we see something new. It's the {Code|if ... then ... else ...} expression. You can probably guess how it works: first we give it a condition (in this case {Code|age == 0} - notice the double {Code|=}). That's our halting condition.
+On line 39 we see something new. It's the {Code|if ... then ... else ...} expression. You can probably guess how it works: first we give it a condition (in this case {Code|age <= 0} - meaning that age is zero or less). That's our halting condition.
=
=In the {Code|then} block we put the value to produce if the condition is met (here an empty SVG group). This will effectively stop the growth of the tree - no more children from this segment.
=
@@ -950,7 +950,7 @@ Since the size depends on the age, and the age is given for every segment, we ca
=
=| Code
= segment age { color, rotation } =
- if age == 0 then
+ if age <= 0 then
= Svg.g [] []
=
= else
@@ -1035,7 +1035,7 @@ If you reload now, the tree will look as if it exploded. It's funny. The reason
=
=
= segment age { color, rotation } =
- if age == 0 then
+ if age <= 0 then
= Svg.g [] []
=
= elseCorrect link from day 4 to day 5
index 1816461..03acab1 100644
--- a/content/day-4.txt
+++ b/content/day-4.txt
@@ -1149,4 +1149,4 @@ Now the tree should look correctly, like this:
= {Icon|name=award}
=
=| Emphasize
- You are ready for {Link|the final day|url=/day-4.html}!
+ You are ready for {Link|the final day|url=/day-5.html}!Draft day 5
index b7f1ebe..ce02674 100644
--- a/content/day-5.txt
+++ b/content/day-5.txt
@@ -21,4 +21,372 @@
=| Header
= The Problem
=
+We have a nice picture of a tree, but ultimately it may have been easier to just draw it using some graphics program. We promised you a growing tree, so let's make it grow like this:
=
+| Monospace
+ TODO: Implement growing tree example
+
+ | AnimatedTree
+ | Axiom
+ color = brown
+ rotation = -90
+ minAge = 0
+ maxAge = 10
+
+ | Rule
+ | Parent
+ color = brown
+
+ | Child
+ color = "green"
+ rotation = 0
+
+ | Child
+ color = "green"
+ rotation = 20
+
+ | Child
+ color = "green"
+ rotation = -30
+
+ | Rule
+ | Parent
+ color = green
+
+ | Child
+ color = "red"
+ rotation = -45
+
+ | Child
+ color = "red"
+ rotation = -5
+
+ | Child
+ color = "red"
+ rotation = 50
+
+| Header
+ The Elm Architecture
+
+So far our programs were very static. The value of {Code|main} would be evaluated once, painted on the screen and stay there forever. Now we need to make our program react to the events in outside world. What events? A passage of time {Icon|name=watch} If we want our tree to grow over time, we need to know how much time have passed, so we can update it's age accordingly.
+
+In Elm we can do it using a different type of {Code|main}. One that ties together four functions:
+
+| List
+ #. {Code|init} specifying what's the initial state of the running program
+
+ #. {Code|subscriptions} specifying what events we are expecting and what messages will be sent when they happen
+
+ #. {Code|update} specifying how the state of the application will change when messages are received
+
+ #. {Code|view} specifying what value should be displayed depending on the current state.
+
+State is sometimes called model. There are also commands, but we will not use them in our program.
+
+| Image
+ src = /assets/Elm MVU architecture.jpg
+ description = "Model View Update - The Elm Architecture"
+
+| Monospace
+ TODO: Attribution
+
+First let's change the value of {Code|main} and create the {Code|view} function, like this:
+
+| Code
+ module Main exposing (main)
+
+ import Browser
+ import Dict
+ import Element
+ import Svg exposing (Svg)
+ import Svg.Attributes
+
+
+ main =
+ Browser.element
+ { init = init
+ , view = view
+ , update = update
+ , subscriptions = subscriptions
+ }
+
+
+ view age =
+ [ segment (age / 5000) { color = "brown", rotation = -90 } ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: none"
+ , Svg.Attributes.viewBox "-500 -500 1000 1000"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+
+ rules =
+ Dict.empty
+ |> Dict.insert "brown"
+ [ { color = "brown", rotation = 0 }
+ , { color = "green", rotation = 20 }
+ , { color = "green", rotation = -30 }
+ ]
+ |> Dict.insert "green"
+ [ { color = "red", rotation = -45 }
+ , { color = "red", rotation = -5 }
+ , { color = "red", rotation = 50 }
+ ]
+
+
+ segment age { color, rotation } =
+ if age <= 0 then
+ Svg.g [] []
+
+ else
+ Svg.g []
+ [ rules
+ |> Dict.get color
+ |> Maybe.withDefault []
+ |> List.map (segment (age - 1))
+ |> Svg.g
+ [ Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate("
+ , String.fromFloat (age * 10)
+ , ")"
+ ]
+ )
+ ]
+ , dot age color rotation
+ , line age color rotation
+ ]
+
+
+ dot age color rotation =
+ Svg.circle
+ [ Svg.Attributes.r (String.fromFloat age)
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate("
+ , String.fromFloat (age * 10)
+ , ")"
+ ]
+ )
+ ]
+ []
+
+
+ line age color rotation =
+ Svg.line
+ [ Svg.Attributes.strokeWidth "1"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x1 (String.fromFloat (age * 10))
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.stroke color
+ , Svg.Attributes.strokeWidth (String.fromFloat age)
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ")"
+ ]
+ )
+ ]
+ []
+
+Notice that the {Code|view} takes an argument called {Code|age} and passes it to the first (brown) segment. The value of {Code|age} is our state. In some programs the type of state can be very complex, but in our program it's simply a {Code|Float} number. It will indicate how much time in milliseconds have passed from the start of the program. Because the time is counted in milliseconds, to get correct results we need to divide it by 5000 (so the tree will grows one generation of segments per five second).
+
+Now let's provide the init function. It takes an argument sometimes called flags, but we will ignore it. Let's just say that it will always receive {Code|()} - an empty value called unit. It needs to return two values bound together in a structure called /tuple/.
+
+| Note
+ Tuple is like a list, but have a fixed number of elements. Unlike the lists, elements of tuples can have different types.
+
+The second value is a command. We don't need any, so we just give it a {Code|Cmd.none}, which as you can guess produces a command that does nothing.
+
+First value is more important. This is the initial age of the tree, right after the program starts. Since no time have passed yet, we set it to 0.
+
+That's our init:
+
+| Code
+ init () =
+ ( 0, Cmd.none )
+
+Time for the {Code|update} function. It will get two arguments: an incoming message and the current state. Incoming message will always be a duration of time since previous frame, so let's simply call it {Code|duration}. The state is the current age of the tree, so again we can call it {Code|age}. In return we must produce a tuple with:
+
+| List
+ #. the new state ({Code|age + duration})
+ #. a command (we don't need it, so we give {Code|Cmd.none})
+
+It looks like this:
+
+| Code
+ update duration age =
+ ( age + duration
+ , Cmd.none
+ )
+
+Finally let's subscribe to the events marking the passage of time. We can do it using {Code|Browser.Events.onAnimationFrameDelta}. Here is how it works. Whenever the browser is ready for the next frame it will send us a message containing the number of milliseconds that have passed since previous frame.
+
+To use it we first need to import the {Code|Browser.Events} module. The {Code|Browser.Events.onAnimationFrameDelta} function expects us to give it a function that will get a duration and return a message. But our message is simply a duration! We don't need to do anything with it, just take it as it is. So we need a function that just returns whatever it gets. This function is called {Code|identity}.
+
+| Note
+ Perhaps you have noticed that we use a lot of functions. Functions are passed to functions that sometimes return functions 🤕 That's why Elm is called a *functional programming language*.
+
+The whole {Code|subscriptions} looks like that:
+
+| Code
+ subscriptions age =
+ Browser.Events.onAnimationFrameDelta identity
+
+And the complete code like this:
+
+| Code
+ module Main exposing (main)
+
+ import Browser
+ import Browser.Events
+ import Dict
+ import Element
+ import Svg exposing (Svg)
+ import Svg.Attributes
+
+
+ main =
+ Browser.element
+ { init = init
+ , view = view
+ , update = update
+ , subscriptions = subscriptions
+ }
+
+
+ init () =
+ ( 0, Cmd.none )
+
+
+ view age =
+ [ segment (age / 5000) { color = "brown", rotation = -90 } ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: none"
+ , Svg.Attributes.viewBox "-500 -500 1000 1000"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+
+ update duration age =
+ ( duration
+ |> Debug.log "Duration"
+ |> (+) age
+ |> Debug.log "Age"
+ , Cmd.none
+ )
+
+
+ subscriptions age =
+ Browser.Events.onAnimationFrameDelta identity
+
+
+ rules =
+ Dict.empty
+ |> Dict.insert "brown"
+ [ { color = "brown", rotation = 0 }
+ , { color = "green", rotation = 20 }
+ , { color = "green", rotation = -30 }
+ ]
+ |> Dict.insert "green"
+ [ { color = "red", rotation = -45 }
+ , { color = "red", rotation = -5 }
+ , { color = "red", rotation = 50 }
+ ]
+
+
+ segment age { color, rotation } =
+ if age <= 0 then
+ Svg.g [] []
+
+ else
+ Svg.g []
+ [ rules
+ |> Dict.get color
+ |> Maybe.withDefault []
+ |> List.map (segment (age - 1))
+ |> Svg.g
+ [ Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate("
+ , String.fromFloat (age * 10)
+ , ")"
+ ]
+ )
+ ]
+ , dot age color rotation
+ , line age color rotation
+ ]
+
+
+ dot age color rotation =
+ Svg.circle
+ [ Svg.Attributes.r (String.fromFloat age)
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate("
+ , String.fromFloat (age * 10)
+ , ")"
+ ]
+ )
+ ]
+ []
+
+
+ line age color rotation =
+ Svg.line
+ [ Svg.Attributes.strokeWidth "1"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x1 (String.fromFloat (age * 10))
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.stroke color
+ , Svg.Attributes.strokeWidth (String.fromFloat age)
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ")"
+ ]
+ )
+ ]
+ []
+
+| Emphasize
+ That's it!
+
+| Emphasize
+ {Icon|name=flag}
+
+| Emphasize
+ Your tree is growing over time!
+
+We hope you had good time learning programming with us. If we still have some time left, we can play with the code.
Commits: 5
Fix typo
index 32fc805..e54b4b9 100644
--- a/content/day-5.txt
+++ b/content/day-5.txt
@@ -6,7 +6,7 @@
=
=
=| Note
- Today we are going to lear about
+ Today we are going to learn about
=
= | List
= - The Elm ArchitectureControl oprhans in paragraphs
See https://css-tricks.com/almanac/properties/o/orphans/
index b49f7d3..8430329 100644
--- a/src/Mark/Custom.elm
+++ b/src/Mark/Custom.elm
@@ -55,6 +55,7 @@ paragraph =
= [ Element.paddingXY 0 10
= , Element.spacing 12
= , css "hyphens" "auto"
+ , css "orphans" "3"
= , Font.justify
= ]
= (content model)Write first section of day 4 - manual tree
index ea07e93..80d31eb 100644
--- a/content/day-4.txt
+++ b/content/day-4.txt
@@ -1,20 +1,372 @@
=| Title
- Day 3
+ Day 4
=
=| Emphasize
= Let's Make a Tree
=
+| Note
+ *We are still working on this content.*
+
+ Stay tuned {Icon|name=radio}
=
=| Note
- Today we are going to lear about
+ Last day we have learned about basic building blocks of an Elm program: *value*, *name* and *type*. Today we are going to use this blocks to compose more complex structures. On the way we are going to learn about:
=
= | List
- - Recursion
+ -> Recursion
=
=| Header
= The Problem
=
+| Monospace
+ TODO: Static tree example
+
+The tree is built from segments: a line and a dot. In this respect it is similar to the connected dots we created yesterday. If we group the dot and a line, we will have the building block for our tree. We can do it with {Code|Svg.g} function ({Code|g} is an abbreviation of "group"). Just like {Code|Svg.svg}, it takes list of attributes and list of children. We can use it like that:
+
+| Code
+ main =
+ [ Svg.g []
+ [ dot "skyblue" 0
+ , line "skyblue" 0
+ ]
+ , Svg.g []
+ [ dot "orange" 72
+ , line "orange" 72
+ ]
+ , Svg.g []
+ [ dot "red" 144
+ , line "red" 144
+ ]
+ , Svg.g []
+ [ dot "lime" 216
+ , line "lime" 216
+ ]
+ , Svg.g []
+ [ dot "maroon" 288
+ , line "maroon" 288
+ ]
+ ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: none"
+ , Svg.Attributes.viewBox "-100 -100 200 200"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+Reload the browser there should be no difference at this point. Looking back at code I notice that each segment looks the same: it is a group with a dot and a line, where the {Code|group} and {Code|line} functions have the same arguments passed to them. This kind of repetition begs for a function. Let's call it {Code|segment} and define like this:
+
+| Code
+ segment color rotation =
+ Svg.g []
+ [ dot color rotation
+ , line color rotation
+ ]
+
+Type this code at the bottom of the file and then replace main with the following:
+
+| Code
+ main =
+ [ segment "skyblue" 0
+ , segment "orange" 72
+ , segment "red" 144
+ , segment "lime" 216
+ , segment "maroon" 288
+ ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: none"
+ , Svg.Attributes.viewBox "-100 -100 200 200"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
=| Note
- *We are still working on this content.*
+ I hope you can see the pattern in what we are doing. We are taking repetitive blocks of code and turning them into named functions. Parameters help us deal with variability in the repeated code (like color and rotation that is different for each segment).
=
- Stay tuned {Icon|name=radio}
+Reload the browser. There should still be no visible difference in the behavior of the program, but our source code is getting more readable. That's good.
+
+The big difference between our program and the one we are trying to build is that our have only one level of segments, whereas in the example segments grow from the tip of other segments. You can think of it as segments having child segments: the green segment has two child brown segments. Each brown segment has three red child segments
+
+| Note
+ TODO: Make sure the description matches the example
+
+It's similar to how the way an SVG group has child elements. But group itself (together with it's children) is an element, so we can put a group within a group. Let's do that! Change the definition of {Code|segment} function as follows:
+
+| Code
+ segment color rotation =
+ Svg.g []
+ [ dot color rotation
+ , line color rotation
+ , Svg.g
+ [ Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ [ dot "red" 15
+ , line "red" 15
+ , dot "blue" -15
+ , line "blue" -15
+ ]
+ ]
+
+Now the program should display something like this:
+
+| Note
+ TODO: Example
+
+It's already starts to look interesting, but there are two problems with it. First it doesn't fit on screen. This is easy to fix using {Code|viewBox}. We need to zoom out to see more of a picture. Change the value passed to {Code|viewBox} on line 19 to {Code|"-500 -500 1000 1000"}, effectively zooming out 5x but preserving keeping the origin in the center of our viewbox.
+
+Then the lines look ugly displayed on top of the dots of different color. This is also easy to fix. In SVG several siblings (children of the same parent element) lay one on top of another. The "younger" sibling is laying on top of the older, so in our case the group containing the blue and red dots and lines lays on top of the dot and line. Let's change the order of siblings by moving the group to the beginning of the list, like this:
+
+| Code
+ segment color rotation =
+ Svg.g []
+ [ Svg.g
+ [ Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ [ dot "red" 15
+ , line "red" 15
+ , dot "blue" -15
+ , line "blue" -15
+ ]
+ , dot color rotation
+ , line color rotation
+ ]
+
+With this two changes applied you should see something like this in the browser:
+
+| Note
+ TODO: Example
+
+So now each segment has two sub segments: red and blue. The red one is rotate 15 degree clockwise, the blue one is rotated 15 degree counterclockwise. But they are look the same. Our goal is to make them variable. This means that we need one more parameter to the segment: list of children. Let's do it:
+
+| Code
+ module Main exposing (main)
+
+ import Element
+ import Svg exposing (Svg)
+ import Svg.Attributes
+
+
+ main =
+ [ segment "skyblue"
+ 0
+ [ dot "red" 15
+ , line "red" 15
+ , dot "blue" -15
+ , line "blue" -15
+ ]
+ , segment "orange" 72 []
+ , segment "red" 144 []
+ , segment "lime" 216 []
+ , segment "maroon" 288 []
+ ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: none"
+ , Svg.Attributes.viewBox "-500 -500 1000 1000"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+
+ segment : String -> Float -> List (Svg msg) -> Svg.Svg msg
+ segment color rotation children =
+ Svg.g []
+ [ Svg.g
+ [ Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ children
+ , dot color rotation
+ , line color rotation
+ ]
+
+
+ dot color rotation =
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ []
+
+
+ line color rotation =
+ Svg.line
+ [ Svg.Attributes.strokeWidth "1"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x1 "80"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.stroke color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ")"
+ ]
+ )
+ ]
+ []
+
+
+We have added third parameter to {Code|segment} function on line 34 (before the changes it was 28). The parameter is named {Code|children} and we use it in place of the list on line 45 (previously 39). The list itself was moved to line 11, where it is passed as a value for the parameter of the first segment. This move made all the lines shift down. All other segments will also need to get value for {Code|children} parameter, so let's just give each of them an empty list - meaning they have 0 children.
+
+Once all changes are applied, reload the browser and see that only first, skyblue segment has child segments.
+
+| Note
+ TODO: Example
+
+Before we continue, let's notice that on lines 11 - 15 we have a nice opportunity to remove some repetition. Consider that dot and a line with same rotation and color are just a segment. We can replace:
+
+| Code
+ [ dot "red" 15
+ , line "red" 15
+ , dot "blue" -15
+ , line "blue" -15
+ ]
+
+with:
+
+| Code
+ [ segment "red" 15 []
+ , segment "blue" -15 []
+ ]
+
+A segment within a segment! Consider that every segment can have child segments. This way we can build a tree as complex as we want. Try adding more children:
+
+| Code
+ module Main exposing (main)
+
+ import Element
+ import Svg exposing (Svg)
+ import Svg.Attributes
+
+
+ main =
+ [ segment "skyblue"
+ 0
+ [ segment "red" 15 []
+ , segment "blue"
+ -15
+ [ segment "yellow" 45 []
+ , segment "pink" -20 []
+ , segment "green" -90 []
+ ]
+ ]
+ , segment "orange" 72 []
+ , segment "red" 144 []
+ , segment "lime" 216 []
+ , segment "maroon" 288 []
+ ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: none"
+ , Svg.Attributes.viewBox "-500 -500 1000 1000"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+
+ segment color rotation children =
+ Svg.g []
+ [ Svg.g
+ [ Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ children
+ , dot color rotation
+ , line color rotation
+ ]
+
+
+ dot color rotation =
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ []
+
+
+ line color rotation =
+ Svg.line
+ [ Svg.Attributes.strokeWidth "1"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x1 "80"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.stroke color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ")"
+ ]
+ )
+ ]
+ []
+
+| Emphasize
+ Time to play 🌳🌲🌴🎄
+
+| Emphasize
+
+ Try building your own tree this way. Play with colors and sizes.
+
+| Note
+ Hint: if you add {Code|Svg.attributes.strokeWidth "2"} attribute to the line, you will get thicker branches. Try different combinations of stroke width and radius of a dot.Start writing Rule Based Tree
index 80d31eb..3dbc52a 100644
--- a/content/day-4.txt
+++ b/content/day-4.txt
@@ -370,3 +370,30 @@ A segment within a segment! Consider that every segment can have child segments.
=
=| Note
= Hint: if you add {Code|Svg.attributes.strokeWidth "2"} attribute to the line, you will get thicker branches. Try different combinations of stroke width and radius of a dot.
+
+| Header
+ Rules Based Tree
+
+I hope you had fun and are proud of your tree. If you made a big one, perhaps you have noticed that all this nesting gets pretty tedious. What if we could make the computer do the hard work? We can do it, but as you may have noticed computers are not very smart, so we need to give them exact rules.
+
+Let's say we have three types of segments: brown, green and red. The child segments depend on the parent like this:
+
+| List
+ #. We always start with a brown segment going up (-90 degree).
+
+ #. The brown segment will have three green child segments:
+
+ - one going straight (0 degrees),
+ - one a little bit to the right (20 degree)
+ - and one little bit to the left (-30 degree).
+
+ #. Green segments will have three red child segments
+
+ - one at -45 degree
+ - one at -5 degree
+ - one at 50 degree
+
+The tree following these rules will look like this:
+
+| Note
+ TODO: ExampleUpgrade mdgriffith/elm-markup to 2.0.6
Fixed startWith bugs.
index 439720e..64c0692 100644
--- a/elm.json
+++ b/elm.json
@@ -22,7 +22,7 @@
= "feathericons/elm-feather": "1.3.0",
= "ianmackenzie/elm-geometry": "1.2.1",
= "ianmackenzie/elm-geometry-svg": "1.0.2",
- "mdgriffith/elm-markup": "2.0.5",
+ "mdgriffith/elm-markup": "2.0.6",
= "mdgriffith/elm-ui": "1.1.0",
= "turboMaCk/any-dict": "1.0.1"
= },
Commits: 1
Style
index 03a2ca8..155a00b 100644
--- a/content/day-3.txt
+++ b/content/day-3.txt
@@ -511,7 +511,7 @@ That's what we need! Let's pass the value of {Code|rotation} through this functi
= ]
= []
=
-This should work! The browser should now present the dots as intended and I would agree that the code is more readable now.
+This should work! The browser should now present the dots as intended and I hope you agree that the code is more readable now.
=
=| Header
= Lines to Connect the Dots
Commits: 8
Correction
index 67e6def..ae30437 100644
--- a/content/day-3.txt
+++ b/content/day-3.txt
@@ -321,7 +321,7 @@ Take your time. Here are some hints to help you:
=
= #. Names are given to values wherever you see a {Code|=} sign. The name is on the left and the value is below and slightly to the right. In our code we are giving only one name and it's value is quite complex. In fact the whole program is about computing this value. Try to find it.
=
- #. Functions are called wherever you see a name that have any expression to the right (or below and right) of it (like: {Code|fun "learning Elm together"}), or that stand to the left of the {Code|\|>} (pipe operator; like: {Code|"learning Elm together" \|> fun}). The expression to the right of the function (or left of pipe) is called /the argument/. Sometimes an argument is a complex expression. Some functions take more than one argument (e.g. {Code|Svg.svg} takes 2 and the second one is very complex), and sometimes one is on the right and the other comes through the pipe 🤕. There is a lot of functions called here (36 to be precise) and most are prefixed with a name of the module that exposed them to us.
+ #. Functions are called wherever you see a name that have any expression to the right (or below and right) of it (like: {Code|fun "learning Elm together"}), or that stand to the left of the {Code|\|>} (pipe operator; like: {Code|"learning Elm together" \|> fun}). The expression to the right of the function (or left of pipe) is called /the argument/. Sometimes an argument is a complex expression. Some functions take more than one argument (e.g. {Code|Svg.svg} takes 2 and the second one is very complex), and sometimes one is on the right and the other comes through the pipe 🤕. There is a lot of functions called here (36 to be precise) and all of them are prefixed with a name of the module that exposed them to us.
=
= #. Interestingly there are only two names exposed by a module that do not refer to a function. Try to find them!
=| NoteMove "We are still working ..." note to the top
index ae30437..ab7167f 100644
--- a/content/day-3.txt
+++ b/content/day-3.txt
@@ -4,6 +4,11 @@
=| Emphasize
= Connecting the Dots
=
+| Note
+ *We are still working on this content.*
+
+ Stay tuned {Icon|name=radio}
+
=| Note
= So far we have been writing the code of our program without thinking too much about what it means. Today we are going to learn about
=
@@ -240,13 +245,10 @@ We don't have to create all the functions that we need. There are thousands of t
=
=And then use any name that it exposes, like this
=
-| Note
- *We are still working on this content.*
=| Terminal
= > String.fromInt 10
= "10" : String
=
- Stay tuned {Icon|name=radio}
=If the name comes from an imported module, you need to prefix it with the name of the module, like we did above.
=
=Let's look at our code and try to recognize some literal values, lists, names and functions. Here is how it should look after day 2:
@@ -324,10 +326,7 @@ Take your time. Here are some hints to help you:
= #. Functions are called wherever you see a name that have any expression to the right (or below and right) of it (like: {Code|fun "learning Elm together"}), or that stand to the left of the {Code|\|>} (pipe operator; like: {Code|"learning Elm together" \|> fun}). The expression to the right of the function (or left of pipe) is called /the argument/. Sometimes an argument is a complex expression. Some functions take more than one argument (e.g. {Code|Svg.svg} takes 2 and the second one is very complex), and sometimes one is on the right and the other comes through the pipe 🤕. There is a lot of functions called here (36 to be precise) and all of them are prefixed with a name of the module that exposed them to us.
=
= #. Interestingly there are only two names exposed by a module that do not refer to a function. Try to find them!
-| Note
- *We are still working on this content.*
=
- Stay tuned {Icon|name=radio}
=
=
=Let's take a deeper look at how we give attributes to an SVG element.Draft section about dot function on day 3
index ab7167f..0898a8a 100644
--- a/content/day-3.txt
+++ b/content/day-3.txt
@@ -121,6 +121,8 @@ Above we applied the function (by calling it's name: {Code|fun}) to a literal va
=
= Alternatively you can think of functions as parametrized values. The value you get depends on what value you provide to it. Eggs can have different sizes . An egg painting machine would give you different painted eggs depending on what egg you put in it.
=
+ Don't worry if the concept of a function is not very clear yet. Most people have difficulty here. You will see some practical examples of functions soon.
+
=Now we see that we can get a value in three ways: (1) by literally expressing it, (2) by calling it's name and (3) by applying a function to another value.
=
=Not every function can be applied to any value. You can add two numbers (like {Code|2} and {Code|5} in {Code|2 + 5}), but you can't add a number to a string ({Code|2 + "Hello!"} - try it and see an error!). The system that governs what function can be applied to what value is called a *type system* and it plays important role in Elm programming.
@@ -327,7 +329,189 @@ Take your time. Here are some hints to help you:
=
= #. Interestingly there are only two names exposed by a module that do not refer to a function. Try to find them!
=
+| Header
+ Let's make a function
+
+Now we know what is what. Let's consider where we have a repetitive code that we could turn into a named function. If you look at the code from a distance, you can see that there is one block that is repeated 5 times almost without a difference. I'm talking about this:
+
+| Monospace
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "..."
+ , Svg.Attributes.transform "rotate(...) translate(80)"
+ ]
+ []
+
+This block of code is repeated five times in our code - once for each dot. Now I'm going to show you how to eliminate the repetition in few steps.
+
+First let's make them even more uniform by adding {Code|rotate(0)} to the first block. It will help us later. The code should look like this:
+
+| Code
+ TODO
+
+
+
+Then let's give the value produced by this block a name. We can call it the {Code|dot}. Select the first block that you have just modified, everything between {Code|[} and the {Code|,} on line 20 and cut it with {Key|command} + {Key|x}. Then in the empty space left type {Code|dot}.
+
+Move your cursor to the end of the file and type {Code|dot =}, press {Key|enter} and paste the code you cut before with {Key|command} + {Key|v}. The code should now look like this:
+
+| Code
+ TODO
+
+Reload the browser. Everything should work exactly the same as before. One of the dots is now a named value, but for our program it makes no difference. What is important is it's value, not where it comes from.
+
+Of course the goal is to have our list of dots looking like this:
+
+| Monospace
+ [ dot
+ , dot
+ , dot
+ , dot
+ , dot
+ ]
+
+Let's try it. The effect will be that all the dots will be in the same place, one on top of another. Also they will all have the same color. So on the screen you should see only one dot, somewhat to the right of center.
+
+The dots need to have some parameters so they can be different. We already identified them: it's color and rotation. Above I said that a function is a parametrized value. Let's turn our dot definition on line {Code|TODO} into a function with two parameters like this:
+
+| Code
+ TODO: Rest of the code
+
+ dot color rotation =
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "skyblue"
+ , Svg.Attributes.transform "rotate(0) translate(80)"
+ ]
+ []
+
+This way we declared that when calling a {Code|dot} name you will provide two values: first one for {Code|color} and second for {Code|rotation}. Let's stay true to our word and provide it. Change the list on lines {Code|todo} to look like this:
+
+| Code
+ TODO: Rest of the code
+
+ [ dot "skyblue" 0
+ , dot "orange" 72
+ , dot "red" 144
+ , dot "lime" 216
+ , dot "maroon" 288
+ ]
+
+Let's reload again and see that there is still only one, skyblue dot visible. That's because we did not tell Elm what to do with the values of the two parameters we provide for the {Code|dot} function. We gave them names ({Code|color} and {Code|rotation}), but never called them.
+
+First, let's use the {Code|color} parameter. If you want to change the color of the dot, you need to pass a different value to the {Code|Svg.Attributes.fill} function on line {Code|TODO}. Currently the value is a literal string {Code|"skyblue"}. Instead we want to give it whatever value was given for the {Code|color} parameter. We do it by simply calling the name of the parameter in place of a literal value, like this:
+
+
+| Code
+ TODO: Rest of the code
+
+ dot color rotation =
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform "rotate(0) translate(80)"
+ ]
+ []
+
+Now let's do the same for rotation. This one is more complex, because we cannot just pass {Code|rotation} value to the {Code|Svg.Attributes.transform} function. The {Code|rotation} is just a number, while the function expects a string formatted like this: {Code|"rotate(...) translate(80)"}.
+
+We can use the {Code|++} operator to glue the strings together. First part is constant, so we can just give it a literal value of {Code|"rotate("}. After that we put the {Code|++} and our variable part: {Code|rotation}. Finally the rest of the string is also constant: {Code|" translate(80)"} (notice the space before {Code|translate}!). All together we have:
+
+| Code
+ TODO: Rest of the code
+
+ dot color rotation =
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform "rotate(" ++ rotation ++ ") translate(80)"
+ ]
+ []
+
+But Elm will complain about this. First it looks like we gave 5 arguments to the {Code|Svg.Attributes.transform} function, and it only takes one. We can fix it by putting a parentheses around the {Code|"rotate(" ++ rotation ++ ") translate(80)" }, basically telling Elm that this is a single expression and it should evaluate it's value before passing it to the {Code|Svg.Attributes.transform} function. Now it should look like this:
+
+| Code
+ TODO: Rest of the code
+
+ dot color rotation =
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform ("rotate(" ++ rotation ++ ") translate(80)")
+ ]
+ []
+
+Here we face another problem. Elm complains with the following:
+
+| Monospace
+ TODO: Provide error message. Make sure the line and column numbers are correct.
+
+It's the type system in action. We can only use {Code|++} function with both arguments being the same type. Here one argument is:
+
+| Monospace
+ "rotate(" : String
+
+and another is:
+
+| Monospace
+ rotation : Float
+
+Elm won't have that. Fortunately there is an easy way to satisfy the type system. The {Code|String.fromFloat} takes a {Code|Float} and gives a {Code|String}. Try it in the REPL:
+
+| Terminal
+ > String.fromFloat
+ <function> : Float -> String
+
+ > String.fromFloat 10
+ "10" : String
+
+ > String.fromFloat 10.2
+ "10.2" : String
+
+That's what we need! Let's pass the value of {Code|rotation} through this function like this:
+
+| Code
+ TODO: Rest of the code
+
+ dot color rotation =
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform ("rotate(" ++ (String.fromFloat rotation) ++ ") translate(80)")
+ ]
+ []
+
+| Code
+ IDEA: What if we use String.concat instead?
+
+ dot color rotation =
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ []
=
+This should work! The browser should now present the dots as intended and I would argue that the code is more readable now.
=
=Let's take a deeper look at how we give attributes to an SVG element.
=Draft section about line function on day 3
index 0898a8a..80cf295 100644
--- a/content/day-3.txt
+++ b/content/day-3.txt
@@ -513,6 +513,111 @@ That's what we need! Let's pass the value of {Code|rotation} through this functi
=
=This should work! The browser should now present the dots as intended and I would argue that the code is more readable now.
=
+| Header
+ Lines to Connect the Dots
+
+Armed with our functional superpowers, let's make another function that draws a line. Let me fist show you a complete code and then we can discuss it.
+
+| Code
+ line color rotation =
+ Svg.line
+ [ Svg.Attributes.strokeWidth "1"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x1 "80"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.stroke color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ")"
+ ]
+ )
+ ]
+ []
+
+It works similar to the {Code|dot} function so you can just copy and past the it. Then apply the following changes:
+
+| List
+ #. The name needs to be different. {Code|line} seems appropriate.
+
+ #. Instead of a {Code|Svg.circle} we will call the {Code|Svg.line} function
+
+ #. The line doesn't have a radius, so remove the first attribute, {Code|Svg.Attributes.r}.
+
+ #. The line goes from one point to another, so it has four coordinates {Code|x1} and {Code|y1} for the beggening, {Code|x2} and {Code|y2} for the end. Beggening should be in the middle of the circle, which is {Code|x=0, y=0} (/the origin/). We will make the end reach to the big circle by giving the {Code|x2} value of {Code|80} (same as a radius of the circle).
+
+ #. Line can't be filled, as it has no area. Instead we give it a color using {Code|Svg.Attributes.stroke} function.
+
+ #. We do not need to translate the line. It's enough to rotate it.
+
+Once you have the function defined, let's use it. For every dot, create one line, like so:
+
+| Code
+ module Main exposing (main)
+
+ import Element
+ import Svg
+ import Svg.Attributes
+
+
+ main =
+ [ dot "skyblue" 0
+ , line "skyblue" 0
+ , dot "orange" 72
+ , line "orange" 72
+ , dot "red" 144
+ , dot "lime" 216
+ , dot "maroon" 288
+ ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: none"
+ , Svg.Attributes.viewBox "-100 -100 200 200"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+
+ dot color rotation =
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ []
+
+
+ line color rotation =
+ Svg.line
+ [ Svg.Attributes.strokeWidth "1"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x1 "80"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.stroke color
+ , Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ")"
+ ]
+ )
+ ]
+ []
=Let's take a deeper look at how we give attributes to an SVG element.
=
=We've already seen a few example of SVG attributes. The radius of our dot ({Code|circle}) is an attribute.Remove SVG gradients from curriculum for day 3
Remove the content pieces from the bottom.
index 80cf295..15d5af6 100644
--- a/content/day-3.txt
+++ b/content/day-3.txt
@@ -17,7 +17,7 @@
= - Functions
= - Modules
= - Types
- - SVG gradients
+ - SVG lines
=
=Before we begin, let's reflect on the following.
=
@@ -618,175 +618,17 @@ Once you have the function defined, let's use it. For every dot, create one line
= )
= ]
= []
-Let's take a deeper look at how we give attributes to an SVG element.
=
-We've already seen a few example of SVG attributes. The radius of our dot ({Code|circle}) is an attribute.
+You should see something like this in the browser:
=
-| Code
- Svg.circle [ Svg.Attributes.r "10" ] []
-
-Try giving it a new value to see your dot grow or shrink.
-
-Our viewBox is also an attribute. It's an attribute of the {Code|svg} element.
-
-| Code
- Svg.svg
- [ Svg.Attributes.viewBox "-300 -300 600 600" ]
- [ Svg.circle [ Svg.Attributes.r "10" ] [] ]
-
-Again, try giving it new values. As we saw earlier, the first two values (which represent the left and top of the viewBox) must be equal to the negative of the half of the last two values (which correspond to width and height). Try other combinations of values to adjust the placement of the dot. If the behavior of the viewBox is difficult to understand, refer again to the interactive tutorial above.
-
-To color our dot, we'll have to give it a second attribute. Many SVG elements take a {Code|List} of attributes. A {Code|List} is a very important concept in software development. {Code|Lists} allow us to group an arbitary number of similar values. In this case, we'll be using a {Code|List} to group {Code|Attribute a} values. Whenever you see {Code|[...]} in an Elm source code file, you are dealing with a list.
-
-Before we give a color to our dot, try to identify all the lists in the code we're writen already. There are 5. Hint: It is possible for a list to have only 1 or 0 items ({Code|[]}).
-
-| Code
- Svg.circle [ Svg.Attributes.r "10" ] []
-
-When we take a look at the code responsible for creating our dot, we see that it is followed by two lists. We'll only need to worry about the first one at the moment. As we've discussed, the first list already contains the {Code|r} attribute of the circle, representing the radius. We'll need to add a second attribute to the circle. We use a comma ({Code|,}) to separate elements in a list.
-
-| Code
- Svg.circle [ Svg.Attributes.r "10", ... ] []
-
-To color our circle, we'll use the {Code|fill} attribute.
-
-| Code
- Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.fill "blue"
- ]
- []
-
-You should see this result:
-
-| Window
- | DotWithViewBox
- background=none
- fill=blue
- viewBox=-300 -300 600 600
-
-Let's use our new understanding of lists to do something a bit more interesting. Let's draw multiple dots!
-
-We can start by taking a look at our {Code|main} value:
-
-| Code
- main =
- Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
- ]
- (Element.html
- (Svg.svg
- [ Svg.Attributes.viewBox "-500 -500 1000 1000"
- ]
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.fill "blue"
- ]
- []
- ]
- )
- )
-
-Where is our list of circles? To find it, first find the circle.
-
-| Code
- Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.fill "blue"
- ]
- []
-
-Next, identify the first open square bracket {Code|[} before the circle.
-
-| Code
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.fill "blue"
- ]
- []
- ...
-
-This is where the list begins.
-
-Finally, identify the matching closing brakcet {Code|]}. This is a bit tricky because there are two lists within this list. The first is the list of attributes to the circle we saw earlier. The second is an empty list. We'll see exactly what they do in a moment. Ignore both of these for now. The matching closing bracket should be on the same indentation level as the opening bracket. Your text editor should be able to help you here. When you move your cursor before the opening bracket, the closing bracket should be highlighted.
-
-| Code
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.fill "blue"
- ]
- []
- ]
-
-Right now, our list of circles has one value. We want to give it a second value. The second value will look identical to the first. What is the first value?
-
-| Code
- Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.fill "blue"
- ]
- []
-
-It might not seem obvious at first that this is all one value. But it is. {Code|Svg.circle} takes two lists as arguments. We'll see what an argument is later. For now, we will need to understand the difference between an attribute and a child.
-
-Imagine a box of colored easter eggs. One egg is painted yellow, another red, another blue. Moreover, they don't all come from the same bird. One is a small robin's egg. Another is a medium chicken egg. Another is a huge ostrich egg.
-
-Some of the elements in our image are 'things'. A box is a thing. Each egg is also a thing. Some of the elements in our image are not things. Blueness is not a thing. Medium-sizeness is not a thing. You can see blueness. You can see medium-sizeness. But they are not things. They are attributes of a thing. A thing can be blue. It can be medium-sized.
-
-It is a convention in many programming languages, including Elm and SVG, to distinguish between the attributes and children of an element. If we wanted to represent our box in SVG, we would give our box several attributes. It would have a color (maybe brown). It would have a width, length, depth. We would also give it several children. Each of the eggs within the box would be a child of the box. A box with 6 eggs would have 6 children. Each of these children (the eggs) would have different attributes: blue, red, medium-sized, etc. The children of an element are the 'things' it contains. The attributes of a thing are the elements that describe it.
-
-The first list following {Code|Svg.circle} is:
-
-| Code
- [ Svg.Attributes.r "10"
- , Svg.Attributes.fill "blue"
- ]
-
-This is a list of attributes. A radius is not a 'thing'. A fill color is not a 'thing'. They are elements that describe a thing. They describe our circle.
-
-The second list is:
-
-| Code
- []
-
-It is an empty list. Nevertheless, it is a list of children. Our circles don't have any children.
-
-Is the circle itself an attribute or a thing?
-
-From Elm's perspective, this entire code block is a circle:
-
-| Code
- Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.fill "blue"
- ]
- []
-
-This is not a circle:
-
-| Code
- Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.fill "blue"
- ]
-
-Nor is this:
+| Monospace
+ TODO: Example showing the connected dots
=
-| Code
- Svg.circle
+| Emphasize
+ Congratulations!
=
-To create a list with two circles, we'll want to duplicate the properly formed circle value. You can give the second dot a different color from the first.
+| Emphasize
+ {Icon|name=award}
=
-| Code
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.fill "blue"
- ]
- []
- , Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.fill "red"
- ]
- []
- ]
+| Emphasize
+ You are ready for {Link|Day 4|url=/day-4.html}!Correct typos
index d1fc7a3..7a9f8a4 100644
--- a/content/day-1.txt
+++ b/content/day-1.txt
@@ -289,7 +289,7 @@ One method is to use the *cartesian coordinates system*. To get some intuition a
=| Note
= TODO: Image of the table, the flower pot and the villain
=
-However, if you had a ruler, you could say: "it's 63.3cm from the top edge and 27.1cm from the left edge". To do that you would have previously agree which edge is left and which is top - not so obvious in case of a table. Fortunately in SVG we have this sort of agreement made for us. We alsways measure from the top and left edge of the viewport.
+However, if you had a ruler, you could say: "it's 63.3cm from the top edge and 27.1cm from the left edge". To do that you would have previously agree which edge is left and which is top - not so obvious in case of a table. Fortunately in SVG we have this sort of agreement made for us. We always measure from the top and left edge of the viewport.
=
=The distance from the left edge is called {Code|x} and the distance from the top is called {Code|y}. The bigger the {Code|x} the more to the right goes the element. The bigger the {Code|y} the more it goes down. Try it out:
=
@@ -302,7 +302,7 @@ The distance from the left edge is called {Code|x} and the distance from the top
=
=Adjust the sliders below to change the {Code|x} and {Code|y} coordinates of the dot. It's like moving the flower pot on the table by describing how far left, right, up or down it should be.
=
-Initially the center of the dot is at a point called /the origin/ or {Code|\{ x = 0, y = 0\}}. The origin is in the top left corner of the viewport. That's why only quarter of the dot is visible. If we want to help our dot get out of hiding, we'll have to place the center of the dot at a point with positive {Code|x} and {Code|y} values.
+Initially the center of the dot is at a point called /the origin/ or {Code|\{ x = 0, y = 0\}}. The origin is in the top left corner of the viewport. That's why only a quarter of the dot is visible. If we want to help our dot get out of hiding, we'll have to place the center of the dot at a point with positive {Code|x} and {Code|y} values.
=
=To change the position of the dot, we can use the {Code|cx} and {Code|cy} attributes of the {Code|circle} element, like this:
=
@@ -412,7 +412,7 @@ In the code we set the viewbox as an attribute of the {Code|svg} element, like t
= , Element.height Element.fill
= ]
=
-There are only two changes here. First we reset the {Code|cx} and {Code|cy} attributes of the {Code|circle} back to {Code|"0"} (on lines 11 and 12). We want the dot to be back at origin. Then we set up the position of the viewbox on line 20. Try experimenting with different values. If you set them right and refresh the broswer the dot should be in the center of the screen.
+There are only two changes here. First we reset the {Code|cx} and {Code|cy} attributes of the {Code|circle} back to {Code|"0"} (on lines 11 and 12). We want the dot to be back at origin. Then we set up the position of the viewbox on line 20. Try experimenting with different values. If you set them right and refresh the browser the dot should be in the center of the screen.
=
=| Window
= | DotWithViewBox
@@ -425,7 +425,7 @@ You can also remove line 19 to remove the pink background, or set it to some oth
=| Header
= Color
=
-Speaking of colors, now that we've centered our dot, it's time to give it a color! We do it by adding an {Code|fill} attribute to the {Code|circle} element, like that:
+Speaking of colors, now that we've centered our dot, it's time to give it a color! We do it by adding a {Code|fill} attribute to the {Code|circle} element, like that:
=
=| Code
= module Main exposing (main)index af026fb..8161521 100644
--- a/content/day-2.txt
+++ b/content/day-2.txt
@@ -5,7 +5,7 @@
= Let's Place the Dots in a Circle
=
=| Note
- Today we are going to lear about
+ Today we are going to learn about
=
= | List
= - SVG transformations
@@ -324,7 +324,7 @@ Now, notice that the dots displayed by the program we are creating are not layin
= radius = 80
= scatter = False
=
-On the right, you immediately see a patern. They form a circle. But in fact both pictures are showing 5 dots laying on a circle. Look:
+On the right, you immediately see a pattern. They form a circle. But in fact both pictures are showing 5 dots laying on a circle. Look:
=
=| Row
= | Circle
@@ -451,7 +451,7 @@ We will start from where we left off yesterday. Open {Code|src//Main.elm} in you
= , Element.height Element.fill
= ]
=
-It's a good starting point. We already have a single dot in the middle. Let's say this is will be the center of our circle.
+It's a good starting point. We already have a single dot in the middle. Let's say this will be the center of our circle.
=
=Now it's time to introduce our protractor and ruler. These is a property of an SVG element called `transform`. You use it like that:
=
@@ -537,7 +537,7 @@ For that we need to add another transformation before the move. First we will ro
= []
= ]
=
-This will rotate the element by 72 degrees. On it's own it wouldn't make any difference, because the dot is round - it doesn't matter how you rotate it, it always looks the same. But It also rotates it's internal coordinates system. So the translation will move it in a different direction then the first dot.
+This will rotate the element by 72 degrees. On its own it wouldn't make any difference, because the dot is round - it doesn't matter how you rotate it, it always looks the same. But It also rotates its internal coordinates system. So the translation will move it in a different direction then the first dot.
=
=Now it's time to add the third, red dot. Duplicate the second one, change fill to `"red"` and put `144` for rotate function, like this:
=index 5780836..4125171 100644
--- a/content/day-3.txt
+++ b/content/day-3.txt
@@ -5,7 +5,7 @@
= Connecting the Dots
=
=| Note
- So far we have been writing the code of our program without thinking too much about about what it means. Today we are going to learn about
+ So far we have been writing the code of our program without thinking too much about what it means. Today we are going to learn about
=
= | List
= - Values and names
@@ -83,7 +83,7 @@ Now let's focus our attention to *names*. You can give any value a name.
= "Hello!" : String
= >
=
-From now on you can refer to this value either literally or by it's name:
+From now on you can refer to this value either literally or by its name:
=
=| Terminal
= > kittens
@@ -109,19 +109,19 @@ Here we gave a name {Code|fun} to a function, that given {Code|something} will p
= > fun "Learning Elm"
= "Learning Elm is fun!" : String
=
-Above we applied the function (by calling it's name: {Code|fun}) to a literal value: {Code|"Learning Elm"}. In return the function produced another value: {Code|"Learning Elm is fun!"}. So a function takes a value and gives a value. But function itself is also a value.
+Above we applied the function (by calling its name: {Code|fun}) to a literal value: {Code|"Learning Elm"}. In return the function produced another value: {Code|"Learning Elm is fun!"}. So a function takes a value and gives a value. But function itself is also a value.
=
=| Note
= Imagine a machine that paints eggs. You put an egg into the machine and on the other side you get a painted egg. The egg is a thing and the painted egg is also a thing. But so is the machine!
=
-Now we see that we can get a value in three ways: (1) by literally expressing it, (2) by calling it's name and (3) by applying a function to another value.
+Now we see that we can get a value in three ways: (1) by literally expressing it, (2) by calling its name and (3) by applying a function to another value.
=
-Not every function can be applied to any value. You can add two numbers (like {Code|2} and {Code|5} in {Code|2 + 5}), but you can't a number to a string ({Code|2 + "Hello!"} - try it and see an error!). The system that governs what function can be applied to what value is called a *type system* and it plays important role in Elm programming.
+Not every function can be applied to any value. You can add two numbers (like {Code|2} and {Code|5} in {Code|2 + 5}), but you can't add a number to a string ({Code|2 + "Hello!"} - try it and see an error!). The system that governs what function can be applied to what value is called a *type system* and it plays important role in Elm programming.
=
=| Note
= Again, imagine a machine that paints eggs. If you try to put a carrot in that machine, the dumb machine would make a mess of your carrot and probably jam. But a smart machine would sound a low pitch buzz and simply refuse to take the carrot. That way a smart machine avoids making a mess and possibly damaging itself. The Elm's type system is what makes your functions smart. It stops you from putting a carrot into the egg painting machine!
=
-Every value in Elm has a type. You can see the type in the REPL - after evaluating the expression it shows it's value and the type, like here:
+Every value in Elm has a type. You can see the type in the REPL - after evaluating the expression it shows its value and the type, like here:
=
=| Terminal
= > kittens
@@ -141,7 +141,7 @@ Composite values like lists have composite types. For example here the type is {
= > [ "Hello", "Good bye" ]
= ["Hello","Good bye"] : List String
=
-This means that it's a list of strings. That means that all the elements of this list are of type {Code|String}. When talking about lists you have to tell that it's a list and what is the type of it's element. Only this can be considered a fully specified type. Compare:
+This means that it's a list of strings. That means that all the elements of this list are of type {Code|String}. When talking about lists you have to tell that it's a list and what is the type of its element. Only this can be considered a fully specified type. Compare:
=
=| Terminal
= > [ 1, 2, 3 ]
@@ -177,12 +177,12 @@ What's the type of an empty list? Let's see:
= > []
= [] : List a
=
-A list of {Code|a}. Obviously {Code|a} is not a type. This means that the type of the elements cannot be determine yet. This is signaled by the lowercase term where the type should be: {Code|a} instead of the capitalized {Code|String}.
+A list of {Code|a}. Obviously {Code|a} is not a type. This means that the type of the elements cannot be determined yet. This is signaled by the lowercase term where the type should be: {Code|a} instead of the capitalized {Code|String}.
=
=| Note
= Notice that the {Code|number} (type of value {Code|15} for example} is also lowercase. That's because it's also not a concrete type! Elm has two concrete types for numbers: {Code|Int} that can represent an integer number (1, 2, 3, 0, -122 etc.) and {Code|Float} that can represent a number with a fraction, (like 1.2, -3.5, 44.2). Because fraction part can be 0 (e.g. 1.0, 2.0, 0.0, -122.0 - so called whole numbers), it's not possible to tell if {Code|2} is an {Code|Int} or a {Code|Float}. We will see some of the implications of this later.
=
-Above I said that every value has a type and also that a function is a value on it's own. Let's try the following in REPL:
+Above I said that every value has a type and also that a function is a value on its own. Let's try the following in REPL:
=
=| Terminal
= > fun
@@ -198,7 +198,7 @@ The part expressed as {Code|"Learning Elm"} is a {Code|String} (we recognize it
=
=The part after the arrow symbol is the type that the function returns. It is also {Code|String}. That also should not surprise us, since {Code|"Learning Elm is fun!"} is also a {Code|String}.
=
-Not every function returns the same type as it takes. For example function {Code|String.fromInt} takes a value of type {Code|Int} and returns a value of type {Code|String}. It's type is therefor {Code|Int -> String}. We read it as: /type of String.fromInt is a function from Int to String/. Let's see it in action:
+Not every function returns the same type as it takes. For example function {Code|String.fromInt} takes a value of type {Code|Int} and returns a value of type {Code|String}. Its type is therefor {Code|Int -> String}. We read it as: /type of String.fromInt is a function from Int to String/. Let's see it in action:
=
=| Terminal
= > String.fromInt 10
@@ -257,9 +257,9 @@ Our viewBox is also an attribute. It's an attribute of the {Code|svg} element.
=
=Again, try giving it new values. As we saw earlier, the first two values (which represent the left and top of the viewBox) must be equal to the negative of the half of the last two values (which correspond to width and height). Try other combinations of values to adjust the placement of the dot. If the behavior of the viewBox is difficult to understand, refer again to the interactive tutorial above.
=
-To color our dot, we'll have to give it a second attribute. Many SVG elements take a {Code|List} of attributes. A {Code|List} is a very important concept in software development. {Code|Lists} allow us to group an arbitary number of similar values. In this case, we'll be using a {Code|List} to group {Code|Attribute a} values. Whenever you see {Code|[...]} in an Elm source code file, you are dealing with a list.
+To color our dot, we'll have to give it a second attribute. Many SVG elements take a {Code|List} of attributes. A {Code|List} is a very important concept in software development. {Code|Lists} allow us to group an arbitrary number of similar values. In this case, we'll be using a {Code|List} to group {Code|Attribute a} values. Whenever you see {Code|[...]} in an Elm source code file, you are dealing with a list.
=
-Before we give a color to our dot, try to identify all the lists in the code we're writen already. There are 5. Hint: It is possible for a list to have only 1 or 0 items ({Code|[]}).
+Before we give a color to our dot, try to identify all the lists in the code we have written already. There are 5. Hint: It is possible for a list to have only 1 or 0 items ({Code|[]}).
=
=| Code
= Svg.circle [ Svg.Attributes.r "10" ] []
@@ -330,7 +330,7 @@ Next, identify the first open square bracket {Code|[} before the circle.
=
=This is where the list begins.
=
-Finally, identify the matching closing brakcet {Code|]}. This is a bit tricky because there are two lists within this list. The first is the list of attributes to the circle we saw earlier. The second is an empty list. We'll see exactly what they do in a moment. Ignore both of these for now. The matching closing bracket should be on the same indentation level as the opening bracket. Your text editor should be able to help you here. When you move your cursor before the opening bracket, the closing bracket should be highlighted.
+Finally, identify the matching closing bracket {Code|]}. This is a bit tricky because there are two lists within this list. The first is the list of attributes to the circle we saw earlier. The second is an empty list. We'll see exactly what they do in a moment. Ignore both of these for now. The matching closing bracket should be on the same indentation level as the opening bracket. Your text editor should be able to help you here. When you move your cursor before the opening bracket, the closing bracket should be highlighted.
=
=| Code
= [ Svg.circleMerge branch 'content' into content-fix
Correct typos and grammatical error
index f70e581..03a2ca8 100644
--- a/content/day-3.txt
+++ b/content/day-3.txt
@@ -59,7 +59,7 @@ Some value literals are simple, like numbers and strings. Some are complex. Prev
= > [ 1, 2, 5, 77, 2 ]
= [1,2,5,77.6,2] : List number
=
-First notice that a list literal starts with {Code|[} and ends with {Code|]}. Inside there are values called /elements of the list/. Each element is separated from others with a {Code|,} (comma). Then notice that each element is a value on it's own, but the whole list is also a value.
+First notice that a list literal starts with {Code|[} and ends with {Code|]}. Inside there are values called /elements of the list/. Each element is separated from others with a {Code|,} (comma). Then notice that each element is a value on its own, but the whole list is also a value.
=
=| Note
= It can be called a /composite value/. It's like the box of eggs. Each egg is a thing, but the box of eggs is also a thing (composed of the box and the eggs).
@@ -119,7 +119,7 @@ Above we applied the function (by calling its name: {Code|fun}) to a literal val
=| Note
= Imagine a machine that paints eggs. You put an egg into the machine and on the other side you get a painted egg. The egg is a thing and the painted egg is also a thing. But so is the machine!
=
- Alternatively you can think of functions as parametrized values. The value you get depends on what value you provide to it. Eggs can have different sizes . An egg painting machine would give you different painted eggs depending on what egg you put in it.
+ Alternatively you can think of functions as parameterized values. The value you get depends on what value you provide to it. Eggs can have different sizes . An egg painting machine would give you different painted eggs depending on what egg you put in it.
=
= Don't worry if the concept of a function is not very clear yet. Most people have difficulty here. You will see some practical examples of functions soon.
=
@@ -189,7 +189,7 @@ What's the type of an empty list? Let's see:
=A list of {Code|a}. Obviously {Code|a} is not a type. This means that the type of the elements cannot be determined yet. This is signaled by the lowercase term where the type should be: {Code|a} instead of the capitalized {Code|String}.
=
=| Note
- Notice that the {Code|number} (type of value {Code|15} for example) is also lowercase. That's because it's also not a concrete type! Elm has two concrete types for numbers: {Code|Int} that can represent an integer number (1, 2, 3, 0, -122 etc.) and {Code|Float} that can represent a number with a fraction, (like 1.2, -3.5, 44.2). Because fraction part can be 0 (e.g. 1.0, 2.0, 0.0, -122.0 - so called whole numbers), it's not possible to tell if {Code|2} is an {Code|Int} or a {Code|Float}. We will see some of the implications of this later.
+ Notice that the {Code|number} (type of value {Code|15} for example) is also lowercase. That's because it's not a concrete type! Elm has two concrete types for numbers: {Code|Int} that can represent an integer number (1, 2, 3, 0, -122 etc.) and {Code|Float} that can represent a number with a fraction, (like 1.2, -3.5, 44.2). Because fraction part can be 0 (e.g. 1.0, 2.0, 0.0, -122.0 - so called whole numbers), it's not possible to tell if {Code|2} is an {Code|Int} or a {Code|Float}. We will see some of the implications of this later.
=
=Above I said that every value has a type and also that a function is a value on its own. Let's try the following in REPL:
=
@@ -327,7 +327,7 @@ Take your time. Here are some hints to help you:
=
= #. Functions are called wherever you see a name that have any expression to the right (or below and right) of it (like: {Code|fun "learning Elm together"}), or that stand to the left of the {Code|\|>} (pipe operator; like: {Code|"learning Elm together" \|> fun}). The expression to the right of the function (or left of pipe) is called /the argument/. Sometimes an argument is a complex expression. Some functions take more than one argument (e.g. {Code|Svg.svg} takes 2 and the second one is very complex), and sometimes one is on the right and the other comes through the pipe 🤕. There is a lot of functions called here (36 to be precise) and all of them are prefixed with a name of the module that exposed them to us.
=
- #. Interestingly there are only two names exposed by a module that do not refer to a function. Try to find them!
+ #. Interestingly there is only one name exposed by a module that do not refer to a function. Try to find it!
=
=| Header
= Let's make a function
@@ -360,7 +360,7 @@ Move your cursor to the end of the file and type {Code|dot =}, press {Key|enter}
=| Code
= TODO
=
-Reload the browser. Everything should work exactly the same as before. One of the dots is now a named value, but for our program it makes no difference. What is important is it's value, not where it comes from.
+Reload the browser. Everything should work exactly the same as before. One of the dots is now a named value, but for our program it makes no difference. What is important is its value, not where it comes from.
=
=Of course the goal is to have our list of dots looking like this:
=
@@ -374,7 +374,7 @@ Of course the goal is to have our list of dots looking like this:
=
=Let's try it. The effect will be that all the dots will be in the same place, one on top of another. Also they will all have the same color. So on the screen you should see only one dot, somewhat to the right of center.
=
-The dots need to have some parameters so they can be different. We already identified them: it's color and rotation. Above I said that a function is a parametrized value. Let's turn our dot definition on line {Code|TODO} into a function with two parameters like this:
+The dots need to have some parameters so they can be different. We already identified them: color and rotation. Above I said that a function is a parametrized value. Let's turn our dot definition on line {Code|TODO} into a function with two parameters like this:
=
=| Code
= TODO: Rest of the code
@@ -436,7 +436,7 @@ We can use the {Code|++} operator to glue the strings together. First part is co
= ]
= []
=
-But Elm will complain about this. First it looks like we gave 5 arguments to the {Code|Svg.Attributes.transform} function, and it only takes one. We can fix it by putting a parentheses around the {Code|"rotate(" ++ rotation ++ ") translate(80)" }, basically telling Elm that this is a single expression and it should evaluate it's value before passing it to the {Code|Svg.Attributes.transform} function. Now it should look like this:
+But Elm will complain about this. First it looks like we gave 5 arguments to the {Code|Svg.Attributes.transform} function, and it only takes one. We can fix it by putting a parentheses around the {Code|"rotate(" ++ rotation ++ ") translate(80)" }, basically telling Elm that this is a single expression and it should evaluate its value before passing it to the {Code|Svg.Attributes.transform} function. Now it should look like this:
=
=| Code
= TODO: Rest of the code
@@ -511,12 +511,12 @@ That's what we need! Let's pass the value of {Code|rotation} through this functi
= ]
= []
=
-This should work! The browser should now present the dots as intended and I would argue that the code is more readable now.
+This should work! The browser should now present the dots as intended and I would agree that the code is more readable now.
=
=| Header
= Lines to Connect the Dots
=
-Armed with our functional superpowers, let's make another function that draws a line. Let me fist show you a complete code and then we can discuss it.
+Armed with our functional superpowers, let's make another function that draws a line. Let me first show you a complete code and then we can discuss it.
=
=| Code
= line color rotation =
@@ -537,7 +537,7 @@ Armed with our functional superpowers, let's make another function that draws a
= ]
= []
=
-It works similar to the {Code|dot} function so you can just copy and past the it. Then apply the following changes:
+It works similar to the {Code|dot} function so you can just copy and paste it. Then apply the following changes:
=
=| List
= #. The name needs to be different. {Code|line} seems appropriate.
@@ -546,7 +546,7 @@ It works similar to the {Code|dot} function so you can just copy and past the it
=
= #. The line doesn't have a radius, so remove the first attribute, {Code|Svg.Attributes.r}.
=
- #. The line goes from one point to another, so it has four coordinates {Code|x1} and {Code|y1} for the beggening, {Code|x2} and {Code|y2} for the end. Beggening should be in the middle of the circle, which is {Code|x=0, y=0} (/the origin/). We will make the end reach to the big circle by giving the {Code|x2} value of {Code|80} (same as a radius of the circle).
+ #. The line goes from one point to another, so it has four coordinates {Code|x1} and {Code|y1} for the beginning, {Code|x2} and {Code|y2} for the end. Beginning should be in the middle of the circle, which is {Code|x=0, y=0} (/the origin/). We will make the end reach to the big circle by giving the {Code|x2} value of {Code|80} (same as a radius of the circle).
=
= #. Line can't be filled, as it has no area. Instead we give it a color using {Code|Svg.Attributes.stroke} function.
=
Commits: 2
Edits to day 3
index 5780836..8309bf5 100644
--- a/content/day-3.txt
+++ b/content/day-3.txt
@@ -5,7 +5,7 @@
= Connecting the Dots
=
=| Note
- So far we have been writing the code of our program without thinking too much about about what it means. Today we are going to learn about
+ So far we have been writing the code of our program without thinking too much about what it means. Today we are going to learn about
=
= | List
= - Values and names
@@ -48,7 +48,7 @@ Let's start with values. The simplest way to create a value is to literally type
=
=These expressions ({Code|2} and {Code|"Hello!"}) are called /literals/, because their values are literally what they look like. The value of {Code|2} is simply {Code|2}! When you enter them, the REPL evaluates and prints their value (which in this case is very easy) and type (the text after {Code|:}). We will get to types later.
=
-Some value literals are simple, like numbers and strings. On day 1 we discussed lists. You can also type them literally, like this:
+Some value literals are simple, like numbers and strings. Some are complex. Previously we have touched the subject of lists. You can also type them literally, like this:
=
=| Terminal
= > [ 1, 2, 5, 77, 2 ]
@@ -72,9 +72,9 @@ or contain only one element:
= ["Hello"] : List String
=
=| Note
- Notice that a list with one element is not the same as the element itself, just like the box with one egg is not the same as an egg alone.
+ Notice that a list with one element is not the same as the element itself, just like the box with one egg is not the same as an egg alone. Also an empty list is not nothing, just like an empty box of eggs is not nothing. It may be a major disappointment when you are about to make some pancakes, but it's still a thing.
=
-Now let's focus our attention to *names*. You can give any value a name.
+Now let's focus our attention to *names*. If you have a value, you can give it a name.
=
=| Terminal
= > kittens = 2
@@ -91,13 +91,13 @@ From now on you can refer to this value either literally or by it's name:
= > 2
= 2 : number
=
-The effect will be exactly the same - you will get a value back. So we see that there are at least two ways of getting values: by literally expressing them or refering to them by names given to them previously. In a composite value of a list, you can mix the two ways together:
+The effect will be exactly the same - you will get a value back. So we see that there are at least two ways of getting values: by literally expressing them or referring to them by names given to them previously. In a composite value of a list, you can mix the two ways together:
=
=| Terminal
= > [ 1, kittens, 3 ]
= [1,2,3] : List number
=
-A function is a special kind of a value. Let's create a named function like this:
+A *function* is a special kind of a value. Let's create a named function like this:
=
=| Terminal
= > fun something = something ++ " is fun!"
@@ -114,12 +114,14 @@ Above we applied the function (by calling it's name: {Code|fun}) to a literal va
=| Note
= Imagine a machine that paints eggs. You put an egg into the machine and on the other side you get a painted egg. The egg is a thing and the painted egg is also a thing. But so is the machine!
=
+ Alternatively you can think of functions as parametrized values. The value you get depends on what value you provide to it. Eggs can have different sizes . An egg painting machine would give you different painted eggs depending on what egg you put in it.
+
=Now we see that we can get a value in three ways: (1) by literally expressing it, (2) by calling it's name and (3) by applying a function to another value.
=
-Not every function can be applied to any value. You can add two numbers (like {Code|2} and {Code|5} in {Code|2 + 5}), but you can't a number to a string ({Code|2 + "Hello!"} - try it and see an error!). The system that governs what function can be applied to what value is called a *type system* and it plays important role in Elm programming.
+Not every function can be applied to any value. You can add two numbers (like {Code|2} and {Code|5} in {Code|2 + 5}), but you can't add a number to a string ({Code|2 + "Hello!"} - try it and see an error!). The system that governs what function can be applied to what value is called a *type system* and it plays important role in Elm programming.
=
=| Note
- Again, imagine a machine that paints eggs. If you try to put a carrot in that machine, the dumb machine would make a mess of your carrot and probably jam. But a smart machine would sound a low pitch buzz and simply refuse to take the carrot. That way a smart machine avoids making a mess and possibly damaging itself. The Elm's type system is what makes your functions smart. It stops you from putting a carrot into the egg painting machine!
+ Again, imagine a machine that paints eggs. If you try to put a carrot in that machine, the dumb machine would make a mess of your carrot and probably get jammed. But a smart machine would sound a low pitched buzz and simply refuse to take the carrot . That way a smart machine avoids making a mess and possibly damaging itself. The Elm's type system is what makes your functions smart. It prevents you from putting a carrot into the egg painting machine!
=
=Every value in Elm has a type. You can see the type in the REPL - after evaluating the expression it shows it's value and the type, like here:
=
@@ -141,7 +143,7 @@ Composite values like lists have composite types. For example here the type is {
= > [ "Hello", "Good bye" ]
= ["Hello","Good bye"] : List String
=
-This means that it's a list of strings. That means that all the elements of this list are of type {Code|String}. When talking about lists you have to tell that it's a list and what is the type of it's element. Only this can be considered a fully specified type. Compare:
+This means that it's a list of strings. All the elements of this list are of type {Code|String}. When talking about lists you have to tell that it's a list and what is the type of it's element. Only this can be considered a fully specified type. Compare:
=
=| Terminal
= > [ 1, 2, 3 ]
@@ -150,13 +152,13 @@ This means that it's a list of strings. That means that all the elements of this
=This is a list of numbers. It's easy to see - all the elements are numbers, just as previously they were strings. All the elements of a list must be of the same type. If you try mixing the types, Elm will refuse to build your program. Try:
=
=| Terminal
- > [ 1, 2, 3, "Hello" ]
+ > [ 1, 2, 3, "Carrot" ]
= -- TYPE MISMATCH ----------------------------------------------------------- elm
=
= The 4th element of this list does not match all the previous elements:
=
- 7| [ 1, 2, 3, "Hello" ]
- ^^^^^^^
+ 7| [ 1, 2, 3, "Carrot" ]
+ ^^^^^^^^
= The 4th element is a string of type:
=
= String
@@ -180,7 +182,7 @@ What's the type of an empty list? Let's see:
=A list of {Code|a}. Obviously {Code|a} is not a type. This means that the type of the elements cannot be determine yet. This is signaled by the lowercase term where the type should be: {Code|a} instead of the capitalized {Code|String}.
=
=| Note
- Notice that the {Code|number} (type of value {Code|15} for example} is also lowercase. That's because it's also not a concrete type! Elm has two concrete types for numbers: {Code|Int} that can represent an integer number (1, 2, 3, 0, -122 etc.) and {Code|Float} that can represent a number with a fraction, (like 1.2, -3.5, 44.2). Because fraction part can be 0 (e.g. 1.0, 2.0, 0.0, -122.0 - so called whole numbers), it's not possible to tell if {Code|2} is an {Code|Int} or a {Code|Float}. We will see some of the implications of this later.
+ Notice that the {Code|number} (type of value {Code|15} for example) is also lowercase. That's because it's also not a concrete type! Elm has two concrete types for numbers: {Code|Int} that can represent an integer number (1, 2, 3, 0, -122 etc.) and {Code|Float} that can represent a number with a fraction, (like 1.2, -3.5, 44.2). Because fraction part can be 0 (e.g. 1.0, 2.0, 0.0, -122.0 - so called whole numbers), it's not possible to tell if {Code|2} is an {Code|Int} or a {Code|Float}. We will see some of the implications of this later.
=
=Above I said that every value has a type and also that a function is a value on it's own. Let's try the following in REPL:
=
@@ -188,7 +190,7 @@ Above I said that every value has a type and also that a function is a value on
= > fun
= <function> : String -> String
=
-We have asked the REPL to evaluate the value referenced by name {Code|fun}. In response the value is display as {Code|<function>}. That's because there is no simple way to represent the value of a function as text. Let's focus our attention on the type (part after the colon {Code|:}). It is {Code|String -> String}. This means that the type is a function (we recognize it by the arrow symbol). What's before the arrow is a type of the value that goes into the function (called the /argument of the function/). The type of the argument is {Code|String}. That shouldn't be a surprise. Remember how we have used the function before to produce the value:
+We have asked the REPL to tell as the value referenced by name {Code|fun}. In response the REPL display the value as {Code|<function>}. That's because there is no simple way to represent the value of a function as text. Let's focus our attention on the type (part after the colon {Code|:}). It is {Code|String -> String}. This means that the type is a function (we recognize it by the arrow symbol). What's before the arrow is a type of the value that goes into the function (called the /argument of the function/). The type of the argument is {Code|String}. That shouldn't be a surprise. Remember how we have used the function before to produce the value:
=
=| Terminal
= > fun "Learning Elm"
@@ -198,7 +200,7 @@ The part expressed as {Code|"Learning Elm"} is a {Code|String} (we recognize it
=
=The part after the arrow symbol is the type that the function returns. It is also {Code|String}. That also should not surprise us, since {Code|"Learning Elm is fun!"} is also a {Code|String}.
=
-Not every function returns the same type as it takes. For example function {Code|String.fromInt} takes a value of type {Code|Int} and returns a value of type {Code|String}. It's type is therefor {Code|Int -> String}. We read it as: /type of String.fromInt is a function from Int to String/. Let's see it in action:
+Not every function returns the same type as it takes. For example the function {Code|String.fromInt} takes a value of type {Code|Int} and returns a value of type {Code|String}. It's type is therefor {Code|Int -> String}. We read it as: /type of String.fromInt is a function from Int to String/. Let's see it in action:
=
=| Terminal
= > String.fromInt 10
@@ -218,7 +220,7 @@ But what if we want to say that a certain number (say, {Code|232}) is fun? We ha
=
=We had to put the {Code|String.fromInt 232} expression in parentheses, so that Elm understands that value {Code|232} is to be passed to {Code|String.fromInt} function, instead of the {Code|String.fromInt} being passed to {Code|fun} (which would not work anyway, since {Code|fun} takes values of type {Code|String}, not {Code|Int -> String}).
=
-Alternatively we can use the pipe operator which looks like this: {Code|\|>}. It has the advantage that the code that the transformations that happen first are on the left side and ones that happen later on the right, so we can read our code left to right instead of inside out. Here is how it would look like:
+Alternatively we can use the pipe operator which looks like this: {Code|\|>}. It has the advantage that the transformations that happen first are on the left side and ones that happen later on the right. We can read our code left to right instead of inside out. Here is how it would look like:
=
=| Terminal
= > 232 |> String.fromInt |> fun
@@ -239,6 +241,70 @@ Now it's more apparent that the value {Code|232} is passed to the function {Code
=
= Stay tuned {Icon|name=radio}
=
+Let's look at our code and try to identify some literal values, names and functions:
+
+| Code
+ module Main exposing (main)
+
+ import Element
+ import Svg
+ import Svg.Attributes
+
+
+ main =
+ Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200"
+ ]
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.transform "translate(80)"
+ , Svg.Attributes.fill "skyblue"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "orange"
+ , Svg.Attributes.transform "rotate(72) translate(80)"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "red"
+ , Svg.Attributes.transform "rotate(144) translate(80)"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "lime"
+ , Svg.Attributes.transform "rotate(216) translate(80)"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "maroon"
+ , Svg.Attributes.transform "rotate(288) translate(80)"
+ ]
+ []
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+Then let's consider where we have a repetitive code that we could turn into a named function.
+
+
=Let's take a deeper look at how we give attributes to an SVG element.
=
=We've already seen a few example of SVG attributes. The radius of our dot ({Code|circle}) is an attribute.Draft the rest of the syntax section
index 8309bf5..67e6def 100644
--- a/content/day-3.txt
+++ b/content/day-3.txt
@@ -228,20 +228,28 @@ Alternatively we can use the pipe operator which looks like this: {Code|\|>}. It
=
=Now it's more apparent that the value {Code|232} is passed to the function {Code|String.fromInt}, and whatever comes on the other end is passed to the {Code|fun} function. We don't need parentheses in this case.
=
-| Note
- TODO: But where does the functions come from? Modules.
-
+But what are functions good for? They are very good for making similar values that depend on some parameters. If you have to repeat similar code many times to get similar, but slightly different results, try using a function.
=
+| Note
+ Just like machines for painting eggs are useful if you have a lot of eggs to paint. If you only need one or two painted eggs, then it's probably better to just get them painted. But if you have thousands, maybe it's worth to build a machine for it.
=
+We don't have to create all the functions that we need. There are thousands of them already defined and we can use them by importing modules. We already did that. First you import a module like this:
=
+| Terminal
+ > import String
=
+And then use any name that it exposes, like this
=
=| Note
= *We are still working on this content.*
+| Terminal
+ > String.fromInt 10
+ "10" : String
=
= Stay tuned {Icon|name=radio}
+If the name comes from an imported module, you need to prefix it with the name of the module, like we did above.
=
-Let's look at our code and try to identify some literal values, names and functions:
+Let's look at our code and try to recognize some literal values, lists, names and functions. Here is how it should look after day 2:
=
=| Code
= module Main exposing (main)
@@ -302,7 +310,24 @@ Let's look at our code and try to identify some literal values, names and functi
= , Element.height Element.fill
= ]
=
-Then let's consider where we have a repetitive code that we could turn into a named function.
+Take your time. Here are some hints to help you:
+
+| List
+ #. We import three modules. Their names are to the right of the {Code|import} keyword.
+
+ #. Literal values are numbers and quoted texts. In our code there are only string literals so try to find all texts in quotes ({Code|"like this"}). I<>count 26 of them.
+
+ #. Then there are 13 lists, 5 of them empty and 8 with elements. Some lists are nested in other lists!
+
+ #. Names are given to values wherever you see a {Code|=} sign. The name is on the left and the value is below and slightly to the right. In our code we are giving only one name and it's value is quite complex. In fact the whole program is about computing this value. Try to find it.
+
+ #. Functions are called wherever you see a name that have any expression to the right (or below and right) of it (like: {Code|fun "learning Elm together"}), or that stand to the left of the {Code|\|>} (pipe operator; like: {Code|"learning Elm together" \|> fun}). The expression to the right of the function (or left of pipe) is called /the argument/. Sometimes an argument is a complex expression. Some functions take more than one argument (e.g. {Code|Svg.svg} takes 2 and the second one is very complex), and sometimes one is on the right and the other comes through the pipe 🤕. There is a lot of functions called here (36 to be precise) and most are prefixed with a name of the module that exposed them to us.
+
+ #. Interestingly there are only two names exposed by a module that do not refer to a function. Try to find them!
+| Note
+ *We are still working on this content.*
+
+ Stay tuned {Icon|name=radio}
=
=
=Let's take a deeper look at how we give attributes to an SVG element.
Commits: 1
Make the Circle example take a radius parameter
It changes the distance of the dots from the center of the circle.
Old parameter radius (controlling the visibility of the lines connecting the dots and center) is renamed to radi.
Also edit day 2.
index 04f4e0a..af026fb 100644
--- a/content/day-2.txt
+++ b/content/day-2.txt
@@ -22,18 +22,17 @@ Previously we have created a program that displays one dot in the center of the
= circle = 0 1
= protractor = False
= center = none
- radius = 0 1
+ radi = 0 1
+ radius = 80
= scatter = False
=
=| Header
= Multiple Dots
=
=| Note
- TODO: Describe going from one to many dots. Most of the content is in Day 3, so just move it here and redact.
+ See the FIXME comment in {Code|Main.elm} on {Code|wip-dots-example} branch.
=
- See the FIXME comment in {Code|Main.elm} on {Code|wip-dots-example}.
-
-First obvious difference is that now we have multiple dots. The only dot we have is created using {Code|Svg.circle} element on line 9 - 15, this code block:
+First obvious difference is that now we have multiple dots. The only dot we have is created using {Code|Svg.circle} function on line 9 - 15, this code block:
=
=| Monospace
= Svg.circle
@@ -44,7 +43,7 @@ First obvious difference is that now we have multiple dots. The only dot we have
= ]
= []
=
-If you look closely in your source code, the block above is surrounded by {Code|[} and {Code|]} characters. That's a list. We will talk about lists during the next day. For now it's enough to say that a list can contain zero, one or more items of the same type. In this case it contains one item of type `Svg msg` - our lonely, blue dot. Let's add a second one like this:
+If you look closely in your source code, the block above is surrounded by {Code|[} and {Code|]} characters. That's a list. We will talk about lists during the next day. For now it's enough to say that a list can contain zero, one or more items of the same type. In this case it contains one item of type {Code|Svg msg} - our lonely, blue dot. Let's add a second one like this:
=
=| Code
= module Main exposing (main)
@@ -81,7 +80,7 @@ If you look closely in your source code, the block above is surrounded by {Code|
= , Element.height Element.fill
= ]
=
-The result should be:
+Now the list spans from lines 9 to 23 and contains two items: skyblue and orange dots. The result should be:
=
=| Monospace
= | Window
@@ -98,7 +97,7 @@ The result should be:
= color = orange
= radius = 10
=
-Oh oh. There we can see the orange dot, but where is the blue one? It's behind the orange. Recall the cartesian coordinates. If we want to see both dots, let's move one of them to a different place, like this:
+Oh oh! The list contains two dots, but we can only see the orange one on the screen. Where is the blue one? It's behind the orange. Recall how the cartesian coordinates work: if {Code|x} and {Code|y} of the center are the same, then the dots are in the same place. If we want to see both dots, let's move one of them to a different place, like this:
=
=| Code
= module Main exposing (main)
@@ -135,6 +134,23 @@ Oh oh. There we can see the orange dot, but where is the blue one? It's behind t
= , Element.height Element.fill
= ]
=
+Now you should see this:
+
+| Monospace
+ | Window
+ | Dots
+ | Dot
+ cx = 0
+ cy = 0
+ color = skyblue
+ radius = 10
+
+ | Dot
+ cx = 50
+ cy = 0
+ color = orange
+ radius = 10
+
=We can add more dots, by simply multiplying the code blocks inside the list. Each block must be separated from others with a {Code|,} (comma). Each time the {Code|cx} and {Code|cy} coordinates need to be different. We can also give dots different colors. Make five of them.
=
=| Header
@@ -147,7 +163,8 @@ We all have some intuition about a circle. You can tell if you see one and if yo
= circle = 1 0
= protractor = False
= center = none
- radius = 0 1
+ radi = 0 1
+ radius = 80
= scatter = False
=
=
@@ -158,38 +175,21 @@ But what is it that makes a circle what it is? First of all we have to realize t
= circle = 1 0
= protractor = False
= center = red
- radius = 0 1
+ radi = 0 1
+ radius = 80
= scatter = False
=
-We can say that four or more dots lay on a circle, if the distance to the center is the same for each of them, like this:
+We can say that dots lay on a circle, if the distance to the center is the same for each of them, like this:
=
=| Circle
= dots = 5
= circle = 3 3
= protractor = False
= center = red
- radius = 3 3
+ radi = 3 3
+ radius = 80
= scatter = False
=
-| Note
- Why more than three? One, two or three dots always lay on at least one circle, so it's not much of a challenge that way.
-
- One dot lay on any circle that crosses the place. You could draw very many circles like that (in fact as many as you want).
-
- Similar for two things. Not every circle will do, but still you could draw many different circles that they will lay on.
-
- For three things there will always be exactly one circle that they lay on (except if they lay in one line). So three is still not much of a challenge. You could place them however you like, and always say that they lay on a circle.
-
- Only when it's four or more things, we can seriously ask whether they lay on a circle or not.
-
- TODO: Consider if the above adds more value or confusion.
-
- TODO: An example showing multiple circles crossing one and two points
-
- TODO: An example showing that for three different points there will always be a circle that crosses them, see {Link|circumcircle|url=https://package.elm-lang.org/packages/ianmackenzie/elm-geometry/latest/Triangle2d#circumcircle}
-
-So, once again - we can say that several things (in our case dots) lay on a circle if the distance between each of them and the center of the circle is the same.
-
=| Note
= A mathematician would say:
=
@@ -206,8 +206,90 @@ We call this distance /a *radius* of a circle/. We already saw radius used as an
=
=The {Code|r} attribute stands for radius. Then it was basically dictating the size of the dot. The larger the radius the bigger the circle. A dot is just a filled circle!
=
+| Row
+ | Monospace
+ radius = 40
+
+ | Monospace
+ radius = 60
+
+ | Monospace
+ radius = 80
+
+| Row
+ | Monospace
+ TODO: Dots example
+ | Dots
+ | Container
+ viewBox = -100 -100 200 200
+ background = none
+ fill = True
+
+ | Dot
+ radius = 40
+ cx = 0
+ cy = 0
+ color = skyblue
+
+ | Monospace
+ TODO: Dots example
+ | Dots
+ | Container
+ viewBox = -100 -100 200 200
+ background = none
+ fill = True
+
+ | Dot
+ radius = 60
+ cx = 0
+ cy = 0
+ color = skyblue
+
+ | Monospace
+ TODO: Dots example
+ | Dots
+ | Container
+ viewBox = -100 -100 200 200
+ background = none
+ fill = True
+
+ | Dot
+ radius = 80
+ cx = 0
+ cy = 0
+ color = skyblue
+
=This time the radius will affect the size of an imaginary circle on which the dots are laying.
=
+| Row
+ | Circle
+ dots = 5
+ circle = 3 3
+ protractor = False
+ center = none
+ radi = 0 1
+ radius = 40
+ scatter = False
+
+ | Circle
+ dots = 5
+ circle = 3 3
+ protractor = False
+ center = none
+ radi = 0 1
+ radius = 60
+ scatter = False
+
+
+ | Circle
+ dots = 5
+ circle = 3 3
+ protractor = False
+ center = none
+ radi = 0 1
+ radius = 80
+ scatter = False
+
=Ok, so this describes what it means to lay on a circle. But what are some efficient methods of putting things there? How about the following.
=
=You choose a center (anywhere, say {Code|(0, 0)}) and radius (any length you like, say 80).
@@ -229,7 +311,8 @@ Now, notice that the dots displayed by the program we are creating are not layin
= circle = 0 1
= protractor = False
= center = none
- radius = 0 1
+ radi = 0 1
+ radius = 80
= scatter = True
=
= | Circle
@@ -237,7 +320,8 @@ Now, notice that the dots displayed by the program we are creating are not layin
= circle = 0 1
= protractor = False
= center = none
- radius = 0 1
+ radi = 0 1
+ radius = 80
= scatter = False
=
=On the right, you immediately see a patern. They form a circle. But in fact both pictures are showing 5 dots laying on a circle. Look:
@@ -248,7 +332,8 @@ On the right, you immediately see a patern. They form a circle. But in fact both
= circle = 3
= protractor = False
= center = none
- radius = 0 1
+ radi = 0 1
+ radius = 80
= scatter = True
=
= | Circle
@@ -256,7 +341,8 @@ On the right, you immediately see a patern. They form a circle. But in fact both
= circle = 3
= protractor = False
= center = none
- radius = 0 1
+ radi = 0 1
+ radius = 80
= scatter = False
=
=
@@ -319,7 +405,8 @@ Turns out that the result is 72. That's the number of degrees we have to turn ou
= circle = 3 3
= protractor = True
= center = red
- radius = 3 3
+ radi = 3 3
+ radius = 80
= scatter = False
=
=| Header
@@ -333,7 +420,8 @@ Now that we understand what it means to be placed on a circle and evenly distrib
= circle = 0 1
= protractor = False
= center = none
- radius = 0 1
+ radi = 0 1
+ radius = 80
= scatter = False
=
=We will start from where we left off yesterday. Open {Code|src//Main.elm} in your editor. It should have the following code:
@@ -399,7 +487,8 @@ and {Link|open the program in your browser|url=http://localhost/src/Main.elm}. Y
= circle = 0 1
= protractor = False
= center = none
- radius = 0 1
+ radi = 0 1
+ radius = 80
= scatter = False
=
=As you can see the dot is no longer in the center - it has moved!
@@ -553,7 +642,8 @@ In the browser it should look exactly as we planned:
= circle = 0 1
= protractor = False
= center = none
- radius = 0 1
+ radi = 0 1
+ radius = 80
= scatter = False
=
=| Emphasizeindex c5d3b90..6e99a6f 100644
--- a/src/Examples/Circle.elm
+++ b/src/Examples/Circle.elm
@@ -18,17 +18,28 @@ type alias Config =
= { dots : Int -- Number of dots
= , circle : String -- Dash array
= , center : String -- Color
- , radius : String -- Dash array
+ , radius : Float -- The radius of the big circle
+ , radi : String -- Dash array
= , protractor : Bool -- Display protractor?
= , scatter : Bool -- Are doot evenly distributed or scattered
= }
=
=
+defaults : Config
+defaults =
+ { dots = 5
+ , circle = "0 1"
+ , center = "none"
+ , radius = 80
+ , radi = "0 1"
+ , protractor = True
+ , scatter = False
+ }
+
+
=
={- TODO:
= - Display radius (text)
- - Display angles of turns (in degrees)
- - Make an arrow head at the end point of the arc representing a turn (see https://package.elm-lang.org/packages/ianmackenzie/elm-geometry/latest/Arc2d#tangentDirection)
=-}
=
=
@@ -69,7 +80,10 @@ ui config =
= protractor =
= case config.protractor of
= True ->
- Protractor.protractor { radius = 60, strokeWidth = 0.5 }
+ Protractor.protractor
+ { radius = config.radius * 0.6
+ , strokeWidth = 0.5
+ }
=
= False ->
= Svg.g
@@ -105,7 +119,7 @@ ui config =
=
= circle =
= Svg.circle
- [ Svg.Attributes.r "80"
+ [ Svg.Attributes.r <| String.fromFloat config.radius
= , Svg.Attributes.stroke "silver"
= , Svg.Attributes.strokeWidth "1"
= , Svg.Attributes.strokeDasharray config.circle
@@ -143,7 +157,7 @@ ui config =
= , Svg.Attributes.transform <|
= Transformations.apply
= [ Rotate angle
- , Translate 80 0
+ , Translate config.radius 0
= ]
= ]
= []
@@ -159,10 +173,10 @@ ui config =
= Svg.line
= [ Svg.Attributes.x1 "0"
= , Svg.Attributes.y1 "0"
- , Svg.Attributes.x2 "80"
+ , Svg.Attributes.x2 <| String.fromFloat config.radius
= , Svg.Attributes.y2 "0"
= , Svg.Attributes.stroke "pink"
- , Svg.Attributes.strokeDasharray config.radius
+ , Svg.Attributes.strokeDasharray config.radi
= , Svg.Attributes.transform <|
= Transformations.apply
= [ Rotate angle
@@ -175,14 +189,3 @@ ui config =
= [ Svg.Attributes.viewBox "-100 -100 200 200"
= ]
= |> Element.html
-
-
-defaults : Config
-defaults =
- { dots = 5
- , circle = "0 1"
- , center = "none"
- , radius = "0 1"
- , protractor = True
- , scatter = False
- }index a02ca19..b92c418 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -442,24 +442,33 @@ document =
= }
= )
= Mark.Custom.title
- (Mark.manyOf
- ([ Mark.Custom.header
- , Mark.Custom.paragraph
- , Mark.Custom.monospace
- , Mark.Custom.code
- , Mark.Custom.terminal
- , Mark.Custom.note
- , Mark.Custom.emphasize
- , Mark.Custom.list
- , Mark.Custom.image
- ]
- ++ examples
- ++ [ Mark.Custom.window (Mark.oneOf (Mark.Custom.paragraph :: examples))
- , Mark.Custom.row (Mark.manyOf examples)
- ]
- )
+ ([ typography, widgets, examples, special ]
+ |> List.concat
+ |> Mark.manyOf
= )
=
+ typography =
+ [ Mark.Custom.header
+ , Mark.Custom.paragraph
+ , Mark.Custom.monospace
+ , Mark.Custom.emphasize
+ , Mark.Custom.list
+ , Mark.Custom.image
+ ]
+
+ widgets =
+ [ Mark.Custom.code
+ , Mark.Custom.terminal
+ , Mark.Custom.note
+ ]
+
+ special =
+ [ Mark.Custom.window <|
+ Mark.oneOf (typography ++ examples ++ widgets)
+ , Mark.Custom.row <|
+ Mark.manyOf (typography ++ examples ++ widgets)
+ ]
+
= examples =
= [ counter
= , dot
@@ -568,12 +577,13 @@ document =
= render config model =
= Examples.Circle.ui config
= in
- Mark.record6 "Circle"
+ Mark.record7 "Circle"
= Examples.Circle.Config
= (Mark.field "dots" Mark.int)
= (Mark.field "circle" Mark.string)
= (Mark.field "center" Mark.string)
- (Mark.field "radius" Mark.string)
+ (Mark.field "radius" Mark.float)
+ (Mark.field "radi" Mark.string)
= (Mark.field "protractor" Mark.bool)
= (Mark.field "scatter" Mark.bool)
= |> Mark.map render
Commits: 12
Make sure the title always displays Software Garden
One problem is that it's repeated twice on index document.
index 64059a0..2e6df38 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -151,7 +151,7 @@ view model =
= "Malformed response body: " ++ message
= )
= |> Element.text
- |> View "Software Garden : Error fetching content"
+ |> View "Error fetching content"
=
= ParseError deadEnds ->
= deadEndsView deadEnds
@@ -161,7 +161,7 @@ view model =
=
= loadingView : View
= loadingView =
- { title = "Software Garden"
+ { title = "Loading Content"
= , body =
= "Loading content..."
= |> Element.text
@@ -182,7 +182,7 @@ view model =
= , Element.padding 40
= , Element.spacing 20
= ]
- |> View "Software Garden : Parsing Errors"
+ |> View "Parsing Errors"
=
= deadEndElement : DeadEnd -> Element Msg
= deadEndElement { row, col, problem, contextStack } =
@@ -296,7 +296,7 @@ view model =
= ++ String.fromInt col
= )
= in
- { title = content.title
+ { title = "Software Garden : " ++ content.title
= , body =
= [ Element.layout
= [ -- Below is a hack that enables static site generationWIP: Create a general purpose Dots example
Requirements:
- Specify any viewbox
- Specify any background
- Specify any number of dots, each having it's own
- cx
- cy
- radius
- color
See FIXME comment in Main.elm.
index 5cd1456..2068cdf 100644
--- a/content/day-2.txt
+++ b/content/day-2.txt
@@ -26,6 +26,17 @@ Previously we have created a program that displays five colorful dots. Today we
= radius = 0 1
= scatter = False
=
+| Dots
+ | Container
+ background = brown
+ viewBox = -200 -200 400 400
+
+ | Dot
+ cx = 10
+ cy = 20
+ color = blue
+ radius = 33
+
=| Header
= What does it mean to be placed on a circle?
=new file mode 100644
index 0000000..03cc7bd
--- /dev/null
+++ b/src/Examples/Dots.elm
@@ -0,0 +1,79 @@
+module Examples.Dots exposing
+ ( Config
+ , Container
+ , Dot
+ , main
+ , ui
+ )
+
+import Circle2d
+import Element exposing (Element)
+import Geometry.Svg
+import Html exposing (Html)
+import Point2d exposing (Point2d)
+import Svg exposing (Attribute, Svg)
+import Svg.Attributes
+
+
+type alias Config =
+ { container : Container
+ , dots : List Dot
+ }
+
+
+type alias Container =
+ { background : String
+ , viewBox : String
+ }
+
+
+type alias Dot =
+ { cx : Float
+ , cy : Float
+ , radius : Float
+ , color : String
+ }
+
+
+defaults : Config
+defaults =
+ { container =
+ { background = "none"
+ , viewBox = "-300 -300 600 600"
+ }
+ , dots =
+ [ { cx = 0
+ , cy = 0
+ , radius = 10
+ , color = "skyblue"
+ }
+ ]
+ }
+
+
+main : Html msg
+main =
+ ui defaults
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+
+ui : Config -> Element msg
+ui { container, dots } =
+ let
+ dot : Dot -> Svg msg
+ dot { cx, cy, color, radius } =
+ ( cx, cy )
+ |> Point2d.fromCoordinates
+ |> Circle2d.withRadius radius
+ |> Geometry.Svg.circle2d [ Svg.Attributes.fill color ]
+ in
+ dots
+ |> List.map dot
+ |> Svg.svg
+ [ Svg.Attributes.viewBox container.viewBox
+ , Svg.Attributes.style <| "background: " ++ container.background
+ ]
+ |> Element.htmlindex 2e6df38..006af8a 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -22,12 +22,10 @@ import Examples
=import Examples.CartesianCoordinates
=import Examples.Circle
=import Examples.Counter
-import Examples.Dot as Dot
-import Examples.DotInElement as DotInElement
-import Examples.DotWithViewBox as DotWithViewBox
+import Examples.DotInElement
+import Examples.Dots
=import Examples.Gradient
=import Examples.Line
-import Examples.MultipleDots as MultipleDots
=import Examples.NestedTransformations
=import Examples.PolarCoordinates
=import Examples.RosetteTypedTransformations
@@ -194,13 +192,13 @@ view model =
= ++ String.fromInt level
=
= Mark.InlineStart ->
- "Inline start"
+ "Expecting inline start"
=
= Mark.InlineEnd ->
- "Inline end"
+ "Expecting inline end"
=
= Mark.BlockStart ->
- "Block start"
+ "Expecting block start"
=
= Mark.Expecting what ->
= "Expecting " ++ what
@@ -232,22 +230,22 @@ view model =
= "Escape"
=
= Mark.EscapedChar ->
- "Escaped character"
+ "Escaped a character"
=
= Mark.Newline ->
- "Expecting newline"
+ "Expecting a newline"
=
= Mark.Space ->
- "Space"
+ "Expecting a space"
=
= Mark.End ->
- "End"
+ "Expecting an end"
=
= Mark.Integer ->
- "Integer"
+ "Expecting an integer"
=
= Mark.FloatingPoint ->
- "Floating point"
+ "ExpectingIndent a floating point number"
=
= Mark.InvalidNumber ->
= "Invalid number"
@@ -461,10 +459,8 @@ document =
=
= examples =
= [ counter
- , dot
= , dotInElement
- , dotWithViewBox
- , twoDots
+ , dots
= , circle
= , line
= , gradient
@@ -494,80 +490,75 @@ document =
= in
= Mark.stub "Counter" render
=
- dot : Mark.Block (Examples.Model -> Element Msg)
- dot =
- let
- render : Dot.Config -> Examples.Model -> Element Msg
- render config model =
- Dot.ui config
- |> Element.html
- |> Element.el
- [ Element.width (Element.px 300)
- , Element.height (Element.px 150)
- ]
- |> Element.el
- [ Element.padding 5 ]
- in
- Mark.record2 "Dot"
- Dot.Config
- (Mark.field "background" Mark.string)
- (Mark.field "fill" Mark.string)
- |> Mark.map render
-
= dotInElement : Mark.Block (Examples.Model -> Element Msg)
= dotInElement =
= let
= render config model =
- DotInElement.ui config
+ Examples.DotInElement.ui config
= in
= Mark.record4 "DotInElement"
- DotInElement.Config
+ Examples.DotInElement.Config
= (Mark.field "background" Mark.string)
= (Mark.field "fill" Mark.string)
= (Mark.field "cx" Mark.string)
= (Mark.field "cy" Mark.string)
= |> Mark.map render
=
- dotWithViewBox : Mark.Block (Examples.Model -> Element Msg)
- dotWithViewBox =
- let
- render config model =
- DotWithViewBox.ui config
- in
- Mark.record3 "DotWithViewBox"
- DotWithViewBox.Config
- (Mark.field "background" Mark.string)
- (Mark.field "fill" Mark.string)
- (Mark.field "viewBox" Mark.string)
- |> Mark.map render
+ dots : Mark.Block (Examples.Model -> Element Msg)
+ dots =
+ {- FIXME: This produces parsing errors
+
+ Markup:
+
+ | Dots
+ | Container
+ background = brown
+ viewBox = -200 -200 400 400
+
+ | Dot
+ cx = 10
+ cy = 20
+ color = blue
+ radius = 33
+
+ Errors:
+ End at 36:1
+ Expecting newline at 36:1
+ Block start at 36:1
=
- twoDots : Mark.Block (Examples.Model -> Element Msg)
- twoDots =
+ TODO: Create a minimal reproducible example on Ellie and report issue at github.com/mdgriffith/elm-markup
+ -}
= let
+ render :
+ Examples.Dots.Config
+ -> Examples.Model
+ -> Element Msg
= render config model =
- MultipleDots.ui config
+ Examples.Dots.ui config
+
+ container : Mark.Block Examples.Dots.Container
+ container =
+ Mark.record2 "Container"
+ Examples.Dots.Container
+ (Mark.field "background" Mark.string)
+ (Mark.field "viewBox" Mark.string)
+
+ dot : Mark.Block Examples.Dots.Dot
+ dot =
+ Mark.record4 "Dot"
+ Examples.Dots.Dot
+ (Mark.field "cx" Mark.float)
+ (Mark.field "cy" Mark.float)
+ (Mark.field "radius" Mark.float)
+ (Mark.field "color" Mark.string)
= in
- Mark.record6 "TwoDots"
- (\background viewBoxConf firstFill secondFill firstCX secondCX ->
- MultipleDots.Config background
- viewBoxConf
- [ [ Svg.Attributes.r "10"
- , Svg.Attributes.fill firstFill
- , Svg.Attributes.cx firstCX
- ]
- , [ Svg.Attributes.r "10"
- , Svg.Attributes.fill secondFill
- , Svg.Attributes.cx secondCX
- ]
- ]
+ Mark.block "Dots"
+ render
+ (Mark.startWith
+ Examples.Dots.Config
+ container
+ (Mark.manyOf [ dot ])
= )
- (Mark.field "background" Mark.string)
- (Mark.field "viewBox" Mark.string)
- (Mark.field "firstFill" Mark.string)
- (Mark.field "secondFill" Mark.string)
- (Mark.field "firstCX" Mark.string)
- (Mark.field "secondCX" Mark.string)
- |> Mark.map render
=
= circle : Mark.Block (Examples.Model -> Element Msg)
= circle =Create Protractor example and display protractor in Circle example
My first real useful code 🥰
Co-Authored-By: Sam Phillips samuel.rodney.phillips@gmail.com
index 5cd1456..6ffd9ca 100644
--- a/content/day-2.txt
+++ b/content/day-2.txt
@@ -21,7 +21,7 @@ Previously we have created a program that displays five colorful dots. Today we
= | Circle
= dots = 5
= circle = 0 1
- angles = 0 1
+ protractor = False
= center = none
= radius = 0 1
= scatter = False
@@ -34,7 +34,7 @@ We all have some intuition about a circle. You can tell if you see one and if yo
=| Circle
= dots = 0
= circle = 1 0
- angles = 0 1
+ protractor = False
= center = none
= radius = 0 1
= scatter = False
@@ -45,7 +45,7 @@ But what is it that makes a circle what it is? First of all we have to realize t
=| Circle
= dots = 0
= circle = 1 0
- angles = 0 1
+ protractor = False
= center = red
= radius = 0 1
= scatter = False
@@ -55,7 +55,7 @@ We can say that four or more dots lay on a circle, if the distance to the center
=| Circle
= dots = 5
= circle = 3 3
- angles = 0 1
+ protractor = False
= center = red
= radius = 3 3
= scatter = False
@@ -119,7 +119,7 @@ Now, notice that the dots displayed by the program we are creating are not layin
= | Circle
= dots = 5
= circle = 0 1
- angles = 0 1
+ protractor = False
= center = none
= radius = 0 1
= scatter = True
@@ -127,7 +127,7 @@ Now, notice that the dots displayed by the program we are creating are not layin
= | Circle
= dots = 5
= circle = 0 1
- angles = 0 1
+ protractor = False
= center = none
= radius = 0 1
= scatter = False
@@ -138,7 +138,7 @@ On the right, you immediately see a patern. They form a circle. But in fact both
= | Circle
= dots = 5
= circle = 3
- angles = 0 1
+ protractor = False
= center = none
= radius = 0 1
= scatter = True
@@ -146,7 +146,7 @@ On the right, you immediately see a patern. They form a circle. But in fact both
= | Circle
= dots = 5
= circle = 3
- angles = 0 1
+ protractor = False
= center = none
= radius = 0 1
= scatter = False
@@ -168,9 +168,9 @@ A common way of representing rotation is in degrees. It works like this: imagine
=
=In physical world we often use a tool called protractor to measure angles. It typically has one degree segments already marked for you. It looks like this:
=
-| Image
- src = /assets/protractor.svg
- description = A protractor by Georges Khaznadar <georgesk@ofset.org>
+| Protractor
+ radius = 400
+ strokeWidth = 2
=
=| Note
= TODO: Modify the image to make marks 0 - 360. We can convert it to Elm code (using https:////level.app//svg-to-elm) and then reuse it in our examples.
@@ -206,6 +206,14 @@ Type {Code|:exit} to close the REPL.
=
=Turns out that the result is 72. That's the number of degrees we have to turn our ruler around the center each time we place a new dot. If you have a protractor like the one pictured above, you can use it to measure the turn. Then measure the distance from the center along the ruler (a length called radius) and place the dot there.
=
+| Circle
+ dots = 5
+ circle = 3 3
+ protractor = True
+ center = red
+ radius = 3 3
+ scatter = False
+
=| Header
= The Code
=
@@ -215,7 +223,7 @@ Now that we understand what it means to be placed on a circle and evenly distrib
= | Circle
= dots = 5
= circle = 0 1
- angles = 0 1
+ protractor = False
= center = none
= radius = 0 1
= scatter = False
@@ -281,7 +289,7 @@ and {Link|open the program in your browser|url=http://localhost/src/Main.elm}. Y
= | Circle
= dots = 1
= circle = 0 1
- angles = 0 1
+ protractor = False
= center = none
= radius = 0 1
= scatter = False
@@ -435,7 +443,7 @@ In the browser it should look exactly as we planned:
= | Circle
= dots = 5
= circle = 0 1
- angles = 0 1
+ protractor = False
= center = none
= radius = 0 1
= scatter = Falseindex 35baf6c..eccb7d7 100644
--- a/src/Examples/Circle.elm
+++ b/src/Examples/Circle.elm
@@ -8,6 +8,7 @@ import Html exposing (Html)
=import List.Extra as List
=import Maybe.Extra as Maybe
=import Point2d
+import Protractor
=import Svg exposing (Svg)
=import Svg.Attributes
=import Transformations exposing (Transformation(..))
@@ -18,7 +19,7 @@ type alias Config =
= , circle : String -- Dash array
= , center : String -- Color
= , radius : String -- Dash array
- , angles : String -- Dash array
+ , protractor : Bool -- Display protractor?
= , scatter : Bool -- Are doot evenly distributed or scattered
= }
=
@@ -57,17 +58,19 @@ ui config =
= [ dots
= , center
= , circle
- , compass
= , radi
+ , protractor
= ]
=
- compass : Svg msg
- compass =
- 0
- :: angles
- |> pairs
- |> List.indexedMap arc
- |> Svg.g []
+ protractor =
+ case config.protractor of
+ True ->
+ Protractor.protractor { radius = 60, strokeWidth = 0.5 }
+
+ False ->
+ Svg.g
+ []
+ []
=
= pairs : List a -> List ( a, a )
= pairs list =
@@ -81,20 +84,6 @@ ui config =
= one :: two :: rest ->
= ( one, two ) :: pairs (two :: rest)
=
- arc : Int -> ( Float, Float ) -> Svg msg
- arc index ( start, end ) =
- Arc2d.with
- { centerPoint = Point2d.origin
- , radius = 30
- , startAngle = degrees start
- , sweptAngle = degrees (end - start)
- }
- |> Geometry.Svg.arc2d
- [ Svg.Attributes.stroke (color index)
- , Svg.Attributes.strokeDasharray config.angles
- , Svg.Attributes.fill "none"
- ]
-
= dots =
= angles
= |> List.indexedMap
@@ -190,6 +179,6 @@ defaults =
= , circle = "0 1"
= , center = "none"
= , radius = "0 1"
- , angles = "0 1"
+ , protractor = True
= , scatter = False
= }new file mode 100644
index 0000000..25b3173
--- /dev/null
+++ b/src/Examples/Protractor.elm
@@ -0,0 +1,41 @@
+module Examples.Protractor exposing (Config, defaults, main, ui)
+
+import Element exposing (Element)
+import Html exposing (Html)
+import Protractor
+import Svg exposing (Svg)
+import Svg.Attributes exposing (fill, stroke, viewBox)
+
+
+type alias Config =
+ { radius : Float
+ , strokeWidth : Float
+ }
+
+
+defaults : Config
+defaults =
+ { radius = 500
+ , strokeWidth = 1
+ }
+
+
+main : Html msg
+main =
+ ui defaults
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+
+ui : Config -> Element msg
+ui config =
+ Protractor.protractor config
+ |> List.singleton
+ |> Svg.svg
+ [ Svg.Attributes.viewBox "-500 -500 1000 1000"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.height "100%"
+ ]
+ |> Element.htmlindex 2e7510a..0905bcd 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -30,6 +30,7 @@ import Examples.Line
=import Examples.MultipleDots as MultipleDots
=import Examples.NestedTransformations
=import Examples.PolarCoordinates
+import Examples.Protractor
=import Examples.RosetteTypedTransformations
=import Examples.Spiral
=import Examples.Transformations
@@ -465,6 +466,7 @@ document =
= , dotInElement
= , dotWithViewBox
= , twoDots
+ , protractor
= , circle
= , line
= , gradient
@@ -479,6 +481,25 @@ document =
= ]
=
= -- Embeded programs' blocks
+ protractor : Mark.Block (Examples.Model -> Element Msg)
+ protractor =
+ let
+ render : Examples.Protractor.Config -> Examples.Model -> Element Msg
+ render config model =
+ Examples.Protractor.ui config
+ |> Element.el
+ [ Element.centerX
+ , Element.centerY
+ ]
+ |> Element.map Examples.CounterMsg
+ |> Element.map ExamplesMsg
+ in
+ Mark.record2 "Protractor"
+ Examples.Protractor.Config
+ (Mark.field "radius" Mark.float)
+ (Mark.field "strokeWidth" Mark.float)
+ |> Mark.map render
+
= counter : Mark.Block (Examples.Model -> Element Msg)
= counter =
= let
@@ -582,7 +603,7 @@ document =
= (Mark.field "circle" Mark.string)
= (Mark.field "center" Mark.string)
= (Mark.field "radius" Mark.string)
- (Mark.field "angles" Mark.string)
+ (Mark.field "protractor" Mark.bool)
= (Mark.field "scatter" Mark.bool)
= |> Mark.map render
=new file mode 100644
index 0000000..f990e26
--- /dev/null
+++ b/src/Protractor.elm
@@ -0,0 +1,92 @@
+module Protractor exposing (protractor)
+
+import Axis2d exposing (Axis2d)
+import Circle2d
+import Element exposing (fill)
+import Geometry.Svg
+import LineSegment2d exposing (LineSegment2d)
+import Point2d
+import Svg exposing (Svg)
+import Svg.Attributes exposing (fill, stroke, viewBox)
+
+
+type alias Config =
+ { radius : Float
+ , strokeWidth : Float
+ }
+
+
+protractor : Config -> Svg msg
+protractor config =
+ let
+ mark : Float -> Svg msg
+ mark direction =
+ if remainderBy 10 (floor direction) == 0 then
+ LineSegment2d.along (axis direction) (config.radius * 0.94) config.radius
+ |> Geometry.Svg.lineSegment2d strokeAttributes
+
+ else if remainderBy 5 (floor direction) == 0 then
+ LineSegment2d.along (axis direction) (config.radius * 0.96) config.radius
+ |> Geometry.Svg.lineSegment2d strokeAttributes
+
+ else
+ LineSegment2d.along (axis direction) (config.radius * 0.98) config.radius
+ |> Geometry.Svg.lineSegment2d strokeAttributes
+
+ axis : Float -> Axis2d
+ axis direction =
+ Axis2d.x
+ |> Axis2d.rotateAround Point2d.origin (degrees direction)
+
+ marks : List (Svg msg)
+ marks =
+ List.range 0 359
+ |> List.map toFloat
+ |> List.map mark
+
+ edge : Svg msg
+ edge =
+ Circle2d.withRadius config.radius Point2d.origin
+ |> Geometry.Svg.circle2d
+ [ Svg.Attributes.stroke "black"
+ , Svg.Attributes.fill "none"
+ ]
+
+ labels : List (Svg msg)
+ labels =
+ List.range 0 35
+ |> List.map ((*) 10)
+ |> List.map String.fromInt
+ |> List.map label
+
+ label : String -> Svg msg
+ label direction =
+ Svg.text_
+ [ Svg.Attributes.x "0"
+ , Svg.Attributes.y "0"
+ , Svg.Attributes.fill "black"
+ , Svg.Attributes.transform ("rotate (" ++ direction ++ ") translate (" ++ String.fromFloat (config.radius * 0.89) ++ ")")
+ , Svg.Attributes.fontSize <| String.fromFloat <| config.radius * 0.05
+ , Svg.Attributes.dominantBaseline "central"
+ , Svg.Attributes.textAnchor "middle"
+ ]
+ [ Svg.text direction ]
+
+ axes : List (Svg msg)
+ axes =
+ [ LineSegment2d.along Axis2d.x (config.radius * 0.84 * -1) (config.radius * 0.84)
+ , LineSegment2d.along Axis2d.y (config.radius * 0.84 * -1) (config.radius * 0.84)
+ ]
+ |> List.map (Geometry.Svg.lineSegment2d strokeAttributes)
+
+ strokeAttributes =
+ [ stroke "gray"
+ , Svg.Attributes.strokeWidth (String.fromFloat config.strokeWidth)
+ ]
+ in
+ edge
+ :: marks
+ |> List.append labels
+ |> List.append axes
+ |> Svg.g
+ []Make Dots example work, but without configurable continer
You can put one or many dots, but the viewbox and background are fixed. See the FIXME comment for more details.
index 03cc7bd..5944489 100644
--- a/src/Examples/Dots.elm
+++ b/src/Examples/Dots.elm
@@ -75,5 +75,7 @@ ui { container, dots } =
= |> Svg.svg
= [ Svg.Attributes.viewBox container.viewBox
= , Svg.Attributes.style <| "background: " ++ container.background
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.height "100%"
= ]
= |> Element.htmlindex 006af8a..eae8b16 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -506,28 +506,6 @@ document =
=
= dots : Mark.Block (Examples.Model -> Element Msg)
= dots =
- {- FIXME: This produces parsing errors
-
- Markup:
-
- | Dots
- | Container
- background = brown
- viewBox = -200 -200 400 400
-
- | Dot
- cx = 10
- cy = 20
- color = blue
- radius = 33
-
- Errors:
- End at 36:1
- Expecting newline at 36:1
- Block start at 36:1
-
- TODO: Create a minimal reproducible example on Ellie and report issue at github.com/mdgriffith/elm-markup
- -}
= let
= render :
= Examples.Dots.Config
@@ -552,13 +530,48 @@ document =
= (Mark.field "radius" Mark.float)
= (Mark.field "color" Mark.string)
= in
+ {- FIXME: This should really be:
+
+ Mark.block "Dots"
+ render
+ (Mark.startWith
+ Examples.Dots.Config
+ container
+ (Mark.manyOf [ dot ])
+ )
+
+ and then markup:
+
+ | Dots
+ | Container
+ background = brown
+ viewBox = -200 -200 400 400
+
+ | Dot
+ cx = 10
+ cy = 20
+ color = blue
+ radius = 33
+
+ | Dot
+ ...
+
+ but it produces parsing errors:
+
+ End at 36:1
+ Expecting newline at 36:1
+ Block start at 36:1
+
+ TODO: Create a minimal reproducible example on Ellie and report issue at github.com/mdgriffith/elm-markup
+ -}
= Mark.block "Dots"
- render
- (Mark.startWith
- Examples.Dots.Config
- container
- (Mark.manyOf [ dot ])
+ (Examples.Dots.Config
+ { background = "none"
+ , viewBox = "-300 -300 600 600"
+ }
+ >> render
= )
+ (Mark.manyOf [ dot ])
=
= circle : Mark.Block (Examples.Model -> Element Msg)
= circle =Update markup to work with previously commit changes
See the message of the parent commit.
index 2068cdf..c7385a8 100644
--- a/content/day-2.txt
+++ b/content/day-2.txt
@@ -26,16 +26,26 @@ Previously we have created a program that displays five colorful dots. Today we
= radius = 0 1
= scatter = False
=
-| Dots
- | Container
- background = brown
- viewBox = -200 -200 400 400
-
- | Dot
- cx = 10
- cy = 20
- color = blue
- radius = 33
+
+| Window
+ | Dots
+ | Dot
+ cx = 0
+ cy = 0
+ color = skyblue
+ radius = 10
+
+ | Dot
+ cx = -50
+ cy = 0
+ color = pink
+ radius = 10
+
+ | Dot
+ cx = 50
+ cy = 0
+ color = lime
+ radius = 10
=
=| Header
= What does it mean to be placed on a circle?Remove two dots example
It doesnt't compile and I'm working on a more versatile Dots example.
index 5cd1456..4a1800a 100644
--- a/content/day-2.txt
+++ b/content/day-2.txt
@@ -4,7 +4,6 @@
=| Emphasize
= Let's Place the Dots in a Circle
=
-
=| Note
= Today we are going to lear about
=
@@ -15,7 +14,7 @@
=| Header
= The Problem
=
-Previously we have created a program that displays five colorful dots. Today we want to place our five dots in a circle, like this:
+Previously we have created a program that displays one dot in the center of the screen. Today we want to place five dots in a circle, like this:
=
=| Window
= | Circle
@@ -26,6 +25,12 @@ Previously we have created a program that displays five colorful dots. Today we
= radius = 0 1
= scatter = False
=
+| Header
+ Multiple Dots
+
+| Note
+ TODO: Describe going from one to many dots. Most of the content is in Day 3, so just move it here and redact.
+
=| Header
= What does it mean to be placed on a circle?
=index 2e6df38..9104b65 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -464,7 +464,6 @@ document =
= , dot
= , dotInElement
= , dotWithViewBox
- , twoDots
= , circle
= , line
= , gradient
@@ -541,34 +540,6 @@ document =
= (Mark.field "viewBox" Mark.string)
= |> Mark.map render
=
- twoDots : Mark.Block (Examples.Model -> Element Msg)
- twoDots =
- let
- render config model =
- MultipleDots.ui config
- in
- Mark.record6 "TwoDots"
- (\background viewBoxConf firstFill secondFill firstCX secondCX ->
- MultipleDots.Config background
- viewBoxConf
- [ [ Svg.Attributes.r "10"
- , Svg.Attributes.fill firstFill
- , Svg.Attributes.cx firstCX
- ]
- , [ Svg.Attributes.r "10"
- , Svg.Attributes.fill secondFill
- , Svg.Attributes.cx secondCX
- ]
- ]
- )
- (Mark.field "background" Mark.string)
- (Mark.field "viewBox" Mark.string)
- (Mark.field "firstFill" Mark.string)
- (Mark.field "secondFill" Mark.string)
- (Mark.field "firstCX" Mark.string)
- (Mark.field "secondCX" Mark.string)
- |> Mark.map render
-
= circle : Mark.Block (Examples.Model -> Element Msg)
= circle =
= letMerge branch 'fix-9-replace-p-with-div' into 'master'
Temporary fix for #9
See merge request software-garden/software-garden.gitlab.io!6
Merge branch 'protractor' into 'master'
Create Protractor example and display protractor in Circle example
See merge request software-garden/software-garden.gitlab.io!7
Merge remote-tracking branch 'origin/master' into content
Merge remote-tracking branch 'origin/master' into content
Deploy
index 4472935..8d347ad 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -13,5 +13,5 @@ pages:
= paths:
= - public
=
- only:
- - master
+ # only:
+ # - masterDraft transition from one to five dots on day 2
index a809f8b..04f4e0a 100644
--- a/content/day-2.txt
+++ b/content/day-2.txt
@@ -31,6 +31,112 @@ Previously we have created a program that displays one dot in the center of the
=| Note
= TODO: Describe going from one to many dots. Most of the content is in Day 3, so just move it here and redact.
=
+ See the FIXME comment in {Code|Main.elm} on {Code|wip-dots-example}.
+
+First obvious difference is that now we have multiple dots. The only dot we have is created using {Code|Svg.circle} element on line 9 - 15, this code block:
+
+| Monospace
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "skyblue"
+ ]
+ []
+
+If you look closely in your source code, the block above is surrounded by {Code|[} and {Code|]} characters. That's a list. We will talk about lists during the next day. For now it's enough to say that a list can contain zero, one or more items of the same type. In this case it contains one item of type `Svg msg` - our lonely, blue dot. Let's add a second one like this:
+
+| Code
+ module Main exposing (main)
+
+ import Element
+ import Svg
+ import Svg.Attributes
+
+
+ main =
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "skyblue"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "orange"
+ ]
+ []
+ ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.viewBox "-300 -300 600 600"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+The result should be:
+
+| Monospace
+ | Window
+ | Dots
+ | Dot
+ cx = 0
+ cy = 0
+ color = skyblue
+ radius = 10
+
+ | Dot
+ cx = 0
+ cy = 0
+ color = orange
+ radius = 10
+
+Oh oh. There we can see the orange dot, but where is the blue one? It's behind the orange. Recall the cartesian coordinates. If we want to see both dots, let's move one of them to a different place, like this:
+
+| Code
+ module Main exposing (main)
+
+ import Element
+ import Svg
+ import Svg.Attributes
+
+
+ main =
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "skyblue"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "50"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "orange"
+ ]
+ []
+ ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.viewBox "-300 -300 600 600"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+We can add more dots, by simply multiplying the code blocks inside the list. Each block must be separated from others with a {Code|,} (comma). Each time the {Code|cx} and {Code|cy} coordinates need to be different. We can also give dots different colors. Make five of them.
+
=| Header
= What does it mean to be placed on a circle?
=
@@ -82,13 +188,10 @@ We can say that four or more dots lay on a circle, if the distance to the center
=
= TODO: An example showing that for three different points there will always be a circle that crosses them, see {Link|circumcircle|url=https://package.elm-lang.org/packages/ianmackenzie/elm-geometry/latest/Triangle2d#circumcircle}
=
-
-Because we are all about challenges, let's make five dots. Also, we already have five dot's we created on {Link|Day 1|url=/day-1.html}, so why not reuse them?
-
=So, once again - we can say that several things (in our case dots) lay on a circle if the distance between each of them and the center of the circle is the same.
=
=| Note
- A matematician would say:
+ A mathematician would say:
=
= /Four or more points belong to a circle when there is a point (called center) that is equally distant from each of the points./
=
@@ -101,7 +204,7 @@ We call this distance /a *radius* of a circle/. We already saw radius used as an
= [ Svg.Attributes.r "10"]
= []
=
-The {Code|r} attribute stands for radius. Then it was basically dictating the size of the dot. The larger the radius the bigger the circle, and a dot is just a filled circle!
+The {Code|r} attribute stands for radius. Then it was basically dictating the size of the dot. The larger the radius the bigger the circle. A dot is just a filled circle!
=
=This time the radius will affect the size of an imaginary circle on which the dots are laying.
=
Commits: 8
Make CartesianCoordinates example parametrized
This required changes to CartesianPlane (graph and axes).
Also refactor code for other examples to use pipes and limit the width of layout (in some cases).
index 5fce82e..91b6b4e 100644
--- a/content/day-1.txt
+++ b/content/day-1.txt
@@ -254,6 +254,12 @@ Reload the browser to confirm your hypothesis. Voila! The SVG space now fills th
= cy=0
=
=It should now be possible to place our dot in the center of the screen. But, how exactly will we achieve it?
+| Row
+ | CartesianCoordinates
+ minX = -10
+ minY = -10
+ maxX = 300
+ maxY = 200
=
=Let's return to the idea we introduced earlier, that the center of the dot is at a point called the {Code|origin} or {Code|(0, 0)}. What exactly does this mean?
=
@@ -261,7 +267,6 @@ Let's return to the idea we introduced earlier, that the center of the dot is at
=
=Adjust the sliders below to change the x and y coordinates of the dot. It's like moving an object on a table by describing how far left, right, up or down it should be.
=
-| CartesianCoordinates
=
=Knowing that the center of the dot is at the {Code|origin} or point {Code|(0,0)} of our plain, we can see that by default the origin of an SVG space is at the upper left corner. If we want to help our dot get out of hiding, we'll have to place the center of the dot at a point with positive {Code|x} and {Code|y} values.
=index 6029a39..a162a84 100644
--- a/src/CartesianPlane.elm
+++ b/src/CartesianPlane.elm
@@ -11,24 +11,39 @@ import Triangle2d
=import Vector2d
=
=
-graph : Float -> List (Svg msg) -> Html msg
-graph size shapes =
+type alias Config =
+ { minX : Float
+ , minY : Float
+ , maxX : Float
+ , maxY : Float
+ }
+
+
+graph : Config -> List (Svg msg) -> Html msg
+graph config shapes =
= let
+ size =
+ Basics.max (config.maxX - config.minX) (config.maxY - config.minY)
+
= hairline =
- size / 1000
+ 20 / size
=
= fontsize =
- size / 200
+ 100 / size
=
= background =
= axes
= [ stroke "gray"
= , fill "gray"
= ]
- size
+ config
= in
= svg
- [ [ negate size / 2, negate size / 2, size, size ]
+ [ [ config.minX
+ , config.minY
+ , config.maxX - config.minX
+ , config.maxY - config.minY
+ ]
= |> List.map String.fromFloat
= |> String.join " "
= |> viewBox
@@ -40,22 +55,17 @@ graph size shapes =
= (background :: shapes)
=
=
-axes attributes size =
+axes : List (Attribute msg) -> Config -> Svg msg
+axes attributes config =
= let
- max =
- size / 2
-
- min =
- negate max
-
= xAxis =
- { start = Point2d.fromCoordinates ( min, 0 )
- , end = Point2d.fromCoordinates ( max, 0 )
+ { start = Point2d.fromCoordinates ( config.minX, 0 )
+ , end = Point2d.fromCoordinates ( config.maxX, 0 )
= }
=
= yAxis =
- { start = Point2d.fromCoordinates ( 0, min )
- , end = Point2d.fromCoordinates ( 0, max )
+ { start = Point2d.fromCoordinates ( 0, config.minY )
+ , end = Point2d.fromCoordinates ( 0, config.maxY )
= }
= in
= g []
@@ -67,9 +77,21 @@ axes attributes size =
= attributes
= yAxis.start
= yAxis.end
- , label [ fontSize "8", color "gray" ] (xAxis.end |> Point2d.translateIn (Direction2d.fromAngle (degrees -135)) 10) "x"
- , label [ fontSize "8", color "gray" ] (yAxis.end |> Point2d.translateIn (Direction2d.fromAngle (degrees -45)) 10) "y"
- , label [ fontSize "8", color "gray" ] (Point2d.origin |> Point2d.translateIn (Direction2d.fromAngle (degrees 135)) 10) "O"
+ , label [ fontSize "8", color "gray" ]
+ (xAxis.end
+ |> Point2d.translateIn (Direction2d.fromAngle (degrees -135)) 10
+ )
+ "x"
+ , label [ fontSize "8", color "gray" ]
+ (yAxis.end
+ |> Point2d.translateIn (Direction2d.fromAngle (degrees -45)) 10
+ )
+ "y"
+ , label [ fontSize "8", color "gray" ]
+ (Point2d.origin
+ |> Point2d.translateIn (Direction2d.fromAngle (degrees 135)) 10
+ )
+ "O"
= ]
=
=index 1c4266c..b8b1723 100644
--- a/src/Examples/CartesianCoordinates.elm
+++ b/src/Examples/CartesianCoordinates.elm
@@ -1,5 +1,6 @@
=module Examples.CartesianCoordinates exposing
- ( Model
+ ( Config
+ , Model
= , Msg
= , init
= , main
@@ -19,6 +20,23 @@ import Svg exposing (..)
=import Svg.Attributes exposing (..)
=
=
+type alias Config =
+ { minX : Float
+ , minY : Float
+ , maxX : Float
+ , maxY : Float
+ }
+
+
+defaults : Config
+defaults =
+ { minX = -150
+ , minY = -150
+ , maxX = 150
+ , maxY = 150
+ }
+
+
=main : Program () Model Msg
=main =
= Browser.sandbox
@@ -46,11 +64,16 @@ init =
=
=view : Model -> Html Msg
=view model =
- Element.layout
- [ Element.height Element.fill
- , Element.width Element.fill
- ]
- (ui model)
+ model
+ |> ui defaults
+ |> Element.el
+ [ Element.width (Element.maximum 600 Element.fill)
+ , Element.centerX
+ ]
+ |> Element.layout
+ [ Element.height Element.fill
+ , Element.width Element.fill
+ ]
=
=
=update : Msg -> Model -> Model
@@ -63,42 +86,48 @@ update msg model =
= { model | y = y }
=
=
-ui : Model -> Element Msg
-ui model =
+ui : Config -> Model -> Element Msg
+ui config model =
+ let
+ graph =
+ [ circle
+ [ cx <| String.fromFloat model.x
+ , cy <| String.fromFloat model.y
+ , r "2"
+ , fill "magenta"
+ ]
+ []
+ , text_
+ [ x <| String.fromFloat (model.x + 5)
+ , y <| String.fromFloat (model.y + 5)
+ , fontSize "6"
+ , fontFamily "Source Code Pro, monospace"
+ , fill "gray"
+ , dominantBaseline "central"
+ ]
+ [ text coordinates ]
+ ]
+ |> CartesianPlane.graph config
+ |> Element.html
+ |> Element.el
+ [ Element.height Element.fill
+ , Element.width Element.fill
+ ]
+
+ coordinates =
+ "{ x = "
+ ++ String.fromFloat model.x
+ ++ ", y = "
+ ++ String.fromFloat model.y
+ ++ "}"
+ in
= Element.column
= [ Element.width Element.fill
= , Element.centerX
= , Element.spacing 30
= , Element.padding 30
= ]
- [ Element.el
- [ Element.height Element.fill
- , Element.width Element.fill
- ]
- <|
- Element.html <|
- CartesianPlane.graph 300
- [ circle
- [ cx <| String.fromFloat model.x
- , cy <| String.fromFloat model.y
- , r "2"
- , fill "magenta"
- ]
- []
- , text_
- [ x <| String.fromFloat (model.x + 0.03)
- , y <| String.fromFloat model.y
- , fontSize "0.05"
- , dominantBaseline "central"
- ]
- [ text <|
- "("
- ++ String.fromFloat model.x
- ++ ", "
- ++ String.fromFloat model.y
- ++ ")"
- ]
- ]
+ [ graph
= , Input.slider
= [ Element.behindContent
= (Element.el
@@ -114,9 +143,9 @@ ui model =
= { onChange = SetX
= , label =
= Input.labelBelow [ Element.centerX ] <|
- Element.text ("x value: " ++ String.fromFloat model.x)
- , min = -150
- , max = 150
+ Element.text ("x = " ++ String.fromFloat model.x)
+ , min = config.minX
+ , max = config.maxX
= , value = model.x
= , thumb = Input.defaultThumb
= , step = Just 0.01
@@ -136,9 +165,9 @@ ui model =
= { onChange = SetY
= , label =
= Input.labelBelow [ Element.centerX ] <|
- Element.text ("y value: " ++ String.fromFloat model.y)
- , min = -150
- , max = 150
+ Element.text ("y = " ++ String.fromFloat model.y)
+ , min = config.minY
+ , max = config.maxY
= , value = model.y
= , thumb = Input.defaultThumb
= , step = Just 0.01index 35baf6c..a79414c 100644
--- a/src/Examples/Circle.elm
+++ b/src/Examples/Circle.elm
@@ -33,11 +33,15 @@ type alias Config =
=
=main : Html.Html msg
=main =
- Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
- ]
- (ui defaults)
+ ui defaults
+ |> Element.el
+ [ Element.width (Element.maximum 600 Element.fill)
+ , Element.centerX
+ ]
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
=
=
=palette : List Stringindex 46899d3..11dbad5 100644
--- a/src/Examples/LineTypedTransformations.elm
+++ b/src/Examples/LineTypedTransformations.elm
@@ -34,10 +34,6 @@ type Transformation
= | Rotate Float
=
=
-
--- transform : List Transformation -> Svg.Attribute msg
-
-
=transform transformations =
= let
= toString : Transformation -> Stringindex b1bc6f5..f4a9dbf 100644
--- a/src/Examples/NestedTransformations.elm
+++ b/src/Examples/NestedTransformations.elm
@@ -154,7 +154,12 @@ ui model =
= in
= shape
= |> List.singleton
- |> CartesianPlane.graph 300
+ |> CartesianPlane.graph
+ { minX = -150
+ , minY = -150
+ , maxX = 150
+ , maxY = 150
+ }
= |> wrapper
=
=
@@ -441,5 +446,12 @@ grid attributes unit size =
= |> List.map LineSegment2d.fromEndpoints
= |> List.map (Geometry.Svg.lineSegment2d attributes)
= )
- |> (::) (CartesianPlane.axes attributes (size * unit))
+ |> (::)
+ (CartesianPlane.axes attributes
+ { minX = size * unit * -0.5
+ , minY = size * unit * -0.5
+ , maxX = size * unit * 0.5
+ , maxY = size * unit * 0.5
+ }
+ )
= |> g []index 4fd1e93..8f552f0 100644
--- a/src/Examples/PolarCoordinates.elm
+++ b/src/Examples/PolarCoordinates.elm
@@ -70,7 +70,12 @@ ui model =
= ]
= <|
= Element.html <|
- CartesianPlane.graph 300
+ CartesianPlane.graph
+ { minX = -150
+ , minY = -150
+ , maxX = 150
+ , maxY = 150
+ }
= [ circle
= [ cx <| String.fromFloat point.x
= , cy <| String.fromFloat point.yindex 3321826..15d42cf 100644
--- a/src/Examples/Transformations.elm
+++ b/src/Examples/Transformations.elm
@@ -57,11 +57,16 @@ init =
=
=view : Model -> Html Msg
=view model =
- Element.layout
- [ Element.height Element.fill
- , Element.width Element.fill
- ]
- (ui model)
+ model
+ |> ui
+ |> Element.el
+ [ Element.width (Element.maximum 600 Element.fill)
+ , Element.centerX
+ ]
+ |> Element.layout
+ [ Element.height Element.fill
+ , Element.width Element.fill
+ ]
=
=
=ui : Model -> Element Msg
@@ -72,8 +77,7 @@ ui model =
=
= wrapper element =
= Element.column
- [ Element.width (Element.maximum 600 Element.fill)
- , Element.centerX
+ [ Element.width Element.fill
= , Element.spacing 20
= ]
= [ Element.el
@@ -113,7 +117,12 @@ ui model =
= in
= shape
= |> List.singleton
- |> CartesianPlane.graph 300
+ |> CartesianPlane.graph
+ { minX = -150
+ , minY = -150
+ , maxX = 150
+ , maxY = 150
+ }
= |> wrapper
=
=
@@ -339,5 +348,12 @@ grid attributes unit size =
= |> List.map LineSegment2d.fromEndpoints
= |> List.map (Geometry.Svg.lineSegment2d attributes)
= )
- |> (::) (CartesianPlane.axes attributes (size * unit))
+ |> (::)
+ (CartesianPlane.axes attributes
+ { minX = -150
+ , minY = -150
+ , maxX = 150
+ , maxY = 150
+ }
+ )
= |> g []index 222da7a..576f5e8 100644
--- a/src/Examples/ViewBox.elm
+++ b/src/Examples/ViewBox.elm
@@ -47,8 +47,8 @@ init : Model
=init =
= { left = 0
= , top = 0
- , width = 400
- , height = 400
+ , width = 50
+ , height = 50
= , preserveAspectRatio = False
= }
=
@@ -95,7 +95,7 @@ ui model =
= let
= viewbox =
= svg
- [ viewBox "-500 -500 1000 1000"
+ [ viewBox "-100 -100 200 200"
= ]
= [ g [] world
= , rect
@@ -187,8 +187,8 @@ ui model =
= , label =
= Input.labelBelow [ Element.centerX ] <|
= Element.text ("left: " ++ String.fromFloat model.left)
- , min = -500
- , max = 500
+ , min = -100
+ , max = 100
= , value = model.left
= , thumb = Input.defaultThumb
= , step = Just 1
@@ -209,8 +209,8 @@ ui model =
= , label =
= Input.labelBelow [ Element.centerX ] <|
= Element.text ("top: " ++ String.fromFloat model.top)
- , min = -500
- , max = 500
+ , min = -100
+ , max = 100
= , value = model.top
= , thumb = Input.defaultThumb
= , step = Just 1
@@ -232,7 +232,7 @@ ui model =
= Input.labelBelow [ Element.centerX ] <|
= Element.text ("width: " ++ String.fromFloat model.width)
= , min = 1
- , max = 500
+ , max = 100
= , value = model.width
= , thumb = Input.defaultThumb
= , step = Just 1
@@ -254,7 +254,7 @@ ui model =
= Input.labelBelow [ Element.centerX ] <|
= Element.text ("height: " ++ String.fromFloat model.height)
= , min = 1
- , max = 500
+ , max = 100
= , value = model.height
= , thumb = Input.defaultThumb
= , step = Just 1
@@ -270,7 +270,20 @@ ui model =
=
=world : List (Svg Msg)
=world =
- [ circle [ cx "400", cy "300", r "250", fill "purple" ] []
- , circle [ cx "200", cy "350", r "50", fill "yellow" ] []
- , circle [ cx "0", cy "0", r "20", fill "red" ] []
+ [ CartesianPlane.axes
+ [ stroke "gray"
+ , fill "gray"
+ ]
+ { minX = -100
+ , minY = -100
+ , maxX = 100
+ , maxY = 100
+ }
+ , circle
+ [ cx "0"
+ , cy "0"
+ , r "2"
+ , fill "magenta"
+ ]
+ []
= ]index 9a9da15..782b5a7 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -209,10 +209,10 @@ view model =
= "Expecting a block name " ++ name
=
= Mark.ExpectingInlineName name ->
- "Expecting an inline name" ++ name
+ "Expecting an inline name " ++ name
=
= Mark.ExpectingFieldName name ->
- "Expecting a field name" ++ name
+ "Expecting a field name " ++ name
=
= Mark.NonMatchingFields { expecting, found } ->
= "Fields don't match. Expecting one of [ "
@@ -640,13 +640,23 @@ document =
= cartesianCoordinates : Mark.Block (Examples.Model -> Element Msg)
= cartesianCoordinates =
= let
- render model =
+ render :
+ Examples.CartesianCoordinates.Config
+ -> Examples.Model
+ -> Element Msg
+ render config model =
= model.cartesianCoordinates
- |> Examples.CartesianCoordinates.ui
+ |> Examples.CartesianCoordinates.ui config
= |> Element.map Examples.CartesianCoordinatesMsg
= |> Element.map ExamplesMsg
= in
- Mark.stub "CartesianCoordinates" render
+ Mark.record4 "CartesianCoordinates"
+ Examples.CartesianCoordinates.Config
+ (Mark.field "minX" Mark.float)
+ (Mark.field "minY" Mark.float)
+ (Mark.field "maxX" Mark.float)
+ (Mark.field "maxY" Mark.float)
+ |> Mark.map render
=
= polarCoordinates : Mark.Block (Examples.Model -> Element Msg)
= polarCoordinates =Small changes to Terminal and Code blocks
Font size, icons, fix background color not bleeding to the edge in Chromium.
index 73c5f76..c047718 100644
--- a/src/Mark/Custom.elm
+++ b/src/Mark/Custom.elm
@@ -105,7 +105,7 @@ code =
= , Background.color colors.charcoal
= , Font.color colors.gray
= ]
- [ FeatherIcons.file
+ [ FeatherIcons.fileText
= |> FeatherIcons.toHtml []
= |> Element.html
= |> Element.el
@@ -184,6 +184,7 @@ terminal =
= [ Border.width 3
= , Border.rounded 5
= , Border.color colors.charcoal
+ , Background.color colors.charcoal
= , css "page-break-inside" "avoid"
= , Font.family
= [ sourceCodePro
@@ -193,7 +194,6 @@ terminal =
= ]
= [ Element.row
= [ Element.width Element.fill
- , Background.color colors.charcoal
= , Font.color colors.gray
= ]
= [ FeatherIcons.terminal
@@ -226,7 +226,7 @@ terminal =
= , bottomLeft = 3
= , bottomRight = 3
= }
- , Font.size 18
+ , Font.size 14
= , Font.family
= [ sourceCodePro
= , Font.monospaceMake Icon size dependant on the font size of the parent
Use em units instead of px.
index c047718..0a42947 100644
--- a/src/Mark/Custom.elm
+++ b/src/Mark/Custom.elm
@@ -396,8 +396,8 @@ icon =
= |> Dict.get name
= |> Maybe.map
= (FeatherIcons.toHtml
- [ Html.Attributes.height 14
- , Html.Attributes.width 14
+ [ Html.Attributes.style "height" "1em"
+ , Html.Attributes.style "width" "1em"
= ]
= )
= |> Maybe.map Element.htmlRe-draft day 1
Back to single dot (it's hard enough for one day).
Move all semantics discussion to day 3 (not redacted yet).
index 91b6b4e..d1fc7a3 100644
--- a/content/day-1.txt
+++ b/content/day-1.txt
@@ -2,7 +2,7 @@
= Day 1
=
=| Emphasize
- Let's Make Some Dots!
+ Let's Make a Dot!
=
=
=| Note
@@ -22,7 +22,7 @@
=As mentioned before, programs are represented as text (called the /source code/). The source code is stored in files {Icon|name=file} and files are organized in directories {Icon|name=folder}.
=
=
-So the first step is to create a directory for our new program. Let's call it "software-garden".
+So the first step is to create a directory for our new program. Let's call it {Code|software-garden}.
=
=In the terminal type:
=
@@ -50,12 +50,34 @@ We can easily create a new program by entering a following command:
=| Terminal
= elm init
=
-Then to create the file which will contain our source code, type:
+You should see something similar to this:
+
+| Terminal
+ Hello! Elm projects always start with an elm.json file. I can create them!
+
+ Now you may be wondering, what will be in this file? How do I add Elm files to
+ my project? How do I see it in the browser? How will my code grow? Do I need
+ more directories? What about tests? Etc.
+
+ Check out <https://elm-lang.org/0.19.0/init> for all the answers!
+
+ Knowing all that, would you like me to create an elm.json file now? [Y/n]:
+
+
+Just press {Key|enter} once again to proceed. Then to create the file which will contain our source code, type:
=
=| Terminal
= atom src/Main.elm
=
-It may take few seconds, but eventually this command should open a new Atom editor window with an empty text file. If you prefer to use different editor, feel free to do so. Once the editor is ready, type the code belowand save the file.
+| Note
+ Notice that the word {Code|Main} is capitalized. It's important! You can enter uppercase letter {Code|M} with {Key|shift} + {Key|m}, which means:
+
+ | List
+ - Press and hold {Key|shift}
+ - While holding press the {Key|m} button once.
+ - Finally release the {Key|shift}.
+
+It may take few seconds, but eventually this command should open a new Atom editor window with an empty text file. If you prefer to use different editor, feel free to do so. Once the editor is ready, type the code below and save the file.
=
=| Code
= module Main exposing (main)
@@ -66,7 +88,7 @@ It may take few seconds, but eventually this command should open a new Atom edit
= main =
= Html.text "Hello, Tree"
=
-Then enter the following command in the terminal:
+Then switch back to the terminal and enter the following command:
=
=| Terminal
= elm reactor
@@ -135,8 +157,17 @@ We are going to use a technology called *SVG (Scalable Vector Graphics)*. It's a
= - While holding press the {Key|c} button once.
= - Finally release the {Key|ctrl}.
=
+In response you should see something like this:
=
-Now that we have {Code|elm//svg} installed, we can write the code that will draw a dot for us. Change the code to look like this:
+| Terminal
+ Here is my plan:
+
+ Add:
+ elm/svg 1.0.1
+
+ Would you like me to update your elm.json accordingly? [Y/n]:
+
+Press {Key|enter} to proceed with the installation. Now that we have {Code|elm//svg} installed, we can write the code that will draw a dot for us. Change the code to look like this:
=
=| Code
= module Main exposing (main)
@@ -168,12 +199,9 @@ Reload the browser. You should see something like this:
=
=At this point we have a dot on the screen - it's at the top left corner of the viewport (the area of the browser window where the content is displayed). We can see only a quarter of it - the rest is outside of the viewport.
=
-| Note
- TODO: Picture // example showing the viewport
-
=It's a good start, but the dot is not in the center of the screen yet. Let's try to understand why it is where it is.
=
-First we have to realize that the dot is inside an SVG element that itself has a position, width and height. We can make the background of the SVG element pink to see where it is:
+First we have to realize that the dot is inside an {Code|svg} element that itself has a position, width and height. We can make the background of the {Code|svg} element pink to see where it is:
=
=| Code
= module Main exposing (main)
@@ -189,33 +217,21 @@ First we have to realize that the dot is inside an SVG element that itself has a
= []
= ]
=
-Realoading the browser should reveal something like this:
+Reloading the browser should reveal something like this:
=
=| Window
= | Dot
= background=pink
= fill=black
=
-Since the dot is inside the SVG element, and the SVG element doesn't cover the center of the screen, it's impossible to place the dot at the center. Before anything else, let's make the SVG fill entire {Definition|term=viewport|definiens=the area of the browser window where the content is displayed}.
-
-There are two reasons. First, because the center of the dot is at a point called the {Code|origin} or {Code|(0, 0)}. We'll explore exactly what this means later.
-
-Second, it's because the SVG space doesn't fill the browser window.
-
-
-When you reload the browser, you'll see this:
+Since the dot is inside the {Code|svg} element, and the {Code|svg} element doesn't cover the center of the screen, it's impossible to place the dot at the center. Before anything else, let's make the {Code|svg} fill entire {Definition|term=viewport|definiens=the area of the browser window where the content is displayed}.
=
+To do this we will use a package called {Code|mdgriffith//elm-ui}. It makes laying out elements relatively easy. Install it with the terminal. Press {Key|ctrl} + {Key|c} to stop the elm reactor and enter the command
=
+| Terminal
+ elm install mdgriffith/elm-ui
=
-Now we can clearly see that our SVG element (which is pink) does not fill the screen.
-
-We can easily correct this with the help of a very handy elm package, {Code|mdgriffith//elm-ui}.
-
-Install it with the terminal.
-
-Press {Key|ctrl} + {Key|c} to stop the elm-reactor, type {Code|elm install mdgriffith//elm-ui} and press {Key|enter}.
-
-Then in {Code|src//Main.elm} add the necessary import {Code|import Element} and change {Code|Main} to look like this:
+As before, press {Key|enter} to follow the plan. Now let's use the package. Change the code to look like this:
=
=| Code
= module Main exposing (main)
@@ -226,25 +242,33 @@ Then in {Code|src//Main.elm} add the necessary import {Code|import Element} and
=
=
= main =
- Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
+ Svg.svg
+ [ Svg.Attributes.style "background: pink"
+ , Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
= ]
- (Element.html
- (Svg.svg
- [ Svg.Attributes.height "100%"
- , Svg.Attributes.width "100%"
- , Svg.Attributes.style "background: pink"
- ]
- [ Svg.circle
- [ Svg.Attributes.r "10" ] []
- ]
- )
- )
-
-Take a wild guess: what do you think {Code|Element.height Element.fill}, {Code|Svg.Attributes.height "100%"}, {Code|Element.width Element.fill} and {Code|Svg.Attributes.width "100%"} do?
-
-Reload the browser to confirm your hypothesis. Voila! The SVG space now fills the screen.
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ ]
+ []
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+
+| Note
+ Don't worry if it all looks like black magic now. We will discuss what it all means on {Link|Day 3|url=day-3.html} of the workshop.
+
+ For now let's just note that on line 3 we have added a new {Code|import} statement for {Code|Element} module (similar to how we did it for {Code|Svg} and {Code|Svg.Attributes} before).
+
+ After imports our code looks the same at first: {Code|main} is an {Code|svg} element containing the {Code|circle}, but we have added two new attributes {Code|width} an {Code|height}, both set to {Code|"100%"}. We will talk more about attributes later today.
+
+ Then there are some new things on lines 19 - 23. First is the pipe operator ({Code|\|>}). We use it to pass the {Code|svg} element together with the {Code|circle} inside it into `Element.html` function (we need to convert it into an {Code|Element}) and then to pass this {Code|Element} into an {Code|Element.layout} which will fill the viewport with its contents. Again, more about the pipe and functions on day 3.
+
+Reload the browser. The SVG space now fills the entire viewport. Even the small white margin is gone!
=
=| Window
= | DotInElement
@@ -253,7 +277,22 @@ Reload the browser to confirm your hypothesis. Voila! The SVG space now fills th
= cx=0
= cy=0
=
-It should now be possible to place our dot in the center of the screen. But, how exactly will we achieve it?
+It should now be possible to place our dot in the center of the screen. Currently the dot is in the top left corner of the viewport, but at least the parent {Code|svg} element is covering it, so the center of the viewport is somewher within the {Code|svg} element.
+
+| Header
+ The Coordinates
+
+We will now explore how child elements (like our {Code|circle}) are positioned inside the parent {Code|svg} element.
+
+One method is to use the *cartesian coordinates system*. To get some intuition about it, imagine that you are a villain in an old James Bond movie. For some sinister purpose you need to tell somebody exactly where a flower pot is located on a large table. The problem is that you can only communicate through an old fashioned phone {Icon|name=phone}. To fool mr. Bond you need to be very precise, so saying something like "it's roughly in the middle" won't work.
+
+| Note
+ TODO: Image of the table, the flower pot and the villain
+
+However, if you had a ruler, you could say: "it's 63.3cm from the top edge and 27.1cm from the left edge". To do that you would have previously agree which edge is left and which is top - not so obvious in case of a table. Fortunately in SVG we have this sort of agreement made for us. We alsways measure from the top and left edge of the viewport.
+
+The distance from the left edge is called {Code|x} and the distance from the top is called {Code|y}. The bigger the {Code|x} the more to the right goes the element. The bigger the {Code|y} the more it goes down. Try it out:
+
=| Row
= | CartesianCoordinates
= minX = -10
@@ -261,16 +300,11 @@ It should now be possible to place our dot in the center of the screen. But, how
= maxX = 300
= maxY = 200
=
-Let's return to the idea we introduced earlier, that the center of the dot is at a point called the {Code|origin} or {Code|(0, 0)}. What exactly does this mean?
-
-* a picture of a vase on a table should be here *
-
-Adjust the sliders below to change the x and y coordinates of the dot. It's like moving an object on a table by describing how far left, right, up or down it should be.
-
+Adjust the sliders below to change the {Code|x} and {Code|y} coordinates of the dot. It's like moving the flower pot on the table by describing how far left, right, up or down it should be.
=
-Knowing that the center of the dot is at the {Code|origin} or point {Code|(0,0)} of our plain, we can see that by default the origin of an SVG space is at the upper left corner. If we want to help our dot get out of hiding, we'll have to place the center of the dot at a point with positive {Code|x} and {Code|y} values.
+Initially the center of the dot is at a point called /the origin/ or {Code|\{ x = 0, y = 0\}}. The origin is in the top left corner of the viewport. That's why only quarter of the dot is visible. If we want to help our dot get out of hiding, we'll have to place the center of the dot at a point with positive {Code|x} and {Code|y} values.
=
-To change the position of the dot, we can use the {Code|cx} and {Code|cy} attributes of the {Code|circle} element.
+To change the position of the dot, we can use the {Code|cx} and {Code|cy} attributes of the {Code|circle} element, like this:
=
=| Code
= module Main exposing (main)
@@ -281,25 +315,23 @@ To change the position of the dot, we can use the {Code|cx} and {Code|cy} attrib
=
=
= main =
- Element.layout
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "100"
+ , Svg.Attributes.cy "50"
+ ]
+ []
+ ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: pink"
+ ]
+ |> Element.html
+ |> Element.layout
= [ Element.width Element.fill
= , Element.height Element.fill
= ]
- (Element.html
- (Svg.svg
- [ Svg.Attributes.height "100%"
- , Svg.Attributes.width "100%"
- , Svg.Attributes.style "background: pink"
- ]
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "100"
- , Svg.Attributes.cy "50"
- ]
- []
- ]
- )
- )
=
=| Window
= | DotInElement
@@ -308,30 +340,49 @@ To change the position of the dot, we can use the {Code|cx} and {Code|cy} attrib
= cx=100
= cy=50
=
-Now, if we want our dot to be exactly in the center of the screen, we'll have to set {Code|cx} and {Code|cy} to values equal to exactly half of the {Code|width} and {Code|height} of the SVG space.
+| Note
+ The {Code|cx} stands for /center x/ and {Code|cy} for /center y/, where center refers to the circle. We will discuss what the center of the circle is in more exact terms on day 2, but you probably get the concept.
+
=
-We can calculate the position of the circle as:
+Now the dot is not in the corner anymore, but it's not in the center yet. If we want our dot to be exactly in the center of the screen, we'll have to set {Code|cx} and {Code|cy} to values equal to exactly half of the {Code|width} and {Code|height} of the SVG viewport. We could calculate the position of the circle as:
=
=| Monospace
- cx = width/2
- cy = height/2
+ cx = width / 2
+ cy = height / 2
=
-There is a problem though! We don't actually know the width and height of the SVG space. All we know is that out {Code|layout's} {Code|width} and {Code|height} will {Code|fill} the viewport, and the {Code|svg} space's {Code|width} and {Code|height} are {Code|100%} of the {Code|layout}. We don't know the exact size of either (because the {Code|layout} will scale to the browser window).
-☹️
+There is a problem though! We don't actually know the width and height of the SVG viewport. All we know is that it will fill all the available space, but this can be different on different devices: {Icon|name=smartphone} {Icon|name=tablet} {Icon|name=monitor}
=
-Fortunately, we don't need to know the width and height of the SVG space!
+Fortunately, we don't need to know the width and height of the {Code|svg} element! That's why it's called *scalable*. We can work with it without knowing the actual scale on the screen.
=
-This is a bit tricky. Instead of moving the dot in the SVG space, we can set the boundaries of the {Code|scene} so that the dot is at the center using a property called {Code|Viewbox}.
+| Header
+ The ViewBox
+
+You can imagine an SVG element as an infinite surface on which elements are placed. Which elements you see, depends on where you look. And where you look is called the *viewbox*. It has four properties: {Code|top}, {Code|left}, {Code|width} and {Code|height}.
+
+It works a little bit like a photo camera pointing directly at the surface. You can move it up, down (along the {Code|y} axis) and left and right (along the {Code|x} axis). You can also change it's focal length to cover more or less area. Unlike a lens of the camera you set the width and height separately. If you set it so that the dot is in the middle of your frame, then no matter how big the print of the picture will be, the dot will always be in the center. Increasing the {Code|width} and {Code|height} of the viewbox will make more of the surrounding surface visible and the dot will become smaller, but it's position won't change. Let's see it in action:
+
+| Row
+ | ViewBox
=
-To use an anology, imagine you've decided to build a holiday home with your family or friends in the Alps. Sounds pretty nice, right? Now, your budget isn't unlimited, so you've had to make a few compromises. You're in a beautiful area with a lovely view of the mountains, but yours isn't the only house in the area, and some of the others aren't the nicest to look at. Of course, you don't want your view of the beautiful snow-capped mountains to be blocked by a big pile of concrete. So what are you going to do about this? You can move your neighbours' homes, and the mountains while you're at it, so they are directly in front of your windows. Or, you can move the windows as you design your house so that you get a full view of the beautiful surroundings. Imagine you could move the windows around, as you decide the best place for them. As the window moves, you it will look as though the surroundings as moving, to the right, the left, up and down, as you shift the window. But in reality it is only your perspective that is changing.
=
-A videwbox is like a window into an SVG space. It allows us to change the point of view of the scene.
+On the left and the right side we show the same set of elements: a dot at the origin and two axes ({Code|x} going from left to right and {Code|y} going top - down). The pink rectangle represents the viewbox. Normally it is not visible, but we display it here to help you understand how it works.
+
+On the right there is a window with a viewport inside. Everything covered by the viewbox will be displayed in the viewport. Their sizes are independent, so the shapes will be scaled to fit. You can also set the option to preserve the aspect ratio to prevent the shapes from being stretched.
+
+You cannot control the size of the window or the viewport. All you can do is play with the viewbox. Try calibrating the {Code|left}, {Code|top}, {Code|width} and {Code|height} properties of the viewbox so that the dot is in the center of the viewport.
+
+| Note
+ Here is how I would do it. First pick any number for the {Code|width}, say {Code|20}. Then you have to move the left edge half way ({Code|20 // 2 = 10}) to the left. Once you click on a slider you can use {Key|◀} and {Key|▶} keys to precisely set the value. Moving to the left means making the value lesser, and since we start from {Code|0} we will end up at {Code|-10}. Do the same for {Code|height} and {Code|top}. The dot should be exactly in the center of the viewbox and the viewport.
+
+The formula is simple:
=
-| ViewBox
+| Monospace
+ left = - (width / 2)
+ top = - (height / 2)
=
-In this example, we can set the width and height of the viewbox to an arbitrary value. As we see in the interactive example, the width and height of the viewbox will effect how big the dot appears, but that's not so important to us at the moment. Let's set them both to 600.
+So instead of moving the dot in the SVG plane, we change where we look by moving the viewbox. By setting it that way, we know that the distance from the left and right edge of the screen to the center of the dot will be the same. We don't know exactly what it will be (because we don't control the size of the viewport), but it will always be equal. That's the definition of being in the center!
=
-The top and left values of the viewbox are a bit trickier. We saw ealier that if we knew the width and height of our SVG scene, we could have set the cx and cy values of the dot to half the width and height of the scene to center the dot. We will have to use similar reasoning here. If we set the top and left values of the viewbox to 0, the dot will still still appear in the top left corner of the scene. If we want to see the dot moved to the right and downward within the scene, we will need the top and left values of the viewbox to be negative. If we want the origin of the SVG space to appear directly in the center of the scene, we will need to set the top and left values of the viewbox to {Code|- height//2} and {Code|- width//2}. In this case, they will both have the value 300.
+In the code we set the viewbox as an attribute of the {Code|svg} element, like this:
=
=| Code
= module Main exposing (main)
@@ -342,240 +393,84 @@ The top and left values of the viewbox are a bit trickier. We saw ealier that if
=
=
= main =
- Element.layout
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ ]
+ []
+ ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: pink"
+ , Svg.Attributes.viewBox "-300 -300 600 600"
+ ]
+ |> Element.html
+ |> Element.layout
= [ Element.width Element.fill
= , Element.height Element.fill
= ]
- (Element.html
- (Svg.svg
- [ Svg.Attributes.viewBox "-300 -300 600 600" ]
- [ Svg.circle [ Svg.Attributes.r "10" ] [] ]
- )
- )
=
-Refresh the broswer. We should now see the dot in the center of the screen.
+There are only two changes here. First we reset the {Code|cx} and {Code|cy} attributes of the {Code|circle} back to {Code|"0"} (on lines 11 and 12). We want the dot to be back at origin. Then we set up the position of the viewbox on line 20. Try experimenting with different values. If you set them right and refresh the broswer the dot should be in the center of the screen.
=
=| Window
= | DotWithViewBox
- background=none
+ background=pink
= fill=black
= viewBox=-300 -300 600 600
=
+You can also remove line 19 to remove the pink background, or set it to some other color.
=
-Now that we've centered our dot, we can customize it by giving it a color! Let's take a deeper look at how we give attributes to an SVG element.
-
-We've already seen a few example of SVG attributes. The radius of our dot ({Code|circle}) is an attribute.
-
-| Code
- Svg.circle [ Svg.Attributes.r "10" ] []
-
-Try giving it a new value to see your dot grow or shrink.
-
-Our viewBox is also an attribute. It's an attribute of the {Code|svg} element.
-
-| Code
- Svg.svg
- [ Svg.Attributes.viewBox "-300 -300 600 600" ]
- [ Svg.circle [ Svg.Attributes.r "10" ] [] ]
-
-Again, try giving it new values. As we saw earlier, the first two values (which represent the left and top of the viewBox) must be equal to the negative of the half of the last two values (which correspond to width and height). Try other combinations of values to adjust the placement of the dot. If the behavior of the viewBox is difficult to understand, refer again to the interactive tutorial above.
-
-To color our dot, we'll have to give it a second attribute. Many SVG elements take a {Code|List} of attributes. A {Code|List} is a very important concept in software development. {Code|Lists} allow us to group an arbitary number of similar values. In this case, we'll be using a {Code|List} to group {Code|Attribute a} values. Whenever you see {Code|[...]} in an Elm source code file, you are dealing with a list.
-
-Before we give a color to our dot, try to identify all the lists in the code we're writen already. There are 5. Hint: It is possible for a list to have only 1 or 0 items ({Code|[]}).
-
-| Code
- Svg.circle [ Svg.Attributes.r "10" ] []
-
-When we take a look at the code responsible for creating our dot, we see that it is followed by two lists. We'll only need to worry about the first one at the moment. As we've discussed, the first list already contains the {Code|r} attribute of the circle, representing the radius. We'll need to add a second attribute to the circle. We use a comma ({Code|,}) to seperate elements in a list.
-
-| Code
- Svg.circle [ Svg.Attributes.r "10", ... ] []
+| Header
+ Color
=
-To color our circle, we'll use the {Code|fill} attribute.
+Speaking of colors, now that we've centered our dot, it's time to give it a color! We do it by adding an {Code|fill} attribute to the {Code|circle} element, like that:
=
=| Code
- Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.fill "blue"
- ]
- []
-
-You should see this result:
-
-| Window
- | DotWithViewBox
- background=none
- fill=blue
- viewBox=-300 -300 600 600
-
+ module Main exposing (main)
=
-Let's use our new understanding of lists to do something a bit more interesting. Let's draw multiple dots!
+ import Element
+ import Svg
+ import Svg.Attributes
=
-We can start by taking a look at our {Code|main} value:
=
-| Code
= main =
- Element.layout
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "skyblue"
+ ]
+ []
+ ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.viewBox "-300 -300 600 600"
+ ]
+ |> Element.html
+ |> Element.layout
= [ Element.width Element.fill
= , Element.height Element.fill
= ]
- (Element.html
- (Svg.svg
- [ Svg.Attributes.viewBox "-500 -500 1000 1000"
- ]
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.fill "blue"
- ]
- []
- ]
- )
- )
-
-Where is our list of circles? To find it, first find the circle.
-
-| Code
- Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.fill "blue"
- ]
- []
=
-Next, identify the first open square bracket {Code|[} before the circle.
-
-| Code
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.fill "blue"
- ]
- []
- ...
-
-This is where the list begins.
-
-Finally, identify the matching closing brakcet {Code|]}. This is a bit tricky because there are two lists within this list. The first is the list of attributes to the circle we saw earlier. The second is an empty list. We'll see exactly what they do in a moment. Ignore both of these for now. The matching closing bracket should be on the same indentation level as the opening bracket. Your text editor should be able to help you here. When you move your cursor before the opening bracket, the closing bracket should be highlighted.
-
-| Code
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.fill "blue"
- ]
- []
- ]
-
-Right now, our list of circles has one value. We want to give it a second value. The second value will look identical to the first. What is the first value?
-
-| Code
- Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.fill "blue"
- ]
- []
-
-It might not seem obvious at first that this is all one value. But it is. {Code|Svg.circle} takes two lists as arguments. We'll see what an argument is later. For now, we will need to understand the difference between an attribute and a child.
-
-Imagine a box of colored easter eggs. One egg is painted yellow, another red, another blue. Moreover, they don't all come from the same bird. One is a small robin's egg. Another is a medium chicken egg. Another is a huge ostrich egg.
-
-Some of the elements in our image are 'things'. A box is a thing. Each egg is also a thing. Some of the elements in our image are not things. Blueness is not a thing. Medium-sizeness is not a thing. You can see blueness. You can see medium-sizeness. But they are not things. They are attributes of a thing. A thing can be blue. It can be medium-sized.
-
-It is a convention in many programming languages, including Elm and SVG, to distinguish between the attributes and children of an element. If we wanted to represent our box in SVG, we would give our box several attributes. It would have a color (maybe brown). It would have a width, length, depth. We would also give it several children. Each of the eggs within the box would be a child of the box. A box with 6 eggs would have 6 children. Each of these children (the eggs) would have different attributes: blue, red, medium-sized, etc. The children of an element are the 'things' it contains. The attributes of a thing are the elements that describe it.
-
-The first list following {Code|Svg.circle} is:
-
-| Code
- [ Svg.Attributes.r "10"
- , Svg.Attributes.fill "blue"
- ]
-
-This is a list of attributes. A radius is not a 'thing'. A fill color is not a 'thing'. They are elements that describe a thing. They describe our circle.
-
-The second list is:
-
-| Code
- []
-
-It is an empty list. Nevertheless, it is a list of children. Our circles don't have any children.
-
-Is the circle itself an attribute or a thing?
-
-From Elm's perspective, this entire code block is a circle:
-
-| Code
- Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.fill "blue"
- ]
- []
-
-This is not a circle:
-
-| Code
- Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.fill "blue"
- ]
-
-Nor is this:
-
-| Code
- Svg.circle
-
-To create a list with two circles, we'll want to duplicate the properly formed circle value. You can give the second dot a different color from the first.
-
-| Code
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.fill "blue"
- ]
- []
- , Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.fill "red"
- ]
- []
- ]
-
-Make add the second circle to the list of circles in your source code and refresh the page. What do you see?
+The only change is on line 13. Reload the browser and see this:
=
=| Window
- | TwoDots
+ | DotWithViewBox
= background=none
+ fill=skyblue
= viewBox=-300 -300 600 600
- firstFill=blue
- secondFill=red
- firstCX=0
- secondCX=0
-
-Only one dot! We see the second one, but what happened to the first dot? It's under the second... Both are at the origin (0, 0), so they are both in the same spot. If we want to see both dots, we will have to change their positions. To do this we'll have to use their {Code|cx} or {Code|cy} values.
=
-| Code
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "-10"
- , Svg.Attributes.fill "blue"
- ]
- []
- , Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "10"
- , Svg.Attributes.fill "red"
- ]
- []
- ]
+| Emphasize
+ Wow!
=
-| Window
- | TwoDots
- background=none
- viewBox=-300 -300 600 600
- firstFill=blue
- secondFill=red
- firstCX=-100
- secondCX=100
=
-As an exercise, try adding an {Code|Svg.Attribute.cy} attribute to the circles to adjust their vertical position.
+That's exactly what we were trying to achieve. Scroll up to the problem section to compare.
=
-For a more advanced exercise, try adding a new circle. Why not draw 5 circles on the screen? Give them each a unique color and position.
+| Emphasize
+ Congratulations!
=
=| Emphasize
= {Icon|name=award}index 1ab9957..5780836 100644
--- a/content/day-3.txt
+++ b/content/day-3.txt
@@ -4,9 +4,8 @@
=| Emphasize
= Connecting the Dots
=
-
=| Note
- Today we are going to lear about
+ So far we have been writing the code of our program without thinking too much about about what it means. Today we are going to learn about
=
= | List
= - Values and names
@@ -15,10 +14,400 @@
= - Types
= - SVG gradients
=
-| Header
- The Problem
+Before we begin, let's reflect on the following.
+
+| Emphasize
+ Every Elm program is composed of three basic building blocks: {Code|values}, {Code|names} and {Code|types}.
+
+For efficiency we are going to play with these concepts in REPL (the Read - Evaluate - Print Loop). Go to the terminal. If you have Elm reactor running, then stop it with {Key|ctrl} + {Key|c}. Then start the REPL by entering
+
+| Terminal
+ elm repl
+
+It should show something like this
+
+| Terminal
+ ---- Elm 0.19.0 ----------------------------------------------------------------
+ Read <https://elm-lang.org/0.19.0/repl> to learn more: exit, help, imports, etc.
+ --------------------------------------------------------------------------------
+ >
+
+Here you can enter snippets of Elm code and see it evaluated.
+
+| Note
+ To /evaluate/ means to compute the value of a given expression, e.g. expression {Code|2 + 2} has a value of {Code|4}. The process of figuring it out is called evaluation. That's what computer programs do!
+
+Let's start with values. The simplest way to create a value is to literally type it in our code. Try it (type things after {Code|>} and expect to see what follows):
+
+| Terminal
+ > 2
+ 2 : number
+ > "Hello"
+ "Hello!" : String
+ >
+
+These expressions ({Code|2} and {Code|"Hello!"}) are called /literals/, because their values are literally what they look like. The value of {Code|2} is simply {Code|2}! When you enter them, the REPL evaluates and prints their value (which in this case is very easy) and type (the text after {Code|:}). We will get to types later.
+
+Some value literals are simple, like numbers and strings. On day 1 we discussed lists. You can also type them literally, like this:
+
+| Terminal
+ > [ 1, 2, 5, 77, 2 ]
+ [1,2,5,77.6,2] : List number
+
+First notice that a list literal starts with {Code|[} and ends with {Code|]}. Inside there are values called /elements of the list/. Each element is separated from others with a {Code|,} (comma). Then notice that each element is a value on it's own, but the whole list is also a value.
+
+| Note
+ It can be called a /composite value/. It's like the box of eggs. Each egg is a thing, but the box of eggs is also a thing (composed of the box and the eggs).
+
+List can be empty:
+
+| Terminal
+ > []
+ [] : List a
+
+or contain only one element:
+
+| Terminal
+ > [ "Hello" ]
+ ["Hello"] : List String
+
+| Note
+ Notice that a list with one element is not the same as the element itself, just like the box with one egg is not the same as an egg alone.
+
+Now let's focus our attention to *names*. You can give any value a name.
+
+| Terminal
+ > kittens = 2
+ 2 : number
+ > greeting = "Hello!"
+ "Hello!" : String
+ >
+
+From now on you can refer to this value either literally or by it's name:
+
+| Terminal
+ > kittens
+ 2 : number
+ > 2
+ 2 : number
+
+The effect will be exactly the same - you will get a value back. So we see that there are at least two ways of getting values: by literally expressing them or refering to them by names given to them previously. In a composite value of a list, you can mix the two ways together:
+
+| Terminal
+ > [ 1, kittens, 3 ]
+ [1,2,3] : List number
+
+A function is a special kind of a value. Let's create a named function like this:
+
+| Terminal
+ > fun something = something ++ " is fun!"
+ <function> : String -> String
+
+Here we gave a name {Code|fun} to a function, that given {Code|something} will produce a string saying that this {Code|something} is fun! Let's see how you can use it:
+
+| Terminal
+ > fun "Learning Elm"
+ "Learning Elm is fun!" : String
+
+Above we applied the function (by calling it's name: {Code|fun}) to a literal value: {Code|"Learning Elm"}. In return the function produced another value: {Code|"Learning Elm is fun!"}. So a function takes a value and gives a value. But function itself is also a value.
+
+| Note
+ Imagine a machine that paints eggs. You put an egg into the machine and on the other side you get a painted egg. The egg is a thing and the painted egg is also a thing. But so is the machine!
+
+Now we see that we can get a value in three ways: (1) by literally expressing it, (2) by calling it's name and (3) by applying a function to another value.
+
+Not every function can be applied to any value. You can add two numbers (like {Code|2} and {Code|5} in {Code|2 + 5}), but you can't a number to a string ({Code|2 + "Hello!"} - try it and see an error!). The system that governs what function can be applied to what value is called a *type system* and it plays important role in Elm programming.
+
+| Note
+ Again, imagine a machine that paints eggs. If you try to put a carrot in that machine, the dumb machine would make a mess of your carrot and probably jam. But a smart machine would sound a low pitch buzz and simply refuse to take the carrot. That way a smart machine avoids making a mess and possibly damaging itself. The Elm's type system is what makes your functions smart. It stops you from putting a carrot into the egg painting machine!
+
+Every value in Elm has a type. You can see the type in the REPL - after evaluating the expression it shows it's value and the type, like here:
+
+| Terminal
+ > kittens
+ 2 : number
+
+Above {Code|2} is the value and `number` is the type. Similar here:
+
+| Terminal
+ > fun "Learning Elm"
+ "Learning Elm is fun!" : String
+
+The value is {Code|"Learning Elm is fun!"} and the type is {Code|String}.
+
+Composite values like lists have composite types. For example here the type is {Code|List String}.
+
+| Terminal
+ > [ "Hello", "Good bye" ]
+ ["Hello","Good bye"] : List String
+
+This means that it's a list of strings. That means that all the elements of this list are of type {Code|String}. When talking about lists you have to tell that it's a list and what is the type of it's element. Only this can be considered a fully specified type. Compare:
+
+| Terminal
+ > [ 1, 2, 3 ]
+ [1,2,3] : List number
+
+This is a list of numbers. It's easy to see - all the elements are numbers, just as previously they were strings. All the elements of a list must be of the same type. If you try mixing the types, Elm will refuse to build your program. Try:
+
+| Terminal
+ > [ 1, 2, 3, "Hello" ]
+ -- TYPE MISMATCH ----------------------------------------------------------- elm
+
+ The 4th element of this list does not match all the previous elements:
+
+ 7| [ 1, 2, 3, "Hello" ]
+ ^^^^^^^
+ The 4th element is a string of type:
+
+ String
+
+ But all the previous elements in the list are:
+
+ number
+
+ Hint: Everything in the list needs to be the same type of value. This way you
+ never run into unexpected values partway through. To mix different types in a
+ single list, create a "union type" as described in:
+ <http://guide.elm-lang.org/types/union_types.html>
+
+
+What's the type of an empty list? Let's see:
+
+| Terminal
+ > []
+ [] : List a
+
+A list of {Code|a}. Obviously {Code|a} is not a type. This means that the type of the elements cannot be determine yet. This is signaled by the lowercase term where the type should be: {Code|a} instead of the capitalized {Code|String}.
+
+| Note
+ Notice that the {Code|number} (type of value {Code|15} for example} is also lowercase. That's because it's also not a concrete type! Elm has two concrete types for numbers: {Code|Int} that can represent an integer number (1, 2, 3, 0, -122 etc.) and {Code|Float} that can represent a number with a fraction, (like 1.2, -3.5, 44.2). Because fraction part can be 0 (e.g. 1.0, 2.0, 0.0, -122.0 - so called whole numbers), it's not possible to tell if {Code|2} is an {Code|Int} or a {Code|Float}. We will see some of the implications of this later.
+
+Above I said that every value has a type and also that a function is a value on it's own. Let's try the following in REPL:
+
+| Terminal
+ > fun
+ <function> : String -> String
+
+We have asked the REPL to evaluate the value referenced by name {Code|fun}. In response the value is display as {Code|<function>}. That's because there is no simple way to represent the value of a function as text. Let's focus our attention on the type (part after the colon {Code|:}). It is {Code|String -> String}. This means that the type is a function (we recognize it by the arrow symbol). What's before the arrow is a type of the value that goes into the function (called the /argument of the function/). The type of the argument is {Code|String}. That shouldn't be a surprise. Remember how we have used the function before to produce the value:
+
+| Terminal
+ > fun "Learning Elm"
+ "Learning Elm is fun!" : String
+
+The part expressed as {Code|"Learning Elm"} is a {Code|String} (we recognize it by the quotation around it) and that's exactly what the function needs.
+
+The part after the arrow symbol is the type that the function returns. It is also {Code|String}. That also should not surprise us, since {Code|"Learning Elm is fun!"} is also a {Code|String}.
+
+Not every function returns the same type as it takes. For example function {Code|String.fromInt} takes a value of type {Code|Int} and returns a value of type {Code|String}. It's type is therefor {Code|Int -> String}. We read it as: /type of String.fromInt is a function from Int to String/. Let's see it in action:
+
+| Terminal
+ > String.fromInt 10
+ "10" : String
+
+That's right! The value {Code|10} is an {Code|Int}, the value {Code|"10"} is a {Code|String}.
+
+| Terminal
+ > String.fromInt
+ <function> : Int -> String
+
+But what if we want to say that a certain number (say, {Code|232}) is fun? We have our {Code|fun} function, but it takes a {Code|String} and what we have is an {Code|Int}. We can use the very same {Code|String.fromInt} to first convert it into the {Code|String} and then pass the value to the {Code|fun} function, like this:
+
+| Terminal
+ > fun (String.fromInt 232)
+ "232 is fun!" : String
+
+We had to put the {Code|String.fromInt 232} expression in parentheses, so that Elm understands that value {Code|232} is to be passed to {Code|String.fromInt} function, instead of the {Code|String.fromInt} being passed to {Code|fun} (which would not work anyway, since {Code|fun} takes values of type {Code|String}, not {Code|Int -> String}).
+
+Alternatively we can use the pipe operator which looks like this: {Code|\|>}. It has the advantage that the code that the transformations that happen first are on the left side and ones that happen later on the right, so we can read our code left to right instead of inside out. Here is how it would look like:
+
+| Terminal
+ > 232 |> String.fromInt |> fun
+ "232 is fun!" : String
+
+Now it's more apparent that the value {Code|232} is passed to the function {Code|String.fromInt}, and whatever comes on the other end is passed to the {Code|fun} function. We don't need parentheses in this case.
+
+| Note
+ TODO: But where does the functions come from? Modules.
+
+
+
+
+
=
=| Note
= *We are still working on this content.*
=
= Stay tuned {Icon|name=radio}
+
+Let's take a deeper look at how we give attributes to an SVG element.
+
+We've already seen a few example of SVG attributes. The radius of our dot ({Code|circle}) is an attribute.
+
+| Code
+ Svg.circle [ Svg.Attributes.r "10" ] []
+
+Try giving it a new value to see your dot grow or shrink.
+
+Our viewBox is also an attribute. It's an attribute of the {Code|svg} element.
+
+| Code
+ Svg.svg
+ [ Svg.Attributes.viewBox "-300 -300 600 600" ]
+ [ Svg.circle [ Svg.Attributes.r "10" ] [] ]
+
+Again, try giving it new values. As we saw earlier, the first two values (which represent the left and top of the viewBox) must be equal to the negative of the half of the last two values (which correspond to width and height). Try other combinations of values to adjust the placement of the dot. If the behavior of the viewBox is difficult to understand, refer again to the interactive tutorial above.
+
+To color our dot, we'll have to give it a second attribute. Many SVG elements take a {Code|List} of attributes. A {Code|List} is a very important concept in software development. {Code|Lists} allow us to group an arbitary number of similar values. In this case, we'll be using a {Code|List} to group {Code|Attribute a} values. Whenever you see {Code|[...]} in an Elm source code file, you are dealing with a list.
+
+Before we give a color to our dot, try to identify all the lists in the code we're writen already. There are 5. Hint: It is possible for a list to have only 1 or 0 items ({Code|[]}).
+
+| Code
+ Svg.circle [ Svg.Attributes.r "10" ] []
+
+When we take a look at the code responsible for creating our dot, we see that it is followed by two lists. We'll only need to worry about the first one at the moment. As we've discussed, the first list already contains the {Code|r} attribute of the circle, representing the radius. We'll need to add a second attribute to the circle. We use a comma ({Code|,}) to separate elements in a list.
+
+| Code
+ Svg.circle [ Svg.Attributes.r "10", ... ] []
+
+To color our circle, we'll use the {Code|fill} attribute.
+
+| Code
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.fill "blue"
+ ]
+ []
+
+You should see this result:
+
+| Window
+ | DotWithViewBox
+ background=none
+ fill=blue
+ viewBox=-300 -300 600 600
+
+Let's use our new understanding of lists to do something a bit more interesting. Let's draw multiple dots!
+
+We can start by taking a look at our {Code|main} value:
+
+| Code
+ main =
+ Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+ (Element.html
+ (Svg.svg
+ [ Svg.Attributes.viewBox "-500 -500 1000 1000"
+ ]
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.fill "blue"
+ ]
+ []
+ ]
+ )
+ )
+
+Where is our list of circles? To find it, first find the circle.
+
+| Code
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.fill "blue"
+ ]
+ []
+
+Next, identify the first open square bracket {Code|[} before the circle.
+
+| Code
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.fill "blue"
+ ]
+ []
+ ...
+
+This is where the list begins.
+
+Finally, identify the matching closing brakcet {Code|]}. This is a bit tricky because there are two lists within this list. The first is the list of attributes to the circle we saw earlier. The second is an empty list. We'll see exactly what they do in a moment. Ignore both of these for now. The matching closing bracket should be on the same indentation level as the opening bracket. Your text editor should be able to help you here. When you move your cursor before the opening bracket, the closing bracket should be highlighted.
+
+| Code
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.fill "blue"
+ ]
+ []
+ ]
+
+Right now, our list of circles has one value. We want to give it a second value. The second value will look identical to the first. What is the first value?
+
+| Code
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.fill "blue"
+ ]
+ []
+
+It might not seem obvious at first that this is all one value. But it is. {Code|Svg.circle} takes two lists as arguments. We'll see what an argument is later. For now, we will need to understand the difference between an attribute and a child.
+
+Imagine a box of colored easter eggs. One egg is painted yellow, another red, another blue. Moreover, they don't all come from the same bird. One is a small robin's egg. Another is a medium chicken egg. Another is a huge ostrich egg.
+
+Some of the elements in our image are 'things'. A box is a thing. Each egg is also a thing. Some of the elements in our image are not things. Blueness is not a thing. Medium-sizeness is not a thing. You can see blueness. You can see medium-sizeness. But they are not things. They are attributes of a thing. A thing can be blue. It can be medium-sized.
+
+It is a convention in many programming languages, including Elm and SVG, to distinguish between the attributes and children of an element. If we wanted to represent our box in SVG, we would give our box several attributes. It would have a color (maybe brown). It would have a width, length, depth. We would also give it several children. Each of the eggs within the box would be a child of the box. A box with 6 eggs would have 6 children. Each of these children (the eggs) would have different attributes: blue, red, medium-sized, etc. The children of an element are the 'things' it contains. The attributes of a thing are the elements that describe it.
+
+The first list following {Code|Svg.circle} is:
+
+| Code
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.fill "blue"
+ ]
+
+This is a list of attributes. A radius is not a 'thing'. A fill color is not a 'thing'. They are elements that describe a thing. They describe our circle.
+
+The second list is:
+
+| Code
+ []
+
+It is an empty list. Nevertheless, it is a list of children. Our circles don't have any children.
+
+Is the circle itself an attribute or a thing?
+
+From Elm's perspective, this entire code block is a circle:
+
+| Code
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.fill "blue"
+ ]
+ []
+
+This is not a circle:
+
+| Code
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.fill "blue"
+ ]
+
+Nor is this:
+
+| Code
+ Svg.circle
+
+To create a list with two circles, we'll want to duplicate the properly formed circle value. You can give the second dot a different color from the first.
+
+| Code
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.fill "blue"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.fill "red"
+ ]
+ []
+ ]index c2832b6..f181d18 100644
--- a/content/index.txt
+++ b/content/index.txt
@@ -13,7 +13,7 @@ Below is material through which we will be going during the workshop. Only the f
=
=| List
= # {Link|Before the course begins|url=/preparation.html}
- # {Link|Day 1 - Let's Make some Dots|url=/day-1.html}
+ # {Link|Day 1 - Let's Make a Dot|url=/day-1.html}
= # {Link|Day 2 - Let's Place the Dots in a Circle|url=/day-2.html}
= # {Link|Day 3 - Connecting the Dots|url=/day-3.html}
= # {Link|Day 4 - Let's Make a Tree|url=/day-4.html}Temporary fix for #9
In capture.coffee preplace all p tags with div.
Upon closer inspection it seems that p tag is never targeted in css sheet. Only classes are targeted. so replacing it with div doesn't affect any styling.
I consider this a temporary solution. Permanent one probably requires changes to github.com/mdgriffith/elm-ui
index bdbd6c6..d630128 100644
--- a/src/capture.coffee
+++ b/src/capture.coffee
@@ -38,13 +38,16 @@ do () =>
= await page.goto url, waitUntil: "networkidle2"
=
= html =
- (await page.content ``).replace "</body>", """
- <script src="/assets/index.js"></script>
- <script>
- Elm.Main.init()
- </script>
- </body>
- """
+ (await page.content ``)
+ .replace /<p\b/gi, "<div"
+ .replace /<\/p\b/gi, "</div"
+ .replace "</body>", """
+ <script src="/assets/index.js"></script>
+ <script>
+ Elm.Main.init()
+ </script>
+ </body>
+ """
=
= await new Promise (resolve, reject) ->
= FS.writeFile path, html, (error) ->Use external stylesheet to load Google fonts to avoid FOAC
Use Montserrat font for all texts except Monospace, Code and Terminal.
Justify text in paragraphs.
index fb384fe..1f43711 100644
--- a/container.html
+++ b/container.html
@@ -3,10 +3,13 @@
= <head>
= <meta charset="utf-8">
= <title>Software Garden</title>
+ <style>
+ @import url('https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Source+Code+Pro:300,400,700');
+ </style>
= </head>
= <body>
= <!-- <div id="app-container"></div> -->
- <script src="/assets/index.js"></script>
+ <script src="/built/index.js"></script>
= <script>
= Elm.Main.init()
= </script>index 1330ef5..b3c69f2 100755
--- a/scripts/build
+++ b/scripts/build
@@ -14,7 +14,7 @@ npx elm make src/Main.elm --output built/index.js
=
=cp -r content/ public/content/
=cp -r assets/ public/assets/
-cp -r built/* public/assets/
+cp -r built/ public/built/
=cp -r .well-known/ public/.well-known/
=
=index 987f698..d3e4036 100755
--- a/scripts/develop
+++ b/scripts/develop
@@ -9,4 +9,10 @@ do
= npx elm make --output "public/${example}.html" "${example}"
=done
=
-npx elm-live src/Main.elm --pushstate --port 8001 -- --debug
+npx elm-live src/Main.elm \
+ --pushstate \
+ --start-page container.html \
+ --port 8001 \
+ -- \
+ --output built/index.js \
+ --debugindex 782b5a7..64059a0 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -302,6 +302,7 @@ view model =
= [ -- Below is a hack that enables static site generation
= Element.htmlAttribute (Html.Attributes.id "app-container")
= , Element.htmlAttribute (Html.Attributes.lang "en")
+ , Font.family [ Font.typeface "Montserrat", Font.sansSerif ]
= ]
= content.body
= ]index 0a42947..18fef27 100644
--- a/src/Mark/Custom.elm
+++ b/src/Mark/Custom.elm
@@ -55,6 +55,7 @@ paragraph =
= [ Element.paddingXY 0 10
= , Element.spacing 12
= , css "hyphens" "auto"
+ , Font.justify
= ]
= (content model)
= in
@@ -71,7 +72,7 @@ monospace =
= , Font.size 16
= , Font.color colors.maroon
= , Font.family
- [ sourceCodePro
+ [ Font.typeface "Source Code Pro"
= , Font.monospace
= ]
= , Element.scrollbarY
@@ -95,7 +96,7 @@ code =
= , Border.color colors.charcoal
= , css "page-break-inside" "avoid"
= , Font.family
- [ sourceCodePro
+ [ Font.typeface "Source Code Pro"
= , Font.monospace
= ]
= , css "page-break-inside" "avoid"
@@ -116,7 +117,7 @@ code =
= [ Element.width Element.fill
= , Font.size 16
= , Font.family
- [ sourceCodePro
+ [ Font.typeface "Source Code Pro"
= , Font.monospace
= ]
= ]
@@ -187,7 +188,7 @@ terminal =
= , Background.color colors.charcoal
= , css "page-break-inside" "avoid"
= , Font.family
- [ sourceCodePro
+ [ Font.typeface "Source Code Pro"
= , Font.monospace
= ]
= , css "page-break-inside" "avoid"
@@ -207,7 +208,7 @@ terminal =
= [ Element.width Element.fill
= , Font.size 16
= , Font.family
- [ sourceCodePro
+ [ Font.typeface "Source Code Pro"
= , Font.monospace
= ]
= ]
@@ -228,7 +229,7 @@ terminal =
= }
= , Font.size 14
= , Font.family
- [ sourceCodePro
+ [ Font.typeface "Source Code Pro"
= , Font.monospace
= ]
= , Font.color colors.gray
@@ -312,9 +313,10 @@ note =
=emphasize : Mark.Block (model -> Element msg)
=emphasize =
= let
- render element model =
+ render elements model =
= model
- |> element
+ |> elements
+ |> Element.paragraph []
= |> Element.el
= [ Element.spacing 30
= , Font.bold
@@ -326,7 +328,7 @@ emphasize =
= in
= Mark.block "Emphasize"
= render
- paragraph
+ text
=
=
=image : Mark.Block (model -> Element msg)
@@ -375,7 +377,7 @@ text =
= , drop
= ]
= , code =
- [ Font.family [ sourceCodePro, Font.monospace ]
+ [ Font.family [ Font.typeface "Source Code Pro", Font.monospace ]
= , css "font-size" "0.8em"
= , Font.color colors.maroon
= , Element.paddingEach
@@ -500,14 +502,6 @@ css property value =
= )
=
=
-sourceCodePro : Font.Font
-sourceCodePro =
- Font.external
- { url = "https://fonts.googleapis.com/css?family=Source+Code+Pro:200,400,700&subset=latin-ext"
- , name = "Source Code Pro"
- }
-
-
=colors =
= { maroon = Element.rgb 0.7 0 0
= , gray = Element.rgb 0.8 0.8 0.8index bdbd6c6..ae3b622 100644
--- a/src/capture.coffee
+++ b/src/capture.coffee
@@ -39,7 +39,7 @@ do () =>
=
= html =
= (await page.content ``).replace "</body>", """
- <script src="/assets/index.js"></script>
+ <script src="/built/index.js"></script>
= <script>
= Elm.Main.init()
= </script>Upgrade feathericons/elm-feather to 1.3.0
index b321d9d..439720e 100644
--- a/elm.json
+++ b/elm.json
@@ -19,7 +19,7 @@
= "elm-community/maybe-extra": "5.0.0",
= "elm-community/result-extra": "2.2.1",
= "elm-explorations/markdown": "1.0.0",
- "feathericons/elm-feather": "1.2.0",
+ "feathericons/elm-feather": "1.3.0",
= "ianmackenzie/elm-geometry": "1.2.1",
= "ianmackenzie/elm-geometry-svg": "1.0.2",
= "mdgriffith/elm-markup": "2.0.5",Correct the vertical alignment of Key, Icon and inline Code
After changing the font they were a little bit off.
index 18fef27..b49f7d3 100644
--- a/src/Mark/Custom.elm
+++ b/src/Mark/Custom.elm
@@ -377,8 +377,10 @@ text =
= , drop
= ]
= , code =
- [ Font.family [ Font.typeface "Source Code Pro", Font.monospace ]
- , css "font-size" "0.8em"
+ [ Font.family
+ [ Font.typeface "Source Code Pro"
+ , Font.monospace
+ ]
= , Font.color colors.maroon
= , Element.paddingEach
= { top = 3
@@ -409,8 +411,7 @@ icon =
= )
= |> Element.el
= [ Element.padding 4
-
- -- , Element.width (Element.px 12)
+ , css "vertical-align" "middle"
= ]
= )
= |> Mark.inlineString "name"
@@ -427,8 +428,8 @@ key =
= )
= |> Element.row
= [ Element.paddingEach
- { top = 6
- , bottom = 4
+ { top = 7
+ , bottom = 7
= , right = 8
= , left = 8
= }
@@ -436,7 +437,7 @@ key =
= , Border.rounded 3
= , Font.variant Font.smallCaps
= , Font.size 12
- , Element.moveUp 2
+ , Element.moveUp 4
= , Font.bold
= , Background.color colors.charcoal
= , Font.color colors.white
Commits: 1
Correct typo
index 6dbdda5..4c3c26d 100644
--- a/content/preparation.txt
+++ b/content/preparation.txt
@@ -34,7 +34,7 @@ and follow the installation instructions.
=
=Some of the tools we use with Elm require Node.js. Go to the {Link|Node.js|url=https://nodejs.org/en/} website and install the current version (the green button on the right)
=
-We will need to use the {Definition|term=terminal|definiens=a program that let's you enter commands for your computer} {Icon|name=terminal} a little bit. Open Lunchpad (the icon with a rocket) and type {Code|terminal}, like this:
+We will need to use the {Definition|term=terminal|definiens=a program that let's you enter commands for your computer} {Icon|name=terminal} a little bit. Open Launchpad (the icon with a rocket) and type {Code|terminal}, like this:
=
=| Image
= src = /assets/mac-launchpad-terminal.png
Commits: 3
Edits to preparation
index 9513bc3..6dbdda5 100644
--- a/content/preparation.txt
+++ b/content/preparation.txt
@@ -1,21 +1,22 @@
=| Title
= Before the course begins
=
+During the workshop you will learn how to write and publish a computer program running in a web browser. Please follow this instructions before we start, so that we have more time to focus on programming. It shouldn't take you more than 30 minutes {Icon|name=watch} and can save us several hours together.
+
=| Note
= This setup instructions are based on an assumption that you are using a Mac.
=
- If you are using Linux or BSD, then you probably know how to install stuff.
-
- If you are using anything else, then... well, good luck.
+ If you are using Linux or BSD, then feel free to ask us for help.
=
- The rest of the instructions should work with any platform.
+ If you are using anything else (like Windows), then we are sorry, but we do not support your operating system. A Mac, Linux or BSD are required for the workshop. If you don't have one, we can advise you on getting and setting up a Linux machine.
=
=| Header
= Setup
=
-We will need
+You will need
=
=| List
+ - a Mac or Linux laptop,
= - a text editor (I use Atom)
= - and the Elm programming language
=
@@ -33,17 +34,14 @@ and follow the installation instructions.
=
=Some of the tools we use with Elm require Node.js. Go to the {Link|Node.js|url=https://nodejs.org/en/} website and install the current version (the green button on the right)
=
-We will need to use the {Definition|term=terminal|definiens=a program that let's you enter commands for your computer} {Icon|name=terminal} a little bit.
-
-| Note
- TODO: Explain how to open a terminal
+We will need to use the {Definition|term=terminal|definiens=a program that let's you enter commands for your computer} {Icon|name=terminal} a little bit. Open Lunchpad (the icon with a rocket) and type {Code|terminal}, like this:
=
=| Image
= src = /assets/mac-launchpad-terminal.png
= description = Here is a picture of terminal
=
=
-You should see a window like this
+Press {Key|enter} key or click the black icon with {Icon|name=terminal} sign. You should see a window similar to this:
=
=
=| Image
@@ -56,7 +54,7 @@ Type the following in the terminal.
=| Terminal
= elm repl
=
-Then press the {Key|enter} key. Note that the command line changes. You should see something like this
+Then press the {Key|enter} key. Note that the command line changes. You should see something like this:
=
=| Terminal
= ---- Elm 0.19.0 --------------------------------------------------
@@ -94,17 +92,18 @@ The command line should get back to its initial state.
=
=Computer Programs are represented as text, so the text editor is the most fundamental tool of a programmer. There is a lot of good text editors, but to get you started, we will use Atom here.
=
-Go to {Link|atom.io|url=https://atom.io} and download the editor. After installation on Mac, open it and from Atom menu choose Install Shell Commands.
+| Note
+ If you are already using a text editor that you like, feel free to continue using it during the workshop.
+
+Go to {Link|atom.io|url=https://atom.io} and download the editor. After installation, open it and from {Code|Atom} menu choose {Code|Install Shell Commands}.
=
-One last thing we need is Elm support for Atom editor. You can install it by typing in the terminal:
+One last thing we need is the Elm support for the Atom editor. You can install it by entering the following command in the terminal:
=
=| Terminal
= apm install language-elm
=
-APM is the Atom Package Manager, but don't pay much attention to it. We won't be using it again.
-We are all set.
-
-Got so far? Congratulations!
+| Note
+ APM is the Atom Package Manager, but don't pay much attention to it. We won't be using it again.
=
=| Emphasize
- You are ready for {Link|Day 1|url=/day-1.html}!
+ Congratulations! We are all set and you are ready for {Link|Day 1|url=/day-1.html}!Enable hyphenation in paragraphs
index e6a9f4f..9a9da15 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -301,6 +301,7 @@ view model =
= [ Element.layout
= [ -- Below is a hack that enables static site generation
= Element.htmlAttribute (Html.Attributes.id "app-container")
+ , Element.htmlAttribute (Html.Attributes.lang "en")
= ]
= content.body
= ]index d8b37ab..73c5f76 100644
--- a/src/Mark/Custom.elm
+++ b/src/Mark/Custom.elm
@@ -54,6 +54,7 @@ paragraph =
= Element.paragraph
= [ Element.paddingXY 0 10
= , Element.spacing 12
+ , css "hyphens" "auto"
= ]
= (content model)
= inCorrect typo
index a8e1794..5fce82e 100644
--- a/content/day-1.txt
+++ b/content/day-1.txt
@@ -63,7 +63,7 @@ It may take few seconds, but eventually this command should open a new Atom edit
=
= import Html
=
- main=
+ main =
= Html.text "Hello, Tree"
=
=Then enter the following command in the terminal:
Commits: 2
Change the port number of ELm Live ran by the develop script to 8001
So we can run Elm reactor on standard port 8000 at the same time.
index bf51dda..987f698 100755
--- a/scripts/develop
+++ b/scripts/develop
@@ -9,4 +9,4 @@ do
= npx elm make --output "public/${example}.html" "${example}"
=done
=
-npx elm-live src/Main.elm --pushstate -- --debug
+npx elm-live src/Main.elm --pushstate --port 8001 -- --debugMake the text column wider by removing the horizontal padding
Also remove the Element.width attribute, which seems to take no effect (why?).
Also remove unused import of Element.Default from Main.
index 2e7510a..e6a9f4f 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -41,7 +41,6 @@ import Html.Attributes
=import Http
=import Mark
=import Mark.Custom
-import Mark.Default
=import Parser
=import Parser.Advanced
=import Result.Extra as Result
@@ -408,9 +407,8 @@ document =
= |> List.map (\child -> child model)
= |> Element.textColumn
= [ Element.centerX
- , Element.width (Element.maximum 800 Element.fill)
= , Element.spacing 20
- , Element.padding 80
+ , Element.paddingXY 0 80
= ]
= |> View title
=
Commits: 8
Fix performance issues (closes #2)
The parsing of the markup is now performed only after the content is fetched, not as part of the view function like before.
To make it work without too much type-system-fu, the part of the model containing the examples is separated in it's own row (model.examples) with it's own type (Examples.Model). Along with it the new Examples module exposes:
update : Examples.Msg -> Examples.Model -> (Examples.Model, Cmd
Examples.Msg)
subscriptions : Examples.Model -> Sub Examples.Msg
init : (Examples.Model, Cmd Examples.Msg)
That way Main was very much simplified.
new file mode 100644
index 0000000..4ebd806
--- /dev/null
+++ b/src/Examples.elm
@@ -0,0 +1,125 @@
+module Examples exposing (Model, Msg(..), init, subscriptions, update)
+
+import Examples.CartesianCoordinates
+import Examples.Circle
+import Examples.Counter
+import Examples.Dot as Dot
+import Examples.DotInElement as DotInElement
+import Examples.DotWithViewBox as DotWithViewBox
+import Examples.Gradient
+import Examples.Line
+import Examples.MultipleDots as MultipleDots
+import Examples.NestedTransformations
+import Examples.PolarCoordinates
+import Examples.RosetteTypedTransformations
+import Examples.Spiral
+import Examples.Transformations
+import Examples.Tree
+import Examples.ViewBox
+
+
+type alias Model =
+ { counter : Examples.Counter.Model
+ , transformations : Examples.Transformations.Model
+ , nestedTransformations : Examples.NestedTransformations.Model
+ , cartesianCoordinates : Examples.CartesianCoordinates.Model
+ , polarCoordinates : Examples.PolarCoordinates.Model
+ , tree : Maybe Examples.Tree.Model
+ , viewBox : Examples.ViewBox.Model
+ }
+
+
+type Msg
+ = CounterMsg Examples.Counter.Msg
+ | TransformationsMsg Examples.Transformations.Msg
+ | NestedTransformationsMsg Examples.NestedTransformations.Msg
+ | CartesianCoordinatesMsg Examples.CartesianCoordinates.Msg
+ | PolarCoordinatesMsg Examples.PolarCoordinates.Msg
+ | ToggleTree (Maybe Examples.Tree.Model)
+ | TreeMsg Examples.Tree.Msg
+ | ViewBoxMsg Examples.ViewBox.Msg
+
+
+init : ( Model, Cmd Msg )
+init =
+ ( { counter = Examples.Counter.init
+ , transformations = Examples.Transformations.init
+ , nestedTransformations = Examples.NestedTransformations.init
+ , cartesianCoordinates = Examples.CartesianCoordinates.init
+ , polarCoordinates = Examples.PolarCoordinates.init
+ , tree = Nothing
+ , viewBox = Examples.ViewBox.init
+ }
+ , Cmd.none
+ )
+
+
+update : Msg -> Model -> ( Model, Cmd Msg )
+update msg model =
+ case msg of
+ CounterMsg m ->
+ ( { model | counter = Examples.Counter.update m model.counter }
+ , Cmd.none
+ )
+
+ TransformationsMsg m ->
+ ( { model
+ | transformations =
+ Examples.Transformations.update m model.transformations
+ }
+ , Cmd.none
+ )
+
+ NestedTransformationsMsg m ->
+ ( { model
+ | nestedTransformations =
+ Examples.NestedTransformations.update m model.nestedTransformations
+ }
+ , Cmd.none
+ )
+
+ CartesianCoordinatesMsg m ->
+ ( { model
+ | cartesianCoordinates =
+ Examples.CartesianCoordinates.update m model.cartesianCoordinates
+ }
+ , Cmd.none
+ )
+
+ PolarCoordinatesMsg m ->
+ ( { model
+ | polarCoordinates =
+ Examples.PolarCoordinates.update m model.polarCoordinates
+ }
+ , Cmd.none
+ )
+
+ ToggleTree tree ->
+ ( { model | tree = tree }, Cmd.none )
+
+ TreeMsg m ->
+ case model.tree of
+ Just tree ->
+ Examples.Tree.update m tree
+ |> (\( newTree, treeCmd ) ->
+ ( { model | tree = Just newTree }
+ , Cmd.map TreeMsg treeCmd
+ )
+ )
+
+ Nothing ->
+ ( model, Cmd.none )
+
+ ViewBoxMsg m ->
+ ( { model | viewBox = Examples.ViewBox.update m model.viewBox }, Cmd.none )
+
+
+subscriptions : Model -> Sub Msg
+subscriptions model =
+ case model.tree of
+ Nothing ->
+ Sub.none
+
+ Just tree ->
+ Examples.Tree.subscriptions tree
+ |> Sub.map TreeMsgindex f8def73..315d5af 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -17,6 +17,7 @@ import Element.Border as Border
=import Element.Events
=import Element.Font as Font
=import Element.Input as Input
+import Examples
=import Examples.CartesianCoordinates
=import Examples.Circle
=import Examples.Counter
@@ -68,47 +69,57 @@ type alias Flags =
=type alias Model =
= { url : Url
= , key : Navigation.Key
- , markup : Maybe String
- , counter : Examples.Counter.Model
- , transformations : Examples.Transformations.Model
- , nestedTransformations : Examples.NestedTransformations.Model
- , cartesianCoordinates : Examples.CartesianCoordinates.Model
- , polarCoordinates : Examples.PolarCoordinates.Model
- , tree : Maybe Examples.Tree.Model
- , viewBox : Examples.ViewBox.Model
+ , content : Content
+ , examples : Examples.Model
= }
=
=
=type Msg
= = UrlRequested Browser.UrlRequest
+ | UrlRequested Browser.UrlRequest
= | UrlChanged Url
= | ContentFetched (Result Http.Error String)
- | CounterMsg Examples.Counter.Msg
- | TransformationsMsg Examples.Transformations.Msg
- | NestedTransformationsMsg Examples.NestedTransformations.Msg
- | CartesianCoordinatesMsg Examples.CartesianCoordinates.Msg
- | PolarCoordinatesMsg Examples.PolarCoordinates.Msg
- | ToggleTree (Maybe Examples.Tree.Model)
- | TreeMsg Examples.Tree.Msg
- | ViewBoxMsg Examples.ViewBox.Msg
+ | ExamplesMsg Examples.Msg
+
+
+type Content
+ = Loading
+ | FetchError Http.Error
+ | ParseError (List DeadEnd)
+ | Loaded (Examples.Model -> View)
+
+
+type alias DeadEnd =
+ Parser.Advanced.DeadEnd Mark.Context Mark.Problem
+
+
+type alias View =
+ { title : String
+ , body : Element Msg
+ }
+
+
+type alias Document =
+ Mark.Document (Examples.Model -> View)
=
=
=init : Flags -> Url -> Navigation.Key -> ( Model, Cmd Msg )
=init flags url key =
+ let
+ ( examplesModel, examplesCmd ) =
+ Examples.init
+ in
= ( { url = url
= , key = key
- , markup = Nothing
- , counter = Examples.Counter.init
- , transformations = Examples.Transformations.init
- , nestedTransformations = Examples.NestedTransformations.init
- , cartesianCoordinates = Examples.CartesianCoordinates.init
- , polarCoordinates = Examples.PolarCoordinates.init
- , tree = Nothing
- , viewBox = Examples.ViewBox.init
+ , content = Loading
+ , examples = examplesModel
= }
- , url
- |> Routes.parse
- |> loadContent
+ , Cmd.batch
+ [ url
+ |> Routes.parse
+ |> loadContent
+ , Cmd.map ExamplesMsg examplesCmd
+ ]
= )
=
=
@@ -117,15 +128,35 @@ view model =
= let
= content : View
= content =
- case model.markup of
- Nothing ->
+ case model.content of
+ Loading ->
= loadingView
=
- Just markup ->
- markup
- |> Mark.parse document
- |> Result.map (\render -> render model)
- |> Result.extract deadEndsView
+ FetchError error ->
+ (case error of
+ Http.BadUrl message ->
+ "Malformed URL: " ++ message
+
+ Http.Timeout ->
+ "Request timeout"
+
+ Http.NetworkError ->
+ "Network error"
+
+ Http.BadStatus status ->
+ "Bad response status: " ++ String.fromInt status
+
+ Http.BadBody message ->
+ "Malformed response body: " ++ message
+ )
+ |> Element.text
+ |> View "Software Garden : Error fetching content"
+
+ ParseError deadEnds ->
+ deadEndsView deadEnds
+
+ Loaded documentView ->
+ documentView model.examples
=
= loadingView : View
= loadingView =
@@ -291,88 +322,44 @@ update msg model =
= )
=
= UrlChanged url ->
- ( { model | url = url }
- , url
- |> Routes.parse
- |> loadContent
- )
-
- ContentFetched (Ok markup) ->
- ( { model | markup = Just markup }
- , Cmd.none
- )
-
- ContentFetched (Err err) ->
- ( { model | markup = Nothing }
- , Cmd.none
- )
-
- CounterMsg m ->
- ( { model | counter = Examples.Counter.update m model.counter }
- , Cmd.none
- )
-
- TransformationsMsg m ->
= ( { model
- | transformations =
- Examples.Transformations.update m model.transformations
+ | url = url
+ , content = Loading
= }
= , Cmd.none
= )
=
- NestedTransformationsMsg m ->
+ ContentFetched (Ok markup) ->
= ( { model
- | nestedTransformations =
- Examples.NestedTransformations.update m model.nestedTransformations
+ | content =
+ markup
+ |> Mark.parse document
+ |> Result.map Loaded
+ |> Result.extract ParseError
= }
= , Cmd.none
= )
=
- CartesianCoordinatesMsg m ->
- ( { model
- | cartesianCoordinates =
- Examples.CartesianCoordinates.update m model.cartesianCoordinates
- }
+ ContentFetched (Err error) ->
+ ( { model | content = FetchError error }
= , Cmd.none
= )
=
- PolarCoordinatesMsg m ->
- ( { model
- | polarCoordinates =
- Examples.PolarCoordinates.update m model.polarCoordinates
- }
- , Cmd.none
+ ExamplesMsg examplesMsg ->
+ let
+ ( examplesModel, examplesCmd ) =
+ Examples.update examplesMsg model.examples
+ in
+ ( { model | examples = examplesModel }
+ , Cmd.map ExamplesMsg examplesCmd
= )
=
- ToggleTree tree ->
- ( { model | tree = tree }, Cmd.none )
-
- TreeMsg m ->
- case model.tree of
- Just tree ->
- Examples.Tree.update m tree
- |> (\( newTree, treeCmd ) ->
- ( { model | tree = Just newTree }
- , Cmd.map TreeMsg treeCmd
- )
- )
-
- Nothing ->
- ( model, Cmd.none )
-
- ViewBoxMsg m ->
- ( { model | viewBox = Examples.ViewBox.update m model.viewBox }, Cmd.none )
-
=
=subscriptions : Model -> Sub Msg
=subscriptions model =
- case model.tree of
- Nothing ->
- Sub.none
-
- Just tree ->
- Examples.Tree.subscriptions tree
- |> Sub.map TreeMsg
+ model.examples
+ |> Examples.subscriptions
+ |> Sub.map ExamplesMsg
=
=
=loadContent : Route -> Cmd Msg
@@ -394,24 +381,14 @@ loadContent route =
= Cmd.none
=
=
-type alias DeadEnd =
- Parser.Advanced.DeadEnd Mark.Context Mark.Problem
-
-
-type alias View =
- { title : String
- , body : Element Msg
- }
-
-
-document : Mark.Document (Model -> View)
+document : Document
=document =
= let
= content :
= { title : String
- , children : List (Model -> Element Msg)
+ , children : List (Examples.Model -> Element Msg)
= }
- -> Model
+ -> Examples.Model
= -> View
= content { title, children } model =
= children
@@ -486,7 +463,7 @@ document =
= ]
=
= -- Embeded programs' blocks
- counter : Mark.Block (Model -> Element Msg)
+ counter : Mark.Block (Examples.Model -> Element Msg)
= counter =
= let
= render model =
@@ -496,14 +473,15 @@ document =
= [ Element.centerX
= , Element.centerY
= ]
- |> Element.map CounterMsg
+ |> Element.map Examples.CounterMsg
+ |> Element.map ExamplesMsg
= in
= Mark.stub "Counter" render
=
- dot : Mark.Block (Model -> Element Msg)
+ dot : Mark.Block (Examples.Model -> Element Msg)
= dot =
= let
- render : Dot.Config -> Model -> Element Msg
+ render : Dot.Config -> Examples.Model -> Element Msg
= render config model =
= Dot.ui config
= |> Element.html
@@ -520,7 +498,7 @@ document =
= (Mark.field "fill" Mark.string)
= |> Mark.map render
=
- dotInElement : Mark.Block (Model -> Element Msg)
+ dotInElement : Mark.Block (Examples.Model -> Element Msg)
= dotInElement =
= let
= render config model =
@@ -534,7 +512,7 @@ document =
= (Mark.field "cy" Mark.string)
= |> Mark.map render
=
- dotWithViewBox : Mark.Block (Model -> Element Msg)
+ dotWithViewBox : Mark.Block (Examples.Model -> Element Msg)
= dotWithViewBox =
= let
= render config model =
@@ -547,7 +525,7 @@ document =
= (Mark.field "viewBox" Mark.string)
= |> Mark.map render
=
- twoDots : Mark.Block (Model -> Element Msg)
+ twoDots : Mark.Block (Examples.Model -> Element Msg)
= twoDots =
= let
= render config model =
@@ -575,10 +553,10 @@ document =
= (Mark.field "secondCX" Mark.string)
= |> Mark.map render
=
- circle : Mark.Block (Model -> Element Msg)
+ circle : Mark.Block (Examples.Model -> Element Msg)
= circle =
= let
- render : Examples.Circle.Config -> Model -> Element Msg
+ render : Examples.Circle.Config -> Examples.Model -> Element Msg
= render config model =
= Examples.Circle.ui config
= in
@@ -592,7 +570,7 @@ document =
= (Mark.field "scatter" Mark.bool)
= |> Mark.map render
=
- line : Mark.Block (Model -> Element Msg)
+ line : Mark.Block (Examples.Model -> Element Msg)
= line =
= let
= render model =
@@ -604,7 +582,7 @@ document =
= in
= Mark.stub "Line" render
=
- gradient : Mark.Block (Model -> Element Msg)
+ gradient : Mark.Block (Examples.Model -> Element Msg)
= gradient =
= let
= render model =
@@ -616,7 +594,7 @@ document =
= in
= Mark.stub "Gradient" render
=
- transformations : Mark.Block (Model -> Element Msg)
+ transformations : Mark.Block (Examples.Model -> Element Msg)
= transformations =
= let
= render model =
@@ -625,11 +603,12 @@ document =
= |> Element.el
= [ Element.width Element.fill
= ]
- |> Element.map TransformationsMsg
+ |> Element.map Examples.TransformationsMsg
+ |> Element.map ExamplesMsg
= in
= Mark.stub "Transformations" render
=
- nestedTransformations : Mark.Block (Model -> Element Msg)
+ nestedTransformations : Mark.Block (Examples.Model -> Element Msg)
= nestedTransformations =
= let
= render model =
@@ -638,21 +617,23 @@ document =
= |> Element.el
= [ Element.width Element.fill
= ]
- |> Element.map NestedTransformationsMsg
+ |> Element.map Examples.NestedTransformationsMsg
+ |> Element.map ExamplesMsg
= in
= Mark.stub "NestedTransformations" render
=
- cartesianCoordinates : Mark.Block (Model -> Element Msg)
+ cartesianCoordinates : Mark.Block (Examples.Model -> Element Msg)
= cartesianCoordinates =
= let
= render model =
= model.cartesianCoordinates
= |> Examples.CartesianCoordinates.ui
- |> Element.map CartesianCoordinatesMsg
+ |> Element.map Examples.CartesianCoordinatesMsg
+ |> Element.map ExamplesMsg
= in
= Mark.stub "CartesianCoordinates" render
=
- polarCoordinates : Mark.Block (Model -> Element Msg)
+ polarCoordinates : Mark.Block (Examples.Model -> Element Msg)
= polarCoordinates =
= let
= render model =
@@ -661,11 +642,12 @@ document =
= |> Element.el
= [ Element.width Element.fill
= ]
- |> Element.map PolarCoordinatesMsg
+ |> Element.map Examples.PolarCoordinatesMsg
+ |> Element.map ExamplesMsg
= in
= Mark.stub "PolarCoordinates" render
=
- rosette : Mark.Block (Model -> Element Msg)
+ rosette : Mark.Block (Examples.Model -> Element Msg)
= rosette =
= let
= render model =
@@ -677,7 +659,7 @@ document =
= in
= Mark.stub "Rosette" render
=
- spiral : Mark.Block (Model -> Element Msg)
+ spiral : Mark.Block (Examples.Model -> Element Msg)
= spiral =
= let
= render model =
@@ -689,7 +671,7 @@ document =
= in
= Mark.stub "Spiral" render
=
- tree : Mark.Block (Model -> Element Msg)
+ tree : Mark.Block (Examples.Model -> Element Msg)
= tree =
= let
= render model =
@@ -701,11 +683,12 @@ document =
= , Element.width Element.fill
= ]
= |> BrowserWindow.window []
- |> Element.map TreeMsg
+ |> Element.map Examples.TreeMsg
+ |> Element.map ExamplesMsg
= in
= Mark.stub "Tree" render
=
- viewBox : Mark.Block (Model -> Element Msg)
+ viewBox : Mark.Block (Examples.Model -> Element Msg)
= viewBox =
= let
= render model =
@@ -714,7 +697,8 @@ document =
= |> Element.el
= [ Element.width Element.fill
= ]
- |> Element.map ViewBoxMsg
+ |> Element.map Examples.ViewBoxMsg
+ |> Element.map ExamplesMsg
= in
= Mark.stub "ViewBox" render
= inReset scroll position on URL change
index 315d5af..84aa27a 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -8,6 +8,7 @@ It fetches the Elm Markup file at /index.txt and renders it. There are a number
=
=import Basics.Extra exposing (curry)
=import Browser
+import Browser.Dom as Dom
=import Browser.Navigation as Navigation
=import BrowserWindow
=import Dict
@@ -46,6 +47,7 @@ import Parser.Advanced
=import Result.Extra as Result
=import Routes exposing (Route)
=import Svg.Attributes
+import Task
=import Transformations
=import Url exposing (Url)
=
@@ -75,7 +77,7 @@ type alias Model =
=
=
=type Msg
- = UrlRequested Browser.UrlRequest
+ = NoOp
= | UrlRequested Browser.UrlRequest
= | UrlChanged Url
= | ContentFetched (Result Http.Error String)
@@ -309,6 +311,11 @@ view model =
=update : Msg -> Model -> ( Model, Cmd Msg )
=update msg model =
= case msg of
+ NoOp ->
+ ( model
+ , Cmd.none
+ )
+
= UrlRequested (Browser.Internal url) ->
= ( model
= , url
@@ -326,7 +333,13 @@ update msg model =
= | url = url
= , content = Loading
= }
- , Cmd.none
+ , Cmd.batch
+ [ url
+ |> Routes.parse
+ |> loadContent
+ , Dom.setViewport 0 0
+ |> Task.perform (always NoOp)
+ ]
= )
=
= ContentFetched (Ok markup) ->Various edits to days 1 and 2
Also nicer Code blocks, new Terminal block and other layout / typography improvements.
index 31b4007..a8e1794 100644
--- a/content/day-1.txt
+++ b/content/day-1.txt
@@ -9,8 +9,9 @@
= Today we are going to learn about
=
= | List
+ -> Setting up an Elm program
= -> Scalable Vector Graphics
- -> Cartesian Coordinates Systems
+ -> Cartesian coordinates systems
= -> Layouts with Elm UI
= -> Lists
=
@@ -25,33 +26,36 @@ So the first step is to create a directory for our new program. Let's call it "s
=
=In the terminal type:
=
-| Monospace
+| Terminal
= mkdir software-garden/
=
-press enter key. Then type:
+press {Key|enter} key. Then type:
=
-| Monospace
+| Terminal
= cd software-garden/
=
=| Note
- Press enter again. Generally when we ask you to type something in a terminal, we imply that you press enter afterwards. That's how you signal that you are done typing and expect computer to process your command. Alternatively we call it entering a command.
+ Press {Key|enter} again. Generally when we ask you to type something in a terminal, we imply that you press enter afterwards. That's how you signal that you are done typing and expect computer to process your command. Alternatively we call it entering a command.
+
+ In this case the first command ({Code|mkdir}) will *m*a*k*e our new *dir*ectory and the second ({Code|cd}) will set it as the *c*urrent *d*irectory, so that subsequent commands will be executed in the context of {Code|software-garden//} directory.
+
+ Don't worry about the details too much. Just remember that if you close your terminal and then open it again, you will need to enter:
+
+ {Code|cd software-garden//}
=
- In this case the first command will create our new directory and the second will make it the current directory, so that subsequent commands will be executed in it's context. Again, don't worry about the details, but if you close your terminal and then open it again, you will need to enter `cd software-garden/` to set this context again.
+ to set the current directory again.
=
=We can easily create a new program by entering a following command:
=
-| Monospace
+| Terminal
= elm init
=
=Then to create the file which will contain our source code, type:
=
-| Monospace
+| Terminal
= atom src/Main.elm
=
-This command should open a new Atom window with an empty text file. If you prefer to use different editor, feel free to do so.
-
-| Header
- Main.elm
+It may take few seconds, but eventually this command should open a new Atom editor window with an empty text file. If you prefer to use different editor, feel free to do so. Once the editor is ready, type the code belowand save the file.
=
=| Code
= module Main exposing (main)
@@ -62,28 +66,22 @@ This command should open a new Atom window with an empty text file. If you prefe
= main=
= Html.text "Hello, Tree"
=
-Type the above in the editor and save the file.
-To run the program, type the following command in the terminal:
+Then enter the following command in the terminal:
=
-| Monospace
+| Terminal
= elm reactor
=
-This command will start the Elm reactor, which will allow you to run your program in the web browser. Note that it won't give you the command line back - it will run as long as you don't stop it.
-
-
-| Emphasize
- Voila!
-
-
-Open following address in the web browser:
+This command will start the Elm Reactor, which will allow you to run your program in the web browser. Note that it won't give you the command prompt back - it will run in the terminal until you stop it. Leave it running and open the following address in the web browser:
=
=| Emphasize
= {Link|http:////localhost:8000//src//Main.elm|url=http://localhost:8000/src/Main.elm}
=
-| Note
- *TODO*: Make the link display // characters
+You should see something like this:
+
+| Window
+ Hello, Tree!
=
-Note that the same address was printed by the Elm reactor.
+This was a warmup. Let's try something more challenging.
=
=| Header
= The problem
@@ -93,7 +91,7 @@ We want to display a dot at the center of the screen, like this:
=| Window
= | DotWithViewBox
= background=none
- fill=black
+ fill=skyblue
= viewBox=-300 -300 600 600
=
=Below is the complete code to draw a dot like this, but don't type it in your editor yet! We are going to recreate it together, step by step.
@@ -114,6 +112,7 @@ Below is the complete code to draw a dot like this, but don't type it in your ed
= [ Svg.Attributes.r "10"
= , Svg.Attributes.cx "0"
= , Svg.Attributes.cy "0"
+ , Svg.Attributes.color "skyblue"
= ]
= []
= ]
@@ -123,18 +122,21 @@ Below is the complete code to draw a dot like this, but don't type it in your ed
= , Element.height Element.fill
= ]
=
-We are going to use a technology called SVG (Scalable Vector Graphics).
+We are going to use a technology called *SVG (Scalable Vector Graphics)*. It's all about drawing shapes on the screen. Let's install an Elm Package to help us work with SVG. Stop the reactor running in the terminal by pressing {Key|ctrl} + {Key|c} and then enter the following command:
=
-*Scalable icon should be here*
+| Terminal
+ elm install elm/svg
=
-S for Scalable
+| Note
+ To press {Key|ctrl} + {Key|c} means:
=
-Its all about drawing shapes on the screen. Let's install an Elm Package to help us work with SVG. Stop the reactor running in the terminal by pressing {Code|CTRL} + {Code|C} and then type the following:
+ | List
+ - Press and hold {Key|ctrl} button (on some keyboards it's labeled as {Key|control}).
+ - While holding press the {Key|c} button once.
+ - Finally release the {Key|ctrl}.
=
-| Monospace
- elm install elm/svg
=
-Now that we have {Code|elm//svg} installed, we can write the code that will draw our dot:
+Now that we have {Code|elm//svg} installed, we can write the code that will draw a dot for us. Change the code to look like this:
=
=| Code
= module Main exposing (main)
@@ -149,13 +151,13 @@ Now that we have {Code|elm//svg} installed, we can write the code that will draw
= [ Svg.Attributes.r "10" ] []
= ]
=
-Start the reactor again:
+Start the reactor again by entering:
=
-| Monospace
+| Terminal
= elm reactor
=
=| Note
- You can press the up arrow {Icon|name=arrow-up} on the keyboard to retrieve commands entered previously.
+ You can press the up arrow {Key|▲} on the keyboard to retrieve commands entered previously.
=
=Reload the browser. You should see something like this:
=
@@ -164,11 +166,14 @@ Reload the browser. You should see something like this:
= background=none
= fill=black
=
-Something isn't quite right here. Why is the dot in the corner?
+At this point we have a dot on the screen - it's at the top left corner of the viewport (the area of the browser window where the content is displayed). We can see only a quarter of it - the rest is outside of the viewport.
=
-There are two reasons. First, because the center of the dot is at a point called the {Code|origin} or {Code|(0, 0)}. We'll explore exactly what this means later.
+| Note
+ TODO: Picture // example showing the viewport
=
-Second, it's because the SVG space doesn't fill the browser window. Don't believe me? We can see that the space doesn't fill the window by changing the background color of the SVG element.
+It's a good start, but the dot is not in the center of the screen yet. Let's try to understand why it is where it is.
+
+First we have to realize that the dot is inside an SVG element that itself has a position, width and height. We can make the background of the SVG element pink to see where it is:
=
=| Code
= module Main exposing (main)
@@ -184,20 +189,31 @@ Second, it's because the SVG space doesn't fill the browser window. Don't believ
= []
= ]
=
-When you reload the browser, you'll see this:
+Realoading the browser should reveal something like this:
=
=| Window
= | Dot
= background=pink
= fill=black
=
+Since the dot is inside the SVG element, and the SVG element doesn't cover the center of the screen, it's impossible to place the dot at the center. Before anything else, let's make the SVG fill entire {Definition|term=viewport|definiens=the area of the browser window where the content is displayed}.
+
+There are two reasons. First, because the center of the dot is at a point called the {Code|origin} or {Code|(0, 0)}. We'll explore exactly what this means later.
+
+Second, it's because the SVG space doesn't fill the browser window.
+
+
+When you reload the browser, you'll see this:
+
+
+
=Now we can clearly see that our SVG element (which is pink) does not fill the screen.
=
=We can easily correct this with the help of a very handy elm package, {Code|mdgriffith//elm-ui}.
=
=Install it with the terminal.
=
-Press {Code|CTRL + C} to stop the elm-reactor, type {Code|elm install mdgriffith//elm-ui} and press {Code|Enter}.
+Press {Key|ctrl} + {Key|c} to stop the elm-reactor, type {Code|elm install mdgriffith//elm-ui} and press {Key|enter}.
=
=Then in {Code|src//Main.elm} add the necessary import {Code|import Element} and change {Code|Main} to look like this:
=
@@ -239,7 +255,7 @@ Reload the browser to confirm your hypothesis. Voila! The SVG space now fills th
=
=It should now be possible to place our dot in the center of the screen. But, how exactly will we achieve it?
=
-Let's return to the idea we introduced earlier, that the center of the dot is at a point called the {Code|origin} or {Code|(0, 0)}. What eactly does this mean?
+Let's return to the idea we introduced earlier, that the center of the dot is at a point called the {Code|origin} or {Code|(0, 0)}. What exactly does this mean?
=
=* a picture of a vase on a table should be here *
=index 8114efd..1ab9957 100644
--- a/content/day-3.txt
+++ b/content/day-3.txt
@@ -18,4 +18,7 @@
=| Header
= The Problem
=
-...
+| Note
+ *We are still working on this content.*
+
+ Stay tuned {Icon|name=radio}index 751dc76..ea07e93 100644
--- a/content/day-4.txt
+++ b/content/day-4.txt
@@ -14,4 +14,7 @@
=| Header
= The Problem
=
-...
+| Note
+ *We are still working on this content.*
+
+ Stay tuned {Icon|name=radio}index d6be380..32fc805 100644
--- a/content/day-5.txt
+++ b/content/day-5.txt
@@ -15,4 +15,7 @@
=| Header
= The Problem
=
-...
+| Note
+ *We are still working on this content.*
+
+ Stay tuned {Icon|name=radio}index 84aa27a..2e7510a 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -409,6 +409,8 @@ document =
= |> Element.textColumn
= [ Element.centerX
= , Element.width (Element.maximum 800 Element.fill)
+ , Element.spacing 20
+ , Element.padding 80
= ]
= |> View title
=
@@ -430,7 +432,7 @@ document =
= [ Element.width Element.fill
= , Element.paddingXY 0 32
= , Font.center
- , Font.size 86
+ , Font.size 64
= , Font.extraBold
= ]
= in
@@ -444,13 +446,14 @@ document =
= , Mark.Custom.paragraph
= , Mark.Custom.monospace
= , Mark.Custom.code
+ , Mark.Custom.terminal
= , Mark.Custom.note
= , Mark.Custom.emphasize
= , Mark.Custom.list
= , Mark.Custom.image
= ]
= ++ examples
- ++ [ Mark.Custom.window (Mark.oneOf examples)
+ ++ [ Mark.Custom.window (Mark.oneOf (Mark.Custom.paragraph :: examples))
= , Mark.Custom.row (Mark.manyOf examples)
= ]
= )index bf2f03e..d8b37ab 100644
--- a/src/Mark/Custom.elm
+++ b/src/Mark/Custom.elm
@@ -11,6 +11,7 @@ module Mark.Custom exposing
= , note
= , paragraph
= , row
+ , terminal
= , text
= , title
= , window
@@ -51,7 +52,8 @@ paragraph =
= let
= render content model =
= Element.paragraph
- [ Element.paddingXY 0 24
+ [ Element.paddingXY 0 10
+ , Element.spacing 12
= ]
= (content model)
= in
@@ -63,63 +65,177 @@ paragraph =
=monospace : Mark.Block (model -> Element msg)
=monospace =
= Mark.Default.monospace
- [ Element.padding 20
- , Font.size 14
- , Element.width Element.fill
- , Border.color colors.gray
- , Border.width 3
- , Border.rounded 5
+ [ Element.width Element.fill
+ , Element.padding 20
+ , Font.size 16
+ , Font.color colors.maroon
= , Font.family
= [ sourceCodePro
= , Font.monospace
= ]
= , Element.scrollbarY
+ , css "page-break-inside" "avoid"
= ]
=
=
+type alias File =
+ { path : String
+ , line : Int
+ }
+
+
=code : Mark.Block (a -> Element msg)
=code =
= let
- render string model =
- string
- |> String.split "\n"
- |> List.indexedMap
- (\n loc ->
- Element.row [ Element.spacing 10 ]
- [ Element.el
- [ Font.color colors.gray
- , Font.extraLight
- , Element.width (Element.px 40)
- , css "user-select" "none"
- , css "-webkit-user-select" "none"
- , css "-ms-user-select" "none"
- , css "-webkit-touch-callout" "none"
- , css "-o-user-select" "none"
- , css "-moz-user-select" "none"
- ]
- (Element.text (String.fromInt (n + 1)))
- , Element.el
- [ Element.width Element.fill
- ]
- (Element.text loc)
+ render { path, line } contents model =
+ Element.column
+ [ Border.width 3
+ , Border.rounded 5
+ , Border.color colors.charcoal
+ , css "page-break-inside" "avoid"
+ , Font.family
+ [ sourceCodePro
+ , Font.monospace
+ ]
+ , css "page-break-inside" "avoid"
+ ]
+ [ Element.row
+ [ Element.width Element.fill
+ , Background.color colors.charcoal
+ , Font.color colors.gray
+ ]
+ [ FeatherIcons.file
+ |> FeatherIcons.toHtml []
+ |> Element.html
+ |> Element.el
+ [ Element.height (Element.px 35)
+ , Element.padding 8
+ ]
+ , Element.el
+ [ Element.width Element.fill
+ , Font.size 16
+ , Font.family
+ [ sourceCodePro
+ , Font.monospace
= ]
- )
- |> Element.column
- [ Element.padding 20
- , Element.spacing 10
- , Element.width Element.fill
- , Border.color colors.gray
- , Border.width 3
- , Border.rounded 5
- , Font.size 18
- , Font.family
- [ sourceCodePro
- , Font.monospace
= ]
- , Element.scrollbarY
+ (Element.text path)
= ]
+ , contents
+ |> String.split "\n"
+ |> List.indexedMap
+ (\n loc ->
+ Element.row []
+ [ Element.el
+ [ Font.color colors.gray
+ , Font.extraLight
+ , Element.width (Element.px 40)
+ , Element.padding 10
+ , Font.alignRight
+ , css "user-select" "none"
+ , css "-webkit-user-select" "none"
+ , css "-ms-user-select" "none"
+ , css "-webkit-touch-callout" "none"
+ , css "-o-user-select" "none"
+ , css "-moz-user-select" "none"
+ ]
+ (n
+ |> (+) line
+ |> String.fromInt
+ |> Element.text
+ )
+ , Element.el
+ [ Element.width Element.fill
+ , Element.padding 10
+ ]
+ (Element.text loc)
+ ]
+ )
+ |> Element.column
+ [ Element.width Element.fill
+ , Font.size 16
+ , Element.scrollbarY
+ ]
+ ]
= in
+ -- FIXME: There is a weird bug where 1st line of contents gets rendered indented 4 spaces
+ -- Mark.block "Code"
+ -- identity
+ -- (Mark.startWith
+ -- render
+ -- (Mark.record2 "File"
+ -- File
+ -- (Mark.field "path" Mark.string)
+ -- (Mark.field "line" Mark.int)
+ -- )
+ -- Mark.multiline
+ -- )
= Mark.block "Code"
+ (render (File "src/Main.elm" 1))
+ Mark.multiline
+
+
+terminal : Mark.Block (a -> Element msg)
+terminal =
+ let
+ render content model =
+ Element.column
+ [ Border.width 3
+ , Border.rounded 5
+ , Border.color colors.charcoal
+ , css "page-break-inside" "avoid"
+ , Font.family
+ [ sourceCodePro
+ , Font.monospace
+ ]
+ , css "page-break-inside" "avoid"
+ ]
+ [ Element.row
+ [ Element.width Element.fill
+ , Background.color colors.charcoal
+ , Font.color colors.gray
+ ]
+ [ FeatherIcons.terminal
+ |> FeatherIcons.toHtml []
+ |> Element.html
+ |> Element.el
+ [ Element.height (Element.px 35)
+ , Element.padding 8
+ ]
+ , Element.el
+ [ Element.width Element.fill
+ , Font.size 16
+ , Font.family
+ [ sourceCodePro
+ , Font.monospace
+ ]
+ ]
+ (Element.text "terminal")
+ ]
+ , content
+ |> String.split "\n"
+ |> List.map Element.text
+ |> Element.column
+ [ Element.width Element.fill
+ , Element.padding 20
+ , Background.color colors.black
+ , Border.roundEach
+ { topLeft = 0
+ , topRight = 0
+ , bottomLeft = 3
+ , bottomRight = 3
+ }
+ , Font.size 18
+ , Font.family
+ [ sourceCodePro
+ , Font.monospace
+ ]
+ , Font.color colors.gray
+ , Element.scrollbarY
+ ]
+ ]
+ in
+ Mark.block "Terminal"
= render
= Mark.multiline
=
@@ -135,7 +251,7 @@ window block =
= [ Element.height (Element.px 400)
= , Element.width Element.fill
= ]
- |> BrowserWindow.window []
+ |> BrowserWindow.window [ css "page-break-inside" "avoid" ]
= in
= Mark.block "Window"
= render
@@ -177,8 +293,9 @@ note =
= , Element.width Element.fill
= , Border.width 1
= , Border.color colors.gray
- , Font.color colors.gray
+ , Element.alpha 0.6
= , Border.rounded 5
+ , Font.size 16
= ]
= in
= Mark.block "Note"
@@ -249,7 +366,24 @@ text =
= in
= Mark.Default.textWith
= { defaultTextStyle
- | inlines = defaultTextStyle.inlines ++ [ icon ]
+ | inlines =
+ defaultTextStyle.inlines
+ ++ [ icon
+ , definition
+ , key
+ , drop
+ ]
+ , code =
+ [ Font.family [ sourceCodePro, Font.monospace ]
+ , css "font-size" "0.8em"
+ , Font.color colors.maroon
+ , Element.paddingEach
+ { top = 3
+ , right = 5
+ , bottom = 2
+ , left = 5
+ }
+ ]
= }
=
=
@@ -259,16 +393,99 @@ icon =
= (\name model ->
= icons
= |> Dict.get name
- |> Maybe.map (FeatherIcons.toHtml [])
+ |> Maybe.map
+ (FeatherIcons.toHtml
+ [ Html.Attributes.height 14
+ , Html.Attributes.width 14
+ ]
+ )
= |> Maybe.map Element.html
= |> Maybe.withDefault
= (Element.text
= ("Icon not found: '" ++ name ++ "'")
= )
+ |> Element.el
+ [ Element.padding 4
+
+ -- , Element.width (Element.px 12)
+ ]
= )
= |> Mark.inlineString "name"
=
=
+key : Mark.Inline (model -> Element msg)
+key =
+ Mark.inline "Key"
+ (\name model ->
+ name
+ |> List.map
+ (\chunk ->
+ Mark.Default.textFragment chunk model
+ )
+ |> Element.row
+ [ Element.paddingEach
+ { top = 6
+ , bottom = 4
+ , right = 8
+ , left = 8
+ }
+ , Border.width 1
+ , Border.rounded 3
+ , Font.variant Font.smallCaps
+ , Font.size 12
+ , Element.moveUp 2
+ , Font.bold
+ , Background.color colors.charcoal
+ , Font.color colors.white
+ ]
+ )
+ |> Mark.inlineText
+
+
+drop : Mark.Inline (model -> Element msg)
+drop =
+ Mark.inline "Drop"
+ (\chunks model ->
+ chunks
+ |> List.map
+ (\chunk ->
+ Mark.Default.textFragment chunk model
+ )
+ |> Element.row
+ [ Element.paddingEach
+ { top = 8
+ , bottom = 0
+ , right = 8
+ , left = 8
+ }
+ , Font.size 40
+ , Element.spacing 0
+ , Element.alignLeft
+ ]
+ )
+ |> Mark.inlineText
+
+
+definition : Mark.Inline (model -> Element msg)
+definition =
+ Mark.inline "Definition"
+ (\term definiens model ->
+ term
+ |> Element.text
+ |> Element.el
+ [ Font.letterSpacing 1.1
+ , Font.shadow
+ { offset = ( 0, 0 )
+ , blur = 1
+ , color = colors.gray
+ }
+ , Element.htmlAttribute (Html.Attributes.title definiens)
+ ]
+ )
+ |> Mark.inlineString "term"
+ |> Mark.inlineString "definiens"
+
+
=
=-- Helpers
=
@@ -285,14 +502,17 @@ css property value =
=sourceCodePro : Font.Font
=sourceCodePro =
= Font.external
- { url = "https://fonts.googleapis.com/css?family=Source+Code+Pro:200,400&subset=latin-ext"
+ { url = "https://fonts.googleapis.com/css?family=Source+Code+Pro:200,400,700&subset=latin-ext"
= , name = "Source Code Pro"
= }
=
=
-colors : { gray : Element.Color, maroon : Element.Color, pink : Element.Color }
=colors =
= { maroon = Element.rgb 0.7 0 0
= , gray = Element.rgb 0.8 0.8 0.8
+ , charcoal = Element.rgb 0.2 0.2 0.2
= , pink = Element.rgb 1 0.6 0.6
+ , white = Element.rgb 1 1 1
+ , black = Element.rgb 0 0 0
+ , badass = Element.rgb255 0xBA 0xDA 0x55
= }Add general information about the workshop to index document
index 3565ea5..c2832b6 100644
--- a/content/index.txt
+++ b/content/index.txt
@@ -7,6 +7,10 @@
=| Emphasize
= A software development workshop for non-programmers
=
+Hello! We are running a workshop that will give you a glimpse into the way software is created. Our workshop is intended for people with no prior experience in programming and doesn't require any technical knowledge. Everybody is welcome!
+
+Below is material through which we will be going during the workshop. Only the first section is a required read before you come, but feel free to read more to get more prepared.
+
=| List
= # {Link|Before the course begins|url=/preparation.html}
= # {Link|Day 1 - Let's Make some Dots|url=/day-1.html}Merge branch 'master' into master-merge
Merge remote-tracking branch 'refs/remotes/origin/master'
Merge branch 'master-merge'
Some edits to preparations document
index 33c56c2..9513bc3 100644
--- a/content/preparation.txt
+++ b/content/preparation.txt
@@ -33,14 +33,16 @@ and follow the installation instructions.
=
=Some of the tools we use with Elm require Node.js. Go to the {Link|Node.js|url=https://nodejs.org/en/} website and install the current version (the green button on the right)
=
-We will need to use the terminal {Icon|name=terminal} a little bit. Don't be scared. It's easy.
+We will need to use the {Definition|term=terminal|definiens=a program that let's you enter commands for your computer} {Icon|name=terminal} a little bit.
+
+| Note
+ TODO: Explain how to open a terminal
=
=| Image
= src = /assets/mac-launchpad-terminal.png
= description = Here is a picture of terminal
=
=
-Mac Terminal app window
=You should see a window like this
=
=
@@ -51,12 +53,12 @@ You should see a window like this
=Type the following in the terminal.
=
=
-| Monospace
+| Terminal
= elm repl
=
-Note that the command line changes. You should see something like this
+Then press the {Key|enter} key. Note that the command line changes. You should see something like this
=
-| Monospace
+| Terminal
= ---- Elm 0.19.0 --------------------------------------------------
= Read <https://elm-lang.org/0.19.0/repl> to learn more: exit, help
= ------------------------------------------------------------------
@@ -64,12 +66,15 @@ Note that the command line changes. You should see something like this
=
=It's the Elm REPL (Read-Evaluate-Print Loop). Inside the REPL type.
=
-| Monospace
+| Terminal
= 2+2
=
-And expect to see
+| Note
+ Press {Key|enter} again. Generally when we ask you to type something in a terminal, we imply that you press enter afterwards. That's how you signal that you are done typing and expect computer to process your command. Alternatively we call it entering a command.
+
+You should see:
=
-| Monospace
+| Terminal
= ---- Elm 0.19.0 ----------------
= Read <https://elm-lang.org/0.19.0/repl> to learn more: exit, help
= --------------------------------
@@ -77,9 +82,11 @@ And expect to see
= 4 : number
= >
=
-Easy, huh?
-We will learn more about REPL later. For now type
-:exit to close it.
+Easy, huh? We will learn more about REPL later. For now enter the following command to close it.
+
+| Terminal
+ :exit
+
=The command line should get back to its initial state.
=
=| Header
@@ -91,7 +98,7 @@ Go to {Link|atom.io|url=https://atom.io} and download the editor. After installa
=
=One last thing we need is Elm support for Atom editor. You can install it by typing in the terminal:
=
-| Monospace
+| Terminal
= apm install language-elm
=
=APM is the Atom Package Manager, but don't pay much attention to it. We won't be using it again.
Commits: 3
Drop "functional" from the title of the workshops
index 39d8332..97ab3a3 100644
--- a/content/index.txt
+++ b/content/index.txt
@@ -5,7 +5,7 @@
= ⚘
=
=| Emphasize
- A functional programming workshop for non-programmers
+ A software development workshop for non-programmers
=
=| List
= # {Link|Before the course begins|url=/preparation.html}Create stub documents for days 3, 4 and 5
index 7ff4fc4..31b4007 100644
--- a/content/day-1.txt
+++ b/content/day-1.txt
@@ -2,7 +2,7 @@
= Day 1
=
=| Emphasize
- Let's make a Dot!
+ Let's Make Some Dots!
=
=
=| Note
@@ -12,6 +12,7 @@
= -> Scalable Vector Graphics
= -> Cartesian Coordinates Systems
= -> Layouts with Elm UI
+ -> Lists
=
=
=| Headernew file mode 100644
index 0000000..8114efd
--- /dev/null
+++ b/content/day-3.txt
@@ -0,0 +1,21 @@
+| Title
+ Day 3
+
+| Emphasize
+ Connecting the Dots
+
+
+| Note
+ Today we are going to lear about
+
+ | List
+ - Values and names
+ - Functions
+ - Modules
+ - Types
+ - SVG gradients
+
+| Header
+ The Problem
+
+...new file mode 100644
index 0000000..751dc76
--- /dev/null
+++ b/content/day-4.txt
@@ -0,0 +1,17 @@
+| Title
+ Day 3
+
+| Emphasize
+ Let's Make a Tree
+
+
+| Note
+ Today we are going to lear about
+
+ | List
+ - Recursion
+
+| Header
+ The Problem
+
+...new file mode 100644
index 0000000..d6be380
--- /dev/null
+++ b/content/day-5.txt
@@ -0,0 +1,18 @@
+| Title
+ Day 3
+
+| Emphasize
+ Let's Make the Tree Grow
+
+
+| Note
+ Today we are going to lear about
+
+ | List
+ - The Elm Architecture
+ - Animation frames
+
+| Header
+ The Problem
+
+...index 97ab3a3..a1195ae 100644
--- a/content/index.txt
+++ b/content/index.txt
@@ -9,6 +9,8 @@
=
=| List
= # {Link|Before the course begins|url=/preparation.html}
- # {Link|Day 1 - Let's Make a Dot|url=/day-1.html}
+ # {Link|Day 1 - Let's Make some Dots|url=/day-1.html}
= # {Link|Day 2 - Let's Place the Dots in a Circle|url=/day-2.html}
- # {Link|The rest - Work in progress|url=/rest.html}
+ # {Link|Day 3 - Connecting the Dots|url=/day-3.html}
+ # {Link|Day 4 - Let's Make a Tree|url=/day-4.html}
+ # {Link|Day 5 - Let's Make the Tree Grow|url=/day-5.html}Write a stub of About Us section
index a1195ae..3565ea5 100644
--- a/content/index.txt
+++ b/content/index.txt
@@ -14,3 +14,15 @@
= # {Link|Day 3 - Connecting the Dots|url=/day-3.html}
= # {Link|Day 4 - Let's Make a Tree|url=/day-4.html}
= # {Link|Day 5 - Let's Make the Tree Grow|url=/day-5.html}
+
+
+| Header
+ About us
+
+*Ted*: I'm going to be your teacher during the workshop. I've been working as a software developer for the best part of the last six years. Before that I've been a lawyer.
+
+I love the creativity of the software development and hope to share that passion with you.
+
+*Sam* is a co-author of the workshop. He is an Elm developer at {Link|itravel|url=https://www.itravel.de/} in Cologne, Germany.
+
+*Fana* is a coordinator of our project. She keeps us on the right track. Also she is taking care of our media presence.
Commits: 7
Correct the format of the code sample with pink background
index 9f2145e..b3585cc 100644
--- a/content/day-1.txt
+++ b/content/day-1.txt
@@ -171,10 +171,12 @@ Second, it's because the SVG space doesn't fill the browser window. Don't believ
= import Svg
= import Svg.Attributes
=
+
= main =
= Svg.svg [ Svg.Attributes.style "background: pink" ]
= [ Svg.circle
- [ Svg.Attributes.r "10" ] []
+ [ Svg.Attributes.r "10" ]
+ []
= ]
=
=When you reload the browser, you'll see this:Fix the line numbers in Code block to count from 1 instead of 0
index 193f700..bf2f03e 100644
--- a/src/Mark/Custom.elm
+++ b/src/Mark/Custom.elm
@@ -97,7 +97,7 @@ code =
= , css "-o-user-select" "none"
= , css "-moz-user-select" "none"
= ]
- (Element.text (String.fromInt n))
+ (Element.text (String.fromInt (n + 1)))
= , Element.el
= [ Element.width Element.fill
= ]Make the build script copy .well-known/ directory to public/
So we can pass the ECMA challenge.
index d9def3c..1330ef5 100755
--- a/scripts/build
+++ b/scripts/build
@@ -15,6 +15,7 @@ npx elm make src/Main.elm --output built/index.js
=cp -r content/ public/content/
=cp -r assets/ public/assets/
=cp -r built/* public/assets/
+cp -r .well-known/ public/.well-known/
=
=
=mkdir -p built/captured/Disable the pages CI task for custom-domain branch
It works, so we are going to merge it to master soon. No need for this entry anymore.
index 28e2f71..4472935 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -15,4 +15,3 @@ pages:
=
= only:
= - master
- - custom-domainMerge branch 'custom-domain' into 'master'
Setup software.garden domain
See merge request software-garden/software-garden.gitlab.io!5
Edit day-2
index edb1bc0..5cd1456 100644
--- a/content/day-2.txt
+++ b/content/day-2.txt
@@ -15,7 +15,7 @@
=| Header
= The Problem
=
-We want to place the dots in a circle, like this:
+Previously we have created a program that displays five colorful dots. Today we want to place our five dots in a circle, like this:
=
=| Window
= | Circle
@@ -26,12 +26,10 @@ We want to place the dots in a circle, like this:
= radius = 0 1
= scatter = False
=
-We will also make the dots have different colors... so it's more fun!
-
=| Header
= What does it mean to be placed on a circle?
=
-We all have some intuition about a circle. You can tell if you see one and if you have a steady hand, you can probably draw one with a pencil. It's a thing that looks like this:
+We all have some intuition about a circle. You can tell if you see one and if you have a steady hand, you can probably draw one with a pencil. It's a shape that looks like this:
=
=| Circle
= dots = 0
@@ -42,7 +40,7 @@ We all have some intuition about a circle. You can tell if you see one and if yo
= scatter = False
=
=
-But what is it that makes a circle what it is? First of all we have to realize that a circle has a center. It's a point that lays exactly in the middle:
+But what is it that makes a circle what it is? First of all we have to realize that every circle has a center. It's a point that lays exactly in the middle:
=
=| Circle
= dots = 0
@@ -52,8 +50,7 @@ But what is it that makes a circle what it is? First of all we have to realize t
= radius = 0 1
= scatter = False
=
-
-We can say that four or more things lay on a circle, if the distance to the center is the same for each of them, like this:
+We can say that four or more dots lay on a circle, if the distance to the center is the same for each of them, like this:
=
=| Circle
= dots = 5
@@ -64,9 +61,9 @@ We can say that four or more things lay on a circle, if the distance to the cent
= scatter = False
=
=| Note
- Why more than three? One, two or three things always lay on at least one circle, so it's not much of a challenge that way.
+ Why more than three? One, two or three dots always lay on at least one circle, so it's not much of a challenge that way.
=
- One thing lay on any circle that crosses the place. You could draw very many circles like that (in fact as many as you want).
+ One dot lay on any circle that crosses the place. You could draw very many circles like that (in fact as many as you want).
=
= Similar for two things. Not every circle will do, but still you could draw many different circles that they will lay on.
=
@@ -81,17 +78,28 @@ We can say that four or more things lay on a circle, if the distance to the cent
= TODO: An example showing that for three different points there will always be a circle that crosses them, see {Link|circumcircle|url=https://package.elm-lang.org/packages/ianmackenzie/elm-geometry/latest/Triangle2d#circumcircle}
=
=
-Because we are all about challenges, let's make five dots.
+Because we are all about challenges, let's make five dots. Also, we already have five dot's we created on {Link|Day 1|url=/day-1.html}, so why not reuse them?
=
-So, once again - we can say that several things (in our case dots) lay on a circle if the distance between each of them and the center of the circle is the same. We call this distance /a *radius* of a circle/.
+So, once again - we can say that several things (in our case dots) lay on a circle if the distance between each of them and the center of the circle is the same.
=
=| Note
- Matematician would say:
+ A matematician would say:
=
= /Four or more points belong to a circle when there is a point (called center) that is equally distant from each of the points./
=
= That is more precise, but perhaps a little more difficult to visualize.
=
+We call this distance /a *radius* of a circle/. We already saw radius used as an attribute of a circle when we were making our first dot. Remember:
+
+| Code
+ Svg.circle
+ [ Svg.Attributes.r "10"]
+ []
+
+The {Code|r} attribute stands for radius. Then it was basically dictating the size of the dot. The larger the radius the bigger the circle, and a dot is just a filled circle!
+
+This time the radius will affect the size of an imaginary circle on which the dots are laying.
+
=Ok, so this describes what it means to lay on a circle. But what are some efficient methods of putting things there? How about the following.
=
=You choose a center (anywhere, say {Code|(0, 0)}) and radius (any length you like, say 80).
@@ -105,7 +113,7 @@ Next slightly rotate the ruler around the center (keep point {Code|0} of the rul
=
=In other words, you can put a thing in the center and then move it in any direction, let's say 1 meter. If you do the same to several things in several directions (but same length), you will make them lay on a circle. The direction must be different each time. Otherwise the things would just stack one on top of another! We wouldn't call it a circle, right?
=
-Now, notice that the dots displayed by the program we are creating are not laying just anywhere on the circle. They are evenly distributed. It means that not only the distance between each dot and the center is the same. Also each dots lay the same distance away from it's two nearest neighbors. Just compare the following two pictures:
+Now, notice that the dots displayed by the program we are creating are not laying just anywhere on the circle. They are evenly distributed. It means that not only the distance between each dot and the center is the same. Also each dot lay the same distance away from it's two nearest neighbors. Just compare the following two pictures:
=
=| Row
= | Circle
@@ -124,7 +132,7 @@ Now, notice that the dots displayed by the program we are creating are not layin
= radius = 0 1
= scatter = False
=
-Both are showing 5 dots laying on a circle. Look:
+On the right, you immediately see a patern. They form a circle. But in fact both pictures are showing 5 dots laying on a circle. Look:
=
=| Row
= | Circle
@@ -156,7 +164,7 @@ So the dots on the left lay on a circle, but there is a striking difference. The
=
=Since the center and radius is the same for each dot, the only thing we can play with is the angle that we rotate the ruler each time.
=
-A common way of representing rotation is in degrees. It works like that: imagine a circle around you. You are in the center. Now imagine that the circle is divided in 360 equal segments. If you turn from the segment you face to the next one on the right (a very small turn) then you have turned one degree. If you turn around to face the opposite direction, that's a turn of 180 degrees. Do it again and you are facing the same direction as before (a 360 degree turn). If you turn to the right (as on a corner of a street), then you have turned 90 degrees (half of 180, quarter of 360).
+A common way of representing rotation is in degrees. It works like this: imagine a circle around you. You are in the center. Now imagine that the circle is divided in 360 equal segments. If you turn from one segment to the next one on the right (a very small turn) then you have turned one degree. If you turn around to face the opposite direction, that's a turn of 180 degrees. Do it again and you are facing the same direction as before (a 360 degree turn). If you turn to the right (as on a corner of a street), then you have turned 90 degrees (half of 180, quarter of 360).
=
=In physical world we often use a tool called protractor to measure angles. It typically has one degree segments already marked for you. It looks like this:
=
@@ -326,7 +334,7 @@ For that we need to add another transformation before the move. First we will ro
=
=This will rotate the element by 72 degrees. On it's own it wouldn't make any difference, because the dot is round - it doesn't matter how you rotate it, it always looks the same. But It also rotates it's internal coordinates system. So the translation will move it in a different direction then the first dot.
=
-Now it's time to add the fird, red dot. Duplicate the second one, change fill to `"red"` and put `144` for rotate function, like this:
+Now it's time to add the third, red dot. Duplicate the second one, change fill to `"red"` and put `144` for rotate function, like this:
=
=| Code
= [ Svg.circleMerge branch 'day-1' into days-1-2
Commits: 2
Put scattered and evenly distributed examples next to each other
Implement new block: Row
index c28bbed..edb1bc0 100644
--- a/content/day-2.txt
+++ b/content/day-2.txt
@@ -105,37 +105,44 @@ Next slightly rotate the ruler around the center (keep point {Code|0} of the rul
=
=In other words, you can put a thing in the center and then move it in any direction, let's say 1 meter. If you do the same to several things in several directions (but same length), you will make them lay on a circle. The direction must be different each time. Otherwise the things would just stack one on top of another! We wouldn't call it a circle, right?
=
-Now, notice that the dots displayed by the program we are creating are not laying just anywhere on the circle. They are evenly distributed:
+Now, notice that the dots displayed by the program we are creating are not laying just anywhere on the circle. They are evenly distributed. It means that not only the distance between each dot and the center is the same. Also each dots lay the same distance away from it's two nearest neighbors. Just compare the following two pictures:
=
-| Circle
- dots = 5
- circle = 0 1
- angles = 0 1
- center = none
- radius = 0 1
- scatter = False
+| Row
+ | Circle
+ dots = 5
+ circle = 0 1
+ angles = 0 1
+ center = none
+ radius = 0 1
+ scatter = True
=
-It means that not only the distance between each dot and the center is the same. Also each dots lay the same distance away from it's two nearest neighbors.
+ | Circle
+ dots = 5
+ circle = 0 1
+ angles = 0 1
+ center = none
+ radius = 0 1
+ scatter = False
=
-Just compare it with the following picture:
+Both are showing 5 dots laying on a circle. Look:
=
-| Circle
- dots = 5
- circle = 0 1
- angles = 0 1
- center = none
- radius = 0 1
- scatter = True
+| Row
+ | Circle
+ dots = 5
+ circle = 3
+ angles = 0 1
+ center = none
+ radius = 0 1
+ scatter = True
=
-These are also 5 dots laying on a circle. Look:
+ | Circle
+ dots = 5
+ circle = 3
+ angles = 0 1
+ center = none
+ radius = 0 1
+ scatter = False
=
-| Circle
- dots = 5
- circle = 3
- angles = 0 1
- center = none
- radius = 0 1
- scatter = True
=
=| Note
= IDEA: An interactive program (game of sorts) that shows a ruler and dots. User can drag the ruler around and rotate it. Also dots can be dragged around. Once the dots are placed on a circle, the program rewards the user with a nice animation and message. It would be super awesome to make it touch capable {Icon|name=award}
@@ -145,7 +152,7 @@ These are also 5 dots laying on a circle. Look:
= In the mean time we have two non-interactive examples with evenly and randomly distributed dots.
=
=
-So they lay on a circle, but there is a striking difference. They are all scattered! This means that there is something missing in our solution. We need to take care of one more aspect when we place our dots.
+So the dots on the left lay on a circle, but there is a striking difference. They are all scattered! This means that there is something missing in our solution. We need to take care of one more aspect when we place our dots.
=
=Since the center and radius is the same for each dot, the only thing we can play with is the angle that we rotate the ruler each time.
=index 444dac4..f8def73 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -450,57 +450,41 @@ document =
= )
= Mark.Custom.title
= (Mark.manyOf
- [ Mark.Custom.header
- , Mark.Custom.paragraph
- , Mark.Custom.monospace
- , Mark.Custom.code
- , Mark.Custom.note
- , Mark.Custom.emphasize
- , Mark.Custom.list
- , Mark.Custom.image
-
- -- Embeded programs
- , counter
- , dot
- , dotInElement
- , dotWithViewBox
- , twoDots
- , circle
- , line
- , gradient
- , transformations
- , nestedTransformations
- , cartesianCoordinates
- , polarCoordinates
- , rosette
- , spiral
- , tree
- , viewBox
-
- -- Window block can contain any embeded program
- , Mark.Custom.window
- (Mark.oneOf
- [ counter
- , dot
- , dotInElement
- , dotWithViewBox
- , twoDots
- , circle
- , line
- , gradient
- , transformations
- , nestedTransformations
- , cartesianCoordinates
- , polarCoordinates
- , rosette
- , spiral
- , tree
- , viewBox
- ]
- )
- ]
+ ([ Mark.Custom.header
+ , Mark.Custom.paragraph
+ , Mark.Custom.monospace
+ , Mark.Custom.code
+ , Mark.Custom.note
+ , Mark.Custom.emphasize
+ , Mark.Custom.list
+ , Mark.Custom.image
+ ]
+ ++ examples
+ ++ [ Mark.Custom.window (Mark.oneOf examples)
+ , Mark.Custom.row (Mark.manyOf examples)
+ ]
+ )
= )
=
+ examples =
+ [ counter
+ , dot
+ , dotInElement
+ , dotWithViewBox
+ , twoDots
+ , circle
+ , line
+ , gradient
+ , transformations
+ , nestedTransformations
+ , cartesianCoordinates
+ , polarCoordinates
+ , rosette
+ , spiral
+ , tree
+ , viewBox
+ ]
+
= -- Embeded programs' blocks
= counter : Mark.Block (Model -> Element Msg)
= counter =index bfe94a4..193f700 100644
--- a/src/Mark/Custom.elm
+++ b/src/Mark/Custom.elm
@@ -10,6 +10,7 @@ module Mark.Custom exposing
= , monospace
= , note
= , paragraph
+ , row
= , text
= , title
= , window
@@ -141,6 +142,29 @@ window block =
= block
=
=
+row :
+ Mark.Block (List (a -> Element msg))
+ -> Mark.Block (a -> Element msg)
+row block =
+ let
+ render children model =
+ children
+ |> List.map (\child -> child model)
+ |> List.map
+ (Element.el
+ [ Element.width Element.fill
+ , Border.width 1
+ , Border.rounded 5
+ , Border.color (Element.rgb 0.6 0.6 0.6)
+ ]
+ )
+ |> Element.row [ Element.spacing 20 ]
+ in
+ Mark.block "Row"
+ render
+ block
+
+
=note : Mark.Block (model -> Element msg)
=note =
= letAdd ACME challenge code for Let's Encrypt verification
Temporarily enable this branch (custom-domain) in CI, so we can test if it works before merging.
index 4472935..28e2f71 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -15,3 +15,4 @@ pages:
=
= only:
= - master
+ - custom-domainnew file mode 100644
index 0000000..44f3504
--- /dev/null
+++ b/.well-known/acme-challenge/HVUehfup9PAAASIMOcJoCGvD9q9bzX_W_tBUSSI6RXI
@@ -0,0 +1 @@
+HVUehfup9PAAASIMOcJoCGvD9q9bzX_W_tBUSSI6RXI.HvUI1nPJPHTnM--CR4G53bMPL5yhCFd2qGlsMDuIw14
Commits: 13
Merge branch 'multiple-pages' into 'master'
Split the content into multiple pages
Closes #1
See merge request software-garden/software-garden.gitlab.io!3
Fix slashes in inlines by upgrading mdgriffith/elm-markup to 2.0.5
index 07ece92..b321d9d 100644
--- a/elm.json
+++ b/elm.json
@@ -22,7 +22,7 @@
= "feathericons/elm-feather": "1.2.0",
= "ianmackenzie/elm-geometry": "1.2.1",
= "ianmackenzie/elm-geometry-svg": "1.0.2",
- "mdgriffith/elm-markup": "2.0.2",
+ "mdgriffith/elm-markup": "2.0.5",
= "mdgriffith/elm-ui": "1.1.0",
= "turboMaCk/any-dict": "1.0.1"
= },
@@ -40,4 +40,4 @@
= "direct": {},
= "indirect": {}
= }
-}
\ No newline at end of file
+}Fix "Elm" capitalization in day-1
index 78ccd3e..9767b34 100644
--- a/content/day-1.txt
+++ b/content/day-1.txt
@@ -11,7 +11,7 @@
= | List
= -> Scalable Vector Graphics
= -> Cartesian Coordinates Systems
- -> Layouts with ELM UI
+ -> Layouts with Elm UI
=
=
=| HeaderChange directory name to "software-garden"
index 9767b34..228262e 100644
--- a/content/day-1.txt
+++ b/content/day-1.txt
@@ -20,16 +20,17 @@
=As mentioned before, programs are represented as text (called the /source code/). The source code is stored in files {Icon|name=file} and files are organized in directories {Icon|name=folder}.
=
=
-So the first step is to create a directory for our new program. Lets call it fpart.
+So the first step is to create a directory for our new program. Let's call it "software-garden".
+
=In the terminal type:
=
=| Monospace
- mkdir fpart/
+ mkdir software-garden/
=
=and then:
=
=| Monospace
- cd fpart/
+ cd software-garden/
=
=The first command will create our new directory and the second will make it the current directory. Again, don't worry about the details.
=Elaborate on pressing Enter in terminal
index 228262e..b64da0d 100644
--- a/content/day-1.txt
+++ b/content/day-1.txt
@@ -27,14 +27,17 @@ In the terminal type:
=| Monospace
= mkdir software-garden/
=
-and then:
+press enter key. Then type:
=
=| Monospace
= cd software-garden/
=
-The first command will create our new directory and the second will make it the current directory. Again, don't worry about the details.
+| Note
+ Press enter again. Generally when we ask you to type something in a terminal, we imply that you press enter afterwards. That's how you signal that you are done typing and expect computer to process your command. Alternatively we call it entering a command.
+
+ In this case the first command will create our new directory and the second will make it the current directory. Again, don't worry about the details.
=
-We can easily create a new program by typing:
+We can easily create a new program by entering a following command:
=
=| Monospace
= elm initExplain that we need to change directory after opening a terminal
index b64da0d..9a5ba1a 100644
--- a/content/day-1.txt
+++ b/content/day-1.txt
@@ -35,7 +35,7 @@ press enter key. Then type:
=| Note
= Press enter again. Generally when we ask you to type something in a terminal, we imply that you press enter afterwards. That's how you signal that you are done typing and expect computer to process your command. Alternatively we call it entering a command.
=
- In this case the first command will create our new directory and the second will make it the current directory. Again, don't worry about the details.
+ In this case the first command will create our new directory and the second will make it the current directory, so that subsequent commands will be executed in it's context. Again, don't worry about the details, but if you close your terminal and then open it again, you will need to enter `cd software-garden/` to set this context again.
=
=We can easily create a new program by entering a following command:
=Mention that you can use other editor than Atom
index 9a5ba1a..9c72fa3 100644
--- a/content/day-1.txt
+++ b/content/day-1.txt
@@ -47,7 +47,7 @@ Then to create the file which will contain our source code, type:
=| Monospace
= atom src/Main.elm
=
-This command should open a new Atom window with an empty text file.
+This command should open a new Atom window with an empty text file. If you prefer to use different editor, feel free to do so.
=
=| Header
= Main.elmFix link label in day-1
index 9c72fa3..713c2a3 100644
--- a/content/day-1.txt
+++ b/content/day-1.txt
@@ -77,7 +77,7 @@ This command will start the Elm reactor, which will allow you to run your progra
=Open following address in the web browser:
=
=| Emphasize
- {Link|http://localhost:8000/src/Main.elm|url=http://localhost:8000/src/Main.elm}
+ {Link|http:////localhost:8000//src//Main.elm|url=http://localhost:8000/src/Main.elm}
=
=| Note
= *TODO*: Make the link display // charactersMerge branch 'master' into day-2
Fix typo
index 3dd306a..b9410fd 100644
--- a/content/day-2.txt
+++ b/content/day-2.txt
@@ -426,4 +426,4 @@ In the browser it should look exactly as we planned:
= scatter = False
=
=| Emphasize
- Congtrarulations! You are ready for {Link|Day 3|url=/day-3.html}!
+ Congratulations! You are ready for {Link|Day 3|url=/day-3.html}!Add links to Day 2 from index and Day 1
index 78ccd3e..9f2145e 100644
--- a/content/day-1.txt
+++ b/content/day-1.txt
@@ -548,3 +548,9 @@ Only one dot! We see the second one, but what happened to the first dot? It's un
=As an exercise, try adding an {Code|Svg.Attribute.cy} attribute to the circles to adjust their vertical position.
=
=For a more advanced exercise, try adding a new circle. Why not draw 5 circles on the screen? Give them each a unique color and position.
+
+| Emphasize
+ {Icon|name=award}
+
+| Emphasize
+ We really hope you enjoyed this first day of our workshop. See you at {Link|Day 2|url=/day-2.html}!index 58f6798..39d8332 100644
--- a/content/index.txt
+++ b/content/index.txt
@@ -10,4 +10,5 @@
=| List
= # {Link|Before the course begins|url=/preparation.html}
= # {Link|Day 1 - Let's Make a Dot|url=/day-1.html}
+ # {Link|Day 2 - Let's Place the Dots in a Circle|url=/day-2.html}
= # {Link|The rest - Work in progress|url=/rest.html}Remove Debug.log calls from Circle
index b38e6aa..35baf6c 100644
--- a/src/Examples/Circle.elm
+++ b/src/Examples/Circle.elm
@@ -66,7 +66,6 @@ ui config =
= 0
= :: angles
= |> pairs
- |> Debug.log "pairs"
= |> List.indexedMap arc
= |> Svg.g []
=
@@ -123,7 +122,6 @@ ui config =
=
= radi =
= angles
- |> Debug.log "angles"
= |> List.map radius
= |> Svg.g []
=Express the Day 2 motto in "Let's ..." style
index b9410fd..c28bbed 100644
--- a/content/day-2.txt
+++ b/content/day-2.txt
@@ -2,7 +2,7 @@
= Day 2
=
=| Emphasize
- Place the dots in a circle
+ Let's Place the Dots in a Circle
=
=
=| Note
Commits: 4
Rename TransformationsCircle to Circle, make it parametrized, edit day-2
index 32d4a6c..6d2aed2 100644
--- a/content/day-2.txt
+++ b/content/day-2.txt
@@ -15,36 +15,135 @@
=| Header
= The Problem
=
-We want to place the dots in a circle, like that:
+We want to place the dots in a circle, like this:
=
-| TransformationsCircle
- dots = True
- circle = False
- angle = False
- center = False
+| Window
+ | Circle
+ dots = 5
+ circle = 0 1
+ angles = 0 1
+ center = none
+ radius = 0 1
+ scatter = False
=
-As you can see, we will make the dot's have different colors, so it's more fun!
+We will also make the dots have different colors... so it's more fun!
=
-How we will get there?
+| Header
+ What does it mean to be placed on a circle?
+
+We all have some intuition about a circle. You can tell if you see one and if you have a steady hand, you can probably draw one with a pencil. It's a thing that looks like this:
+
+| Circle
+ dots = 0
+ circle = 1 0
+ angles = 0 1
+ center = none
+ radius = 0 1
+ scatter = False
=
-We will need more than one dot. Let's make it 5. Then let's think what does it mean to be placed on a circle.
=
-First of all we have to realize that a circle has a center. It's a point that lays exactly in the middle:
+But what is it that makes a circle what it is? First of all we have to realize that a circle has a center. It's a point that lays exactly in the middle:
=
+| Circle
+ dots = 0
+ circle = 1 0
+ angles = 0 1
+ center = True
+ radius = 0 1
+ scatter = False
=
-Then we can say that several things lay on a circle if the distance between each of them and the center is the same.
=
-In other words, you can put a thing in the center and then move it in any direction, let's say 1 meter. If you do the same to several things, you will make them lay on a circle.
+We can say that four or more things lay on a circle, if the distance to the center is the same for each of them, like this:
=
-Of course the direction must be different each time. Otherwise the things would just stack one on top of another! We wouldn't call it a circle, right?
+| Circle
+ dots = 5
+ circle = 3 3
+ angles = 0 1
+ center = True
+ radius = 3 3
+ scatter = False
=
-We will change the cartesian coordinates of the dots, nothing else! But figuring out the right coordinates is a bit tricky
+Why more than three? Well, think about it in terms of a fair challenge. If it's one, then it can lay on any circle that crosses the place. There is very many circles like that (in fact infinitely many). The same for two. Not every circle will do, but still you could draw many different circles that they will lay on. For three things there will always be exactly one circle that they lay on (except if they lay in one line, unless you claim that straight line is an infinite circle). So three is still not much of a challenge. You could place them however you like, and claim that they lay on a circle.
=
-How can we figure out the correct cartesian coordinates?
+So only when it's four or more things, we can seriously ask whether they lay on a circle or not.
=
=| Note
- Story about the flowerpot and the table: two ways to measure relative position, one is distance from an x and y axis (cartesian coordinates), the other is angle and distance relative to origin (polar coordinates)
+ TODO: Consider if the above adds more value or confusion.
+
+ TODO: An example showing multiple circles crossing one and two points
+
+ TODO: An example showing that for three different points there will always be a circle that crosses them, see {Link|circumcircle|url=https://package.elm-lang.org/packages/ianmackenzie/elm-geometry/latest/Triangle2d#circumcircle}
+
+
+Because we are all about challenges, let's make five dots.
+
+So, once again - we can say that several things lay on a circle if the distance between each of them and the center of the circle is the same. We call this distance /a *radius* of a circle/.
+
+| Note
+ Matematician would say:
+
+ /Four or more points belong to a circle when there is a point (called center) that is equally distant from each of the points./
+
+ That is more precise, but perhaps a little more difficult to visualize.
+
+Ok, so this describes what it means to lay on a circle. But what are some efficient methods of putting things there? How about this.
+
+You choose a center (anywhere, say {Code|(0, 0)}) and radius (any length you like, say 80).
+
+Take a ruler and place it's one end (the one showing the value 0) at the center point. Then move the thing you want to place along the ruler the length you chose. Leave it there.
+
+Next slightly rotate the ruler around the center (keep point {Code|0} of the ruler at point {Code|(0, 0)}). Repeat the steps above with another thing. Move it along the slider the same length as previously (radius). Because this time the ruler points in different direction, the thing will end up in a different place.
+
+| Note
+ TODO: An interactive example with ruler. Basically a modified Translations.
+
+In other words, you can put a thing in the center and then move it in any direction, let's say 1 meter. If you do the same to several things in several directions (but same length), you will make them lay on a circle. The direction must be different each time. Otherwise the things would just stack one on top of another! We wouldn't call it a circle, right?
+
+Now, notice that the dots displayed by the program we are creating are not laying just anywhere on the circle. They are evenly distributed:
+
+| Circle
+ dots = 5
+ circle = 0 1
+ angles = 0 1
+ center = none
+ radius = 0 1
+ scatter = False
+
+It means that not only the distance between each dot and the center is the same. Also each dots lay the same distance away from it's two nearest neighbors.
+
+Just compare it with the following picture:
+
+| Circle
+ dots = 5
+ circle = 0 1
+ angles = 0 1
+ center = none
+ radius = 0 1
+ scatter = True
+
+These are also 5 dots laying on a circle. Look:
+
+| Circle
+ dots = 5
+ circle = 3
+ angles = 0 1
+ center = none
+ radius = 0 1
+ scatter = True
+
+| Note
+ IDEA: An interactive program (game of sorts) that shows a ruler and dots. User can drag the ruler around and rotate it. Also dots can be dragged around. Once the dots are placed on a circle, the program rewards the user with a nice animation and message. It would be super awesome to make it touch capable {Icon|name=award}
+
+ Then second variety. This time there is also a compass tool. User has to place the dots on a circle so that they are evenly distributed.
+
+ In the mean time we have two non-interactive examples with evenly and randomly distributed dots.
+
+
+So they lay on a circle, but there is a striking difference. They are all scattered! This means that there is something missing in our solution. We need to take care of one more aspect when we place our dots.
+
+Since the center and radius is the same for each dot, the only thing we can play with is the angle that we rotate the ruler each time.
=
+TODO: ___✁___ edit from here
=
=What does this have to do with a circle? Do you know the expression 'I did a 180'. Where would you be looking if you did two 180s (a '360'). We see that circles and angles are closely related!
=new file mode 100644
index 0000000..7d4ced3
--- /dev/null
+++ b/src/Examples/Circle.elm
@@ -0,0 +1,189 @@
+module Examples.Circle exposing (Config, defaults, main, ui)
+
+import Arc2d
+import Array
+import Element exposing (Element)
+import Geometry.Svg
+import Html exposing (Html)
+import List.Extra as List
+import Maybe.Extra as Maybe
+import Point2d
+import Svg exposing (Svg)
+import Svg.Attributes
+import Transformations exposing (Transformation(..))
+
+
+type alias Config =
+ { dots : Int -- Number of dots
+ , circle : String -- Dash array
+ , center : String -- Color
+ , radius : String -- Dash array
+ , angles : String -- Dash array
+ , scatter : Bool -- Are doot evenly distributed or scattered
+ }
+
+
+main : Html.Html msg
+main =
+ Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+ (ui defaults)
+
+
+palette : List String
+palette =
+ [ "skyblue"
+ , "orange"
+ , "red"
+ , "lime"
+ , "maroon"
+ ]
+
+
+ui : Config -> Element msg
+ui config =
+ let
+ shapes =
+ [ dots
+ , center
+ , circle
+ , compass
+ , radi
+ ]
+
+ compass : Svg msg
+ compass =
+ 0
+ :: angles
+ |> pairs
+ |> Debug.log "pairs"
+ |> List.indexedMap arc
+ |> Svg.g []
+
+ pairs : List a -> List ( a, a )
+ pairs list =
+ case list of
+ [] ->
+ []
+
+ [ one ] ->
+ []
+
+ one :: two :: rest ->
+ ( one, two ) :: pairs (two :: rest)
+
+ arc : Int -> ( Float, Float ) -> Svg msg
+ arc index ( start, end ) =
+ Arc2d.with
+ { centerPoint = Point2d.origin
+ , radius = 30
+ , startAngle = degrees start
+ , sweptAngle = degrees (end - start)
+ }
+ |> Geometry.Svg.arc2d
+ [ Svg.Attributes.stroke (color index)
+ , Svg.Attributes.strokeDasharray config.angles
+ , Svg.Attributes.fill "none"
+ ]
+
+ dots =
+ angles
+ |> List.indexedMap
+ (\index angle ->
+ dot angle (color index)
+ )
+ |> Svg.g []
+
+ center =
+ Svg.circle
+ [ Svg.Attributes.r "2"
+ , Svg.Attributes.fill config.center
+ ]
+ []
+
+ circle =
+ Svg.circle
+ [ Svg.Attributes.r "80"
+ , Svg.Attributes.stroke "silver"
+ , Svg.Attributes.strokeWidth "1"
+ , Svg.Attributes.strokeDasharray config.circle
+ , Svg.Attributes.fill "none"
+ ]
+ []
+
+ radi =
+ angles
+ |> Debug.log "angles"
+ |> List.map radius
+ |> Svg.g []
+
+ angles : List Float
+ angles =
+ if config.scatter then
+ -- TODO: Generate a list of numbers [ 0, 5, 10, 15, ... 355 ] and use Random.Extra.List.shuffle and List.take config.dots to take a random sample.
+ [ 5, 35, 85, 100, 260, 340, 300, 95, 180, 220 ]
+ |> List.take config.dots
+
+ else
+ config.dots
+ |> List.range 1
+ |> List.map
+ (\index ->
+ 360 * toFloat index / toFloat config.dots
+ )
+
+ dot : Float -> String -> Svg msg
+ dot angle fill =
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill fill
+ , Svg.Attributes.transform <|
+ Transformations.apply
+ [ Rotate angle
+ , Translate 80 0
+ ]
+ ]
+ []
+
+ color : Int -> String
+ color index =
+ palette
+ |> Array.fromList
+ |> Array.get (modBy (List.length palette) index)
+ |> Maybe.withDefault "black"
+
+ radius angle =
+ Svg.line
+ [ Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x2 "80"
+ , Svg.Attributes.y2 "0"
+ , Svg.Attributes.stroke "pink"
+ , Svg.Attributes.strokeDasharray config.radius
+ , Svg.Attributes.transform <|
+ Transformations.apply
+ [ Rotate angle
+ ]
+ ]
+ []
+ in
+ shapes
+ |> Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200"
+ ]
+ |> Element.html
+
+
+defaults : Config
+defaults =
+ { dots = 5
+ , circle = "0 1"
+ , center = "none"
+ , radius = "0 1"
+ , angles = "0 1"
+ , scatter = False
+ }deleted file mode 100644
index a3a507f..0000000
--- a/src/Examples/TransformationsCircle.elm
+++ /dev/null
@@ -1,135 +0,0 @@
-module Examples.TransformationsCircle exposing (Config, defaults, main, ui)
-
-import Element exposing (Element)
-import Html exposing (Html)
-import Maybe.Extra as Maybe
-import Svg exposing (Svg)
-import Svg.Attributes
-import Transformations exposing (Transformation(..))
-
-
-main : Html.Html msg
-main =
- Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
- ]
- (ui defaults)
-
-
-ui : Config -> Element msg
-ui config =
- let
- present : Bool -> Svg msg -> Maybe (Svg msg)
- present flag shape =
- if flag then
- Just shape
-
- else
- Nothing
-
- shapes =
- dots
- ++ Maybe.values
- [ present config.circle circle
- ]
-
- dots =
- if config.dots then
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill "skyblue"
- , Svg.Attributes.transform <|
- Transformations.apply
- [ Rotate 0
- , Translate 80 0
- ]
- ]
- []
- , Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill "pink"
- , Svg.Attributes.transform <|
- Transformations.apply
- [ Rotate 72
- , Translate 80 0
- ]
- ]
- []
- , Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill "yellow"
- , Svg.Attributes.transform <|
- Transformations.apply
- [ Rotate 144
- , Translate 80 0
- ]
- ]
- []
- , Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill "lime"
- , Svg.Attributes.transform <|
- Transformations.apply
- [ Rotate 216
- , Translate 80 0
- ]
- ]
- []
- , Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- , Svg.Attributes.fill "maroon"
- , Svg.Attributes.transform <|
- Transformations.apply
- [ Rotate 288
- , Translate 80 0
- ]
- ]
- []
- ]
-
- else
- []
-
- circle =
- Svg.circle
- [ Svg.Attributes.r "80"
- , Svg.Attributes.stroke "silver"
- , Svg.Attributes.strokeWidth "1"
- , Svg.Attributes.strokeDasharray "5 5"
- , Svg.Attributes.fill "none"
- ]
- []
- in
- shapes
- |> Svg.svg
- [ Svg.Attributes.viewBox "-100 -100 200 200"
- ]
- |> Element.html
-
-
-defaults : Config
-defaults =
- { circle = False
- , center = False
- , angle = False
- , dots = False
- }
-
-
-type alias Config =
- { circle : Bool
- , center : Bool
- , angle : Bool
- , dots : Bool
- }index 072cef8..d8624ad 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -19,6 +19,7 @@ import Element.Font as Font
=import Element.Input as Input
=import Examples.CartesianCoordinates
=import Examples.CenteredDot
+import Examples.Circle
=import Examples.Counter
=import Examples.DotAtTheCenterOfTheScreen
=import Examples.FillTheScreen
@@ -30,7 +31,6 @@ import Examples.RosetteTypedTransformations
=import Examples.Simplest
=import Examples.Spiral
=import Examples.Transformations
-import Examples.TransformationsCircle
=import Examples.Tree
=import Examples.ViewBox
=import FeatherIcons exposing (icons)
@@ -463,7 +463,7 @@ document =
= , fillTheScreen
= , dotAtTheCenterOfTheScreen
= , centeredDot
- , transformationsCircle
+ , circle
= , line
= , gradient
= , transformations
@@ -569,19 +569,21 @@ document =
= in
= Mark.stub "CenteredDot" render
=
- transformationsCircle : Mark.Block (Model -> Element Msg)
- transformationsCircle =
+ circle : Mark.Block (Model -> Element Msg)
+ circle =
= let
- render : Examples.TransformationsCircle.Config -> Model -> Element Msg
+ render : Examples.Circle.Config -> Model -> Element Msg
= render config model =
- Examples.TransformationsCircle.ui config
+ Examples.Circle.ui config
= in
- Mark.record4 "TransformationsCircle"
- Examples.TransformationsCircle.Config
- (Mark.field "circle" Mark.bool)
- (Mark.field "center" Mark.bool)
- (Mark.field "angle" Mark.bool)
- (Mark.field "dots" Mark.bool)
+ Mark.record6 "Circle"
+ Examples.Circle.Config
+ (Mark.field "dots" Mark.int)
+ (Mark.field "circle" Mark.string)
+ (Mark.field "center" Mark.string)
+ (Mark.field "radius" Mark.string)
+ (Mark.field "angles" Mark.string)
+ (Mark.field "scatter" Mark.bool)
= |> Mark.map render
=
= line : Mark.Block (Model -> Element Msg)Merge branch 'multiple-pages' into review-day-one
Finish first draft of day 2
new file mode 100644
index 0000000..1c0256e
--- /dev/null
+++ b/assets/protractor.svg
@@ -0,0 +1,3406 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://web.resource.org/cc/"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.0"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.45.1"
+ width="531.49603"
+ height="531.49603"
+ sodipodi:docname="rapporteur.svg"
+ sodipodi:docbase="/tmp"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape">
+ <metadata
+ id="metadata733">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs731" />
+ <sodipodi:namedview
+ inkscape:window-height="581"
+ inkscape:window-width="744"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ guidetolerance="10.0"
+ gridtolerance="10.0"
+ objecttolerance="10.0"
+ borderopacity="1.0"
+ bordercolor="#666666"
+ pagecolor="#ffffff"
+ id="base"
+ width="150mm"
+ height="150mm"
+ units="mm"
+ inkscape:document-units="mm"
+ inkscape:zoom="1.4367677"
+ inkscape:cx="183.55229"
+ inkscape:cy="130.82769"
+ inkscape:window-x="46"
+ inkscape:window-y="46"
+ inkscape:current-layer="layer1" />
+ <title
+ id="title4">Protractor</title>
+ <g
+ transform="translate(270.98819,265.42013)"
+ id="g6">
+ <circle
+ cx="0"
+ cy="0"
+ r="250"
+ id="circle8"
+ sodipodi:cx="0"
+ sodipodi:cy="0"
+ sodipodi:rx="250"
+ sodipodi:ry="250"
+ style="fill:#d3d3d3;fill-opacity:0.3;stroke:#000000;stroke-width:3" />
+<!-- Units of 1 --> <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9998477,1.7452406e-2,-1.7452406e-2,0.9998477,0,0)"
+ id="line10"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9993908,3.4899497e-2,-3.4899497e-2,0.9993908,0,0)"
+ id="line12"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9986295,5.2335956e-2,-5.2335956e-2,0.9986295,0,0)"
+ id="line14"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9975641,6.9756474e-2,-6.9756474e-2,0.9975641,0,0)"
+ id="line16"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9945219,0.1045285,-0.1045285,0.9945219,0,0)"
+ id="line18"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9925462,0.1218693,-0.1218693,0.9925462,0,0)"
+ id="line20"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9902681,0.1391731,-0.1391731,0.9902681,0,0)"
+ id="line22"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9876883,0.1564345,-0.1564345,0.9876883,0,0)"
+ id="line24"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9816272,0.190809,-0.190809,0.9816272,0,0)"
+ id="line26"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9781476,0.2079117,-0.2079117,0.9781476,0,0)"
+ id="line28"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9743701,0.2249511,-0.2249511,0.9743701,0,0)"
+ id="line30"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9702957,0.2419219,-0.2419219,0.9702957,0,0)"
+ id="line32"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9612617,0.2756374,-0.2756374,0.9612617,0,0)"
+ id="line34"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9563048,0.2923717,-0.2923717,0.9563048,0,0)"
+ id="line36"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9510565,0.309017,-0.309017,0.9510565,0,0)"
+ id="line38"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9455186,0.3255682,-0.3255682,0.9455186,0,0)"
+ id="line40"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9335804,0.3583679,-0.3583679,0.9335804,0,0)"
+ id="line42"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9271839,0.3746066,-0.3746066,0.9271839,0,0)"
+ id="line44"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9205049,0.3907311,-0.3907311,0.9205049,0,0)"
+ id="line46"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9135455,0.4067366,-0.4067366,0.9135455,0,0)"
+ id="line48"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.898794,0.4383711,-0.4383711,0.898794,0,0)"
+ id="line50"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.8910065,0.4539905,-0.4539905,0.8910065,0,0)"
+ id="line52"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.8829476,0.4694716,-0.4694716,0.8829476,0,0)"
+ id="line54"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.8746197,0.4848096,-0.4848096,0.8746197,0,0)"
+ id="line56"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.8571673,0.5150381,-0.5150381,0.8571673,0,0)"
+ id="line58"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.8480481,0.5299193,-0.5299193,0.8480481,0,0)"
+ id="line60"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.8386706,0.544639,-0.544639,0.8386706,0,0)"
+ id="line62"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.8290376,0.5591929,-0.5591929,0.8290376,0,0)"
+ id="line64"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.809017,0.5877853,-0.5877853,0.809017,0,0)"
+ id="line66"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.7986355,0.601815,-0.601815,0.7986355,0,0)"
+ id="line68"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.7880108,0.6156615,-0.6156615,0.7880108,0,0)"
+ id="line70"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.777146,0.6293204,-0.6293204,0.777146,0,0)"
+ id="line72"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.7547096,0.656059,-0.656059,0.7547096,0,0)"
+ id="line74"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.7431448,0.6691306,-0.6691306,0.7431448,0,0)"
+ id="line76"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.7313537,0.6819984,-0.6819984,0.7313537,0,0)"
+ id="line78"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.7193398,0.6946584,-0.6946584,0.7193398,0,0)"
+ id="line80"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.6946584,0.7193398,-0.7193398,0.6946584,0,0)"
+ id="line82"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.6819984,0.7313537,-0.7313537,0.6819984,0,0)"
+ id="line84"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.6691306,0.7431448,-0.7431448,0.6691306,0,0)"
+ id="line86"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.656059,0.7547096,-0.7547096,0.656059,0,0)"
+ id="line88"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.6293204,0.777146,-0.777146,0.6293204,0,0)"
+ id="line90"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.6156615,0.7880108,-0.7880108,0.6156615,0,0)"
+ id="line92"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.601815,0.7986355,-0.7986355,0.601815,0,0)"
+ id="line94"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.5877853,0.809017,-0.809017,0.5877853,0,0)"
+ id="line96"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.5591929,0.8290376,-0.8290376,0.5591929,0,0)"
+ id="line98"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.544639,0.8386706,-0.8386706,0.544639,0,0)"
+ id="line100"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.5299193,0.8480481,-0.8480481,0.5299193,0,0)"
+ id="line102"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.5150381,0.8571673,-0.8571673,0.5150381,0,0)"
+ id="line104"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.4848096,0.8746197,-0.8746197,0.4848096,0,0)"
+ id="line106"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.4694716,0.8829476,-0.8829476,0.4694716,0,0)"
+ id="line108"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.4539905,0.8910065,-0.8910065,0.4539905,0,0)"
+ id="line110"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.4383711,0.898794,-0.898794,0.4383711,0,0)"
+ id="line112"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.4067366,0.9135455,-0.9135455,0.4067366,0,0)"
+ id="line114"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.3907311,0.9205049,-0.9205049,0.3907311,0,0)"
+ id="line116"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.3746066,0.9271839,-0.9271839,0.3746066,0,0)"
+ id="line118"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.3583679,0.9335804,-0.9335804,0.3583679,0,0)"
+ id="line120"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.3255682,0.9455186,-0.9455186,0.3255682,0,0)"
+ id="line122"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.309017,0.9510565,-0.9510565,0.309017,0,0)"
+ id="line124"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.2923717,0.9563048,-0.9563048,0.2923717,0,0)"
+ id="line126"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.2756374,0.9612617,-0.9612617,0.2756374,0,0)"
+ id="line128"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.2419219,0.9702957,-0.9702957,0.2419219,0,0)"
+ id="line130"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.2249511,0.9743701,-0.9743701,0.2249511,0,0)"
+ id="line132"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.2079117,0.9781476,-0.9781476,0.2079117,0,0)"
+ id="line134"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.190809,0.9816272,-0.9816272,0.190809,0,0)"
+ id="line136"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.1564345,0.9876883,-0.9876883,0.1564345,0,0)"
+ id="line138"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.1391731,0.9902681,-0.9902681,0.1391731,0,0)"
+ id="line140"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.1218693,0.9925462,-0.9925462,0.1218693,0,0)"
+ id="line142"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.1045285,0.9945219,-0.9945219,0.1045285,0,0)"
+ id="line144"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(6.9756474e-2,0.9975641,-0.9975641,6.9756474e-2,0,0)"
+ id="line146"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(5.2335956e-2,0.9986295,-0.9986295,5.2335956e-2,0,0)"
+ id="line148"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(3.4899497e-2,0.9993908,-0.9993908,3.4899497e-2,0,0)"
+ id="line150"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(1.7452406e-2,0.9998477,-0.9998477,1.7452406e-2,0,0)"
+ id="line152"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-1.7452406e-2,0.9998477,-0.9998477,-1.7452406e-2,0,0)"
+ id="line154"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-3.4899497e-2,0.9993908,-0.9993908,-3.4899497e-2,0,0)"
+ id="line156"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-5.2335956e-2,0.9986295,-0.9986295,-5.2335956e-2,0,0)"
+ id="line158"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-6.9756474e-2,0.9975641,-0.9975641,-6.9756474e-2,0,0)"
+ id="line160"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.1045285,0.9945219,-0.9945219,-0.1045285,0,0)"
+ id="line162"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.1218693,0.9925462,-0.9925462,-0.1218693,0,0)"
+ id="line164"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.1391731,0.9902681,-0.9902681,-0.1391731,0,0)"
+ id="line166"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.1564345,0.9876883,-0.9876883,-0.1564345,0,0)"
+ id="line168"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.190809,0.9816272,-0.9816272,-0.190809,0,0)"
+ id="line170"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.2079117,0.9781476,-0.9781476,-0.2079117,0,0)"
+ id="line172"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.2249511,0.9743701,-0.9743701,-0.2249511,0,0)"
+ id="line174"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.2419219,0.9702957,-0.9702957,-0.2419219,0,0)"
+ id="line176"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.2756374,0.9612617,-0.9612617,-0.2756374,0,0)"
+ id="line178"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.2923717,0.9563048,-0.9563048,-0.2923717,0,0)"
+ id="line180"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.309017,0.9510565,-0.9510565,-0.309017,0,0)"
+ id="line182"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.3255682,0.9455186,-0.9455186,-0.3255682,0,0)"
+ id="line184"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.3583679,0.9335804,-0.9335804,-0.3583679,0,0)"
+ id="line186"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.3746066,0.9271839,-0.9271839,-0.3746066,0,0)"
+ id="line188"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.3907311,0.9205049,-0.9205049,-0.3907311,0,0)"
+ id="line190"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.4067366,0.9135455,-0.9135455,-0.4067366,0,0)"
+ id="line192"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.4383711,0.898794,-0.898794,-0.4383711,0,0)"
+ id="line194"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.4539905,0.8910065,-0.8910065,-0.4539905,0,0)"
+ id="line196"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.4694716,0.8829476,-0.8829476,-0.4694716,0,0)"
+ id="line198"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.4848096,0.8746197,-0.8746197,-0.4848096,0,0)"
+ id="line200"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.5150381,0.8571673,-0.8571673,-0.5150381,0,0)"
+ id="line202"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.5299193,0.8480481,-0.8480481,-0.5299193,0,0)"
+ id="line204"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.544639,0.8386706,-0.8386706,-0.544639,0,0)"
+ id="line206"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.5591929,0.8290376,-0.8290376,-0.5591929,0,0)"
+ id="line208"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.5877853,0.809017,-0.809017,-0.5877853,0,0)"
+ id="line210"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.601815,0.7986355,-0.7986355,-0.601815,0,0)"
+ id="line212"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.6156615,0.7880108,-0.7880108,-0.6156615,0,0)"
+ id="line214"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.6293204,0.777146,-0.777146,-0.6293204,0,0)"
+ id="line216"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.656059,0.7547096,-0.7547096,-0.656059,0,0)"
+ id="line218"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.6691306,0.7431448,-0.7431448,-0.6691306,0,0)"
+ id="line220"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.6819984,0.7313537,-0.7313537,-0.6819984,0,0)"
+ id="line222"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.6946584,0.7193398,-0.7193398,-0.6946584,0,0)"
+ id="line224"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.7193398,0.6946584,-0.6946584,-0.7193398,0,0)"
+ id="line226"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.7313537,0.6819984,-0.6819984,-0.7313537,0,0)"
+ id="line228"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.7431448,0.6691306,-0.6691306,-0.7431448,0,0)"
+ id="line230"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.7547096,0.656059,-0.656059,-0.7547096,0,0)"
+ id="line232"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.777146,0.6293204,-0.6293204,-0.777146,0,0)"
+ id="line234"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.7880108,0.6156615,-0.6156615,-0.7880108,0,0)"
+ id="line236"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.7986355,0.601815,-0.601815,-0.7986355,0,0)"
+ id="line238"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.809017,0.5877853,-0.5877853,-0.809017,0,0)"
+ id="line240"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.8290376,0.5591929,-0.5591929,-0.8290376,0,0)"
+ id="line242"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.8386706,0.544639,-0.544639,-0.8386706,0,0)"
+ id="line244"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.8480481,0.5299193,-0.5299193,-0.8480481,0,0)"
+ id="line246"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.8571673,0.5150381,-0.5150381,-0.8571673,0,0)"
+ id="line248"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.8746197,0.4848096,-0.4848096,-0.8746197,0,0)"
+ id="line250"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.8829476,0.4694716,-0.4694716,-0.8829476,0,0)"
+ id="line252"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.8910065,0.4539905,-0.4539905,-0.8910065,0,0)"
+ id="line254"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.898794,0.4383711,-0.4383711,-0.898794,0,0)"
+ id="line256"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9135455,0.4067366,-0.4067366,-0.9135455,0,0)"
+ id="line258"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9205049,0.3907311,-0.3907311,-0.9205049,0,0)"
+ id="line260"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9271839,0.3746066,-0.3746066,-0.9271839,0,0)"
+ id="line262"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9335804,0.3583679,-0.3583679,-0.9335804,0,0)"
+ id="line264"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9455186,0.3255682,-0.3255682,-0.9455186,0,0)"
+ id="line266"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9510565,0.309017,-0.309017,-0.9510565,0,0)"
+ id="line268"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9563048,0.2923717,-0.2923717,-0.9563048,0,0)"
+ id="line270"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9612617,0.2756374,-0.2756374,-0.9612617,0,0)"
+ id="line272"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9702957,0.2419219,-0.2419219,-0.9702957,0,0)"
+ id="line274"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9743701,0.2249511,-0.2249511,-0.9743701,0,0)"
+ id="line276"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9781476,0.2079117,-0.2079117,-0.9781476,0,0)"
+ id="line278"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9816272,0.190809,-0.190809,-0.9816272,0,0)"
+ id="line280"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9876883,0.1564345,-0.1564345,-0.9876883,0,0)"
+ id="line282"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9902681,0.1391731,-0.1391731,-0.9902681,0,0)"
+ id="line284"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9925462,0.1218693,-0.1218693,-0.9925462,0,0)"
+ id="line286"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9945219,0.1045285,-0.1045285,-0.9945219,0,0)"
+ id="line288"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9975641,6.9756474e-2,-6.9756474e-2,-0.9975641,0,0)"
+ id="line290"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9986295,5.2335956e-2,-5.2335956e-2,-0.9986295,0,0)"
+ id="line292"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9993908,3.4899497e-2,-3.4899497e-2,-0.9993908,0,0)"
+ id="line294"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9998477,1.7452406e-2,-1.7452406e-2,-0.9998477,0,0)"
+ id="line296"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9998477,-1.7452406e-2,1.7452406e-2,-0.9998477,0,0)"
+ id="line298"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9993908,-3.4899497e-2,3.4899497e-2,-0.9993908,0,0)"
+ id="line300"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9986295,-5.2335956e-2,5.2335956e-2,-0.9986295,0,0)"
+ id="line302"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9975641,-6.9756474e-2,6.9756474e-2,-0.9975641,0,0)"
+ id="line304"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9945219,-0.1045285,0.1045285,-0.9945219,0,0)"
+ id="line306"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9925462,-0.1218693,0.1218693,-0.9925462,0,0)"
+ id="line308"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9902681,-0.1391731,0.1391731,-0.9902681,0,0)"
+ id="line310"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9876883,-0.1564345,0.1564345,-0.9876883,0,0)"
+ id="line312"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9816272,-0.190809,0.190809,-0.9816272,0,0)"
+ id="line314"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9781476,-0.2079117,0.2079117,-0.9781476,0,0)"
+ id="line316"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9743701,-0.2249511,0.2249511,-0.9743701,0,0)"
+ id="line318"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9702957,-0.2419219,0.2419219,-0.9702957,0,0)"
+ id="line320"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9612617,-0.2756374,0.2756374,-0.9612617,0,0)"
+ id="line322"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9563048,-0.2923717,0.2923717,-0.9563048,0,0)"
+ id="line324"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9510565,-0.309017,0.309017,-0.9510565,0,0)"
+ id="line326"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9455186,-0.3255682,0.3255682,-0.9455186,0,0)"
+ id="line328"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9335804,-0.3583679,0.3583679,-0.9335804,0,0)"
+ id="line330"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9271839,-0.3746066,0.3746066,-0.9271839,0,0)"
+ id="line332"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9205049,-0.3907311,0.3907311,-0.9205049,0,0)"
+ id="line334"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9135455,-0.4067366,0.4067366,-0.9135455,0,0)"
+ id="line336"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.898794,-0.4383711,0.4383711,-0.898794,0,0)"
+ id="line338"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.8910065,-0.4539905,0.4539905,-0.8910065,0,0)"
+ id="line340"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.8829476,-0.4694716,0.4694716,-0.8829476,0,0)"
+ id="line342"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.8746197,-0.4848096,0.4848096,-0.8746197,0,0)"
+ id="line344"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.8571673,-0.5150381,0.5150381,-0.8571673,0,0)"
+ id="line346"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.8480481,-0.5299193,0.5299193,-0.8480481,0,0)"
+ id="line348"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.8386706,-0.544639,0.544639,-0.8386706,0,0)"
+ id="line350"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.8290376,-0.5591929,0.5591929,-0.8290376,0,0)"
+ id="line352"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.809017,-0.5877853,0.5877853,-0.809017,0,0)"
+ id="line354"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.7986355,-0.601815,0.601815,-0.7986355,0,0)"
+ id="line356"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.7880108,-0.6156615,0.6156615,-0.7880108,0,0)"
+ id="line358"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.777146,-0.6293204,0.6293204,-0.777146,0,0)"
+ id="line360"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.7547096,-0.656059,0.656059,-0.7547096,0,0)"
+ id="line362"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.7431448,-0.6691306,0.6691306,-0.7431448,0,0)"
+ id="line364"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.7313537,-0.6819984,0.6819984,-0.7313537,0,0)"
+ id="line366"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.7193398,-0.6946584,0.6946584,-0.7193398,0,0)"
+ id="line368"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.6946584,-0.7193398,0.7193398,-0.6946584,0,0)"
+ id="line370"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.6819984,-0.7313537,0.7313537,-0.6819984,0,0)"
+ id="line372"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.6691306,-0.7431448,0.7431448,-0.6691306,0,0)"
+ id="line374"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.656059,-0.7547096,0.7547096,-0.656059,0,0)"
+ id="line376"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.6293204,-0.777146,0.777146,-0.6293204,0,0)"
+ id="line378"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.6156615,-0.7880108,0.7880108,-0.6156615,0,0)"
+ id="line380"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.601815,-0.7986355,0.7986355,-0.601815,0,0)"
+ id="line382"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.5877853,-0.809017,0.809017,-0.5877853,0,0)"
+ id="line384"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.5591929,-0.8290376,0.8290376,-0.5591929,0,0)"
+ id="line386"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.544639,-0.8386706,0.8386706,-0.544639,0,0)"
+ id="line388"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.5299193,-0.8480481,0.8480481,-0.5299193,0,0)"
+ id="line390"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.5150381,-0.8571673,0.8571673,-0.5150381,0,0)"
+ id="line392"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.4848096,-0.8746197,0.8746197,-0.4848096,0,0)"
+ id="line394"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.4694716,-0.8829476,0.8829476,-0.4694716,0,0)"
+ id="line396"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.4539905,-0.8910065,0.8910065,-0.4539905,0,0)"
+ id="line398"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.4383711,-0.898794,0.898794,-0.4383711,0,0)"
+ id="line400"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.4067366,-0.9135455,0.9135455,-0.4067366,0,0)"
+ id="line402"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.3907311,-0.9205049,0.9205049,-0.3907311,0,0)"
+ id="line404"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.3746066,-0.9271839,0.9271839,-0.3746066,0,0)"
+ id="line406"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.3583679,-0.9335804,0.9335804,-0.3583679,0,0)"
+ id="line408"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.3255682,-0.9455186,0.9455186,-0.3255682,0,0)"
+ id="line410"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.309017,-0.9510565,0.9510565,-0.309017,0,0)"
+ id="line412"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.2923717,-0.9563048,0.9563048,-0.2923717,0,0)"
+ id="line414"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.2756374,-0.9612617,0.9612617,-0.2756374,0,0)"
+ id="line416"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.2419219,-0.9702957,0.9702957,-0.2419219,0,0)"
+ id="line418"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.2249511,-0.9743701,0.9743701,-0.2249511,0,0)"
+ id="line420"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.2079117,-0.9781476,0.9781476,-0.2079117,0,0)"
+ id="line422"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.190809,-0.9816272,0.9816272,-0.190809,0,0)"
+ id="line424"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.1564345,-0.9876883,0.9876883,-0.1564345,0,0)"
+ id="line426"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.1391731,-0.9902681,0.9902681,-0.1391731,0,0)"
+ id="line428"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.1218693,-0.9925462,0.9925462,-0.1218693,0,0)"
+ id="line430"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.1045285,-0.9945219,0.9945219,-0.1045285,0,0)"
+ id="line432"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-6.9756474e-2,-0.9975641,0.9975641,-6.9756474e-2,0,0)"
+ id="line434"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-5.2335956e-2,-0.9986295,0.9986295,-5.2335956e-2,0,0)"
+ id="line436"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-3.4899497e-2,-0.9993908,0.9993908,-3.4899497e-2,0,0)"
+ id="line438"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(-1.7452406e-2,-0.9998477,0.9998477,-1.7452406e-2,0,0)"
+ id="line440"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(1.7452406e-2,-0.9998477,0.9998477,1.7452406e-2,0,0)"
+ id="line442"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(3.4899497e-2,-0.9993908,0.9993908,3.4899497e-2,0,0)"
+ id="line444"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(5.2335956e-2,-0.9986295,0.9986295,5.2335956e-2,0,0)"
+ id="line446"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(6.9756474e-2,-0.9975641,0.9975641,6.9756474e-2,0,0)"
+ id="line448"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.1045285,-0.9945219,0.9945219,0.1045285,0,0)"
+ id="line450"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.1218693,-0.9925462,0.9925462,0.1218693,0,0)"
+ id="line452"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.1391731,-0.9902681,0.9902681,0.1391731,0,0)"
+ id="line454"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.1564345,-0.9876883,0.9876883,0.1564345,0,0)"
+ id="line456"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.190809,-0.9816272,0.9816272,0.190809,0,0)"
+ id="line458"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.2079117,-0.9781476,0.9781476,0.2079117,0,0)"
+ id="line460"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.2249511,-0.9743701,0.9743701,0.2249511,0,0)"
+ id="line462"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.2419219,-0.9702957,0.9702957,0.2419219,0,0)"
+ id="line464"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.2756374,-0.9612617,0.9612617,0.2756374,0,0)"
+ id="line466"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.2923717,-0.9563048,0.9563048,0.2923717,0,0)"
+ id="line468"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.309017,-0.9510565,0.9510565,0.309017,0,0)"
+ id="line470"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.3255682,-0.9455186,0.9455186,0.3255682,0,0)"
+ id="line472"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.3583679,-0.9335804,0.9335804,0.3583679,0,0)"
+ id="line474"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.3746066,-0.9271839,0.9271839,0.3746066,0,0)"
+ id="line476"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.3907311,-0.9205049,0.9205049,0.3907311,0,0)"
+ id="line478"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.4067366,-0.9135455,0.9135455,0.4067366,0,0)"
+ id="line480"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.4383711,-0.898794,0.898794,0.4383711,0,0)"
+ id="line482"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.4539905,-0.8910065,0.8910065,0.4539905,0,0)"
+ id="line484"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.4694716,-0.8829476,0.8829476,0.4694716,0,0)"
+ id="line486"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.4848096,-0.8746197,0.8746197,0.4848096,0,0)"
+ id="line488"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.5150381,-0.8571673,0.8571673,0.5150381,0,0)"
+ id="line490"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.5299193,-0.8480481,0.8480481,0.5299193,0,0)"
+ id="line492"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.544639,-0.8386706,0.8386706,0.544639,0,0)"
+ id="line494"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.5591929,-0.8290376,0.8290376,0.5591929,0,0)"
+ id="line496"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.5877853,-0.809017,0.809017,0.5877853,0,0)"
+ id="line498"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.601815,-0.7986355,0.7986355,0.601815,0,0)"
+ id="line500"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.6156615,-0.7880108,0.7880108,0.6156615,0,0)"
+ id="line502"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.6293204,-0.777146,0.777146,0.6293204,0,0)"
+ id="line504"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.656059,-0.7547096,0.7547096,0.656059,0,0)"
+ id="line506"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.6691306,-0.7431448,0.7431448,0.6691306,0,0)"
+ id="line508"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.6819984,-0.7313537,0.7313537,0.6819984,0,0)"
+ id="line510"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.6946584,-0.7193398,0.7193398,0.6946584,0,0)"
+ id="line512"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.7193398,-0.6946584,0.6946584,0.7193398,0,0)"
+ id="line514"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.7313537,-0.6819984,0.6819984,0.7313537,0,0)"
+ id="line516"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.7431448,-0.6691306,0.6691306,0.7431448,0,0)"
+ id="line518"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.7547096,-0.656059,0.656059,0.7547096,0,0)"
+ id="line520"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.777146,-0.6293204,0.6293204,0.777146,0,0)"
+ id="line522"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.7880108,-0.6156615,0.6156615,0.7880108,0,0)"
+ id="line524"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.7986355,-0.601815,0.601815,0.7986355,0,0)"
+ id="line526"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.809017,-0.5877853,0.5877853,0.809017,0,0)"
+ id="line528"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.8290376,-0.5591929,0.5591929,0.8290376,0,0)"
+ id="line530"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.8386706,-0.544639,0.544639,0.8386706,0,0)"
+ id="line532"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.8480481,-0.5299193,0.5299193,0.8480481,0,0)"
+ id="line534"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.8571673,-0.5150381,0.5150381,0.8571673,0,0)"
+ id="line536"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.8746197,-0.4848096,0.4848096,0.8746197,0,0)"
+ id="line538"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.8829476,-0.4694716,0.4694716,0.8829476,0,0)"
+ id="line540"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.8910065,-0.4539905,0.4539905,0.8910065,0,0)"
+ id="line542"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.898794,-0.4383711,0.4383711,0.898794,0,0)"
+ id="line544"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9135455,-0.4067366,0.4067366,0.9135455,0,0)"
+ id="line546"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9205049,-0.3907311,0.3907311,0.9205049,0,0)"
+ id="line548"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9271839,-0.3746066,0.3746066,0.9271839,0,0)"
+ id="line550"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9335804,-0.3583679,0.3583679,0.9335804,0,0)"
+ id="line552"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9455186,-0.3255682,0.3255682,0.9455186,0,0)"
+ id="line554"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9510565,-0.309017,0.309017,0.9510565,0,0)"
+ id="line556"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9563048,-0.2923717,0.2923717,0.9563048,0,0)"
+ id="line558"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9612617,-0.2756374,0.2756374,0.9612617,0,0)"
+ id="line560"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9702957,-0.2419219,0.2419219,0.9702957,0,0)"
+ id="line562"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9743701,-0.2249511,0.2249511,0.9743701,0,0)"
+ id="line564"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9781476,-0.2079117,0.2079117,0.9781476,0,0)"
+ id="line566"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9816272,-0.190809,0.190809,0.9816272,0,0)"
+ id="line568"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9876883,-0.1564345,0.1564345,0.9876883,0,0)"
+ id="line570"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9902681,-0.1391731,0.1391731,0.9902681,0,0)"
+ id="line572"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9925462,-0.1218693,0.1218693,0.9925462,0,0)"
+ id="line574"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9945219,-0.1045285,0.1045285,0.9945219,0,0)"
+ id="line576"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9975641,-6.9756474e-2,6.9756474e-2,0.9975641,0,0)"
+ id="line578"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9986295,-5.2335956e-2,5.2335956e-2,0.9986295,0,0)"
+ id="line580"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9993908,-3.4899497e-2,3.4899497e-2,0.9993908,0,0)"
+ id="line582"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="240"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9998477,-1.7452406e-2,1.7452406e-2,0.9998477,0,0)"
+ id="line584"
+ style="stroke:#808080" />
+<!-- Units of 5 --> <line
+ x1="0"
+ y1="230"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9961947,8.7155743e-2,-8.7155743e-2,0.9961947,0,0)"
+ id="line586"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="230"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9659258,0.258819,-0.258819,0.9659258,0,0)"
+ id="line588"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="230"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9063078,0.4226183,-0.4226183,0.9063078,0,0)"
+ id="line590"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="230"
+ x2="0"
+ y2="250"
+ transform="matrix(0.819152,0.5735764,-0.5735764,0.819152,0,0)"
+ id="line592"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="230"
+ x2="0"
+ y2="250"
+ transform="matrix(0.5735764,0.819152,-0.819152,0.5735764,0,0)"
+ id="line594"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="230"
+ x2="0"
+ y2="250"
+ transform="matrix(0.4226183,0.9063078,-0.9063078,0.4226183,0,0)"
+ id="line596"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="230"
+ x2="0"
+ y2="250"
+ transform="matrix(0.258819,0.9659258,-0.9659258,0.258819,0,0)"
+ id="line598"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="230"
+ x2="0"
+ y2="250"
+ transform="matrix(8.7155743e-2,0.9961947,-0.9961947,8.7155743e-2,0,0)"
+ id="line600"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="230"
+ x2="0"
+ y2="250"
+ transform="matrix(-8.7155743e-2,0.9961947,-0.9961947,-8.7155743e-2,0,0)"
+ id="line602"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="230"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.258819,0.9659258,-0.9659258,-0.258819,0,0)"
+ id="line604"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="230"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.4226183,0.9063078,-0.9063078,-0.4226183,0,0)"
+ id="line606"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="230"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.5735764,0.819152,-0.819152,-0.5735764,0,0)"
+ id="line608"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="230"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.819152,0.5735764,-0.5735764,-0.819152,0,0)"
+ id="line610"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="230"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9063078,0.4226183,-0.4226183,-0.9063078,0,0)"
+ id="line612"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="230"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9659258,0.258819,-0.258819,-0.9659258,0,0)"
+ id="line614"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="230"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9961947,8.7155743e-2,-8.7155743e-2,-0.9961947,0,0)"
+ id="line616"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="230"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9961947,-8.7155743e-2,8.7155743e-2,-0.9961947,0,0)"
+ id="line618"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="230"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9659258,-0.258819,0.258819,-0.9659258,0,0)"
+ id="line620"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="230"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9063078,-0.4226183,0.4226183,-0.9063078,0,0)"
+ id="line622"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="230"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.819152,-0.5735764,0.5735764,-0.819152,0,0)"
+ id="line624"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="230"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.5735764,-0.819152,0.819152,-0.5735764,0,0)"
+ id="line626"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="230"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.4226183,-0.9063078,0.9063078,-0.4226183,0,0)"
+ id="line628"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="230"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.258819,-0.9659258,0.9659258,-0.258819,0,0)"
+ id="line630"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="230"
+ x2="0"
+ y2="250"
+ transform="matrix(-8.7155743e-2,-0.9961947,0.9961947,-8.7155743e-2,0,0)"
+ id="line632"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="230"
+ x2="0"
+ y2="250"
+ transform="matrix(8.7155743e-2,-0.9961947,0.9961947,8.7155743e-2,0,0)"
+ id="line634"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="230"
+ x2="0"
+ y2="250"
+ transform="matrix(0.258819,-0.9659258,0.9659258,0.258819,0,0)"
+ id="line636"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="230"
+ x2="0"
+ y2="250"
+ transform="matrix(0.4226183,-0.9063078,0.9063078,0.4226183,0,0)"
+ id="line638"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="230"
+ x2="0"
+ y2="250"
+ transform="matrix(0.5735764,-0.819152,0.819152,0.5735764,0,0)"
+ id="line640"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="230"
+ x2="0"
+ y2="250"
+ transform="matrix(0.819152,-0.5735764,0.5735764,0.819152,0,0)"
+ id="line642"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="230"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9063078,-0.4226183,0.4226183,0.9063078,0,0)"
+ id="line644"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="230"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9659258,-0.258819,0.258819,0.9659258,0,0)"
+ id="line646"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="230"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9961947,-8.7155743e-2,8.7155743e-2,0.9961947,0,0)"
+ id="line648"
+ style="stroke:#808080" />
+<!-- Units of 10 --> <line
+ x1="0"
+ y1="210"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9848078,0.1736482,-0.1736482,0.9848078,0,0)"
+ id="line650"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="210"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9396926,0.3420201,-0.3420201,0.9396926,0,0)"
+ id="line652"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="210"
+ x2="0"
+ y2="250"
+ transform="matrix(0.8660254,0.5,-0.5,0.8660254,0,0)"
+ id="line654"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="210"
+ x2="0"
+ y2="250"
+ transform="matrix(0.7660444,0.6427876,-0.6427876,0.7660444,0,0)"
+ id="line656"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="210"
+ x2="0"
+ y2="250"
+ transform="matrix(0.6427876,0.7660444,-0.7660444,0.6427876,0,0)"
+ id="line658"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="210"
+ x2="0"
+ y2="250"
+ transform="matrix(0.5,0.8660254,-0.8660254,0.5,0,0)"
+ id="line660"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="210"
+ x2="0"
+ y2="250"
+ transform="matrix(0.3420201,0.9396926,-0.9396926,0.3420201,0,0)"
+ id="line662"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="210"
+ x2="0"
+ y2="250"
+ transform="matrix(0.1736482,0.9848078,-0.9848078,0.1736482,0,0)"
+ id="line664"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="210"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.1736482,0.9848078,-0.9848078,-0.1736482,0,0)"
+ id="line666"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="210"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.3420201,0.9396926,-0.9396926,-0.3420201,0,0)"
+ id="line668"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="210"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.5,0.8660254,-0.8660254,-0.5,0,0)"
+ id="line670"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="210"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.6427876,0.7660444,-0.7660444,-0.6427876,0,0)"
+ id="line672"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="210"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.7660444,0.6427876,-0.6427876,-0.7660444,0,0)"
+ id="line674"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="210"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.8660254,0.5,-0.5,-0.8660254,0,0)"
+ id="line676"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="210"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9396926,0.3420201,-0.3420201,-0.9396926,0,0)"
+ id="line678"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="210"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9848078,0.1736482,-0.1736482,-0.9848078,0,0)"
+ id="line680"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="210"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9848078,-0.1736482,0.1736482,-0.9848078,0,0)"
+ id="line682"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="210"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.9396926,-0.3420201,0.3420201,-0.9396926,0,0)"
+ id="line684"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="210"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.8660254,-0.5,0.5,-0.8660254,0,0)"
+ id="line686"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="210"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.7660444,-0.6427876,0.6427876,-0.7660444,0,0)"
+ id="line688"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="210"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.6427876,-0.7660444,0.7660444,-0.6427876,0,0)"
+ id="line690"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="210"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.5,-0.8660254,0.8660254,-0.5,0,0)"
+ id="line692"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="210"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.3420201,-0.9396926,0.9396926,-0.3420201,0,0)"
+ id="line694"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="210"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.1736482,-0.9848078,0.9848078,-0.1736482,0,0)"
+ id="line696"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="210"
+ x2="0"
+ y2="250"
+ transform="matrix(0.1736482,-0.9848078,0.9848078,0.1736482,0,0)"
+ id="line698"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="210"
+ x2="0"
+ y2="250"
+ transform="matrix(0.3420201,-0.9396926,0.9396926,0.3420201,0,0)"
+ id="line700"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="210"
+ x2="0"
+ y2="250"
+ transform="matrix(0.5,-0.8660254,0.8660254,0.5,0,0)"
+ id="line702"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="210"
+ x2="0"
+ y2="250"
+ transform="matrix(0.6427876,-0.7660444,0.7660444,0.6427876,0,0)"
+ id="line704"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="210"
+ x2="0"
+ y2="250"
+ transform="matrix(0.7660444,-0.6427876,0.6427876,0.7660444,0,0)"
+ id="line706"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="210"
+ x2="0"
+ y2="250"
+ transform="matrix(0.8660254,-0.5,0.5,0.8660254,0,0)"
+ id="line708"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="210"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9396926,-0.3420201,0.3420201,0.9396926,0,0)"
+ id="line710"
+ style="stroke:#808080" />
+ <line
+ x1="0"
+ y1="210"
+ x2="0"
+ y2="250"
+ transform="matrix(0.9848078,-0.1736482,0.1736482,0.9848078,0,0)"
+ id="line712"
+ style="stroke:#808080" />
+<!-- Highlighted units --> <line
+ x1="0"
+ y1="-250"
+ x2="0"
+ y2="250"
+ transform="matrix(0.7071068,0.7071068,-0.7071068,0.7071068,0,0)"
+ id="line714"
+ style="stroke:#ff0000" />
+ <line
+ x1="0"
+ y1="-250"
+ x2="0"
+ y2="250"
+ transform="matrix(-0.7071068,0.7071068,-0.7071068,-0.7071068,0,0)"
+ id="line716"
+ style="stroke:#ff0000" />
+ <line
+ x1="0"
+ y1="-250"
+ x2="0"
+ y2="250"
+ transform="matrix(0,1,-1,0,0,0)"
+ id="line718"
+ style="stroke:#008000" />
+ <line
+ x1="0"
+ y1="-250"
+ x2="0"
+ y2="250"
+ id="line720"
+ style="stroke:#008000" />
+<!-- Helper circles --> <circle
+ cx="0"
+ cy="0"
+ r="200"
+ id="circle722"
+ sodipodi:cx="0"
+ sodipodi:cy="0"
+ sodipodi:rx="200"
+ sodipodi:ry="200"
+ style="fill:none;stroke:#808080" />
+ <circle
+ cx="0"
+ cy="0"
+ r="150"
+ id="circle724"
+ sodipodi:cx="0"
+ sodipodi:cy="0"
+ sodipodi:rx="150"
+ sodipodi:ry="150"
+ style="fill:none;stroke:#808080" />
+ <circle
+ cx="0"
+ cy="0"
+ r="100"
+ id="circle726"
+ sodipodi:cx="0"
+ sodipodi:cy="0"
+ sodipodi:rx="100"
+ sodipodi:ry="100"
+ style="fill:none;stroke:#808080" />
+ <circle
+ cx="0"
+ cy="0"
+ r="50"
+ id="circle728"
+ sodipodi:cx="0"
+ sodipodi:cy="0"
+ sodipodi:rx="50"
+ sodipodi:ry="50"
+ style="fill:none;stroke:#808080" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer1"
+ inkscape:label="nombres">
+ <g
+ id="g3000">
+ <text
+ sodipodi:linespacing="100%"
+ id="text2886"
+ y="64.47551"
+ x="266.91858"
+ style="font-size:14px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Thryomanes"
+ xml:space="preserve"><tspan
+ y="64.47551"
+ x="266.91858"
+ id="tspan2888"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ transform="matrix(0.934898,0.3549166,-0.3549166,0.934898,0,0)"
+ sodipodi:linespacing="100%"
+ id="text2890"
+ y="-49.55188"
+ x="336.83362"
+ style="font-size:14px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Thryomanes"
+ xml:space="preserve"><tspan
+ y="-49.55188"
+ x="336.83362"
+ id="tspan2892"
+ sodipodi:role="line">20</tspan></text>
+ <text
+ transform="matrix(0.983636,0.1801671,-0.1801671,0.983636,0,0)"
+ sodipodi:linespacing="100%"
+ id="text2894"
+ y="10.690214"
+ x="306.32104"
+ style="font-size:14px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Thryomanes"
+ xml:space="preserve"><tspan
+ y="10.690214"
+ x="306.32104"
+ id="tspan2896"
+ sodipodi:role="line">10</tspan></text>
+ <text
+ transform="matrix(0.858982,0.5120058,-0.5120058,0.858982,0,0)"
+ sodipodi:linespacing="100%"
+ id="text2898"
+ y="-111.83684"
+ x="359.12906"
+ style="font-size:14px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Thryomanes"
+ xml:space="preserve"><tspan
+ y="-111.83684"
+ x="359.12906"
+ id="tspan2900"
+ sodipodi:role="line">30</tspan></text>
+ <text
+ transform="matrix(0.7636146,0.6456724,-0.6456724,0.7636146,0,0)"
+ sodipodi:linespacing="100%"
+ id="text2902"
+ y="-173.86545"
+ x="369.93304"
+ style="font-size:14.00000191px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Thryomanes"
+ xml:space="preserve"><tspan
+ y="-173.86545"
+ x="369.93304"
+ id="tspan2904"
+ sodipodi:role="line">40</tspan></text>
+ <text
+ transform="matrix(0.6410049,0.7675368,-0.7675368,0.6410049,0,0)"
+ sodipodi:linespacing="100%"
+ id="text2906"
+ y="-239.44522"
+ x="369.7211"
+ style="font-size:14px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Thryomanes"
+ xml:space="preserve"><tspan
+ y="-239.44522"
+ x="369.7211"
+ id="tspan2908"
+ sodipodi:role="line">50</tspan></text>
+ <text
+ transform="matrix(0.4920312,0.8705776,-0.8705776,0.4920312,0,0)"
+ sodipodi:linespacing="100%"
+ id="text2910"
+ y="-307.14023"
+ x="355.31683"
+ style="font-size:14px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Thryomanes"
+ xml:space="preserve"><tspan
+ y="-307.14023"
+ x="355.31683"
+ id="tspan2912"
+ sodipodi:role="line">60</tspan></text>
+ <text
+ transform="matrix(0.3329444,0.9429465,-0.9429465,0.3329444,0,0)"
+ sodipodi:linespacing="100%"
+ id="text2914"
+ y="-368.034"
+ x="330.30707"
+ style="font-size:14.00000191px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Thryomanes"
+ xml:space="preserve"><tspan
+ y="-368.034"
+ x="330.30707"
+ id="tspan2916"
+ sodipodi:role="line">70</tspan></text>
+ <text
+ transform="matrix(0.1708576,0.9852957,-0.9852957,0.1708576,0,0)"
+ sodipodi:linespacing="100%"
+ id="text2918"
+ y="-422.65707"
+ x="299.59378"
+ style="font-size:14px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Thryomanes"
+ xml:space="preserve"><tspan
+ y="-422.65707"
+ x="299.59378"
+ id="tspan2920"
+ sodipodi:role="line">80</tspan></text>
+ <text
+ transform="matrix(0,1,-1,0,0,0)"
+ sodipodi:linespacing="100%"
+ id="text2922"
+ y="-472.76172"
+ x="257.04504"
+ style="font-size:14px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Thryomanes"
+ xml:space="preserve"><tspan
+ y="-472.76172"
+ x="257.04504"
+ id="tspan2924"
+ sodipodi:role="line">90</tspan></text>
+ </g>
+ <g
+ id="g3022"
+ transform="matrix(-1,0,0,-1,541.91333,530.70606)">
+ <text
+ sodipodi:linespacing="100%"
+ id="text3024"
+ y="64.47551"
+ x="266.91858"
+ style="font-size:14px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Thryomanes"
+ xml:space="preserve"><tspan
+ y="64.47551"
+ x="266.91858"
+ id="tspan3026"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ transform="matrix(0.934898,0.3549166,-0.3549166,0.934898,0,0)"
+ sodipodi:linespacing="100%"
+ id="text3028"
+ y="-49.55188"
+ x="336.83362"
+ style="font-size:14px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Thryomanes"
+ xml:space="preserve"><tspan
+ y="-49.55188"
+ x="336.83362"
+ id="tspan3030"
+ sodipodi:role="line">20</tspan></text>
+ <text
+ transform="matrix(0.983636,0.1801671,-0.1801671,0.983636,0,0)"
+ sodipodi:linespacing="100%"
+ id="text3032"
+ y="10.690214"
+ x="306.32104"
+ style="font-size:14px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Thryomanes"
+ xml:space="preserve"><tspan
+ y="10.690214"
+ x="306.32104"
+ id="tspan3034"
+ sodipodi:role="line">10</tspan></text>
+ <text
+ transform="matrix(0.858982,0.5120058,-0.5120058,0.858982,0,0)"
+ sodipodi:linespacing="100%"
+ id="text3036"
+ y="-111.83684"
+ x="359.12906"
+ style="font-size:14px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Thryomanes"
+ xml:space="preserve"><tspan
+ y="-111.83684"
+ x="359.12906"
+ id="tspan3038"
+ sodipodi:role="line">30</tspan></text>
+ <text
+ transform="matrix(0.7636146,0.6456724,-0.6456724,0.7636146,0,0)"
+ sodipodi:linespacing="100%"
+ id="text3040"
+ y="-173.86545"
+ x="369.93304"
+ style="font-size:14.00000191px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Thryomanes"
+ xml:space="preserve"><tspan
+ y="-173.86545"
+ x="369.93304"
+ id="tspan3042"
+ sodipodi:role="line">40</tspan></text>
+ <text
+ transform="matrix(0.6410049,0.7675368,-0.7675368,0.6410049,0,0)"
+ sodipodi:linespacing="100%"
+ id="text3044"
+ y="-239.44522"
+ x="369.7211"
+ style="font-size:14px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Thryomanes"
+ xml:space="preserve"><tspan
+ y="-239.44522"
+ x="369.7211"
+ id="tspan3046"
+ sodipodi:role="line">50</tspan></text>
+ <text
+ transform="matrix(0.4920312,0.8705776,-0.8705776,0.4920312,0,0)"
+ sodipodi:linespacing="100%"
+ id="text3048"
+ y="-307.14023"
+ x="355.31683"
+ style="font-size:14px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Thryomanes"
+ xml:space="preserve"><tspan
+ y="-307.14023"
+ x="355.31683"
+ id="tspan3050"
+ sodipodi:role="line">60</tspan></text>
+ <text
+ transform="matrix(0.3329444,0.9429465,-0.9429465,0.3329444,0,0)"
+ sodipodi:linespacing="100%"
+ id="text3052"
+ y="-368.034"
+ x="330.30707"
+ style="font-size:14.00000191px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Thryomanes"
+ xml:space="preserve"><tspan
+ y="-368.034"
+ x="330.30707"
+ id="tspan3054"
+ sodipodi:role="line">70</tspan></text>
+ <text
+ transform="matrix(0.1708576,0.9852957,-0.9852957,0.1708576,0,0)"
+ sodipodi:linespacing="100%"
+ id="text3056"
+ y="-422.65707"
+ x="299.59378"
+ style="font-size:14px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Thryomanes"
+ xml:space="preserve"><tspan
+ y="-422.65707"
+ x="299.59378"
+ id="tspan3058"
+ sodipodi:role="line">80</tspan></text>
+ <text
+ transform="matrix(0,1,-1,0,0,0)"
+ sodipodi:linespacing="100%"
+ id="text3060"
+ y="-472.76172"
+ x="257.04504"
+ style="font-size:14px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Thryomanes"
+ xml:space="preserve"><tspan
+ y="-472.76172"
+ x="257.04504"
+ id="tspan3062"
+ sodipodi:role="line">90</tspan></text>
+ </g>
+ <g
+ id="g3100">
+ <text
+ transform="matrix(0.9847623,-0.1739055,0.1739055,0.9847623,0,0)"
+ sodipodi:linespacing="100%"
+ id="text3064"
+ y="107.18554"
+ x="214.40683"
+ style="font-size:13.99999809px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Thryomanes"
+ xml:space="preserve"><tspan
+ y="107.18554"
+ x="214.40683"
+ id="tspan3066"
+ sodipodi:role="line">10</tspan></text>
+ <text
+ transform="matrix(0.9361242,-0.3516695,0.3516695,0.9361242,0,0)"
+ sodipodi:linespacing="100%"
+ id="text3068"
+ y="142.12463"
+ x="154.94203"
+ style="font-size:14px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Thryomanes"
+ xml:space="preserve"><tspan
+ y="142.12463"
+ x="154.94203"
+ id="tspan3070"
+ sodipodi:role="line">20</tspan></text>
+ <text
+ transform="matrix(0.8651952,-0.5014352,0.5014352,0.8651952,0,0)"
+ sodipodi:linespacing="100%"
+ id="text3072"
+ y="164.11133"
+ x="94.490524"
+ style="font-size:14px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Thryomanes"
+ xml:space="preserve"><tspan
+ y="164.11133"
+ x="94.490524"
+ id="tspan3074"
+ sodipodi:role="line">30</tspan></text>
+ <text
+ transform="matrix(0.7658449,-0.6430253,0.6430253,0.7658449,0,0)"
+ sodipodi:linespacing="100%"
+ id="text3076"
+ y="176.02191"
+ x="29.440561"
+ style="font-size:14px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Thryomanes"
+ xml:space="preserve"><tspan
+ y="176.02191"
+ x="29.440561"
+ id="tspan3078"
+ sodipodi:role="line">40</tspan></text>
+ <text
+ transform="matrix(0.6414771,-0.7671422,0.7671422,0.6414771,0,0)"
+ sodipodi:linespacing="100%"
+ id="text3080"
+ y="176.32138"
+ x="-36.683174"
+ style="font-size:14px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Thryomanes"
+ xml:space="preserve"><tspan
+ y="176.32138"
+ x="-36.683174"
+ id="tspan3082"
+ sodipodi:role="line">50</tspan></text>
+ <text
+ transform="matrix(0.497669,-0.867367,0.867367,0.497669,0,0)"
+ sodipodi:linespacing="100%"
+ id="text3084"
+ y="165.79233"
+ x="-102.21782"
+ style="font-size:14px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Thryomanes"
+ xml:space="preserve"><tspan
+ y="165.79233"
+ x="-102.21782"
+ id="tspan3086"
+ sodipodi:role="line">60</tspan></text>
+ <text
+ transform="matrix(0.3403569,-0.9402963,0.9402963,0.3403569,0,0)"
+ sodipodi:linespacing="100%"
+ id="text3088"
+ y="143.97623"
+ x="-164.87398"
+ style="font-size:14px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Thryomanes"
+ xml:space="preserve"><tspan
+ y="143.97623"
+ x="-164.87398"
+ id="tspan3090"
+ sodipodi:role="line">70</tspan></text>
+ <text
+ transform="matrix(0.1752809,-0.9845185,0.9845185,0.1752809,0,0)"
+ sodipodi:linespacing="100%"
+ id="text3092"
+ y="112.04415"
+ x="-221.67944"
+ style="font-size:14.00000191px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Thryomanes"
+ xml:space="preserve"><tspan
+ y="112.04415"
+ x="-221.67944"
+ id="tspan3094"
+ sodipodi:role="line">80</tspan></text>
+ </g>
+ <g
+ id="g3118"
+ transform="matrix(-1,0,0,-1,542.14643,530.77343)">
+ <text
+ transform="matrix(0.9847623,-0.1739055,0.1739055,0.9847623,0,0)"
+ sodipodi:linespacing="100%"
+ id="text3120"
+ y="107.18554"
+ x="214.40683"
+ style="font-size:13.99999809px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Thryomanes"
+ xml:space="preserve"><tspan
+ y="107.18554"
+ x="214.40683"
+ id="tspan3122"
+ sodipodi:role="line">10</tspan></text>
+ <text
+ transform="matrix(0.9361242,-0.3516695,0.3516695,0.9361242,0,0)"
+ sodipodi:linespacing="100%"
+ id="text3124"
+ y="142.12463"
+ x="154.94203"
+ style="font-size:14px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Thryomanes"
+ xml:space="preserve"><tspan
+ y="142.12463"
+ x="154.94203"
+ id="tspan3126"
+ sodipodi:role="line">20</tspan></text>
+ <text
+ transform="matrix(0.8651952,-0.5014352,0.5014352,0.8651952,0,0)"
+ sodipodi:linespacing="100%"
+ id="text3128"
+ y="164.11133"
+ x="94.490524"
+ style="font-size:14px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Thryomanes"
+ xml:space="preserve"><tspan
+ y="164.11133"
+ x="94.490524"
+ id="tspan3130"
+ sodipodi:role="line">30</tspan></text>
+ <text
+ transform="matrix(0.7658449,-0.6430253,0.6430253,0.7658449,0,0)"
+ sodipodi:linespacing="100%"
+ id="text3132"
+ y="176.02191"
+ x="29.440561"
+ style="font-size:14px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Thryomanes"
+ xml:space="preserve"><tspan
+ y="176.02191"
+ x="29.440561"
+ id="tspan3134"
+ sodipodi:role="line">40</tspan></text>
+ <text
+ transform="matrix(0.6414771,-0.7671422,0.7671422,0.6414771,0,0)"
+ sodipodi:linespacing="100%"
+ id="text3136"
+ y="176.32138"
+ x="-36.683174"
+ style="font-size:14px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Thryomanes"
+ xml:space="preserve"><tspan
+ y="176.32138"
+ x="-36.683174"
+ id="tspan3138"
+ sodipodi:role="line">50</tspan></text>
+ <text
+ transform="matrix(0.497669,-0.867367,0.867367,0.497669,0,0)"
+ sodipodi:linespacing="100%"
+ id="text3140"
+ y="165.79233"
+ x="-102.21782"
+ style="font-size:14px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Thryomanes"
+ xml:space="preserve"><tspan
+ y="165.79233"
+ x="-102.21782"
+ id="tspan3142"
+ sodipodi:role="line">60</tspan></text>
+ <text
+ transform="matrix(0.3403569,-0.9402963,0.9402963,0.3403569,0,0)"
+ sodipodi:linespacing="100%"
+ id="text3144"
+ y="143.97623"
+ x="-164.87398"
+ style="font-size:14px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Thryomanes"
+ xml:space="preserve"><tspan
+ y="143.97623"
+ x="-164.87398"
+ id="tspan3146"
+ sodipodi:role="line">70</tspan></text>
+ <text
+ transform="matrix(0.1752809,-0.9845185,0.9845185,0.1752809,0,0)"
+ sodipodi:linespacing="100%"
+ id="text3148"
+ y="112.04415"
+ x="-221.67944"
+ style="font-size:14.00000191px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Thryomanes"
+ xml:space="preserve"><tspan
+ y="112.04415"
+ x="-221.67944"
+ id="tspan3150"
+ sodipodi:role="line">80</tspan></text>
+ </g>
+ </g>
+</svg>index 6d2aed2..3dd306a 100644
--- a/content/day-2.txt
+++ b/content/day-2.txt
@@ -48,7 +48,7 @@ But what is it that makes a circle what it is? First of all we have to realize t
= dots = 0
= circle = 1 0
= angles = 0 1
- center = True
+ center = red
= radius = 0 1
= scatter = False
=
@@ -59,15 +59,21 @@ We can say that four or more things lay on a circle, if the distance to the cent
= dots = 5
= circle = 3 3
= angles = 0 1
- center = True
+ center = red
= radius = 3 3
= scatter = False
=
-Why more than three? Well, think about it in terms of a fair challenge. If it's one, then it can lay on any circle that crosses the place. There is very many circles like that (in fact infinitely many). The same for two. Not every circle will do, but still you could draw many different circles that they will lay on. For three things there will always be exactly one circle that they lay on (except if they lay in one line, unless you claim that straight line is an infinite circle). So three is still not much of a challenge. You could place them however you like, and claim that they lay on a circle.
+| Note
+ Why more than three? One, two or three things always lay on at least one circle, so it's not much of a challenge that way.
=
-So only when it's four or more things, we can seriously ask whether they lay on a circle or not.
+ One thing lay on any circle that crosses the place. You could draw very many circles like that (in fact as many as you want).
+
+ Similar for two things. Not every circle will do, but still you could draw many different circles that they will lay on.
+
+ For three things there will always be exactly one circle that they lay on (except if they lay in one line). So three is still not much of a challenge. You could place them however you like, and always say that they lay on a circle.
+
+ Only when it's four or more things, we can seriously ask whether they lay on a circle or not.
=
-| Note
= TODO: Consider if the above adds more value or confusion.
=
= TODO: An example showing multiple circles crossing one and two points
@@ -77,7 +83,7 @@ So only when it's four or more things, we can seriously ask whether they lay on
=
=Because we are all about challenges, let's make five dots.
=
-So, once again - we can say that several things lay on a circle if the distance between each of them and the center of the circle is the same. We call this distance /a *radius* of a circle/.
+So, once again - we can say that several things (in our case dots) lay on a circle if the distance between each of them and the center of the circle is the same. We call this distance /a *radius* of a circle/.
=
=| Note
= Matematician would say:
@@ -86,7 +92,7 @@ So, once again - we can say that several things lay on a circle if the distance
=
= That is more precise, but perhaps a little more difficult to visualize.
=
-Ok, so this describes what it means to lay on a circle. But what are some efficient methods of putting things there? How about this.
+Ok, so this describes what it means to lay on a circle. But what are some efficient methods of putting things there? How about the following.
=
=You choose a center (anywhere, say {Code|(0, 0)}) and radius (any length you like, say 80).
=
@@ -143,32 +149,281 @@ So they lay on a circle, but there is a striking difference. They are all scatte
=
=Since the center and radius is the same for each dot, the only thing we can play with is the angle that we rotate the ruler each time.
=
-TODO: ___✁___ edit from here
+A common way of representing rotation is in degrees. It works like that: imagine a circle around you. You are in the center. Now imagine that the circle is divided in 360 equal segments. If you turn from the segment you face to the next one on the right (a very small turn) then you have turned one degree. If you turn around to face the opposite direction, that's a turn of 180 degrees. Do it again and you are facing the same direction as before (a 360 degree turn). If you turn to the right (as on a corner of a street), then you have turned 90 degrees (half of 180, quarter of 360).
+
+In physical world we often use a tool called protractor to measure angles. It typically has one degree segments already marked for you. It looks like this:
+
+| Image
+ src = /assets/protractor.svg
+ description = A protractor by Georges Khaznadar <georgesk@ofset.org>
+
+| Note
+ TODO: Modify the image to make marks 0 - 360. We can convert it to Elm code (using https:////level.app//svg-to-elm) and then reuse it in our examples.
+
+Now if we make the turns of the ruler equal between placing each dot, then the dots will be placed evenly. Equal turns mean that the number of degrees covered by each turn will be the same, including the turn you would need to make to go from the last dot back to the first. But how many degrees shall it be?
+
+In mathematics there is a special operation to determine it. Perhaps you have heard about it: it's called division and is written like that:
+
+| Monospace
+ 360 / 5
+
+Maybe you can solve it in your head, but if not, why not use Elm REPL? Go to the terminal, stop the reactor (if it's running) and type:
+
+| Monospace
+ elm repl
=
-What does this have to do with a circle? Do you know the expression 'I did a 180'. Where would you be looking if you did two 180s (a '360'). We see that circles and angles are closely related!
+And then
=
-Exercise with compass and 5 objects, place objects evenly along compass. How many degrees apart are they? (observe if we multiply the result by 5, we're back to 360)
+| Monospace
+ 360 / 5
=
-We have one part of it (angle), we now need the distance. Notice they are all the same distance from the origin (the center dot) of the compass. Definition of circle: points that are an equal distance from a center. Actually, the distance doesn't matter, so long as they all have the same distance. You can have a big circle or a small circle, they're both circles
+You should see:
=
-Ok great, we're done! Now, does anyone know how to give an angle and distance to svg? Oh... no? We don't either... you can't. You can only give x and y values relative to the origin
+| Monospace
+ ---- Elm 0.19.0 ----------------------------------------------------------------
+ Read <https://elm-lang.org/0.19.0/repl> to learn more: exit, help, imports, etc.
+ --------------------------------------------------------------------------------
+ > 360 / 5
+ 72 : Float
+ >
=
-So we already know that angle and length are just another way of describing x and y. But we need some way of translating between the two
+Type {Code|:exit} to close the REPL.
+
+Turns out that the result is 72. That's the number of degrees we have to turn our ruler around the center each time we place a new dot. If you have a protractor like the one pictured above, you can use it to measure the turn. Then measure the distance from the center along the ruler (a length called radius) and place the dot there.
+
+| Header
+ The Code
+
+Now that we understand what it means to be placed on a circle and evenly distributed, it's time to create a program that displays dots like that. Once again, this is the intended result:
+
+| Window
+ | Circle
+ dots = 5
+ circle = 0 1
+ angles = 0 1
+ center = none
+ radius = 0 1
+ scatter = False
=
-sing a visual demonstration, if you draw a line from your object to the x axis, you have a triangle. Same if you draw a line to the y axis. You can figure out the point on the axis using sin and cos functions and multiplying the result by the length.
+We will start from where we left off yesterday. Open {Code|src//Main.elm} in your editor. It should have the following code:
=
-Let's use a chart to figure out the sin and cos of our angles
+| Code
+ module Main exposing (main)
=
-/It's important to get a chart, otherwise we have to use calculators which work with radians/
+ import Element
+ import Svg
+ import Svg.Attributes
=
-Now we are done... we can plug in our x and y values, and presto, our dots are arranged in a circle. But we don't see most of them...
=
-Our origin is in the top left corner. This means any dots with negative x or y values are off the screen.
+ main =
+ Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200"
+ ]
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ ]
+ []
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
=
-We can shift the dots so they are on the screen, or shift the screen so that it covers the dots. We will be shifting the screen
+It's a good starting point. We already have a single dot in the middle. Let's say this is will be the center of our circle.
=
-Sample viewbox program to demonstrate
+Now it's time to introduce our protractor and ruler. These is a property of an SVG element called `transform`. You use it like that:
=
-Create a viewbox with correct perimeters (must be more than the radius of the circle plus the radius of a dot in each direction, and width and height are circumference)
+| Monospace
+ Svg.Attributes.transform "translate(80)"
=
-{Code|svg [viewbox "-100 -100 200 200 " ] []}
+This will move the dot ("translate" is a fancy term for "move") 80 units.
+
+Also, let's change the color of the dot. In the end it should look like this:
+
+
+| Code
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.transform "translate(80)"
+ , Svg.Attributes.fill "skyblue"
+ ]
+ []
+ ]
+
+With this change, start the reactor by typing in your terminal:
+
+| Monospace
+ elm reactor
+
+and {Link|open the program in your browser|url=http://localhost/src/Main.elm}. You should see something like this:
+
+| Window
+ | Circle
+ dots = 1
+ circle = 0 1
+ angles = 0 1
+ center = none
+ radius = 0 1
+ scatter = False
+
+As you can see the dot is no longer in the center - it has moved!
+
+Now we need a second dot. If you remember the discussion about lists from previous day, you know what to do. Just duplicate the first SVG circle and put it after the first one, with a coma in between. Like this:
+
+| Code
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.transform "translate(80)"
+ , Svg.Attributes.fill "skyblue"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "orange"
+ , Svg.Attributes.transform "translate(80)"
+ ]
+ []
+ ]
+
+Now let's focus on the second one. We gave it a different color, so we can distinguish between them, but now we don't see the first one! That's because it's exactly in the same spot as the second one, which covers it. They are stacked in a way. We need to move the second dot in a different direction, so it's on a circle, but 72 degree apart from the first one.
+
+For that we need to add another transformation before the move. First we will rotate, then translate:
+
+| Code
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.transform "translate(80)"
+ , Svg.Attributes.fill "skyblue"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "orange"
+ , Svg.Attributes.transform "rotate(72) translate(80)"
+ ]
+ []
+ ]
+
+This will rotate the element by 72 degrees. On it's own it wouldn't make any difference, because the dot is round - it doesn't matter how you rotate it, it always looks the same. But It also rotates it's internal coordinates system. So the translation will move it in a different direction then the first dot.
+
+Now it's time to add the fird, red dot. Duplicate the second one, change fill to `"red"` and put `144` for rotate function, like this:
+
+| Code
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.transform "translate(80)"
+ , Svg.Attributes.fill "skyblue"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "orange"
+ , Svg.Attributes.transform "rotate(72) translate(80)"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "red"
+ , Svg.Attributes.transform "rotate(144) translate(80)"
+ ]
+ []
+ ]
+
+Why 144? It's because 72 + 72 is 144! So to be 72 degree apart from a dot that's at 72 degree, you need to be at 144 (or 0, but the first dot is already there).
+
+This makes the last two dots trivial. Just rotate them to 216 and 288 and give them lime and maroon colors. Note that 288 + 72 = 360, so the distance between the first and last dot is correct.
+
+
+The complete program should look like this:
+
+| Code
+ module Main exposing (main)
+
+ import Element
+ import Svg
+ import Svg.Attributes
+
+
+ main =
+ Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200"
+ ]
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.transform "translate(80)"
+ , Svg.Attributes.fill "skyblue"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "orange"
+ , Svg.Attributes.transform "rotate(72) translate(80)"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "red"
+ , Svg.Attributes.transform "rotate(144) translate(80)"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "lime"
+ , Svg.Attributes.transform "rotate(216) translate(80)"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "maroon"
+ , Svg.Attributes.transform "rotate(288) translate(80)"
+ ]
+ []
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+In the browser it should look exactly as we planned:
+
+| Window
+ | Circle
+ dots = 5
+ circle = 0 1
+ angles = 0 1
+ center = none
+ radius = 0 1
+ scatter = False
+
+| Emphasize
+ Congtrarulations! You are ready for {Link|Day 3|url=/day-3.html}!index 7d4ced3..b38e6aa 100644
--- a/src/Examples/Circle.elm
+++ b/src/Examples/Circle.elm
@@ -23,6 +23,14 @@ type alias Config =
= }
=
=
+
+{- TODO:
+ - Display radius (text)
+ - Display angles of turns (in degrees)
+ - Make an arrow head at the end point of the arc representing a turn (see https://package.elm-lang.org/packages/ianmackenzie/elm-geometry/latest/Arc2d#tangentDirection)
+-}
+
+
=main : Html.Html msg
=main =
= Element.layoutMake Monospace block font size smaller
Otherwise Elm REPL welcom lines don't fit in the block and cause scroll.
index ca76c37..bfe94a4 100644
--- a/src/Mark/Custom.elm
+++ b/src/Mark/Custom.elm
@@ -63,7 +63,7 @@ monospace : Mark.Block (model -> Element msg)
=monospace =
= Mark.Default.monospace
= [ Element.padding 20
- , Font.size 18
+ , Font.size 14
= , Element.width Element.fill
= , Border.color colors.gray
= , Border.width 3
Commits: 8
More progress on Day 1
index 8359570..beac6da 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@
=/public/
=/built/
=/node_modules/
+/src/Playground.elmindex 83cc9bf..9cf6974 100644
--- a/content/day-1.txt
+++ b/content/day-1.txt
@@ -85,7 +85,11 @@ Note that the same address was printed by the Elm reactor.
=
=We want to display a dot at the center of the screen, like this:
=
-| DotAtTheCenterOfTheScreen
+| DotWithViewBox
+ background=none
+ fill=black
+ viewBox=-300 -300 600 600
+
=
=Below is the complete code to draw a dot like this, but don't type it in your editor yet! We are going to recreate it together, step by step.
=
@@ -128,9 +132,8 @@ Its all about drawing shapes on the screen. Let's install an Elm Package to help
=Now that we have {Code|elm//svg} installed, we can write the code that will draw our dot:
=
=| Code
- module Simplest exposing (main)
+ module Main exposing (main)
=
- import Element
= import Svg
= import Svg.Attributes
=
@@ -138,9 +141,7 @@ Now that we have {Code|elm//svg} installed, we can write the code that will draw
= main =
= Svg.svg []
= [ Svg.circle
- [ Svg.Attributes.r "10"
- ]
- []
+ [ Svg.Attributes.r "10" ] []
= ]
=
=Start the reactor again:
@@ -153,7 +154,7 @@ Start the reactor again:
=
=Reload the browser. You should see something like this:
=
-| Simplest
+| Dot
= background=none
= fill=black
=
@@ -163,15 +164,22 @@ There are two reasons. First, because the center of the dot is at a point called
=
=Second, it's because the SVG space doesn't fill the browser window. Don't believe me? We can see that the space doesn't fill the window by changing the background color of the SVG element.
=
-| Note
- Code sample for simplest with pink background should be here.
+| Code
+ module Main exposing (main)
=
-When you reload the browser, you'll see this:
+ import Svg
+ import Svg.Attributes
=
-| Note
- Example of simplest with pink background should be here.
=
-| Simplest
+ main =
+ Svg.svg [ Svg.Attributes.style "background: pink" ]
+ [ Svg.circle
+ [ Svg.Attributes.r "10" ] []
+ ]
+
+When you reload the browser, you'll see this:
+
+| Dot
= background=pink
= fill=black
=
@@ -185,11 +193,42 @@ Press {Code|CTRL + C} to stop the elm-reactor, type {Code|elm install mdgriffith
=
=Then in {Code|src//Main.elm} add the necessary import {Code|import Element} and change {Code|Main} to look like this:
=
-Main = * the code of Element.layout should be here*
+| Code
+ module Main exposing (main)
=
-Take a wild guess: what do you think {Code|Element.height Element.fill} and {Code|Element.height Element.width} do?
+ import Element
+ import Svg
+ import Svg.Attributes
=
-Reload the browser to confirm your hypothesis. Voila! The SVG space now fills the screen. It should now be possible to place our dot in the center of the screen. But, how exactly will we achieve it?
+
+ main =
+ Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+ (Element.html
+ (Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: pink"
+ ]
+ [ Svg.circle
+ [ Svg.Attributes.r "10" ] []
+ ]
+ )
+ )
+
+Take a wild guess: what do you think {Code|Element.height Element.fill}, {Code|Svg.Attributes.height "100%"}, {Code|Element.width Element.fill} and {Code|Svg.Attributes.width "100%"} do?
+
+Reload the browser to confirm your hypothesis. Voila! The SVG space now fills the screen.
+
+| DotInElement
+ background=pink
+ fill=black
+ cx=0
+ cy=0
+
+It should now be possible to place our dot in the center of the screen. But, how exactly will we achieve it?
=
=Let's return to the idea we introduced earlier, that the center of the dot is at a point called the {Code|origin} or {Code|(0, 0)}. What eactly does this mean?
=
@@ -203,7 +242,40 @@ Knowing that the center of the dot is at the {Code|origin} or point {Code|(0,0)}
=
=To change the position of the dot, we can use the {Code|cx} and {Code|cy} attributes of the {Code|circle} element.
=
-* simplest with cx and cy should be here *
+| Code
+ module Main exposing (main)
+
+ import Element
+ import Svg
+ import Svg.Attributes
+
+
+ main =
+ Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+ (Element.html
+ (Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: pink"
+ ]
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "100"
+ , Svg.Attributes.cy "50"
+ ]
+ []
+ ]
+ )
+ )
+
+| DotInElement
+ background=pink
+ fill=black
+ cx=100
+ cy=50
=
=Now, if we want our dot to be exactly in the center of the screen, we'll have to set {Code|cx} and {Code|cy} to values equal to exactly half of the {Code|width} and {Code|height} of the SVG space.
=
@@ -213,7 +285,7 @@ We can calculate the position of the circle as:
= cx = width/2
= cy = height/2
=
-There is a problem though! We don't actually know the width and height of the SVG space. All we know is that its {Code|width} and {Code|height} will {Code|fill} the viewport, whatever its size.
+There is a problem though! We don't actually know the width and height of the SVG space. All we know is that out {Code|layout's} {Code|width} and {Code|height} will {Code|fill} the viewport, and the {Code|svg} space's {Code|width} and {Code|height} are {Code|100%} of the {Code|layout}. We don't know the exact size of either (because the {Code|layout} will scale to the browser window).
=☹️
=
=Fortunately, we don't need to know the width and height of the SVG space!
@@ -226,12 +298,37 @@ A videwbox is like a window into an SVG space. It allows us to change the point
=
=| ViewBox
=
-In this example, we can set the width and height of the viewbox to an arbitrary value. As we see in the interactive example, the width and height of the viewbox will effect how big the dot appears, but that's not so important to us at the moment. Let's set them both to 1000.
+In this example, we can set the width and height of the viewbox to an arbitrary value. As we see in the interactive example, the width and height of the viewbox will effect how big the dot appears, but that's not so important to us at the moment. Let's set them both to 600.
+
+The top and left values of the viewbox are a bit trickier. We saw ealier that if we knew the width and height of our SVG scene, we could have set the cx and cy values of the dot to half the width and height of the scene to center the dot. We will have to use similar reasoning here. If we set the top and left values of the viewbox to 0, the dot will still still appear in the top left corner of the scene. If we want to see the dot moved to the right and downward within the scene, we will need the top and left values of the viewbox to be negative. If we want the origin of the SVG space to appear directly in the center of the scene, we will need to set the top and left values of the viewbox to {Code|- height//2} and {Code|- width//2}. In this case, they will both have the value 300.
+
+| Code
+ module Main exposing (main)
+
+ import Element
+ import Svg
+ import Svg.Attributes
+
=
-The top and left values of the viewbox are a bit trickier. We saw ealier that if we knew the width and height of our SVG scene, we could have set the cx and cy values of the dot to half the width and height of the scene to center the dot. We will have to use similar reasoning here. If we set the top and left values of the viewbox to 0, the dot will still still appear in the top left corner of the scene. If we want to see the dot moved to the right and downward within the scene, we will need the top and left values of the viewbox to be negative. If we want the origin of the SVG space to appear directly in the center of the scene, we will need to set the top and left values of the viewbox to {Code|- height//2} and {Code|- width//2}. In this case, they will both have the value 500.
+ main =
+ Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+ (Element.html
+ (Svg.svg
+ [ Svg.Attributes.viewBox "-300 -300 600 600" ]
+ [ Svg.circle [ Svg.Attributes.r "10" ] []
+ ]
+ )
+ )
=
=Refresh the broswer. We should now see the dot in the center of the screen.
=
+| DotWithViewBox
+ background=none
+ fill=black
+ viewBox=-300 -300 600 600
=
=Give a color to the dot
=similarity index 91%
rename from src/Simplest.elm
rename to src/Dot.elm
index 2e65fa4..0f6eec4 100644
--- a/src/Simplest.elm
+++ b/src/Dot.elm
@@ -1,4 +1,4 @@
-module Simplest exposing (Config, defaults, ui)
+module Dot exposing (Config, defaults, ui)
=
=import Html exposing (Html)
=import Svgdeleted file mode 100644
index 83f0d1d..0000000
--- a/src/DotAtTheCenterOfTheScreen.elm
+++ /dev/null
@@ -1,27 +0,0 @@
-module DotAtTheCenterOfTheScreen exposing (main, ui)
-
-import Element
-import Svg
-import Svg.Attributes
-
-
-main =
- Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
- ]
- ui
-
-
-ui =
- Svg.svg
- [ Svg.Attributes.viewBox "-100 -100 200 200"
- ]
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- ]
- []
- ]
- |> Element.htmlnew file mode 100644
index 0000000..a3e3397
--- /dev/null
+++ b/src/DotInElement.elm
@@ -0,0 +1,44 @@
+module DotInElement exposing (Config, main, ui)
+
+import Element exposing (Element)
+import Svg
+import Svg.Attributes
+
+
+type alias Config =
+ { background : String
+ , fill : String
+ , cx : String
+ , cy : String
+ }
+
+
+defaults : Config
+defaults =
+ Config "pink" "black" "0" "0"
+
+
+main =
+ Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+ (ui defaults)
+
+
+ui : Config -> Element msg
+ui { background, fill, cx, cy } =
+ Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style <| "background: " ++ background
+ ]
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.fill fill
+ , Svg.Attributes.cx cx
+ , Svg.Attributes.cy cy
+ ]
+ []
+ ]
+ |> Element.htmlsimilarity index 66%
rename from src/CenteredDot.elm
rename to src/DotWithViewBox.elm
index 59fb692..d26753c 100644
--- a/src/CenteredDot.elm
+++ b/src/DotWithViewBox.elm
@@ -1,4 +1,9 @@
-module CenteredDot exposing (main)
+module DotWithViewBox exposing (Config, main, ui)
+
+import Element
+import Svg
+import Svg.Attributes
+
=
={-| This program demonstrates how to center an element within an SVG viewport. The idea is to make sure that (0, 0) point (so called origin) should be in the middle of the ViewBox. We can achive that, by making the top-left corner of the ViewBox same distance from the (0, 0) as the bottom right.
=
@@ -18,23 +23,35 @@ In fact it doesnt matter how wide and high the viewBox is. As long as the top wi
=That's the trick to position an object (like a dot) exactly in the center of the SVG viewport
=
=-}
+type alias Config =
+ { background : String
+ , fill : String
+ , viewBox : String
+ }
=
-import Element
-import Svg
-import Svg.Attributes
+
+defaults : Config
+defaults =
+ Config "white" "black" "-500 -500 1000 1000"
=
=
=main =
+ Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+ (ui defaults)
+
+
+ui { background, fill, viewBox } =
= Svg.svg
- [ Svg.Attributes.style "background: pink"
- , Svg.Attributes.width "300"
- , Svg.Attributes.height "150"
- , Svg.Attributes.viewBox "-100 -100 200 200"
+ [ Svg.Attributes.viewBox viewBox
+ , Svg.Attributes.style <| "background: " ++ background
= ]
= [ Svg.circle
= [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill fill
= ]
= []
= ]
+ |> Element.htmldeleted file mode 100644
index 56f88d6..0000000
--- a/src/FillTheScreen.elm
+++ /dev/null
@@ -1,28 +0,0 @@
-module FillTheScreen exposing (main, ui)
-
-import Element
-import Svg
-import Svg.Attributes
-
-
-main =
- Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
- ]
- ui
-
-
-ui =
- Svg.svg
- [ Svg.Attributes.style "background: pink"
- , Svg.Attributes.viewBox "-100 -100 200 200"
- ]
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- ]
- []
- ]
- |> Element.htmlindex 019ba58..87b6396 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -11,10 +11,11 @@ import Browser
=import Browser.Navigation as Navigation
=import BrowserWindow
=import CartesianCoordinates
-import CenteredDot
=import Counter
=import Dict
-import DotAtTheCenterOfTheScreen
+import Dot
+import DotInElement
+import DotWithViewBox
=import Element exposing (Element)
=import Element.Background as Background
=import Element.Border as Border
@@ -22,7 +23,6 @@ import Element.Events
=import Element.Font as Font
=import Element.Input as Input
=import FeatherIcons exposing (icons)
-import FillTheScreen
=import Gradient
=import Html exposing (Html)
=import Html.Attributes
@@ -38,7 +38,6 @@ import PolarCoordinates
=import Result.Extra as Result
=import RosetteTypedTransformations
=import Routes exposing (Route)
-import Simplest
=import Spiral
=import Transformations
=import Tree
@@ -447,10 +446,9 @@ document =
=
= -- Embeded programs
= , counter
- , simplest
- , fillTheScreen
- , dotAtTheCenterOfTheScreen
- , centeredDot
+ , dot
+ , dotInElement
+ , dotWithViewBox
= , line
= , gradient
= , transformations
@@ -484,66 +482,54 @@ document =
= in
= Mark.stub "Counter" render
=
- simplest : Mark.Block (Model -> Element Msg)
- simplest =
+ dot : Mark.Block (Model -> Element Msg)
+ dot =
= let
- render : Simplest.Config -> Model -> Element Msg
+ render : Dot.Config -> Model -> Element Msg
= render config model =
- Simplest.ui config
+ Dot.ui config
= |> Element.html
- |> Element.el []
= |> Element.el
- [ Element.height (Element.px 400)
- , Element.width Element.fill
+ [ Element.width (Element.px 300)
= , Element.padding 5
= ]
- |> BrowserWindow.window []
+ |> BrowserWindow.window [ Element.height (Element.px 300) ]
= in
- Mark.record2 "Simplest"
- Simplest.Config
+ Mark.record2 "Dot"
+ Dot.Config
= (Mark.field "background" Mark.string)
= (Mark.field "fill" Mark.string)
= |> Mark.map render
=
- fillTheScreen : Mark.Block (Model -> Element Msg)
- fillTheScreen =
- let
- render model =
- FillTheScreen.ui
- |> Element.el
- [ Element.height (Element.px 400)
- , Element.width Element.fill
- ]
- |> BrowserWindow.window []
- in
- Mark.stub "FillTheScreen" render
-
- dotAtTheCenterOfTheScreen : Mark.Block (Model -> Element Msg)
- dotAtTheCenterOfTheScreen =
+ dotInElement : Mark.Block (Model -> Element Msg)
+ dotInElement =
= let
- render model =
- DotAtTheCenterOfTheScreen.ui
- |> Element.el
- [ Element.height (Element.px 400)
- , Element.width Element.fill
- ]
- |> BrowserWindow.window []
+ render config model =
+ DotInElement.ui config
+ |> BrowserWindow.window [ Element.height (Element.px 300) ]
= in
- Mark.stub "DotAtTheCenterOfTheScreen" render
+ Mark.record4 "DotInElement"
+ DotInElement.Config
+ (Mark.field "background" Mark.string)
+ (Mark.field "fill" Mark.string)
+ (Mark.field "cx" Mark.string)
+ (Mark.field "cy" Mark.string)
+ |> Mark.map render
=
- centeredDot : Mark.Block (Model -> Element Msg)
- centeredDot =
+ dotWithViewBox : Mark.Block (Model -> Element Msg)
+ dotWithViewBox =
= let
- render model =
- CenteredDot.main
- |> Element.html
- |> Element.el
- [ Element.height (Element.px 400)
- , Element.width Element.fill
- ]
+ render config model =
+ DotWithViewBox.ui config
+ |> Element.el [ Element.height (Element.px 300) ]
= |> BrowserWindow.window []
= in
- Mark.stub "CenteredDot" render
+ Mark.record3 "DotWithViewBox"
+ DotWithViewBox.Config
+ (Mark.field "background" Mark.string)
+ (Mark.field "fill" Mark.string)
+ (Mark.field "viewBox" Mark.string)
+ |> Mark.map render
=
= line : Mark.Block (Model -> Element Msg)
= line =Merge branch 'multiple-pages' into review-day-one
Fix bad imports with merge
index 0f6eec4..5581546 100644
--- a/src/Examples/Dot.elm
+++ b/src/Examples/Dot.elm
@@ -1,4 +1,4 @@
-module Dot exposing (Config, defaults, ui)
+module Examples.Dot exposing (Config, defaults, ui)
=
=import Html exposing (Html)
=import Svgindex a3e3397..ee127b3 100644
--- a/src/Examples/DotInElement.elm
+++ b/src/Examples/DotInElement.elm
@@ -1,4 +1,4 @@
-module DotInElement exposing (Config, main, ui)
+module Examples.DotInElement exposing (Config, main, ui)
=
=import Element exposing (Element)
=import Svgindex d26753c..6f91dc6 100644
--- a/src/Examples/DotWithViewBox.elm
+++ b/src/Examples/DotWithViewBox.elm
@@ -1,4 +1,4 @@
-module DotWithViewBox exposing (Config, main, ui)
+module Examples.DotWithViewBox exposing (Config, main, ui)
=
=import Element
=import Svgindex 6451e35..56ef197 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -10,12 +10,7 @@ import Basics.Extra exposing (curry)
=import Browser
=import Browser.Navigation as Navigation
=import BrowserWindow
-import CartesianCoordinates
-import Counter
=import Dict
-import Dot
-import DotInElement
-import DotWithViewBox
=import Element exposing (Element)
=import Element.Background as Background
=import Element.Border as Border
@@ -23,23 +18,21 @@ import Element.Events
=import Element.Font as Font
=import Element.Input as Input
=import Examples.CartesianCoordinates
-import Examples.CenteredDot
=import Examples.Counter
-import Examples.DotAtTheCenterOfTheScreen
-import Examples.FillTheScreen
+import Examples.Dot as Dot
+import Examples.DotInElement as DotInElement
+import Examples.DotWithViewBox as DotWithViewBox
=import Examples.Gradient
=import Examples.Line
=import Examples.NestedTransformations
=import Examples.PolarCoordinates
=import Examples.RosetteTypedTransformations
-import Examples.Simplest
=import Examples.Spiral
=import Examples.Transformations
=import Examples.TransformationsCircle
=import Examples.Tree
=import Examples.ViewBox
=import FeatherIcons exposing (icons)
-import Gradient
=import Html exposing (Html)
=import Html.Attributes
=import Http
@@ -50,9 +43,7 @@ import Parser
=import Parser.Advanced
=import Result.Extra as Result
=import Routes exposing (Route)
-import Spiral
=import Transformations
-import Tree
=import Url exposing (Url)
=
=Finish day 1
Create multiple dots example
index 9cf6974..b9a8560 100644
--- a/content/day-1.txt
+++ b/content/day-1.txt
@@ -318,8 +318,7 @@ The top and left values of the viewbox are a bit trickier. We saw ealier that if
= (Element.html
= (Svg.svg
= [ Svg.Attributes.viewBox "-300 -300 600 600" ]
- [ Svg.circle [ Svg.Attributes.r "10" ] []
- ]
+ [ Svg.circle [ Svg.Attributes.r "10" ] [] ]
= )
= )
=
@@ -330,29 +329,215 @@ Refresh the broswer. We should now see the dot in the center of the screen.
= fill=black
= viewBox=-300 -300 600 600
=
-Give a color to the dot
=
+Now that we've centered our dot, we can customize it by giving it a color! Let's take a deeper look at how we give attributes to an SVG element.
+
+We've already seen a few example of SVG attributes. The radius of our dot ({Code|circle}) is an attribute.
+
+| Code
+ Svg.circle [ Svg.Attributes.r "10" ] []
=
-| List
- - Explain SVG attributes ( svg elements take a list of attributes)
- - Everyone can pick a color
+Try giving it a new value to see your dot grow or shrink.
=
+Our viewBox is also an attribute. It's an attribute of the {Code|svg} element.
=
-Multiple dots
+| Code
+ Svg.svg
+ [ Svg.Attributes.viewBox "-300 -300 600 600" ]
+ [ Svg.circle [ Svg.Attributes.r "10" ] [] ]
=
+Again, try giving it new values. As we saw earlier, the first two values (which represent the left and top of the viewBox) must be equal to the negative of the half of the last two values (which correspond to width and height). Try other combinations of values to adjust the placement of the dot. If the behavior of the viewBox is difficult to understand, refer again to the interactive tutorial above.
=
-| List
- -> Introduce a problem: We want to have two dots on the screen
- -> Explain a list, and show there is already a list there (with one item)
+To color our dot, we'll have to give it a second attribute. Many SVG elements take a {Code|List} of attributes. A {Code|List} is a very important concept in software development. {Code|Lists} allow us to group an arbitary number of similar values. In this case, we'll be using a {Code|List} to group {Code|Attribute a} values. Whenever you see {Code|[...]} in an Elm source code file, you are dealing with a list.
=
+Before we give a color to our dot, try to identify all the lists in the code we're writen already. There are 5. Hint: It is possible for a list to have only 1 or 0 items ({Code|[]}).
=
-| Header
- Do you see any other lists?
+| Code
+ Svg.circle [ Svg.Attributes.r "10" ] []
+
+When we take a look at the code responsible for creating our dot, we see that it is followed by two lists. We'll only need to worry about the first one at the moment. As we've discussed, the first list already contains the {Code|r} attribute of the circle, representing the radius. We'll need to add a second attribute to the circle. We use a comma ({Code|,}) to seperate elements in a list.
+
+| Code
+ Svg.circle [ Svg.Attributes.r "10", ... ] []
+
+To color our circle, we'll use the {Code|fill} attribute.
+
+| Code
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.fill "blue"
+ ]
+ []
+
+You should see this result:
+
+| DotWithViewBox
+ background=none
+ fill=blue
+ viewBox=-300 -300 600 600
+
+
+Let's use our new understanding of lists to do something a bit more interesting. Let's draw multiple dots!
+
+We can start by taking a look at our {Code|main} value:
+
+| Code
+ main =
+ Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+ (Element.html
+ (Svg.svg
+ [ Svg.Attributes.viewBox "-500 -500 1000 1000"
+ ]
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.fill "blue"
+ ]
+ []
+ ]
+ )
+ )
+
+Where is our list of circles? To find it, first find the circle.
+
+| Code
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.fill "blue"
+ ]
+ []
+
+Next, identify the first open square bracket {Code|[} before the circle.
+
+| Code
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.fill "blue"
+ ]
+ []
+ ...
+
+This is where the list begins.
+
+Finally, identify the matching closing brakcet {Code|]}. This is a bit tricky because there are two lists within this list. The first is the list of attributes to the circle we saw earlier. The second is an empty list. We'll see exactly what they do in a moment. Ignore both of these for now. The matching closing bracket should be on the same indentation level as the opening bracket. Your text editor should be able to help you here. When you move your cursor before the opening bracket, the closing bracket should be highlighted.
+
+| Code
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.fill "blue"
+ ]
+ []
+ ]
+
+Right now, our list of circles has one value. We want to give it a second value. The second value will look identical to the first. What is the first value?
+
+| Code
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.fill "blue"
+ ]
+ []
+
+It might not seem obvious at first that this is all one value. But it is. {Code|Svg.circle} takes two lists as arguments. We'll see what an argument is later. For now, we will need to understand the difference between an attribute and a child.
+
+Imagine a box of colored easter eggs. One egg is painted yellow, another red, another blue. Moreover, they don't all come from the same bird. One is a small robin's egg. Another is a medium chicken egg. Another is a huge ostrich egg.
+
+Some of the elements in our image are 'things'. A box is a thing. Each egg is also a thing. Some of the elements in our image are not things. Blueness is not a thing. Medium-sizeness is not a thing. You can see blueness. You can see medium-sizeness. But they are not things. They are attributes of a thing. A thing can be blue. It can be medium-sized.
+
+It is a convention in many programming languages, including Elm and SVG, to distinguish between the attributes and children of an element. If we wanted to represent our box in SVG, we would give our box several attributes. It would have a color (maybe brown). It would have a width, length, depth. We would also give it several children. Each of the eggs within the box would be a child of the box. A box with 6 eggs would have 6 children. Each of these children (the eggs) would have different attributes: blue, red, medium-sized, etc. The children of an element are the 'things' it contains. The attributes of a thing are the elements that describe it.
+
+The first list following {Code|Svg.circle} is:
+
+| Code
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.fill "blue"
+ ]
+
+This is a list of attributes. A radius is not a 'thing'. A fill color is not a 'thing'. They are elements that describe a thing. They describe our circle.
+
+The second list is:
+
+| Code
+ []
+
+It is an empty list. Nevertheless, it is a list of children. Our circles don't have any children.
+
+Is the circle itself an attribute or a thing?
+
+From Elm's perspective, this entire code block is a circle:
+
+| Code
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.fill "blue"
+ ]
+ []
+
+This is not a circle:
+
+| Code
+ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.fill "blue"
+ ]
+
+Nor is this:
+
+| Code
+ Svg.circle
+
+To create a list with two circles, we'll want to duplicate the properly formed circle value. You can give the second dot a different color from the first.
+
+| Code
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.fill "blue"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.fill "red"
+ ]
+ []
+ ]
+
+Make add the second circle to the list of circles in your source code and refresh the page. What do you see?
+
+| TwoDots
+ background=none
+ viewBox=-300 -300 600 600
+ firstFill=blue
+ secondFill=red
+ firstCX=0
+ secondCX=0
+
+Only one dot! We see the second one, but what happened to the first dot? It's under the second... Both are at the origin (0, 0), so they are both in the same spot. If we want to see both dots, we will have to change their positions. To do this we'll have to use their {Code|cx} or {Code|cy} values.
+
+| Code
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "-10"
+ , Svg.Attributes.fill "blue"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "10"
+ , Svg.Attributes.fill "red"
+ ]
+ []
+ ]
+
+| TwoDots
+ background=none
+ viewBox=-300 -300 600 600
+ firstFill=blue
+ secondFill=red
+ firstCX=-100
+ secondCX=100
=
+As an exercise, try adding an {Code|Svg.Attribute.cy} attribute to the circles to adjust their vertical position.
=
-| List
- -> Element takes a list of children
- -> Mention , how can we have a list with one or zero elements? Shopping list with one item. Empty spoon drawer( list of spoons)
- -> Give your new dot a color and different coordinates
- -> Show the code for 2 dots
- -> Exercise: make 3 more dots (5 total)
+For a more advanced exercise, try adding a new circle. Why not draw 5 circles on the screen? Give them each a unique color and position.new file mode 100644
index 0000000..b0625da
--- /dev/null
+++ b/src/Examples/MultipleDots.elm
@@ -0,0 +1,41 @@
+module Examples.MultipleDots exposing (Config, main, ui)
+
+import Element
+import Svg exposing (Attribute)
+import Svg.Attributes
+
+
+type alias Config msg =
+ { background : String
+ , viewBox : String
+ , dotsAttributes : List (List (Attribute msg))
+ }
+
+
+defaults : Config msg
+defaults =
+ Config "white" "-500 -500 1000 1000" [ [ Svg.Attributes.r "10" ] ]
+
+
+main =
+ Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+ (ui defaults)
+
+
+ui { background, viewBox, dotsAttributes } =
+ Svg.svg
+ [ Svg.Attributes.viewBox viewBox
+ , Svg.Attributes.style <| "background: " ++ background
+ ]
+ (dotsAttributes
+ |> List.map
+ (\attributes ->
+ Svg.circle
+ attributes
+ []
+ )
+ )
+ |> Element.htmlindex 56ef197..5f855b9 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -24,6 +24,7 @@ import Examples.DotInElement as DotInElement
=import Examples.DotWithViewBox as DotWithViewBox
=import Examples.Gradient
=import Examples.Line
+import Examples.MultipleDots as MultipleDots
=import Examples.NestedTransformations
=import Examples.PolarCoordinates
=import Examples.RosetteTypedTransformations
@@ -43,6 +44,7 @@ import Parser
=import Parser.Advanced
=import Result.Extra as Result
=import Routes exposing (Route)
+import Svg.Attributes
=import Transformations
=import Url exposing (Url)
=
@@ -463,6 +465,7 @@ document =
= , dot
= , dotInElement
= , dotWithViewBox
+ , twoDots
= , transformationsCircle
= , line
= , gradient
@@ -546,6 +549,36 @@ document =
= (Mark.field "viewBox" Mark.string)
= |> Mark.map render
=
+ twoDots : Mark.Block (Model -> Element Msg)
+ twoDots =
+ let
+ render config model =
+ MultipleDots.ui config
+ |> Element.el [ Element.height (Element.px 300) ]
+ |> BrowserWindow.window []
+ in
+ Mark.record6 "TwoDots"
+ (\background viewBoxConf firstFill secondFill firstCX secondCX ->
+ MultipleDots.Config background
+ viewBoxConf
+ [ [ Svg.Attributes.r "10"
+ , Svg.Attributes.fill firstFill
+ , Svg.Attributes.cx firstCX
+ ]
+ , [ Svg.Attributes.r "10"
+ , Svg.Attributes.fill secondFill
+ , Svg.Attributes.cx secondCX
+ ]
+ ]
+ )
+ (Mark.field "background" Mark.string)
+ (Mark.field "viewBox" Mark.string)
+ (Mark.field "firstFill" Mark.string)
+ (Mark.field "secondFill" Mark.string)
+ (Mark.field "firstCX" Mark.string)
+ (Mark.field "secondCX" Mark.string)
+ |> Mark.map render
+
= transformationsCircle : Mark.Block (Model -> Element Msg)
= transformationsCircle =
= letRemove duplicated ViewBox module
deleted file mode 100644
index b3f0a44..0000000
--- a/src/ViewBox.elm
+++ /dev/null
@@ -1,276 +0,0 @@
-module ViewBox exposing
- ( Model
- , Msg
- , init
- , main
- , ui
- , update
- , view
- )
-
-import Browser
-import BrowserWindow
-import CartesianPlane exposing (graph)
-import Element
-import Element.Background as Background
-import Element.Border as Border
-import Element.Input as Input
-import Html exposing (Html)
-import Svg exposing (..)
-import Svg.Attributes exposing (..)
-
-
-main =
- Browser.sandbox
- { init = init
- , view = view
- , update = update
- }
-
-
-type alias Model =
- { left : Float
- , top : Float
- , width : Float
- , height : Float
- , preserveAspectRatio : Bool
- }
-
-
-type Msg
- = Move Float Float
- | Resize Float Float
- | PreserveAspectRatio Bool
-
-
-init : Model
-init =
- { left = 0
- , top = 0
- , width = 400
- , height = 400
- , preserveAspectRatio = False
- }
-
-
-view : Model -> Html.Html Msg
-view model =
- let
- wrapper element =
- Element.el
- [ Element.width (Element.maximum 1200 Element.fill)
- , Element.height Element.fill
- , Element.centerX
- ]
- element
- in
- ui model
- |> wrapper
- |> Element.layout
- [ Element.height Element.fill
- , Element.width Element.fill
- ]
-
-
-update : Msg -> Model -> Model
-update msg model =
- case msg of
- Move left top ->
- { model
- | left = left
- , top = top
- }
-
- Resize width height ->
- { model
- | width = width
- , height = height
- }
-
- PreserveAspectRatio value ->
- { model | preserveAspectRatio = value }
-
-
-ui model =
- let
- viewbox =
- svg
- [ viewBox "-500 -500 1000 1000"
- ]
- [ g [] world
- , rect
- [ x (String.fromFloat model.left)
- , y (String.fromFloat model.top)
- , width (String.fromFloat model.width)
- , height (String.fromFloat model.height)
- , opacity "0.3"
- , stroke "white"
- , fill "pink"
- ]
- []
- ]
- |> Element.html
- |> Element.el
- [ Element.centerX
- , Element.centerY
- , Element.width Element.fill
- , Element.height Element.fill
- ]
-
- viewport =
- svg
- [ [ model.left, model.top, model.width, model.height ]
- |> List.map String.fromFloat
- |> String.join " "
- |> viewBox
- , preserveAspectRatio <|
- case model.preserveAspectRatio of
- True ->
- "xMidYMid meet"
-
- False ->
- "none"
- , Svg.Attributes.style "width: 100%; height: 100%; flex-grow: 1; flex-basis: 0"
- ]
- [ g [] world
- , rect
- [ x (String.fromFloat model.left)
- , y (String.fromFloat model.top)
- , width (String.fromFloat model.width)
- , height (String.fromFloat model.height)
- , opacity "0.3"
- , stroke "white"
- , fill "pink"
- ]
- []
- ]
- |> Element.html
- |> Element.el
- [ Element.width Element.fill
- , Element.height Element.fill
- ]
- |> BrowserWindow.window
- [ Element.centerX
- , Element.centerY
- , Element.width Element.fill
- , Element.height Element.fill
- ]
- in
- Element.column
- [ Element.width Element.fill
- , Element.height Element.fill
- , Element.spacing 30
- , Element.padding 30
- ]
- [ Element.row
- [ Element.width Element.fill
- , Element.height Element.fill
- , Element.centerX
- , Element.spacing 30
- ]
- [ viewbox
- , viewport
- ]
- , Input.slider
- [ Element.behindContent
- (Element.el
- [ Element.width Element.fill
- , Element.height (Element.px 2)
- , Element.centerY
- , Background.color <| Element.rgb 0.7 0.7 0.7
- , Border.rounded 2
- ]
- Element.none
- )
- ]
- { onChange = \left -> Move left model.top
- , label =
- Input.labelBelow [ Element.centerX ] <|
- Element.text ("left: " ++ String.fromFloat model.left)
- , min = -500
- , max = 500
- , value = model.left
- , thumb = Input.defaultThumb
- , step = Just 1
- }
- , Input.slider
- [ Element.behindContent
- (Element.el
- [ Element.width Element.fill
- , Element.height (Element.px 2)
- , Element.centerY
- , Background.color <| Element.rgb 0.7 0.7 0.7
- , Border.rounded 2
- ]
- Element.none
- )
- ]
- { onChange = \top -> Move model.left top
- , label =
- Input.labelBelow [ Element.centerX ] <|
- Element.text ("top: " ++ String.fromFloat model.top)
- , min = -500
- , max = 500
- , value = model.top
- , thumb = Input.defaultThumb
- , step = Just 1
- }
- , Input.slider
- [ Element.behindContent
- (Element.el
- [ Element.width Element.fill
- , Element.height (Element.px 2)
- , Element.centerY
- , Background.color <| Element.rgb 0.7 0.7 0.7
- , Border.rounded 2
- ]
- Element.none
- )
- ]
- { onChange = \width -> Resize width model.height
- , label =
- Input.labelBelow [ Element.centerX ] <|
- Element.text ("width: " ++ String.fromFloat model.width)
- , min = 1
- , max = 500
- , value = model.width
- , thumb = Input.defaultThumb
- , step = Just 1
- }
- , Input.slider
- [ Element.behindContent
- (Element.el
- [ Element.width Element.fill
- , Element.height (Element.px 2)
- , Element.centerY
- , Background.color <| Element.rgb 0.7 0.7 0.7
- , Border.rounded 2
- ]
- Element.none
- )
- ]
- { onChange = \height -> Resize model.width height
- , label =
- Input.labelBelow [ Element.centerX ] <|
- Element.text ("height: " ++ String.fromFloat model.height)
- , min = 1
- , max = 500
- , value = model.height
- , thumb = Input.defaultThumb
- , step = Just 1
- }
- , Input.checkbox []
- { checked = model.preserveAspectRatio
- , icon = Input.defaultCheckbox
- , label = Input.labelRight [] (Element.text "Preserve aspect ratio")
- , onChange = PreserveAspectRatio
- }
- ]
-
-
-world : List (Svg Msg)
-world =
- [ circle [ cx "400", cy "300", r "250", fill "purple" ] []
- , circle [ cx "200", cy "350", r "50", fill "yellow" ] []
- , circle [ cx "0", cy "0", r "20", fill "red" ] []
- ]Separate Window block from example blocks
Now each example can be nested in a Window, but doesn't have to be.
index 83cc9bf..bda2078 100644
--- a/content/day-1.txt
+++ b/content/day-1.txt
@@ -85,7 +85,8 @@ Note that the same address was printed by the Elm reactor.
=
=We want to display a dot at the center of the screen, like this:
=
-| DotAtTheCenterOfTheScreen
+| Window
+ | DotAtTheCenterOfTheScreen
=
=Below is the complete code to draw a dot like this, but don't type it in your editor yet! We are going to recreate it together, step by step.
=
@@ -153,9 +154,10 @@ Start the reactor again:
=
=Reload the browser. You should see something like this:
=
-| Simplest
- background=none
- fill=black
+| Window
+ | Simplest
+ background=none
+ fill=black
=
=Something isn't quite right here. Why is the dot in the corner?
=
@@ -171,9 +173,10 @@ When you reload the browser, you'll see this:
=| Note
= Example of simplest with pink background should be here.
=
-| Simplest
- background=pink
- fill=black
+| Window
+ | Simplest
+ background=pink
+ fill=black
=
=Now we can clearly see that our SVG element (which is pink) does not fill the screen.
=index 99d1276..e6bd250 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -475,6 +475,28 @@ document =
= , spiral
= , tree
= , viewBox
+
+ -- Window block can contain any embeded program
+ , Mark.Custom.window
+ (Mark.oneOf
+ [ counter
+ , simplest
+ , fillTheScreen
+ , dotAtTheCenterOfTheScreen
+ , centeredDot
+ , circle
+ , line
+ , gradient
+ , transformations
+ , nestedTransformations
+ , cartesianCoordinates
+ , polarCoordinates
+ , rosette
+ , spiral
+ , tree
+ , viewBox
+ ]
+ )
= ]
= )
=
@@ -489,11 +511,6 @@ document =
= [ Element.centerX
= , Element.centerY
= ]
- |> Element.el
- [ Element.height (Element.px 400)
- , Element.width Element.fill
- ]
- |> BrowserWindow.window []
= |> Element.map CounterMsg
= in
= Mark.stub "Counter" render
@@ -505,13 +522,12 @@ document =
= render config model =
= Examples.Simplest.ui config
= |> Element.html
- |> Element.el []
= |> Element.el
- [ Element.height (Element.px 400)
- , Element.width Element.fill
- , Element.padding 5
+ [ Element.width (Element.px 300)
+ , Element.height (Element.px 150)
= ]
- |> BrowserWindow.window []
+ |> Element.el
+ [ Element.padding 5 ]
= in
= Mark.record2 "Simplest"
= Examples.Simplest.Config
@@ -537,11 +553,6 @@ document =
= let
= render model =
= Examples.DotAtTheCenterOfTheScreen.ui
- |> Element.el
- [ Element.height (Element.px 400)
- , Element.width Element.fill
- ]
- |> BrowserWindow.window []
= in
= Mark.stub "DotAtTheCenterOfTheScreen" render
=
@@ -565,12 +576,6 @@ document =
= render : Examples.TransformationsCircle.Config -> Model -> Element Msg
= render config model =
= Examples.TransformationsCircle.ui config
- |> Element.el
- [ Element.height (Element.px 400)
- , Element.width Element.fill
- , Element.padding 5
- ]
- |> BrowserWindow.window []
= in
= Mark.record4 "TransformationsCircle"
= Examples.TransformationsCircle.Config
@@ -640,10 +645,6 @@ document =
= render model =
= model.cartesianCoordinates
= |> Examples.CartesianCoordinates.ui
- |> Element.el
- [ Element.width Element.fill
- ]
- |> BrowserWindow.window []
= |> Element.map CartesianCoordinatesMsg
= in
= Mark.stub "CartesianCoordinates" renderindex 3e50354..ca76c37 100644
--- a/src/Mark/Custom.elm
+++ b/src/Mark/Custom.elm
@@ -1,5 +1,21 @@
-module Mark.Custom exposing (code, colors, css, emphasize, header, icon, image, list, monospace, note, paragraph, text, title)
-
+module Mark.Custom exposing
+ ( code
+ , colors
+ , css
+ , emphasize
+ , header
+ , icon
+ , image
+ , list
+ , monospace
+ , note
+ , paragraph
+ , text
+ , title
+ , window
+ )
+
+import BrowserWindow
=import Dict
=import Element exposing (Element)
=import Element.Background as Background
@@ -107,6 +123,24 @@ code =
= Mark.multiline
=
=
+window :
+ Mark.Block (a -> Element msg)
+ -> Mark.Block (a -> Element msg)
+window block =
+ let
+ render child model =
+ child model
+ |> Element.el
+ [ Element.height (Element.px 400)
+ , Element.width Element.fill
+ ]
+ |> BrowserWindow.window []
+ in
+ Mark.block "Window"
+ render
+ block
+
+
=note : Mark.Block (model -> Element msg)
=note =
= letMake develop script print the modules it compiles to HTML
Otherwise it just prints a lot of "Success" which looks dumb.
index 3b0e90e..bf51dda 100755
--- a/scripts/develop
+++ b/scripts/develop
@@ -5,6 +5,7 @@ set -euo pipefail
=# Build examples
=for example in src/Examples/*.elm
=do
+ echo "Compiling ${example} to public/${example}.html"
= npx elm make --output "public/${example}.html" "${example}"
=done
=Remove calls to Debug.log from Main
index e6bd250..072cef8 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -275,11 +275,10 @@ view model =
=
=update : Msg -> Model -> ( Model, Cmd Msg )
=update msg model =
- case Debug.log "update" msg of
+ case msg of
= UrlRequested (Browser.Internal url) ->
= ( model
= , url
- |> Debug.log "New URL"
= |> Url.toString
= |> Navigation.pushUrl model.key
= )
@@ -376,7 +375,7 @@ subscriptions model =
=
=loadContent : Route -> Cmd Msg
=loadContent route =
- case Debug.log "Route" route of
+ case route of
= Routes.Home ->
= Http.get
= { url = "/content/index.txt"
Commits: 3
Edits to the day-1 text
Modify simplest embedded example to take background and fill config
index 3cd7b9e..83cc9bf 100644
--- a/content/day-1.txt
+++ b/content/day-1.txt
@@ -10,40 +10,40 @@
=
= | List
= -> Scalable Vector Graphics
- -> cartesian Coordinates System
+ -> Cartesian Coordinates Systems
= -> Layouts with ELM UI
=
=
=| Header
- First Program!
+ Our First Program!
=
=As mentioned before, programs are represented as text (called the /source code/). The source code is stored in files {Icon|name=file} and files are organized in directories {Icon|name=folder}.
=
=
=So the first step is to create a directory for our new program. Lets call it fpart.
-In the terminal type
+In the terminal type:
=
=| Monospace
= mkdir fpart/
=
-and then
+and then:
=
=| Monospace
= cd fpart/
=
-This creates a new directory and makes it the current one. Again, don't worry about the details.
+The first command will create our new directory and the second will make it the current directory. Again, don't worry about the details.
=
-To easily create a new program, we can type
+We can easily create a new program by typing:
=
=| Monospace
= elm init
=
-Then to create a file with source code, type
+Then to create the file which will contain our source code, type:
=
=| Monospace
= atom src/Main.elm
=
-This command should open a new Atom window with empty text file.
+This command should open a new Atom window with an empty text file.
=
=| Header
= Main.elm
@@ -58,19 +58,19 @@ This command should open a new Atom window with empty text file.
= Html.text "Hello, Tree"
=
=Type the above in the editor and save the file.
-To see the program running type the following in the terminal
+To run the program, type the following command in the terminal:
=
=| Monospace
= elm reactor
=
-This command starts the Elm reactor, which will let you run your program in the web browser. Note that it won't give you the command line back - it will run as long as you don't stop it.
+This command will start the Elm reactor, which will allow you to run your program in the web browser. Note that it won't give you the command line back - it will run as long as you don't stop it.
=
=
=| Emphasize
= Voila!
=
=
-Open following address in the web browser
+Open following address in the web browser:
=
=| Emphasize
= {Link|http://localhost:8000/src/Main.elm|url=http://localhost:8000/src/Main.elm}
@@ -78,16 +78,16 @@ Open following address in the web browser
=| Note
= *TODO*: Make the link display // characters
=
-Note that the same address was printed by Elm reactor
+Note that the same address was printed by the Elm reactor.
=
=| Header
= The problem
=
-We want to have a dot at the center of the screen, like this
+We want to display a dot at the center of the screen, like this:
=
=| DotAtTheCenterOfTheScreen
=
-Below is the complete code to draw a dot like this, but don't type it in your editor yet! We are going to recreate together it step by step.
+Below is the complete code to draw a dot like this, but don't type it in your editor yet! We are going to recreate it together, step by step.
=
=| Code
= module Main exposing (main)
@@ -114,113 +114,124 @@ Below is the complete code to draw a dot like this, but don't type it in your ed
= , Element.height Element.fill
= ]
=
+We are going to use a technology called SVG (Scalable Vector Graphics).
=
+*Scalable icon should be here*
+
+S for Scalable
=
-We are going to use a technology called SVG (Scalable Vector Graphics). Its all about drawing shapes on the screen. Let's install an Elm Package to help us work with SVG. Stop the reactor running in terminal by pressing {Code|CTRL} + {Code|C} and type the following:
+Its all about drawing shapes on the screen. Let's install an Elm Package to help us work with SVG. Stop the reactor running in the terminal by pressing {Code|CTRL} + {Code|C} and then type the following:
=
=| Monospace
= elm install elm/svg
=
-Then start the reactor again:
+Now that we have {Code|elm//svg} installed, we can write the code that will draw our dot:
+
+| Code
+ module Simplest exposing (main)
+
+ import Element
+ import Svg
+ import Svg.Attributes
+
+
+ main =
+ Svg.svg []
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ ]
+ []
+ ]
+
+Start the reactor again:
=
=| Monospace
= elm reactor
=
=| Note
- You can press up arrow {Icon|name=arrow-up} on the keyboard to get to the previous commands.
+ You can press the up arrow {Icon|name=arrow-up} on the keyboard to retrieve commands entered previously.
=
=Reload the browser. You should see something like this:
=
=| Simplest
+ background=none
+ fill=black
=
-Why is the dot at the corner?
+Something isn't quite right here. Why is the dot in the corner?
=
-It's because its center is at point called {Code|origin} or {Code|(0, 0)}. But what does it mean ?
+There are two reasons. First, because the center of the dot is at a point called the {Code|origin} or {Code|(0, 0)}. We'll explore exactly what this means later.
=
-* a picture of a vase on a table should be here *
+Second, it's because the SVG space doesn't fill the browser window. Don't believe me? We can see that the space doesn't fill the window by changing the background color of the SVG element.
=
-Move sliders to change the x and y coordinates of the dot. It's like moving a thing on a table by telling how far left, right, up or down it should go.
-
-| CartesianCoordinates
+| Note
+ Code sample for simplest with pink background should be here.
=
-To change where the dot is we can use cx and cy attributes of the circle
+When you reload the browser, you'll see this:
=
-* simplest with cx and cy should be here *
+| Note
+ Example of simplest with pink background should be here.
=
-* Module main exposing main should be here *
+| Simplest
+ background=pink
+ fill=black
=
-Note that we have a complete program that draws a dot at the center of the screen , lets take a moment to understand it.
+Now we can clearly see that our SVG element (which is pink) does not fill the screen.
=
-Think about this ( * a picture of eggs and a box of eggs should be here * )
+We can easily correct this with the help of a very handy elm package, {Code|mdgriffith//elm-ui}.
=
-| Note
- An egg is a thing.
- Six eggs are six things.
- A box of six eggs is a thing.
+Install it with the terminal.
=
+Press {Code|CTRL + C} to stop the elm-reactor, type {Code|elm install mdgriffith//elm-ui} and press {Code|Enter}.
=
+Then in {Code|src//Main.elm} add the necessary import {Code|import Element} and change {Code|Main} to look like this:
=
-Now, let's make the SVG element fill the screen
+Main = * the code of Element.layout should be here*
=
+Take a wild guess: what do you think {Code|Element.height Element.fill} and {Code|Element.height Element.width} do?
=
-| FillTheScreen
+Reload the browser to confirm your hypothesis. Voila! The SVG space now fills the screen. It should now be possible to place our dot in the center of the screen. But, how exactly will we achieve it?
=
+Let's return to the idea we introduced earlier, that the center of the dot is at a point called the {Code|origin} or {Code|(0, 0)}. What eactly does this mean?
=
-For that we will need a package called {Link|mdgriffith//elm-ui|url=https://package.elm-lang.org/packages/mdgriffith/elm-ui/latest/}
+* a picture of a vase on a table should be here *
=
-Install it using terminal
+Adjust the sliders below to change the x and y coordinates of the dot. It's like moving an object on a table by describing how far left, right, up or down it should be.
=
-{Code|CTRL + C } to stop elm-reactor and type {Code|elm install mdgriffith//elm-ui}
+| CartesianCoordinates
=
-Then in {Code|src//Main.elm} add import {Code|import Element} and change {Code|Main} to look like this :
+Knowing that the center of the dot is at the {Code|origin} or point {Code|(0,0)} of our plain, we can see that by default the origin of an SVG space is at the upper left corner. If we want to help our dot get out of hiding, we'll have to place the center of the dot at a point with positive {Code|x} and {Code|y} values.
=
-Main = * the code of Element.layout should be here*
+To change the position of the dot, we can use the {Code|cx} and {Code|cy} attributes of the {Code|circle} element.
=
-Note that our scene fills the screen, it's time to put the dot at the center of the scene.
+* simplest with cx and cy should be here *
=
-* a picture of cx and cy with width and height should be here *
+Now, if we want our dot to be exactly in the center of the screen, we'll have to set {Code|cx} and {Code|cy} to values equal to exactly half of the {Code|width} and {Code|height} of the SVG space.
=
-If we would know the height and width of the screen , we could calculate its position as
+We can calculate the position of the circle as:
=
=| Monospace
= cx = width/2
= cy = height/2
=
-There is a problem though, we don't know the height and width.
+There is a problem though! We don't actually know the width and height of the SVG space. All we know is that its {Code|width} and {Code|height} will {Code|fill} the viewport, whatever its size.
=☹️
=
-But we don't need to!
+Fortunately, we don't need to know the width and height of the SVG space!
=
-*Scalable icon should be here*
+This is a bit tricky. Instead of moving the dot in the SVG space, we can set the boundaries of the {Code|scene} so that the dot is at the center using a property called {Code|Viewbox}.
=
-S for Scalable
+To use an anology, imagine you've decided to build a holiday home with your family or friends in the Alps. Sounds pretty nice, right? Now, your budget isn't unlimited, so you've had to make a few compromises. You're in a beautiful area with a lovely view of the mountains, but yours isn't the only house in the area, and some of the others aren't the nicest to look at. Of course, you don't want your view of the beautiful snow-capped mountains to be blocked by a big pile of concrete. So what are you going to do about this? You can move your neighbours' homes, and the mountains while you're at it, so they are directly in front of your windows. Or, you can move the windows as you design your house so that you get a full view of the beautiful surroundings. Imagine you could move the windows around, as you decide the best place for them. As the window moves, you it will look as though the surroundings as moving, to the right, the left, up and down, as you shift the window. But in reality it is only your perspective that is changing.
=
-Instead of moving the dot, we can set the boundaries of the scene so that the dot is at the center using a property called Viewbox.
+A videwbox is like a window into an SVG space. It allows us to change the point of view of the scene.
=
=| ViewBox
=
-Here is how the viewbox works
-
-* a picture of viewbox should be here*
-
-viewbox = 0 0 100 100 (left, top, width and height respectively)
-
-* a picture of periscope looking table should be here*
+In this example, we can set the width and height of the viewbox to an arbitrary value. As we see in the interactive example, the width and height of the viewbox will effect how big the dot appears, but that's not so important to us at the moment. Let's set them both to 1000.
=
-The trick is to set the width and height to any arbitrary value (say 1000) and top and left to {Code|- width//2}!
+The top and left values of the viewbox are a bit trickier. We saw ealier that if we knew the width and height of our SVG scene, we could have set the cx and cy values of the dot to half the width and height of the scene to center the dot. We will have to use similar reasoning here. If we set the top and left values of the viewbox to 0, the dot will still still appear in the top left corner of the scene. If we want to see the dot moved to the right and downward within the scene, we will need the top and left values of the viewbox to be negative. If we want the origin of the SVG space to appear directly in the center of the scene, we will need to set the top and left values of the viewbox to {Code|- height//2} and {Code|- width//2}. In this case, they will both have the value 500.
=
-That way point (0,0) will always be right in the middle (the distance to the left and to the right is the same, likewise the distance to the top and bottom).
+Refresh the broswer. We should now see the dot in the center of the screen.
=
-First, lets see where the SVG boundaries are by giving it a background color
-
-
-| CenteredDot
-
-
-| Code
- Svg [background "pink"]
- [...
- ]
=
=Give a color to the dot
=index d542d78..019ba58 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -487,16 +487,23 @@ document =
= simplest : Mark.Block (Model -> Element Msg)
= simplest =
= let
- render model =
- Simplest.main
+ render : Simplest.Config -> Model -> Element Msg
+ render config model =
+ Simplest.ui config
= |> Element.html
+ |> Element.el []
= |> Element.el
= [ Element.height (Element.px 400)
= , Element.width Element.fill
+ , Element.padding 5
= ]
= |> BrowserWindow.window []
= in
- Mark.stub "Simplest" render
+ Mark.record2 "Simplest"
+ Simplest.Config
+ (Mark.field "background" Mark.string)
+ (Mark.field "fill" Mark.string)
+ |> Mark.map render
=
= fillTheScreen : Mark.Block (Model -> Element Msg)
= fillTheScreen =index 9f90116..2e65fa4 100644
--- a/src/Simplest.elm
+++ b/src/Simplest.elm
@@ -1,17 +1,32 @@
-module Simplest exposing (main)
+module Simplest exposing (Config, defaults, ui)
=
-import Element
+import Html exposing (Html)
=import Svg
=import Svg.Attributes
=
=
+type alias Config =
+ { background : String
+ , fill : String
+ }
+
+
+defaults : Config
+defaults =
+ Config "none" "black"
+
+
+main : Html msg
=main =
- Svg.svg
- [ Svg.Attributes.width "300"
- , Svg.Attributes.height "150"
- ]
+ ui defaults
+
+
+ui : Config -> Html msg
+ui { background, fill } =
+ Svg.svg [ Svg.Attributes.style <| "background: " ++ background ]
= [ Svg.circle
= [ Svg.Attributes.r "10"
+ , Svg.Attributes.fill fill
= ]
= []
= ]Separate day 2, add TransformationsCircle example
Expose Transformation(..) and Apply from Transformations module.
new file mode 100644
index 0000000..32d4a6c
--- /dev/null
+++ b/content/day-2.txt
@@ -0,0 +1,75 @@
+| Title
+ Day 2
+
+| Emphasize
+ Place the dots in a circle
+
+
+| Note
+ Today we are going to lear about
+
+ | List
+ - SVG transformations
+ - SVG groups (?)
+
+| Header
+ The Problem
+
+We want to place the dots in a circle, like that:
+
+| TransformationsCircle
+ dots = True
+ circle = False
+ angle = False
+ center = False
+
+As you can see, we will make the dot's have different colors, so it's more fun!
+
+How we will get there?
+
+We will need more than one dot. Let's make it 5. Then let's think what does it mean to be placed on a circle.
+
+First of all we have to realize that a circle has a center. It's a point that lays exactly in the middle:
+
+
+Then we can say that several things lay on a circle if the distance between each of them and the center is the same.
+
+In other words, you can put a thing in the center and then move it in any direction, let's say 1 meter. If you do the same to several things, you will make them lay on a circle.
+
+Of course the direction must be different each time. Otherwise the things would just stack one on top of another! We wouldn't call it a circle, right?
+
+We will change the cartesian coordinates of the dots, nothing else! But figuring out the right coordinates is a bit tricky
+
+How can we figure out the correct cartesian coordinates?
+
+| Note
+ Story about the flowerpot and the table: two ways to measure relative position, one is distance from an x and y axis (cartesian coordinates), the other is angle and distance relative to origin (polar coordinates)
+
+
+What does this have to do with a circle? Do you know the expression 'I did a 180'. Where would you be looking if you did two 180s (a '360'). We see that circles and angles are closely related!
+
+Exercise with compass and 5 objects, place objects evenly along compass. How many degrees apart are they? (observe if we multiply the result by 5, we're back to 360)
+
+We have one part of it (angle), we now need the distance. Notice they are all the same distance from the origin (the center dot) of the compass. Definition of circle: points that are an equal distance from a center. Actually, the distance doesn't matter, so long as they all have the same distance. You can have a big circle or a small circle, they're both circles
+
+Ok great, we're done! Now, does anyone know how to give an angle and distance to svg? Oh... no? We don't either... you can't. You can only give x and y values relative to the origin
+
+So we already know that angle and length are just another way of describing x and y. But we need some way of translating between the two
+
+sing a visual demonstration, if you draw a line from your object to the x axis, you have a triangle. Same if you draw a line to the y axis. You can figure out the point on the axis using sin and cos functions and multiplying the result by the length.
+
+Let's use a chart to figure out the sin and cos of our angles
+
+/It's important to get a chart, otherwise we have to use calculators which work with radians/
+
+Now we are done... we can plug in our x and y values, and presto, our dots are arranged in a circle. But we don't see most of them...
+
+Our origin is in the top left corner. This means any dots with negative x or y values are off the screen.
+
+We can shift the dots so they are on the screen, or shift the screen so that it covers the dots. We will be shifting the screen
+
+Sample viewbox program to demonstrate
+
+Create a viewbox with correct perimeters (must be more than the radius of the circle plus the radius of a dot in each direction, and width and height are circumference)
+
+{Code|svg [viewbox "-100 -100 200 200 " ] []}index 54d5902..07ece92 100644
--- a/elm.json
+++ b/elm.json
@@ -16,6 +16,7 @@
= "elm/url": "1.0.0",
= "elm-community/basics-extra": "4.0.0",
= "elm-community/list-extra": "8.1.0",
+ "elm-community/maybe-extra": "5.0.0",
= "elm-community/result-extra": "2.2.1",
= "elm-explorations/markdown": "1.0.0",
= "feathericons/elm-feather": "1.2.0",index 019ba58..ab8ac60 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -41,6 +41,7 @@ import Routes exposing (Route)
=import Simplest
=import Spiral
=import Transformations
+import TransformationsCircle
=import Tree
=import Url exposing (Url)
=import ViewBox
@@ -451,6 +452,7 @@ document =
= , fillTheScreen
= , dotAtTheCenterOfTheScreen
= , centeredDot
+ , transformationsCircle
= , line
= , gradient
= , transformations
@@ -545,6 +547,27 @@ document =
= in
= Mark.stub "CenteredDot" render
=
+ transformationsCircle : Mark.Block (Model -> Element Msg)
+ transformationsCircle =
+ let
+ render : TransformationsCircle.Config -> Model -> Element Msg
+ render config model =
+ TransformationsCircle.ui config
+ |> Element.el
+ [ Element.height (Element.px 400)
+ , Element.width Element.fill
+ , Element.padding 5
+ ]
+ |> BrowserWindow.window []
+ in
+ Mark.record4 "TransformationsCircle"
+ TransformationsCircle.Config
+ (Mark.field "circle" Mark.bool)
+ (Mark.field "center" Mark.bool)
+ (Mark.field "angle" Mark.bool)
+ (Mark.field "dots" Mark.bool)
+ |> Mark.map render
+
= line : Mark.Block (Model -> Element Msg)
= line =
= letindex 295c1ef..1b3937d 100644
--- a/src/Transformations.elm
+++ b/src/Transformations.elm
@@ -1,6 +1,8 @@
=module Transformations exposing
= ( Model
= , Msg
+ , Transformation(..)
+ , apply
= , init
= , main
= , uinew file mode 100644
index 0000000..96264ff
--- /dev/null
+++ b/src/TransformationsCircle.elm
@@ -0,0 +1,135 @@
+module TransformationsCircle exposing (Config, defaults, main, ui)
+
+import Element exposing (Element)
+import Html exposing (Html)
+import Maybe.Extra as Maybe
+import Svg exposing (Svg)
+import Svg.Attributes
+import Transformations exposing (Transformation(..))
+
+
+main : Html.Html msg
+main =
+ Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+ (ui defaults)
+
+
+ui : Config -> Element msg
+ui config =
+ let
+ present : Bool -> Svg msg -> Maybe (Svg msg)
+ present flag shape =
+ if flag then
+ Just shape
+
+ else
+ Nothing
+
+ shapes =
+ dots
+ ++ Maybe.values
+ [ present config.circle circle
+ ]
+
+ dots =
+ if config.dots then
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "skyblue"
+ , Svg.Attributes.transform <|
+ Transformations.apply
+ [ Rotate 0
+ , Translate 80 0
+ ]
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "pink"
+ , Svg.Attributes.transform <|
+ Transformations.apply
+ [ Rotate 72
+ , Translate 80 0
+ ]
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "yellow"
+ , Svg.Attributes.transform <|
+ Transformations.apply
+ [ Rotate 144
+ , Translate 80 0
+ ]
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "lime"
+ , Svg.Attributes.transform <|
+ Transformations.apply
+ [ Rotate 216
+ , Translate 80 0
+ ]
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "maroon"
+ , Svg.Attributes.transform <|
+ Transformations.apply
+ [ Rotate 288
+ , Translate 80 0
+ ]
+ ]
+ []
+ ]
+
+ else
+ []
+
+ circle =
+ Svg.circle
+ [ Svg.Attributes.r "80"
+ , Svg.Attributes.stroke "silver"
+ , Svg.Attributes.strokeWidth "1"
+ , Svg.Attributes.strokeDasharray "5 5"
+ , Svg.Attributes.fill "none"
+ ]
+ []
+ in
+ shapes
+ |> Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200"
+ ]
+ |> Element.html
+
+
+defaults : Config
+defaults =
+ { circle = False
+ , center = False
+ , angle = False
+ , dots = False
+ }
+
+
+type alias Config =
+ { circle : Bool
+ , center : Bool
+ , angle : Bool
+ , dots : Bool
+ }Make develop script build all examples to public/
It's a quick and dirty solution.
All examples are now in src/Examples/ directory.
There is a new Transformations module that exposes Transformation type and it's constructors and apply function.
index 568384b..3b0e90e 100755
--- a/scripts/develop
+++ b/scripts/develop
@@ -2,4 +2,10 @@
=
=set -euo pipefail
=
+# Build examples
+for example in src/Examples/*.elm
+do
+ npx elm make --output "public/${example}.html" "${example}"
+done
+
=npx elm-live src/Main.elm --pushstate -- --debugsimilarity index 98%
rename from src/CartesianCoordinates.elm
rename to src/Examples/CartesianCoordinates.elm
index fc29be3..1c4266c 100644
--- a/src/CartesianCoordinates.elm
+++ b/src/Examples/CartesianCoordinates.elm
@@ -1,4 +1,4 @@
-module CartesianCoordinates exposing
+module Examples.CartesianCoordinates exposing
= ( Model
= , Msg
= , initsimilarity index 96%
rename from src/CenteredDot.elm
rename to src/Examples/CenteredDot.elm
index 59fb692..9f00631 100644
--- a/src/CenteredDot.elm
+++ b/src/Examples/CenteredDot.elm
@@ -1,4 +1,4 @@
-module CenteredDot exposing (main)
+module Examples.CenteredDot exposing (main)
=
={-| This program demonstrates how to center an element within an SVG viewport. The idea is to make sure that (0, 0) point (so called origin) should be in the middle of the ViewBox. We can achive that, by making the top-left corner of the ViewBox same distance from the (0, 0) as the bottom right.
=similarity index 97%
rename from src/Counter.elm
rename to src/Examples/Counter.elm
index 0f0689c..66d1400 100644
--- a/src/Counter.elm
+++ b/src/Examples/Counter.elm
@@ -1,4 +1,4 @@
-module Counter exposing
+module Examples.Counter exposing
= ( Model
= , Msg
= , initsimilarity index 87%
rename from src/DotAtTheCenterOfTheScreen.elm
rename to src/Examples/DotAtTheCenterOfTheScreen.elm
index 83f0d1d..cc1875e 100644
--- a/src/DotAtTheCenterOfTheScreen.elm
+++ b/src/Examples/DotAtTheCenterOfTheScreen.elm
@@ -1,4 +1,4 @@
-module DotAtTheCenterOfTheScreen exposing (main, ui)
+module Examples.DotAtTheCenterOfTheScreen exposing (main, ui)
=
=import Element
=import Svgsimilarity index 90%
rename from src/FillTheScreen.elm
rename to src/Examples/FillTheScreen.elm
index 56f88d6..cef2371 100644
--- a/src/FillTheScreen.elm
+++ b/src/Examples/FillTheScreen.elm
@@ -1,4 +1,4 @@
-module FillTheScreen exposing (main, ui)
+module Examples.FillTheScreen exposing (main, ui)
=
=import Element
=import Svgsimilarity index 96%
rename from src/Gradient.elm
rename to src/Examples/Gradient.elm
index 4aa189c..1d7a0c9 100644
--- a/src/Gradient.elm
+++ b/src/Examples/Gradient.elm
@@ -1,4 +1,4 @@
-module Gradient exposing (main, ui)
+module Examples.Gradient exposing (main, ui)
=
=import Element
=import Svgsimilarity index 93%
rename from src/Line.elm
rename to src/Examples/Line.elm
index 53ab314..d105ef5 100644
--- a/src/Line.elm
+++ b/src/Examples/Line.elm
@@ -1,4 +1,4 @@
-module Line exposing (main, ui)
+module Examples.Line exposing (main, ui)
=
=import Element
=import Svgsimilarity index 96%
rename from src/LineTypedTransformations.elm
rename to src/Examples/LineTypedTransformations.elm
index f39d91f..46899d3 100644
--- a/src/LineTypedTransformations.elm
+++ b/src/Examples/LineTypedTransformations.elm
@@ -1,4 +1,4 @@
-module LineTypedTransformations exposing (main)
+module Examples.LineTypedTransformations exposing (main)
=
=import Element
=import Svgsimilarity index 98%
rename from src/NestedTransformations.elm
rename to src/Examples/NestedTransformations.elm
index e0aaded..b1bc6f5 100644
--- a/src/NestedTransformations.elm
+++ b/src/Examples/NestedTransformations.elm
@@ -1,4 +1,4 @@
-module NestedTransformations exposing
+module Examples.NestedTransformations exposing
= ( Model
= , Msg
= , init
@@ -26,8 +26,10 @@ import List.Extra as List
=import Point2d
=import Svg exposing (..)
=import Svg.Attributes exposing (..)
+import Transformations exposing (Transformation(..))
=
=
+main : Program () Model Msg
=main =
= Browser.sandbox
= { init = init
@@ -40,13 +42,6 @@ type alias Model =
= AnyDict String Group (Array Transformation)
=
=
-type Transformation
- = Identity
- | Scale Float Float
- | Translate Float Float
- | Rotate Float
-
-
=type Group
= = Pink
= | Green
@@ -134,7 +129,9 @@ ui model =
= nestTransformationsGroup group transformations item =
= let
= transformation =
- transformations |> Array.toList |> apply
+ transformations
+ |> Array.toList
+ |> Transformations.apply
=
= color =
= groupsimilarity index 99%
rename from src/PolarCoordinates.elm
rename to src/Examples/PolarCoordinates.elm
index 3a476cd..4fd1e93 100644
--- a/src/PolarCoordinates.elm
+++ b/src/Examples/PolarCoordinates.elm
@@ -1,4 +1,4 @@
-module PolarCoordinates exposing
+module Examples.PolarCoordinates exposing
= ( Model
= , Msg
= , initsimilarity index 98%
rename from src/RosetteTypedTransformations.elm
rename to src/Examples/RosetteTypedTransformations.elm
index 2ae161e..e83eb99 100644
--- a/src/RosetteTypedTransformations.elm
+++ b/src/Examples/RosetteTypedTransformations.elm
@@ -1,4 +1,4 @@
-module RosetteTypedTransformations exposing (main, ui)
+module Examples.RosetteTypedTransformations exposing (main, ui)
=
=import Element
=import Svgsimilarity index 89%
rename from src/Simplest.elm
rename to src/Examples/Simplest.elm
index 2e65fa4..74f75dc 100644
--- a/src/Simplest.elm
+++ b/src/Examples/Simplest.elm
@@ -1,4 +1,4 @@
-module Simplest exposing (Config, defaults, ui)
+module Examples.Simplest exposing (Config, defaults, ui)
=
=import Html exposing (Html)
=import Svgsimilarity index 98%
rename from src/Spiral.elm
rename to src/Examples/Spiral.elm
index 7e1ae87..3b52178 100644
--- a/src/Spiral.elm
+++ b/src/Examples/Spiral.elm
@@ -1,4 +1,4 @@
-module Spiral exposing (main, ui)
+module Examples.Spiral exposing (main, ui)
=
=import Element
=import Svgnew file mode 100644
index 0000000..3321826
--- /dev/null
+++ b/src/Examples/Transformations.elm
@@ -0,0 +1,343 @@
+module Examples.Transformations exposing
+ ( Model
+ , Msg
+ , init
+ , main
+ , ui
+ , update
+ )
+
+import Array exposing (Array)
+import Browser
+import Browser.Events
+import CartesianPlane
+import Dict exposing (Dict)
+import Element exposing (Element)
+import Element.Background as Background
+import Element.Border as Border
+import Element.Input as Input
+import Geometry.Svg
+import Html exposing (Html)
+import Json.Decode exposing (Decoder)
+import LineSegment2d
+import List.Extra as List
+import Point2d
+import Svg exposing (..)
+import Svg.Attributes exposing (..)
+import Transformations exposing (Transformation(..))
+
+
+main : Program () Model Msg
+main =
+ Browser.sandbox
+ { init = init
+ , view = view
+ , update = update
+ }
+
+
+type alias Model =
+ Array Transformation
+
+
+type Msg
+ = AddTransformation Transformation
+ | DeleteTransformation Int
+ | SetTransformation Int Transformation
+
+
+init : Model
+init =
+ Array.fromList
+ [ Translate 0 0
+ , Rotate 0
+ , Scale 1 1
+ ]
+
+
+view : Model -> Html Msg
+view model =
+ Element.layout
+ [ Element.height Element.fill
+ , Element.width Element.fill
+ ]
+ (ui model)
+
+
+ui : Model -> Element Msg
+ui model =
+ let
+ transformations =
+ Array.toList model
+
+ wrapper element =
+ Element.column
+ [ Element.width (Element.maximum 600 Element.fill)
+ , Element.centerX
+ , Element.spacing 20
+ ]
+ [ Element.el
+ [ Element.width Element.fill
+ ]
+ (Element.html element)
+ , Element.el
+ [ Background.color (Element.rgb 1 0.8 0.8)
+ , Element.padding 10
+ , Element.width Element.fill
+ ]
+ (transformationsUI transformations)
+ ]
+
+ shape =
+ g [ transform (Transformations.apply transformations) ]
+ [ line
+ [ x1 "0"
+ , x2 "100"
+ , y1 "0"
+ , y2 "0"
+ , stroke "red"
+ , strokeWidth "1"
+ ]
+ []
+ , circle [ cx "0", cy "0", r "2", fill "red" ] []
+ , grid
+ [ stroke "pink"
+ , fill "pink"
+ , strokeWidth "0.3"
+ ]
+ 10
+ 30
+
+ -- , rect [ x "", y "-2", width "1", height "4" ] []
+ ]
+ in
+ shape
+ |> List.singleton
+ |> CartesianPlane.graph 300
+ |> wrapper
+
+
+transformationsUI : List Transformation -> Element Msg
+transformationsUI transformations =
+ let
+ addButtons =
+ [ Element.text "Add transformation: "
+ , Input.button []
+ { onPress = Just (AddTransformation (Translate 0 0))
+ , label = Element.text "Translate"
+ }
+ , Input.button []
+ { onPress = Just (AddTransformation (Scale 1 1))
+ , label = Element.text "Scale"
+ }
+ , Input.button []
+ { onPress = Just (AddTransformation (Rotate 0))
+ , label = Element.text "Rotate"
+ }
+ ]
+
+ currentTrasformations =
+ transformations
+ |> List.indexedMap transformationUI
+ in
+ Element.column
+ [ Element.width Element.fill
+ , Element.spacing 10
+ ]
+ [ Element.row
+ [ Element.width Element.fill
+ , Element.spacing 10
+ ]
+ addButtons
+ , Element.column
+ [ Element.width Element.fill
+ , Element.spacing 10
+ ]
+ currentTrasformations
+ ]
+
+
+transformationUI : Int -> Transformation -> Element Msg
+transformationUI index transformation =
+ let
+ sliderBackground =
+ Element.el
+ [ Element.width Element.fill
+ , Element.height (Element.px 2)
+ , Element.centerY
+ , Background.color <| Element.rgb 0.7 0.7 0.7
+ , Border.rounded 2
+ ]
+ Element.none
+
+ controls =
+ case transformation of
+ Identity ->
+ [ Element.text "Identity" ]
+
+ Scale horizontal vertical ->
+ [ Input.slider
+ [ Element.behindContent sliderBackground
+ ]
+ { onChange =
+ \x ->
+ SetTransformation index (Scale x vertical)
+ , label = Input.labelLeft [] (Element.text "horizontal")
+ , min = 0
+ , max = 10
+ , value = horizontal
+ , thumb = Input.defaultThumb
+ , step = Just 0.1
+ }
+ , Input.slider
+ [ Element.behindContent sliderBackground
+ ]
+ { onChange =
+ \y ->
+ SetTransformation index (Scale horizontal y)
+ , label = Input.labelLeft [] (Element.text "vertical")
+ , min = 0
+ , max = 10
+ , value = vertical
+ , thumb = Input.defaultThumb
+ , step = Just 0.1
+ }
+ ]
+
+ Translate x y ->
+ [ Input.slider
+ [ Element.behindContent sliderBackground
+ ]
+ { onChange =
+ \value ->
+ SetTransformation index (Translate value y)
+ , label = Input.labelLeft [] (Element.text "x")
+ , min = -100
+ , max = 100
+ , value = x
+ , thumb = Input.defaultThumb
+ , step = Just 1
+ }
+ , Input.slider
+ [ Element.behindContent sliderBackground
+ ]
+ { onChange =
+ \value ->
+ SetTransformation index (Translate x value)
+ , label = Input.labelLeft [] (Element.text "y")
+ , min = -100
+ , max = 100
+ , value = y
+ , thumb = Input.defaultThumb
+ , step = Just 1
+ }
+ ]
+
+ Rotate angle ->
+ [ Input.slider
+ [ Element.behindContent sliderBackground
+ ]
+ { onChange =
+ \value ->
+ SetTransformation index (Rotate value)
+ , label = Input.labelLeft [] (Element.text "angle")
+ , min = -360
+ , max = 360
+ , value = angle
+ , thumb = Input.defaultThumb
+ , step = Just 1
+ }
+ ]
+ in
+ Element.column
+ [ Element.width Element.fill
+ , Border.color (Element.rgb 0.9 0.9 0.9)
+ , Border.width 3
+ , Element.padding 5
+ , Element.spacing 20
+ ]
+ [ Element.row [ Element.width Element.fill ]
+ [ transformation
+ |> List.singleton
+ |> Transformations.apply
+ |> Element.text
+ |> Element.el [ Element.width Element.fill ]
+ , Input.button []
+ { onPress = Just (DeleteTransformation index)
+ , label = Element.text "X"
+ }
+ ]
+ , Element.column
+ [ Element.width Element.fill
+ , Element.spacing 20
+ ]
+ controls
+ ]
+
+
+update : Msg -> Model -> Model
+update msg model =
+ case msg of
+ AddTransformation transformation ->
+ Array.push transformation model
+
+ DeleteTransformation index ->
+ let
+ end =
+ Array.length model
+
+ front =
+ Array.slice 0 index model
+
+ back =
+ Array.slice (index + 1) end model
+ in
+ Array.append front back
+
+ SetTransformation index transformation ->
+ Array.set index transformation model
+
+
+subscriptions : Model -> Sub Msg
+subscriptions model =
+ Sub.none
+
+
+grid : List (Svg.Attribute msg) -> Float -> Float -> Svg msg
+grid attributes unit size =
+ let
+ positiveValues =
+ size
+ / 2
+ |> floor
+ |> List.range 1
+ |> List.map toFloat
+ |> List.map ((*) unit)
+
+ negativeValues =
+ positiveValues
+ |> List.map negate
+
+ max =
+ unit * size / 2
+
+ min =
+ negate max
+ in
+ ((positiveValues ++ negativeValues)
+ |> List.map
+ (\value ->
+ [ ( Point2d.fromCoordinates ( value, min )
+ , Point2d.fromCoordinates ( value, max )
+ )
+ , ( Point2d.fromCoordinates ( min, value )
+ , Point2d.fromCoordinates ( max, value )
+ )
+ ]
+ )
+ |> List.concat
+ |> List.map LineSegment2d.fromEndpoints
+ |> List.map (Geometry.Svg.lineSegment2d attributes)
+ )
+ |> (::) (CartesianPlane.axes attributes (size * unit))
+ |> g []similarity index 98%
rename from src/TransformationsCircle.elm
rename to src/Examples/TransformationsCircle.elm
index 96264ff..a3a507f 100644
--- a/src/TransformationsCircle.elm
+++ b/src/Examples/TransformationsCircle.elm
@@ -1,4 +1,4 @@
-module TransformationsCircle exposing (Config, defaults, main, ui)
+module Examples.TransformationsCircle exposing (Config, defaults, main, ui)
=
=import Element exposing (Element)
=import Html exposing (Html)similarity index 99%
rename from src/Tree.elm
rename to src/Examples/Tree.elm
index e2925a8..eaed7b3 100644
--- a/src/Tree.elm
+++ b/src/Examples/Tree.elm
@@ -1,4 +1,4 @@
-module Tree exposing
+module Examples.Tree exposing
= ( Model
= , Msg
= , init
@@ -19,6 +19,7 @@ import Svg exposing (..)
=import Svg.Attributes exposing (..)
=
=
+main : Program Flags Model Msg
=main =
= Browser.element
= { init = initnew file mode 100644
index 0000000..222da7a
--- /dev/null
+++ b/src/Examples/ViewBox.elm
@@ -0,0 +1,276 @@
+module Examples.ViewBox exposing
+ ( Model
+ , Msg
+ , init
+ , main
+ , ui
+ , update
+ , view
+ )
+
+import Browser
+import BrowserWindow
+import CartesianPlane exposing (graph)
+import Element
+import Element.Background as Background
+import Element.Border as Border
+import Element.Input as Input
+import Html exposing (Html)
+import Svg exposing (..)
+import Svg.Attributes exposing (..)
+
+
+main =
+ Browser.sandbox
+ { init = init
+ , view = view
+ , update = update
+ }
+
+
+type alias Model =
+ { left : Float
+ , top : Float
+ , width : Float
+ , height : Float
+ , preserveAspectRatio : Bool
+ }
+
+
+type Msg
+ = Move Float Float
+ | Resize Float Float
+ | PreserveAspectRatio Bool
+
+
+init : Model
+init =
+ { left = 0
+ , top = 0
+ , width = 400
+ , height = 400
+ , preserveAspectRatio = False
+ }
+
+
+view : Model -> Html.Html Msg
+view model =
+ let
+ wrapper element =
+ Element.el
+ [ Element.width (Element.maximum 1200 Element.fill)
+ , Element.height Element.fill
+ , Element.centerX
+ ]
+ element
+ in
+ ui model
+ |> wrapper
+ |> Element.layout
+ [ Element.height Element.fill
+ , Element.width Element.fill
+ ]
+
+
+update : Msg -> Model -> Model
+update msg model =
+ case msg of
+ Move left top ->
+ { model
+ | left = left
+ , top = top
+ }
+
+ Resize width height ->
+ { model
+ | width = width
+ , height = height
+ }
+
+ PreserveAspectRatio value ->
+ { model | preserveAspectRatio = value }
+
+
+ui model =
+ let
+ viewbox =
+ svg
+ [ viewBox "-500 -500 1000 1000"
+ ]
+ [ g [] world
+ , rect
+ [ x (String.fromFloat model.left)
+ , y (String.fromFloat model.top)
+ , width (String.fromFloat model.width)
+ , height (String.fromFloat model.height)
+ , opacity "0.3"
+ , stroke "white"
+ , fill "pink"
+ ]
+ []
+ ]
+ |> Element.html
+ |> Element.el
+ [ Element.centerX
+ , Element.centerY
+ , Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+ viewport =
+ svg
+ [ [ model.left, model.top, model.width, model.height ]
+ |> List.map String.fromFloat
+ |> String.join " "
+ |> viewBox
+ , preserveAspectRatio <|
+ case model.preserveAspectRatio of
+ True ->
+ "xMidYMid meet"
+
+ False ->
+ "none"
+ , Svg.Attributes.style "width: 100%; height: 100%; flex-grow: 1; flex-basis: 0"
+ ]
+ [ g [] world
+ , rect
+ [ x (String.fromFloat model.left)
+ , y (String.fromFloat model.top)
+ , width (String.fromFloat model.width)
+ , height (String.fromFloat model.height)
+ , opacity "0.3"
+ , stroke "white"
+ , fill "pink"
+ ]
+ []
+ ]
+ |> Element.html
+ |> Element.el
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+ |> BrowserWindow.window
+ [ Element.centerX
+ , Element.centerY
+ , Element.width Element.fill
+ , Element.height Element.fill
+ ]
+ in
+ Element.column
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ , Element.spacing 30
+ , Element.padding 30
+ ]
+ [ Element.row
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ , Element.centerX
+ , Element.spacing 30
+ ]
+ [ viewbox
+ , viewport
+ ]
+ , Input.slider
+ [ Element.behindContent
+ (Element.el
+ [ Element.width Element.fill
+ , Element.height (Element.px 2)
+ , Element.centerY
+ , Background.color <| Element.rgb 0.7 0.7 0.7
+ , Border.rounded 2
+ ]
+ Element.none
+ )
+ ]
+ { onChange = \left -> Move left model.top
+ , label =
+ Input.labelBelow [ Element.centerX ] <|
+ Element.text ("left: " ++ String.fromFloat model.left)
+ , min = -500
+ , max = 500
+ , value = model.left
+ , thumb = Input.defaultThumb
+ , step = Just 1
+ }
+ , Input.slider
+ [ Element.behindContent
+ (Element.el
+ [ Element.width Element.fill
+ , Element.height (Element.px 2)
+ , Element.centerY
+ , Background.color <| Element.rgb 0.7 0.7 0.7
+ , Border.rounded 2
+ ]
+ Element.none
+ )
+ ]
+ { onChange = \top -> Move model.left top
+ , label =
+ Input.labelBelow [ Element.centerX ] <|
+ Element.text ("top: " ++ String.fromFloat model.top)
+ , min = -500
+ , max = 500
+ , value = model.top
+ , thumb = Input.defaultThumb
+ , step = Just 1
+ }
+ , Input.slider
+ [ Element.behindContent
+ (Element.el
+ [ Element.width Element.fill
+ , Element.height (Element.px 2)
+ , Element.centerY
+ , Background.color <| Element.rgb 0.7 0.7 0.7
+ , Border.rounded 2
+ ]
+ Element.none
+ )
+ ]
+ { onChange = \width -> Resize width model.height
+ , label =
+ Input.labelBelow [ Element.centerX ] <|
+ Element.text ("width: " ++ String.fromFloat model.width)
+ , min = 1
+ , max = 500
+ , value = model.width
+ , thumb = Input.defaultThumb
+ , step = Just 1
+ }
+ , Input.slider
+ [ Element.behindContent
+ (Element.el
+ [ Element.width Element.fill
+ , Element.height (Element.px 2)
+ , Element.centerY
+ , Background.color <| Element.rgb 0.7 0.7 0.7
+ , Border.rounded 2
+ ]
+ Element.none
+ )
+ ]
+ { onChange = \height -> Resize model.width height
+ , label =
+ Input.labelBelow [ Element.centerX ] <|
+ Element.text ("height: " ++ String.fromFloat model.height)
+ , min = 1
+ , max = 500
+ , value = model.height
+ , thumb = Input.defaultThumb
+ , step = Just 1
+ }
+ , Input.checkbox []
+ { checked = model.preserveAspectRatio
+ , icon = Input.defaultCheckbox
+ , label = Input.labelRight [] (Element.text "Preserve aspect ratio")
+ , onChange = PreserveAspectRatio
+ }
+ ]
+
+
+world : List (Svg Msg)
+world =
+ [ circle [ cx "400", cy "300", r "250", fill "purple" ] []
+ , circle [ cx "200", cy "350", r "50", fill "yellow" ] []
+ , circle [ cx "0", cy "0", r "20", fill "red" ] []
+ ]index ab8ac60..99d1276 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -10,41 +10,41 @@ import Basics.Extra exposing (curry)
=import Browser
=import Browser.Navigation as Navigation
=import BrowserWindow
-import CartesianCoordinates
-import CenteredDot
-import Counter
=import Dict
-import DotAtTheCenterOfTheScreen
=import Element exposing (Element)
=import Element.Background as Background
=import Element.Border as Border
=import Element.Events
=import Element.Font as Font
=import Element.Input as Input
+import Examples.CartesianCoordinates
+import Examples.CenteredDot
+import Examples.Counter
+import Examples.DotAtTheCenterOfTheScreen
+import Examples.FillTheScreen
+import Examples.Gradient
+import Examples.Line
+import Examples.NestedTransformations
+import Examples.PolarCoordinates
+import Examples.RosetteTypedTransformations
+import Examples.Simplest
+import Examples.Spiral
+import Examples.Transformations
+import Examples.TransformationsCircle
+import Examples.Tree
+import Examples.ViewBox
=import FeatherIcons exposing (icons)
-import FillTheScreen
-import Gradient
=import Html exposing (Html)
=import Html.Attributes
=import Http
-import Line
=import Mark
=import Mark.Custom
=import Mark.Default
-import NestedTransformations
=import Parser
=import Parser.Advanced
-import PolarCoordinates
=import Result.Extra as Result
-import RosetteTypedTransformations
=import Routes exposing (Route)
-import Simplest
-import Spiral
-import Transformations
-import TransformationsCircle
-import Tree
=import Url exposing (Url)
-import ViewBox
=
=
=main : Program Flags Model Msg
@@ -67,13 +67,13 @@ type alias Model =
= { url : Url
= , key : Navigation.Key
= , markup : Maybe String
- , counter : Counter.Model
- , transformations : Transformations.Model
- , nestedTransformations : NestedTransformations.Model
- , cartesianCoordinates : CartesianCoordinates.Model
- , polarCoordinates : PolarCoordinates.Model
- , tree : Maybe Tree.Model
- , viewBox : ViewBox.Model
+ , counter : Examples.Counter.Model
+ , transformations : Examples.Transformations.Model
+ , nestedTransformations : Examples.NestedTransformations.Model
+ , cartesianCoordinates : Examples.CartesianCoordinates.Model
+ , polarCoordinates : Examples.PolarCoordinates.Model
+ , tree : Maybe Examples.Tree.Model
+ , viewBox : Examples.ViewBox.Model
= }
=
=
@@ -81,14 +81,14 @@ type Msg
= = UrlRequested Browser.UrlRequest
= | UrlChanged Url
= | ContentFetched (Result Http.Error String)
- | CounterMsg Counter.Msg
- | TransformationsMsg Transformations.Msg
- | NestedTransformationsMsg NestedTransformations.Msg
- | CartesianCoordinatesMsg CartesianCoordinates.Msg
- | PolarCoordinatesMsg PolarCoordinates.Msg
- | ToggleTree (Maybe Tree.Model)
- | TreeMsg Tree.Msg
- | ViewBoxMsg ViewBox.Msg
+ | CounterMsg Examples.Counter.Msg
+ | TransformationsMsg Examples.Transformations.Msg
+ | NestedTransformationsMsg Examples.NestedTransformations.Msg
+ | CartesianCoordinatesMsg Examples.CartesianCoordinates.Msg
+ | PolarCoordinatesMsg Examples.PolarCoordinates.Msg
+ | ToggleTree (Maybe Examples.Tree.Model)
+ | TreeMsg Examples.Tree.Msg
+ | ViewBoxMsg Examples.ViewBox.Msg
=
=
=init : Flags -> Url -> Navigation.Key -> ( Model, Cmd Msg )
@@ -96,13 +96,13 @@ init flags url key =
= ( { url = url
= , key = key
= , markup = Nothing
- , counter = Counter.init
- , transformations = Transformations.init
- , nestedTransformations = NestedTransformations.init
- , cartesianCoordinates = CartesianCoordinates.init
- , polarCoordinates = PolarCoordinates.init
+ , counter = Examples.Counter.init
+ , transformations = Examples.Transformations.init
+ , nestedTransformations = Examples.NestedTransformations.init
+ , cartesianCoordinates = Examples.CartesianCoordinates.init
+ , polarCoordinates = Examples.PolarCoordinates.init
= , tree = Nothing
- , viewBox = ViewBox.init
+ , viewBox = Examples.ViewBox.init
= }
= , url
= |> Routes.parse
@@ -307,27 +307,39 @@ update msg model =
= )
=
= CounterMsg m ->
- ( { model | counter = Counter.update m model.counter }
+ ( { model | counter = Examples.Counter.update m model.counter }
= , Cmd.none
= )
=
= TransformationsMsg m ->
- ( { model | transformations = Transformations.update m model.transformations }
+ ( { model
+ | transformations =
+ Examples.Transformations.update m model.transformations
+ }
= , Cmd.none
= )
=
= NestedTransformationsMsg m ->
- ( { model | nestedTransformations = NestedTransformations.update m model.nestedTransformations }
+ ( { model
+ | nestedTransformations =
+ Examples.NestedTransformations.update m model.nestedTransformations
+ }
= , Cmd.none
= )
=
= CartesianCoordinatesMsg m ->
- ( { model | cartesianCoordinates = CartesianCoordinates.update m model.cartesianCoordinates }
+ ( { model
+ | cartesianCoordinates =
+ Examples.CartesianCoordinates.update m model.cartesianCoordinates
+ }
= , Cmd.none
= )
=
= PolarCoordinatesMsg m ->
- ( { model | polarCoordinates = PolarCoordinates.update m model.polarCoordinates }
+ ( { model
+ | polarCoordinates =
+ Examples.PolarCoordinates.update m model.polarCoordinates
+ }
= , Cmd.none
= )
=
@@ -337,7 +349,7 @@ update msg model =
= TreeMsg m ->
= case model.tree of
= Just tree ->
- Tree.update m tree
+ Examples.Tree.update m tree
= |> (\( newTree, treeCmd ) ->
= ( { model | tree = Just newTree }
= , Cmd.map TreeMsg treeCmd
@@ -348,7 +360,7 @@ update msg model =
= ( model, Cmd.none )
=
= ViewBoxMsg m ->
- ( { model | viewBox = ViewBox.update m model.viewBox }, Cmd.none )
+ ( { model | viewBox = Examples.ViewBox.update m model.viewBox }, Cmd.none )
=
=
=subscriptions : Model -> Sub Msg
@@ -358,7 +370,7 @@ subscriptions model =
= Sub.none
=
= Just tree ->
- Tree.subscriptions tree
+ Examples.Tree.subscriptions tree
= |> Sub.map TreeMsg
=
=
@@ -472,7 +484,7 @@ document =
= let
= render model =
= model.counter
- |> Counter.ui
+ |> Examples.Counter.ui
= |> Element.el
= [ Element.centerX
= , Element.centerY
@@ -489,9 +501,9 @@ document =
= simplest : Mark.Block (Model -> Element Msg)
= simplest =
= let
- render : Simplest.Config -> Model -> Element Msg
+ render : Examples.Simplest.Config -> Model -> Element Msg
= render config model =
- Simplest.ui config
+ Examples.Simplest.ui config
= |> Element.html
= |> Element.el []
= |> Element.el
@@ -502,7 +514,7 @@ document =
= |> BrowserWindow.window []
= in
= Mark.record2 "Simplest"
- Simplest.Config
+ Examples.Simplest.Config
= (Mark.field "background" Mark.string)
= (Mark.field "fill" Mark.string)
= |> Mark.map render
@@ -511,7 +523,7 @@ document =
= fillTheScreen =
= let
= render model =
- FillTheScreen.ui
+ Examples.FillTheScreen.ui
= |> Element.el
= [ Element.height (Element.px 400)
= , Element.width Element.fill
@@ -524,7 +536,7 @@ document =
= dotAtTheCenterOfTheScreen =
= let
= render model =
- DotAtTheCenterOfTheScreen.ui
+ Examples.DotAtTheCenterOfTheScreen.ui
= |> Element.el
= [ Element.height (Element.px 400)
= , Element.width Element.fill
@@ -537,7 +549,7 @@ document =
= centeredDot =
= let
= render model =
- CenteredDot.main
+ Examples.CenteredDot.main
= |> Element.html
= |> Element.el
= [ Element.height (Element.px 400)
@@ -550,9 +562,9 @@ document =
= transformationsCircle : Mark.Block (Model -> Element Msg)
= transformationsCircle =
= let
- render : TransformationsCircle.Config -> Model -> Element Msg
+ render : Examples.TransformationsCircle.Config -> Model -> Element Msg
= render config model =
- TransformationsCircle.ui config
+ Examples.TransformationsCircle.ui config
= |> Element.el
= [ Element.height (Element.px 400)
= , Element.width Element.fill
@@ -561,7 +573,7 @@ document =
= |> BrowserWindow.window []
= in
= Mark.record4 "TransformationsCircle"
- TransformationsCircle.Config
+ Examples.TransformationsCircle.Config
= (Mark.field "circle" Mark.bool)
= (Mark.field "center" Mark.bool)
= (Mark.field "angle" Mark.bool)
@@ -572,7 +584,7 @@ document =
= line =
= let
= render model =
- Line.ui
+ Examples.Line.ui
= |> Element.el
= [ Element.height (Element.px 400)
= , Element.width Element.fill
@@ -585,7 +597,7 @@ document =
= gradient =
= let
= render model =
- Gradient.ui
+ Examples.Gradient.ui
= |> Element.el
= [ Element.height (Element.px 400)
= , Element.width Element.fill
@@ -599,7 +611,7 @@ document =
= let
= render model =
= model.transformations
- |> Transformations.ui
+ |> Examples.Transformations.ui
= |> Element.el
= [ Element.width Element.fill
= ]
@@ -613,7 +625,7 @@ document =
= let
= render model =
= model.nestedTransformations
- |> NestedTransformations.ui
+ |> Examples.NestedTransformations.ui
= |> Element.el
= [ Element.width Element.fill
= ]
@@ -627,7 +639,7 @@ document =
= let
= render model =
= model.cartesianCoordinates
- |> CartesianCoordinates.ui
+ |> Examples.CartesianCoordinates.ui
= |> Element.el
= [ Element.width Element.fill
= ]
@@ -641,7 +653,7 @@ document =
= let
= render model =
= model.polarCoordinates
- |> PolarCoordinates.ui
+ |> Examples.PolarCoordinates.ui
= |> Element.el
= [ Element.width Element.fill
= ]
@@ -654,7 +666,7 @@ document =
= rosette =
= let
= render model =
- RosetteTypedTransformations.ui
+ Examples.RosetteTypedTransformations.ui
= |> Element.el
= [ Element.height (Element.px 400)
= , Element.width Element.fill
@@ -667,7 +679,7 @@ document =
= spiral =
= let
= render model =
- Spiral.ui
+ Examples.Spiral.ui
= |> Element.el
= [ Element.height (Element.px 400)
= , Element.width Element.fill
@@ -681,7 +693,7 @@ document =
= let
= render model =
= model.tree
- |> Maybe.map Tree.ui
+ |> Maybe.map Examples.Tree.ui
= |> Maybe.withDefault Element.none
= |> Element.el
= [ Element.height (Element.px 600)
@@ -697,7 +709,7 @@ document =
= let
= render model =
= model.viewBox
- |> ViewBox.ui
+ |> Examples.ViewBox.ui
= |> Element.el
= [ Element.width Element.fill
= ]index 1b3937d..d72965f 100644
--- a/src/Transformations.elm
+++ b/src/Transformations.elm
@@ -1,45 +1,8 @@
=module Transformations exposing
- ( Model
- , Msg
- , Transformation(..)
+ ( Transformation(..)
= , apply
- , init
- , main
- , ui
- , update
= )
=
-import Array exposing (Array)
-import Browser
-import Browser.Events
-import CartesianPlane
-import Dict exposing (Dict)
-import Element exposing (Element)
-import Element.Background as Background
-import Element.Border as Border
-import Element.Input as Input
-import Geometry.Svg
-import Html exposing (Html)
-import Json.Decode exposing (Decoder)
-import LineSegment2d
-import List.Extra as List
-import Point2d
-import Svg exposing (..)
-import Svg.Attributes exposing (..)
-
-
-main : Program () Model Msg
-main =
- Browser.sandbox
- { init = init
- , view = view
- , update = update
- }
-
-
-type alias Model =
- Array Transformation
-
=
=type Transformation
= = Identity
@@ -48,269 +11,6 @@ type Transformation
= | Rotate Float
=
=
-type Msg
- = AddTransformation Transformation
- | DeleteTransformation Int
- | SetTransformation Int Transformation
-
-
-init : Model
-init =
- Array.fromList
- [ Translate 0 0
- , Rotate 0
- , Scale 1 1
- ]
-
-
-view : Model -> Html Msg
-view model =
- Element.layout
- [ Element.height Element.fill
- , Element.width Element.fill
- ]
- (ui model)
-
-
-ui : Model -> Element Msg
-ui model =
- let
- transformations =
- Array.toList model
-
- wrapper element =
- Element.column
- [ Element.width (Element.maximum 600 Element.fill)
- , Element.centerX
- , Element.spacing 20
- ]
- [ Element.el
- [ Element.width Element.fill
- ]
- (Element.html element)
- , Element.el
- [ Background.color (Element.rgb 1 0.8 0.8)
- , Element.padding 10
- , Element.width Element.fill
- ]
- (transformationsUI transformations)
- ]
-
- shape =
- g [ transform (apply transformations) ]
- [ line
- [ x1 "0"
- , x2 "100"
- , y1 "0"
- , y2 "0"
- , stroke "red"
- , strokeWidth "1"
- ]
- []
- , circle [ cx "0", cy "0", r "2", fill "red" ] []
- , grid
- [ stroke "pink"
- , fill "pink"
- , strokeWidth "0.3"
- ]
- 10
- 30
-
- -- , rect [ x "", y "-2", width "1", height "4" ] []
- ]
- in
- shape
- |> List.singleton
- |> CartesianPlane.graph 300
- |> wrapper
-
-
-transformationsUI : List Transformation -> Element Msg
-transformationsUI transformations =
- let
- addButtons =
- [ Element.text "Add transformation: "
- , Input.button []
- { onPress = Just (AddTransformation (Translate 0 0))
- , label = Element.text "Translate"
- }
- , Input.button []
- { onPress = Just (AddTransformation (Scale 1 1))
- , label = Element.text "Scale"
- }
- , Input.button []
- { onPress = Just (AddTransformation (Rotate 0))
- , label = Element.text "Rotate"
- }
- ]
-
- currentTrasformations =
- transformations
- |> List.indexedMap transformationUI
- in
- Element.column
- [ Element.width Element.fill
- , Element.spacing 10
- ]
- [ Element.row
- [ Element.width Element.fill
- , Element.spacing 10
- ]
- addButtons
- , Element.column
- [ Element.width Element.fill
- , Element.spacing 10
- ]
- currentTrasformations
- ]
-
-
-transformationUI : Int -> Transformation -> Element Msg
-transformationUI index transformation =
- let
- sliderBackground =
- Element.el
- [ Element.width Element.fill
- , Element.height (Element.px 2)
- , Element.centerY
- , Background.color <| Element.rgb 0.7 0.7 0.7
- , Border.rounded 2
- ]
- Element.none
-
- controls =
- case transformation of
- Identity ->
- [ Element.text "Identity" ]
-
- Scale horizontal vertical ->
- [ Input.slider
- [ Element.behindContent sliderBackground
- ]
- { onChange =
- \x ->
- SetTransformation index (Scale x vertical)
- , label = Input.labelLeft [] (Element.text "horizontal")
- , min = 0
- , max = 10
- , value = horizontal
- , thumb = Input.defaultThumb
- , step = Just 0.1
- }
- , Input.slider
- [ Element.behindContent sliderBackground
- ]
- { onChange =
- \y ->
- SetTransformation index (Scale horizontal y)
- , label = Input.labelLeft [] (Element.text "vertical")
- , min = 0
- , max = 10
- , value = vertical
- , thumb = Input.defaultThumb
- , step = Just 0.1
- }
- ]
-
- Translate x y ->
- [ Input.slider
- [ Element.behindContent sliderBackground
- ]
- { onChange =
- \value ->
- SetTransformation index (Translate value y)
- , label = Input.labelLeft [] (Element.text "x")
- , min = -100
- , max = 100
- , value = x
- , thumb = Input.defaultThumb
- , step = Just 1
- }
- , Input.slider
- [ Element.behindContent sliderBackground
- ]
- { onChange =
- \value ->
- SetTransformation index (Translate x value)
- , label = Input.labelLeft [] (Element.text "y")
- , min = -100
- , max = 100
- , value = y
- , thumb = Input.defaultThumb
- , step = Just 1
- }
- ]
-
- Rotate angle ->
- [ Input.slider
- [ Element.behindContent sliderBackground
- ]
- { onChange =
- \value ->
- SetTransformation index (Rotate value)
- , label = Input.labelLeft [] (Element.text "angle")
- , min = -360
- , max = 360
- , value = angle
- , thumb = Input.defaultThumb
- , step = Just 1
- }
- ]
- in
- Element.column
- [ Element.width Element.fill
- , Border.color (Element.rgb 0.9 0.9 0.9)
- , Border.width 3
- , Element.padding 5
- , Element.spacing 20
- ]
- [ Element.row [ Element.width Element.fill ]
- [ transformation
- |> List.singleton
- |> apply
- |> Element.text
- |> Element.el [ Element.width Element.fill ]
- , Input.button []
- { onPress = Just (DeleteTransformation index)
- , label = Element.text "X"
- }
- ]
- , Element.column
- [ Element.width Element.fill
- , Element.spacing 20
- ]
- controls
- ]
-
-
-update : Msg -> Model -> Model
-update msg model =
- case msg of
- AddTransformation transformation ->
- Array.push transformation model
-
- DeleteTransformation index ->
- let
- end =
- Array.length model
-
- front =
- Array.slice 0 index model
-
- back =
- Array.slice (index + 1) end model
- in
- Array.append front back
-
- SetTransformation index transformation ->
- Array.set index transformation model
-
-
-subscriptions : Model -> Sub Msg
-subscriptions model =
- Sub.none
-
-
=apply : List Transformation -> String
=apply transformations =
= let
@@ -342,43 +42,3 @@ apply transformations =
= transformations
= |> List.map toString
= |> String.join " "
-
-
-grid : List (Svg.Attribute msg) -> Float -> Float -> Svg msg
-grid attributes unit size =
- let
- positiveValues =
- size
- / 2
- |> floor
- |> List.range 1
- |> List.map toFloat
- |> List.map ((*) unit)
-
- negativeValues =
- positiveValues
- |> List.map negate
-
- max =
- unit * size / 2
-
- min =
- negate max
- in
- ((positiveValues ++ negativeValues)
- |> List.map
- (\value ->
- [ ( Point2d.fromCoordinates ( value, min )
- , Point2d.fromCoordinates ( value, max )
- )
- , ( Point2d.fromCoordinates ( min, value )
- , Point2d.fromCoordinates ( max, value )
- )
- ]
- )
- |> List.concat
- |> List.map LineSegment2d.fromEndpoints
- |> List.map (Geometry.Svg.lineSegment2d attributes)
- )
- |> (::) (CartesianPlane.axes attributes (size * unit))
- |> g []
Commits: 3
Rename capture script to build
This name better reflects it's function.
index 8ae0061..801b12d 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,3 +1,4 @@
+
=image: alekzonder/puppeteer:1.8.0-0
=
=before_script:
@@ -6,7 +7,7 @@ before_script:
=pages:
= stage: deploy
= script:
- - scripts/capture
+ - scripts/build
=
= artifacts:
= paths:similarity index 99%
rename from scripts/capture
rename to scripts/build
index 4236c08..d9def3c 100755
--- a/scripts/capture
+++ b/scripts/build
@@ -2,6 +2,7 @@
=
=set -euo pipefail
=
+
=rm -rf built/
=rm -rf public/
=mkdir -p built/
@@ -15,6 +16,7 @@ cp -r content/ public/content/
=cp -r assets/ public/assets/
=cp -r built/* public/assets/
=
+
=mkdir -p built/captured/
=npx coffee src/capture.coffee
=cp -r built/captured/* public/Make capture.coffee automatically scan for content/*.txt files
index 0ce2b31..b00bfa5 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,7 @@
= "elm": "^0.19.0-bugfix2",
= "elm-live": "^3.4.0",
= "express": "^4.16.4",
+ "glob": "^7.1.3",
= "puppeteer": "^1.11.0"
= }
=}index 46eae85..bdbd6c6 100644
--- a/src/capture.coffee
+++ b/src/capture.coffee
@@ -1,6 +1,14 @@
=puppeteer = require "puppeteer"
=express = require "express"
-fs = require "fs"
+FS = require "fs"
+glob = require "glob"
+Path = require "path"
+
+match = (pattern, options) =>
+ new Promise (resolve, reject) =>
+ glob pattern, options, (error, paths) =>
+ if error then return reject errro
+ resolve paths
=
=do () =>
= app = express ``
@@ -19,14 +27,11 @@ do () =>
=
= page = await browser.newPage ``
=
- # TODO: Scan the directory and make this list dynamic!
- for base in [
- "index",
- "preparation",
- "day-1",
- "rest"
- ]
- await do (base) ->
+ for match in await match "*.txt", cwd: "content/"
+ await do (match) ->
+ { dir, name } = Path.parse match
+ base = if dir is "" then name else "#{dir}/#{name}"
+
= url = "http://localhost:8000/#{base}.html"
= path = "built/captured/#{base}.html"
=
@@ -42,7 +47,7 @@ do () =>
= """
=
= await new Promise (resolve, reject) ->
- fs.writeFile path, html, (error) ->
+ FS.writeFile path, html, (error) ->
= if error then return reject error
= do resolve
=Disable pages CI task on branches different than master
index 801b12d..4472935 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -12,5 +12,6 @@ pages:
= artifacts:
= paths:
= - public
- # only:
- # - master
+
+ only:
+ - master
Commits: 9
Merge remote-tracking branch 'origin/master' into content
Use Source Code Pro 18px font in Code and Monospace blocks
Make line numbers extra light and remove the period.
Remove spacing between blocks.
index 36923a4..0a40895 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -355,11 +355,18 @@ document =
= monospace =
= Mark.Default.monospace
= [ Element.padding 20
+ , Font.size 18
= , Element.width Element.fill
= , Border.color colors.gray
= , Border.width 3
= , Border.rounded 5
- , Font.family [ Font.monospace ]
+ , Font.family
+ [ Font.external
+ { url = "https://fonts.googleapis.com/css?family=Source+Code+Pro:200,400&subset=latin-ext"
+ , name = "Source Code Pro"
+ }
+ , Font.monospace
+ ]
= , Element.scrollbarY
= ]
=
@@ -381,7 +388,7 @@ document =
= Element.row [ Element.spacing 10 ]
= [ Element.el
= [ Font.color colors.gray
- , Font.size 20
+ , Font.extraLight
= , Element.width (Element.px 40)
= , css "user-select" "none"
= , css "-webkit-user-select" "none"
@@ -390,7 +397,7 @@ document =
= , css "-o-user-select" "none"
= , css "-moz-user-select" "none"
= ]
- (Element.text (String.fromInt n ++ "."))
+ (Element.text (String.fromInt n))
= , Element.el
= [ Element.width Element.fill
= ]
@@ -404,7 +411,14 @@ document =
= , Border.color colors.gray
= , Border.width 3
= , Border.rounded 5
- , Font.family [ Font.monospace ]
+ , Font.size 18
+ , Font.family
+ [ Font.external
+ { url = "https://fonts.googleapis.com/css?family=Source+Code+Pro:200,400&subset=latin-ext"
+ , name = "Source Code Pro"
+ }
+ , Font.monospace
+ ]
= , Element.scrollbarY
= ]
= in
@@ -724,7 +738,6 @@ document =
= Element.textColumn
= [ Element.centerX
= , Element.width (Element.px 800)
- , Element.spacing 24
= ]
= (List.map (\render -> render model) children)
= )Merge branch 'content' into 'master'
Add the content from paper slides
See merge request software-garden/software-garden.gitlab.io!2
Evolve Main into Browser.document program
The title is extracted from the markup document.
All the blocks and in-lines that do not depend on Model and do not produce Msg are extracted to the Mark.Custom module. This is mainly to avoid name collisions, as their names are pretty common terms like "title" or "link".
index 0a40895..0a89a20 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -28,6 +28,7 @@ import Html.Attributes
=import Http
=import Line
=import Mark
+import Mark.Custom
=import Mark.Default
=import NestedTransformations
=import Parser
@@ -44,7 +45,7 @@ import ViewBox
=
=main : Program Flags Model Msg
=main =
- Browser.element
+ Browser.document
= { init = init
= , view = view
= , update = update
@@ -98,33 +99,45 @@ init flags =
= )
=
=
-view : Model -> Html Msg
+view : Model -> Browser.Document Msg
=view model =
= let
- content : Element Msg
+ content : View
= content =
= case model.markup of
= Nothing ->
- Element.el [ Element.centerX, Element.centerY ] <|
- Element.text "Loading content..."
+ loadingView
=
= Just markup ->
= markup
= |> Mark.parse document
= |> Result.map (\render -> render model)
- |> Result.extract deadEndsElement
+ |> Result.extract deadEndsView
+
+ loadingView : View
+ loadingView =
+ { title = "Software Garden"
+ , body =
+ "Loading content..."
+ |> Element.text
+ |> Element.el
+ [ Element.centerX
+ , Element.centerY
+ ]
+ }
=
- deadEndsElement : List DeadEnd -> Element Msg
- deadEndsElement deadEnds =
+ deadEndsView : List DeadEnd -> View
+ deadEndsView deadEnds =
= deadEnds
= |> List.map deadEndElement
= |> Element.column
= [ Element.centerX
= , Element.centerY
- , Background.color colors.maroon
+ , Background.color Mark.Custom.colors.maroon
= , Element.padding 40
= , Element.spacing 20
= ]
+ |> View "Software Garden : Parsing Errors"
=
= deadEndElement : DeadEnd -> Element Msg
= deadEndElement { row, col, problem, contextStack } =
@@ -238,11 +251,15 @@ view model =
= ++ String.fromInt col
= )
= in
- Element.layout
- [ -- Below is a hack that enables static site generation
- Element.htmlAttribute (Html.Attributes.id "app-container")
+ { title = content.title
+ , body =
+ [ Element.layout
+ [ -- Below is a hack that enables static site generation
+ Element.htmlAttribute (Html.Attributes.id "app-container")
+ ]
+ content.body
= ]
- content
+ }
=
=
=update : Msg -> Model -> ( Model, Cmd Msg )
@@ -318,210 +335,86 @@ type alias DeadEnd =
= Parser.Advanced.DeadEnd Mark.Context Mark.Problem
=
=
-document : Mark.Document (Model -> Element Msg)
+type alias View =
+ { title : String
+ , body : Element Msg
+ }
+
+
+document : Mark.Document (Model -> View)
=document =
= let
- title =
- Mark.Default.title
- [ Element.width Element.fill
- , Element.paddingXY 0 32
- , Font.center
- , Font.size 86
- , Font.extraBold
- ]
- text
-
- header =
- Mark.Default.header
- [ Font.size 24
- , Font.underline
- , Element.paddingXY 0 24
- ]
- text
-
- paragraph : Mark.Block (Model -> Element Msg)
- paragraph =
- let
- render content model =
- Element.paragraph
- [ Element.paddingXY 0 24
- ]
- (content model)
- in
- Mark.map
- render
- text
-
- monospace =
- Mark.Default.monospace
- [ Element.padding 20
- , Font.size 18
- , Element.width Element.fill
- , Border.color colors.gray
- , Border.width 3
- , Border.rounded 5
- , Font.family
- [ Font.external
- { url = "https://fonts.googleapis.com/css?family=Source+Code+Pro:200,400&subset=latin-ext"
- , name = "Source Code Pro"
- }
- , Font.monospace
+ content :
+ { title : String
+ , children : List (Model -> Element Msg)
+ }
+ -> Model
+ -> View
+ content { title, children } model =
+ children
+ |> List.map (\child -> child model)
+ |> Element.textColumn
+ [ Element.centerX
+ , Element.width (Element.maximum 800 Element.fill)
= ]
- , Element.scrollbarY
- ]
-
- css : String -> String -> Element.Attribute msg
- css property value =
- Element.htmlAttribute
- (Html.Attributes.style
- property
- value
- )
-
- code =
- let
- render string model =
- string
- |> String.split "\n"
- |> List.indexedMap
- (\n loc ->
- Element.row [ Element.spacing 10 ]
- [ Element.el
- [ Font.color colors.gray
- , Font.extraLight
- , Element.width (Element.px 40)
- , css "user-select" "none"
- , css "-webkit-user-select" "none"
- , css "-ms-user-select" "none"
- , css "-webkit-touch-callout" "none"
- , css "-o-user-select" "none"
- , css "-moz-user-select" "none"
- ]
- (Element.text (String.fromInt n))
- , Element.el
- [ Element.width Element.fill
- ]
- (Element.text loc)
+ |> View title
+
+ {- The document has to start with a Title block containing a String (i.e. single line of unforamtted text). This String will be used in two ways:
+
+ It will be turned into an Element and appended to list of children that are passed to the view (so it can be rendered in the body of the page).
+
+ It will also serve as a title for a Browser.Document.
+ -}
+ structure =
+ Mark.startWith
+ (\title rest ->
+ let
+ titleElement model =
+ title
+ |> Element.text
+ |> List.singleton
+ |> Element.paragraph
+ [ Element.width Element.fill
+ , Element.paddingXY 0 32
+ , Font.center
+ , Font.size 86
+ , Font.extraBold
= ]
- )
- |> Element.column
- [ Element.padding 20
- , Element.spacing 10
- , Element.width Element.fill
- , Border.color colors.gray
- , Border.width 3
- , Border.rounded 5
- , Font.size 18
- , Font.family
- [ Font.external
- { url = "https://fonts.googleapis.com/css?family=Source+Code+Pro:200,400&subset=latin-ext"
- , name = "Source Code Pro"
- }
- , Font.monospace
- ]
- , Element.scrollbarY
- ]
- in
- Mark.block "Code"
- render
- Mark.multiline
-
- note : Mark.Block (Model -> Element Msg)
- note =
- let
- render elements model =
- elements
- |> List.map (\element -> element model)
- |> Element.textColumn
- [ Element.padding 20
- , Element.spacing 10
- , Element.width Element.fill
- , Border.width 1
- , Border.color colors.gray
- , Font.color colors.gray
- , Border.rounded 5
- ]
- in
- Mark.block "Note"
- render
+ in
+ { title = title
+ , children = titleElement :: rest
+ }
+ )
+ Mark.Custom.title
= (Mark.manyOf
- [ paragraph
- , header
- , list
+ [ Mark.Custom.header
+ , Mark.Custom.paragraph
+ , Mark.Custom.monospace
+ , Mark.Custom.code
+ , Mark.Custom.note
+ , Mark.Custom.emphasize
+ , Mark.Custom.list
+ , Mark.Custom.image
+
+ -- Embeded programs
+ , counter
+ , simplest
+ , fillTheScreen
+ , dotAtTheCenterOfTheScreen
+ , centeredDot
+ , line
+ , gradient
+ , transformations
+ , nestedTransformations
+ , cartesianCoordinates
+ , polarCoordinates
+ , rosette
+ , spiral
+ , tree
+ , viewBox
= ]
= )
=
- emphasize : Mark.Block (Model -> Element Msg)
- emphasize =
- let
- render element model =
- model
- |> element
- |> Element.el
- [ Element.spacing 30
- , Font.bold
- , Font.size 30
- , Font.center
- , Element.paddingXY 0 40
- , Element.width Element.fill
- ]
- in
- Mark.block "Emphasize"
- render
- paragraph
-
- image =
- Mark.Default.image
- [ Element.width Element.fill
- ]
-
- list =
- Mark.Default.list
- { icon = Mark.Default.listIcon
- , style =
- \cursor ->
- case List.length cursor of
- 0 ->
- -- top level element
- [ Element.spacing 16 ]
-
- 1 ->
- [ Element.spacing 16 ]
-
- 2 ->
- [ Element.spacing 16 ]
-
- _ ->
- [ Element.spacing 8 ]
- }
- text
-
- text : Mark.Block (Model -> List (Element Msg))
- text =
- let
- defaultTextStyle =
- Mark.Default.defaultTextStyle
- in
- Mark.Default.textWith
- { defaultTextStyle
- | inlines = defaultTextStyle.inlines ++ [ icon ]
- }
-
- icon : Mark.Inline (Model -> Element Msg)
- icon =
- Mark.inline "Icon"
- (\name model ->
- icons
- |> Dict.get name
- |> Maybe.map (FeatherIcons.toHtml [])
- |> Maybe.map Element.html
- |> Maybe.withDefault
- (Element.text
- ("Icon not found: '" ++ name ++ "'")
- )
- )
- |> Mark.inlineString "name"
-
= -- Embeded programs' blocks
= counter : Mark.Block (Model -> Element Msg)
= counter =
@@ -734,46 +627,5 @@ document =
= Mark.stub "ViewBox" render
= in
= Mark.document
- (\children model ->
- Element.textColumn
- [ Element.centerX
- , Element.width (Element.px 800)
- ]
- (List.map (\render -> render model) children)
- )
- (Mark.manyOf
- [ title
- , header
- , paragraph
- , monospace
- , code
- , note
- , emphasize
- , list
- , image
-
- -- Embeded programs
- , counter
- , simplest
- , fillTheScreen
- , dotAtTheCenterOfTheScreen
- , centeredDot
- , line
- , gradient
- , transformations
- , nestedTransformations
- , cartesianCoordinates
- , polarCoordinates
- , rosette
- , spiral
- , tree
- , viewBox
- ]
- )
-
-
-colors =
- { maroon = Element.rgb 0.7 0 0
- , gray = Element.rgb 0.8 0.8 0.8
- , pink = Element.rgb 1 0.6 0.6
- }
+ content
+ structurenew file mode 100644
index 0000000..3e50354
--- /dev/null
+++ b/src/Mark/Custom.elm
@@ -0,0 +1,240 @@
+module Mark.Custom exposing (code, colors, css, emphasize, header, icon, image, list, monospace, note, paragraph, text, title)
+
+import Dict
+import Element exposing (Element)
+import Element.Background as Background
+import Element.Border as Border
+import Element.Font as Font
+import FeatherIcons exposing (icons)
+import Html exposing (Html)
+import Html.Attributes
+import Mark
+import Mark.Default
+
+
+title : Mark.Block String
+title =
+ Mark.block "Title"
+ identity
+ Mark.string
+
+
+header : Mark.Block (model -> Element msg)
+header =
+ Mark.Default.header
+ [ Font.size 24
+ , Font.underline
+ , Element.paddingXY 0 24
+ ]
+ text
+
+
+paragraph : Mark.Block (model -> Element msg)
+paragraph =
+ let
+ render content model =
+ Element.paragraph
+ [ Element.paddingXY 0 24
+ ]
+ (content model)
+ in
+ Mark.map
+ render
+ text
+
+
+monospace : Mark.Block (model -> Element msg)
+monospace =
+ Mark.Default.monospace
+ [ Element.padding 20
+ , Font.size 18
+ , Element.width Element.fill
+ , Border.color colors.gray
+ , Border.width 3
+ , Border.rounded 5
+ , Font.family
+ [ sourceCodePro
+ , Font.monospace
+ ]
+ , Element.scrollbarY
+ ]
+
+
+code : Mark.Block (a -> Element msg)
+code =
+ let
+ render string model =
+ string
+ |> String.split "\n"
+ |> List.indexedMap
+ (\n loc ->
+ Element.row [ Element.spacing 10 ]
+ [ Element.el
+ [ Font.color colors.gray
+ , Font.extraLight
+ , Element.width (Element.px 40)
+ , css "user-select" "none"
+ , css "-webkit-user-select" "none"
+ , css "-ms-user-select" "none"
+ , css "-webkit-touch-callout" "none"
+ , css "-o-user-select" "none"
+ , css "-moz-user-select" "none"
+ ]
+ (Element.text (String.fromInt n))
+ , Element.el
+ [ Element.width Element.fill
+ ]
+ (Element.text loc)
+ ]
+ )
+ |> Element.column
+ [ Element.padding 20
+ , Element.spacing 10
+ , Element.width Element.fill
+ , Border.color colors.gray
+ , Border.width 3
+ , Border.rounded 5
+ , Font.size 18
+ , Font.family
+ [ sourceCodePro
+ , Font.monospace
+ ]
+ , Element.scrollbarY
+ ]
+ in
+ Mark.block "Code"
+ render
+ Mark.multiline
+
+
+note : Mark.Block (model -> Element msg)
+note =
+ let
+ render elements model =
+ elements
+ |> List.map (\element -> element model)
+ |> Element.textColumn
+ [ Element.padding 20
+ , Element.spacing 10
+ , Element.width Element.fill
+ , Border.width 1
+ , Border.color colors.gray
+ , Font.color colors.gray
+ , Border.rounded 5
+ ]
+ in
+ Mark.block "Note"
+ render
+ (Mark.manyOf
+ [ paragraph
+ , header
+ , list
+ ]
+ )
+
+
+emphasize : Mark.Block (model -> Element msg)
+emphasize =
+ let
+ render element model =
+ model
+ |> element
+ |> Element.el
+ [ Element.spacing 30
+ , Font.bold
+ , Font.size 30
+ , Font.center
+ , Element.paddingXY 0 40
+ , Element.width Element.fill
+ ]
+ in
+ Mark.block "Emphasize"
+ render
+ paragraph
+
+
+image : Mark.Block (model -> Element msg)
+image =
+ Mark.Default.image
+ [ Element.width Element.fill
+ ]
+
+
+list : Mark.Block (model -> Element msg)
+list =
+ Mark.Default.list
+ { icon = Mark.Default.listIcon
+ , style =
+ \cursor ->
+ case List.length cursor of
+ 0 ->
+ -- top level element
+ [ Element.spacing 16 ]
+
+ 1 ->
+ [ Element.spacing 16 ]
+
+ 2 ->
+ [ Element.spacing 16 ]
+
+ _ ->
+ [ Element.spacing 8 ]
+ }
+ text
+
+
+text : Mark.Block (model -> List (Element msg))
+text =
+ let
+ defaultTextStyle =
+ Mark.Default.defaultTextStyle
+ in
+ Mark.Default.textWith
+ { defaultTextStyle
+ | inlines = defaultTextStyle.inlines ++ [ icon ]
+ }
+
+
+icon : Mark.Inline (model -> Element msg)
+icon =
+ Mark.inline "Icon"
+ (\name model ->
+ icons
+ |> Dict.get name
+ |> Maybe.map (FeatherIcons.toHtml [])
+ |> Maybe.map Element.html
+ |> Maybe.withDefault
+ (Element.text
+ ("Icon not found: '" ++ name ++ "'")
+ )
+ )
+ |> Mark.inlineString "name"
+
+
+
+-- Helpers
+
+
+css : String -> String -> Element.Attribute msg
+css property value =
+ Element.htmlAttribute
+ (Html.Attributes.style
+ property
+ value
+ )
+
+
+sourceCodePro : Font.Font
+sourceCodePro =
+ Font.external
+ { url = "https://fonts.googleapis.com/css?family=Source+Code+Pro:200,400&subset=latin-ext"
+ , name = "Source Code Pro"
+ }
+
+
+colors : { gray : Element.Color, maroon : Element.Color, pink : Element.Color }
+colors =
+ { maroon = Element.rgb 0.7 0 0
+ , gray = Element.rgb 0.8 0.8 0.8
+ , pink = Element.rgb 1 0.6 0.6
+ }Evolve Main program into SPA, split content into pages
Create scripts (develop and capture).
Temporarily enable static pages CI pipeline for all branches to test it before merging.
index 266c915..8359570 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,4 +4,5 @@
=.#*
=/index.html
=/public/
+/built/
=/node_modules/index 74b4280..aae0419 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -6,14 +6,10 @@ before_script:
=pages:
= stage: deploy
= script:
- - npx elm make src/Main.elm --output public/index.js
- - cp index.txt container.html public/
- ## FIXME: Make puppeteer work in the container
- #- cp container.html public/index.html
- - npx coffee src/capture.coffee > public/index.html
+ - scripts/capture
=
= artifacts:
= paths:
= - public
- only:
- - master
+ # only:
+ # - masterindex b30969d..fb384fe 100644
--- a/container.html
+++ b/container.html
@@ -5,10 +5,10 @@
= <title>Software Garden</title>
= </head>
= <body>
- <div id="app-container"></div>
- <script src="/index.js"></script>
+ <!-- <div id="app-container"></div> -->
+ <script src="/assets/index.js"></script>
= <script>
- Elm.Main.init({ node: document.querySelector("#app-container") })
+ Elm.Main.init()
= </script>
= </body>
=</html>new file mode 100644
index 0000000..3cd7b9e
--- /dev/null
+++ b/content/day-1.txt
@@ -0,0 +1,250 @@
+| Title
+ Day 1
+
+| Emphasize
+ Let's make a Dot!
+
+
+| Note
+ Today we are going to learn about
+
+ | List
+ -> Scalable Vector Graphics
+ -> cartesian Coordinates System
+ -> Layouts with ELM UI
+
+
+| Header
+ First Program!
+
+As mentioned before, programs are represented as text (called the /source code/). The source code is stored in files {Icon|name=file} and files are organized in directories {Icon|name=folder}.
+
+
+So the first step is to create a directory for our new program. Lets call it fpart.
+In the terminal type
+
+| Monospace
+ mkdir fpart/
+
+and then
+
+| Monospace
+ cd fpart/
+
+This creates a new directory and makes it the current one. Again, don't worry about the details.
+
+To easily create a new program, we can type
+
+| Monospace
+ elm init
+
+Then to create a file with source code, type
+
+| Monospace
+ atom src/Main.elm
+
+This command should open a new Atom window with empty text file.
+
+| Header
+ Main.elm
+
+| Code
+ module Main exposing (main)
+
+
+ import Html
+
+ main=
+ Html.text "Hello, Tree"
+
+Type the above in the editor and save the file.
+To see the program running type the following in the terminal
+
+| Monospace
+ elm reactor
+
+This command starts the Elm reactor, which will let you run your program in the web browser. Note that it won't give you the command line back - it will run as long as you don't stop it.
+
+
+| Emphasize
+ Voila!
+
+
+Open following address in the web browser
+
+| Emphasize
+ {Link|http://localhost:8000/src/Main.elm|url=http://localhost:8000/src/Main.elm}
+
+| Note
+ *TODO*: Make the link display // characters
+
+Note that the same address was printed by Elm reactor
+
+| Header
+ The problem
+
+We want to have a dot at the center of the screen, like this
+
+| DotAtTheCenterOfTheScreen
+
+Below is the complete code to draw a dot like this, but don't type it in your editor yet! We are going to recreate together it step by step.
+
+| Code
+ module Main exposing (main)
+
+ import Element
+ import Svg
+ import Svg.Attributes
+
+
+ main =
+ Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200"
+ ]
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ ]
+ []
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+
+
+We are going to use a technology called SVG (Scalable Vector Graphics). Its all about drawing shapes on the screen. Let's install an Elm Package to help us work with SVG. Stop the reactor running in terminal by pressing {Code|CTRL} + {Code|C} and type the following:
+
+| Monospace
+ elm install elm/svg
+
+Then start the reactor again:
+
+| Monospace
+ elm reactor
+
+| Note
+ You can press up arrow {Icon|name=arrow-up} on the keyboard to get to the previous commands.
+
+Reload the browser. You should see something like this:
+
+| Simplest
+
+Why is the dot at the corner?
+
+It's because its center is at point called {Code|origin} or {Code|(0, 0)}. But what does it mean ?
+
+* a picture of a vase on a table should be here *
+
+Move sliders to change the x and y coordinates of the dot. It's like moving a thing on a table by telling how far left, right, up or down it should go.
+
+| CartesianCoordinates
+
+To change where the dot is we can use cx and cy attributes of the circle
+
+* simplest with cx and cy should be here *
+
+* Module main exposing main should be here *
+
+Note that we have a complete program that draws a dot at the center of the screen , lets take a moment to understand it.
+
+Think about this ( * a picture of eggs and a box of eggs should be here * )
+
+| Note
+ An egg is a thing.
+ Six eggs are six things.
+ A box of six eggs is a thing.
+
+
+
+Now, let's make the SVG element fill the screen
+
+
+| FillTheScreen
+
+
+For that we will need a package called {Link|mdgriffith//elm-ui|url=https://package.elm-lang.org/packages/mdgriffith/elm-ui/latest/}
+
+Install it using terminal
+
+{Code|CTRL + C } to stop elm-reactor and type {Code|elm install mdgriffith//elm-ui}
+
+Then in {Code|src//Main.elm} add import {Code|import Element} and change {Code|Main} to look like this :
+
+Main = * the code of Element.layout should be here*
+
+Note that our scene fills the screen, it's time to put the dot at the center of the scene.
+
+* a picture of cx and cy with width and height should be here *
+
+If we would know the height and width of the screen , we could calculate its position as
+
+| Monospace
+ cx = width/2
+ cy = height/2
+
+There is a problem though, we don't know the height and width.
+☹️
+
+But we don't need to!
+
+*Scalable icon should be here*
+
+S for Scalable
+
+Instead of moving the dot, we can set the boundaries of the scene so that the dot is at the center using a property called Viewbox.
+
+| ViewBox
+
+Here is how the viewbox works
+
+* a picture of viewbox should be here*
+
+viewbox = 0 0 100 100 (left, top, width and height respectively)
+
+* a picture of periscope looking table should be here*
+
+The trick is to set the width and height to any arbitrary value (say 1000) and top and left to {Code|- width//2}!
+
+That way point (0,0) will always be right in the middle (the distance to the left and to the right is the same, likewise the distance to the top and bottom).
+
+First, lets see where the SVG boundaries are by giving it a background color
+
+
+| CenteredDot
+
+
+| Code
+ Svg [background "pink"]
+ [...
+ ]
+
+Give a color to the dot
+
+
+| List
+ - Explain SVG attributes ( svg elements take a list of attributes)
+ - Everyone can pick a color
+
+
+Multiple dots
+
+
+| List
+ -> Introduce a problem: We want to have two dots on the screen
+ -> Explain a list, and show there is already a list there (with one item)
+
+
+| Header
+ Do you see any other lists?
+
+
+| List
+ -> Element takes a list of children
+ -> Mention , how can we have a list with one or zero elements? Shopping list with one item. Empty spoon drawer( list of spoons)
+ -> Give your new dot a color and different coordinates
+ -> Show the code for 2 dots
+ -> Exercise: make 3 more dots (5 total)new file mode 100644
index 0000000..58f6798
--- /dev/null
+++ b/content/index.txt
@@ -0,0 +1,13 @@
+| Title
+ Software Garden
+
+| Emphasize
+ ⚘
+
+| Emphasize
+ A functional programming workshop for non-programmers
+
+| List
+ # {Link|Before the course begins|url=/preparation.html}
+ # {Link|Day 1 - Let's Make a Dot|url=/day-1.html}
+ # {Link|The rest - Work in progress|url=/rest.html}new file mode 100644
index 0000000..33c56c2
--- /dev/null
+++ b/content/preparation.txt
@@ -0,0 +1,103 @@
+| Title
+ Before the course begins
+
+| Note
+ This setup instructions are based on an assumption that you are using a Mac.
+
+ If you are using Linux or BSD, then you probably know how to install stuff.
+
+ If you are using anything else, then... well, good luck.
+
+ The rest of the instructions should work with any platform.
+
+| Header
+ Setup
+
+We will need
+
+| List
+ - a text editor (I use Atom)
+ - and the Elm programming language
+
+| Header
+ Installing Elm
+
+
+To install the Elm programming language, go to it's website:
+
+| Emphasize
+ {Link|elm-lang.org|url=http://elm-lang.org/}
+
+
+and follow the installation instructions.
+
+Some of the tools we use with Elm require Node.js. Go to the {Link|Node.js|url=https://nodejs.org/en/} website and install the current version (the green button on the right)
+
+We will need to use the terminal {Icon|name=terminal} a little bit. Don't be scared. It's easy.
+
+| Image
+ src = /assets/mac-launchpad-terminal.png
+ description = Here is a picture of terminal
+
+
+Mac Terminal app window
+You should see a window like this
+
+
+| Image
+ src = /assets/mac-terminal-window.png
+ description = A picture of Mac Terminal
+
+Type the following in the terminal.
+
+
+| Monospace
+ elm repl
+
+Note that the command line changes. You should see something like this
+
+| Monospace
+ ---- Elm 0.19.0 --------------------------------------------------
+ Read <https://elm-lang.org/0.19.0/repl> to learn more: exit, help
+ ------------------------------------------------------------------
+ >
+
+It's the Elm REPL (Read-Evaluate-Print Loop). Inside the REPL type.
+
+| Monospace
+ 2+2
+
+And expect to see
+
+| Monospace
+ ---- Elm 0.19.0 ----------------
+ Read <https://elm-lang.org/0.19.0/repl> to learn more: exit, help
+ --------------------------------
+ > 2 + 2
+ 4 : number
+ >
+
+Easy, huh?
+We will learn more about REPL later. For now type
+:exit to close it.
+The command line should get back to its initial state.
+
+| Header
+ Install Atom Text Editor
+
+Computer Programs are represented as text, so the text editor is the most fundamental tool of a programmer. There is a lot of good text editors, but to get you started, we will use Atom here.
+
+Go to {Link|atom.io|url=https://atom.io} and download the editor. After installation on Mac, open it and from Atom menu choose Install Shell Commands.
+
+One last thing we need is Elm support for Atom editor. You can install it by typing in the terminal:
+
+| Monospace
+ apm install language-elm
+
+APM is the Atom Package Manager, but don't pay much attention to it. We won't be using it again.
+We are all set.
+
+Got so far? Congratulations!
+
+| Emphasize
+ You are ready for {Link|Day 1|url=/day-1.html}!similarity index 74%
rename from index.txt
rename to content/rest.txt
index b241d7f..250aed1 100644
--- a/index.txt
+++ b/content/rest.txt
@@ -1,361 +1,8 @@
=| Title
- Software Garden
+ The rest
=
=| Emphasize
- ⚘
-
-| Emphasize
- A functional programming workshop for non-programmers
-
-
-
-| Header
- Before the course begins
-
-| Header
- Setup
-
-| Note
- This setup instructions are based on an assumption that you are using a Mac.
-
- If you are using Linux or BSD, then you probably know how to install stuff.
-
- If you are using anything else, then... well, good luck.
-
- The rest of the instructions should work with any platform.
-
-
-We will need
-
-| List
- - a text editor (I use Atom)
- - and the Elm programming language
-
-| Header
- Installing Elm
-
-
-To install the Elm programming language, go to it's website:
-
-| Emphasize
- {Link|elm-lang.org|url=http://elm-lang.org/}
-
-
-and follow the installation instructions.
-
-Some of the tools we use with Elm require Node.js. Go to the {Link|Node.js|url=https://nodejs.org/en/} website and install the current version (the green button on the right)
-
-We will need to use the terminal {Icon|name=terminal} a little bit. Don't be scared. It's easy.
-
-| Image
- src = /assets/mac-launchpad-terminal.png
- description = Here is a picture of terminal
-
-
-Mac Terminal app window
-You should see a window like this
-
-
-| Image
- src = /assets/mac-terminal-window.png
- description = A picture of Mac Terminal
-
-Type the following in the terminal.
-
-
-| Monospace
- elm repl
-
-Note that the command line changes. You should see something like this
-
-| Monospace
- ---- Elm 0.19.0 --------------------------------------------------
- Read <https://elm-lang.org/0.19.0/repl> to learn more: exit, help
- ------------------------------------------------------------------
- >
-
-It's the Elm REPL (Read-Evaluate-Print Loop). Inside the REPL type.
-
-| Monospace
- 2+2
-
-And expect to see
-
-| Monospace
- ---- Elm 0.19.0 ----------------
- Read <https://elm-lang.org/0.19.0/repl> to learn more: exit, help
- --------------------------------
- > 2 + 2
- 4 : number
- >
-
-Easy, huh?
-We will learn more about REPL later. For now type
-:exit to close it.
-The command line should get back to its initial state.
-
-| Header
- Install Atom Text Editor
-
-Computer Programs are represented as text, so the text editor is the most fundamental tool of a programmer. There is a lot of good text editors, but to get you started, we will use Atom here.
-
-Go to {Link|atom.io|url=https://atom.io} and download the editor. After installation on Mac, open it and from Atom menu choose Install Shell Commands.
-
-One last thing we need is Elm support for Atom editor. You can install it by typing in the terminal:
-
-| Monospace
- apm install language-elm
-
-APM is the Atom Package Manager, but don't pay much attention to it. We won't be using it again.
-We are all set.
-
-| Header
- Day 1 - let's make a dot!
-
-| Note
- Today we are going to learn about
-
- | List
- -> Scalable Vector Graphics
- -> cartesian Coordinates System
- -> Layouts with ELM UI
-
-
-| Header
- First Program!
-
-As mentioned before, programs are represented as text (called the /source code/).
-The source code is stored in files {Icon|name=file} and files are organized in directories {Icon|name=folder}.
-
-
-So the first step is to create a directory for our new program. Lets call it fpart.
-In the terminal type
-
-| Monospace
- mkdir fpart/
-
-and then
-
-| Monospace
- cd fpart/
-
-This creates a new directory and makes it the current one. Again, don't worry about the details.
-
-To easily create a new program, we can type
-
-| Monospace
- elm init
-
-Then to create a file with source code, type
-
-| Monospace
- atom src/Main.elm
-
-This command should open a new Atom window with empty text file.
-
-| Header
- Main.elm
-
-| Code
- module Main exposing (main)
-
-
- import Html
-
- main=
- Html.text "Hello, Tree"
-
-Type the above in the editor and save the file.
-To see the program running type the following in the terminal
-
-| Monospace
- elm reactor
-
-This command starts the Elm reactor, which will let you run your program in the web browser. Note that it won't give you the command line back - it will run as long as you don't stop it.
-
-
-| Emphasize
- Voila!
-
-
-Open following address in the web browser
-
-| Emphasize
- {Link|http://localhost:8000/src/Main.elm|url=http://localhost:8000/src/Main.elm}
-
-| Note
- *TODO*: Make the link display // characters
-
-Note that the same address was printed by Elm reactor
-
-| Header
- The problem
-
-We want to have a dot at the center of the screen, like this
-
-| DotAtTheCenterOfTheScreen
-
-Below is the complete code to draw a dot like this, but don't type it in your editor yet! We are going to recreate together it step by step.
-
-| Code
- module Main exposing (main)
-
- import Element
- import Svg
- import Svg.Attributes
-
-
- main =
- Svg.svg
- [ Svg.Attributes.viewBox "-100 -100 200 200"
- ]
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- ]
- []
- ]
- |> Element.html
- |> Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
- ]
-
-
-
-We are going to use a technology called SVG (Scalable Vector Graphics). Its all about drawing shapes on the screen. Let's install an Elm Package to help us work with SVG. Stop the reactor running in terminal by pressing {Code|CTRL} + {Code|C} and type the following:
-
-| Monospace
- elm install elm/svg
-
-Then start the reactor again:
-
-| Monospace
- elm reactor
-
-| Note
- You can press up arrow {Icon|name=arrow-up} on the keyboard to get to the previous commands.
-
-Reload the browser. You should see something like this:
-
-| Simplest
-
-Why is the dot at the corner?
-
-It's because its center is at point called {Code|origin} or {Code|(0, 0)}. But what does it mean ?
-
-* a picture of a vase on a table should be here *
-
-Move sliders to change the x and y coordinates of the dot. It's like moving a thing on a table by telling how far left, right, up or down it should go.
-
-| CartesianCoordinates
-
-To change where the dot is we can use cx and cy attributes of the circle
-
-* simplest with cx and cy should be here *
-
-* Module main exposing main should be here *
-
-Note that we have a complete program that draws a dot at the center of the screen , lets take a moment to understand it.
-
-Think about this ( * a picture of eggs and a box of eggs should be here * )
-
-| Note
- An egg is a thing.
- Six eggs are six things.
- A box of six eggs is a thing.
-
-
-
-Now, let's make the SVG element fill the screen
-
-
-| FillTheScreen
-
-
-For that we will need a package called {Link|mdgriffith//elm-ui|url=https://package.elm-lang.org/packages/mdgriffith/elm-ui/latest/}
-
-Install it using terminal
-
-{Code|CTRL + C } to stop elm-reactor and type {Code|elm install mdgriffith//elm-ui}
-
-Then in {Code|src//Main.elm} add import {Code|import Element} and change {Code|Main} to look like this :
-
-Main = * the code of Element.layout should be here*
-
-Note that our scene fills the screen, it's time to put the dot at the center of the scene.
-
-* a picture of cx and cy with width and height should be here *
-
-If we would know the height and width of the screen , we could calculate its position as
-
-| Monospace
- cx = width/2
- cy = height/2
-
-There is a problem though, we don't know the height and width.
-☹️
-
-But we don't need to!
-
-*Scalable icon should be here*
-
-S for Scalable
-
-Instead of moving the dot, we can set the boundaries of the scene so that the dot is at the center using a property called Viewbox.
-
-| ViewBox
-
-Here is how the viewbox works
-
-* a picture of viewbox should be here*
-
-viewbox = 0 0 100 100 (left, top, width and height respectively)
-
-* a picture of periscope looking table should be here*
-
-The trick is to set the width and height to any arbitrary value (say 1000) and top and left to {Code|- width//2}!
-
-That way point (0,0) will always be right in the middle (the distance to the left and to the right is the same, likewise the distance to the top and bottom).
-
-First, lets see where the SVG boundaries are by giving it a background color
-
-
-| CenteredDot
-
-
-| Code
- Svg [background "pink"]
- [...
- ]
-
-Give a color to the dot
-
-
-| List
- - Explain SVG attributes ( svg elements take a list of attributes)
- - Everyone can pick a color
-
-
-Multiple dots
-
-
-| List
- -> Introduce a problem: We want to have two dots on the screen
- -> Explain a list, and show there is already a list there (with one item)
-
-
-| Header
- Do you see any other lists?
-
-
-| List
- -> Element takes a list of children
- -> Mention , how can we have a list with one or zero elements? Shopping list with one item. Empty spoon drawer( list of spoons)
- -> Give your new dot a color and different coordinates
- -> Show the code for 2 dots
- -> Exercise: make 3 more dots (5 total)
+ This is a work in progress. We need to split this part into days.
=
=
=| Headerindex 2bb88f6..54d5902 100644
--- a/elm.json
+++ b/elm.json
@@ -13,6 +13,7 @@
= "elm/json": "1.1.2",
= "elm/parser": "1.1.0",
= "elm/svg": "1.0.1",
+ "elm/url": "1.0.0",
= "elm-community/basics-extra": "4.0.0",
= "elm-community/list-extra": "8.1.0",
= "elm-community/result-extra": "2.2.1",
@@ -28,7 +29,6 @@
= "elm/bytes": "1.0.7",
= "elm/file": "1.0.1",
= "elm/time": "1.0.0",
- "elm/url": "1.0.0",
= "elm/virtual-dom": "1.0.2",
= "ianmackenzie/elm-float-extra": "1.0.1",
= "ianmackenzie/elm-interval": "1.0.1",
@@ -39,4 +39,4 @@
= "direct": {},
= "indirect": {}
= }
-}
+}
\ No newline at end of fileindex 3125dbc..19dd474 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -32,11 +32,53 @@
= "uri-js": "^4.2.2"
= }
= },
+ "ansi-regex": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
+ },
+ "ansi-styles": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+ "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4="
+ },
+ "anymatch": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz",
+ "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==",
+ "requires": {
+ "micromatch": "^2.1.5",
+ "normalize-path": "^2.0.0"
+ }
+ },
+ "arr-diff": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz",
+ "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=",
+ "requires": {
+ "arr-flatten": "^1.0.1"
+ }
+ },
+ "arr-flatten": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
+ "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg=="
+ },
+ "arr-union": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz",
+ "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ="
+ },
= "array-flatten": {
= "version": "1.1.1",
= "resolved": "http://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
= "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
= },
+ "array-unique": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz",
+ "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM="
+ },
= "asn1": {
= "version": "0.2.4",
= "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
@@ -50,6 +92,16 @@
= "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
= "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
= },
+ "assign-symbols": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
+ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c="
+ },
+ "async-each": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz",
+ "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0="
+ },
= "async-limiter": {
= "version": "1.0.0",
= "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
@@ -60,6 +112,11 @@
= "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
= "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
= },
+ "atob": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
+ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg=="
+ },
= "aws-sign2": {
= "version": "0.7.0",
= "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
@@ -75,6 +132,66 @@
= "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
= "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
= },
+ "base": {
+ "version": "0.11.2",
+ "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
+ "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==",
+ "requires": {
+ "cache-base": "^1.0.1",
+ "class-utils": "^0.3.5",
+ "component-emitter": "^1.2.1",
+ "define-property": "^1.0.0",
+ "isobject": "^3.0.1",
+ "mixin-deep": "^1.2.0",
+ "pascalcase": "^0.1.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "requires": {
+ "is-descriptor": "^1.0.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ },
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
+ },
+ "kind-of": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
+ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA=="
+ }
+ }
+ },
= "bcrypt-pbkdf": {
= "version": "1.0.2",
= "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
@@ -92,6 +209,11 @@
= "chainsaw": "~0.1.0"
= }
= },
+ "binary-extensions": {
+ "version": "1.12.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.12.0.tgz",
+ "integrity": "sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg=="
+ },
= "binwrap": {
= "version": "0.1.4",
= "resolved": "https://registry.npmjs.org/binwrap/-/binwrap-0.1.4.tgz",
@@ -157,6 +279,16 @@
= "concat-map": "0.0.1"
= }
= },
+ "braces": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz",
+ "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=",
+ "requires": {
+ "expand-range": "^1.8.1",
+ "preserve": "^0.2.0",
+ "repeat-element": "^1.1.2"
+ }
+ },
= "buffer-from": {
= "version": "1.1.1",
= "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
@@ -172,6 +304,29 @@
= "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
= "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
= },
+ "cache-base": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
+ "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==",
+ "requires": {
+ "collection-visit": "^1.0.0",
+ "component-emitter": "^1.2.1",
+ "get-value": "^2.0.6",
+ "has-value": "^1.0.0",
+ "isobject": "^3.0.1",
+ "set-value": "^2.0.0",
+ "to-object-path": "^0.3.0",
+ "union-value": "^1.0.0",
+ "unset-value": "^1.0.0"
+ },
+ "dependencies": {
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
+ }
+ }
+ },
= "caseless": {
= "version": "0.12.0",
= "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
@@ -185,11 +340,92 @@
= "traverse": ">=0.3.0 <0.4"
= }
= },
+ "chalk": {
+ "version": "1.1.3",
+ "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+ "requires": {
+ "ansi-styles": "^2.2.1",
+ "escape-string-regexp": "^1.0.2",
+ "has-ansi": "^2.0.0",
+ "strip-ansi": "^3.0.0",
+ "supports-color": "^2.0.0"
+ }
+ },
+ "charenc": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
+ "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc="
+ },
+ "chokidar": {
+ "version": "1.6.0",
+ "resolved": "http://registry.npmjs.org/chokidar/-/chokidar-1.6.0.tgz",
+ "integrity": "sha1-kMMq1IApAddxPeUy3ChOlqY60Fg=",
+ "requires": {
+ "anymatch": "^1.3.0",
+ "async-each": "^1.0.0",
+ "fsevents": "^1.0.0",
+ "glob-parent": "^2.0.0",
+ "inherits": "^2.0.1",
+ "is-binary-path": "^1.0.0",
+ "is-glob": "^2.0.0",
+ "path-is-absolute": "^1.0.0",
+ "readdirp": "^2.0.0"
+ }
+ },
+ "class-utils": {
+ "version": "0.3.6",
+ "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
+ "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==",
+ "requires": {
+ "arr-union": "^3.1.0",
+ "define-property": "^0.2.5",
+ "isobject": "^3.0.0",
+ "static-extend": "^0.1.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ },
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
+ }
+ }
+ },
+ "cli-color": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.2.0.tgz",
+ "integrity": "sha1-OlrnT9drYmevZm5p4q+70B3vNNE=",
+ "requires": {
+ "ansi-regex": "^2.1.1",
+ "d": "1",
+ "es5-ext": "^0.10.12",
+ "es6-iterator": "2",
+ "memoizee": "^0.4.3",
+ "timers-ext": "0.1"
+ }
+ },
= "coffeescript": {
= "version": "2.3.2",
= "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-2.3.2.tgz",
= "integrity": "sha512-YObiFDoukx7qPBi/K0kUKyntEZDfBQiqs/DbrR1xzASKOBjGT7auD85/DiPeRr9k++lRj7l3uA9TNMLfyfcD/Q=="
= },
+ "collection-visit": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
+ "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=",
+ "requires": {
+ "map-visit": "^1.0.0",
+ "object-visit": "^1.0.0"
+ }
+ },
= "combined-stream": {
= "version": "1.0.7",
= "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz",
@@ -198,6 +434,16 @@
= "delayed-stream": "~1.0.0"
= }
= },
+ "commander": {
+ "version": "2.17.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz",
+ "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg=="
+ },
+ "component-emitter": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
+ "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY="
+ },
= "concat-map": {
= "version": "0.0.1",
= "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -214,6 +460,11 @@
= "typedarray": "^0.0.6"
= }
= },
+ "connect-pushstate": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/connect-pushstate/-/connect-pushstate-1.1.0.tgz",
+ "integrity": "sha1-vKsiQnHEOWBKD7D2FMCl9WPojiQ="
+ },
= "content-disposition": {
= "version": "0.5.2",
= "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
@@ -234,11 +485,39 @@
= "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
= "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
= },
+ "copy-descriptor": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
+ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40="
+ },
= "core-util-is": {
= "version": "1.0.2",
= "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
= "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
= },
+ "cross-spawn": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.0.1.tgz",
+ "integrity": "sha1-o7uzAtsil8vqPATt82lB9GE6o5k=",
+ "requires": {
+ "lru-cache": "^4.0.1",
+ "shebang-command": "^1.2.0",
+ "which": "^1.2.9"
+ }
+ },
+ "crypt": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
+ "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs="
+ },
+ "d": {
+ "version": "1.0.0",
+ "resolved": "http://registry.npmjs.org/d/-/d-1.0.0.tgz",
+ "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=",
+ "requires": {
+ "es5-ext": "^0.10.9"
+ }
+ },
= "dashdash": {
= "version": "1.14.1",
= "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
@@ -255,6 +534,67 @@
= "ms": "^2.1.1"
= }
= },
+ "decode-uri-component": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
+ "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU="
+ },
+ "default-gateway": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-2.7.2.tgz",
+ "integrity": "sha512-lAc4i9QJR0YHSDFdzeBQKfZ1SRDG3hsJNEkrpcZa8QhBfidLAilT60BDEIVUUGqosFp425KOgB3uYqcnQrWafQ==",
+ "requires": {
+ "execa": "^0.10.0",
+ "ip-regex": "^2.1.0"
+ }
+ },
+ "define-property": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
+ "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
+ "requires": {
+ "is-descriptor": "^1.0.2",
+ "isobject": "^3.0.1"
+ },
+ "dependencies": {
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ },
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
+ },
+ "kind-of": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
+ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA=="
+ }
+ }
+ },
= "delayed-stream": {
= "version": "1.0.0",
= "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -292,11 +632,86 @@
= "binwrap": "0.1.4"
= }
= },
+ "elm-live": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/elm-live/-/elm-live-3.4.0.tgz",
+ "integrity": "sha512-t/pdd6yvFsft7cOysiV0ilmlE6TgCzDL6JQq+lG0TyL5ZjjTAdaYt5evJdyMliRfGY768zXKKQll4clM4VQyCA==",
+ "requires": {
+ "chalk": "^1.1.1",
+ "chokidar": "1.6.0",
+ "commander": "2.17.1",
+ "cross-spawn": "5.0.1",
+ "elm-serve": "0.4.0"
+ }
+ },
+ "elm-serve": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/elm-serve/-/elm-serve-0.4.0.tgz",
+ "integrity": "sha512-NYXzzaJT/zw8v7jzDWGXuvX3/soj+5NTLHxX0n/T6DICbmyDj8kO7rlI2wSKs9UTNjXhZ7quFQEKcgcf/SZksw==",
+ "requires": {
+ "cli-color": "1.2.0",
+ "commander": "2.9.0",
+ "connect-pushstate": "1.1.0",
+ "finalhandler": "1.1.1",
+ "http-proxy": "1.17.0",
+ "internal-ip": "3.0.1",
+ "minimist": "1.2.0",
+ "opn": "5.3.0",
+ "pem": "1.13.2",
+ "serve-static": "1.13.2",
+ "supervisor": "0.12.0",
+ "url-parse": "1.4.3",
+ "ws": "5.2.0"
+ },
+ "dependencies": {
+ "commander": {
+ "version": "2.9.0",
+ "resolved": "http://registry.npmjs.org/commander/-/commander-2.9.0.tgz",
+ "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=",
+ "requires": {
+ "graceful-readlink": ">= 1.0.0"
+ }
+ },
+ "minimist": {
+ "version": "1.2.0",
+ "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
+ },
+ "ws": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.0.tgz",
+ "integrity": "sha512-c18dMeW+PEQdDFzkhDsnBAlS4Z8KGStBQQUcQ5mf7Nf689jyGk0594L+i9RaQuf4gog6SvWLJorz2NfSaqxZ7w==",
+ "requires": {
+ "async-limiter": "~1.0.0"
+ }
+ }
+ }
+ },
= "encodeurl": {
= "version": "1.0.2",
= "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
= "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
= },
+ "es5-ext": {
+ "version": "0.10.46",
+ "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.46.tgz",
+ "integrity": "sha512-24XxRvJXNFwEMpJb3nOkiRJKRoupmjYmOPVlI65Qy2SrtxwOTB+g6ODjBKOtwEHbYrhWRty9xxOWLNdClT2djw==",
+ "requires": {
+ "es6-iterator": "~2.0.3",
+ "es6-symbol": "~3.1.1",
+ "next-tick": "1"
+ }
+ },
+ "es6-iterator": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
+ "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=",
+ "requires": {
+ "d": "1",
+ "es5-ext": "^0.10.35",
+ "es6-symbol": "^3.1.1"
+ }
+ },
= "es6-promise": {
= "version": "4.2.5",
= "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.5.tgz",
@@ -310,16 +725,99 @@
= "es6-promise": "^4.0.3"
= }
= },
+ "es6-symbol": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz",
+ "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=",
+ "requires": {
+ "d": "1",
+ "es5-ext": "~0.10.14"
+ }
+ },
+ "es6-weak-map": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz",
+ "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=",
+ "requires": {
+ "d": "1",
+ "es5-ext": "^0.10.14",
+ "es6-iterator": "^2.0.1",
+ "es6-symbol": "^3.1.1"
+ }
+ },
= "escape-html": {
= "version": "1.0.3",
= "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
= "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
= },
+ "escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
+ },
= "etag": {
= "version": "1.8.1",
= "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
= "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
= },
+ "event-emitter": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
+ "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=",
+ "requires": {
+ "d": "1",
+ "es5-ext": "~0.10.14"
+ }
+ },
+ "eventemitter3": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz",
+ "integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA=="
+ },
+ "execa": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz",
+ "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==",
+ "requires": {
+ "cross-spawn": "^6.0.0",
+ "get-stream": "^3.0.0",
+ "is-stream": "^1.1.0",
+ "npm-run-path": "^2.0.0",
+ "p-finally": "^1.0.0",
+ "signal-exit": "^3.0.0",
+ "strip-eof": "^1.0.0"
+ },
+ "dependencies": {
+ "cross-spawn": {
+ "version": "6.0.5",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
+ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
+ "requires": {
+ "nice-try": "^1.0.4",
+ "path-key": "^2.0.1",
+ "semver": "^5.5.0",
+ "shebang-command": "^1.2.0",
+ "which": "^1.2.9"
+ }
+ }
+ }
+ },
+ "expand-brackets": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz",
+ "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=",
+ "requires": {
+ "is-posix-bracket": "^0.1.0"
+ }
+ },
+ "expand-range": {
+ "version": "1.8.2",
+ "resolved": "http://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz",
+ "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=",
+ "requires": {
+ "fill-range": "^2.1.0"
+ }
+ },
= "express": {
= "version": "4.16.4",
= "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz",
@@ -377,6 +875,33 @@
= "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
= "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
= },
+ "extend-shallow": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
+ "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
+ "requires": {
+ "assign-symbols": "^1.0.0",
+ "is-extendable": "^1.0.1"
+ },
+ "dependencies": {
+ "is-extendable": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+ "requires": {
+ "is-plain-object": "^2.0.4"
+ }
+ }
+ }
+ },
+ "extglob": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz",
+ "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=",
+ "requires": {
+ "is-extglob": "^1.0.0"
+ }
+ },
= "extract-zip": {
= "version": "1.6.7",
= "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz",
@@ -426,6 +951,23 @@
= "pend": "~1.2.0"
= }
= },
+ "filename-regex": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz",
+ "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY="
+ },
+ "fill-range": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz",
+ "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==",
+ "requires": {
+ "is-number": "^2.1.0",
+ "isobject": "^2.0.0",
+ "randomatic": "^3.0.0",
+ "repeat-element": "^1.1.2",
+ "repeat-string": "^1.5.2"
+ }
+ },
= "finalhandler": {
= "version": "1.1.1",
= "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz",
@@ -455,6 +997,42 @@
= }
= }
= },
+ "follow-redirects": {
+ "version": "1.5.10",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
+ "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
+ "requires": {
+ "debug": "=3.1.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ }
+ }
+ },
+ "for-in": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
+ "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA="
+ },
+ "for-own": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz",
+ "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=",
+ "requires": {
+ "for-in": "^1.0.1"
+ }
+ },
= "forever-agent": {
= "version": "0.6.1",
= "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
@@ -475,6 +1053,14 @@
= "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
= "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
= },
+ "fragment-cache": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
+ "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=",
+ "requires": {
+ "map-cache": "^0.2.2"
+ }
+ },
= "fresh": {
= "version": "0.5.2",
= "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
@@ -485,57 +1071,613 @@
= "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
= "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
= },
- "fstream": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz",
- "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=",
- "requires": {
- "graceful-fs": "^4.1.2",
- "inherits": "~2.0.0",
- "mkdirp": ">=0.5 0",
- "rimraf": "2"
- }
- },
- "getpass": {
- "version": "0.1.7",
- "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
- "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
- "requires": {
- "assert-plus": "^1.0.0"
- }
- },
- "glob": {
- "version": "7.1.3",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
- "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
- "requires": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.0.4",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- }
- },
- "graceful-fs": {
- "version": "4.1.15",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz",
- "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA=="
- },
- "har-schema": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
- "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
- },
- "har-validator": {
- "version": "5.1.3",
- "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
- "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
+ "fsevents": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz",
+ "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==",
+ "optional": true,
= "requires": {
- "ajv": "^6.5.5",
- "har-schema": "^2.0.0"
- }
- },
+ "nan": "^2.9.2",
+ "node-pre-gyp": "^0.10.0"
+ },
+ "dependencies": {
+ "abbrev": {
+ "version": "1.1.1",
+ "bundled": true,
+ "optional": true
+ },
+ "ansi-regex": {
+ "version": "2.1.1",
+ "bundled": true
+ },
+ "aproba": {
+ "version": "1.2.0",
+ "bundled": true,
+ "optional": true
+ },
+ "are-we-there-yet": {
+ "version": "1.1.4",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "delegates": "^1.0.0",
+ "readable-stream": "^2.0.6"
+ }
+ },
+ "balanced-match": {
+ "version": "1.0.0",
+ "bundled": true
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "bundled": true,
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "chownr": {
+ "version": "1.0.1",
+ "bundled": true,
+ "optional": true
+ },
+ "code-point-at": {
+ "version": "1.1.0",
+ "bundled": true
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "bundled": true
+ },
+ "console-control-strings": {
+ "version": "1.1.0",
+ "bundled": true
+ },
+ "core-util-is": {
+ "version": "1.0.2",
+ "bundled": true,
+ "optional": true
+ },
+ "debug": {
+ "version": "2.6.9",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "deep-extend": {
+ "version": "0.5.1",
+ "bundled": true,
+ "optional": true
+ },
+ "delegates": {
+ "version": "1.0.0",
+ "bundled": true,
+ "optional": true
+ },
+ "detect-libc": {
+ "version": "1.0.3",
+ "bundled": true,
+ "optional": true
+ },
+ "fs-minipass": {
+ "version": "1.2.5",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "minipass": "^2.2.1"
+ }
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "bundled": true,
+ "optional": true
+ },
+ "gauge": {
+ "version": "2.7.4",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "aproba": "^1.0.3",
+ "console-control-strings": "^1.0.0",
+ "has-unicode": "^2.0.0",
+ "object-assign": "^4.1.0",
+ "signal-exit": "^3.0.0",
+ "string-width": "^1.0.1",
+ "strip-ansi": "^3.0.1",
+ "wide-align": "^1.1.0"
+ }
+ },
+ "glob": {
+ "version": "7.1.2",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "has-unicode": {
+ "version": "2.0.1",
+ "bundled": true,
+ "optional": true
+ },
+ "iconv-lite": {
+ "version": "0.4.21",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "safer-buffer": "^2.1.0"
+ }
+ },
+ "ignore-walk": {
+ "version": "3.0.1",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "minimatch": "^3.0.4"
+ }
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "inherits": {
+ "version": "2.0.3",
+ "bundled": true
+ },
+ "ini": {
+ "version": "1.3.5",
+ "bundled": true,
+ "optional": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "1.0.0",
+ "bundled": true,
+ "requires": {
+ "number-is-nan": "^1.0.0"
+ }
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "bundled": true,
+ "optional": true
+ },
+ "minimatch": {
+ "version": "3.0.4",
+ "bundled": true,
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
+ "minimist": {
+ "version": "0.0.8",
+ "bundled": true
+ },
+ "minipass": {
+ "version": "2.2.4",
+ "bundled": true,
+ "requires": {
+ "safe-buffer": "^5.1.1",
+ "yallist": "^3.0.0"
+ }
+ },
+ "minizlib": {
+ "version": "1.1.0",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "minipass": "^2.2.1"
+ }
+ },
+ "mkdirp": {
+ "version": "0.5.1",
+ "bundled": true,
+ "requires": {
+ "minimist": "0.0.8"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "bundled": true,
+ "optional": true
+ },
+ "needle": {
+ "version": "2.2.0",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "debug": "^2.1.2",
+ "iconv-lite": "^0.4.4",
+ "sax": "^1.2.4"
+ }
+ },
+ "node-pre-gyp": {
+ "version": "0.10.0",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "detect-libc": "^1.0.2",
+ "mkdirp": "^0.5.1",
+ "needle": "^2.2.0",
+ "nopt": "^4.0.1",
+ "npm-packlist": "^1.1.6",
+ "npmlog": "^4.0.2",
+ "rc": "^1.1.7",
+ "rimraf": "^2.6.1",
+ "semver": "^5.3.0",
+ "tar": "^4"
+ }
+ },
+ "nopt": {
+ "version": "4.0.1",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "abbrev": "1",
+ "osenv": "^0.1.4"
+ }
+ },
+ "npm-bundled": {
+ "version": "1.0.3",
+ "bundled": true,
+ "optional": true
+ },
+ "npm-packlist": {
+ "version": "1.1.10",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "ignore-walk": "^3.0.1",
+ "npm-bundled": "^1.0.1"
+ }
+ },
+ "npmlog": {
+ "version": "4.1.2",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "are-we-there-yet": "~1.1.2",
+ "console-control-strings": "~1.1.0",
+ "gauge": "~2.7.3",
+ "set-blocking": "~2.0.0"
+ }
+ },
+ "number-is-nan": {
+ "version": "1.0.1",
+ "bundled": true
+ },
+ "object-assign": {
+ "version": "4.1.1",
+ "bundled": true,
+ "optional": true
+ },
+ "once": {
+ "version": "1.4.0",
+ "bundled": true,
+ "requires": {
+ "wrappy": "1"
+ }
+ },
+ "os-homedir": {
+ "version": "1.0.2",
+ "bundled": true,
+ "optional": true
+ },
+ "os-tmpdir": {
+ "version": "1.0.2",
+ "bundled": true,
+ "optional": true
+ },
+ "osenv": {
+ "version": "0.1.5",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "os-homedir": "^1.0.0",
+ "os-tmpdir": "^1.0.0"
+ }
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "bundled": true,
+ "optional": true
+ },
+ "process-nextick-args": {
+ "version": "2.0.0",
+ "bundled": true,
+ "optional": true
+ },
+ "rc": {
+ "version": "1.2.7",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "deep-extend": "^0.5.1",
+ "ini": "~1.3.0",
+ "minimist": "^1.2.0",
+ "strip-json-comments": "~2.0.1"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "1.2.0",
+ "bundled": true,
+ "optional": true
+ }
+ }
+ },
+ "readable-stream": {
+ "version": "2.3.6",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "rimraf": {
+ "version": "2.6.2",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "glob": "^7.0.5"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.1.1",
+ "bundled": true
+ },
+ "safer-buffer": {
+ "version": "2.1.2",
+ "bundled": true,
+ "optional": true
+ },
+ "sax": {
+ "version": "1.2.4",
+ "bundled": true,
+ "optional": true
+ },
+ "semver": {
+ "version": "5.5.0",
+ "bundled": true,
+ "optional": true
+ },
+ "set-blocking": {
+ "version": "2.0.0",
+ "bundled": true,
+ "optional": true
+ },
+ "signal-exit": {
+ "version": "3.0.2",
+ "bundled": true,
+ "optional": true
+ },
+ "string-width": {
+ "version": "1.0.2",
+ "bundled": true,
+ "requires": {
+ "code-point-at": "^1.0.0",
+ "is-fullwidth-code-point": "^1.0.0",
+ "strip-ansi": "^3.0.0"
+ }
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "3.0.1",
+ "bundled": true,
+ "requires": {
+ "ansi-regex": "^2.0.0"
+ }
+ },
+ "strip-json-comments": {
+ "version": "2.0.1",
+ "bundled": true,
+ "optional": true
+ },
+ "tar": {
+ "version": "4.4.1",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "chownr": "^1.0.1",
+ "fs-minipass": "^1.2.5",
+ "minipass": "^2.2.4",
+ "minizlib": "^1.1.0",
+ "mkdirp": "^0.5.0",
+ "safe-buffer": "^5.1.1",
+ "yallist": "^3.0.2"
+ }
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "bundled": true,
+ "optional": true
+ },
+ "wide-align": {
+ "version": "1.1.2",
+ "bundled": true,
+ "optional": true,
+ "requires": {
+ "string-width": "^1.0.2"
+ }
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "bundled": true
+ },
+ "yallist": {
+ "version": "3.0.2",
+ "bundled": true
+ }
+ }
+ },
+ "fstream": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz",
+ "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=",
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "inherits": "~2.0.0",
+ "mkdirp": ">=0.5 0",
+ "rimraf": "2"
+ }
+ },
+ "get-stream": {
+ "version": "3.0.0",
+ "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
+ "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
+ },
+ "get-value": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
+ "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg="
+ },
+ "getpass": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+ "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+ "requires": {
+ "assert-plus": "^1.0.0"
+ }
+ },
+ "glob": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
+ "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "glob-base": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz",
+ "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=",
+ "requires": {
+ "glob-parent": "^2.0.0",
+ "is-glob": "^2.0.0"
+ }
+ },
+ "glob-parent": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz",
+ "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=",
+ "requires": {
+ "is-glob": "^2.0.0"
+ }
+ },
+ "graceful-fs": {
+ "version": "4.1.15",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz",
+ "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA=="
+ },
+ "graceful-readlink": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
+ "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU="
+ },
+ "har-schema": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
+ "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
+ },
+ "har-validator": {
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
+ "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
+ "requires": {
+ "ajv": "^6.5.5",
+ "har-schema": "^2.0.0"
+ }
+ },
+ "has-ansi": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
+ "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
+ "requires": {
+ "ansi-regex": "^2.0.0"
+ }
+ },
+ "has-value": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
+ "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=",
+ "requires": {
+ "get-value": "^2.0.6",
+ "has-values": "^1.0.0",
+ "isobject": "^3.0.0"
+ },
+ "dependencies": {
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
+ }
+ }
+ },
+ "has-values": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz",
+ "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=",
+ "requires": {
+ "is-number": "^3.0.0",
+ "kind-of": "^4.0.0"
+ },
+ "dependencies": {
+ "is-number": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
+ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "kind-of": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
+ "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
= "http-errors": {
= "version": "1.6.3",
= "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
@@ -547,6 +1689,16 @@
= "statuses": ">= 1.4.0 < 2"
= }
= },
+ "http-proxy": {
+ "version": "1.17.0",
+ "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz",
+ "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==",
+ "requires": {
+ "eventemitter3": "^3.0.0",
+ "follow-redirects": "^1.0.0",
+ "requires-port": "^1.0.0"
+ }
+ },
= "http-signature": {
= "version": "1.2.0",
= "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
@@ -598,21 +1750,178 @@
= "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
= "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
= },
+ "internal-ip": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-3.0.1.tgz",
+ "integrity": "sha512-NXXgESC2nNVtU+pqmC9e6R8B1GpKxzsAQhffvh5AL79qKnodd+L7tnEQmTiUAVngqLalPbSqRA7XGIEL5nCd0Q==",
+ "requires": {
+ "default-gateway": "^2.6.0",
+ "ipaddr.js": "^1.5.2"
+ }
+ },
+ "ip-regex": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz",
+ "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk="
+ },
= "ipaddr.js": {
= "version": "1.8.0",
= "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz",
= "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4="
= },
+ "is-accessor-descriptor": {
+ "version": "0.1.6",
+ "resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
+ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
+ "requires": {
+ "kind-of": "^3.0.2"
+ }
+ },
+ "is-binary-path": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
+ "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=",
+ "requires": {
+ "binary-extensions": "^1.0.0"
+ }
+ },
+ "is-buffer": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
+ },
+ "is-data-descriptor": {
+ "version": "0.1.4",
+ "resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
+ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
+ "requires": {
+ "kind-of": "^3.0.2"
+ }
+ },
+ "is-descriptor": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
+ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
+ "requires": {
+ "is-accessor-descriptor": "^0.1.6",
+ "is-data-descriptor": "^0.1.4",
+ "kind-of": "^5.0.0"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
+ "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw=="
+ }
+ }
+ },
+ "is-dotfile": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz",
+ "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE="
+ },
+ "is-equal-shallow": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz",
+ "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=",
+ "requires": {
+ "is-primitive": "^2.0.0"
+ }
+ },
+ "is-extendable": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
+ "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik="
+ },
+ "is-extglob": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
+ "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA="
+ },
+ "is-glob": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
+ "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
+ "requires": {
+ "is-extglob": "^1.0.0"
+ }
+ },
+ "is-number": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz",
+ "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=",
+ "requires": {
+ "kind-of": "^3.0.2"
+ }
+ },
+ "is-plain-object": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
+ "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
+ "requires": {
+ "isobject": "^3.0.1"
+ },
+ "dependencies": {
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
+ }
+ }
+ },
+ "is-posix-bracket": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz",
+ "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q="
+ },
+ "is-primitive": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz",
+ "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU="
+ },
+ "is-promise": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
+ "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o="
+ },
+ "is-stream": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+ "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
+ },
= "is-typedarray": {
= "version": "1.0.0",
= "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
= "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
= },
+ "is-windows": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
+ "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA=="
+ },
+ "is-wsl": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz",
+ "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0="
+ },
= "isarray": {
= "version": "1.0.0",
= "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
= "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
= },
+ "isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
+ },
+ "isobject": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
+ "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
+ "requires": {
+ "isarray": "1.0.0"
+ }
+ },
= "isstream": {
= "version": "0.1.2",
= "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
@@ -649,11 +1958,49 @@
= "verror": "1.10.0"
= }
= },
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ },
= "lodash": {
= "version": "4.17.11",
= "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
= "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
= },
+ "lru-cache": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
+ "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
+ "requires": {
+ "pseudomap": "^1.0.2",
+ "yallist": "^2.1.2"
+ }
+ },
+ "lru-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz",
+ "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=",
+ "requires": {
+ "es5-ext": "~0.10.2"
+ }
+ },
+ "map-cache": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz",
+ "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8="
+ },
+ "map-visit": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
+ "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=",
+ "requires": {
+ "object-visit": "^1.0.0"
+ }
+ },
= "match-stream": {
= "version": "0.0.2",
= "resolved": "https://registry.npmjs.org/match-stream/-/match-stream-0.0.2.tgz",
@@ -686,11 +2033,41 @@
= }
= }
= },
+ "math-random": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.1.tgz",
+ "integrity": "sha1-izqsWIuKZuSXXjzepn97sylgH6w="
+ },
+ "md5": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz",
+ "integrity": "sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=",
+ "requires": {
+ "charenc": "~0.0.1",
+ "crypt": "~0.0.1",
+ "is-buffer": "~1.1.1"
+ }
+ },
= "media-typer": {
= "version": "0.3.0",
= "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
= "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
= },
+ "memoizee": {
+ "version": "0.4.14",
+ "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz",
+ "integrity": "sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg==",
+ "requires": {
+ "d": "1",
+ "es5-ext": "^0.10.45",
+ "es6-weak-map": "^2.0.2",
+ "event-emitter": "^0.3.5",
+ "is-promise": "^2.1",
+ "lru-queue": "0.1",
+ "next-tick": "1",
+ "timers-ext": "^0.1.5"
+ }
+ },
= "merge-descriptors": {
= "version": "1.0.1",
= "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
@@ -701,6 +2078,26 @@
= "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
= "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
= },
+ "micromatch": {
+ "version": "2.3.11",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz",
+ "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=",
+ "requires": {
+ "arr-diff": "^2.0.0",
+ "array-unique": "^0.2.1",
+ "braces": "^1.8.2",
+ "expand-brackets": "^0.1.4",
+ "extglob": "^0.3.1",
+ "filename-regex": "^2.0.0",
+ "is-extglob": "^1.0.0",
+ "is-glob": "^2.0.1",
+ "kind-of": "^3.0.2",
+ "normalize-path": "^2.0.1",
+ "object.omit": "^2.0.0",
+ "parse-glob": "^3.0.4",
+ "regex-cache": "^0.4.2"
+ }
+ },
= "mime": {
= "version": "2.4.0",
= "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz",
@@ -732,6 +2129,25 @@
= "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
= "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
= },
+ "mixin-deep": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz",
+ "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==",
+ "requires": {
+ "for-in": "^1.0.2",
+ "is-extendable": "^1.0.1"
+ },
+ "dependencies": {
+ "is-extendable": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+ "requires": {
+ "is-plain-object": "^2.0.4"
+ }
+ }
+ }
+ },
= "mkdirp": {
= "version": "0.5.1",
= "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
@@ -745,6 +2161,47 @@
= "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
= "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
= },
+ "nan": {
+ "version": "2.11.1",
+ "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.1.tgz",
+ "integrity": "sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA==",
+ "optional": true
+ },
+ "nanomatch": {
+ "version": "1.2.13",
+ "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
+ "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==",
+ "requires": {
+ "arr-diff": "^4.0.0",
+ "array-unique": "^0.3.2",
+ "define-property": "^2.0.2",
+ "extend-shallow": "^3.0.2",
+ "fragment-cache": "^0.2.1",
+ "is-windows": "^1.0.2",
+ "kind-of": "^6.0.2",
+ "object.pick": "^1.3.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "arr-diff": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
+ "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA="
+ },
+ "array-unique": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
+ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg="
+ },
+ "kind-of": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
+ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA=="
+ }
+ }
+ },
= "natives": {
= "version": "1.1.6",
= "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.6.tgz",
@@ -755,11 +2212,96 @@
= "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
= "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk="
= },
+ "next-tick": {
+ "version": "1.0.0",
+ "resolved": "http://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
+ "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
+ },
+ "nice-try": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
+ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ=="
+ },
+ "normalize-path": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
+ "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
+ "requires": {
+ "remove-trailing-separator": "^1.0.1"
+ }
+ },
+ "npm-run-path": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
+ "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
+ "requires": {
+ "path-key": "^2.0.0"
+ }
+ },
= "oauth-sign": {
= "version": "0.9.0",
= "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
= "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
= },
+ "object-copy": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
+ "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=",
+ "requires": {
+ "copy-descriptor": "^0.1.0",
+ "define-property": "^0.2.5",
+ "kind-of": "^3.0.3"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ }
+ }
+ },
+ "object-visit": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
+ "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=",
+ "requires": {
+ "isobject": "^3.0.0"
+ },
+ "dependencies": {
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
+ }
+ }
+ },
+ "object.omit": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz",
+ "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=",
+ "requires": {
+ "for-own": "^0.1.4",
+ "is-extendable": "^0.1.1"
+ }
+ },
+ "object.pick": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
+ "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=",
+ "requires": {
+ "isobject": "^3.0.1"
+ },
+ "dependencies": {
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
+ }
+ }
+ },
= "on-finished": {
= "version": "2.3.0",
= "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
@@ -776,26 +2318,83 @@
= "wrappy": "1"
= }
= },
+ "opn": {
+ "version": "5.3.0",
+ "resolved": "http://registry.npmjs.org/opn/-/opn-5.3.0.tgz",
+ "integrity": "sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==",
+ "requires": {
+ "is-wsl": "^1.1.0"
+ }
+ },
+ "os-tmpdir": {
+ "version": "1.0.2",
+ "resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+ "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
+ },
= "over": {
= "version": "0.0.5",
= "resolved": "https://registry.npmjs.org/over/-/over-0.0.5.tgz",
= "integrity": "sha1-8phS5w/X4l82DgE6jsRMgq7bVwg="
= },
+ "p-finally": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
+ "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4="
+ },
+ "parse-glob": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz",
+ "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=",
+ "requires": {
+ "glob-base": "^0.3.0",
+ "is-dotfile": "^1.0.0",
+ "is-extglob": "^1.0.0",
+ "is-glob": "^2.0.0"
+ }
+ },
= "parseurl": {
= "version": "1.3.2",
= "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz",
= "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M="
= },
+ "pascalcase": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
+ "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ="
+ },
= "path-is-absolute": {
= "version": "1.0.1",
= "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
= "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
= },
+ "path-key": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
+ "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A="
+ },
= "path-to-regexp": {
= "version": "0.1.7",
= "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
= "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
= },
+ "pem": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/pem/-/pem-1.13.2.tgz",
+ "integrity": "sha512-MPJWuEb/r6AG+GpZi2JnfNtGAZDeL/8+ERKwXEWRuST5i+4lq/Uy36B352OWIUSPQGH+HR1HEDcIDi+8cKxXNg==",
+ "requires": {
+ "es6-promisify": "^6.0.0",
+ "md5": "^2.2.1",
+ "os-tmpdir": "^1.0.1",
+ "which": "^1.3.1"
+ },
+ "dependencies": {
+ "es6-promisify": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-6.0.1.tgz",
+ "integrity": "sha512-J3ZkwbEnnO+fGAKrjVpeUAnZshAdfZvbhQpqfIH9kSAspReRC4nJnu8ewm55b4y9ElyeuhCTzJD0XiH8Tsbhlw=="
+ }
+ }
+ },
= "pend": {
= "version": "1.2.0",
= "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
@@ -806,6 +2405,16 @@
= "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
= "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
= },
+ "posix-character-classes": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
+ "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs="
+ },
+ "preserve": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz",
+ "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks="
+ },
= "process-nextick-args": {
= "version": "2.0.0",
= "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
@@ -830,6 +2439,11 @@
= "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz",
= "integrity": "sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4="
= },
+ "pseudomap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
+ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
+ },
= "psl": {
= "version": "1.1.29",
= "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz",
@@ -894,6 +2508,33 @@
= "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
= "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
= },
+ "querystringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.0.tgz",
+ "integrity": "sha512-sluvZZ1YiTLD5jsqZcDmFyV2EwToyXZBfpoVOmktMmW+VEnhgakFHnasVph65fOjGPTWN0Nw3+XQaSeMayr0kg=="
+ },
+ "randomatic": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz",
+ "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==",
+ "requires": {
+ "is-number": "^4.0.0",
+ "kind-of": "^6.0.0",
+ "math-random": "^1.0.1"
+ },
+ "dependencies": {
+ "is-number": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz",
+ "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ=="
+ },
+ "kind-of": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
+ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA=="
+ }
+ }
+ },
= "range-parser": {
= "version": "1.2.0",
= "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
@@ -924,6 +2565,311 @@
= "util-deprecate": "~1.0.1"
= }
= },
+ "readdirp": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz",
+ "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==",
+ "requires": {
+ "graceful-fs": "^4.1.11",
+ "micromatch": "^3.1.10",
+ "readable-stream": "^2.0.2"
+ },
+ "dependencies": {
+ "arr-diff": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
+ "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA="
+ },
+ "array-unique": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
+ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg="
+ },
+ "braces": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
+ "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
+ "requires": {
+ "arr-flatten": "^1.1.0",
+ "array-unique": "^0.3.2",
+ "extend-shallow": "^2.0.1",
+ "fill-range": "^4.0.0",
+ "isobject": "^3.0.1",
+ "repeat-element": "^1.1.2",
+ "snapdragon": "^0.8.1",
+ "snapdragon-node": "^2.0.1",
+ "split-string": "^3.0.2",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "expand-brackets": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
+ "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
+ "requires": {
+ "debug": "^2.3.3",
+ "define-property": "^0.2.5",
+ "extend-shallow": "^2.0.1",
+ "posix-character-classes": "^0.1.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "0.1.6",
+ "resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
+ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "is-data-descriptor": {
+ "version": "0.1.4",
+ "resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
+ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "is-descriptor": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
+ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
+ "requires": {
+ "is-accessor-descriptor": "^0.1.6",
+ "is-data-descriptor": "^0.1.4",
+ "kind-of": "^5.0.0"
+ }
+ },
+ "kind-of": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
+ "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw=="
+ }
+ }
+ },
+ "extglob": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
+ "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
+ "requires": {
+ "array-unique": "^0.3.2",
+ "define-property": "^1.0.0",
+ "expand-brackets": "^2.1.4",
+ "extend-shallow": "^2.0.1",
+ "fragment-cache": "^0.2.1",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "requires": {
+ "is-descriptor": "^1.0.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "fill-range": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
+ "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
+ "requires": {
+ "extend-shallow": "^2.0.1",
+ "is-number": "^3.0.0",
+ "repeat-string": "^1.6.1",
+ "to-regex-range": "^2.1.0"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ },
+ "is-number": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
+ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
+ },
+ "kind-of": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
+ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA=="
+ },
+ "micromatch": {
+ "version": "3.1.10",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
+ "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
+ "requires": {
+ "arr-diff": "^4.0.0",
+ "array-unique": "^0.3.2",
+ "braces": "^2.3.1",
+ "define-property": "^2.0.2",
+ "extend-shallow": "^3.0.2",
+ "extglob": "^2.0.4",
+ "fragment-cache": "^0.2.1",
+ "kind-of": "^6.0.2",
+ "nanomatch": "^1.2.9",
+ "object.pick": "^1.3.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.2"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ }
+ }
+ },
+ "regex-cache": {
+ "version": "0.4.4",
+ "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz",
+ "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==",
+ "requires": {
+ "is-equal-shallow": "^0.1.3"
+ }
+ },
+ "regex-not": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
+ "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==",
+ "requires": {
+ "extend-shallow": "^3.0.2",
+ "safe-regex": "^1.1.0"
+ }
+ },
+ "remove-trailing-separator": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
+ "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8="
+ },
+ "repeat-element": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz",
+ "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g=="
+ },
+ "repeat-string": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
+ "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc="
+ },
= "request": {
= "version": "2.88.0",
= "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
@@ -970,6 +2916,21 @@
= "lodash": "^4.13.1"
= }
= },
+ "requires-port": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+ "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
+ },
+ "resolve-url": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
+ "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo="
+ },
+ "ret": {
+ "version": "0.1.15",
+ "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
+ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg=="
+ },
= "rimraf": {
= "version": "2.6.2",
= "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
@@ -983,11 +2944,24 @@
= "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
= "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
= },
+ "safe-regex": {
+ "version": "1.1.0",
+ "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
+ "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
+ "requires": {
+ "ret": "~0.1.10"
+ }
+ },
= "safer-buffer": {
= "version": "2.1.2",
= "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
= "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
= },
+ "semver": {
+ "version": "5.6.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
+ "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg=="
+ },
= "send": {
= "version": "0.16.2",
= "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz",
@@ -1039,6 +3013,27 @@
= "send": "0.16.2"
= }
= },
+ "set-value": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz",
+ "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==",
+ "requires": {
+ "extend-shallow": "^2.0.1",
+ "is-extendable": "^0.1.1",
+ "is-plain-object": "^2.0.3",
+ "split-string": "^3.0.1"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
= "setimmediate": {
= "version": "1.0.5",
= "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
@@ -1049,6 +3044,24 @@
= "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
= "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ=="
= },
+ "shebang-command": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
+ "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
+ "requires": {
+ "shebang-regex": "^1.0.0"
+ }
+ },
+ "shebang-regex": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
+ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM="
+ },
+ "signal-exit": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
+ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
+ },
= "slice-stream": {
= "version": "1.0.0",
= "resolved": "https://registry.npmjs.org/slice-stream/-/slice-stream-1.0.0.tgz",
@@ -1080,6 +3093,146 @@
= }
= }
= },
+ "snapdragon": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
+ "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==",
+ "requires": {
+ "base": "^0.11.1",
+ "debug": "^2.2.0",
+ "define-property": "^0.2.5",
+ "extend-shallow": "^2.0.1",
+ "map-cache": "^0.2.2",
+ "source-map": "^0.5.6",
+ "source-map-resolve": "^0.5.0",
+ "use": "^3.1.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ }
+ }
+ },
+ "snapdragon-node": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz",
+ "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==",
+ "requires": {
+ "define-property": "^1.0.0",
+ "isobject": "^3.0.0",
+ "snapdragon-util": "^3.0.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "requires": {
+ "is-descriptor": "^1.0.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ },
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
+ },
+ "kind-of": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
+ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA=="
+ }
+ }
+ },
+ "snapdragon-util": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz",
+ "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==",
+ "requires": {
+ "kind-of": "^3.2.0"
+ }
+ },
+ "source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
+ },
+ "source-map-resolve": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz",
+ "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==",
+ "requires": {
+ "atob": "^2.1.1",
+ "decode-uri-component": "^0.2.0",
+ "resolve-url": "^0.2.1",
+ "source-map-url": "^0.4.0",
+ "urix": "^0.1.0"
+ }
+ },
+ "source-map-url": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz",
+ "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM="
+ },
+ "split-string": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
+ "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==",
+ "requires": {
+ "extend-shallow": "^3.0.0"
+ }
+ },
= "sshpk": {
= "version": "1.15.2",
= "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz",
@@ -1096,6 +3249,25 @@
= "tweetnacl": "~0.14.0"
= }
= },
+ "static-extend": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
+ "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=",
+ "requires": {
+ "define-property": "^0.2.5",
+ "object-copy": "^0.1.0"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ }
+ }
+ },
= "statuses": {
= "version": "1.4.0",
= "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
@@ -1114,6 +3286,29 @@
= "safe-buffer": "~5.1.0"
= }
= },
+ "strip-ansi": {
+ "version": "3.0.1",
+ "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+ "requires": {
+ "ansi-regex": "^2.0.0"
+ }
+ },
+ "strip-eof": {
+ "version": "1.0.0",
+ "resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
+ "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8="
+ },
+ "supervisor": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/supervisor/-/supervisor-0.12.0.tgz",
+ "integrity": "sha1-3n5jNwFbKRhRwQ81OMSn8EkX7ME="
+ },
+ "supports-color": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+ "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
+ },
= "tar": {
= "version": "2.2.1",
= "resolved": "http://registry.npmjs.org/tar/-/tar-2.2.1.tgz",
@@ -1124,6 +3319,53 @@
= "inherits": "2"
= }
= },
+ "timers-ext": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz",
+ "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==",
+ "requires": {
+ "es5-ext": "~0.10.46",
+ "next-tick": "1"
+ }
+ },
+ "to-object-path": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz",
+ "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=",
+ "requires": {
+ "kind-of": "^3.0.2"
+ }
+ },
+ "to-regex": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz",
+ "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==",
+ "requires": {
+ "define-property": "^2.0.2",
+ "extend-shallow": "^3.0.2",
+ "regex-not": "^1.0.2",
+ "safe-regex": "^1.1.0"
+ }
+ },
+ "to-regex-range": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
+ "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
+ "requires": {
+ "is-number": "^3.0.0",
+ "repeat-string": "^1.6.1"
+ },
+ "dependencies": {
+ "is-number": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
+ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
+ "requires": {
+ "kind-of": "^3.0.2"
+ }
+ }
+ }
+ },
= "tough-cookie": {
= "version": "2.4.3",
= "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
@@ -1172,11 +3414,84 @@
= "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
= "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
= },
+ "union-value": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz",
+ "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=",
+ "requires": {
+ "arr-union": "^3.1.0",
+ "get-value": "^2.0.6",
+ "is-extendable": "^0.1.1",
+ "set-value": "^0.4.3"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ },
+ "set-value": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz",
+ "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=",
+ "requires": {
+ "extend-shallow": "^2.0.1",
+ "is-extendable": "^0.1.1",
+ "is-plain-object": "^2.0.1",
+ "to-object-path": "^0.3.0"
+ }
+ }
+ }
+ },
= "unpipe": {
= "version": "1.0.0",
= "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
= "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
= },
+ "unset-value": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
+ "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=",
+ "requires": {
+ "has-value": "^0.3.1",
+ "isobject": "^3.0.0"
+ },
+ "dependencies": {
+ "has-value": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
+ "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=",
+ "requires": {
+ "get-value": "^2.0.3",
+ "has-values": "^0.1.4",
+ "isobject": "^2.0.0"
+ },
+ "dependencies": {
+ "isobject": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
+ "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
+ "requires": {
+ "isarray": "1.0.0"
+ }
+ }
+ }
+ },
+ "has-values": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz",
+ "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E="
+ },
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
+ }
+ }
+ },
= "unzip": {
= "version": "0.1.11",
= "resolved": "https://registry.npmjs.org/unzip/-/unzip-0.1.11.tgz",
@@ -1240,6 +3555,25 @@
= "punycode": "^2.1.0"
= }
= },
+ "urix": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
+ "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI="
+ },
+ "url-parse": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.3.tgz",
+ "integrity": "sha512-rh+KuAW36YKo0vClhQzLLveoj8FwPJNu65xLb7Mrt+eZht0IPT0IXgSv8gcMegZ6NvjJUALf6Mf25POlMwD1Fw==",
+ "requires": {
+ "querystringify": "^2.0.0",
+ "requires-port": "^1.0.0"
+ }
+ },
+ "use": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
+ "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ=="
+ },
= "util-deprecate": {
= "version": "1.0.2",
= "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -1270,6 +3604,14 @@
= "extsprintf": "^1.2.0"
= }
= },
+ "which": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+ "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+ "requires": {
+ "isexe": "^2.0.0"
+ }
+ },
= "wrappy": {
= "version": "1.0.2",
= "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
@@ -1283,6 +3625,11 @@
= "async-limiter": "~1.0.0"
= }
= },
+ "yallist": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
+ "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI="
+ },
= "yauzl": {
= "version": "2.4.1",
= "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz",index 3432d08..0ce2b31 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,7 @@
= "dependencies": {
= "coffeescript": "^2.3.2",
= "elm": "^0.19.0-bugfix2",
+ "elm-live": "^3.4.0",
= "express": "^4.16.4",
= "puppeteer": "^1.11.0"
= }new file mode 100755
index 0000000..2952960
--- /dev/null
+++ b/scripts/capture
@@ -0,0 +1,14 @@
+#! /usr/bin/env bash -euo pipefail
+
+rm -rf built/*
+rm -rf public/*
+
+npx elm make src/Main.elm --output built/index.js
+
+cp -r content/ public/content/
+cp -r assets/ public/assets/
+cp -r built/* public/assets/
+
+mkdir -p built/captured/
+npx coffee src/capture.coffee
+cp -r built/captured/* public/new file mode 100755
index 0000000..b8fc9a4
--- /dev/null
+++ b/scripts/develop
@@ -0,0 +1,3 @@
+#! /usr/bin/env bash -euo pipefail
+
+npx elm-live src/Main.elm --pushstate -- --debugindex 0a89a20..d542d78 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -8,6 +8,7 @@ It fetches the Elm Markup file at /index.txt and renders it. There are a number
=
=import Basics.Extra exposing (curry)
=import Browser
+import Browser.Navigation as Navigation
=import BrowserWindow
=import CartesianCoordinates
=import CenteredDot
@@ -36,20 +37,24 @@ import Parser.Advanced
=import PolarCoordinates
=import Result.Extra as Result
=import RosetteTypedTransformations
+import Routes exposing (Route)
=import Simplest
=import Spiral
=import Transformations
=import Tree
+import Url exposing (Url)
=import ViewBox
=
=
=main : Program Flags Model Msg
=main =
- Browser.document
+ Browser.application
= { init = init
= , view = view
= , update = update
= , subscriptions = subscriptions
+ , onUrlChange = UrlChanged
+ , onUrlRequest = UrlRequested
= }
=
=
@@ -58,7 +63,9 @@ type alias Flags =
=
=
=type alias Model =
- { markup : Maybe String
+ { url : Url
+ , key : Navigation.Key
+ , markup : Maybe String
= , counter : Counter.Model
= , transformations : Transformations.Model
= , nestedTransformations : NestedTransformations.Model
@@ -70,7 +77,9 @@ type alias Model =
=
=
=type Msg
- = DocumentFetched (Result Http.Error String)
+ = UrlRequested Browser.UrlRequest
+ | UrlChanged Url
+ | ContentFetched (Result Http.Error String)
= | CounterMsg Counter.Msg
= | TransformationsMsg Transformations.Msg
= | NestedTransformationsMsg NestedTransformations.Msg
@@ -81,9 +90,11 @@ type Msg
= | ViewBoxMsg ViewBox.Msg
=
=
-init : Flags -> ( Model, Cmd Msg )
-init flags =
- ( { markup = Nothing
+init : Flags -> Url -> Navigation.Key -> ( Model, Cmd Msg )
+init flags url key =
+ ( { url = url
+ , key = key
+ , markup = Nothing
= , counter = Counter.init
= , transformations = Transformations.init
= , nestedTransformations = NestedTransformations.init
@@ -92,10 +103,9 @@ init flags =
= , tree = Nothing
= , viewBox = ViewBox.init
= }
- , Http.get
- { url = "/index.txt"
- , expect = Http.expectString DocumentFetched
- }
+ , url
+ |> Routes.parse
+ |> loadContent
= )
=
=
@@ -264,13 +274,33 @@ view model =
=
=update : Msg -> Model -> ( Model, Cmd Msg )
=update msg model =
- case msg of
- DocumentFetched (Ok markup) ->
+ case Debug.log "update" msg of
+ UrlRequested (Browser.Internal url) ->
+ ( model
+ , url
+ |> Debug.log "New URL"
+ |> Url.toString
+ |> Navigation.pushUrl model.key
+ )
+
+ UrlRequested (Browser.External url) ->
+ ( model
+ , Navigation.load url
+ )
+
+ UrlChanged url ->
+ ( { model | url = url }
+ , url
+ |> Routes.parse
+ |> loadContent
+ )
+
+ ContentFetched (Ok markup) ->
= ( { model | markup = Just markup }
= , Cmd.none
= )
=
- DocumentFetched (Err markup) ->
+ ContentFetched (Err err) ->
= ( { model | markup = Nothing }
= , Cmd.none
= )
@@ -331,6 +361,25 @@ subscriptions model =
= |> Sub.map TreeMsg
=
=
+loadContent : Route -> Cmd Msg
+loadContent route =
+ case Debug.log "Route" route of
+ Routes.Home ->
+ Http.get
+ { url = "/content/index.txt"
+ , expect = Http.expectString ContentFetched
+ }
+
+ Routes.Content base ->
+ Http.get
+ { url = "/content/" ++ base ++ ".txt"
+ , expect = Http.expectString ContentFetched
+ }
+
+ Routes.NotFound ->
+ Cmd.none
+
+
=type alias DeadEnd =
= Parser.Advanced.DeadEnd Mark.Context Mark.Problem
=new file mode 100644
index 0000000..f76545a
--- /dev/null
+++ b/src/Routes.elm
@@ -0,0 +1,37 @@
+module Routes exposing
+ ( Route(..)
+ , parse
+ , parser
+ )
+
+import Url exposing (Url)
+import Url.Parser as Parser exposing (..)
+
+
+type Route
+ = Home
+ | Content String
+ | NotFound
+
+
+parser : Parser (Route -> b) b
+parser =
+ Parser.oneOf
+ [ Parser.map Home Parser.top
+ , Parser.custom "Content"
+ (\path ->
+ case String.split "." path of
+ [ base, "html" ] ->
+ Just (Content base)
+
+ _ ->
+ Nothing
+ )
+ ]
+
+
+parse : Url -> Route
+parse url =
+ url
+ |> Parser.parse parser
+ |> Maybe.withDefault NotFoundindex 8764395..15eb598 100644
--- a/src/capture.coffee
+++ b/src/capture.coffee
@@ -1,9 +1,14 @@
=puppeteer = require "puppeteer"
=express = require "express"
+{ promises: fs } = require "fs"
=
=do () =>
= app = express ``
= app.use express.static "public/"
+
+ app.get '*', (req, res) ->
+ res.sendFile "container.html", root: process.cwd ``
+
= server = app.listen 8000
=
= browser = await puppeteer.launch
@@ -14,12 +19,34 @@ do () =>
=
= page = await browser.newPage ``
=
- await page.goto "http://localhost:8000/container.html",
- waitUntil: "networkidle0"
+ # TODO: Scan the directory and make this list dynamic!
+ for base in [
+ "index",
+ "preparation",
+ "day-1",
+ "rest"
+ ]
+ await do (base) ->
+ url = "http://localhost:8000/#{base}.html"
+ path = "built/captured/#{base}.html"
+ console.log "#{url} -> #{path}"
+
+
+ await page.goto url, waitUntil: "networkidle2"
+ console.log "Ready"
=
- html = await page.content ``
+ html =
+ (await page.content ``).replace "</body>", """
+ <script src="/assets/index.js"></script>
+ <script>
+ Elm.Main.init()
+ </script>
+ </body>
+ """
=
- console.log html
+ output = await fs.open path, "w"
+ await output.writeFile html
+ await output.close ``
=
= await browser.close ``
= server.close ``Upgrade CI configuration to use alekzonder/puppeteer:1.8.0-0
index aae0419..8ae0061 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,4 +1,4 @@
-image: alekzonder/puppeteer:1.0.0
+image: alekzonder/puppeteer:1.8.0-0
=
=before_script:
= - npm installUse set instead of command line options to bash in scripts
The previous syntax doesn't work in the Docker container.
index 2952960..f710c3c 100755
--- a/scripts/capture
+++ b/scripts/capture
@@ -1,4 +1,6 @@
-#! /usr/bin/env bash -euo pipefail
+#! /usr/bin/env bash
+
+set -euo pipefail
=
=rm -rf built/*
=rm -rf public/*index b8fc9a4..568384b 100755
--- a/scripts/develop
+++ b/scripts/develop
@@ -1,3 +1,5 @@
-#! /usr/bin/env bash -euo pipefail
+#! /usr/bin/env bash
+
+set -euo pipefail
=
=npx elm-live src/Main.elm --pushstate -- --debugMake capture script ensure that public/ and built/ directories exists
index f710c3c..4236c08 100755
--- a/scripts/capture
+++ b/scripts/capture
@@ -2,11 +2,15 @@
=
=set -euo pipefail
=
-rm -rf built/*
-rm -rf public/*
+rm -rf built/
+rm -rf public/
+mkdir -p built/
+mkdir -p public/
+
=
=npx elm make src/Main.elm --output built/index.js
=
+
=cp -r content/ public/content/
=cp -r assets/ public/assets/
=cp -r built/* public/assets/Use classic (callback) API of FS module in capture.coffee
The Promise API is not available in Node v. 8, which is used in alekzonder/puppeteer Docker image.
index 15eb598..46eae85 100644
--- a/src/capture.coffee
+++ b/src/capture.coffee
@@ -1,6 +1,6 @@
=puppeteer = require "puppeteer"
=express = require "express"
-{ promises: fs } = require "fs"
+fs = require "fs"
=
=do () =>
= app = express ``
@@ -29,11 +29,8 @@ do () =>
= await do (base) ->
= url = "http://localhost:8000/#{base}.html"
= path = "built/captured/#{base}.html"
- console.log "#{url} -> #{path}"
-
=
= await page.goto url, waitUntil: "networkidle2"
- console.log "Ready"
=
= html =
= (await page.content ``).replace "</body>", """
@@ -44,9 +41,10 @@ do () =>
= </body>
= """
=
- output = await fs.open path, "w"
- await output.writeFile html
- await output.close ``
+ await new Promise (resolve, reject) ->
+ fs.writeFile path, html, (error) ->
+ if error then return reject error
+ do resolve
=
= await browser.close ``
= server.close ``
Commits: 7
Update Elm Markup to 2.0.2
Fixe slider length and remove Debug.toString from CartesianCordinates.
index c770fd4..2bb88f6 100644
--- a/elm.json
+++ b/elm.json
@@ -11,21 +11,22 @@
= "elm/html": "1.0.0",
= "elm/http": "2.0.0",
= "elm/json": "1.1.2",
+ "elm/parser": "1.1.0",
= "elm/svg": "1.0.1",
= "elm-community/basics-extra": "4.0.0",
= "elm-community/list-extra": "8.1.0",
= "elm-community/result-extra": "2.2.1",
= "elm-explorations/markdown": "1.0.0",
+ "feathericons/elm-feather": "1.2.0",
= "ianmackenzie/elm-geometry": "1.2.1",
= "ianmackenzie/elm-geometry-svg": "1.0.2",
- "mdgriffith/elm-markup": "1.0.0",
+ "mdgriffith/elm-markup": "2.0.2",
= "mdgriffith/elm-ui": "1.1.0",
= "turboMaCk/any-dict": "1.0.1"
= },
= "indirect": {
= "elm/bytes": "1.0.7",
= "elm/file": "1.0.1",
- "elm/parser": "1.1.0",
= "elm/time": "1.0.0",
= "elm/url": "1.0.0",
= "elm/virtual-dom": "1.0.2",index 138e422..2afd30d 100644
--- a/index.txt
+++ b/index.txt
@@ -1,19 +1,21 @@
-| title
+| Title
= Software Garden
=
+| Emphasize
= ⚘
=
+| Emphasize
= A functional programming workshop for non-programmers
=
=
=
-| header
+| Header
= Before the course begins
=
-| header
+| Header
= Setup
=
-| note
+| Note
= This setup instructions are based on an assumption that you are using a Mac.
=
= If you are using Linux or BSD, then you probably know how to install stuff.
@@ -25,114 +27,97 @@
=
=We will need
=
-| list
+| List
= - a text editor (I use Atom)
= - and the Elm programming language
=
-
-| header
+| Header
= Installing Elm
=
=To install the Elm programming language, go to it's website:
=
-| emphasize
- [elm-lang.org](http://elm-lang.org/)
-
+| Emphasize
+ {Link|elm-lang.org|url=http://elm-lang.org/}
=
=and follow the installation instructions.
=
-
-
-
-
-| header
+| Header
= To do:
=
=Steps to reproduce the tree:
=
-Make a dot
-
- Centered (Elm UI, viewBox, cartesian coordinates)
-
-Make a line
-
- Play with transformations (union types)
-
-Gradients
-
-Multiple lines
-
- Rosettes (different kinds)
-
-Groups and transformations
-
- Spiral (recursion)
-
-Tree
-
-
-| header
+| List
+ - Make a dot (Centered (Elm UI, viewBox, cartesian coordinates))
+ - Make a line (Play with transformations (union types))
+ - Gradients
+ - Multiple lines
+ - Rosettes (different kinds)
+ - Groups and transformations
+ - Spiral (recursion)
+ - Tree
+
+| Header
= Before the course begins
=
=Setup the development environment
=
-| list
+| List
= - Elm
= - Node.js
=
=
-| counter
+| Counter
=
=Embedded simplest example:
=
-| simplest
+| Simplest
=
=Embedded fill the screen example:
=
-| fill-the-screen
+| FillTheScreen
=
=Embedded centered dot example:
=
-| centered-dot
+| CenteredDot
=
=Embedded line example:
=
-| line
+| Line
=
=Embedded gradient example:
=
-| gradient
+| Gradient
=
=Embedded cartesian coordinates example:
=
-| cartesian-coordinates
+| CartesianCoordinates
=
=Embedded polar coordinates example:
=
-| polar-coordinates
+| PolarCoordinates
=
=Embeded transformations example:
=
-| transformations
+| Transformations
=
=Embedded nested-transformations example:
=
-| nested-transformations
+| NestedTransformations
=
=Embedded rosette example:
=
-| rosette
+| Rosette
=
=Embedded spiral example:
=
-| spiral
+| Spiral
=
=Embedded tree example:
=
-| tree
+| Tree
=
=Embedded view box example:
=
-| view-box
+| ViewBox
=
=Finito!index 7441ca2..fc29be3 100644
--- a/src/CartesianCoordinates.elm
+++ b/src/CartesianCoordinates.elm
@@ -91,12 +91,18 @@ ui model =
= , fontSize "0.05"
= , dominantBaseline "central"
= ]
- [ text <| Debug.toString ( model.x, model.y ) ]
+ [ text <|
+ "("
+ ++ String.fromFloat model.x
+ ++ ", "
+ ++ String.fromFloat model.y
+ ++ ")"
+ ]
= ]
= , Input.slider
= [ Element.behindContent
= (Element.el
- [ Element.width (Element.maximum 600 Element.fill)
+ [ Element.width Element.fill
= , Element.height (Element.px 2)
= , Element.centerY
= , Background.color <| Element.rgb 0.7 0.7 0.7index 29bbd46..31f964d 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -1,16 +1,25 @@
=module Main exposing (main)
=
+{-| This program is responsible for rendering the website.
+
+It fetches the Elm Markup file at /index.txt and renders it. There are a number of embeded programs in the markup.
+
+-}
+
+import Basics.Extra exposing (curry)
=import Browser
=import BrowserWindow
=import CartesianCoordinates
=import CenteredDot
=import Counter
+import Dict
=import Element exposing (Element)
=import Element.Background as Background
=import Element.Border as Border
=import Element.Events
=import Element.Font as Font
=import Element.Input as Input
+import FeatherIcons exposing (icons)
=import FillTheScreen
=import Gradient
=import Html exposing (Html)
@@ -18,8 +27,10 @@ import Html.Attributes
=import Http
=import Line
=import Mark
-import Mark.Custom
+import Mark.Default
=import NestedTransformations
+import Parser
+import Parser.Advanced
=import PolarCoordinates
=import Result.Extra as Result
=import RosetteTypedTransformations
@@ -30,6 +41,7 @@ import Tree
=import ViewBox
=
=
+main : Program Flags Model Msg
=main =
= Browser.element
= { init = init
@@ -88,6 +100,7 @@ init flags =
=view : Model -> Html Msg
=view model =
= let
+ content : Element Msg
= content =
= case model.markup of
= Nothing ->
@@ -96,13 +109,14 @@ view model =
=
= Just markup ->
= markup
- |> Mark.parseWith options
- |> Result.map (\fn -> fn model)
- |> Result.extract problemsElement
-
- problemsElement problems =
- problems
- |> List.map problemElement
+ |> Mark.parse document
+ |> Result.map (\render -> render model)
+ |> Result.extract deadEndsElement
+
+ deadEndsElement : List DeadEnd -> Element Msg
+ deadEndsElement deadEnds =
+ deadEnds
+ |> List.map deadEndElement
= |> Element.column
= [ Element.centerX
= , Element.centerY
@@ -111,13 +125,117 @@ view model =
= , Element.spacing 20
= ]
=
- problemElement { row, col, problem } =
- Debug.toString problem
- ++ " at "
- ++ String.fromInt row
- ++ ":"
- ++ String.fromInt col
- |> Element.text
+ deadEndElement : DeadEnd -> Element Msg
+ deadEndElement { row, col, problem, contextStack } =
+ let
+ message =
+ case problem of
+ Mark.ExpectingIndent level ->
+ "Expecting indentation level "
+ ++ String.fromInt level
+
+ Mark.InlineStart ->
+ "Inline start"
+
+ Mark.InlineEnd ->
+ "Inline end"
+
+ Mark.BlockStart ->
+ "Block start"
+
+ Mark.Expecting what ->
+ "Expecting " ++ what
+
+ Mark.ExpectingBlockName name ->
+ "Expecting a block name " ++ name
+
+ Mark.ExpectingInlineName name ->
+ "Expecting an inline name" ++ name
+
+ Mark.ExpectingFieldName name ->
+ "Expecting a field name" ++ name
+
+ Mark.NonMatchingFields { expecting, found } ->
+ "Fields don't match. Expecting one of [ "
+ ++ String.join ", " expecting
+ ++ " ], but got [ "
+ ++ String.join ", " found
+ ++ " ]"
+
+ Mark.MissingField name ->
+ "A field is missing from the record: "
+ ++ name
+
+ Mark.RecordError ->
+ "A record error"
+
+ Mark.Escape ->
+ "Escape"
+
+ Mark.EscapedChar ->
+ "Escaped character"
+
+ Mark.Newline ->
+ "Expecting newline"
+
+ Mark.Space ->
+ "Space"
+
+ Mark.End ->
+ "End"
+
+ Mark.Integer ->
+ "Integer"
+
+ Mark.FloatingPoint ->
+ "Floating point"
+
+ Mark.InvalidNumber ->
+ "Invalid number"
+
+ Mark.UnexpectedEnd ->
+ "Unexpected end"
+
+ Mark.CantStartTextWithSpace ->
+ "Can't start text with a space"
+
+ Mark.UnclosedStyles styles ->
+ let
+ styleName : Mark.Style -> String
+ styleName style =
+ case style of
+ Mark.Bold ->
+ "Bold"
+
+ Mark.Italic ->
+ "Italic"
+
+ Mark.Strike ->
+ "Strike"
+ in
+ "Unclosed styles: [ "
+ ++ (styles
+ |> List.map styleName
+ |> String.join ", "
+ )
+ ++ "]"
+
+ Mark.UnexpectedField { found, options, recordName } ->
+ "Unexpected field: "
+ ++ found
+ ++ ". Valid fields for "
+ ++ recordName
+ ++ " are: [ "
+ ++ String.join ", " options
+ ++ " ]"
+ in
+ Element.text
+ (message
+ ++ " at "
+ ++ String.fromInt row
+ ++ ":"
+ ++ String.fromInt col
+ )
= in
= Element.layout
= [ -- Below is a hack that enables static site generation
@@ -128,7 +246,7 @@ view model =
=
=update : Msg -> Model -> ( Model, Cmd Msg )
=update msg model =
- case Debug.log "update" msg of
+ case msg of
= DocumentFetched (Ok markup) ->
= ( { model | markup = Just markup }
= , Cmd.none
@@ -195,363 +313,438 @@ subscriptions model =
= |> Sub.map TreeMsg
=
=
-type alias Styling =
- Mark.Styling Msg
-
-
-type alias Options =
- Mark.Options Model Styling Msg
-
-
-colors =
- { maroon = Element.rgb 0.7 0 0
- , gray = Element.rgb 0.8 0.8 0.8
- , pink = Element.rgb 1 0.6 0.6
- }
+type alias DeadEnd =
+ Parser.Advanced.DeadEnd Mark.Context Mark.Problem
=
=
-options : Options
-options =
+document : Mark.Document (Model -> Element Msg)
+document =
= let
- default =
- Mark.default
-
- emphasizeBlock : Mark.Custom.Block Model Styling Msg
- emphasizeBlock =
- Mark.Custom.section "emphasize" emphasizeView
+ title =
+ Mark.Default.title
+ [ Element.width Element.fill
+ , Element.paddingXY 0 32
+ , Font.center
+ , Font.size 86
+ , Font.extraBold
+ ]
+ text
=
- emphasizeView : List (Element Msg) -> Styling -> Model -> Element Msg
- emphasizeView elements style model =
- elements
- |> Element.column
- [ Element.spacing 30
- , Font.bold
- , Font.size 30
- , Font.center
- , Element.paddingXY 0 40
- ]
+ header =
+ Mark.Default.header
+ [ Font.size 24
+ , Font.underline
+ , Element.paddingXY 0 24
+ ]
+ text
=
- titleBlock : Mark.Custom.Block Model Styling Msg
- titleBlock =
- Mark.Custom.section "title" titleView
-
- titleView : List (Element Msg) -> Styling -> Model -> Element Msg
- titleView elements style model =
- case elements of
- title :: subtitles ->
- let
- titleElement =
- Element.el
- [ Font.center
- , Font.size 60
- , Font.extraBold
- , Element.width Element.fill
- ]
- title
- in
- Element.column
- [ Element.spacing 30
- , Font.bold
- , Font.size 30
- , Font.center
- , Element.paddingXY 0 80
+ paragraph : Mark.Block (Model -> Element Msg)
+ paragraph =
+ let
+ render content model =
+ Element.paragraph
+ [ Element.paddingXY 0 24
= ]
- (titleElement :: subtitles)
-
- [] ->
- Element.none
-
- noteBlock : Mark.Custom.Block Model Styling Msg
- noteBlock =
- Mark.Custom.section "note" noteView
+ (content model)
+ in
+ Mark.map
+ render
+ text
=
- noteView : List (Element Msg) -> Styling -> Model -> Element Msg
- noteView elements style model =
- Element.column
+ monospace =
+ Mark.Default.monospace
= [ Element.padding 20
- , Element.spacing 10
- , Border.width 1
+ , Element.width Element.fill
= , Border.color colors.gray
+ , Border.width 3
= , Border.rounded 5
- , Font.italic
+ , Font.family [ Font.monospace ]
+ , Element.scrollbarY
= ]
- elements
=
- counterBlock : Mark.Custom.Block Model Styling Msg
- counterBlock =
- Mark.Custom.block "counter" counterView
+ css : String -> String -> Element.Attribute msg
+ css property value =
+ Element.htmlAttribute
+ (Html.Attributes.style
+ property
+ value
+ )
=
- counterView : Styling -> Model -> Element Msg
- counterView style model =
- model.counter
- |> Counter.ui
- |> Element.el
- [ Element.centerX
- , Element.centerY
- ]
- |> Element.el
- [ Element.height (Element.px 400)
- , Element.width Element.fill
- ]
- |> BrowserWindow.window []
- |> Element.map CounterMsg
-
- simplestBlock : Mark.Custom.Block Model Styling Msg
- simplestBlock =
- Mark.Custom.block "simplest" simplestView
-
- simplestView : Styling -> Model -> Element Msg
- simplestView style model =
- Simplest.main
- |> Element.html
- |> Element.el
- [ Element.height (Element.px 400)
- , Element.width Element.fill
- , Element.padding 10
- ]
- |> BrowserWindow.window []
-
- fillTheScreenBlock : Mark.Custom.Block Model Styling Msg
- fillTheScreenBlock =
- Mark.Custom.block "fill-the-screen" fillTheScreenView
-
- fillTheScreenView : Styling -> Model -> Element Msg
- fillTheScreenView style model =
- FillTheScreen.ui
- |> Element.el
- [ Element.height (Element.px 400)
- , Element.width Element.fill
- ]
- |> BrowserWindow.window []
-
- centeredDotBlock : Mark.Custom.Block Model Styling Msg
- centeredDotBlock =
- Mark.Custom.block "centered-dot" centeredDotView
-
- centeredDotView : Styling -> Model -> Element Msg
- centeredDotView style model =
- CenteredDot.main
- |> Element.html
- |> Element.el
- [ Element.height (Element.px 400)
- , Element.width Element.fill
- , Element.padding 10
- ]
- |> BrowserWindow.window []
-
- lineBlock : Mark.Custom.Block Model Styling Msg
- lineBlock =
- Mark.Custom.block "line" lineView
+ code =
+ let
+ render string model =
+ string
+ |> String.split "\n"
+ |> List.indexedMap
+ (\n loc ->
+ Element.row [ Element.spacing 10 ]
+ [ Element.el
+ [ Font.color colors.gray
+ , Font.size 20
+ , Element.width (Element.px 40)
+ , css "user-select" "none"
+ , css "-webkit-user-select" "none"
+ , css "-ms-user-select" "none"
+ , css "-webkit-touch-callout" "none"
+ , css "-o-user-select" "none"
+ , css "-moz-user-select" "none"
+ ]
+ (Element.text (String.fromInt n ++ "."))
+ , Element.el
+ [ Element.width Element.fill
+ ]
+ (Element.text loc)
+ ]
+ )
+ |> Element.column
+ [ Element.padding 20
+ , Element.spacing 10
+ , Element.width Element.fill
+ , Border.color colors.gray
+ , Border.width 3
+ , Border.rounded 5
+ , Font.family [ Font.monospace ]
+ , Element.scrollbarY
+ ]
+ in
+ Mark.block "Code"
+ render
+ Mark.multiline
=
- lineView : Styling -> Model -> Element Msg
- lineView style model =
- Line.ui
- |> Element.el
- [ Element.centerX
+ note : Mark.Block (Model -> Element Msg)
+ note =
+ let
+ render elements model =
+ elements
+ |> List.map (\element -> element model)
+ |> Element.textColumn
+ [ Element.padding 20
+ , Element.spacing 10
+ , Element.width Element.fill
+ , Border.width 1
+ , Border.color colors.gray
+ , Font.color colors.gray
+ , Border.rounded 5
+ ]
+ in
+ Mark.block "Note"
+ render
+ (Mark.manyOf
+ [ paragraph
+ , header
= ]
- |> BrowserWindow.window []
+ )
=
- gradientBlock : Mark.Custom.Block Model Styling Msg
- gradientBlock =
- Mark.Custom.block "gradient" gradientView
+ emphasize : Mark.Block (Model -> Element Msg)
+ emphasize =
+ let
+ render element model =
+ model
+ |> element
+ |> Element.el
+ [ Element.spacing 30
+ , Font.bold
+ , Font.size 30
+ , Font.center
+ , Element.paddingXY 0 40
+ , Element.width Element.fill
+ ]
+ in
+ Mark.block "Emphasize"
+ render
+ paragraph
=
- gradientView : Styling -> Model -> Element Msg
- gradientView style model =
- Gradient.ui
- |> Element.el
- [ Element.centerX
- ]
- |> BrowserWindow.window []
-
- transformationsBlock : Mark.Custom.Block Model Styling Msg
- transformationsBlock =
- Mark.Custom.block "transformations" transformationsView
-
- transformationsView : Styling -> Model -> Element Msg
- transformationsView style model =
- model.transformations
- |> Transformations.ui
- |> Element.el
- [ Element.height Element.fill
- , Element.width Element.fill
- ]
- |> Element.el
- [ Element.centerX
- , Border.color colors.pink
- , Border.rounded 5
- , Border.width 2
- ]
- |> Element.map TransformationsMsg
+ image =
+ Mark.Default.image
+ [ Element.width Element.fill
+ ]
=
- nestedTransformationsBlock : Mark.Custom.Block Model Styling Msg
- nestedTransformationsBlock =
- Mark.Custom.block "nested-transformations" nestedTransformationsView
+ list =
+ Mark.Default.list
+ { icon = Mark.Default.listIcon
+ , style =
+ \cursor ->
+ case List.length cursor of
+ 0 ->
+ -- top level element
+ [ Element.spacing 16 ]
+
+ 1 ->
+ [ Element.spacing 16 ]
+
+ 2 ->
+ [ Element.spacing 16 ]
+
+ _ ->
+ [ Element.spacing 8 ]
+ }
+ text
+
+ text : Mark.Block (Model -> List (Element Msg))
+ text =
+ let
+ defaultTextStyle =
+ Mark.Default.defaultTextStyle
+ in
+ Mark.Default.textWith
+ { defaultTextStyle
+ | inlines = defaultTextStyle.inlines ++ [ icon ]
+ }
+
+ icon : Mark.Inline (Model -> Element Msg)
+ icon =
+ Mark.inline "Icon"
+ (\name model ->
+ icons
+ |> Dict.get name
+ |> Maybe.map (FeatherIcons.toHtml [])
+ |> Maybe.map Element.html
+ |> Maybe.withDefault
+ (Element.text
+ ("Icon not found: '" ++ name ++ "'")
+ )
+ )
+ |> Mark.inlineString "name"
+
+ -- Embeded programs' blocks
+ counter : Mark.Block (Model -> Element Msg)
+ counter =
+ let
+ render model =
+ model.counter
+ |> Counter.ui
+ |> Element.el
+ [ Element.centerX
+ , Element.centerY
+ ]
+ |> Element.el
+ [ Element.height (Element.px 400)
+ , Element.width Element.fill
+ ]
+ |> BrowserWindow.window []
+ |> Element.map CounterMsg
+ in
+ Mark.stub "Counter" render
=
- nestedTransformationsView : Styling -> Model -> Element Msg
- nestedTransformationsView style model =
- model.nestedTransformations
- |> NestedTransformations.ui
- |> Element.el
- [ Element.centerX
- ]
- |> Element.el
- [ Element.centerX
- , Border.color (Element.rgb 1 0.6 0.6)
- , Border.rounded 5
- , Border.width 2
- ]
- |> Element.map NestedTransformationsMsg
+ simplest : Mark.Block (Model -> Element Msg)
+ simplest =
+ let
+ render model =
+ Simplest.main
+ |> Element.html
+ |> Element.el
+ [ Element.height (Element.px 400)
+ , Element.width Element.fill
+ ]
+ |> BrowserWindow.window []
+ in
+ Mark.stub "Simplest" render
=
- cartesianCoordinatesBlock : Mark.Custom.Block Model Styling Msg
- cartesianCoordinatesBlock =
- Mark.Custom.block "cartesian-coordinates" cartesianCoordinatesView
+ fillTheScreen : Mark.Block (Model -> Element Msg)
+ fillTheScreen =
+ let
+ render model =
+ FillTheScreen.ui
+ |> Element.el
+ [ Element.height (Element.px 400)
+ , Element.width Element.fill
+ ]
+ |> BrowserWindow.window []
+ in
+ Mark.stub "FillTheScreen" render
=
- cartesianCoordinatesView : Styling -> Model -> Element Msg
- cartesianCoordinatesView style model =
- model.cartesianCoordinates
- |> CartesianCoordinates.ui
- |> Element.el
- [ Element.centerX
- , Element.width Element.fill
- ]
- |> Element.el
- [ Element.centerX
- , Border.color (Element.rgb 1 0.6 0.6)
- , Border.rounded 5
- , Border.width 2
- ]
- |> Element.map CartesianCoordinatesMsg
+ centeredDot : Mark.Block (Model -> Element Msg)
+ centeredDot =
+ let
+ render model =
+ CenteredDot.main
+ |> Element.html
+ |> Element.el
+ [ Element.height (Element.px 400)
+ , Element.width Element.fill
+ ]
+ |> BrowserWindow.window []
+ in
+ Mark.stub "CenteredDot" render
=
- polarCoordinatesBlock : Mark.Custom.Block Model Styling Msg
- polarCoordinatesBlock =
- Mark.Custom.block "polar-coordinates" polarCoordinatesView
+ line : Mark.Block (Model -> Element Msg)
+ line =
+ let
+ render model =
+ Line.ui
+ |> Element.el
+ [ Element.height (Element.px 400)
+ , Element.width Element.fill
+ ]
+ |> BrowserWindow.window []
+ in
+ Mark.stub "Line" render
=
- polarCoordinatesView : Styling -> Model -> Element Msg
- polarCoordinatesView style model =
- model.polarCoordinates
- |> PolarCoordinates.ui
- |> Element.el
- [ Element.centerX
- , Element.width Element.fill
- ]
- |> Element.el
- [ Element.centerX
- , Border.color (Element.rgb 1 0.6 0.6)
- , Border.rounded 5
- , Border.width 2
- ]
- |> Element.map PolarCoordinatesMsg
+ gradient : Mark.Block (Model -> Element Msg)
+ gradient =
+ let
+ render model =
+ Gradient.ui
+ |> Element.el
+ [ Element.height (Element.px 400)
+ , Element.width Element.fill
+ ]
+ |> BrowserWindow.window []
+ in
+ Mark.stub "Gradient" render
=
- rosetteBlock : Mark.Custom.Block Model Styling Msg
- rosetteBlock =
- Mark.Custom.block "rosette" rosetteView
+ transformations : Mark.Block (Model -> Element Msg)
+ transformations =
+ let
+ render model =
+ model.transformations
+ |> Transformations.ui
+ |> Element.el
+ [ Element.width Element.fill
+ ]
+ |> BrowserWindow.window []
+ |> Element.map TransformationsMsg
+ in
+ Mark.stub "Transformations" render
=
- rosetteView : Styling -> Model -> Element Msg
- rosetteView style model =
- RosetteTypedTransformations.ui
- |> Element.el
- [ Element.centerX
- ]
- |> BrowserWindow.window []
+ nestedTransformations : Mark.Block (Model -> Element Msg)
+ nestedTransformations =
+ let
+ render model =
+ model.nestedTransformations
+ |> NestedTransformations.ui
+ |> Element.el
+ [ Element.width Element.fill
+ ]
+ |> BrowserWindow.window []
+ |> Element.map NestedTransformationsMsg
+ in
+ Mark.stub "NestedTransformations" render
=
- spiralBlock : Mark.Custom.Block Model Styling Msg
- spiralBlock =
- Mark.Custom.block "spiral" spiralView
+ cartesianCoordinates : Mark.Block (Model -> Element Msg)
+ cartesianCoordinates =
+ let
+ render model =
+ model.cartesianCoordinates
+ |> CartesianCoordinates.ui
+ |> Element.el
+ [ Element.width Element.fill
+ ]
+ |> BrowserWindow.window []
+ |> Element.map CartesianCoordinatesMsg
+ in
+ Mark.stub "CartesianCoordinates" render
=
- spiralView : Styling -> Model -> Element Msg
- spiralView style model =
- Spiral.ui
- |> Element.el
- [ Element.centerX
- , Element.width Element.fill
+ polarCoordinates : Mark.Block (Model -> Element Msg)
+ polarCoordinates =
+ let
+ render model =
+ model.polarCoordinates
+ |> PolarCoordinates.ui
+ |> Element.el
+ [ Element.width Element.fill
+ ]
+ |> BrowserWindow.window []
+ |> Element.map PolarCoordinatesMsg
+ in
+ Mark.stub "PolarCoordinates" render
=
- -- , Element.height Element.fill
- ]
- |> BrowserWindow.window []
+ rosette : Mark.Block (Model -> Element Msg)
+ rosette =
+ let
+ render model =
+ RosetteTypedTransformations.ui
+ |> Element.el
+ [ Element.height (Element.px 400)
+ , Element.width Element.fill
+ ]
+ |> BrowserWindow.window []
+ in
+ Mark.stub "Rosette" render
=
- treeBlock : Mark.Custom.Block Model Styling Msg
- treeBlock =
- Mark.Custom.block "tree" treeView
+ spiral : Mark.Block (Model -> Element Msg)
+ spiral =
+ let
+ render model =
+ Spiral.ui
+ |> Element.el
+ [ Element.height (Element.px 400)
+ , Element.width Element.fill
+ ]
+ |> BrowserWindow.window []
+ in
+ Mark.stub "Spiral" render
=
- treeView : Styling -> Model -> Element Msg
- treeView style model =
+ tree : Mark.Block (Model -> Element Msg)
+ tree =
= let
- height =
- 500
-
- content =
- case model.tree of
- Nothing ->
- Element.text "Click Here to Display Tree"
- |> List.singleton
- |> Element.paragraph
- [ Element.centerY
- ]
- |> Element.el
- [ Element.centerX
- , Element.height <|
- Element.minimum height <|
- Element.maximum height <|
- Element.fill
- , Element.Events.onClick
- (Tree.init ()
- |> Tuple.first
- |> Just
- |> ToggleTree
- )
- ]
+ render model =
+ model.tree
+ |> Maybe.map Tree.ui
+ |> Maybe.withDefault Element.none
+ |> Element.el
+ [ Element.height (Element.px 600)
+ , Element.width Element.fill
+ ]
+ |> BrowserWindow.window []
+ |> Element.map TreeMsg
+ in
+ Mark.stub "Tree" render
=
- Just tree ->
- tree
- |> Tree.ui
- |> Element.map TreeMsg
- |> Element.el
- [ Element.centerX
- , Element.height <|
- Element.minimum height <|
- Element.maximum height <|
- Element.fill
- , Element.width Element.fill
- , Element.Events.onClick (ToggleTree Nothing)
- ]
+ viewBox : Mark.Block (Model -> Element Msg)
+ viewBox =
+ let
+ render model =
+ model.viewBox
+ |> ViewBox.ui
+ |> Element.el
+ [ Element.width Element.fill
+ ]
+ |> Element.map ViewBoxMsg
= in
- content
- |> BrowserWindow.window []
-
- viewBoxBlock : Mark.Custom.Block Model Styling Msg
- viewBoxBlock =
- Mark.Custom.block "view-box" viewBoxView
-
- viewBoxView : Styling -> Model -> Element Msg
- viewBoxView style model =
- model.viewBox
- |> ViewBox.ui
- |> Element.el
- [ Element.centerX
- , Border.color (Element.rgb 1 0.6 0.6)
- , Border.rounded 5
- , Border.width 2
- ]
- |> Element.map ViewBoxMsg
+ Mark.stub "ViewBox" render
= in
- { default
- | blocks =
- titleBlock
- :: emphasizeBlock
- :: noteBlock
- :: counterBlock
- :: simplestBlock
- :: fillTheScreenBlock
- :: lineBlock
- :: gradientBlock
- :: centeredDotBlock
- :: transformationsBlock
- :: nestedTransformationsBlock
- :: cartesianCoordinatesBlock
- :: polarCoordinatesBlock
- :: rosetteBlock
- :: spiralBlock
- :: treeBlock
- :: viewBoxBlock
- :: Mark.defaultBlocks
+ Mark.document
+ (\children model ->
+ Element.textColumn
+ [ Element.centerX
+ , Element.width (Element.px 800)
+ , Element.spacing 24
+ ]
+ (List.map (\render -> render model) children)
+ )
+ (Mark.manyOf
+ [ title
+ , header
+ , paragraph
+ , monospace
+ , code
+ , note
+ , emphasize
+ , list
+ , image
+
+ -- Embeded programs
+ , counter
+ , simplest
+ , fillTheScreen
+ , centeredDot
+ , line
+ , gradient
+ , transformations
+ , nestedTransformations
+ , cartesianCoordinates
+ , polarCoordinates
+ , rosette
+ , spiral
+ , tree
+ , viewBox
+ ]
+ )
+
+
+colors =
+ { maroon = Element.rgb 0.7 0 0
+ , gray = Element.rgb 0.8 0.8 0.8
+ , pink = Element.rgb 1 0.6 0.6
= }Remove calls to Debug functions
This enables the optimized compilation.
index 1f34423..e0aaded 100644
--- a/src/NestedTransformations.elm
+++ b/src/NestedTransformations.elm
@@ -64,7 +64,7 @@ type GroupMsg
=
=init : Model
=init =
- Dict.empty Debug.toString
+ Dict.empty groupName
= |> Dict.insert Pink
= (Array.fromList
= [ Translate 0 0
@@ -138,7 +138,7 @@ ui model =
=
= color =
= group
- |> Debug.toString
+ |> groupName
= |> String.toLower
= in
= g [ transform transformation ]
@@ -218,7 +218,7 @@ transformationUI index transformation =
= controls =
= case transformation of
= Identity ->
- [ Element.text <| Debug.toString transformation ]
+ [ Element.text "Identity" ]
=
= Scale horizontal vertical ->
= [ Input.slider
@@ -388,6 +388,16 @@ apply transformations =
= |> String.join " "
=
=
+groupName : Group -> String
+groupName group =
+ case group of
+ Pink ->
+ "Pink"
+
+ Green ->
+ "Green"
+
+
=toColor : Group -> Element.Color
=toColor group =
= case group ofindex 5da8133..3a476cd 100644
--- a/src/PolarCoordinates.elm
+++ b/src/PolarCoordinates.elm
@@ -93,7 +93,17 @@ ui model =
= , Svg.Attributes.fontSize "10pt"
= ]
= [ text <|
- Debug.toString ( round point.x, round point.y )
+ "( "
+ ++ (point.x
+ |> round
+ |> String.fromInt
+ )
+ ++ " , "
+ ++ (point.x
+ |> round
+ |> String.fromInt
+ )
+ ++ " )"
= ]
= ]
= , Input.sliderindex 0bcd917..295c1ef 100644
--- a/src/Transformations.elm
+++ b/src/Transformations.elm
@@ -179,7 +179,7 @@ transformationUI index transformation =
= controls =
= case transformation of
= Identity ->
- [ Element.text <| Debug.toString transformation ]
+ [ Element.text "Identity" ]
=
= Scale horizontal vertical ->
= [ Input.sliderindex 44e7a9a..e2925a8 100644
--- a/src/Tree.elm
+++ b/src/Tree.elm
@@ -173,7 +173,7 @@ subscriptions model =
= Json.Decode.field "key" Json.Decode.string
= |> Json.Decode.andThen
= (\key ->
- case Debug.log "Key" key of
+ case key of
= "," ->
= Json.Decode.succeed (Regress 10)
=Remove background color and position of the dot from Simplest
index dee7dbd..9f90116 100644
--- a/src/Simplest.elm
+++ b/src/Simplest.elm
@@ -7,14 +7,11 @@ import Svg.Attributes
=
=main =
= Svg.svg
- [ Svg.Attributes.style "background: pink"
- , Svg.Attributes.width "300"
+ [ Svg.Attributes.width "300"
= , Svg.Attributes.height "150"
= ]
= [ Svg.circle
= [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "30"
- , Svg.Attributes.cy "30"
= ]
= []
= ]Create DotAtTheCenterOfTheScreen program
It's the same as the FillTheScreen but without background color
new file mode 100644
index 0000000..83f0d1d
--- /dev/null
+++ b/src/DotAtTheCenterOfTheScreen.elm
@@ -0,0 +1,27 @@
+module DotAtTheCenterOfTheScreen exposing (main, ui)
+
+import Element
+import Svg
+import Svg.Attributes
+
+
+main =
+ Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+ ui
+
+
+ui =
+ Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200"
+ ]
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ ]
+ []
+ ]
+ |> Element.htmlindex 29bbd46..82bc5a7 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -5,6 +5,7 @@ import BrowserWindow
=import CartesianCoordinates
=import CenteredDot
=import Counter
+import DotAtTheCenterOfTheScreen
=import Element exposing (Element)
=import Element.Background as Background
=import Element.Border as Border
@@ -324,6 +325,19 @@ options =
= ]
= |> BrowserWindow.window []
=
+ dotAtTheCenterOfTheScreenBlock : Mark.Custom.Block Model Styling Msg
+ dotAtTheCenterOfTheScreenBlock =
+ Mark.Custom.block "dot-at-the-center-of-the-screen" dotAtTheCenterOfTheScreenView
+
+ dotAtTheCenterOfTheScreenView : Styling -> Model -> Element Msg
+ dotAtTheCenterOfTheScreenView style model =
+ DotAtTheCenterOfTheScreen.ui
+ |> Element.el
+ [ Element.height (Element.px 400)
+ , Element.width Element.fill
+ ]
+ |> BrowserWindow.window []
+
= centeredDotBlock : Mark.Custom.Block Model Styling Msg
= centeredDotBlock =
= Mark.Custom.block "centered-dot" centeredDotView
@@ -542,6 +556,7 @@ options =
= :: counterBlock
= :: simplestBlock
= :: fillTheScreenBlock
+ :: dotAtTheCenterOfTheScreenBlock
= :: lineBlock
= :: gradientBlock
= :: centeredDotBlockAdd the content from paper slides
index e9d30d1..7deafce 100644
--- a/index.txt
+++ b/index.txt
@@ -33,15 +33,18 @@ We will need
=| header
= Installing Elm
=
+
=To install the Elm programming language, go to it's website:
=
+
=| emphasize
= [elm-lang.org](http://elm-lang.org/)
=
=
+
=and follow the installation instructions.
-Some of the tools we use with Elm require [Node.js.](https://nodejs.org/en/)
-Go to the [Node.js](https://nodejs.org/en/) website and install the current version (the green button on the right)
+
+Some of the tools we use with Elm require Node.js. Go to the [Node.js](https://nodejs.org/en/) website and install the current version (the green button on the right)
=
=We will need to use the terminal a little bit.
=
@@ -57,6 +60,1021 @@ Don't be scared. It's easy.
= "Here is a picture of terminal"
=
=
+Mac Terminal app window
+You should see a window like this
+
+
+| image "/assets/mac-terminal-window.png"
+ "A picture of Mac Terminal"
+
+Type the following in the terminal.
+
+
+| monospace
+ elm repl
+
+Inside the REPL type
+
+
+| monospace
+ 2+2
+
+And expect to see
+
+
+| monospace
+ ---- Elm 0.19.0 ----------------
+ Read <https://elm-lang.org/0.19.0/repl> to learn more: exit, help
+ --------------------------------
+ > 2 + 2
+ 4 : number
+ >
+
+Easy, huh?
+We will learn more about REPL later. For now type
+:exit to close it.
+The command line should get back to its initial state.
+
+| header
+ Install Atom Text Editor
+
+Computer Programs are represented as text, so the text editor is the most fundamental tool of a programmer. There is a lot of good text editors, but to get you started, we will use Atom here.
+
+Go to
+[atom.io](https://atom.io)
+and download the editor.
+After installation on Mac, open it and from Atom menu choose Install Shell Commands.
+
+One last thing we need is Elm support for Atom editor.
+You can install it by typing in the terminal:
+
+| monospace
+ apm install language-elm
+
+APM is the Atom Package Manager, but don't pay much attention to it. We won't be using it again.
+We are all set.
+
+| header
+ Day 1 - let's make a dot!
+
+| note
+ Today we are going to learn about
+
+ | list
+ -> Scalable Vector Graphics
+ -> cartesian Coordinates System
+ -> Layouts with ELM UI
+
+
+| header
+ First Program!
+
+As mentioned before, programs are represented as text (called the /source code/).
+The source code is stored in files
+
+| emphasize
+ * icon|name=file *
+
+
+and files are organized in directories
+
+
+| emphasize
+ * icon|name=directory *
+
+
+So the first step is to create a directory for our new program. Lets call it fpart.
+In the terminal type
+
+| monospace
+ mkdir fpart/
+
+and then
+
+| monospace
+ cd fpart/
+
+This creates a new directory and makes it the current one. Again, don't worry about the details.
+
+To easily create a new program, we can type
+
+| monospace
+ elm init
+
+Then to create a file with source code, type
+
+| monospace
+ atom src/Main.elm
+
+This command should open a new Atom window with empty text file.
+
+| header
+ Main.elm
+
+| monospace
+ module Main exposing (main)
+ import Html
+ main=
+ Html.text "Hello, Tree"
+
+Type the above in the editor and save the file.
+To see the program running type the following in the terminal
+
+| monospace
+ elm reactor
+
+This command starts the Elm reactor, which will let you run your program in the web browser. Note that it won't give you the command line back - it will run as long as you don't stop it.
+
+
+| emphasize
+ Voila!
+
+
+Open following address in the web browser
+[localhost](http://localhost:8000/src/Main.elm)
+Note that the same address was printed by Elm reactor
+
+| header
+ The problem
+
+We want to have a dot at the center of the screen, like this
+
+| dot-at-the-center-of-the-screen
+
+We are going to use a technology called SVG (Scalable Vector Graphics)
+Its all about drawing shapes on the screen
+
+* to be filled with the complete code *
+
+Let's install an Elm Package to help us work with SVG. Stop the reactor running in terminal by pressing `CTRL + C`
+and type the following
+
+| monospace
+ elm install elm/svg
+
+Then start the reactor again
+
+| monospace
+ elm reactor
+
+you can press up arrow *icon up-arrow* on the keyboard to get to the previous commands
+
+* simplest elm should be displayed here *
+
+| simplest
+
+Why is the dot at the corner?
+
+| simplest
+
+It's because its center is at point (0, 0)
+But what does it mean ?
+
+* a picture of a vase on a table should be here *
+
+Move sliders to change the x and y coordinates of the dot. It's like moving a thing on a table by telling how far left, right, up or down it should go.
+
+| cartesian-coordinates
+
+Note that the command line changes. You should see something like this
+
+| monospace
+ ---- Elm 0.19.0 --------------------------------------------------
+ Read <https://elm-lang.org/0.19.0/repl> to learn more: exit, help
+ ---------------------------------------------------------------------
+ >
+
+It's the Elm REPL
+Read-Evaluate-Print Loop
+
+To change where the dot is we can use cx and cy attributes of the circle
+
+* simplest with cx and cy should be here *
+
+* Module main exposing main should be here *
+
+Note that we have a complete program that draws a dot at the center of the screen , lets take a moment to understand it.
+
+Think about this ( * a picture of eggs and a box of eggs should be here * )
+
+| note
+ An egg is a thing.
+ Six eggs are six things.
+ A box of six eggs is a thing.
+
+
+
+Now, let's make the SVG element fill the screen
+
+
+| fill-the-screen
+
+
+For that we will need a package called [mdgriffith](https://package.elm-lang.org/packages/mdgriffith/elm-ui/latest/)
+
+Install it using terminal
+
+`CTRL + C` to stop elm-reactor and type `elm install mdgriffith/elm-ui`
+
+Then in `src/Main.elm` add import `import Element` and change `Main` to look like this :
+
+Main = * the code of Element.layout should be here*
+
+Note that our scene fills the screen, it's time to put the dot at the center of the scene.
+
+* a picture of cx and cy with width and height should be here *
+
+If we would know the height and width of the screen , we could calculate its position as
+
+`cx = width/2`
+
+`cy = height/2`
+
+There is a problem though, we don't know the height and width.
+☹️
+
+But we don't need to!
+
+*Scalable icon should be here*
+
+S for Scalable
+
+Instead of moving the dot, we can set the boundaries of the scene so that the dot is at the center using a property called Viewbox.
+
+| view-box
+
+Here is how the viewbox works
+
+* a picture of viewbox should be here*
+
+viewbox = 0 0 100 100 (left, top, width and height respectively)
+
+* a picture of periscope looking table should be here*
+
+The trick is to set the width and height to any arbitrary value (say 1000) and top and left to `- width/2`!
+
+That way point (0,0) will always be right in the middle (the distance to the left and to the right is the same, likewise the distance to the top and bottom).
+
+First, lets see where the SVG boundaries are by giving it a background color
+
+
+| centered-dot
+
+
+`Svg [background "pink"]
+[...
+]`
+
+Give a color to the dot
+
+
+| list
+ - Explain SVG attributes ( svg elements take a list of attributes)
+ - Everyone can pick a color
+
+
+Multiple dots
+
+
+| list
+ -> Introduce a problem: We want to have two dots on the screen
+ -> Explain a list, and show there is already a list there (with one item)
+
+
+| header
+ Do you see any other lists?
+
+
+| list
+ -> Element takes a list of children
+ -> Mention , how can we have a list with one or zero elements? Shopping list with one item. Empty spoon drawer( list of spoons)
+ -> Give your new dot a color and different coordinates
+ -> Show the code for 2 dots
+ -> Exercise: make 3 more dots (5 total)
+
+
+| header
+ Day 2 - Place the dots in a circle
+
+Introduce a problem: We want to place the dots in a circle, show an example on the slides
+
+How we will get there: We will change the cartesian coordinates of the dots, nothing else! But figuring out the right coordinates is a bit tricky
+
+How can we figure out the correct cartesian coordinates?
+
+| note
+ Story about the flowerpot and the table: two ways to measure relative position, one is distance from an x and y axis (cartesian coordinates), the other is angle and distance relative to origin (polar coordinates)
+
+
+What does this have to do with a circle? Do you know the expression 'I did a 180'. Where would you be looking if you did two 180s (a '360'). We see that circles and angles are closely related!
+
+Exercise with compass and 5 objects, place objects evenly along compass. How many degrees apart are they? (observe if we multiply the result by 5, we're back to 360)
+
+We have one part of it (angle), we now need the distance. Notice they are all the same distance from the origin (the center dot) of the compass. Definition of circle: points that are an equal distance from a center. Actually, the distance doesn't matter, so long as they all have the same distance. You can have a big circle or a small circle, they're both circles
+
+Ok great, we're done! Now, does anyone know how to give an angle and distance to svg? Oh... no? We don't either... you can't. You can only give x and y values relative to the origin
+
+So we already know that angle and length are just another way of describing x and y. But we need some way of translating between the two
+
+sing a visual demonstration, if you draw a line from your object to the x axis, you have a triangle. Same if you draw a line to the y axis. You can figure out the point on the axis using sin and cos functions and multiplying the result by the length.
+
+Let's use a chart to figure out the sin and cos of our angles
+
+/It's important to get a chart, otherwise we have to use calculators which work with radians/
+
+Now we are done... we can plug in our x and y values, and presto, our dots are arranged in a circle. But we don't see most of them...
+
+Our origin is in the top left corner. This means any dots with negative x or y values are off the screen.
+
+We can shift the dots so they are on the screen, or shift the screen so that it covers the dots. We will be shifting the screen
+
+Sample viewbox program to demonstrate
+
+Create a viewbox with correct perimeters (must be more than the radius of the circle plus the radius of a dot in each direction, and width and height are circumference)
+
+`svg [viewbox "-100 -100 200 200 " ] []`
+
+| header
+ Day 3 - Let the computer do the math
+
+Introduce the problem: Can we avoid all these repetitive and manual steps?
+
+What is a program made of? (in the REPL):
+
+Values and names
+
+| monospace
+ 5
+ 10.4
+ "Hello World"
+ [1, 2, 3, 4, 5]
+
+
+| list
+ -> Numbers like 5, 10.4
+ -> Strings like "Hello World"
+ -> and lists containing numbers or strings
+
+
+are all values
+
+They are self-referential. They simply state what they are. There are other kinds of values that we will see later.
+
+You can give (or assign) a name to a value, as shown.
+
+| monospace
+ family = 5
+ price = 10.4
+ morningGreeting = "Hello World"
+ fingers = [1, 2, 3, 4, 5]
+
+Here, `family` is a name and `5` is the value assigned to `family`.
+
+You can get the value back by calling its name.
+
+```
+---- Elm 0.19.0 ----------------------------------------------------------------
+Read <https://elm-lang.org/0.19.0/repl> to learn more: exit, help, imports, etc.
+--------------------------------------------------------------------------------
+> family = 5
+5 : number
+> family
+5 : number
+```
+
+Operators (`+`, `-`, `++`, `::`)
+
+| monospace
+ > 2 + 5
+ 7 : number
+
+| monospace
+ > 4 - 6
+ -2 : number
+
+| monospace
+ > "Hello" ++ " world!"
+ "Hello world!" : String
+
+
+| monospace
+ > 1 :: [2, 3, 4, 5]
+ [1,2,3,4,5] : List number
+
+
+Operators take two values and return a new value. We know that names refer to values, so we can use them in place of values:
+
+| monospace
+ > family = 5
+ 5 : number
+ > family + 2
+ 7 : number
+
+
+You can also give a name to the value returned by an operator:
+
+| monospace
+ > family = 5
+ 5 : number
+ > familyAndPets = family + 2
+ 7 : number
+ > familyAndPets
+ 7 : number
+
+Note that different values have different types.
+
+| monospace
+ 5 : number
+ 10.4 : Float
+ "Hello World" : String
+ [1,2,3,4,5] : List number
+
+Different operators work on different types. Adding a number and a string doesn't make sense. So if you try, Elm will complain (and give a helpful hint):
+
+| monospace
+ > "Hello " + 5
+ -- TYPE MISMATCH ----------------------------------------------------------- elm
+ I cannot do addition with String values like this one:
+ 5| "Hello " + 5
+ ^^^^^^^^
+ The (+) operator only works with Int and Float values.
+ Hint: Switch to the (++) operator to append strings!
+
+
+(Int and Float are both types representing numbers)
+
+Functions
+
+`fun something = something ++ " is fun."`
+
+Explain: a function is a thing that takes some values (called arguments) and return one value. So it's similar to operators. In fact operators are functions!
+
+| monospace
+ (-) 5 10
+ 5 - 10
+
+
+You can think of a function as a machine. You put something in the machine, and it produces something in return. For example think about a machine that produces rubber ducks. You put a bucket of white plastic pellets and a bucket of red paint, and you get a bunch of red rubber ducks!
+
+What you get will depends on what you put. The color of the ducks depends on the paint you put. Quantity of ducks depends on how much plastic you put in.
+
+| monospace
+ makeMeSomeDucks color plastic =
+ String.fromInt plastic ++ " " ++ color ++ " rubber ducks"
+
+
+Once you have a function, you can call it like this:
+
+| monospace
+ > makeMeSomeDucks "blue" 12
+ "12 blue rubber ducks" : String
+
+
+Note: functions help organize code into nice reusable chunks.
+
+How do you get functions? There are three ways.
+
+Some functions are always there for you
+
+`(+)`, `(-)`
+
+Some functions you can import using code like this:
+
+`import Svg`
+
+
+and then
+
+| monospace
+ Svg.circle [ cx "10", cy "10", r "20" ] [ ]
+
+
+To call a function that was imported, you have to prefix it with the name of the module (in this example `Svg`).
+
+
+Finally, you will write some functions, just like we saw in the `fun` and `makeMeSomeDucks` examples.
+
+Finally there is one special thing: the first line of the program is a module declaration. For now it's enough for us to know, that it has to be there and it has to match the name of the file.
+
+Exercise: In our Main.elm, try to identify some values, names and function calls:
+
+Hint: `"darkred"` is a value, `main` is a name, `width` is a function.
+
+Hint: There is nothing else there now, but we will soon introduce our own functions.
+
+* Where can we use some functions?*
+
+As we said before, functions are good when we have some repetitive operation that can be parametrized (like rubber ducks production).
+
+Obviously calculating `x` and `y` coordinates is repetitive and can be parametrized (parameters are `radius` and `angle`).
+
+*Apply to our trigonometry calculations*
+
+Compose and copy and paste the trigonometry functions to calculate cx and cy:
+
+| monospace
+ cx (String.fromFloat (cos (degrees 72) * 100))
+ cy (String.fromFloat (sin (degrees 72) * 100))
+
+*Eliminate the repetition:*
+
+Assign a value of `100` to a name `radius`:
+
+| monospace
+ radius = 100
+
+and plug it to our functions:
+
+| monospace
+ cx (String.fromFloat (cos (degrees 72) * radius))
+ cy (String.fromFloat (sin (degrees 72) * radius))
+
+As you see the names are good for repetitive things too.
+
+Define and reuse
+
+`x : angle -> cx`
+
+| monospace
+ x angle =
+ String.fromFloat (cos (degrees angle) * radius)
+
+`y : angle -> cy`
+
+| monospace
+ y angle =
+ String.fromFloat (sin (degrees angle) * radius)
+
+Then plug it into the code making circles:
+| monospace
+ circle
+ [ cx (x 72)
+ , cy (y 72)
+ , r "10"
+ , fill "darkred"
+ ]
+
+define and reuse `dot : angle -> color -> Svg`
+
+| monospace
+ dot angle color =
+ circle
+ [ cx (x angle)
+ , cy (y angle)
+ , r "10"
+ , fill color
+ ]
+
+and plug it into our SVG picture:
+
+| monospace
+ svg [ viewbox "-100 -100 200 200" ]
+ [ dot 0 "darkred"
+ , dot 72 "fuchsia"
+ , dot 144 "saddlebrown"
+ , dot 216 "deepskyblue"
+ , dot 288 "gold"
+ ]
+
+For more cool colors check out [Color keywords page at Mozilla Developer's Network](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#Color_keywords)
+
+*Make it more general*
+
+| list
+ - Show List and list operations (map and indexedMap)
+ - There are a number of functions that operate on lists, for example `List.length` and `List.map`.
+
+
+Examples of a map: a shopping list, after you find each item and place it in your basket, you are 'mapping' from a list of needed items to a list of items in your basket. Now you have two lists, and they are related. One is your original shopping list, the other the list of items in the basket.
+
+Demonstrate:
+
+| monospace
+ > things = ["Programming", "Swimming", "Dancing", "Saddle brown"]
+ ["Programming","Swimming","Dancing","Saddle brown"]
+ : List String
+
+| monospace
+ > List.length things
+ 4 : Int
+
+| monospace
+ > List.map fun things
+ ["Programming is fun!","Swimming is fun!","Dancing is fun!","Saddle brown is fun!"]
+ : List String
+
+| monospace
+ > things
+ ["Programming","Swimming","Dancing","Saddle brown"]
+ : List String
+
+
+Notice our original `things` list is unchanged. This is different from our rubber duck machine. The rubber duck turns the plastic and paint into rubber ducks. A function on the other hand 'creates' the value it gives you. You don't loose the original value given to it.
+
+Make a `palette : List color`
+
+| monospace
+ palette =
+ [ "darkred"
+ , "fuchsia"
+ , "saddlebrown"
+ , "deepskyblue"
+ , "gold"
+ ]
+
+
+Use `List.indexedMap dot palette` to generate the dots
+
+
+Another function that operates on lists is `List.indexedMap`. Let's see it at work:
+
+| monospace
+ dot index color =
+ circle
+ [ cx (x ((360 / (List.length palette)) * index))
+ , cy (y ((360 / (List.length palette)) * index))
+ , r "10"
+ , fill color
+ ]
+
+`List.indexedMap dot palette`
+
+We can introduce a `let` block to make our code more readable and avoid repetition.
+
+| monospace
+ dot index color =
+ let
+ angle =
+ (360 / count) * index
+ count =
+ List.length palette
+ in
+ circle
+ [ cx (x angle)
+ , cy (y angle)
+ , r "10"
+ , fill color
+ ]
+
+
+Take a look at the documentation for [List.indexedMap](https://package.elm-lang.org/packages/elm/core/latest/List#indexedMap).
+
+
+| header
+ Day 4 - Introduce SVG groups
+
+We'll need one group for the dots and one group for the lines
+
+| monospace
+ svg [ viewBox "-100 -100 200 200" ]
+ [ Svg.g [] (List.indexedMap dot pallete)
+ , Svg.g [] [ Svg.line [ x1 "123", y1 "112", x2 "41", y2 "11", stroke "black" ] [] ]
+ ]
+
+
+Exercise: add a few lines with different colors
+
+Now we'll learn how to color a line with a gradient (going from one color to another)
+
+| monospace
+ svg [ width "600", height "600", viewBox "-300 -300 600 600" ]
+ [ Svg.g [] (List.indexedMap dot pallete)
+ , Svg.defs []
+ [ Svg.linearGradient [ Svg.Attributes.id "MyGradient", x1 "0", y1 "0", x2 "1", y2 "0" ]
+ [ Svg.stop [ Svg.Attributes.offset "0", Svg.Attributes.stopColor "saddlebrown" ] []
+ , Svg.stop [ Svg.Attributes.offset "1", Svg.Attributes.stopColor "pink" ] []
+ ]
+ ]
+ , Svg.g []
+ [ Svg.line [ x1 "0", y1 "0", x2 "100", y2 "100", stroke "url(#MyGradient)" ] []
+ , Svg.line [ x1 "0", y1 "0", x2 "-100", y2 "-100", stroke "url(#MyGradient)" ] []
+ ]
+ ]
+
+
+We see a problem here. The gradient always goes from saddlebrown to pink from top left to bottom right. If we want to use the same gradient to connect different points, we'll need to be creative.
+
+| monospace
+ main =
+ svg [ width "600", height "600", viewBox "-300 -300 600 600" ]
+ [ Svg.g [] (List.indexedMap dot pallete)
+ , Svg.defs []
+ [ Svg.linearGradient [ Svg.Attributes.id "MyGradient", x1 "0", y1 "0", x2 "1", y2 "0", gradientUnits "userSpaceOnUse" ]
+ [ Svg.stop [ Svg.Attributes.offset "0", Svg.Attributes.stopColor "saddlebrown" ] []
+ , Svg.stop [ Svg.Attributes.offset "1", Svg.Attributes.stopColor "pink" ] []
+ ]
+ ]
+ , Svg.g []
+ [ Svg.line [ x1 "0", y1 "0", x2 "1", y2 "0", transform "rotate(45),scale(100,1)", stroke "url(#MyGradient)" ] []
+ , Svg.line [ x1 "0", y1 "0", x2 "1", y2 "0", transform "rotate(180),scale(100,1)", stroke "url(#MyGradient)" ] []
+ , Svg.line [ x1 "0", y1 "0", x2 "1", y2 "0", transform "rotate(270),scale(100,1)", stroke "url(#MyGradient)" ] []
+ ]
+ ]
+
+
+*TODO:* We need to fix the viewbox!
+
+
+Here's the trick: we have the lines initially match the gradient. We're using the same x and y values for all the lines and the gradient. Then we transform the lines to position them where we'd like them. We're actually using our old friend polar coordinates here.
+
+If you're interested in what `gradientUnits "userSpaceOnUse"` does, come see me after the lesson.
+
+
+| monospace
+ gradient : Int -> String -> Svg msg
+ gradient index color =
+ Svg.linearGradient
+ [ id ("Gradient-" ++ String.fromInt index)
+ , x1 "0"
+ , y1 "0"
+ , x2 "1"
+ , y2 "0"
+ , gradientUnits "userSpaceOnUse"
+ ]
+ [ Svg.stop
+ [ Svg.Attributes.offset "0"
+ , Svg.Attributes.stopColor "saddlebrown"
+ ]
+ []
+ , Svg.stop
+ [ Svg.Attributes.offset "1"
+ , Svg.Attributes.stopColor color
+ ]
+ []
+ ]
+
+| monospace
+ line : Int -> String -> Svg msg
+ line index color =
+ let
+ count =
+ List.length pallete
+
+ angle =
+ (360 / toFloat count) * toFloat index
+
+ transformation =
+ "rotate("
+ ++ String.fromFloat angle
+ ++ "), scale("
+ ++ String.fromInt radius
+ ++ ", 1)"
+
+| monospace
+ url =
+ "url(#Gradient-" ++ String.fromInt index ++ ")"
+ in
+ Svg.line
+ [ x1 "0"
+ , y1 "0"
+ , x2 "1"
+ , y2 "0"
+ , transform transformation
+ , stroke url
+ ]
+ []
+
+
+| monospace
+ main =
+ svg [ width "600", height "600", viewBox "-300 -300 600 600" ]
+ [ Svg.g [] (List.indexedMap dot pallete)
+ , Svg.defs [] (List.indexedMap gradient pallete)
+ , Svg.g [] (List.indexedMap line pallete)
+ ]
+
+
+This may look like a lot, but our `gradient` and `line` functions are very similar to our `dot` function from earlier. In fact we see an essential principle of good programming design here. We spent a lot of time banging our heads over gradients. Now that we've done that work, we can hide the implementation in our gradient and line functions. Now, if we want to draw a new dot, line, and gradient going from the center to the dot, we only need to add a new item to our palette. Our functions do the rest for us. Our functions conceal our complexity. We can forget about the implementation, so long as we know how to operate them. They're like black boxes, we know what goes in and what comes out, but we don't have to know how they work on the inside.
+
+
+| header
+ Day 5 - Let's make a beautiful tree!
+
+The Problem:
+Describe A tree using data
+
+A tree is a composition of segments. A segment can be either a twig or a branch. Twig is a terminal segment of a tree. When the tree is very young it consists of a single twig. Later this twig will evolve into a branch and will grow it's own twigs, that in turn will evolve into branches. So a branch is a segment of a tree that ends with some other segments (twigs or branches).
+
+
+Note that a branch can split to several branches that each in turn can split into several branches and so on. This way a tree can be as complex as we want. Note that real trees are like that. It's difficult to say how many generations of branches it can have from the trunk to the smallest little twigs on top of the crown. This kind of structure is called recursive. It repeats the same pattern.
+
+
+Each segment (a twig or a branch) has length, a direction (represented as an angle) and a color (so that it's beautiful despite having no leafs or flowers).
+
+Here is how we can represent it in code:
+
+| monospace
+ type alias Segment =
+ { length : Float
+ , direction : Float
+ , color : Color
+ }
+
+
+Consider the following
+
+| list
+ - A branch can split into more branches or twigs,
+ - And then that a trunk of a tree is just a first branch or a twig if the tree is very young, then you can see that each segment separated from the parent branch can be considered a tree.
+
+
+So we can reuse the same structure to represent a tree or any of it's segments!
+
+Look:
+
+| monospace
+ type Tree
+ = Twig Segment
+ | Branch Segment (List Tree)
+
+
+Think about it! A tree is a twig (when it's young) or a branch that splits into __a zero, one or many__ __twigs or branches__.
+
+| list
+ -> zero, one or many: a list
+ -> twig or branch: a tree
+
+
+So a Twig is just a Segment while a branch is a Segment with a list of Trees!
+
+Let's encode a simple tree by hand:
+
+| monospace
+ tree =
+ Branch { length = 10, direction = degrees -90, color = "brown" }
+ [ Branch { length = 6, direction = degrees -45, color = "lightbrown" }
+ [ Twig { length = 2, direction = degrees -30, color = "green" }
+ , Twig { length = 2, direction = degrees -60, color = "green" }
+ ]
+ , Branch { length = 6, direction = degrees -135, color = "lightbrown" }
+ [ Twig { length = 2, direction = degrees -120, color = "green" }
+ , Twig { length = 2, direction = degrees -150, color = "green" }
+ ]
+ ]
+
+
+| header
+ Day 6 - Make the tree grow!
+
+Make a function that given the age of the tree and rules returns a tree
+
+Use time subscription to make the tree grow
+
+Show complete code.
+There is a lot going on here, and it's not obvious how it all works.
+
+Our main value has changed.
+
+| monospace
+ main : Program () State Msg
+ main =
+ Browser.sandbox
+ { init = init
+ , view = view
+ , update = update
+ }
+
+Previously, the value of `main` was an SVG element. Now it contains a call to `Browser.sandbox` function.
+
+A sandbox is a basic interactive program. For a program to be interactive, it need more than just a SVG element.
+
+| monospace
+ type alias State =
+ Color
+ type alias Color =
+ String
+
+
+Here we create something called a type alias. We've already discussed types. A type alias is basically a way to give an alternative name for a type. We give the alias `Color` to `String` and the alias `State` to `Color`. So in the end all three names point to the same type!
+
+
+Why are we doing this? All of these types are strings, and Elm will treat them all as strings. But it will make our code more readable to use these aliases. We are writing an application that allows the user to change the background color. That should explain why our `State` is a `Color`.
+
+
+Our `Browser.sandbox` takes something with the name `init`. This is our initial state. Let's take a look at the value of `init`.
+
+| monospace
+ init : State
+ init =
+ "white"
+
+
+We see that the initial state is `"white"`. We'll see how our application uses this value in a moment.
+
+
+Looking back at our `sandbox`, we see it also takes a `view`. Let's take a look at our view function:
+
+| monospace
+ view : State -> Html Msg
+ view color =
+ svg
+ [ width "600"
+ , height "600"
+ , viewBox "-300 -300 600 600"
+ , Svg.Attributes.style ("background: " ++ color)
+ ]
+ [ Svg.g [] (List.indexedMap dot pallete)
+ , Svg.defs [] (List.indexedMap gradient pallete)
+ , Svg.g [] (List.indexedMap line pallete)
+ ]
+
+
+You'll notice that this `view` function looks very similar to the `main` variable from earlier. Before, our application was only a view. Now the view is only one piece of our interactive application.
+
+
+One important way our `view` function differs from the `main` variable from ealier is that `view` is a function. We see it takes a variable of type `State` (a string), which we have named `color`. In an Elm sandbox project the `view` always takes a variable with the same type as the `init` variable. This is the current state of the application. `view` can use the state to render the application properly, just as Atom uses its state to render text for its user.
+
+
+We see that we've added a line to the svg attributes, `Svg.Attributes.style ("background: " ++ color)`. Here we use the state of the application to set the background color of the svg element.
+
+
+All we need now is some way to update the state (the background color) of the application.
+
+
+Taking a look at our `main` function, we see that it take a third and final function, `update`. Let's look at our update function:
+
+| monospace
+ type Msg
+ = SetBackground Color
+ update : Msg -> State -> State
+ update msg state =
+ case msg of
+ SetBackground color ->
+ color
+
+
+
+We see our `update` function takes two variables, of type `Msg` and `State`. If we think of update as a machine which takes an old state and spits out a new state, this state variable corresponds to the old state.
+
+
+The `msg` variable is something new. Looking above, we see that we have defined a `Msg` variable type. This is similar to our `State` and `Color` union types, in that we have defined a new type that Elm understands. However, unlike the `type alias` constructor, the `type` constructor does not simply give a new name for an existing type. Instead, here we define a completely unique type. We see values with type Msg must have the form `SetBackground Color`. `SetBackground "white"`, `SetBackground "blue"`, `SetBackground "saddlebrown"` are all valid values of the type `Msg`.
+
+
+What is important to understand is that our `msg` variable is used by `update` to determine how it should update the state. We see that update first checks that `msg` has the form `SetBackground color`, and then returns the value of `color`. This will become our new state. Every time update is called, the sandbox will re-render our view with the new state.
+
+
+So now we see how our application changes the background color of the svg element. However, we haven't seen when it will update the background color. Well, we want to change the background color by clicking a dot. So let's take a look at our `dot` function to see if we can find a hint there.
+
+
+*Model*
+The structure of the state of the program
+
+| note
+ *TO DO* /Consistently use Model and model instead of less standard state to make it easier to our participants.
+
+
+
+*Msg*
+What events can there be in the program? Currently have only one possible event- setting the background to a given color.
+
+
+`update`
+
+The instructions of how to change the state when a particular message comes in.
+
+`view`
+
+The instruction of what to display on the screen. It also contains instructions of what messages to send when certain events (like clicks on elements) happen. Every time the state changes the instructions will be applied.
+
+`init`
+
+What is the initial state right after the program is started.
+
+`main`
+
+Glues it all together.
+
+| monospace
+ dot : Int -> String -> Svg Msg
+ dot index color =
+ let
+ angle =
+ (360 / toFloat (List.length pallete)) * toFloat index
+ in
+ circle
+ [ cx (x angle)
+ , cy (y angle)
+ , r "40"
+ , fill color
+ , Svg.Events.onClick (SetBackground color)
+ ]
+ []
+
+
+
+We see there is a new line here:
+
+
+| monospace
+ Svg.Events.onClick (SetBackground color)
+
+
+
+
+`onClick` is an event. It basically tells our svg element to listen for someone to click on it. It will then handle the event by emitting the Msg `SetBackground color`. We know `color` is the color of the dot. We see then that when someone clicks on the dot, the Msg will be emitted, `update` will be called with this Msg and will update our state. Our sandbox will rerender the view with this new state. The user will see the svg element with a new background color.
+
+
+Notice that our `view` function, as well as all the functions that call functions responsible for rendering svg elements, including `gradient`, `line`, and `dot` have return values with type `Svg Msg` or `Html Msg`. This is because svg element can emit Msgs which will be handled by `update`.
=
=| header
= To do:Merge remote-tracking branch 'origin/master' into elm-markup-2
Merge branch 'elm-markup-2' into 'master'
Update Elm Markup package to 2.0.2
See merge request software-garden/software-garden.gitlab.io!1
Commits: 1
Add terminal image
index 138e422..e9d30d1 100644
--- a/index.txt
+++ b/index.txt
@@ -40,9 +40,22 @@ To install the Elm programming language, go to it's website:
=
=
=and follow the installation instructions.
+Some of the tools we use with Elm require [Node.js.](https://nodejs.org/en/)
+Go to the [Node.js](https://nodejs.org/en/) website and install the current version (the green button on the right)
=
+We will need to use the terminal a little bit.
=
=
+| emphasize
+ >_
+
+
+Don't be scared. It's easy.
+
+
+| image "/assets/mac-launchpad-terminal.png"
+ "Here is a picture of terminal"
+
=
=
=| header
Commits: 3
Use alekzonder/puppeteer:1.0.0 docker image in gitlab CI/CD
index 8687ffe..ad4f592 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,4 +1,4 @@
-image: node:10.9.0
+image: alekzonder/puppeteer:1.0.0
=
=before_script:
= - npm installEnable puppeteer in CI/CD
Use capture.coffee script to render HTML
index ad4f592..74b4280 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -9,8 +9,8 @@ pages:
= - npx elm make src/Main.elm --output public/index.js
= - cp index.txt container.html public/
= ## FIXME: Make puppeteer work in the container
- - cp container.html public/index.html
- # - npx coffee src/capture.coffee > public/index.html
+ #- cp container.html public/index.html
+ - npx coffee src/capture.coffee > public/index.html
=
= artifacts:
= paths:Add options required by puppeteer container image to puppeteer.launch
index adc6353..8764395 100644
--- a/src/capture.coffee
+++ b/src/capture.coffee
@@ -6,7 +6,12 @@ do () =>
= app.use express.static "public/"
= server = app.listen 8000
=
- browser = await puppeteer.launch ``
+ browser = await puppeteer.launch
+ args: [
+ "--no-sandbox"
+ "--disable-setuid-sandbox"
+ ]
+
= page = await browser.newPage ``
=
= await page.goto "http://localhost:8000/container.html",
Commits: 9
Remove FeatherIcons import from Main module
It leaked there from another branch.
index acbf627..ac0641b 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -10,7 +10,6 @@ import Element.Background as Background
=import Element.Border as Border
=import Element.Events
=import Element.Input as Input
-import FeatherIcons exposing (icons)
=import FillTheScreen
=import Gradient
=import Html exposing (Html)Make an SVG element representing viewport fill the container in the ViewBox example running on Chrome
index 5a4bad5..b3f0a44 100644
--- a/src/ViewBox.elm
+++ b/src/ViewBox.elm
@@ -130,7 +130,7 @@ ui model =
=
= False ->
= "none"
- , Svg.Attributes.style "width: 100%; height: 100%"
+ , Svg.Attributes.style "width: 100%; height: 100%; flex-grow: 1; flex-basis: 0"
= ]
= [ g [] world
= , rectConfigure project for GitLab pages
Make git ignore /index.html and /public/
index 1cf77ef..14d21f1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,5 @@
=/**/.DS_Store
=*.swp
=.#*
+/index.html
+/public/new file mode 100644
index 0000000..0a8a769
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,15 @@
+image: node:7.2.0
+
+before_script:
+ - npm install -g elm@0.19.0-bugfix2
+
+pages:
+ stage: deploy
+ script:
+ - elm make src/Main.elm --output public/index.html
+ - cp index.txt public/
+ artifacts:
+ paths:
+ - public
+ only:
+ - masterMerge branch 'master' into elm-markup-content
Implement capture script that will generate a static HTML file from Elm program
For users with disabled JS.
index 14d21f1..266c915 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@
=.#*
=/index.html
=/public/
+/node_modules/index 0a8a769..0ecc581 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,13 +1,15 @@
=image: node:7.2.0
=
=before_script:
- - npm install -g elm@0.19.0-bugfix2
+ - npm install
=
=pages:
= stage: deploy
= script:
- - elm make src/Main.elm --output public/index.html
- - cp index.txt public/
+ - elm make src/Main.elm --output public/index.js
+ - cp index.txt container.html public/
+ - capture > public/index.html
+
= artifacts:
= paths:
= - publicnew file mode 100644
index 0000000..b30969d
--- /dev/null
+++ b/container.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html lang="en" dir="ltr">
+ <head>
+ <meta charset="utf-8">
+ <title>Software Garden</title>
+ </head>
+ <body>
+ <div id="app-container"></div>
+ <script src="/index.js"></script>
+ <script>
+ Elm.Main.init({ node: document.querySelector("#app-container") })
+ </script>
+ </body>
+</html>new file mode 100644
index 0000000..3125dbc
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,1295 @@
+{
+ "name": "website",
+ "version": "1.0.0",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "accepts": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz",
+ "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=",
+ "requires": {
+ "mime-types": "~2.1.18",
+ "negotiator": "0.6.1"
+ }
+ },
+ "agent-base": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz",
+ "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==",
+ "requires": {
+ "es6-promisify": "^5.0.0"
+ }
+ },
+ "ajv": {
+ "version": "6.6.1",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.1.tgz",
+ "integrity": "sha512-ZoJjft5B+EJBjUyu9C9Hc0OZyPZSSlOF+plzouTrg6UlA8f+e/n8NIgBFG/9tppJtpPWfthHakK7juJdNDODww==",
+ "requires": {
+ "fast-deep-equal": "^2.0.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ }
+ },
+ "array-flatten": {
+ "version": "1.1.1",
+ "resolved": "http://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
+ },
+ "asn1": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
+ "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
+ "requires": {
+ "safer-buffer": "~2.1.0"
+ }
+ },
+ "assert-plus": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
+ },
+ "async-limiter": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
+ "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
+ },
+ "asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
+ },
+ "aws-sign2": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
+ "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
+ },
+ "aws4": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
+ "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ=="
+ },
+ "balanced-match": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
+ },
+ "bcrypt-pbkdf": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
+ "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
+ "requires": {
+ "tweetnacl": "^0.14.3"
+ }
+ },
+ "binary": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz",
+ "integrity": "sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=",
+ "requires": {
+ "buffers": "~0.1.1",
+ "chainsaw": "~0.1.0"
+ }
+ },
+ "binwrap": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/binwrap/-/binwrap-0.1.4.tgz",
+ "integrity": "sha1-yh94cDAiElGPoksHcm+cUKFcdVk=",
+ "requires": {
+ "request": "^2.81.0",
+ "request-promise": "^4.2.0",
+ "tar": "^2.2.1",
+ "unzip": "^0.1.11"
+ }
+ },
+ "block-stream": {
+ "version": "0.0.9",
+ "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
+ "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=",
+ "requires": {
+ "inherits": "~2.0.0"
+ }
+ },
+ "bluebird": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz",
+ "integrity": "sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw=="
+ },
+ "body-parser": {
+ "version": "1.18.3",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz",
+ "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=",
+ "requires": {
+ "bytes": "3.0.0",
+ "content-type": "~1.0.4",
+ "debug": "2.6.9",
+ "depd": "~1.1.2",
+ "http-errors": "~1.6.3",
+ "iconv-lite": "0.4.23",
+ "on-finished": "~2.3.0",
+ "qs": "6.5.2",
+ "raw-body": "2.3.3",
+ "type-is": "~1.6.16"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ }
+ }
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "buffer-from": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
+ "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
+ },
+ "buffers": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz",
+ "integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s="
+ },
+ "bytes": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
+ "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
+ },
+ "caseless": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+ "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
+ },
+ "chainsaw": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz",
+ "integrity": "sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=",
+ "requires": {
+ "traverse": ">=0.3.0 <0.4"
+ }
+ },
+ "coffeescript": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-2.3.2.tgz",
+ "integrity": "sha512-YObiFDoukx7qPBi/K0kUKyntEZDfBQiqs/DbrR1xzASKOBjGT7auD85/DiPeRr9k++lRj7l3uA9TNMLfyfcD/Q=="
+ },
+ "combined-stream": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz",
+ "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==",
+ "requires": {
+ "delayed-stream": "~1.0.0"
+ }
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+ },
+ "concat-stream": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
+ "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
+ "requires": {
+ "buffer-from": "^1.0.0",
+ "inherits": "^2.0.3",
+ "readable-stream": "^2.2.2",
+ "typedarray": "^0.0.6"
+ }
+ },
+ "content-disposition": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
+ "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ="
+ },
+ "content-type": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
+ "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
+ },
+ "cookie": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
+ "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s="
+ },
+ "cookie-signature": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
+ },
+ "core-util-is": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+ },
+ "dashdash": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+ "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+ "requires": {
+ "assert-plus": "^1.0.0"
+ }
+ },
+ "debug": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz",
+ "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==",
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
+ },
+ "depd": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+ "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
+ },
+ "destroy": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
+ "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
+ },
+ "ecc-jsbn": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
+ "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
+ "requires": {
+ "jsbn": "~0.1.0",
+ "safer-buffer": "^2.1.0"
+ }
+ },
+ "ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
+ },
+ "elm": {
+ "version": "0.19.0-bugfix2",
+ "resolved": "https://registry.npmjs.org/elm/-/elm-0.19.0-bugfix2.tgz",
+ "integrity": "sha512-kEbsC7SzTo6B2aq9ZEhdNZnqSehqVZvdXcm2FBKIAp1eRa1pxQr7VG1WXF5oC/XtYoQaQHVT3QMt5/PqA00ygg==",
+ "requires": {
+ "binwrap": "0.1.4"
+ }
+ },
+ "encodeurl": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
+ },
+ "es6-promise": {
+ "version": "4.2.5",
+ "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.5.tgz",
+ "integrity": "sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg=="
+ },
+ "es6-promisify": {
+ "version": "5.0.0",
+ "resolved": "http://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
+ "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=",
+ "requires": {
+ "es6-promise": "^4.0.3"
+ }
+ },
+ "escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
+ },
+ "etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
+ },
+ "express": {
+ "version": "4.16.4",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz",
+ "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==",
+ "requires": {
+ "accepts": "~1.3.5",
+ "array-flatten": "1.1.1",
+ "body-parser": "1.18.3",
+ "content-disposition": "0.5.2",
+ "content-type": "~1.0.4",
+ "cookie": "0.3.1",
+ "cookie-signature": "1.0.6",
+ "debug": "2.6.9",
+ "depd": "~1.1.2",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "1.1.1",
+ "fresh": "0.5.2",
+ "merge-descriptors": "1.0.1",
+ "methods": "~1.1.2",
+ "on-finished": "~2.3.0",
+ "parseurl": "~1.3.2",
+ "path-to-regexp": "0.1.7",
+ "proxy-addr": "~2.0.4",
+ "qs": "6.5.2",
+ "range-parser": "~1.2.0",
+ "safe-buffer": "5.1.2",
+ "send": "0.16.2",
+ "serve-static": "1.13.2",
+ "setprototypeof": "1.1.0",
+ "statuses": "~1.4.0",
+ "type-is": "~1.6.16",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ }
+ }
+ },
+ "extend": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
+ },
+ "extract-zip": {
+ "version": "1.6.7",
+ "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz",
+ "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=",
+ "requires": {
+ "concat-stream": "1.6.2",
+ "debug": "2.6.9",
+ "mkdirp": "0.5.1",
+ "yauzl": "2.4.1"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ }
+ }
+ },
+ "extsprintf": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
+ "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
+ },
+ "fast-deep-equal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
+ "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk="
+ },
+ "fast-json-stable-stringify": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
+ "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I="
+ },
+ "fd-slicer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz",
+ "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=",
+ "requires": {
+ "pend": "~1.2.0"
+ }
+ },
+ "finalhandler": {
+ "version": "1.1.1",
+ "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz",
+ "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==",
+ "requires": {
+ "debug": "2.6.9",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "on-finished": "~2.3.0",
+ "parseurl": "~1.3.2",
+ "statuses": "~1.4.0",
+ "unpipe": "~1.0.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ }
+ }
+ },
+ "forever-agent": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+ "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
+ },
+ "form-data": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
+ "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
+ "requires": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.6",
+ "mime-types": "^2.1.12"
+ }
+ },
+ "forwarded": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
+ "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
+ },
+ "fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
+ },
+ "fstream": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz",
+ "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=",
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "inherits": "~2.0.0",
+ "mkdirp": ">=0.5 0",
+ "rimraf": "2"
+ }
+ },
+ "getpass": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+ "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+ "requires": {
+ "assert-plus": "^1.0.0"
+ }
+ },
+ "glob": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
+ "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "graceful-fs": {
+ "version": "4.1.15",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz",
+ "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA=="
+ },
+ "har-schema": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
+ "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
+ },
+ "har-validator": {
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
+ "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
+ "requires": {
+ "ajv": "^6.5.5",
+ "har-schema": "^2.0.0"
+ }
+ },
+ "http-errors": {
+ "version": "1.6.3",
+ "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
+ "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
+ "requires": {
+ "depd": "~1.1.2",
+ "inherits": "2.0.3",
+ "setprototypeof": "1.1.0",
+ "statuses": ">= 1.4.0 < 2"
+ }
+ },
+ "http-signature": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
+ "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
+ "requires": {
+ "assert-plus": "^1.0.0",
+ "jsprim": "^1.2.2",
+ "sshpk": "^1.7.0"
+ }
+ },
+ "https-proxy-agent": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz",
+ "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==",
+ "requires": {
+ "agent-base": "^4.1.0",
+ "debug": "^3.1.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+ "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ }
+ }
+ },
+ "iconv-lite": {
+ "version": "0.4.23",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
+ "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==",
+ "requires": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ }
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "requires": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "inherits": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+ },
+ "ipaddr.js": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz",
+ "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4="
+ },
+ "is-typedarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+ "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+ },
+ "isstream": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
+ },
+ "jsbn": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
+ },
+ "json-schema": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
+ "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
+ },
+ "json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
+ },
+ "json-stringify-safe": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
+ },
+ "jsprim": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
+ "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
+ "requires": {
+ "assert-plus": "1.0.0",
+ "extsprintf": "1.3.0",
+ "json-schema": "0.2.3",
+ "verror": "1.10.0"
+ }
+ },
+ "lodash": {
+ "version": "4.17.11",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
+ "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
+ },
+ "match-stream": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/match-stream/-/match-stream-0.0.2.tgz",
+ "integrity": "sha1-mesFAJOzTf+t5CG5rAtBCpz6F88=",
+ "requires": {
+ "buffers": "~0.1.1",
+ "readable-stream": "~1.0.0"
+ },
+ "dependencies": {
+ "isarray": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
+ },
+ "readable-stream": {
+ "version": "1.0.34",
+ "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
+ "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.1",
+ "isarray": "0.0.1",
+ "string_decoder": "~0.10.x"
+ }
+ },
+ "string_decoder": {
+ "version": "0.10.31",
+ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+ "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
+ }
+ }
+ },
+ "media-typer": {
+ "version": "0.3.0",
+ "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
+ },
+ "merge-descriptors": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+ "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
+ },
+ "methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
+ },
+ "mime": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz",
+ "integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w=="
+ },
+ "mime-db": {
+ "version": "1.37.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz",
+ "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg=="
+ },
+ "mime-types": {
+ "version": "2.1.21",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz",
+ "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==",
+ "requires": {
+ "mime-db": "~1.37.0"
+ }
+ },
+ "minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
+ "minimist": {
+ "version": "0.0.8",
+ "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
+ },
+ "mkdirp": {
+ "version": "0.5.1",
+ "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+ "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+ "requires": {
+ "minimist": "0.0.8"
+ }
+ },
+ "ms": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+ "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
+ },
+ "natives": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.6.tgz",
+ "integrity": "sha512-6+TDFewD4yxY14ptjKaS63GVdtKiES1pTPyxn9Jb0rBqPMZ7VcCiooEhPNsr+mqHtMGxa/5c/HhcC4uPEUw/nA=="
+ },
+ "negotiator": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
+ "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk="
+ },
+ "oauth-sign": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
+ "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
+ },
+ "on-finished": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+ "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+ "requires": {
+ "ee-first": "1.1.1"
+ }
+ },
+ "once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "requires": {
+ "wrappy": "1"
+ }
+ },
+ "over": {
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/over/-/over-0.0.5.tgz",
+ "integrity": "sha1-8phS5w/X4l82DgE6jsRMgq7bVwg="
+ },
+ "parseurl": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz",
+ "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M="
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
+ },
+ "path-to-regexp": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+ "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
+ },
+ "pend": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
+ "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA="
+ },
+ "performance-now": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
+ },
+ "process-nextick-args": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
+ "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
+ },
+ "progress": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.2.tgz",
+ "integrity": "sha512-/OLz5F9beZUWwSHZDreXgap1XShX6W+DCHQCqwCF7uZ88s6uTlD2cR3JBE77SegCmNtb1Idst+NfmwcdU6KVhw=="
+ },
+ "proxy-addr": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz",
+ "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==",
+ "requires": {
+ "forwarded": "~0.1.2",
+ "ipaddr.js": "1.8.0"
+ }
+ },
+ "proxy-from-env": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz",
+ "integrity": "sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4="
+ },
+ "psl": {
+ "version": "1.1.29",
+ "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz",
+ "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ=="
+ },
+ "pullstream": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/pullstream/-/pullstream-0.4.1.tgz",
+ "integrity": "sha1-1vs79a7Wl+gxFQ6xACwlo/iuExQ=",
+ "requires": {
+ "over": ">= 0.0.5 < 1",
+ "readable-stream": "~1.0.31",
+ "setimmediate": ">= 1.0.2 < 2",
+ "slice-stream": ">= 1.0.0 < 2"
+ },
+ "dependencies": {
+ "isarray": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
+ },
+ "readable-stream": {
+ "version": "1.0.34",
+ "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
+ "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.1",
+ "isarray": "0.0.1",
+ "string_decoder": "~0.10.x"
+ }
+ },
+ "string_decoder": {
+ "version": "0.10.31",
+ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+ "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
+ }
+ }
+ },
+ "punycode": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
+ },
+ "puppeteer": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-1.11.0.tgz",
+ "integrity": "sha512-iG4iMOHixc2EpzqRV+pv7o3GgmU2dNYEMkvKwSaQO/vMZURakwSOn/EYJ6OIRFYOque1qorzIBvrytPIQB3YzQ==",
+ "requires": {
+ "debug": "^4.1.0",
+ "extract-zip": "^1.6.6",
+ "https-proxy-agent": "^2.2.1",
+ "mime": "^2.0.3",
+ "progress": "^2.0.1",
+ "proxy-from-env": "^1.0.0",
+ "rimraf": "^2.6.1",
+ "ws": "^6.1.0"
+ }
+ },
+ "qs": {
+ "version": "6.5.2",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
+ "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
+ },
+ "range-parser": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
+ "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4="
+ },
+ "raw-body": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz",
+ "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==",
+ "requires": {
+ "bytes": "3.0.0",
+ "http-errors": "1.6.3",
+ "iconv-lite": "0.4.23",
+ "unpipe": "1.0.0"
+ }
+ },
+ "readable-stream": {
+ "version": "2.3.6",
+ "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "request": {
+ "version": "2.88.0",
+ "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
+ "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
+ "requires": {
+ "aws-sign2": "~0.7.0",
+ "aws4": "^1.8.0",
+ "caseless": "~0.12.0",
+ "combined-stream": "~1.0.6",
+ "extend": "~3.0.2",
+ "forever-agent": "~0.6.1",
+ "form-data": "~2.3.2",
+ "har-validator": "~5.1.0",
+ "http-signature": "~1.2.0",
+ "is-typedarray": "~1.0.0",
+ "isstream": "~0.1.2",
+ "json-stringify-safe": "~5.0.1",
+ "mime-types": "~2.1.19",
+ "oauth-sign": "~0.9.0",
+ "performance-now": "^2.1.0",
+ "qs": "~6.5.2",
+ "safe-buffer": "^5.1.2",
+ "tough-cookie": "~2.4.3",
+ "tunnel-agent": "^0.6.0",
+ "uuid": "^3.3.2"
+ }
+ },
+ "request-promise": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.2.tgz",
+ "integrity": "sha1-0epG1lSm7k+O5qT+oQGMIpEZBLQ=",
+ "requires": {
+ "bluebird": "^3.5.0",
+ "request-promise-core": "1.1.1",
+ "stealthy-require": "^1.1.0",
+ "tough-cookie": ">=2.3.3"
+ }
+ },
+ "request-promise-core": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz",
+ "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=",
+ "requires": {
+ "lodash": "^4.13.1"
+ }
+ },
+ "rimraf": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
+ "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==",
+ "requires": {
+ "glob": "^7.0.5"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+ },
+ "safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+ },
+ "send": {
+ "version": "0.16.2",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz",
+ "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==",
+ "requires": {
+ "debug": "2.6.9",
+ "depd": "~1.1.2",
+ "destroy": "~1.0.4",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "0.5.2",
+ "http-errors": "~1.6.2",
+ "mime": "1.4.1",
+ "ms": "2.0.0",
+ "on-finished": "~2.3.0",
+ "range-parser": "~1.2.0",
+ "statuses": "~1.4.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "mime": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz",
+ "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ=="
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ }
+ }
+ },
+ "serve-static": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz",
+ "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==",
+ "requires": {
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.2",
+ "send": "0.16.2"
+ }
+ },
+ "setimmediate": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
+ "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU="
+ },
+ "setprototypeof": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
+ "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ=="
+ },
+ "slice-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/slice-stream/-/slice-stream-1.0.0.tgz",
+ "integrity": "sha1-WzO9ZvATsaf4ZGCwPUY97DmtPqA=",
+ "requires": {
+ "readable-stream": "~1.0.31"
+ },
+ "dependencies": {
+ "isarray": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
+ },
+ "readable-stream": {
+ "version": "1.0.34",
+ "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
+ "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.1",
+ "isarray": "0.0.1",
+ "string_decoder": "~0.10.x"
+ }
+ },
+ "string_decoder": {
+ "version": "0.10.31",
+ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+ "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
+ }
+ }
+ },
+ "sshpk": {
+ "version": "1.15.2",
+ "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz",
+ "integrity": "sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA==",
+ "requires": {
+ "asn1": "~0.2.3",
+ "assert-plus": "^1.0.0",
+ "bcrypt-pbkdf": "^1.0.0",
+ "dashdash": "^1.12.0",
+ "ecc-jsbn": "~0.1.1",
+ "getpass": "^0.1.1",
+ "jsbn": "~0.1.0",
+ "safer-buffer": "^2.0.2",
+ "tweetnacl": "~0.14.0"
+ }
+ },
+ "statuses": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
+ "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew=="
+ },
+ "stealthy-require": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
+ "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks="
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "tar": {
+ "version": "2.2.1",
+ "resolved": "http://registry.npmjs.org/tar/-/tar-2.2.1.tgz",
+ "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=",
+ "requires": {
+ "block-stream": "*",
+ "fstream": "^1.0.2",
+ "inherits": "2"
+ }
+ },
+ "tough-cookie": {
+ "version": "2.4.3",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
+ "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
+ "requires": {
+ "psl": "^1.1.24",
+ "punycode": "^1.4.1"
+ },
+ "dependencies": {
+ "punycode": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+ "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
+ }
+ }
+ },
+ "traverse": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz",
+ "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk="
+ },
+ "tunnel-agent": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+ "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
+ "requires": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "tweetnacl": {
+ "version": "0.14.5",
+ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
+ },
+ "type-is": {
+ "version": "1.6.16",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz",
+ "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==",
+ "requires": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.18"
+ }
+ },
+ "typedarray": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
+ },
+ "unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
+ },
+ "unzip": {
+ "version": "0.1.11",
+ "resolved": "https://registry.npmjs.org/unzip/-/unzip-0.1.11.tgz",
+ "integrity": "sha1-iXScY7BY19kNYZ+GuYqhU107l/A=",
+ "requires": {
+ "binary": ">= 0.3.0 < 1",
+ "fstream": ">= 0.1.30 < 1",
+ "match-stream": ">= 0.0.2 < 1",
+ "pullstream": ">= 0.4.1 < 1",
+ "readable-stream": "~1.0.31",
+ "setimmediate": ">= 1.0.1 < 2"
+ },
+ "dependencies": {
+ "fstream": {
+ "version": "0.1.31",
+ "resolved": "https://registry.npmjs.org/fstream/-/fstream-0.1.31.tgz",
+ "integrity": "sha1-czfwWPu7vvqMn1YaKMqwhJICyYg=",
+ "requires": {
+ "graceful-fs": "~3.0.2",
+ "inherits": "~2.0.0",
+ "mkdirp": "0.5",
+ "rimraf": "2"
+ }
+ },
+ "graceful-fs": {
+ "version": "3.0.11",
+ "resolved": "http://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.11.tgz",
+ "integrity": "sha1-dhPHeKGv6mLyXGMKCG1/Osu92Bg=",
+ "requires": {
+ "natives": "^1.1.0"
+ }
+ },
+ "isarray": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
+ },
+ "readable-stream": {
+ "version": "1.0.34",
+ "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
+ "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.1",
+ "isarray": "0.0.1",
+ "string_decoder": "~0.10.x"
+ }
+ },
+ "string_decoder": {
+ "version": "0.10.31",
+ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+ "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
+ }
+ }
+ },
+ "uri-js": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
+ "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
+ "requires": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
+ },
+ "utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
+ },
+ "uuid": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
+ "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
+ },
+ "vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
+ },
+ "verror": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+ "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
+ "requires": {
+ "assert-plus": "^1.0.0",
+ "core-util-is": "1.0.2",
+ "extsprintf": "^1.2.0"
+ }
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+ },
+ "ws": {
+ "version": "6.1.2",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.2.tgz",
+ "integrity": "sha512-rfUqzvz0WxmSXtJpPMX2EeASXabOrSMk1ruMOV3JBTBjo4ac2lDjGGsbQSyxj8Odhw5fBib8ZKEjDNvgouNKYw==",
+ "requires": {
+ "async-limiter": "~1.0.0"
+ }
+ },
+ "yauzl": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz",
+ "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=",
+ "requires": {
+ "fd-slicer": "~1.0.1"
+ }
+ }
+ }
+}new file mode 100644
index 0000000..3432d08
--- /dev/null
+++ b/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "website",
+ "version": "1.0.0",
+ "description": "A website for Software Garden project",
+ "main": "built/index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+ssh://git@gitlab.com/software-garden/website.git"
+ },
+ "author": "",
+ "license": "ISC",
+ "bugs": {
+ "url": "https://gitlab.com/software-garden/website/issues"
+ },
+ "homepage": "https://gitlab.com/software-garden/website#README",
+ "dependencies": {
+ "coffeescript": "^2.3.2",
+ "elm": "^0.19.0-bugfix2",
+ "express": "^4.16.4",
+ "puppeteer": "^1.11.0"
+ }
+}index ce96065..29bbd46 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -14,6 +14,7 @@ import Element.Input as Input
=import FillTheScreen
=import Gradient
=import Html exposing (Html)
+import Html.Attributes
=import Http
=import Line
=import Mark
@@ -119,7 +120,9 @@ view model =
= |> Element.text
= in
= Element.layout
- []
+ [ -- Below is a hack that enables static site generation
+ Element.htmlAttribute (Html.Attributes.id "app-container")
+ ]
= content
=
=new file mode 100644
index 0000000..adc6353
--- /dev/null
+++ b/src/capture.coffee
@@ -0,0 +1,20 @@
+puppeteer = require "puppeteer"
+express = require "express"
+
+do () =>
+ app = express ``
+ app.use express.static "public/"
+ server = app.listen 8000
+
+ browser = await puppeteer.launch ``
+ page = await browser.newPage ``
+
+ await page.goto "http://localhost:8000/container.html",
+ waitUntil: "networkidle0"
+
+ html = await page.content ``
+
+ console.log html
+
+ await browser.close ``
+ server.close ``Fix GitLab CI config
index 0ecc581..438ae7e 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -6,9 +6,9 @@ before_script:
=pages:
= stage: deploy
= script:
- - elm make src/Main.elm --output public/index.js
+ - npx elm make src/Main.elm --output public/index.js
= - cp index.txt container.html public/
- - capture > public/index.html
+ - npx coffee src/capture > public/index.html
=
= artifacts:
= paths:CI try newer version of Node.js container
index 438ae7e..b1c5166 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,4 +1,4 @@
-image: node:7.2.0
+image: node:10.9.0
=
=before_script:
= - npm installCI: Fix path to capture.coffee script
index b1c5166..c2011f5 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -8,7 +8,7 @@ pages:
= script:
= - npx elm make src/Main.elm --output public/index.js
= - cp index.txt container.html public/
- - npx coffee src/capture > public/index.html
+ - npx coffee src/capture.coffee > public/index.html
=
= artifacts:
= paths:Temporarily disable capture script in CI
The puppeteer does not work in a container. To be fixed later.
index c2011f5..8687ffe 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -8,7 +8,9 @@ pages:
= script:
= - npx elm make src/Main.elm --output public/index.js
= - cp index.txt container.html public/
- - npx coffee src/capture.coffee > public/index.html
+ ## FIXME: Make puppeteer work in the container
+ - cp container.html public/index.html
+ # - npx coffee src/capture.coffee > public/index.html
=
= artifacts:
= paths:
Commits: 5
Make BrowserWindow example (main) bigger
index 2714ed3..b5db0fa 100644
--- a/src/BrowserWindow.elm
+++ b/src/BrowserWindow.elm
@@ -10,6 +10,10 @@ import Html exposing (Html)
=main : Html msg
=main =
= Element.text "Hello World!"
+ |> Element.el
+ [ Element.width (Element.px 400)
+ , Element.height (Element.px 400)
+ ]
= |> window
= |> Element.layout []
=Remove the ui helper from Simplest and Centered dot
Instead move this code to Main module.
index fef1177..7c7ef14 100644
--- a/src/CenteredDot.elm
+++ b/src/CenteredDot.elm
@@ -6,18 +6,16 @@ import Svg.Attributes
=
=
=main =
- Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
+ Svg.svg
+ [ Svg.Attributes.style "background: pink"
+ , Svg.Attributes.width "300"
+ , Svg.Attributes.height "150"
+ , Svg.Attributes.viewBox "-100 -100 200 200"
= ]
- ui
-
-
-ui =
- Element.html
- (Svg.svg
- [ Svg.Attributes.viewBox "-100 -100 200 200"
- , Svg.Attributes.height "200"
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
= ]
- [ Svg.circle [ Svg.Attributes.r "10" ] [] ]
- )
+ []
+ ]index 0591a0c..a32f254 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -225,8 +225,17 @@ options =
=
= simplestView : Styling -> Model -> Element Msg
= simplestView style model =
- Simplest.ui
- |> Element.el []
+ Simplest.main
+ |> Element.html
+ |> Element.el
+ [ Element.width (Element.px 300)
+ , Element.height (Element.px 150)
+ ]
+ |> Element.el
+ [ Element.height (Element.minimum 400 Element.shrink)
+ , Element.width Element.fill
+ , Element.padding 10
+ ]
= |> BrowserWindow.window
=
= fillTheScreenBlock : Mark.Custom.Block Model Styling Msg
@@ -248,9 +257,11 @@ options =
=
= centeredDotView : Styling -> Model -> Element Msg
= centeredDotView style model =
- CenteredDot.ui
+ CenteredDot.main
+ |> Element.html
= |> Element.el
- [ Element.centerX
+ [ Element.height (Element.px 400)
+ , Element.width Element.fill
= ]
= |> BrowserWindow.window
=index 27930cc..dee7dbd 100644
--- a/src/Simplest.elm
+++ b/src/Simplest.elm
@@ -1,4 +1,4 @@
-module Simplest exposing (main, ui)
+module Simplest exposing (main)
=
=import Element
=import Svg
@@ -7,7 +7,10 @@ import Svg.Attributes
=
=main =
= Svg.svg
- [ Svg.Attributes.style "background: pink; height: 100%; width: 100%" ]
+ [ Svg.Attributes.style "background: pink"
+ , Svg.Attributes.width "300"
+ , Svg.Attributes.height "150"
+ ]
= [ Svg.circle
= [ Svg.Attributes.r "10"
= , Svg.Attributes.cx "30"
@@ -15,7 +18,3 @@ main =
= ]
= []
= ]
-
-
-ui =
- Element.html mainWrite a comment explaining how to position an object in the center of the viewBox
index 7c7ef14..59fb692 100644
--- a/src/CenteredDot.elm
+++ b/src/CenteredDot.elm
@@ -1,4 +1,23 @@
-module CenteredDot exposing (main, ui)
+module CenteredDot exposing (main)
+
+{-| This program demonstrates how to center an element within an SVG viewport. The idea is to make sure that (0, 0) point (so called origin) should be in the middle of the ViewBox. We can achive that, by making the top-left corner of the ViewBox same distance from the (0, 0) as the bottom right.
+
+The viewBox property takes a string consisting of four numbers:
+
+ - position of the top edge
+ - position of the left edge
+ - width
+ - and height
+
+If we set the width to 200 and then set the left edge to -100, that will place the right edge at +100. Both edges are 100 away from the 0!
+
+The same way you can control the bottom edge by tweaking the position of the top edge and the height.
+
+In fact it doesnt matter how wide and high the viewBox is. As long as the top will be -1/2 of the width and left will be -1/2 of the width.
+
+That's the trick to position an object (like a dot) exactly in the center of the SVG viewport
+
+-}
=
=import Element
=import SvgFix the embeded programs layouts
Make BrowserWindow.window take a list of attributes, so it's possible to size the window as required.
index b5db0fa..91c211c 100644
--- a/src/BrowserWindow.elm
+++ b/src/BrowserWindow.elm
@@ -14,12 +14,12 @@ main =
= [ Element.width (Element.px 400)
= , Element.height (Element.px 400)
= ]
- |> window
+ |> window []
= |> Element.layout []
=
=
-window : Element msg -> Element msg
-window content =
+window : List (Element.Attribute msg) -> Element msg -> Element msg
+window attributes content =
= let
= circle : Element.Color -> Element msg
= circle color =
@@ -34,32 +34,33 @@ window content =
= , Font.center
= ]
= in
- Element.column
- [ Element.width Element.shrink
- , Element.height Element.shrink
- , Border.width 2
- , Border.rounded 6
- , Border.color <| Element.rgb 0.27 0.27 0.27
- , Background.color <| Element.rgb 0.27 0.27 0.27
- , Element.clip
- ]
- [ Element.row
- [ Element.height <| Element.px <| 23
- , Element.width Element.fill
- , Border.widthEach { bottom = 2, left = 0, top = 0, right = 0 }
- , Border.color <| Element.rgb 0.2 0.2 0.2
- , Element.paddingXY 10 15
- ]
- ([ ( 1, 0.4, 0.3 ), ( 1, 0.75, 0.18 ), ( 0.16, 0.79, 0.26 ) ]
- |> List.map
- (\( red, green, blue ) ->
- circle (Element.rgb red green blue)
- )
- )
- , Element.el
+ Element.el attributes <|
+ Element.column
= [ Element.width Element.fill
- , Element.width Element.fill
- , Background.color <| Element.rgb 1 1 1
+ , Element.height Element.fill
+ , Border.width 2
+ , Border.rounded 6
+ , Border.color <| Element.rgb 0.27 0.27 0.27
+ , Background.color <| Element.rgb 0.27 0.27 0.27
+ , Element.clip
+ ]
+ [ Element.row
+ [ Element.height <| Element.px <| 23
+ , Element.width Element.fill
+ , Border.widthEach { bottom = 2, left = 0, top = 0, right = 0 }
+ , Border.color <| Element.rgb 0.2 0.2 0.2
+ , Element.paddingXY 10 15
+ ]
+ ([ ( 1, 0.4, 0.3 ), ( 1, 0.75, 0.18 ), ( 0.16, 0.79, 0.26 ) ]
+ |> List.map
+ (\( red, green, blue ) ->
+ circle (Element.rgb red green blue)
+ )
+ )
+ , Element.el
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ , Background.color <| Element.rgb 1 1 1
+ ]
+ content
= ]
- content
- ]index d7cfc83..56f88d6 100644
--- a/src/FillTheScreen.elm
+++ b/src/FillTheScreen.elm
@@ -14,14 +14,15 @@ main =
=
=
=ui =
- Element.html
- (Svg.svg
- [ Svg.Attributes.style "background: pink; height: 100%; width: 100%" ]
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "30"
- , Svg.Attributes.cy "30"
- ]
- []
+ Svg.svg
+ [ Svg.Attributes.style "background: pink"
+ , Svg.Attributes.viewBox "-100 -100 200 200"
+ ]
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
= ]
- )
+ []
+ ]
+ |> Element.htmlindex a32f254..acbf627 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -10,6 +10,7 @@ import Element.Background as Background
=import Element.Border as Border
=import Element.Events
=import Element.Input as Input
+import FeatherIcons exposing (icons)
=import FillTheScreen
=import Gradient
=import Html exposing (Html)
@@ -215,8 +216,13 @@ options =
= |> Counter.ui
= |> Element.el
= [ Element.centerX
+ , Element.centerY
+ ]
+ |> Element.el
+ [ Element.height (Element.px 400)
+ , Element.width Element.fill
= ]
- |> BrowserWindow.window
+ |> BrowserWindow.window []
= |> Element.map CounterMsg
=
= simplestBlock : Mark.Custom.Block Model Styling Msg
@@ -228,15 +234,11 @@ options =
= Simplest.main
= |> Element.html
= |> Element.el
- [ Element.width (Element.px 300)
- , Element.height (Element.px 150)
- ]
- |> Element.el
- [ Element.height (Element.minimum 400 Element.shrink)
+ [ Element.height (Element.px 400)
= , Element.width Element.fill
= , Element.padding 10
= ]
- |> BrowserWindow.window
+ |> BrowserWindow.window []
=
= fillTheScreenBlock : Mark.Custom.Block Model Styling Msg
= fillTheScreenBlock =
@@ -246,10 +248,10 @@ options =
= fillTheScreenView style model =
= FillTheScreen.ui
= |> Element.el
- [ Element.width Element.fill
- , Element.height Element.fill
+ [ Element.height (Element.px 400)
+ , Element.width Element.fill
= ]
- |> BrowserWindow.window
+ |> BrowserWindow.window []
=
= centeredDotBlock : Mark.Custom.Block Model Styling Msg
= centeredDotBlock =
@@ -262,8 +264,9 @@ options =
= |> Element.el
= [ Element.height (Element.px 400)
= , Element.width Element.fill
+ , Element.padding 10
= ]
- |> BrowserWindow.window
+ |> BrowserWindow.window []
=
= lineBlock : Mark.Custom.Block Model Styling Msg
= lineBlock =
@@ -275,7 +278,7 @@ options =
= |> Element.el
= [ Element.centerX
= ]
- |> BrowserWindow.window
+ |> BrowserWindow.window []
=
= gradientBlock : Mark.Custom.Block Model Styling Msg
= gradientBlock =
@@ -287,7 +290,7 @@ options =
= |> Element.el
= [ Element.centerX
= ]
- |> BrowserWindow.window
+ |> BrowserWindow.window []
=
= transformationsBlock : Mark.Custom.Block Model Styling Msg
= transformationsBlock =
@@ -298,7 +301,8 @@ options =
= model.transformations
= |> Transformations.ui
= |> Element.el
- [ Element.centerX
+ [ Element.height Element.fill
+ , Element.width Element.fill
= ]
= |> Element.el
= [ Element.centerX
@@ -377,7 +381,7 @@ options =
= |> Element.el
= [ Element.centerX
= ]
- |> BrowserWindow.window
+ |> BrowserWindow.window []
=
= spiralBlock : Mark.Custom.Block Model Styling Msg
= spiralBlock =
@@ -388,8 +392,11 @@ options =
= Spiral.ui
= |> Element.el
= [ Element.centerX
+ , Element.width Element.fill
+
+ -- , Element.height Element.fill
= ]
- |> BrowserWindow.window
+ |> BrowserWindow.window []
=
= treeBlock : Mark.Custom.Block Model Styling Msg
= treeBlock =
@@ -438,7 +445,7 @@ options =
= ]
= in
= content
- |> BrowserWindow.window
+ |> BrowserWindow.window []
=
= viewBoxBlock : Mark.Custom.Block Model Styling Msg
= viewBoxBlock =
@@ -448,9 +455,6 @@ options =
= viewBoxView style model =
= model.viewBox
= |> ViewBox.ui
- |> Element.el
- [ Element.centerX
- ]
= |> Element.el
= [ Element.centerX
= , Border.color (Element.rgb 1 0.6 0.6)index 27129df..1f34423 100644
--- a/src/NestedTransformations.elm
+++ b/src/NestedTransformations.elm
@@ -95,7 +95,7 @@ ui model =
= let
= wrapper element =
= Element.column
- [ Element.width (Element.maximum 600 Element.fill)
+ [ Element.width Element.fill
= , Element.centerX
= , Element.spacing 20
= ]index 09c888e..7e1ae87 100644
--- a/src/Spiral.elm
+++ b/src/Spiral.elm
@@ -40,10 +40,9 @@ ui =
= in
= Element.html
= (Svg.svg
- [ Svg.Attributes.height "400"
- , Svg.Attributes.viewBox "-100 -100 200 200"
+ [ Svg.Attributes.viewBox "-100 -100 200 200"
= ]
- (defs :: spiral 500)
+ (defs :: spiral 50)
= )
=
=
@@ -52,7 +51,7 @@ spiral age =
= if age > 0 then
= let
= length =
- 500 / toFloat age
+ 50 / toFloat age
= in
= [ Svg.line
= [ Svg.Attributes.x2 "1"index 5de8663..cfc9865 100644
--- a/src/ViewBox.elm
+++ b/src/ViewBox.elm
@@ -73,49 +73,48 @@ update : Msg -> Model -> Model
=update msg model =
= case msg of
= Move left top ->
- { model | left = left, top = top }
+ { model
+ | left = left
+ , top = top
+ }
=
= Resize width height ->
- { model | width = width, height = height }
+ { model
+ | width = width
+ , height = height
+ }
=
=
=ui model =
- Element.column
- [ Element.width Element.fill
- , Element.height Element.fill
- , Element.spacing 30
- , Element.padding 30
- ]
- [ Element.row
- [ Element.width Element.fill
- , Element.height Element.fill
- , Element.centerX
- , Element.spacing 30
- , Element.padding 30
- ]
- [ Element.el
- [ Element.height <| Element.maximum 500 Element.fill
- , Element.width <| Element.maximum 500 Element.fill
+ let
+ viewbox =
+ svg
+ [ viewBox "-500 -500 1000 1000"
+
+ -- , Svg.Attributes.style "width: 100%; height: 100%"
= ]
- (Element.html <|
- svg
- [ viewBox "0 0 1000 1000"
- , Svg.Attributes.style "width: 100%; height: 100%"
- ]
- [ g [] world
- , rect
- [ x (String.fromFloat model.left)
- , y (String.fromFloat model.top)
- , width (String.fromFloat model.width)
- , height (String.fromFloat model.height)
- , opacity "0.3"
- , stroke "white"
- , fill "pink"
- ]
- []
- ]
- )
- , svg
+ [ g [] world
+ , rect
+ [ x (String.fromFloat model.left)
+ , y (String.fromFloat model.top)
+ , width (String.fromFloat model.width)
+ , height (String.fromFloat model.height)
+ , opacity "0.3"
+ , stroke "white"
+ , fill "pink"
+ ]
+ []
+ ]
+ |> Element.html
+ |> Element.el
+ [ Element.centerX
+ , Element.centerY
+ , Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+ viewport =
+ svg
= [ [ model.left, model.top, model.width, model.height ]
= |> List.map String.fromFloat
= |> String.join " "
@@ -136,11 +135,31 @@ ui model =
= []
= ]
= |> Element.html
- |> BrowserWindow.window
= |> Element.el
= [ Element.width Element.fill
= , Element.height Element.fill
= ]
+ |> BrowserWindow.window
+ [ Element.centerX
+ , Element.centerY
+ , Element.width Element.fill
+ , Element.height Element.fill
+ ]
+ in
+ Element.column
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ , Element.spacing 30
+ , Element.padding 30
+ ]
+ [ Element.row
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ , Element.centerX
+ , Element.spacing 30
+ ]
+ [ viewbox
+ , viewport
= ]
= , Input.slider
= [ Element.behindContent
@@ -158,8 +177,8 @@ ui model =
= , label =
= Input.labelBelow [ Element.centerX ] <|
= Element.text ("left: " ++ String.fromFloat model.left)
- , min = 0
- , max = 1000 - model.width
+ , min = -500
+ , max = 500
= , value = model.left
= , thumb = Input.defaultThumb
= , step = Just 1
@@ -180,8 +199,8 @@ ui model =
= , label =
= Input.labelBelow [ Element.centerX ] <|
= Element.text ("top: " ++ String.fromFloat model.top)
- , min = 0
- , max = 1000 - model.width
+ , min = -500
+ , max = 500
= , value = model.top
= , thumb = Input.defaultThumb
= , step = Just 1
@@ -203,7 +222,7 @@ ui model =
= Input.labelBelow [ Element.centerX ] <|
= Element.text ("width: " ++ String.fromFloat model.width)
= , min = 1
- , max = 1000 - model.left
+ , max = 500
= , value = model.width
= , thumb = Input.defaultThumb
= , step = Just 1
@@ -225,7 +244,7 @@ ui model =
= Input.labelBelow [ Element.centerX ] <|
= Element.text ("height: " ++ String.fromFloat model.height)
= , min = 1
- , max = 1000 - model.top
+ , max = 500
= , value = model.height
= , thumb = Input.defaultThumb
= , step = Just 1
@@ -237,5 +256,5 @@ world : List (Svg Msg)
=world =
= [ circle [ cx "400", cy "300", r "250", fill "purple" ] []
= , circle [ cx "200", cy "350", r "50", fill "yellow" ] []
- , circle [ cx "400", cy "600", r "20", fill "red" ] []
+ , circle [ cx "0", cy "0", r "20", fill "red" ] []
= ]Add control of aspect ratio preservation to ViewBox example
index cfc9865..5a4bad5 100644
--- a/src/ViewBox.elm
+++ b/src/ViewBox.elm
@@ -33,12 +33,14 @@ type alias Model =
= , top : Float
= , width : Float
= , height : Float
+ , preserveAspectRatio : Bool
= }
=
=
=type Msg
= = Move Float Float
= | Resize Float Float
+ | PreserveAspectRatio Bool
=
=
=init : Model
@@ -47,6 +49,7 @@ init =
= , top = 0
= , width = 400
= , height = 400
+ , preserveAspectRatio = False
= }
=
=
@@ -84,14 +87,15 @@ update msg model =
= , height = height
= }
=
+ PreserveAspectRatio value ->
+ { model | preserveAspectRatio = value }
+
=
=ui model =
= let
= viewbox =
= svg
= [ viewBox "-500 -500 1000 1000"
-
- -- , Svg.Attributes.style "width: 100%; height: 100%"
= ]
= [ g [] world
= , rect
@@ -119,7 +123,13 @@ ui model =
= |> List.map String.fromFloat
= |> String.join " "
= |> viewBox
- , preserveAspectRatio "none"
+ , preserveAspectRatio <|
+ case model.preserveAspectRatio of
+ True ->
+ "xMidYMid meet"
+
+ False ->
+ "none"
= , Svg.Attributes.style "width: 100%; height: 100%"
= ]
= [ g [] world
@@ -249,6 +259,12 @@ ui model =
= , thumb = Input.defaultThumb
= , step = Just 1
= }
+ , Input.checkbox []
+ { checked = model.preserveAspectRatio
+ , icon = Input.defaultCheckbox
+ , label = Input.labelRight [] (Element.text "Preserve aspect ratio")
+ , onChange = PreserveAspectRatio
+ }
= ]
=
=
Commits: 8
Create browser window element that wraps ui element in a mock browser window
new file mode 100644
index 0000000..7902e60
--- /dev/null
+++ b/src/BrowserWindow.elm
@@ -0,0 +1,54 @@
+module BrowserWindow exposing (main, window)
+
+import Element exposing (Element)
+import Element.Background as Background
+import Element.Border as Border
+import Element.Font as Font
+import Html exposing (Html)
+
+
+main : Html msg
+main =
+ Element.text "Hello World!"
+ |> window
+ |> Element.layout []
+
+
+window : Element msg -> Element msg
+window content =
+ let
+ circle : Char
+ circle =
+ Char.fromCode 9679
+ in
+ Element.column
+ [ Element.width Element.shrink
+ , Element.height Element.shrink
+ , Border.width 1
+ , Border.rounded 6
+ , Border.color <| Element.rgb 0.4 0.4 0.4
+ , Border.shadow { offset = ( 0, 0 ), size = 2, blur = 0, color = Element.rgb 0.35 0.35 0.35 }
+ ]
+ [ Element.row
+ [ Element.height <| Element.px <| 23
+ , Background.color <| Element.rgb 0.27 0.27 0.27
+ , Element.width Element.fill
+ , Border.widthEach { bottom = 2, left = 0, top = 0, right = 0 }
+ , Border.color <| Element.rgb 0.2 0.2 0.2
+ ]
+ ([ ( 1, 0.4, 0.3 ), ( 1, 0.75, 0.18 ), ( 0.16, 0.79, 0.26 ) ]
+ |> List.map
+ (\( red, green, blue ) ->
+ circle
+ |> String.fromChar
+ |> Element.text
+ |> Element.el
+ [ Element.rgb red green blue |> Font.color
+ , Font.size 35
+ , Element.height <| Element.px 42
+ , Element.moveRight 6
+ ]
+ )
+ )
+ , content
+ ]index b4f72e0..0da1f44 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -1,6 +1,7 @@
=module Main exposing (main)
=
=import Browser
+import BrowserWindow
=import CartesianCoordinates
=import CenteredDot
=import Counter
@@ -217,12 +218,7 @@ options =
= |> Element.el
= [ Element.centerX
= ]
- |> Element.el
- [ Element.centerX
- , Border.color (Element.rgb 1 0.6 0.6)
- , Border.rounded 5
- , Border.width 2
- ]
+ |> BrowserWindow.window
= |> Element.map CounterMsg
=
= simplestBlock : Mark.Custom.Block Model Styling Msg
@@ -236,12 +232,7 @@ options =
= [ Element.width Element.fill
= , Element.height Element.fill
= ]
- |> Element.el
- [ Element.centerX
- , Border.color (Element.rgb 1 0.6 0.6)
- , Border.rounded 5
- , Border.width 2
- ]
+ |> BrowserWindow.window
=
= fillTheScreenBlock : Mark.Custom.Block Model Styling Msg
= fillTheScreenBlock =
@@ -254,12 +245,7 @@ options =
= [ Element.width Element.fill
= , Element.height Element.fill
= ]
- |> Element.el
- [ Element.centerX
- , Border.color (Element.rgb 1 0.6 0.6)
- , Border.rounded 5
- , Border.width 2
- ]
+ |> BrowserWindow.window
=
= centeredDotBlock : Mark.Custom.Block Model Styling Msg
= centeredDotBlock =
@@ -271,12 +257,7 @@ options =
= |> Element.el
= [ Element.centerX
= ]
- |> Element.el
- [ Element.centerX
- , Border.color (Element.rgb 1 0.6 0.6)
- , Border.rounded 5
- , Border.width 2
- ]
+ |> BrowserWindow.window
=
= lineBlock : Mark.Custom.Block Model Styling Msg
= lineBlock =
@@ -288,12 +269,7 @@ options =
= |> Element.el
= [ Element.centerX
= ]
- |> Element.el
- [ Element.centerX
- , Border.color (Element.rgb 1 0.6 0.6)
- , Border.rounded 5
- , Border.width 2
- ]
+ |> BrowserWindow.window
=
= gradientBlock : Mark.Custom.Block Model Styling Msg
= gradientBlock =
@@ -305,12 +281,7 @@ options =
= |> Element.el
= [ Element.centerX
= ]
- |> Element.el
- [ Element.centerX
- , Border.color (Element.rgb 1 0.6 0.6)
- , Border.rounded 5
- , Border.width 2
- ]
+ |> BrowserWindow.window
=
= transformationsBlock : Mark.Custom.Block Model Styling Msg
= transformationsBlock =
@@ -400,12 +371,7 @@ options =
= |> Element.el
= [ Element.centerX
= ]
- |> Element.el
- [ Element.centerX
- , Border.color (Element.rgb 1 0.6 0.6)
- , Border.rounded 5
- , Border.width 2
- ]
+ |> BrowserWindow.window
=
= spiralBlock : Mark.Custom.Block Model Styling Msg
= spiralBlock =
@@ -417,12 +383,7 @@ options =
= |> Element.el
= [ Element.centerX
= ]
- |> Element.el
- [ Element.centerX
- , Border.color (Element.rgb 1 0.6 0.6)
- , Border.rounded 5
- , Border.width 2
- ]
+ |> BrowserWindow.window
=
= treeBlock : Mark.Custom.Block Model Styling Msg
= treeBlock =
@@ -471,11 +432,7 @@ options =
= ]
= in
= content
- |> Element.el
- [ Border.color (Element.rgb 1 0.6 0.6)
- , Border.rounded 5
- , Border.width 2
- ]
+ |> BrowserWindow.window
=
= viewBoxBlock : Mark.Custom.Block Model Styling Msg
= viewBoxBlock =Refactor circle in browser window
index 7902e60..34177eb 100644
--- a/src/BrowserWindow.elm
+++ b/src/BrowserWindow.elm
@@ -17,9 +17,15 @@ main =
=window : Element msg -> Element msg
=window content =
= let
- circle : Char
- circle =
+ circle : Element.Color -> Element msg
+ circle color =
= Char.fromCode 9679
+ |> String.fromChar
+ |> Element.text
+ |> Element.el
+ [ color |> Font.color
+ , Font.size 35
+ ]
= in
= Element.column
= [ Element.width Element.shrink
@@ -39,14 +45,10 @@ window content =
= ([ ( 1, 0.4, 0.3 ), ( 1, 0.75, 0.18 ), ( 0.16, 0.79, 0.26 ) ]
= |> List.map
= (\( red, green, blue ) ->
- circle
- |> String.fromChar
- |> Element.text
+ circle (Element.rgb red green blue)
= |> Element.el
- [ Element.rgb red green blue |> Font.color
- , Font.size 35
- , Element.height <| Element.px 42
- , Element.moveRight 6
+ [ Element.moveRight 6
+ , Element.moveUp 3
= ]
= )
= )Minor improvements to BrowserWindow layout
index 34177eb..161f379 100644
--- a/src/BrowserWindow.elm
+++ b/src/BrowserWindow.elm
@@ -5,6 +5,7 @@ import Element.Background as Background
=import Element.Border as Border
=import Element.Font as Font
=import Html exposing (Html)
+import Html.Attributes
=
=
=main : Html msg
@@ -25,32 +26,49 @@ window content =
= |> Element.el
= [ color |> Font.color
= , Font.size 35
+ , Element.width (Element.px 20)
+ , Element.height (Element.px 35)
+ , Font.center
= ]
= in
= Element.column
= [ Element.width Element.shrink
= , Element.height Element.shrink
- , Border.width 1
+ , Border.width 2
= , Border.rounded 6
- , Border.color <| Element.rgb 0.4 0.4 0.4
- , Border.shadow { offset = ( 0, 0 ), size = 2, blur = 0, color = Element.rgb 0.35 0.35 0.35 }
+ , Border.color <| Element.rgb 0.27 0.27 0.27
+ , Background.color <| Element.rgb 0.27 0.27 0.27
= ]
= [ Element.row
= [ Element.height <| Element.px <| 23
- , Background.color <| Element.rgb 0.27 0.27 0.27
= , Element.width Element.fill
= , Border.widthEach { bottom = 2, left = 0, top = 0, right = 0 }
= , Border.color <| Element.rgb 0.2 0.2 0.2
+ , Border.roundEach
+ { topLeft = 5
+ , topRight = 5
+ , bottomLeft = 0
+ , bottomRight = 0
+ }
+ , Element.paddingXY 10 15
= ]
= ([ ( 1, 0.4, 0.3 ), ( 1, 0.75, 0.18 ), ( 0.16, 0.79, 0.26 ) ]
= |> List.map
= (\( red, green, blue ) ->
= circle (Element.rgb red green blue)
- |> Element.el
- [ Element.moveRight 6
- , Element.moveUp 3
- ]
= )
= )
- , content
+ , Element.el
+ [ Background.color <| Element.rgb 1 1 1
+ , Element.width Element.fill
+ , Element.height Element.fill
+ , Border.roundEach
+ { topLeft = 0
+ , topRight = 0
+ , bottomLeft = 5
+ , bottomRight = 5
+ }
+ , Element.htmlAttribute (Html.Attributes.style "overflow" "hidden")
+ ]
+ content
= ]Make browser window content not stretch to fill window
index 161f379..257c30b 100644
--- a/src/BrowserWindow.elm
+++ b/src/BrowserWindow.elm
@@ -38,18 +38,13 @@ window content =
= , Border.rounded 6
= , Border.color <| Element.rgb 0.27 0.27 0.27
= , Background.color <| Element.rgb 0.27 0.27 0.27
+ , Element.htmlAttribute (Html.Attributes.style "overflow" "hidden")
= ]
= [ Element.row
= [ Element.height <| Element.px <| 23
= , Element.width Element.fill
= , Border.widthEach { bottom = 2, left = 0, top = 0, right = 0 }
= , Border.color <| Element.rgb 0.2 0.2 0.2
- , Border.roundEach
- { topLeft = 5
- , topRight = 5
- , bottomLeft = 0
- , bottomRight = 0
- }
= , Element.paddingXY 10 15
= ]
= ([ ( 1, 0.4, 0.3 ), ( 1, 0.75, 0.18 ), ( 0.16, 0.79, 0.26 ) ]
@@ -59,16 +54,14 @@ window content =
= )
= )
= , Element.el
- [ Background.color <| Element.rgb 1 1 1
+ [ Element.width Element.fill
= , Element.width Element.fill
- , Element.height Element.fill
- , Border.roundEach
- { topLeft = 0
- , topRight = 0
- , bottomLeft = 5
- , bottomRight = 5
- }
- , Element.htmlAttribute (Html.Attributes.style "overflow" "hidden")
+ , Background.color <| Element.rgb 1 1 1
= ]
- content
+ (Element.el
+ [ Element.width Element.shrink
+ , Element.height Element.shrink
+ ]
+ content
+ )
= ]index 0da1f44..1edee6f 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -118,9 +118,7 @@ view model =
= |> Element.text
= in
= Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
- ]
+ []
= content
=
=
@@ -228,10 +226,6 @@ options =
= simplestView : Styling -> Model -> Element Msg
= simplestView style model =
= Simplest.ui
- |> Element.el
- [ Element.width Element.fill
- , Element.height Element.fill
- ]
= |> BrowserWindow.window
=
= fillTheScreenBlock : Mark.Custom.Block Model Styling MsgMake only simplest example not fill screen
index 257c30b..d3b8508 100644
--- a/src/BrowserWindow.elm
+++ b/src/BrowserWindow.elm
@@ -58,10 +58,5 @@ window content =
= , Element.width Element.fill
= , Background.color <| Element.rgb 1 1 1
= ]
- (Element.el
- [ Element.width Element.shrink
- , Element.height Element.shrink
- ]
- content
- )
+ content
= ]index 1edee6f..0591a0c 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -226,6 +226,7 @@ options =
= simplestView : Styling -> Model -> Element Msg
= simplestView style model =
= Simplest.ui
+ |> Element.el []
= |> BrowserWindow.window
=
= fillTheScreenBlock : Mark.Custom.Block Model Styling MsgMake simplest example display a circle instead of a line
index ae4d919..27930cc 100644
--- a/src/Simplest.elm
+++ b/src/Simplest.elm
@@ -6,14 +6,12 @@ import Svg.Attributes
=
=
=main =
- Svg.svg [ Svg.Attributes.style "background: pink" ]
- [ Svg.line
- [ Svg.Attributes.x1 "0"
- , Svg.Attributes.x2 "20"
- , Svg.Attributes.y1 "0"
- , Svg.Attributes.y2 "0"
- , Svg.Attributes.stroke "black"
- , Svg.Attributes.strokeWidth <| String.fromInt <| 5
+ Svg.svg
+ [ Svg.Attributes.style "background: pink; height: 100%; width: 100%" ]
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "30"
+ , Svg.Attributes.cy "30"
= ]
= []
= ]Use Element.clip instead of overflow CSS property in BorwserWindow
index d3b8508..2714ed3 100644
--- a/src/BrowserWindow.elm
+++ b/src/BrowserWindow.elm
@@ -5,7 +5,6 @@ import Element.Background as Background
=import Element.Border as Border
=import Element.Font as Font
=import Html exposing (Html)
-import Html.Attributes
=
=
=main : Html msg
@@ -38,7 +37,7 @@ window content =
= , Border.rounded 6
= , Border.color <| Element.rgb 0.27 0.27 0.27
= , Background.color <| Element.rgb 0.27 0.27 0.27
- , Element.htmlAttribute (Html.Attributes.style "overflow" "hidden")
+ , Element.clip
= ]
= [ Element.row
= [ Element.height <| Element.px <| 23Use BrowserWindow decoration inside ViewBox example
index fadec31..5de8663 100644
--- a/src/ViewBox.elm
+++ b/src/ViewBox.elm
@@ -9,6 +9,7 @@ module ViewBox exposing
= )
=
=import Browser
+import BrowserWindow
=import CartesianPlane exposing (graph)
=import Element
=import Element.Background as Background
@@ -114,32 +115,32 @@ ui model =
= []
= ]
= )
- , Element.el
- [ Element.height <| Element.maximum 170 Element.fill
- , Element.width <| Element.maximum 170 Element.fill
+ , svg
+ [ [ model.left, model.top, model.width, model.height ]
+ |> List.map String.fromFloat
+ |> String.join " "
+ |> viewBox
+ , preserveAspectRatio "none"
+ , Svg.Attributes.style "width: 100%; height: 100%"
= ]
- (Element.html <|
- svg
- [ [ model.left, model.top, model.width, model.height ]
- |> List.map String.fromFloat
- |> String.join " "
- |> viewBox
- , preserveAspectRatio "none"
- , Svg.Attributes.style "width: 100%; height: 100%"
- ]
- [ g [] world
- , rect
- [ x (String.fromFloat model.left)
- , y (String.fromFloat model.top)
- , width (String.fromFloat model.width)
- , height (String.fromFloat model.height)
- , opacity "0.3"
- , stroke "white"
- , fill "pink"
- ]
- []
- ]
- )
+ [ g [] world
+ , rect
+ [ x (String.fromFloat model.left)
+ , y (String.fromFloat model.top)
+ , width (String.fromFloat model.width)
+ , height (String.fromFloat model.height)
+ , opacity "0.3"
+ , stroke "white"
+ , fill "pink"
+ ]
+ []
+ ]
+ |> Element.html
+ |> BrowserWindow.window
+ |> Element.el
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
= ]
= , Input.slider
= [ Element.behindContent
Commits: 8
Embed simplest, roestte, and spiral examples
index 5dd325d..f789f82 100644
--- a/index.txt
+++ b/index.txt
@@ -36,6 +36,10 @@ Setup the development environment
=
=| counter
=
+Embedded simplest example:
+
+| simplest
+
=Embedded fill the screen example:
=
=| fill-the-screen
@@ -64,8 +68,16 @@ Embeded transformations example:
=
=| transformations
=
-Embeded nested-transformations example:
+Embedded nested-transformations example:
=
=| nested-transformations
=
+Embedded rosette example:
+
+| rosette
+
+Embedded spiral example:
+
+| spiral
+
=Finito!index 0d3eb5a..d450539 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -18,6 +18,9 @@ import Mark.Custom
=import NestedTransformations
=import PolarCoordinates
=import Result.Extra as Result
+import RosetteTypedTransformations
+import Simplest
+import Spiral
=import Transformations
=
=
@@ -187,6 +190,24 @@ options =
= ]
= |> Element.map CounterMsg
=
+ simplestBlock : Mark.Custom.Block Model Styling Msg
+ simplestBlock =
+ Mark.Custom.block "simplest" simplestView
+
+ simplestView : Styling -> Model -> Element Msg
+ simplestView style model =
+ Simplest.ui
+ |> Element.el
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+ |> Element.el
+ [ Element.centerX
+ , Border.color (Element.rgb 1 0.6 0.6)
+ , Border.rounded 5
+ , Border.width 2
+ ]
+
= fillTheScreenBlock : Mark.Custom.Block Model Styling Msg
= fillTheScreenBlock =
= Mark.Custom.block "fill-the-screen" fillTheScreenView
@@ -331,10 +352,45 @@ options =
= , Border.width 2
= ]
= |> Element.map PolarCoordinatesMsg
+
+ rosetteBlock : Mark.Custom.Block Model Styling Msg
+ rosetteBlock =
+ Mark.Custom.block "rosette" rosetteView
+
+ rosetteView : Styling -> Model -> Element Msg
+ rosetteView style model =
+ RosetteTypedTransformations.ui
+ |> Element.el
+ [ Element.centerX
+ ]
+ |> Element.el
+ [ Element.centerX
+ , Border.color (Element.rgb 1 0.6 0.6)
+ , Border.rounded 5
+ , Border.width 2
+ ]
+
+ spiralBlock : Mark.Custom.Block Model Styling Msg
+ spiralBlock =
+ Mark.Custom.block "spiral" spiralView
+
+ spiralView : Styling -> Model -> Element Msg
+ spiralView style model =
+ Spiral.ui
+ |> Element.el
+ [ Element.centerX
+ ]
+ |> Element.el
+ [ Element.centerX
+ , Border.color (Element.rgb 1 0.6 0.6)
+ , Border.rounded 5
+ , Border.width 2
+ ]
= in
= { default
= | blocks =
= counterBlock
+ :: simplestBlock
= :: fillTheScreenBlock
= :: lineBlock
= :: gradientBlock
@@ -343,5 +399,7 @@ options =
= :: nestedTransformationsBlock
= :: cartesianCoordinatesBlock
= :: polarCoordinatesBlock
+ :: rosetteBlock
+ :: spiralBlock
= :: Mark.defaultBlocks
= }index dcd20c1..2ae161e 100644
--- a/src/RosetteTypedTransformations.elm
+++ b/src/RosetteTypedTransformations.elm
@@ -1,4 +1,4 @@
-module RosetteTypedTransformations exposing (main)
+module RosetteTypedTransformations exposing (main, ui)
=
=import Element
=import Svg
@@ -10,77 +10,82 @@ main =
= [ Element.width Element.fill
= , Element.height Element.fill
= ]
- (Element.html
- (Svg.svg
- [ Svg.Attributes.viewBox "-100 -100 200 200" ]
- [ Svg.defs []
- [ Svg.linearGradient
- [ Svg.Attributes.id "blue-pink-gradient"
- , Svg.Attributes.x1 "0"
- , Svg.Attributes.y1 "0"
- , Svg.Attributes.x2 "1"
- , Svg.Attributes.y2 "0"
- , Svg.Attributes.gradientUnits "userSpaceOnUse"
+ ui
+
+
+ui =
+ Element.html
+ (Svg.svg
+ [ Svg.Attributes.height "400"
+ , Svg.Attributes.viewBox "-100 -100 200 200"
+ ]
+ [ Svg.defs []
+ [ Svg.linearGradient
+ [ Svg.Attributes.id "blue-pink-gradient"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x2 "1"
+ , Svg.Attributes.y2 "0"
+ , Svg.Attributes.gradientUnits "userSpaceOnUse"
+ ]
+ [ Svg.stop
+ [ Svg.Attributes.stopColor "blue"
+ , Svg.Attributes.offset "0"
= ]
- [ Svg.stop
- [ Svg.Attributes.stopColor "blue"
- , Svg.Attributes.offset "0"
- ]
- []
- , Svg.stop
- [ Svg.Attributes.stopColor "pink"
- , Svg.Attributes.offset "1"
- ]
- []
+ []
+ , Svg.stop
+ [ Svg.Attributes.stopColor "pink"
+ , Svg.Attributes.offset "1"
= ]
+ []
= ]
- , Svg.line
- [ Svg.Attributes.x2 "1"
- , Svg.Attributes.stroke "url(#blue-pink-gradient)"
- , transform
- [ Rotate 0
- , Scale 100 1
- ]
+ ]
+ , Svg.line
+ [ Svg.Attributes.x2 "1"
+ , Svg.Attributes.stroke "url(#blue-pink-gradient)"
+ , transform
+ [ Rotate 0
+ , Scale 100 1
= ]
- []
- , Svg.line
- [ Svg.Attributes.x2 "1"
- , Svg.Attributes.stroke "url(#blue-pink-gradient)"
- , transform
- [ Rotate 72
- , Scale 100 1
- ]
+ ]
+ []
+ , Svg.line
+ [ Svg.Attributes.x2 "1"
+ , Svg.Attributes.stroke "url(#blue-pink-gradient)"
+ , transform
+ [ Rotate 72
+ , Scale 100 1
= ]
- []
- , Svg.line
- [ Svg.Attributes.x2 "1"
- , Svg.Attributes.stroke "url(#blue-pink-gradient)"
- , transform
- [ Rotate 144
- , Scale 100 1
- ]
+ ]
+ []
+ , Svg.line
+ [ Svg.Attributes.x2 "1"
+ , Svg.Attributes.stroke "url(#blue-pink-gradient)"
+ , transform
+ [ Rotate 144
+ , Scale 100 1
= ]
- []
- , Svg.line
- [ Svg.Attributes.x2 "1"
- , Svg.Attributes.stroke "url(#blue-pink-gradient)"
- , transform
- [ Rotate 216
- , Scale 100 1
- ]
+ ]
+ []
+ , Svg.line
+ [ Svg.Attributes.x2 "1"
+ , Svg.Attributes.stroke "url(#blue-pink-gradient)"
+ , transform
+ [ Rotate 216
+ , Scale 100 1
= ]
- []
- , Svg.line
- [ Svg.Attributes.x2 "1"
- , Svg.Attributes.stroke "url(#blue-pink-gradient)"
- , transform
- [ Rotate 288
- , Scale 100 1
- ]
+ ]
+ []
+ , Svg.line
+ [ Svg.Attributes.x2 "1"
+ , Svg.Attributes.stroke "url(#blue-pink-gradient)"
+ , transform
+ [ Rotate 288
+ , Scale 100 1
= ]
- []
= ]
- )
+ []
+ ]
= )
=
=index 9686d17..ae4d919 100644
--- a/src/Simplest.elm
+++ b/src/Simplest.elm
@@ -1,5 +1,6 @@
-module Simplest exposing (main)
+module Simplest exposing (main, ui)
=
+import Element
=import Svg
=import Svg.Attributes
=
@@ -8,10 +9,15 @@ main =
= Svg.svg [ Svg.Attributes.style "background: pink" ]
= [ Svg.line
= [ Svg.Attributes.x1 "0"
- , Svg.Attributes.x2 "1"
+ , Svg.Attributes.x2 "20"
= , Svg.Attributes.y1 "0"
= , Svg.Attributes.y2 "0"
= , Svg.Attributes.stroke "black"
+ , Svg.Attributes.strokeWidth <| String.fromInt <| 5
= ]
= []
= ]
+
+
+ui =
+ Element.html mainindex f2e2aa0..09c888e 100644
--- a/src/Spiral.elm
+++ b/src/Spiral.elm
@@ -1,4 +1,4 @@
-module Spiral exposing (main)
+module Spiral exposing (main, ui)
=
=import Element
=import Svg
@@ -6,6 +6,14 @@ import Svg.Attributes
=
=
=main =
+ Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+ ui
+
+
+ui =
= let
= defs =
= Svg.defs []
@@ -30,15 +38,12 @@ main =
= ]
= ]
= in
- Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
- ]
- (Element.html
- (Svg.svg
- [ Svg.Attributes.viewBox "-100 -100 200 200" ]
- (defs :: spiral 500)
- )
+ Element.html
+ (Svg.svg
+ [ Svg.Attributes.height "400"
+ , Svg.Attributes.viewBox "-100 -100 200 200"
+ ]
+ (defs :: spiral 500)
= )
=
=(Re-) implement title, note and emphasize blocks for markup
Title has subtitles (lines following the first one). Note and emphasize are visually separated from the normal content.
index 1a452ee..67d6710 100644
--- a/index.txt
+++ b/index.txt
@@ -1,3 +1,50 @@
+| title
+ Software Garden
+
+ ⚘
+
+ A functional programming workshop for non-programmers
+
+
+
+| header
+ Before the course begins
+
+| header
+ Setup
+
+| note
+ This setup instructions are based on an assumption that you are using a Mac.
+
+ If you are using Linux or BSD, then you probably know how to install stuff.
+
+ If you are using anything else, then... well, good luck.
+
+ The rest of the instructions should work with any platform.
+
+
+We will need
+
+| list
+ - a text editor (I use Atom)
+ - and the Elm programming language
+
+
+| header
+ Installing Elm
+
+To install the Elm programming language, go to it's website:
+
+| emphasize
+ [elm-lang.org](http://elm-lang.org/)
+
+
+and follow the installation instructions.
+
+
+
+
+
=| header
= To do:
=
@@ -22,22 +69,3 @@ Groups and transformations
= Spiral (recursion)
=
=Tree
-
-
-| header
- Before the course begins
-
-Setup the development environment
-
-| list
- - Elm
- - Node.js
-
-
-| counter
-
-Embeded transformations example:
-
-| transformations
-
-Finito!index b7a1bc1..41b9ce4 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -5,6 +5,7 @@ import Counter
=import Element exposing (Element)
=import Element.Background as Background
=import Element.Border as Border
+import Element.Font as Font
=import Element.Input as Input
=import Html exposing (Html)
=import Http
@@ -74,7 +75,7 @@ view model =
= |> Element.column
= [ Element.centerX
= , Element.centerY
- , Background.color (Element.rgb 0.7 0 0)
+ , Background.color colors.maroon
= , Element.padding 40
= , Element.spacing 20
= ]
@@ -131,12 +132,80 @@ type alias Options =
= Mark.Options Model Styling Msg
=
=
+colors =
+ { maroon = Element.rgb 0.7 0 0
+ , gray = Element.rgb 0.8 0.8 0.8
+ , pink = Element.rgb 1 0.6 0.6
+ }
+
+
=options : Options
=options =
= let
= default =
= Mark.default
=
+ emphasizeBlock : Mark.Custom.Block Model Styling Msg
+ emphasizeBlock =
+ Mark.Custom.section "emphasize" emphasizeView
+
+ emphasizeView : List (Element Msg) -> Styling -> Model -> Element Msg
+ emphasizeView elements style model =
+ elements
+ |> Element.column
+ [ Element.spacing 30
+ , Font.bold
+ , Font.size 30
+ , Font.center
+ , Element.paddingXY 0 40
+ ]
+
+ titleBlock : Mark.Custom.Block Model Styling Msg
+ titleBlock =
+ Mark.Custom.section "title" titleView
+
+ titleView : List (Element Msg) -> Styling -> Model -> Element Msg
+ titleView elements style model =
+ case elements of
+ title :: subtitles ->
+ let
+ titleElement =
+ Element.el
+ [ Font.center
+ , Font.size 60
+ , Font.extraBold
+ , Element.width Element.fill
+ ]
+ title
+ in
+ Element.column
+ [ Element.spacing 30
+ , Font.bold
+ , Font.size 30
+ , Font.center
+ , Element.paddingXY 0 80
+ ]
+ (titleElement :: subtitles)
+
+ [] ->
+ Element.none
+
+ noteBlock : Mark.Custom.Block Model Styling Msg
+ noteBlock =
+ Mark.Custom.section "note" noteView
+
+ noteView : List (Element Msg) -> Styling -> Model -> Element Msg
+ noteView elements style model =
+ Element.column
+ [ Element.padding 20
+ , Element.spacing 10
+ , Border.width 1
+ , Border.color colors.gray
+ , Border.rounded 5
+ , Font.italic
+ ]
+ elements
+
= counterBlock : Mark.Custom.Block Model Styling Msg
= counterBlock =
= Mark.Custom.block "counter" counterView
@@ -150,7 +219,7 @@ options =
= ]
= |> Element.el
= [ Element.centerX
- , Border.color (Element.rgb 1 0.6 0.6)
+ , Border.color colors.pink
= , Border.rounded 5
= , Border.width 2
= ]
@@ -169,7 +238,7 @@ options =
= ]
= |> Element.el
= [ Element.centerX
- , Border.color (Element.rgb 1 0.6 0.6)
+ , Border.color colors.pink
= , Border.rounded 5
= , Border.width 2
= ]
@@ -177,7 +246,10 @@ options =
= in
= { default
= | blocks =
- counterBlock
+ titleBlock
+ :: emphasizeBlock
+ :: noteBlock
+ :: counterBlock
= :: transformationsBlock
= :: Mark.defaultBlocks
= }Embed tree example
The embedded example is toggled by clicking on the element
index f789f82..96e0265 100644
--- a/index.txt
+++ b/index.txt
@@ -80,4 +80,8 @@ Embedded spiral example:
=
=| spiral
=
+Embedded tree example:
+
+| tree
+
=Finito!index d450539..689e5cb 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -7,6 +7,7 @@ import Counter
=import Element exposing (Element)
=import Element.Background as Background
=import Element.Border as Border
+import Element.Events
=import Element.Input as Input
=import FillTheScreen
=import Gradient
@@ -22,6 +23,7 @@ import RosetteTypedTransformations
=import Simplest
=import Spiral
=import Transformations
+import Tree
=
=
=main =
@@ -44,6 +46,7 @@ type alias Model =
= , nestedTransformations : NestedTransformations.Model
= , cartesianCoordinates : CartesianCoordinates.Model
= , polarCoordinates : PolarCoordinates.Model
+ , tree : Maybe Tree.Model
= }
=
=
@@ -54,6 +57,8 @@ type Msg
= | NestedTransformationsMsg NestedTransformations.Msg
= | CartesianCoordinatesMsg CartesianCoordinates.Msg
= | PolarCoordinatesMsg PolarCoordinates.Msg
+ | ToggleTree (Maybe Tree.Model)
+ | TreeMsg Tree.Msg
=
=
=init : Flags -> ( Model, Cmd Msg )
@@ -64,6 +69,9 @@ init flags =
= , nestedTransformations = NestedTransformations.init
= , cartesianCoordinates = CartesianCoordinates.init
= , polarCoordinates = PolarCoordinates.init
+ , tree = Nothing
+
+ --, tree = Tree.init () |> Tuple.first
= }
= , Http.get
= { url = "/index.txt"
@@ -151,10 +159,32 @@ update msg model =
= , Cmd.none
= )
=
+ ToggleTree tree ->
+ ( { model | tree = tree }, Cmd.none )
+
+ TreeMsg m ->
+ case model.tree of
+ Just tree ->
+ Tree.update m tree
+ |> (\( newTree, treeCmd ) ->
+ ( { model | tree = Just newTree }
+ , Cmd.map TreeMsg treeCmd
+ )
+ )
+
+ Nothing ->
+ ( model, Cmd.none )
+
=
=subscriptions : Model -> Sub Msg
=subscriptions model =
- Sub.none
+ case model.tree of
+ Nothing ->
+ Sub.none
+
+ Just tree ->
+ Tree.subscriptions tree
+ |> Sub.map TreeMsg
=
=
=type alias Styling =
@@ -386,6 +416,48 @@ options =
= , Border.rounded 5
= , Border.width 2
= ]
+
+ treeBlock : Mark.Custom.Block Model Styling Msg
+ treeBlock =
+ Mark.Custom.block "tree" treeView
+
+ treeView : Styling -> Model -> Element Msg
+ treeView style model =
+ let
+ content =
+ case model.tree of
+ Nothing ->
+ Element.text "Click Here to Display Tree"
+ |> List.singleton
+ |> Element.paragraph
+ [ Element.centerY ]
+ |> Element.el
+ [ Element.centerX
+ , Element.height <| Element.minimum 300 <| Element.fill
+ , Element.Events.onClick
+ (Tree.init ()
+ |> Tuple.first
+ |> Just
+ |> ToggleTree
+ )
+ ]
+
+ Just tree ->
+ tree
+ |> Tree.ui
+ |> Element.map TreeMsg
+ |> Element.el
+ [ Element.centerX
+ , Element.height <| Element.minimum 300 <| Element.fill
+ , Element.Events.onClick (ToggleTree Nothing)
+ ]
+ in
+ content
+ |> Element.el
+ [ Border.color (Element.rgb 1 0.6 0.6)
+ , Border.rounded 5
+ , Border.width 2
+ ]
= in
= { default
= | blocks =
@@ -401,5 +473,6 @@ options =
= :: polarCoordinatesBlock
= :: rosetteBlock
= :: spiralBlock
+ :: treeBlock
= :: Mark.defaultBlocks
= }index cde6818..44e7a9a 100644
--- a/src/Tree.elm
+++ b/src/Tree.elm
@@ -1,9 +1,17 @@
-module Tree exposing (main)
+module Tree exposing
+ ( Model
+ , Msg
+ , init
+ , main
+ , subscriptions
+ , ui
+ , update
+ )
=
=import Browser
=import Browser.Events
=import Dict exposing (Dict)
-import Element
+import Element exposing (Element)
=import Html exposing (Html)
=import Json.Decode exposing (Decoder)
=import List.Extra as List
@@ -81,6 +89,15 @@ init () =
=
=view : Model -> Html Msg
=view model =
+ Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+ (ui model)
+
+
+ui : Model -> Element Msg
+ui model =
= let
= gradients : List (Svg Msg)
= gradients =
@@ -123,20 +140,15 @@ view model =
= )
= ]
= in
- Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
- ]
- (Element.html <|
- svg
- [ viewBox "-1000 -1000 2000 2000"
- , height "100%"
- , width "100%"
- ]
- [ defs [] gradients
- , tree
- ]
- )
+ Element.html <|
+ svg
+ [ viewBox "-1000 -1000 2000 2000"
+ , height "100%"
+ , width "100%"
+ ]
+ [ defs [] gradients
+ , tree
+ ]
=
=
=update : Msg -> Model -> ( Model, Cmd Msg )Embed view box example
index 96e0265..1a106ac 100644
--- a/index.txt
+++ b/index.txt
@@ -84,4 +84,8 @@ Embedded tree example:
=
=| tree
=
+Embedded view box example:
+
+| view-box
+
=Finito!index 689e5cb..b88bb06 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -24,6 +24,7 @@ import Simplest
=import Spiral
=import Transformations
=import Tree
+import ViewBox
=
=
=main =
@@ -47,6 +48,7 @@ type alias Model =
= , cartesianCoordinates : CartesianCoordinates.Model
= , polarCoordinates : PolarCoordinates.Model
= , tree : Maybe Tree.Model
+ , viewBox : ViewBox.Model
= }
=
=
@@ -59,6 +61,7 @@ type Msg
= | PolarCoordinatesMsg PolarCoordinates.Msg
= | ToggleTree (Maybe Tree.Model)
= | TreeMsg Tree.Msg
+ | ViewBoxMsg ViewBox.Msg
=
=
=init : Flags -> ( Model, Cmd Msg )
@@ -70,8 +73,7 @@ init flags =
= , cartesianCoordinates = CartesianCoordinates.init
= , polarCoordinates = PolarCoordinates.init
= , tree = Nothing
-
- --, tree = Tree.init () |> Tuple.first
+ , viewBox = ViewBox.init
= }
= , Http.get
= { url = "/index.txt"
@@ -175,6 +177,9 @@ update msg model =
= Nothing ->
= ( model, Cmd.none )
=
+ ViewBoxMsg m ->
+ ( { model | viewBox = ViewBox.update m model.viewBox }, Cmd.none )
+
=
=subscriptions : Model -> Sub Msg
=subscriptions model =
@@ -458,6 +463,25 @@ options =
= , Border.rounded 5
= , Border.width 2
= ]
+
+ viewBoxBlock : Mark.Custom.Block Model Styling Msg
+ viewBoxBlock =
+ Mark.Custom.block "view-box" viewBoxView
+
+ viewBoxView : Styling -> Model -> Element Msg
+ viewBoxView style model =
+ model.viewBox
+ |> ViewBox.ui
+ |> Element.el
+ [ Element.centerX
+ ]
+ |> Element.el
+ [ Element.centerX
+ , Border.color (Element.rgb 1 0.6 0.6)
+ , Border.rounded 5
+ , Border.width 2
+ ]
+ |> Element.map ViewBoxMsg
= in
= { default
= | blocks =
@@ -474,5 +498,6 @@ options =
= :: rosetteBlock
= :: spiralBlock
= :: treeBlock
+ :: viewBoxBlock
= :: Mark.defaultBlocks
= }index de064d5..fadec31 100644
--- a/src/ViewBox.elm
+++ b/src/ViewBox.elm
@@ -1,10 +1,8 @@
=module ViewBox exposing
- ( Flags
- , Model
+ ( Model
= , Msg
= , init
= , main
- , subscriptions
= , ui
= , update
= , view
@@ -22,18 +20,13 @@ import Svg.Attributes exposing (..)
=
=
=main =
- Browser.element
+ Browser.sandbox
= { init = init
= , view = view
= , update = update
- , subscriptions = subscriptions
= }
=
=
-type alias Flags =
- ()
-
-
=type alias Model =
= { left : Float
= , top : Float
@@ -47,15 +40,13 @@ type Msg
= | Resize Float Float
=
=
-init : Flags -> ( Model, Cmd Msg )
-init () =
- ( { left = 0
- , top = 0
- , width = 400
- , height = 400
- }
- , Cmd.none
- )
+init : Model
+init =
+ { left = 0
+ , top = 0
+ , width = 400
+ , height = 400
+ }
=
=
=view : Model -> Html.Html Msg
@@ -77,19 +68,14 @@ view model =
= ]
=
=
-update : Msg -> Model -> ( Model, Cmd Msg )
+update : Msg -> Model -> Model
=update msg model =
= case msg of
= Move left top ->
- ( { model | left = left, top = top }, Cmd.none )
+ { model | left = left, top = top }
=
= Resize width height ->
- ( { model | width = width, height = height }, Cmd.none )
-
-
-subscriptions : Model -> Sub Msg
-subscriptions model =
- Sub.none
+ { model | width = width, height = height }
=
=
=ui model =
@@ -107,11 +93,14 @@ ui model =
= , Element.padding 30
= ]
= [ Element.el
- [ Element.height Element.fill
- , Element.width Element.fill
+ [ Element.height <| Element.maximum 500 Element.fill
+ , Element.width <| Element.maximum 500 Element.fill
= ]
= (Element.html <|
- svg [ viewBox "0 0 1000 1000" ]
+ svg
+ [ viewBox "0 0 1000 1000"
+ , Svg.Attributes.style "width: 100%; height: 100%"
+ ]
= [ g [] world
= , rect
= [ x (String.fromFloat model.left)
@@ -126,8 +115,8 @@ ui model =
= ]
= )
= , Element.el
- [ Element.height Element.fill
- , Element.width Element.fill
+ [ Element.height <| Element.maximum 170 Element.fill
+ , Element.width <| Element.maximum 170 Element.fill
= ]
= (Element.html <|
= svg
@@ -136,7 +125,7 @@ ui model =
= |> String.join " "
= |> viewBox
= , preserveAspectRatio "none"
- , Svg.Attributes.style "width: 100; height: 100%"
+ , Svg.Attributes.style "width: 100%; height: 100%"
= ]
= [ g [] world
= , rectUnify styles of grid examples
index d9b0f00..7441ca2 100644
--- a/src/CartesianCoordinates.elm
+++ b/src/CartesianCoordinates.elm
@@ -66,12 +66,14 @@ update msg model =
=ui : Model -> Element Msg
=ui model =
= Element.column
- [ Element.width (Element.maximum 600 Element.fill)
+ [ Element.width Element.fill
= , Element.centerX
- , Element.spacing 20
+ , Element.spacing 30
+ , Element.padding 30
= ]
= [ Element.el
- [ Element.width Element.fill
+ [ Element.height Element.fill
+ , Element.width Element.fill
= ]
= <|
= Element.html <|index b88bb06..c295c9b 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -360,6 +360,7 @@ options =
= |> CartesianCoordinates.ui
= |> Element.el
= [ Element.centerX
+ , Element.width Element.fill
= ]
= |> Element.el
= [ Element.centerX
@@ -379,6 +380,7 @@ options =
= |> PolarCoordinates.ui
= |> Element.el
= [ Element.centerX
+ , Element.width Element.fill
= ]
= |> Element.el
= [ Element.centerXindex fa27050..27129df 100644
--- a/src/NestedTransformations.elm
+++ b/src/NestedTransformations.elm
@@ -101,6 +101,7 @@ ui model =
= ]
= [ Element.el
= [ Element.width Element.fill
+ , Element.paddingEach { top = 0, right = 30, bottom = 0, left = 30 }
= ]
= (Element.html element)
= , Element.row [ Element.width Element.fill ]index 0c519ab..0bcd917 100644
--- a/src/Transformations.elm
+++ b/src/Transformations.elm
@@ -86,14 +86,12 @@ ui model =
= [ Element.width Element.fill
= ]
= (Element.html element)
- , Element.row [ Element.width Element.fill ]
- [ Element.el
- [ Background.color (Element.rgb 1 0.8 0.8)
- , Element.padding 10
- , Element.width Element.fill
- ]
- (transformationsUI transformations)
+ , Element.el
+ [ Background.color (Element.rgb 1 0.8 0.8)
+ , Element.padding 10
+ , Element.width Element.fill
= ]
+ (transformationsUI transformations)
= ]
=
= shape =Fix embedded tree style
index c295c9b..b4f72e0 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -431,16 +431,23 @@ options =
= treeView : Styling -> Model -> Element Msg
= treeView style model =
= let
+ height =
+ 500
+
= content =
= case model.tree of
= Nothing ->
= Element.text "Click Here to Display Tree"
= |> List.singleton
= |> Element.paragraph
- [ Element.centerY ]
+ [ Element.centerY
+ ]
= |> Element.el
= [ Element.centerX
- , Element.height <| Element.minimum 300 <| Element.fill
+ , Element.height <|
+ Element.minimum height <|
+ Element.maximum height <|
+ Element.fill
= , Element.Events.onClick
= (Tree.init ()
= |> Tuple.first
@@ -455,7 +462,11 @@ options =
= |> Element.map TreeMsg
= |> Element.el
= [ Element.centerX
- , Element.height <| Element.minimum 300 <| Element.fill
+ , Element.height <|
+ Element.minimum height <|
+ Element.maximum height <|
+ Element.fill
+ , Element.width Element.fill
= , Element.Events.onClick (ToggleTree Nothing)
= ]
= inMerge embeded programs with content branch
Merge embeded programs with content branch
Commits: 8
Embed Transformations example in Markup document
index 5dc347c..1a452ee 100644
--- a/index.txt
+++ b/index.txt
@@ -36,4 +36,8 @@ Setup the development environment
=
=| counter
=
-Last sentence
+Embeded transformations example:
+
+| transformations
+
+Finito!index b8450ee..b7a1bc1 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -3,6 +3,7 @@ module Main exposing (main)
=import Browser
=import Counter
=import Element exposing (Element)
+import Element.Background as Background
=import Element.Border as Border
=import Element.Input as Input
=import Html exposing (Html)
@@ -10,6 +11,7 @@ import Http
=import Mark
=import Mark.Custom
=import Result.Extra as Result
+import Transformations
=
=
=main =
@@ -28,18 +30,21 @@ type alias Flags =
=type alias Model =
= { markup : Maybe String
= , counter : Counter.Model
+ , transformations : Transformations.Model
= }
=
=
=type Msg
= = DocumentFetched (Result Http.Error String)
= | CounterMsg Counter.Msg
+ | TransformationsMsg Transformations.Msg
=
=
=init : Flags -> ( Model, Cmd Msg )
=init flags =
= ( { markup = Nothing
= , counter = Counter.init
+ , transformations = Transformations.init
= }
= , Http.get
= { url = "/index.txt"
@@ -60,9 +65,27 @@ view model =
= Just markup ->
= markup
= |> Mark.parseWith options
- |> Result.mapError Debug.toString
= |> Result.map (\fn -> fn model)
- |> Result.extract Element.text
+ |> Result.extract problemsElement
+
+ problemsElement problems =
+ problems
+ |> List.map problemElement
+ |> Element.column
+ [ Element.centerX
+ , Element.centerY
+ , Background.color (Element.rgb 0.7 0 0)
+ , Element.padding 40
+ , Element.spacing 20
+ ]
+
+ problemElement { row, col, problem } =
+ Debug.toString problem
+ ++ " at "
+ ++ String.fromInt row
+ ++ ":"
+ ++ String.fromInt col
+ |> Element.text
= in
= Element.layout
= [ Element.width Element.fill
@@ -89,6 +112,11 @@ update msg model =
= , Cmd.none
= )
=
+ TransformationsMsg m ->
+ ( { model | transformations = Transformations.update m model.transformations }
+ , Cmd.none
+ )
+
=
=subscriptions : Model -> Sub Msg
=subscriptions model =
@@ -127,8 +155,29 @@ options =
= , Border.width 2
= ]
= |> Element.map CounterMsg
+
+ transformationsBlock : Mark.Custom.Block Model Styling Msg
+ transformationsBlock =
+ Mark.Custom.block "transformations" transformationsView
+
+ transformationsView : Styling -> Model -> Element Msg
+ transformationsView style model =
+ model.transformations
+ |> Transformations.ui
+ |> Element.el
+ [ Element.centerX
+ ]
+ |> Element.el
+ [ Element.centerX
+ , Border.color (Element.rgb 1 0.6 0.6)
+ , Border.rounded 5
+ , Border.width 2
+ ]
+ |> Element.map TransformationsMsg
= in
= { default
= | blocks =
- counterBlock :: Mark.defaultBlocks
+ counterBlock
+ :: transformationsBlock
+ :: Mark.defaultBlocks
= }index 0d66cf5..0c519ab 100644
--- a/src/Transformations.elm
+++ b/src/Transformations.elm
@@ -1,4 +1,11 @@
-module Transformations exposing (main)
+module Transformations exposing
+ ( Model
+ , Msg
+ , init
+ , main
+ , ui
+ , update
+ )
=
=import Array exposing (Array)
=import Browser
@@ -19,19 +26,15 @@ import Svg exposing (..)
=import Svg.Attributes exposing (..)
=
=
+main : Program () Model Msg
=main =
- Browser.element
+ Browser.sandbox
= { init = init
= , view = view
= , update = update
- , subscriptions = subscriptions
= }
=
=
-type alias Flags =
- ()
-
-
=type alias Model =
= Array Transformation
=
@@ -49,19 +52,26 @@ type Msg
= | SetTransformation Int Transformation
=
=
-init : Flags -> ( Model, Cmd Msg )
-init () =
- ( Array.fromList
+init : Model
+init =
+ Array.fromList
= [ Translate 0 0
= , Rotate 0
= , Scale 1 1
= ]
- , Cmd.none
- )
=
=
=view : Model -> Html Msg
=view model =
+ Element.layout
+ [ Element.height Element.fill
+ , Element.width Element.fill
+ ]
+ (ui model)
+
+
+ui : Model -> Element Msg
+ui model =
= let
= transformations =
= Array.toList model
@@ -113,10 +123,6 @@ view model =
= |> List.singleton
= |> CartesianPlane.graph 300
= |> wrapper
- |> Element.layout
- [ Element.height Element.fill
- , Element.width Element.fill
- ]
=
=
=transformationsUI : List Transformation -> Element Msg
@@ -277,13 +283,11 @@ transformationUI index transformation =
= ]
=
=
-update : Msg -> Model -> ( Model, Cmd Msg )
+update : Msg -> Model -> Model
=update msg model =
= case msg of
= AddTransformation transformation ->
- ( Array.push transformation model
- , Cmd.none
- )
+ Array.push transformation model
=
= DeleteTransformation index ->
= let
@@ -296,14 +300,10 @@ update msg model =
= back =
= Array.slice (index + 1) end model
= in
- ( Array.append front back
- , Cmd.none
- )
+ Array.append front back
=
= SetTransformation index transformation ->
- ( Array.set index transformation model
- , Cmd.none
- )
+ Array.set index transformation model
=
=
=subscriptions : Model -> Sub MsgEmbed cartesian coordinates example
index 1a452ee..da39b4c 100644
--- a/index.txt
+++ b/index.txt
@@ -36,6 +36,10 @@ Setup the development environment
=
=| counter
=
+Embedded cartesian coordinates example:
+
+| cartesian-coordinates
+
=Embeded transformations example:
=
=| transformationsindex 6be4d7b..d9b0f00 100644
--- a/src/CartesianCoordinates.elm
+++ b/src/CartesianCoordinates.elm
@@ -1,10 +1,8 @@
=module CartesianCoordinates exposing
- ( Flags
- , Model
+ ( Model
= , Msg
= , init
= , main
- , subscriptions
= , ui
= , update
= , view
@@ -12,7 +10,7 @@ module CartesianCoordinates exposing
=
=import Browser
=import CartesianPlane exposing (graph)
-import Element
+import Element exposing (Element)
=import Element.Background as Background
=import Element.Border as Border
=import Element.Input as Input
@@ -21,19 +19,15 @@ import Svg exposing (..)
=import Svg.Attributes exposing (..)
=
=
+main : Program () Model Msg
=main =
- Browser.element
+ Browser.sandbox
= { init = init
= , view = view
= , update = update
- , subscriptions = subscriptions
= }
=
=
-type alias Flags =
- ()
-
-
=type alias Model =
= { x : Float
= , y : Float
@@ -45,62 +39,47 @@ type Msg
= | SetY Float
=
=
-init : Flags -> ( Model, Cmd Msg )
-init () =
- ( { x = 0, y = 0 }
- , Cmd.none
- )
+init : Model
+init =
+ { x = 0, y = 0 }
=
=
-view : Model -> Html.Html Msg
+view : Model -> Html Msg
=view model =
- let
- wrapper element =
- Element.el
- [ Element.width (Element.maximum 600 Element.fill), Element.centerX ]
- element
- in
- ui model
- |> wrapper
- |> Element.layout
- [ Element.height Element.fill
- , Element.width Element.fill
- ]
+ Element.layout
+ [ Element.height Element.fill
+ , Element.width Element.fill
+ ]
+ (ui model)
=
=
-update : Msg -> Model -> ( Model, Cmd Msg )
+update : Msg -> Model -> Model
=update msg model =
= case msg of
= SetX x ->
- ( { model | x = x }, Cmd.none )
+ { model | x = x }
=
= SetY y ->
- ( { model | y = y }, Cmd.none )
-
-
-subscriptions : Model -> Sub Msg
-subscriptions model =
- Sub.none
+ { model | y = y }
=
=
+ui : Model -> Element Msg
=ui model =
= Element.column
- [ Element.width Element.fill
+ [ Element.width (Element.maximum 600 Element.fill)
= , Element.centerX
- , Element.spacing 30
- , Element.padding 30
+ , Element.spacing 20
= ]
= [ Element.el
- [ Element.height Element.fill
- , Element.width Element.fill
+ [ Element.width Element.fill
= ]
= <|
= Element.html <|
- graph
+ CartesianPlane.graph 300
= [ circle
= [ cx <| String.fromFloat model.x
= , cy <| String.fromFloat model.y
- , r "0.01"
+ , r "2"
= , fill "magenta"
= ]
= []
@@ -115,7 +94,7 @@ ui model =
= , Input.slider
= [ Element.behindContent
= (Element.el
- [ Element.width Element.fill
+ [ Element.width (Element.maximum 600 Element.fill)
= , Element.height (Element.px 2)
= , Element.centerY
= , Background.color <| Element.rgb 0.7 0.7 0.7
@@ -128,8 +107,8 @@ ui model =
= , label =
= Input.labelBelow [ Element.centerX ] <|
= Element.text ("x value: " ++ String.fromFloat model.x)
- , min = -1
- , max = 1
+ , min = -150
+ , max = 150
= , value = model.x
= , thumb = Input.defaultThumb
= , step = Just 0.01
@@ -150,8 +129,8 @@ ui model =
= , label =
= Input.labelBelow [ Element.centerX ] <|
= Element.text ("y value: " ++ String.fromFloat model.y)
- , min = -1
- , max = 1
+ , min = -150
+ , max = 150
= , value = model.y
= , thumb = Input.defaultThumb
= , step = Just 0.01index b7a1bc1..9610763 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -1,6 +1,7 @@
=module Main exposing (main)
=
=import Browser
+import CartesianCoordinates
=import Counter
=import Element exposing (Element)
=import Element.Background as Background
@@ -31,6 +32,7 @@ type alias Model =
= { markup : Maybe String
= , counter : Counter.Model
= , transformations : Transformations.Model
+ , cartesianCoordinates : CartesianCoordinates.Model
= }
=
=
@@ -38,6 +40,7 @@ type Msg
= = DocumentFetched (Result Http.Error String)
= | CounterMsg Counter.Msg
= | TransformationsMsg Transformations.Msg
+ | CartesianCoordinatesMsg CartesianCoordinates.Msg
=
=
=init : Flags -> ( Model, Cmd Msg )
@@ -45,6 +48,7 @@ init flags =
= ( { markup = Nothing
= , counter = Counter.init
= , transformations = Transformations.init
+ , cartesianCoordinates = CartesianCoordinates.init
= }
= , Http.get
= { url = "/index.txt"
@@ -117,6 +121,11 @@ update msg model =
= , Cmd.none
= )
=
+ CartesianCoordinatesMsg m ->
+ ( { model | cartesianCoordinates = CartesianCoordinates.update m model.cartesianCoordinates }
+ , Cmd.none
+ )
+
=
=subscriptions : Model -> Sub Msg
=subscriptions model =
@@ -174,10 +183,30 @@ options =
= , Border.width 2
= ]
= |> Element.map TransformationsMsg
+
+ cartesianCoordinatesBlock : Mark.Custom.Block Model Styling Msg
+ cartesianCoordinatesBlock =
+ Mark.Custom.block "cartesian-coordinates" cartesianCoordinatesView
+
+ cartesianCoordinatesView : Styling -> Model -> Element Msg
+ cartesianCoordinatesView style model =
+ model.cartesianCoordinates
+ |> CartesianCoordinates.ui
+ |> Element.el
+ [ Element.centerX
+ ]
+ |> Element.el
+ [ Element.centerX
+ , Border.color (Element.rgb 1 0.6 0.6)
+ , Border.rounded 5
+ , Border.width 2
+ ]
+ |> Element.map CartesianCoordinatesMsg
= in
= { default
= | blocks =
= counterBlock
= :: transformationsBlock
+ :: cartesianCoordinatesBlock
= :: Mark.defaultBlocks
= }Embed centered dot example
index da39b4c..c024bc9 100644
--- a/index.txt
+++ b/index.txt
@@ -36,6 +36,10 @@ Setup the development environment
=
=| counter
=
+Embedded centered dot example:
+
+| centered-dot
+
=Embedded cartesian coordinates example:
=
=| cartesian-coordinatesindex cbd5f2e..fef1177 100644
--- a/src/CenteredDot.elm
+++ b/src/CenteredDot.elm
@@ -1,4 +1,4 @@
-module CenteredDot exposing (main)
+module CenteredDot exposing (main, ui)
=
=import Element
=import Svg
@@ -10,9 +10,14 @@ main =
= [ Element.width Element.fill
= , Element.height Element.fill
= ]
- (Element.html
- (Svg.svg
- [ Svg.Attributes.viewBox "-1000 -1000 2000 2000" ]
- [ Svg.circle [ Svg.Attributes.r "10" ] [] ]
- )
+ ui
+
+
+ui =
+ Element.html
+ (Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200"
+ , Svg.Attributes.height "200"
+ ]
+ [ Svg.circle [ Svg.Attributes.r "10" ] [] ]
= )index 9610763..7327b7d 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -2,6 +2,7 @@ module Main exposing (main)
=
=import Browser
=import CartesianCoordinates
+import CenteredDot
=import Counter
=import Element exposing (Element)
=import Element.Background as Background
@@ -165,6 +166,23 @@ options =
= ]
= |> Element.map CounterMsg
=
+ centeredDotBlock : Mark.Custom.Block Model Styling Msg
+ centeredDotBlock =
+ Mark.Custom.block "centered-dot" centeredDotView
+
+ centeredDotView : Styling -> Model -> Element Msg
+ centeredDotView style model =
+ CenteredDot.ui
+ |> Element.el
+ [ Element.centerX
+ ]
+ |> Element.el
+ [ Element.centerX
+ , Border.color (Element.rgb 1 0.6 0.6)
+ , Border.rounded 5
+ , Border.width 2
+ ]
+
= transformationsBlock : Mark.Custom.Block Model Styling Msg
= transformationsBlock =
= Mark.Custom.block "transformations" transformationsView
@@ -206,6 +224,7 @@ options =
= { default
= | blocks =
= counterBlock
+ :: centeredDotBlock
= :: transformationsBlock
= :: cartesianCoordinatesBlock
= :: Mark.defaultBlocksEmbed fill the screen example
index c024bc9..8d8b39c 100644
--- a/index.txt
+++ b/index.txt
@@ -36,6 +36,10 @@ Setup the development environment
=
=| counter
=
+Embedded fill the screen example:
+
+| fill-the-screen
+
=Embedded centered dot example:
=
=| centered-dotindex 753928b..d7cfc83 100644
--- a/src/FillTheScreen.elm
+++ b/src/FillTheScreen.elm
@@ -1,4 +1,4 @@
-module FillTheScreen exposing (main)
+module FillTheScreen exposing (main, ui)
=
=import Element
=import Svg
@@ -10,15 +10,18 @@ main =
= [ Element.width Element.fill
= , Element.height Element.fill
= ]
- (Element.html
- (Svg.svg
- [ Svg.Attributes.style "background: pink; height: 100%; width: 100%" ]
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "30"
- , Svg.Attributes.cy "30"
- ]
- []
+ ui
+
+
+ui =
+ Element.html
+ (Svg.svg
+ [ Svg.Attributes.style "background: pink; height: 100%; width: 100%" ]
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "30"
+ , Svg.Attributes.cy "30"
= ]
- )
+ []
+ ]
= )index 7327b7d..e962fd1 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -8,6 +8,7 @@ import Element exposing (Element)
=import Element.Background as Background
=import Element.Border as Border
=import Element.Input as Input
+import FillTheScreen
=import Html exposing (Html)
=import Http
=import Mark
@@ -166,6 +167,24 @@ options =
= ]
= |> Element.map CounterMsg
=
+ fillTheScreenBlock : Mark.Custom.Block Model Styling Msg
+ fillTheScreenBlock =
+ Mark.Custom.block "fill-the-screen" fillTheScreenView
+
+ fillTheScreenView : Styling -> Model -> Element Msg
+ fillTheScreenView style model =
+ FillTheScreen.ui
+ |> Element.el
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+ |> Element.el
+ [ Element.centerX
+ , Border.color (Element.rgb 1 0.6 0.6)
+ , Border.rounded 5
+ , Border.width 2
+ ]
+
= centeredDotBlock : Mark.Custom.Block Model Styling Msg
= centeredDotBlock =
= Mark.Custom.block "centered-dot" centeredDotView
@@ -224,6 +243,7 @@ options =
= { default
= | blocks =
= counterBlock
+ :: fillTheScreenBlock
= :: centeredDotBlock
= :: transformationsBlock
= :: cartesianCoordinatesBlockEmbed gradient example
index 8d8b39c..b6243e7 100644
--- a/index.txt
+++ b/index.txt
@@ -44,6 +44,10 @@ Embedded centered dot example:
=
=| centered-dot
=
+Embedded gradient example:
+
+| gradient
+
=Embedded cartesian coordinates example:
=
=| cartesian-coordinatesindex 81e07be..4aa189c 100644
--- a/src/Gradient.elm
+++ b/src/Gradient.elm
@@ -1,4 +1,4 @@
-module Gradient exposing (main)
+module Gradient exposing (main, ui)
=
=import Element
=import Svg
@@ -10,36 +10,41 @@ main =
= [ Element.width Element.fill
= , Element.height Element.fill
= ]
- (Element.html
- (Svg.svg
- [ Svg.Attributes.viewBox "-100 -100 200 200" ]
- [ Svg.defs []
- [ Svg.linearGradient
- [ Svg.Attributes.id "blue-pink-gradient"
- , Svg.Attributes.x1 "0"
- , Svg.Attributes.y1 "0"
- , Svg.Attributes.x2 "1"
- , Svg.Attributes.y2 "0"
- , Svg.Attributes.gradientUnits "userSpaceOnUse"
+ ui
+
+
+ui =
+ Element.html
+ (Svg.svg
+ [ Svg.Attributes.height "200"
+ , Svg.Attributes.viewBox "-10 -25 100 100"
+ ]
+ [ Svg.defs []
+ [ Svg.linearGradient
+ [ Svg.Attributes.id "blue-pink-gradient"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x2 "1"
+ , Svg.Attributes.y2 "0"
+ , Svg.Attributes.gradientUnits "userSpaceOnUse"
+ ]
+ [ Svg.stop
+ [ Svg.Attributes.stopColor "blue"
+ , Svg.Attributes.offset "0"
= ]
- [ Svg.stop
- [ Svg.Attributes.stopColor "blue"
- , Svg.Attributes.offset "0"
- ]
- []
- , Svg.stop
- [ Svg.Attributes.stopColor "pink"
- , Svg.Attributes.offset "1"
- ]
- []
+ []
+ , Svg.stop
+ [ Svg.Attributes.stopColor "pink"
+ , Svg.Attributes.offset "1"
= ]
+ []
= ]
- , Svg.line
- [ Svg.Attributes.x2 "1"
- , Svg.Attributes.stroke "url(#blue-pink-gradient)"
- , Svg.Attributes.transform "rotate(30) scale(100, 1)"
- ]
- []
= ]
- )
+ , Svg.line
+ [ Svg.Attributes.x2 "1"
+ , Svg.Attributes.stroke "url(#blue-pink-gradient)"
+ , Svg.Attributes.transform "rotate(30) scale(100, 1)"
+ ]
+ []
+ ]
= )index e962fd1..7799c4b 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -9,6 +9,7 @@ import Element.Background as Background
=import Element.Border as Border
=import Element.Input as Input
=import FillTheScreen
+import Gradient
=import Html exposing (Html)
=import Http
=import Mark
@@ -202,6 +203,23 @@ options =
= , Border.width 2
= ]
=
+ gradientBlock : Mark.Custom.Block Model Styling Msg
+ gradientBlock =
+ Mark.Custom.block "gradient" gradientView
+
+ gradientView : Styling -> Model -> Element Msg
+ gradientView style model =
+ Gradient.ui
+ |> Element.el
+ [ Element.centerX
+ ]
+ |> Element.el
+ [ Element.centerX
+ , Border.color (Element.rgb 1 0.6 0.6)
+ , Border.rounded 5
+ , Border.width 2
+ ]
+
= transformationsBlock : Mark.Custom.Block Model Styling Msg
= transformationsBlock =
= Mark.Custom.block "transformations" transformationsView
@@ -244,6 +262,7 @@ options =
= | blocks =
= counterBlock
= :: fillTheScreenBlock
+ :: gradientBlock
= :: centeredDotBlock
= :: transformationsBlock
= :: cartesianCoordinatesBlockEmbed line example
index b6243e7..5481c9e 100644
--- a/index.txt
+++ b/index.txt
@@ -44,6 +44,10 @@ Embedded centered dot example:
=
=| centered-dot
=
+Embedded line example:
+
+| line
+
=Embedded gradient example:
=
=| gradientindex 0eee932..53ab314 100644
--- a/src/Line.elm
+++ b/src/Line.elm
@@ -1,4 +1,4 @@
-module Line exposing (main)
+module Line exposing (main, ui)
=
=import Element
=import Svg
@@ -10,15 +10,20 @@ main =
= [ Element.width Element.fill
= , Element.height Element.fill
= ]
- (Element.html
- (Svg.svg
- [ Svg.Attributes.viewBox "-100 -100 200 200" ]
- [ Svg.line
- [ Svg.Attributes.x2 "1"
- , Svg.Attributes.stroke "black"
- , Svg.Attributes.transform "rotate(30) scale(100, 1)"
- ]
- []
+ ui
+
+
+ui =
+ Element.html
+ (Svg.svg
+ [ Svg.Attributes.height "200"
+ , Svg.Attributes.viewBox "-10 -25 100 100"
+ ]
+ [ Svg.line
+ [ Svg.Attributes.x2 "1"
+ , Svg.Attributes.stroke "black"
+ , Svg.Attributes.transform "rotate(30) scale(100, 1)"
= ]
- )
+ []
+ ]
= )index 7799c4b..20f8888 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -12,6 +12,7 @@ import FillTheScreen
=import Gradient
=import Html exposing (Html)
=import Http
+import Line
=import Mark
=import Mark.Custom
=import Result.Extra as Result
@@ -203,6 +204,23 @@ options =
= , Border.width 2
= ]
=
+ lineBlock : Mark.Custom.Block Model Styling Msg
+ lineBlock =
+ Mark.Custom.block "line" lineView
+
+ lineView : Styling -> Model -> Element Msg
+ lineView style model =
+ Line.ui
+ |> Element.el
+ [ Element.centerX
+ ]
+ |> Element.el
+ [ Element.centerX
+ , Border.color (Element.rgb 1 0.6 0.6)
+ , Border.rounded 5
+ , Border.width 2
+ ]
+
= gradientBlock : Mark.Custom.Block Model Styling Msg
= gradientBlock =
= Mark.Custom.block "gradient" gradientView
@@ -262,6 +280,7 @@ options =
= | blocks =
= counterBlock
= :: fillTheScreenBlock
+ :: lineBlock
= :: gradientBlock
= :: centeredDotBlock
= :: transformationsBlockEmbed nested transformations program
index 5481c9e..b76690a 100644
--- a/index.txt
+++ b/index.txt
@@ -60,4 +60,8 @@ Embeded transformations example:
=
=| transformations
=
+Embeded nested-transformations example:
+
+| nested-transformations
+
=Finito!index 20f8888..72898e4 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -15,6 +15,7 @@ import Http
=import Line
=import Mark
=import Mark.Custom
+import NestedTransformations
=import Result.Extra as Result
=import Transformations
=
@@ -36,6 +37,7 @@ type alias Model =
= { markup : Maybe String
= , counter : Counter.Model
= , transformations : Transformations.Model
+ , nestedTransformations : NestedTransformations.Model
= , cartesianCoordinates : CartesianCoordinates.Model
= }
=
@@ -44,6 +46,7 @@ type Msg
= = DocumentFetched (Result Http.Error String)
= | CounterMsg Counter.Msg
= | TransformationsMsg Transformations.Msg
+ | NestedTransformationsMsg NestedTransformations.Msg
= | CartesianCoordinatesMsg CartesianCoordinates.Msg
=
=
@@ -52,6 +55,7 @@ init flags =
= ( { markup = Nothing
= , counter = Counter.init
= , transformations = Transformations.init
+ , nestedTransformations = NestedTransformations.init
= , cartesianCoordinates = CartesianCoordinates.init
= }
= , Http.get
@@ -125,6 +129,11 @@ update msg model =
= , Cmd.none
= )
=
+ NestedTransformationsMsg m ->
+ ( { model | nestedTransformations = NestedTransformations.update m model.nestedTransformations }
+ , Cmd.none
+ )
+
= CartesianCoordinatesMsg m ->
= ( { model | cartesianCoordinates = CartesianCoordinates.update m model.cartesianCoordinates }
= , Cmd.none
@@ -257,6 +266,25 @@ options =
= ]
= |> Element.map TransformationsMsg
=
+ nestedTransformationsBlock : Mark.Custom.Block Model Styling Msg
+ nestedTransformationsBlock =
+ Mark.Custom.block "nested-transformations" nestedTransformationsView
+
+ nestedTransformationsView : Styling -> Model -> Element Msg
+ nestedTransformationsView style model =
+ model.nestedTransformations
+ |> NestedTransformations.ui
+ |> Element.el
+ [ Element.centerX
+ ]
+ |> Element.el
+ [ Element.centerX
+ , Border.color (Element.rgb 1 0.6 0.6)
+ , Border.rounded 5
+ , Border.width 2
+ ]
+ |> Element.map NestedTransformationsMsg
+
= cartesianCoordinatesBlock : Mark.Custom.Block Model Styling Msg
= cartesianCoordinatesBlock =
= Mark.Custom.block "cartesian-coordinates" cartesianCoordinatesView
@@ -284,6 +312,7 @@ options =
= :: gradientBlock
= :: centeredDotBlock
= :: transformationsBlock
+ :: nestedTransformationsBlock
= :: cartesianCoordinatesBlock
= :: Mark.defaultBlocks
= }index 30da4f4..fa27050 100644
--- a/src/NestedTransformations.elm
+++ b/src/NestedTransformations.elm
@@ -1,4 +1,11 @@
-module Transformations exposing (main)
+module NestedTransformations exposing
+ ( Model
+ , Msg
+ , init
+ , main
+ , ui
+ , update
+ )
=
=import Array exposing (Array)
=import Basics.Extra exposing (..)
@@ -9,6 +16,7 @@ import Dict.Any as Dict exposing (AnyDict)
=import Element exposing (Element)
=import Element.Background as Background
=import Element.Border as Border
+import Element.Font as Font
=import Element.Input as Input
=import Geometry.Svg
=import Html exposing (Html)
@@ -21,18 +29,13 @@ import Svg.Attributes exposing (..)
=
=
=main =
- Browser.element
+ Browser.sandbox
= { init = init
= , view = view
= , update = update
- , subscriptions = subscriptions
= }
=
=
-type alias Flags =
- ()
-
-
=type alias Model =
= AnyDict String Group (Array Transformation)
=
@@ -59,9 +62,9 @@ type GroupMsg
= | SetTransformation Int Transformation
=
=
-init : Flags -> ( Model, Cmd Msg )
-init () =
- ( Dict.empty Debug.toString
+init : Model
+init =
+ Dict.empty Debug.toString
= |> Dict.insert Pink
= (Array.fromList
= [ Translate 0 0
@@ -76,12 +79,19 @@ init () =
= , Scale 1 1
= ]
= )
- , Cmd.none
- )
=
=
=view : Model -> Html Msg
=view model =
+ Element.layout
+ [ Element.height Element.fill
+ , Element.width Element.fill
+ ]
+ (ui model)
+
+
+ui : Model -> Element Msg
+ui model =
= let
= wrapper element =
= Element.column
@@ -148,10 +158,6 @@ view model =
= |> List.singleton
= |> CartesianPlane.graph 300
= |> wrapper
- |> Element.layout
- [ Element.height Element.fill
- , Element.width Element.fill
- ]
=
=
=transformationsUI : List Transformation -> Element GroupMsg
@@ -179,11 +185,12 @@ transformationsUI transformations =
= in
= Element.column
= [ Element.width Element.fill
- , Element.spacing 10
+ , Font.size 12
+ , Element.spacing 4
= ]
= [ Element.row
= [ Element.width Element.fill
- , Element.spacing 10
+ , Element.spacing 2
= ]
= addButtons
= , Element.column
@@ -301,7 +308,7 @@ transformationUI index transformation =
= |> Element.el [ Element.width Element.fill ]
= , Input.button []
= { onPress = Just (DeleteTransformation index)
- , label = Element.text "X"
+ , label = Element.el [] (Element.text "X")
= }
= ]
= , Element.column
@@ -312,7 +319,7 @@ transformationUI index transformation =
= ]
=
=
-update : Msg -> Model -> ( Model, Cmd Msg )
+update : Msg -> Model -> Model
=update (Msg group msg) model =
= let
= transformations =
@@ -344,14 +351,7 @@ update (Msg group msg) model =
= in
= Array.append front back
= in
- ( Dict.insert group transformations model
- , Cmd.none
- )
-
-
-subscriptions : Model -> Sub Msg
-subscriptions model =
- Sub.none
+ Dict.insert group transformations model
=
=
=apply : List Transformation -> StringEmbed polar coordinates program
index b76690a..5dd325d 100644
--- a/index.txt
+++ b/index.txt
@@ -56,6 +56,10 @@ Embedded cartesian coordinates example:
=
=| cartesian-coordinates
=
+Embedded polar coordinates example:
+
+| polar-coordinates
+
=Embeded transformations example:
=
=| transformationsindex 72898e4..0d3eb5a 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -16,6 +16,7 @@ import Line
=import Mark
=import Mark.Custom
=import NestedTransformations
+import PolarCoordinates
=import Result.Extra as Result
=import Transformations
=
@@ -39,6 +40,7 @@ type alias Model =
= , transformations : Transformations.Model
= , nestedTransformations : NestedTransformations.Model
= , cartesianCoordinates : CartesianCoordinates.Model
+ , polarCoordinates : PolarCoordinates.Model
= }
=
=
@@ -48,6 +50,7 @@ type Msg
= | TransformationsMsg Transformations.Msg
= | NestedTransformationsMsg NestedTransformations.Msg
= | CartesianCoordinatesMsg CartesianCoordinates.Msg
+ | PolarCoordinatesMsg PolarCoordinates.Msg
=
=
=init : Flags -> ( Model, Cmd Msg )
@@ -57,6 +60,7 @@ init flags =
= , transformations = Transformations.init
= , nestedTransformations = NestedTransformations.init
= , cartesianCoordinates = CartesianCoordinates.init
+ , polarCoordinates = PolarCoordinates.init
= }
= , Http.get
= { url = "/index.txt"
@@ -139,6 +143,11 @@ update msg model =
= , Cmd.none
= )
=
+ PolarCoordinatesMsg m ->
+ ( { model | polarCoordinates = PolarCoordinates.update m model.polarCoordinates }
+ , Cmd.none
+ )
+
=
=subscriptions : Model -> Sub Msg
=subscriptions model =
@@ -303,6 +312,25 @@ options =
= , Border.width 2
= ]
= |> Element.map CartesianCoordinatesMsg
+
+ polarCoordinatesBlock : Mark.Custom.Block Model Styling Msg
+ polarCoordinatesBlock =
+ Mark.Custom.block "polar-coordinates" polarCoordinatesView
+
+ polarCoordinatesView : Styling -> Model -> Element Msg
+ polarCoordinatesView style model =
+ model.polarCoordinates
+ |> PolarCoordinates.ui
+ |> Element.el
+ [ Element.centerX
+ ]
+ |> Element.el
+ [ Element.centerX
+ , Border.color (Element.rgb 1 0.6 0.6)
+ , Border.rounded 5
+ , Border.width 2
+ ]
+ |> Element.map PolarCoordinatesMsg
= in
= { default
= | blocks =
@@ -314,5 +342,6 @@ options =
= :: transformationsBlock
= :: nestedTransformationsBlock
= :: cartesianCoordinatesBlock
+ :: polarCoordinatesBlock
= :: Mark.defaultBlocks
= }index 7a0d79c..5da8133 100644
--- a/src/PolarCoordinates.elm
+++ b/src/PolarCoordinates.elm
@@ -1,10 +1,8 @@
=module PolarCoordinates exposing
- ( Flags
- , Model
+ ( Model
= , Msg
= , init
= , main
- , subscriptions
= , ui
= , update
= , view
@@ -22,18 +20,13 @@ import Svg.Attributes exposing (..)
=
=
=main =
- Browser.element
+ Browser.sandbox
= { init = init
= , view = view
= , update = update
- , subscriptions = subscriptions
= }
=
=
-type alias Flags =
- ()
-
-
=type alias Model =
= { angle : Float
= , radius : Float
@@ -45,27 +38,18 @@ type Msg
= | SetRadius Float
=
=
-init : Flags -> ( Model, Cmd Msg )
-init () =
- ( { angle = 0, radius = 0.5 }
- , Cmd.none
- )
+init : Model
+init =
+ { angle = 0, radius = 75 }
=
=
-view : Model -> Html.Html Msg
+view : Model -> Html Msg
=view model =
- let
- wrapper element =
- Element.el
- [ Element.width (Element.maximum 600 Element.fill), Element.centerX ]
- element
- in
- ui model
- |> wrapper
- |> Element.layout
- [ Element.height Element.fill
- , Element.width Element.fill
- ]
+ Element.layout
+ [ Element.height Element.fill
+ , Element.width Element.fill
+ ]
+ (ui model)
=
=
=ui : Model -> Element Msg
@@ -86,11 +70,11 @@ ui model =
= ]
= <|
= Element.html <|
- CartesianPlane.graph
+ CartesianPlane.graph 300
= [ circle
= [ cx <| String.fromFloat point.x
= , cy <| String.fromFloat point.y
- , r "0.01"
+ , r "2"
= , fill "magenta"
= ]
= []
@@ -100,16 +84,16 @@ ui model =
= , y1 "0"
= , y2 <| String.fromFloat point.y
= , stroke "magenta"
- , strokeWidth "0.001"
+ , strokeWidth "0.5"
= ]
= []
= , text_
- [ x <| String.fromFloat (point.x + 0.02)
- , y <| String.fromFloat (point.y + 0.01)
- , Svg.Attributes.fontSize "0.03pt"
+ [ x <| String.fromFloat (point.x + 5)
+ , y <| String.fromFloat (point.y + 5)
+ , Svg.Attributes.fontSize "10pt"
= ]
= [ text <|
- Debug.toString ( point.x, point.y )
+ Debug.toString ( round point.x, round point.y )
= ]
= ]
= , Input.slider
@@ -151,7 +135,7 @@ ui model =
= Input.labelBelow [ Element.centerX ] <|
= Element.text ("radius value: " ++ String.fromFloat model.radius)
= , min = 0
- , max = 1
+ , max = 150
= , value = model.radius
= , thumb = Input.defaultThumb
= , step = Just 0.01
@@ -159,19 +143,14 @@ ui model =
= ]
=
=
-update : Msg -> Model -> ( Model, Cmd Msg )
+update : Msg -> Model -> Model
=update msg model =
= case msg of
= SetAngle angle ->
- ( { model | angle = angle }, Cmd.none )
+ { model | angle = angle }
=
= SetRadius radius ->
- ( { model | radius = radius }, Cmd.none )
-
-
-subscriptions : Model -> Sub Msg
-subscriptions model =
- Sub.none
+ { model | radius = radius }
=
=
=cartesian : { angle : Float, radius : Float } -> { x : Float, y : Float }
Commits: 2
Evolve the Main program into Browser.element
index d1066f3..939218f 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -12,13 +12,18 @@ import Result.Extra as Result
=
=
=main =
- Browser.sandbox
+ Browser.element
= { init = init
= , view = view
= , update = update
+ , subscriptions = subscriptions
= }
=
=
+type alias Flags =
+ ()
+
+
=type alias Model =
= { counter : Counter.Model }
=
@@ -27,9 +32,12 @@ type Msg
= = CounterMsg Counter.Msg
=
=
-init =
- { counter = Counter.init
- }
+init : Flags -> ( Model, Cmd Msg )
+init flags =
+ ( { counter = Counter.init
+ }
+ , Cmd.none
+ )
=
=
=view : Model -> Html Msg
@@ -42,10 +50,18 @@ view model =
= |> Element.layout []
=
=
+update : Msg -> Model -> ( Model, Cmd Msg )
=update msg model =
= case msg of
= CounterMsg m ->
- { model | counter = Counter.update m model.counter }
+ ( { model | counter = Counter.update m model.counter }
+ , Cmd.none
+ )
+
+
+subscriptions : Model -> Sub Msg
+subscriptions model =
+ Sub.none
=
=
=type alias Styling =Separate markup content to own file, fetch it with HTTP
The newly released elm/http 2.0.0 requires updated versions of elm/core and elm/json.
index d305cfa..c770fd4 100644
--- a/elm.json
+++ b/elm.json
@@ -7,9 +7,10 @@
= "dependencies": {
= "direct": {
= "elm/browser": "1.0.0",
- "elm/core": "1.0.0",
+ "elm/core": "1.0.2",
= "elm/html": "1.0.0",
- "elm/json": "1.0.0",
+ "elm/http": "2.0.0",
+ "elm/json": "1.1.2",
= "elm/svg": "1.0.1",
= "elm-community/basics-extra": "4.0.0",
= "elm-community/list-extra": "8.1.0",
@@ -22,6 +23,8 @@
= "turboMaCk/any-dict": "1.0.1"
= },
= "indirect": {
+ "elm/bytes": "1.0.7",
+ "elm/file": "1.0.1",
= "elm/parser": "1.1.0",
= "elm/time": "1.0.0",
= "elm/url": "1.0.0",
@@ -35,4 +38,4 @@
= "direct": {},
= "indirect": {}
= }
-}
\ No newline at end of file
+}new file mode 100644
index 0000000..5dc347c
--- /dev/null
+++ b/index.txt
@@ -0,0 +1,39 @@
+| header
+ To do:
+
+Steps to reproduce the tree:
+
+Make a dot
+
+ Centered (Elm UI, viewBox, cartesian coordinates)
+
+Make a line
+
+ Play with transformations (union types)
+
+Gradients
+
+Multiple lines
+
+ Rosettes (different kinds)
+
+Groups and transformations
+
+ Spiral (recursion)
+
+Tree
+
+
+| header
+ Before the course begins
+
+Setup the development environment
+
+| list
+ - Elm
+ - Node.js
+
+
+| counter
+
+Last sentenceindex 939218f..b8450ee 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -6,6 +6,7 @@ import Element exposing (Element)
=import Element.Border as Border
=import Element.Input as Input
=import Html exposing (Html)
+import Http
=import Mark
=import Mark.Custom
=import Result.Extra as Result
@@ -25,34 +26,64 @@ type alias Flags =
=
=
=type alias Model =
- { counter : Counter.Model }
+ { markup : Maybe String
+ , counter : Counter.Model
+ }
=
=
=type Msg
- = CounterMsg Counter.Msg
+ = DocumentFetched (Result Http.Error String)
+ | CounterMsg Counter.Msg
=
=
=init : Flags -> ( Model, Cmd Msg )
=init flags =
- ( { counter = Counter.init
+ ( { markup = Nothing
+ , counter = Counter.init
= }
- , Cmd.none
+ , Http.get
+ { url = "/index.txt"
+ , expect = Http.expectString DocumentFetched
+ }
= )
=
=
=view : Model -> Html Msg
=view model =
- content
- |> Mark.parseWith options
- |> Result.mapError Debug.toString
- |> Result.map (\fn -> fn model)
- |> Result.extract Element.text
- |> Element.layout []
+ let
+ content =
+ case model.markup of
+ Nothing ->
+ Element.el [ Element.centerX, Element.centerY ] <|
+ Element.text "Loading content..."
+
+ Just markup ->
+ markup
+ |> Mark.parseWith options
+ |> Result.mapError Debug.toString
+ |> Result.map (\fn -> fn model)
+ |> Result.extract Element.text
+ in
+ Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+ content
=
=
=update : Msg -> Model -> ( Model, Cmd Msg )
=update msg model =
- case msg of
+ case Debug.log "update" msg of
+ DocumentFetched (Ok markup) ->
+ ( { model | markup = Just markup }
+ , Cmd.none
+ )
+
+ DocumentFetched (Err markup) ->
+ ( { model | markup = Nothing }
+ , Cmd.none
+ )
+
= CounterMsg m ->
= ( { model | counter = Counter.update m model.counter }
= , Cmd.none
@@ -101,50 +132,3 @@ options =
= | blocks =
= counterBlock :: Mark.defaultBlocks
= }
-
-
-content =
- """
-| header
- To do:
-
-Steps to reproduce the tree:
-
-Make a dot
-
- Centered (Elm UI, viewBox, cartesian coordinates)
-
-Make a line
-
- Play with transformations (union types)
-
-Gradients
-
-Multiple lines
-
- Rosettes (different kinds)
-
-Groups and transformations
-
- Spiral (recursion)
-
-Tree
-
-
-| header
- Before the course begins
-
-Setup the development environment
-
-| list
- - Elm
- - Node.js
-
-
-| counter
-
-Last sentence
-
-| counter
-
-"""
Commits: 3
Make sliders stepped in Transformations example
Also other minor tweaks.
index 744c43a..0d66cf5 100644
--- a/src/Transformations.elm
+++ b/src/Transformations.elm
@@ -51,7 +51,11 @@ type Msg
=
=init : Flags -> ( Model, Cmd Msg )
=init () =
- ( Array.empty
+ ( Array.fromList
+ [ Translate 0 0
+ , Rotate 0
+ , Scale 1 1
+ ]
= , Cmd.none
= )
=
@@ -69,9 +73,17 @@ view model =
= , Element.spacing 20
= ]
= [ Element.el
- [ Element.width Element.fill ]
+ [ Element.width Element.fill
+ ]
= (Element.html element)
- , transformationsUI transformations
+ , Element.row [ Element.width Element.fill ]
+ [ Element.el
+ [ Background.color (Element.rgb 1 0.8 0.8)
+ , Element.padding 10
+ , Element.width Element.fill
+ ]
+ (transformationsUI transformations)
+ ]
= ]
=
= shape =
@@ -111,7 +123,8 @@ transformationsUI : List Transformation -> Element Msg
=transformationsUI transformations =
= let
= addButtons =
- [ Input.button []
+ [ Element.text "Add transformation: "
+ , Input.button []
= { onPress = Just (AddTransformation (Translate 0 0))
= , label = Element.text "Translate"
= }
@@ -176,7 +189,7 @@ transformationUI index transformation =
= , max = 10
= , value = horizontal
= , thumb = Input.defaultThumb
- , step = Nothing
+ , step = Just 0.1
= }
= , Input.slider
= [ Element.behindContent sliderBackground
@@ -189,7 +202,7 @@ transformationUI index transformation =
= , max = 10
= , value = vertical
= , thumb = Input.defaultThumb
- , step = Nothing
+ , step = Just 0.1
= }
= ]
=
@@ -205,7 +218,7 @@ transformationUI index transformation =
= , max = 100
= , value = x
= , thumb = Input.defaultThumb
- , step = Nothing
+ , step = Just 1
= }
= , Input.slider
= [ Element.behindContent sliderBackground
@@ -218,7 +231,7 @@ transformationUI index transformation =
= , max = 100
= , value = y
= , thumb = Input.defaultThumb
- , step = Nothing
+ , step = Just 1
= }
= ]
=
@@ -234,13 +247,14 @@ transformationUI index transformation =
= , max = 360
= , value = angle
= , thumb = Input.defaultThumb
- , step = Nothing
+ , step = Just 1
= }
= ]
= in
= Element.column
= [ Element.width Element.fill
- , Background.color (Element.rgb 0.9 0.9 0.9)
+ , Border.color (Element.rgb 0.9 0.9 0.9)
+ , Border.width 3
= , Element.padding 5
= , Element.spacing 20
= ]Implement NestedTransformations example
index d17943a..6cfda8a 100644
--- a/elm.json
+++ b/elm.json
@@ -11,11 +11,13 @@
= "elm/html": "1.0.0",
= "elm/json": "1.0.0",
= "elm/svg": "1.0.1",
+ "elm-community/basics-extra": "4.0.0",
= "elm-community/list-extra": "8.1.0",
= "elm-explorations/markdown": "1.0.0",
= "ianmackenzie/elm-geometry": "1.2.1",
= "ianmackenzie/elm-geometry-svg": "1.0.2",
- "mdgriffith/elm-ui": "1.1.0"
+ "mdgriffith/elm-ui": "1.1.0",
+ "turboMaCk/any-dict": "1.0.1"
= },
= "indirect": {
= "elm/time": "1.0.0",new file mode 100644
index 0000000..30da4f4
--- /dev/null
+++ b/src/NestedTransformations.elm
@@ -0,0 +1,437 @@
+module Transformations exposing (main)
+
+import Array exposing (Array)
+import Basics.Extra exposing (..)
+import Browser
+import Browser.Events
+import CartesianPlane
+import Dict.Any as Dict exposing (AnyDict)
+import Element exposing (Element)
+import Element.Background as Background
+import Element.Border as Border
+import Element.Input as Input
+import Geometry.Svg
+import Html exposing (Html)
+import Json.Decode exposing (Decoder)
+import LineSegment2d
+import List.Extra as List
+import Point2d
+import Svg exposing (..)
+import Svg.Attributes exposing (..)
+
+
+main =
+ Browser.element
+ { init = init
+ , view = view
+ , update = update
+ , subscriptions = subscriptions
+ }
+
+
+type alias Flags =
+ ()
+
+
+type alias Model =
+ AnyDict String Group (Array Transformation)
+
+
+type Transformation
+ = Identity
+ | Scale Float Float
+ | Translate Float Float
+ | Rotate Float
+
+
+type Group
+ = Pink
+ | Green
+
+
+type Msg
+ = Msg Group GroupMsg
+
+
+type GroupMsg
+ = AddTransformation Transformation
+ | DeleteTransformation Int
+ | SetTransformation Int Transformation
+
+
+init : Flags -> ( Model, Cmd Msg )
+init () =
+ ( Dict.empty Debug.toString
+ |> Dict.insert Pink
+ (Array.fromList
+ [ Translate 0 0
+ , Rotate 0
+ , Scale 1 1
+ ]
+ )
+ |> Dict.insert Green
+ (Array.fromList
+ [ Translate 0 0
+ , Rotate 0
+ , Scale 1 1
+ ]
+ )
+ , Cmd.none
+ )
+
+
+view : Model -> Html Msg
+view model =
+ let
+ wrapper element =
+ Element.column
+ [ Element.width (Element.maximum 600 Element.fill)
+ , Element.centerX
+ , Element.spacing 20
+ ]
+ [ Element.el
+ [ Element.width Element.fill
+ ]
+ (Element.html element)
+ , Element.row [ Element.width Element.fill ]
+ (model
+ |> Dict.toList
+ |> List.map (uncurry controls)
+ )
+ ]
+
+ controls : Group -> Array Transformation -> Element Msg
+ controls group transformations =
+ Element.el
+ [ Background.color (toColor group)
+ , Element.padding 10
+ , Element.width Element.fill
+ ]
+ (transformations
+ |> Array.toList
+ |> transformationsUI
+ |> Element.map (Msg group)
+ )
+
+ shape =
+ Dict.foldr
+ nestTransformationsGroup
+ (g [] [])
+ model
+
+ nestTransformationsGroup : Group -> Array Transformation -> Svg Msg -> Svg Msg
+ nestTransformationsGroup group transformations item =
+ let
+ transformation =
+ transformations |> Array.toList |> apply
+
+ color =
+ group
+ |> Debug.toString
+ |> String.toLower
+ in
+ g [ transform transformation ]
+ [ line
+ [ x1 "0"
+ , x2 "100"
+ , y1 "0"
+ , y2 "0"
+ , stroke color
+ , strokeWidth "1"
+ ]
+ []
+ , circle [ cx "0", cy "0", r "2", fill color ] []
+ , item
+ ]
+ in
+ shape
+ |> List.singleton
+ |> CartesianPlane.graph 300
+ |> wrapper
+ |> Element.layout
+ [ Element.height Element.fill
+ , Element.width Element.fill
+ ]
+
+
+transformationsUI : List Transformation -> Element GroupMsg
+transformationsUI transformations =
+ let
+ addButtons =
+ [ Element.text "Add transformation: "
+ , Input.button []
+ { onPress = Just (AddTransformation (Translate 0 0))
+ , label = Element.text "Translate"
+ }
+ , Input.button []
+ { onPress = Just (AddTransformation (Scale 1 1))
+ , label = Element.text "Scale"
+ }
+ , Input.button []
+ { onPress = Just (AddTransformation (Rotate 0))
+ , label = Element.text "Rotate"
+ }
+ ]
+
+ currentTrasformations =
+ transformations
+ |> List.indexedMap transformationUI
+ in
+ Element.column
+ [ Element.width Element.fill
+ , Element.spacing 10
+ ]
+ [ Element.row
+ [ Element.width Element.fill
+ , Element.spacing 10
+ ]
+ addButtons
+ , Element.column
+ [ Element.width Element.fill
+ , Element.spacing 10
+ ]
+ currentTrasformations
+ ]
+
+
+transformationUI : Int -> Transformation -> Element GroupMsg
+transformationUI index transformation =
+ let
+ sliderBackground =
+ Element.el
+ [ Element.width Element.fill
+ , Element.height (Element.px 2)
+ , Element.centerY
+ , Background.color <| Element.rgb 0.7 0.7 0.7
+ , Border.rounded 2
+ ]
+ Element.none
+
+ controls =
+ case transformation of
+ Identity ->
+ [ Element.text <| Debug.toString transformation ]
+
+ Scale horizontal vertical ->
+ [ Input.slider
+ [ Element.behindContent sliderBackground
+ ]
+ { onChange =
+ \x ->
+ SetTransformation index (Scale x vertical)
+ , label = Input.labelLeft [] (Element.text "horizontal")
+ , min = 0
+ , max = 10
+ , value = horizontal
+ , thumb = Input.defaultThumb
+ , step = Just 0.1
+ }
+ , Input.slider
+ [ Element.behindContent sliderBackground
+ ]
+ { onChange =
+ \y ->
+ SetTransformation index (Scale horizontal y)
+ , label = Input.labelLeft [] (Element.text "vertical")
+ , min = 0
+ , max = 10
+ , value = vertical
+ , thumb = Input.defaultThumb
+ , step = Just 0.1
+ }
+ ]
+
+ Translate x y ->
+ [ Input.slider
+ [ Element.behindContent sliderBackground
+ ]
+ { onChange =
+ \value ->
+ SetTransformation index (Translate value y)
+ , label = Input.labelLeft [] (Element.text "x")
+ , min = -100
+ , max = 100
+ , value = x
+ , thumb = Input.defaultThumb
+ , step = Just 1
+ }
+ , Input.slider
+ [ Element.behindContent sliderBackground
+ ]
+ { onChange =
+ \value ->
+ SetTransformation index (Translate x value)
+ , label = Input.labelLeft [] (Element.text "y")
+ , min = -100
+ , max = 100
+ , value = y
+ , thumb = Input.defaultThumb
+ , step = Just 1
+ }
+ ]
+
+ Rotate angle ->
+ [ Input.slider
+ [ Element.behindContent sliderBackground
+ ]
+ { onChange =
+ \value ->
+ SetTransformation index (Rotate value)
+ , label = Input.labelLeft [] (Element.text "angle")
+ , min = -360
+ , max = 360
+ , value = angle
+ , thumb = Input.defaultThumb
+ , step = Just 1
+ }
+ ]
+ in
+ Element.column
+ [ Element.width Element.fill
+ , Border.color (Element.rgb 0.9 0.9 0.9)
+ , Border.width 3
+ , Element.padding 5
+ , Element.spacing 20
+ ]
+ [ Element.row [ Element.width Element.fill ]
+ [ transformation
+ |> List.singleton
+ |> apply
+ |> Element.text
+ |> Element.el [ Element.width Element.fill ]
+ , Input.button []
+ { onPress = Just (DeleteTransformation index)
+ , label = Element.text "X"
+ }
+ ]
+ , Element.column
+ [ Element.width Element.fill
+ , Element.spacing 20
+ ]
+ controls
+ ]
+
+
+update : Msg -> Model -> ( Model, Cmd Msg )
+update (Msg group msg) model =
+ let
+ transformations =
+ model
+ |> Dict.get group
+ |> Maybe.withDefault Array.empty
+ |> (\current ->
+ case msg of
+ AddTransformation transformation ->
+ Array.push transformation current
+
+ DeleteTransformation index ->
+ arrayDelete index current
+
+ SetTransformation index transformation ->
+ Array.set index transformation current
+ )
+
+ arrayDelete index array =
+ let
+ end =
+ Array.length array
+
+ front =
+ Array.slice 0 index array
+
+ back =
+ Array.slice (index + 1) end array
+ in
+ Array.append front back
+ in
+ ( Dict.insert group transformations model
+ , Cmd.none
+ )
+
+
+subscriptions : Model -> Sub Msg
+subscriptions model =
+ Sub.none
+
+
+apply : List Transformation -> String
+apply transformations =
+ let
+ toString : Transformation -> String
+ toString transformation =
+ case transformation of
+ Identity ->
+ ""
+
+ Scale x y ->
+ "scale("
+ ++ String.fromFloat x
+ ++ ", "
+ ++ String.fromFloat y
+ ++ ")"
+
+ Translate x y ->
+ "translate("
+ ++ String.fromFloat x
+ ++ ", "
+ ++ String.fromFloat y
+ ++ ")"
+
+ Rotate angle ->
+ "rotate("
+ ++ String.fromFloat angle
+ ++ ")"
+ in
+ transformations
+ |> List.map toString
+ |> String.join " "
+
+
+toColor : Group -> Element.Color
+toColor group =
+ case group of
+ Pink ->
+ Element.rgb 1 0.73 0.8
+
+ Green ->
+ Element.rgb 0.0 0.5 0.0
+
+
+grid : List (Svg.Attribute msg) -> Float -> Float -> Svg msg
+grid attributes unit size =
+ let
+ positiveValues =
+ size
+ / 2
+ |> floor
+ |> List.range 1
+ |> List.map toFloat
+ |> List.map ((*) unit)
+
+ negativeValues =
+ positiveValues
+ |> List.map negate
+
+ max =
+ unit * size / 2
+
+ min =
+ negate max
+ in
+ ((positiveValues ++ negativeValues)
+ |> List.map
+ (\value ->
+ [ ( Point2d.fromCoordinates ( value, min )
+ , Point2d.fromCoordinates ( value, max )
+ )
+ , ( Point2d.fromCoordinates ( min, value )
+ , Point2d.fromCoordinates ( max, value )
+ )
+ ]
+ )
+ |> List.concat
+ |> List.map LineSegment2d.fromEndpoints
+ |> List.map (Geometry.Svg.lineSegment2d attributes)
+ )
+ |> (::) (CartesianPlane.axes attributes (size * unit))
+ |> g []Reimplement Main with Elm Markup, nest a simple counter program
The idea is that Main will be our website and all the example programs will be nested in it.
index 6cfda8a..d305cfa 100644
--- a/elm.json
+++ b/elm.json
@@ -13,13 +13,16 @@
= "elm/svg": "1.0.1",
= "elm-community/basics-extra": "4.0.0",
= "elm-community/list-extra": "8.1.0",
+ "elm-community/result-extra": "2.2.1",
= "elm-explorations/markdown": "1.0.0",
= "ianmackenzie/elm-geometry": "1.2.1",
= "ianmackenzie/elm-geometry-svg": "1.0.2",
+ "mdgriffith/elm-markup": "1.0.0",
= "mdgriffith/elm-ui": "1.1.0",
= "turboMaCk/any-dict": "1.0.1"
= },
= "indirect": {
+ "elm/parser": "1.1.0",
= "elm/time": "1.0.0",
= "elm/url": "1.0.0",
= "elm/virtual-dom": "1.0.2",new file mode 100644
index 0000000..0f0689c
--- /dev/null
+++ b/src/Counter.elm
@@ -0,0 +1,71 @@
+module Counter exposing
+ ( Model
+ , Msg
+ , init
+ , main
+ , ui
+ , update
+ )
+
+import Browser
+import Element exposing (Element)
+import Element.Border as Border
+import Element.Input as Input
+import Html exposing (Html)
+
+
+main =
+ Browser.sandbox
+ { init = init
+ , view = view
+ , update = update
+ }
+
+
+type alias Model =
+ Int
+
+
+type Msg
+ = Increment
+ | Decrement
+
+
+init =
+ 0
+
+
+view : Model -> Html Msg
+view model =
+ model
+ |> ui
+ |> Element.layout []
+
+
+ui : Model -> Element Msg
+ui model =
+ Element.row
+ [ Element.padding 10
+ , Element.spacing 10
+ ]
+ [ Input.button []
+ { onPress = Just Decrement
+ , label = Element.text "-"
+ }
+ , model
+ |> String.fromInt
+ |> Element.text
+ , Input.button []
+ { onPress = Just Increment
+ , label = Element.text "+"
+ }
+ ]
+
+
+update msg model =
+ case msg of
+ Increment ->
+ model + 1
+
+ Decrement ->
+ model - 1index f18bc73..d1066f3 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -1,578 +1,134 @@
=module Main exposing (main)
=
-import Browser exposing (Document)
-import Browser.Events
-import CartesianCoordinates
-import Dict
-import Element
-import Element.Font as Font
-import Element.Keyed
-import Html
-import Html.Attributes
-import Json.Decode as Decode exposing (Decoder)
-import PolarCoordinates
-import Presentation exposing (Slide, markdown)
+import Browser
+import Counter
+import Element exposing (Element)
+import Element.Border as Border
+import Element.Input as Input
+import Html exposing (Html)
+import Mark
+import Mark.Custom
+import Result.Extra as Result
=
=
=main =
- Browser.document
+ Browser.sandbox
= { init = init
= , view = view
= , update = update
- , subscriptions = subscriptions
= }
=
=
-type alias Flags =
- ()
-
-
=type alias Model =
- { currentSlide : Int
-
- -- Nested programs
- , cartesianCoordinates : CartesianCoordinates.Model
- , polarCoordinates : PolarCoordinates.Model
- }
+ { counter : Counter.Model }
=
=
=type Msg
- = Next
- | Previous
- -- Nested programs
- | CartesianCoordinatesMsg CartesianCoordinates.Msg
- | PolarCoordinatesMsg PolarCoordinates.Msg
+ = CounterMsg Counter.Msg
=
=
-init : Flags -> ( Model, Cmd Msg )
-init flags =
- let
- ( cartesianCoordinatesModel, cartesianCoordinatesCmd ) =
- CartesianCoordinates.init ()
+init =
+ { counter = Counter.init
+ }
=
- ( polarCoordinatesModel, polarCoordinatesCmd ) =
- PolarCoordinates.init ()
- in
- ( { currentSlide = 0
- , cartesianCoordinates = cartesianCoordinatesModel
- , polarCoordinates = polarCoordinatesModel
- }
- , Cmd.batch
- [ Cmd.map CartesianCoordinatesMsg cartesianCoordinatesCmd
- , Cmd.map PolarCoordinatesMsg polarCoordinatesCmd
- ]
- )
-
-
-view : Model -> Document Msg
+
+view : Model -> Html Msg
=view model =
- { title = "FP-Art!"
- , body =
- [ Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
- ]
- <|
- Element.column
- [ Element.centerX
- , Font.center
- , Element.height Element.fill
- , Element.width (Element.maximum 800 Element.fill)
- ]
- [ slides model
- |> Dict.get model.currentSlide
- |> Maybe.map
- (Element.column
- [ Element.width Element.fill
- , Element.centerY
- ]
- )
- |> Maybe.withDefault (Element.text "404: Slide not found")
- ]
- ]
- }
+ content
+ |> Mark.parseWith options
+ |> Result.mapError Debug.toString
+ |> Result.map (\fn -> fn model)
+ |> Result.extract Element.text
+ |> Element.layout []
=
=
-update : Msg -> Model -> ( Model, Cmd Msg )
=update msg model =
= case msg of
- Next ->
- ( { model
- | currentSlide =
- min (Dict.size (slides model) - 1) (model.currentSlide + 1)
- }
- , Cmd.none
- )
-
- Previous ->
- ( { model
- | currentSlide =
- max 0 (model.currentSlide - 1)
- }
- , Cmd.none
- )
-
- -- Nested programs
- CartesianCoordinatesMsg msg_ ->
- let
- ( model_, cmd_ ) =
- CartesianCoordinates.update msg_ model.cartesianCoordinates
- in
- ( { model | cartesianCoordinates = model_ }
- , Cmd.map CartesianCoordinatesMsg cmd_
- )
-
- PolarCoordinatesMsg msg_ ->
- let
- ( model_, cmd_ ) =
- PolarCoordinates.update msg_ model.polarCoordinates
- in
- ( { model | polarCoordinates = model_ }
- , Cmd.map PolarCoordinatesMsg cmd_
- )
-
-
-subscriptions model =
- let
- handleKeyPress : Decoder Msg
- handleKeyPress =
- Decode.field "key" Decode.string
- |> Decode.andThen
- (\key ->
- case Debug.log "Key" key of
- "ArrowLeft" ->
- Decode.succeed Previous
-
- "ArrowRight" ->
- Decode.succeed Next
-
- "a" ->
- Decode.succeed Previous
-
- "d" ->
- Decode.succeed Next
-
- _ ->
- Decode.fail "Unsupported key"
- )
- in
- Browser.Events.onKeyPress handleKeyPress
-
-
-slides model =
- Dict.fromList <|
- List.indexedMap Tuple.pair <|
- [ markdown """
- # Software Garden
- ## A functional programming workshop
- ### for non-programmers
- """
- ]
- :: [ markdown """
- ## Setup
- """
- ]
- :: [ markdown """
- > This setup instructions are based on an assumption that you are using a Mac.
- >
- > If you are using Linux or BSD, then you probably know how to install stuff.
- >
- > If you are using anything else, then... well, good luck.
- >
- > The rest of the instructions should work with any platform.
-
- """ ]
- :: [ markdown """
-
- We will need:
-
- - a text editor (I use [Atom][])
- - and the [Elm programming language][]
-
- [Atom]: https://atom.io/
- [Elm programming language]: https://elm-lang.org/
-
- """
- ]
- :: [ markdown """
- ### Elm Installation
- """
- ]
- :: [ markdown """
- To install the Elm programming language, go to the [website][Elm] and follow the installation instructions.
-
- [Elm]: http://elm-lang.org/
- """
- ]
- :: [ markdown """
- Some of the tools we use with Elm require Node.js.
-
- Go to the [Node.js website][Node.js] and install the current version (the green button on the right).
-
- [Node.js]: https://nodejs.org/en/
- """
- ]
- :: [ markdown """
- We will need to use the terminal a little bit.
-
- # :fa-terminal:
-
- Don't be scared. It's easy :)
-
- <!-- slide data-background-image="images/mac-launchpad-terminal.png" data-background-size="cover" data-background-position="top center"-->
-
- > <p style="color: white">In Launchpad find a program called <code>terminal</code> and start it.</p>
-
- """
- ]
- :: [ markdown """
- You should see a window like this
- """
- , Html.img
- [ Html.Attributes.alt "Mac Terminal app window"
- , Html.Attributes.src "../assets/mac-terminal-window.png"
- ]
- []
- |> Element.html
- |> Element.el
- [ Element.height Element.fill
- , Element.width Element.fill
- ]
- , markdown """
- Here is some more markdown.
- """
- ]
- :: [ markdown """
-
- Now we are going to install few things.
-
- - Homebrew (to install other things)
- - Elm programming language
- - Atom editor
-
- """
- ]
- :: [ markdown """
-
- ### Install Homebrew
-
- Follow instructions on the [Homebrew website](https://brew.sh/) by typing the following in the terminal (you probably want to copy and paste it):
-
- ```sh
- /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
- ```
-
- <small>
- Make sure that the command is exactly like the one above, including the `/` character at the beginning, the quotes and parentheses.
-
- It will ask you to confirm several actions and ask for your password. It may take few minutes to finish, so get your coffee
- </small>
-
- :fa-coffee:
-
- Once it's done you should see a command prompt like that:
-
- ```
- ~ your-name$
- ```
-
-
- """
- ]
- :: [ markdown """
-
- ### Install the Elm programming language
-
- <small>Once we have Homebrew installed, we can use it to install the language.</small>
-
- Type the following in the terminal.
-
- ```sh
- brew install node elm
- ```
-
- and check if it works by typing:
-
- ```
- elm repl
- ```
-
- """
- ]
- :: [ markdown """
-
- Note that the command line changes. You should see something like that
-
- ```
- ---- Elm 0.19.0 ----------------------------------------------------------------
- Read <https://elm-lang.org/0.19.0/repl> to learn more: exit, help, imports, etc.
- --------------------------------------------------------------------------------
- >
- ```
-
- It's the Elm REPL
-
- <small>Read - Evaluate - Print Loop</small>
-
- *[REPL]: Read - Evaluate - Print Loop.
-
- """
- ]
- :: [ markdown """
-
- Inside the REPL type
-
- ```
- 2 + 2
- ```
-
- And expect to see
-
- ```
- ---- Elm 0.19.0 ----------------------------------------------------------------
- Read <https://elm-lang.org/0.19.0/repl> to learn more: exit, help, imports, etc.
- --------------------------------------------------------------------------------
- > 2 + 2
- 4 : number
- >
- ```
-
- Easy, huh?
-
- """
- ]
- :: [ markdown """
-
- We will learn more about REPL later. For now type `:exit` to close it.
+ CounterMsg m ->
+ { model | counter = Counter.update m model.counter }
=
- The command line should look like before again.
=
- """
- ]
- :: [ markdown """
+type alias Styling =
+ Mark.Styling Msg
=
- ### Install Atom text editor
=
- Computer programs are represented as text, so the text editor is the most fundamental tool of a programmer. There is a lot of good text editors, but to get you started, we will use [Atom] here.
+type alias Options =
+ Mark.Options Model Styling Msg
=
- """
- ]
- :: [ markdown """
=
- Type following in the terminal:
-
- ```
- brew cask install atom
- ```
-
- And start it with:
-
- ```sh
- atom
- ```
-
- """
- ]
- :: [ markdown """
-
- One last thing we need is Elm support for Atom editor. You can install it by typing in the terminal:
-
- ```
- apm install language-elm
- ```
-
- <small>APM is the Atom Package Manager, but don't pay much attention to it. We won't be using it again.</small>
-
- """
- ]
- :: [ markdown """
-
- **We are all set!**
-
- :smile:
-
- """
- ]
- :: [ markdown """
-
-
- # First program!
-
- """
- ]
- :: [ markdown """
-
- As mentioned before, programs are represented as text (called *the source code*).
-
- The source code is stored in files
-
- :fa-file:
-
- and files are organized in directories
-
- :fa-folder:
-
- """
- ]
- :: [ markdown """
-
- So the first step is to create a directory for our new program. Let's call it `fpart`.
-
- In the terminal type
-
- ```sh
- mkdir fpart/
- ```
-
- and then
-
- ```
- cd fpart/
- ```
-
- <small>This creates a new directory and makes it the current one. Again, don't worry about the details.</small>
-
- """
- ]
- :: [ markdown """
-
- To easily create a new program, we can type
-
- ```
- elm init
- ```
-
- """
- ]
- :: [ markdown """
-
- Then to create a file with source code, type
-
- ```
- atom src/Main.elm
- ```
-
- <small>This command should open a new Atom window with empty text file.</small>
-
- """
- ]
- :: [ markdown """
-
- ### `main.elm`
-
- ```elm
- module Main exposing (main)
-
- import Html
-
-
- main =
- Html.text "Hello, Tree!"
- ```
-
- <small>Type the above in the editor and save the file</small>
-
- """
- ]
- :: [ markdown """
-
- To see the program running type following in the terminal
-
- ```sh
- elm reactor
- ```
-
- <small>This command starts the Elm reactor, which will let you run your program in the web browser. Note that it won't give you the command line back - it will run as long as you don't stop it.</small>
-
- """
- ]
- :: [ markdown """
-
- # Voila!
-
- <small>Open following address in the web browser</small>
-
- http://localhost:8000/src/Main.elm
+options : Options
+options =
+ let
+ default =
+ Mark.default
+
+ counterBlock : Mark.Custom.Block Model Styling Msg
+ counterBlock =
+ Mark.Custom.block "counter" counterView
+
+ counterView : Styling -> Model -> Element Msg
+ counterView style model =
+ model.counter
+ |> Counter.ui
+ |> Element.el
+ [ Element.centerX
+ ]
+ |> Element.el
+ [ Element.centerX
+ , Border.color (Element.rgb 1 0.6 0.6)
+ , Border.rounded 5
+ , Border.width 2
+ ]
+ |> Element.map CounterMsg
+ in
+ { default
+ | blocks =
+ counterBlock :: Mark.defaultBlocks
+ }
=
- <small>Note that the same address was printed by Elm reactor</small>
=
- """
- ]
- :: [ markdown """
+content =
+ """
+| header
+ To do:
=
- # Let's make a dot!
- # :fa-circle:
+Steps to reproduce the tree:
=
- """
- ]
- :: [ markdown """
+Make a dot
=
- We are going to use a technology called SVG
+ Centered (Elm UI, viewBox, cartesian coordinates)
=
- <small>Scalable Vector Graphics</small>
+Make a line
=
+ Play with transformations (union types)
=
- *[SVG]: Scalable Vector Graphics
+Gradients
=
- """
- ]
- :: [ markdown """
+Multiple lines
=
- Let's install an Elm package to help us work with SVG.
+ Rosettes (different kinds)
=
- Stop the Reactor running in terminal by pressing
+Groups and transformations
=
- `CTRL-C`
+ Spiral (recursion)
=
- and type the following
+Tree
=
- ```
- elm install elm/svg
- ```
=
- Then start the reactor again
+| header
+ Before the course begins
=
- ```
- elm reactor
- ```
+Setup the development environment
=
- <small>you can press up arrow :fa-arrow-up: on the keyboard to get to previous commands</small>
+| list
+ - Elm
+ - Node.js
=
- """
- ]
- :: [ markdown """
=
- <iframe id="cartesian" data-src="./CartesianCoordinates.html" class="stretch">
- </iframe>
+| counter
=
- """
- ]
- :: [ markdown """
- Move sliders to change the `x` and `y` coordinates of the dot. It's like moving a thing on a table by telling how far left, right, up or down should it go.
- """
- , model.cartesianCoordinates
- |> CartesianCoordinates.ui
- |> Element.map CartesianCoordinatesMsg
- |> Element.el
- [ Element.centerX
- , Element.width (Element.maximum 600 Element.fill)
- ]
- ]
- :: [ markdown """
- Move sliders to change the `angle` and `length` properties of the line connecting the dot and origin. The x and y coordinates will be calculated like this:
+Last sentence
=
- ```
- x = cos(angle) * length
+| counter
=
- y = sin(angle) * length
- ```
- """
- , model.polarCoordinates
- |> PolarCoordinates.ui
- |> Element.map PolarCoordinatesMsg
- |> Element.el
- [ Element.centerX
- , Element.width (Element.maximum 600 Element.fill)
- ]
- ]
- :: []
+"""
Commits: 1
Make transformations example interactive
Separate axes from CartesianPlane.graph.
index 6e4ad58..6029a39 100644
--- a/src/CartesianPlane.elm
+++ b/src/CartesianPlane.elm
@@ -1,25 +1,19 @@
-module CartesianPlane exposing (graph)
+module CartesianPlane exposing (axes, graph)
=
+import Direction2d
+import Geometry.Svg
=import Html exposing (Html)
+import LineSegment2d
+import Point2d exposing (Point2d)
=import Svg exposing (..)
=import Svg.Attributes exposing (..)
+import Triangle2d
+import Vector2d
=
=
=graph : Float -> List (Svg msg) -> Html msg
=graph size shapes =
= let
- top =
- 0 - size / 2
-
- left =
- 0 - size / 2
-
- bottom =
- size / 2
-
- right =
- size / 2
-
= hairline =
= size / 1000
=
@@ -27,29 +21,14 @@ graph size shapes =
= size / 200
=
= background =
- g []
- [ line
- [ x1 (String.fromFloat left)
- , y1 "0"
- , x2 (String.fromFloat right)
- , y2 "0"
- , stroke "black"
- , strokeWidth (String.fromFloat hairline)
- ]
- []
- , line
- [ x1 "0"
- , x2 "0"
- , y1 (String.fromFloat top)
- , y2 (String.fromFloat bottom)
- , stroke "black"
- , strokeWidth (String.fromFloat hairline)
- ]
- []
+ axes
+ [ stroke "gray"
+ , fill "gray"
= ]
+ size
= in
= svg
- [ [ left, top, size, size ]
+ [ [ negate size / 2, negate size / 2, size, size ]
= |> List.map String.fromFloat
= |> String.join " "
= |> viewBox
@@ -59,3 +38,85 @@ graph size shapes =
= , Svg.Attributes.style "width: 100%, height: 100%"
= ]
= (background :: shapes)
+
+
+axes attributes size =
+ let
+ max =
+ size / 2
+
+ min =
+ negate max
+
+ xAxis =
+ { start = Point2d.fromCoordinates ( min, 0 )
+ , end = Point2d.fromCoordinates ( max, 0 )
+ }
+
+ yAxis =
+ { start = Point2d.fromCoordinates ( 0, min )
+ , end = Point2d.fromCoordinates ( 0, max )
+ }
+ in
+ g []
+ [ arrow
+ attributes
+ xAxis.start
+ xAxis.end
+ , arrow
+ attributes
+ yAxis.start
+ yAxis.end
+ , label [ fontSize "8", color "gray" ] (xAxis.end |> Point2d.translateIn (Direction2d.fromAngle (degrees -135)) 10) "x"
+ , label [ fontSize "8", color "gray" ] (yAxis.end |> Point2d.translateIn (Direction2d.fromAngle (degrees -45)) 10) "y"
+ , label [ fontSize "8", color "gray" ] (Point2d.origin |> Point2d.translateIn (Direction2d.fromAngle (degrees 135)) 10) "O"
+ ]
+
+
+arrow : List (Svg.Attribute msg) -> Point2d -> Point2d -> Svg msg
+arrow attributes start end =
+ let
+ origin =
+ Point2d.fromCoordinates ( 0, 0 )
+
+ direction =
+ Direction2d.from start end
+
+ triangle =
+ Triangle2d.fromVertices
+ ( Point2d.fromCoordinates ( 0, 0 )
+ , Point2d.fromCoordinates ( -4, -2 )
+ , Point2d.fromCoordinates ( -4, 2 )
+ )
+
+ vector =
+ Vector2d.from origin end
+ in
+ case direction of
+ Nothing ->
+ g [] []
+
+ Just dir ->
+ g []
+ [ triangle
+ |> Triangle2d.rotateAround origin (Direction2d.toAngle dir)
+ |> Triangle2d.translateBy vector
+ |> Geometry.Svg.triangle2d ([ strokeWidth "0" ] ++ attributes)
+ , end
+ |> Point2d.translateIn dir -4
+ |> LineSegment2d.from start
+ |> Geometry.Svg.lineSegment2d attributes
+ ]
+
+
+label : List (Svg.Attribute msg) -> Point2d -> String -> Svg msg
+label attributes center content =
+ text_
+ ([ x <| String.fromFloat (Point2d.xCoordinate center)
+ , y <| String.fromFloat (Point2d.yCoordinate center)
+ , dominantBaseline "central"
+ , textAnchor "middle"
+ ]
+ ++ attributes
+ )
+ [ text content ]index 80a3718..744c43a 100644
--- a/src/Transformations.elm
+++ b/src/Transformations.elm
@@ -1,13 +1,20 @@
=module Transformations exposing (main)
=
+import Array exposing (Array)
=import Browser
=import Browser.Events
=import CartesianPlane
=import Dict exposing (Dict)
-import Element
+import Element exposing (Element)
+import Element.Background as Background
+import Element.Border as Border
+import Element.Input as Input
+import Geometry.Svg
=import Html exposing (Html)
=import Json.Decode exposing (Decoder)
+import LineSegment2d
=import List.Extra as List
+import Point2d
=import Svg exposing (..)
=import Svg.Attributes exposing (..)
=
@@ -26,7 +33,7 @@ type alias Flags =
=
=
=type alias Model =
- List Transformation
+ Array Transformation
=
=
=type Transformation
@@ -37,16 +44,14 @@ type Transformation
=
=
=type Msg
- = Progress Float
- | Regress Float
+ = AddTransformation Transformation
+ | DeleteTransformation Int
+ | SetTransformation Int Transformation
=
=
=init : Flags -> ( Model, Cmd Msg )
=init () =
- ( [ Identity
- , Scale 2 2
- , Translate 20 0
- ]
+ ( Array.empty
= , Cmd.none
= )
=
@@ -54,13 +59,23 @@ init () =
=view : Model -> Html Msg
=view model =
= let
+ transformations =
+ Array.toList model
+
= wrapper element =
- Element.el
- [ Element.width (Element.maximum 600 Element.fill), Element.centerX ]
- (Element.html element)
+ Element.column
+ [ Element.width (Element.maximum 600 Element.fill)
+ , Element.centerX
+ , Element.spacing 20
+ ]
+ [ Element.el
+ [ Element.width Element.fill ]
+ (Element.html element)
+ , transformationsUI transformations
+ ]
=
= shape =
- g [ transform (apply model) ]
+ g [ transform (apply transformations) ]
= [ line
= [ x1 "0"
= , x2 "100"
@@ -71,13 +86,20 @@ view model =
= ]
= []
= , circle [ cx "0", cy "0", r "2", fill "red" ] []
+ , grid
+ [ stroke "pink"
+ , fill "pink"
+ , strokeWidth "0.3"
+ ]
+ 10
+ 30
=
= -- , rect [ x "", y "-2", width "1", height "4" ] []
= ]
= in
= shape
= |> List.singleton
- |> CartesianPlane.graph 600
+ |> CartesianPlane.graph 300
= |> wrapper
= |> Element.layout
= [ Element.height Element.fill
@@ -85,16 +107,187 @@ view model =
= ]
=
=
+transformationsUI : List Transformation -> Element Msg
+transformationsUI transformations =
+ let
+ addButtons =
+ [ Input.button []
+ { onPress = Just (AddTransformation (Translate 0 0))
+ , label = Element.text "Translate"
+ }
+ , Input.button []
+ { onPress = Just (AddTransformation (Scale 1 1))
+ , label = Element.text "Scale"
+ }
+ , Input.button []
+ { onPress = Just (AddTransformation (Rotate 0))
+ , label = Element.text "Rotate"
+ }
+ ]
+
+ currentTrasformations =
+ transformations
+ |> List.indexedMap transformationUI
+ in
+ Element.column
+ [ Element.width Element.fill
+ , Element.spacing 10
+ ]
+ [ Element.row
+ [ Element.width Element.fill
+ , Element.spacing 10
+ ]
+ addButtons
+ , Element.column
+ [ Element.width Element.fill
+ , Element.spacing 10
+ ]
+ currentTrasformations
+ ]
+
+
+transformationUI : Int -> Transformation -> Element Msg
+transformationUI index transformation =
+ let
+ sliderBackground =
+ Element.el
+ [ Element.width Element.fill
+ , Element.height (Element.px 2)
+ , Element.centerY
+ , Background.color <| Element.rgb 0.7 0.7 0.7
+ , Border.rounded 2
+ ]
+ Element.none
+
+ controls =
+ case transformation of
+ Identity ->
+ [ Element.text <| Debug.toString transformation ]
+
+ Scale horizontal vertical ->
+ [ Input.slider
+ [ Element.behindContent sliderBackground
+ ]
+ { onChange =
+ \x ->
+ SetTransformation index (Scale x vertical)
+ , label = Input.labelLeft [] (Element.text "horizontal")
+ , min = 0
+ , max = 10
+ , value = horizontal
+ , thumb = Input.defaultThumb
+ , step = Nothing
+ }
+ , Input.slider
+ [ Element.behindContent sliderBackground
+ ]
+ { onChange =
+ \y ->
+ SetTransformation index (Scale horizontal y)
+ , label = Input.labelLeft [] (Element.text "vertical")
+ , min = 0
+ , max = 10
+ , value = vertical
+ , thumb = Input.defaultThumb
+ , step = Nothing
+ }
+ ]
+
+ Translate x y ->
+ [ Input.slider
+ [ Element.behindContent sliderBackground
+ ]
+ { onChange =
+ \value ->
+ SetTransformation index (Translate value y)
+ , label = Input.labelLeft [] (Element.text "x")
+ , min = -100
+ , max = 100
+ , value = x
+ , thumb = Input.defaultThumb
+ , step = Nothing
+ }
+ , Input.slider
+ [ Element.behindContent sliderBackground
+ ]
+ { onChange =
+ \value ->
+ SetTransformation index (Translate x value)
+ , label = Input.labelLeft [] (Element.text "y")
+ , min = -100
+ , max = 100
+ , value = y
+ , thumb = Input.defaultThumb
+ , step = Nothing
+ }
+ ]
+
+ Rotate angle ->
+ [ Input.slider
+ [ Element.behindContent sliderBackground
+ ]
+ { onChange =
+ \value ->
+ SetTransformation index (Rotate value)
+ , label = Input.labelLeft [] (Element.text "angle")
+ , min = -360
+ , max = 360
+ , value = angle
+ , thumb = Input.defaultThumb
+ , step = Nothing
+ }
+ ]
+ in
+ Element.column
+ [ Element.width Element.fill
+ , Background.color (Element.rgb 0.9 0.9 0.9)
+ , Element.padding 5
+ , Element.spacing 20
+ ]
+ [ Element.row [ Element.width Element.fill ]
+ [ transformation
+ |> List.singleton
+ |> apply
+ |> Element.text
+ |> Element.el [ Element.width Element.fill ]
+ , Input.button []
+ { onPress = Just (DeleteTransformation index)
+ , label = Element.text "X"
+ }
+ ]
+ , Element.column
+ [ Element.width Element.fill
+ , Element.spacing 20
+ ]
+ controls
+ ]
+
+
=update : Msg -> Model -> ( Model, Cmd Msg )
=update msg model =
= case msg of
- Progress delta ->
- ( model
+ AddTransformation transformation ->
+ ( Array.push transformation model
= , Cmd.none
= )
=
- Regress delta ->
- ( model
+ DeleteTransformation index ->
+ let
+ end =
+ Array.length model
+
+ front =
+ Array.slice 0 index model
+
+ back =
+ Array.slice (index + 1) end model
+ in
+ ( Array.append front back
+ , Cmd.none
+ )
+
+ SetTransformation index transformation ->
+ ( Array.set index transformation model
= , Cmd.none
= )
=
@@ -135,3 +328,43 @@ apply transformations =
= transformations
= |> List.map toString
= |> String.join " "
+
+
+grid : List (Svg.Attribute msg) -> Float -> Float -> Svg msg
+grid attributes unit size =
+ let
+ positiveValues =
+ size
+ / 2
+ |> floor
+ |> List.range 1
+ |> List.map toFloat
+ |> List.map ((*) unit)
+
+ negativeValues =
+ positiveValues
+ |> List.map negate
+
+ max =
+ unit * size / 2
+
+ min =
+ negate max
+ in
+ ((positiveValues ++ negativeValues)
+ |> List.map
+ (\value ->
+ [ ( Point2d.fromCoordinates ( value, min )
+ , Point2d.fromCoordinates ( value, max )
+ )
+ , ( Point2d.fromCoordinates ( min, value )
+ , Point2d.fromCoordinates ( max, value )
+ )
+ ]
+ )
+ |> List.concat
+ |> List.map LineSegment2d.fromEndpoints
+ |> List.map (Geometry.Svg.lineSegment2d attributes)
+ )
+ |> (::) (CartesianPlane.axes attributes (size * unit))
+ |> g []
Commits: 1
Write down steps to reproduce the tree, implement or improve some examples
index c58cd80..47685ff 100644
--- a/index.md
+++ b/index.md
@@ -4,6 +4,29 @@ presentation:
= theme: solarized.css
=---
=
+Steps to reproduce the tree:
+
+Make a dot
+
+ Centered (Elm UI, viewBox, cartesian coordinates)
+
+Make a line
+
+ Play with transformations (union types)
+
+Gradients
+
+Multiple lines
+
+ Rosettes (different kinds)
+
+Groups and transformations
+
+ Spiral (recursion)
+
+Tree
+
+
=<!-- slide -->
=
=## Before the course beginsindex 0382141..cbd5f2e 100644
--- a/src/CenteredDot.elm
+++ b/src/CenteredDot.elm
@@ -1,4 +1,4 @@
-module Simplest exposing (main)
+module CenteredDot exposing (main)
=
=import Element
=import Svgnew file mode 100644
index 0000000..81e07be
--- /dev/null
+++ b/src/Gradient.elm
@@ -0,0 +1,45 @@
+module Gradient exposing (main)
+
+import Element
+import Svg
+import Svg.Attributes
+
+
+main =
+ Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+ (Element.html
+ (Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200" ]
+ [ Svg.defs []
+ [ Svg.linearGradient
+ [ Svg.Attributes.id "blue-pink-gradient"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x2 "1"
+ , Svg.Attributes.y2 "0"
+ , Svg.Attributes.gradientUnits "userSpaceOnUse"
+ ]
+ [ Svg.stop
+ [ Svg.Attributes.stopColor "blue"
+ , Svg.Attributes.offset "0"
+ ]
+ []
+ , Svg.stop
+ [ Svg.Attributes.stopColor "pink"
+ , Svg.Attributes.offset "1"
+ ]
+ []
+ ]
+ ]
+ , Svg.line
+ [ Svg.Attributes.x2 "1"
+ , Svg.Attributes.stroke "url(#blue-pink-gradient)"
+ , Svg.Attributes.transform "rotate(30) scale(100, 1)"
+ ]
+ []
+ ]
+ )
+ )new file mode 100644
index 0000000..0eee932
--- /dev/null
+++ b/src/Line.elm
@@ -0,0 +1,24 @@
+module Line exposing (main)
+
+import Element
+import Svg
+import Svg.Attributes
+
+
+main =
+ Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+ (Element.html
+ (Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200" ]
+ [ Svg.line
+ [ Svg.Attributes.x2 "1"
+ , Svg.Attributes.stroke "black"
+ , Svg.Attributes.transform "rotate(30) scale(100, 1)"
+ ]
+ []
+ ]
+ )
+ )new file mode 100644
index 0000000..f39d91f
--- /dev/null
+++ b/src/LineTypedTransformations.elm
@@ -0,0 +1,71 @@
+module LineTypedTransformations exposing (main)
+
+import Element
+import Svg
+import Svg.Attributes
+
+
+main =
+ Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+ (Element.html
+ (Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200" ]
+ [ Svg.line
+ [ Svg.Attributes.x2 "1"
+ , Svg.Attributes.stroke "black"
+ , transform
+ [ Rotate 30
+ , Scale 100 1
+ ]
+ ]
+ []
+ ]
+ )
+ )
+
+
+type Transformation
+ = Identity
+ | Scale Float Float
+ | Translate Float Float
+ | Rotate Float
+
+
+
+-- transform : List Transformation -> Svg.Attribute msg
+
+
+transform transformations =
+ let
+ toString : Transformation -> String
+ toString transformation =
+ case transformation of
+ Identity ->
+ ""
+
+ Scale x y ->
+ "scale("
+ ++ String.fromFloat x
+ ++ ", "
+ ++ String.fromFloat y
+ ++ ")"
+
+ Translate x y ->
+ "translate("
+ ++ String.fromFloat x
+ ++ ", "
+ ++ String.fromFloat y
+ ++ ")"
+
+ Rotate angle ->
+ "rotate("
+ ++ String.fromFloat angle
+ ++ ")"
+ in
+ transformations
+ |> List.map toString
+ |> String.join " "
+ |> Svg.Attributes.transformnew file mode 100644
index 0000000..d9d2060
--- /dev/null
+++ b/src/Rosette.elm
@@ -0,0 +1,69 @@
+module Rosette exposing (main)
+
+import Element
+import Svg
+import Svg.Attributes
+
+
+main =
+ Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+ (Element.html
+ (Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200" ]
+ [ Svg.defs []
+ [ Svg.linearGradient
+ [ Svg.Attributes.id "blue-pink-gradient"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x2 "1"
+ , Svg.Attributes.y2 "0"
+ , Svg.Attributes.gradientUnits "userSpaceOnUse"
+ ]
+ [ Svg.stop
+ [ Svg.Attributes.stopColor "blue"
+ , Svg.Attributes.offset "0"
+ ]
+ []
+ , Svg.stop
+ [ Svg.Attributes.stopColor "pink"
+ , Svg.Attributes.offset "1"
+ ]
+ []
+ ]
+ ]
+ , Svg.line
+ [ Svg.Attributes.x2 "1"
+ , Svg.Attributes.stroke "url(#blue-pink-gradient)"
+ , Svg.Attributes.transform "rotate(0) scale(100, 1)"
+ ]
+ []
+ , Svg.line
+ [ Svg.Attributes.x2 "1"
+ , Svg.Attributes.stroke "url(#blue-pink-gradient)"
+ , Svg.Attributes.transform "rotate(72) scale(100, 1)"
+ ]
+ []
+ , Svg.line
+ [ Svg.Attributes.x2 "1"
+ , Svg.Attributes.stroke "url(#blue-pink-gradient)"
+ , Svg.Attributes.transform "rotate(144) scale(100, 1)"
+ ]
+ []
+ , Svg.line
+ [ Svg.Attributes.x2 "1"
+ , Svg.Attributes.stroke "url(#blue-pink-gradient)"
+ , Svg.Attributes.transform "rotate(216) scale(100, 1)"
+ ]
+ []
+ , Svg.line
+ [ Svg.Attributes.x2 "1"
+ , Svg.Attributes.stroke "url(#blue-pink-gradient)"
+ , Svg.Attributes.transform "rotate(288) scale(100, 1)"
+ ]
+ []
+ ]
+ )
+ )new file mode 100644
index 0000000..dcd20c1
--- /dev/null
+++ b/src/RosetteTypedTransformations.elm
@@ -0,0 +1,125 @@
+module RosetteTypedTransformations exposing (main)
+
+import Element
+import Svg
+import Svg.Attributes
+
+
+main =
+ Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+ (Element.html
+ (Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200" ]
+ [ Svg.defs []
+ [ Svg.linearGradient
+ [ Svg.Attributes.id "blue-pink-gradient"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x2 "1"
+ , Svg.Attributes.y2 "0"
+ , Svg.Attributes.gradientUnits "userSpaceOnUse"
+ ]
+ [ Svg.stop
+ [ Svg.Attributes.stopColor "blue"
+ , Svg.Attributes.offset "0"
+ ]
+ []
+ , Svg.stop
+ [ Svg.Attributes.stopColor "pink"
+ , Svg.Attributes.offset "1"
+ ]
+ []
+ ]
+ ]
+ , Svg.line
+ [ Svg.Attributes.x2 "1"
+ , Svg.Attributes.stroke "url(#blue-pink-gradient)"
+ , transform
+ [ Rotate 0
+ , Scale 100 1
+ ]
+ ]
+ []
+ , Svg.line
+ [ Svg.Attributes.x2 "1"
+ , Svg.Attributes.stroke "url(#blue-pink-gradient)"
+ , transform
+ [ Rotate 72
+ , Scale 100 1
+ ]
+ ]
+ []
+ , Svg.line
+ [ Svg.Attributes.x2 "1"
+ , Svg.Attributes.stroke "url(#blue-pink-gradient)"
+ , transform
+ [ Rotate 144
+ , Scale 100 1
+ ]
+ ]
+ []
+ , Svg.line
+ [ Svg.Attributes.x2 "1"
+ , Svg.Attributes.stroke "url(#blue-pink-gradient)"
+ , transform
+ [ Rotate 216
+ , Scale 100 1
+ ]
+ ]
+ []
+ , Svg.line
+ [ Svg.Attributes.x2 "1"
+ , Svg.Attributes.stroke "url(#blue-pink-gradient)"
+ , transform
+ [ Rotate 288
+ , Scale 100 1
+ ]
+ ]
+ []
+ ]
+ )
+ )
+
+
+type Transformation
+ = Identity
+ | Scale Float Float
+ | Translate Float Float
+ | Rotate Float
+
+
+transform : List Transformation -> Svg.Attribute msg
+transform transformations =
+ let
+ toString : Transformation -> String
+ toString transformation =
+ case transformation of
+ Identity ->
+ ""
+
+ Scale x y ->
+ "scale("
+ ++ String.fromFloat x
+ ++ ", "
+ ++ String.fromFloat y
+ ++ ")"
+
+ Translate x y ->
+ "translate("
+ ++ String.fromFloat x
+ ++ ", "
+ ++ String.fromFloat y
+ ++ ")"
+
+ Rotate angle ->
+ "rotate("
+ ++ String.fromFloat angle
+ ++ ")"
+ in
+ transformations
+ |> List.map toString
+ |> String.join " "
+ |> Svg.Attributes.transformindex 8710e3c..9686d17 100644
--- a/src/Simplest.elm
+++ b/src/Simplest.elm
@@ -6,10 +6,12 @@ import Svg.Attributes
=
=main =
= Svg.svg [ Svg.Attributes.style "background: pink" ]
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "30"
- , Svg.Attributes.cy "30"
+ [ Svg.line
+ [ Svg.Attributes.x1 "0"
+ , Svg.Attributes.x2 "1"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.y2 "0"
+ , Svg.Attributes.stroke "black"
= ]
= []
= ]new file mode 100644
index 0000000..f2e2aa0
--- /dev/null
+++ b/src/Spiral.elm
@@ -0,0 +1,110 @@
+module Spiral exposing (main)
+
+import Element
+import Svg
+import Svg.Attributes
+
+
+main =
+ let
+ defs =
+ Svg.defs []
+ [ Svg.linearGradient
+ [ Svg.Attributes.id "blue-pink-gradient"
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x2 "1"
+ , Svg.Attributes.y2 "0"
+ , Svg.Attributes.gradientUnits "userSpaceOnUse"
+ ]
+ [ Svg.stop
+ [ Svg.Attributes.stopColor "blue"
+ , Svg.Attributes.offset "0"
+ ]
+ []
+ , Svg.stop
+ [ Svg.Attributes.stopColor "pink"
+ , Svg.Attributes.offset "1"
+ ]
+ []
+ ]
+ ]
+ in
+ Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+ (Element.html
+ (Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200" ]
+ (defs :: spiral 500)
+ )
+ )
+
+
+spiral : Int -> List (Svg.Svg msg)
+spiral age =
+ if age > 0 then
+ let
+ length =
+ 500 / toFloat age
+ in
+ [ Svg.line
+ [ Svg.Attributes.x2 "1"
+ , Svg.Attributes.stroke "url(#blue-pink-gradient)"
+ , transform [ Scale length 1 ]
+ ]
+ []
+ , Svg.g
+ [ transform
+ [ Identity
+ , Translate length 0
+ , Rotate 15
+ ]
+ ]
+ (spiral (age - 1))
+ ]
+
+ else
+ []
+
+
+type Transformation
+ = Identity
+ | Scale Float Float
+ | Translate Float Float
+ | Rotate Float
+
+
+transform : List Transformation -> Svg.Attribute msg
+transform transformations =
+ let
+ toString : Transformation -> String
+ toString transformation =
+ case transformation of
+ Identity ->
+ ""
+
+ Scale x y ->
+ "scale("
+ ++ String.fromFloat x
+ ++ ", "
+ ++ String.fromFloat y
+ ++ ")"
+
+ Translate x y ->
+ "translate("
+ ++ String.fromFloat x
+ ++ ", "
+ ++ String.fromFloat y
+ ++ ")"
+
+ Rotate angle ->
+ "rotate("
+ ++ String.fromFloat angle
+ ++ ")"
+ in
+ transformations
+ |> List.map toString
+ |> String.join " "
+ |> Svg.Attributes.transform
Commits: 1
Tree example: Use Elm UI to fill the screen and center the tree
index a7f36d8..cde6818 100644
--- a/src/Tree.elm
+++ b/src/Tree.elm
@@ -3,6 +3,7 @@ module Tree exposing (main)
=import Browser
=import Browser.Events
=import Dict exposing (Dict)
+import Element
=import Html exposing (Html)
=import Json.Decode exposing (Decoder)
=import List.Extra as List
@@ -51,7 +52,7 @@ type alias Rules =
=
=init : Flags -> ( Model, Cmd Msg )
=init () =
- ( { time = 50000
+ ( { time = 0
= , rules =
= Dict.fromList
= [ ( "brown"
@@ -122,17 +123,20 @@ view model =
= )
= ]
= in
- Html.div []
- [ Html.h1 [] [ Html.text <| String.fromFloat model.time ]
- , svg
- [ viewBox "-1000 -1000 2000 2000"
- , height "800px"
- , width "800px"
- ]
- [ defs [] gradients
- , tree
- ]
+ Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
= ]
+ (Element.html <|
+ svg
+ [ viewBox "-1000 -1000 2000 2000"
+ , height "100%"
+ , width "100%"
+ ]
+ [ defs [] gradients
+ , tree
+ ]
+ )
=
=
=update : Msg -> Model -> ( Model, Cmd Msg )
Commits: 7
Fix cartesian plane's Y axis
index 83986b5..6e4ad58 100644
--- a/src/CartesianPlane.elm
+++ b/src/CartesianPlane.elm
@@ -40,7 +40,7 @@ graph size shapes =
= , line
= [ x1 "0"
= , x2 "0"
- , x2 (String.fromFloat top)
+ , y1 (String.fromFloat top)
= , y2 (String.fromFloat bottom)
= , stroke "black"
= , strokeWidth (String.fromFloat hairline)Create simple SVG transformations example
Not interactive yet
new file mode 100644
index 0000000..80a3718
--- /dev/null
+++ b/src/Transformations.elm
@@ -0,0 +1,137 @@
+module Transformations exposing (main)
+
+import Browser
+import Browser.Events
+import CartesianPlane
+import Dict exposing (Dict)
+import Element
+import Html exposing (Html)
+import Json.Decode exposing (Decoder)
+import List.Extra as List
+import Svg exposing (..)
+import Svg.Attributes exposing (..)
+
+
+main =
+ Browser.element
+ { init = init
+ , view = view
+ , update = update
+ , subscriptions = subscriptions
+ }
+
+
+type alias Flags =
+ ()
+
+
+type alias Model =
+ List Transformation
+
+
+type Transformation
+ = Identity
+ | Scale Float Float
+ | Translate Float Float
+ | Rotate Float
+
+
+type Msg
+ = Progress Float
+ | Regress Float
+
+
+init : Flags -> ( Model, Cmd Msg )
+init () =
+ ( [ Identity
+ , Scale 2 2
+ , Translate 20 0
+ ]
+ , Cmd.none
+ )
+
+
+view : Model -> Html Msg
+view model =
+ let
+ wrapper element =
+ Element.el
+ [ Element.width (Element.maximum 600 Element.fill), Element.centerX ]
+ (Element.html element)
+
+ shape =
+ g [ transform (apply model) ]
+ [ line
+ [ x1 "0"
+ , x2 "100"
+ , y1 "0"
+ , y2 "0"
+ , stroke "red"
+ , strokeWidth "1"
+ ]
+ []
+ , circle [ cx "0", cy "0", r "2", fill "red" ] []
+
+ -- , rect [ x "", y "-2", width "1", height "4" ] []
+ ]
+ in
+ shape
+ |> List.singleton
+ |> CartesianPlane.graph 600
+ |> wrapper
+ |> Element.layout
+ [ Element.height Element.fill
+ , Element.width Element.fill
+ ]
+
+
+update : Msg -> Model -> ( Model, Cmd Msg )
+update msg model =
+ case msg of
+ Progress delta ->
+ ( model
+ , Cmd.none
+ )
+
+ Regress delta ->
+ ( model
+ , Cmd.none
+ )
+
+
+subscriptions : Model -> Sub Msg
+subscriptions model =
+ Sub.none
+
+
+apply : List Transformation -> String
+apply transformations =
+ let
+ toString : Transformation -> String
+ toString transformation =
+ case transformation of
+ Identity ->
+ ""
+
+ Scale x y ->
+ "scale("
+ ++ String.fromFloat x
+ ++ ", "
+ ++ String.fromFloat y
+ ++ ")"
+
+ Translate x y ->
+ "translate("
+ ++ String.fromFloat x
+ ++ ", "
+ ++ String.fromFloat y
+ ++ ")"
+
+ Rotate angle ->
+ "rotate("
+ ++ String.fromFloat angle
+ ++ ")"
+ in
+ transformations
+ |> List.map toString
+ |> String.join " "Expand conspect to slides
index b6f46a0..c58cd80 100644
--- a/index.md
+++ b/index.md
@@ -6,7 +6,9 @@ presentation:
=
=<!-- slide -->
=
-## Conspect
+## Before the course begins
+
+<!-- slide -->
=
=- Setup the development environment
=
@@ -20,7 +22,13 @@ presentation:
=
= - Atom
=
-Day 1
+<!-- slide -->
+
+## Day 1
+
+Let's make a dot!
+
+<!-- slide -->
=
=- Setup the project
=
@@ -38,12 +46,17 @@ Day 1
=
= - Web browser
=
+<!-- slide -->
+
+### Introduce a problem
=
-- Let's make a dot!
+We want to have a dot on the screen
=
- - Introduce a problem: We want to have a dot on the screen
+<!-- slide -->
+
+Show complete code
=
- - Show complete code
+<!-- slide -->
=
= - Explain:
=
@@ -53,6 +66,7 @@ Day 1
=
= - Introduce SVG
=
+<!-- slide -->
=
=- Give a color to the dot
=
@@ -60,6 +74,7 @@ Day 1
=
= - Everyone can pick a color
=
+<!-- slide -->
=
=- Multiple dots
=
@@ -67,7 +82,9 @@ Day 1
=
= - Explain a list, and show there is already a list there (with one item)
=
- Do you see any other lists?
+<!-- slide -->
+
+Do you see any other lists?
=
= - Element takes a list of children
=
@@ -79,487 +96,664 @@ Day 1
=
= - Exercise: make 3 more dots (5 total)
=
+<!-- slide -->
=
-Day 2
+## Day 2
=
-- Place the dots in a circle
+Place the dots in a circle
=
- - Introduce a problem: We want to place the dots in a circle: show an example in the slides
+<!-- slide -->
=
- - How we will get there: we will change the cartesian coordinates of the dots, nothing else! But figuring out the right coordinates is a bit tricky
=
- - How can we figure out the correct cartesian coordinates?
+Introduce a problem: We want to place the dots in a circle: show an example in the slides
+
+How we will get there: we will change the cartesian coordinates of the dots, nothing else! But figuring out the right coordinates is a bit tricky
+
+How can we figure out the correct cartesian coordinates?
+
+<!-- slide -->
+
+Story about the flowerpot and the table: two ways to measure relative position, one is distance from a x and y axis (cartesian coordinates), the other is angle and distance relative to origin (polar coordinates)
=
- - Story about the flowerpot and the table: two ways to measure relative position, one is distance from a x and y axis (cartesian coordinates), the other is angle and distance relative to origin (polar coordinates)
+<!-- slide -->
=
- - What does this have to do with a circle? Do you know the expression 'I did a 180'. Where would you be looking if you did two 180s (a "360"). We see that circles and angles are closely related!
+What does this have to do with a circle? Do you know the expression 'I did a 180'. Where would you be looking if you did two 180s (a "360"). We see that circles and angles are closely related!
=
- - Exercise with compass and 5 objects, place objects evenly along compass. How many degrees apart are they? (observe if we multiply the result by 5, we're back to 360)
+<!-- slide -->
=
- - We have one part of it (angle), we now need the distance. Notice they are all the same distance from the origin (the center dot) of the compass. Definition of circle: points that are an equal distance from a center. Actually, the distance doesn't matter, so long as they all have the same distance. You can have a big circle or a small circle, they're both circles
+Exercise with compass and 5 objects, place objects evenly along compass. How many degrees apart are they? (observe if we multiply the result by 5, we're back to 360)
=
- - Ok great, we're done! Now, does anyone know how to give an angle and distance to svg? Oh... no? We don't either... you can't. You can only give x and y values relative to the origin
+<!-- slide -->
=
- - So we already know that angle and length are just another way of describing x and y. But we need some way of translating between the two
+We have one part of it (angle), we now need the distance. Notice they are all the same distance from the origin (the center dot) of the compass. Definition of circle: points that are an equal distance from a center. Actually, the distance doesn't matter, so long as they all have the same distance. You can have a big circle or a small circle, they're both circles
=
- Using a visual demonstration, if you draw a line from your object to the x axis, you have a triangle. Same if you draw a line to the y axis. You can figure out the point on the axis using sin and cos functions and multiplying the result by the length.
+<!-- slide -->
=
- Let's use a chart to figure out the sin and cos of our angles
+Ok great, we're done! Now, does anyone know how to give an angle and distance to svg? Oh... no? We don't either... you can't. You can only give x and y values relative to the origin
=
- *It's important to get a chart, otherwise we have to use calculators which work with radians*
+<!-- slide -->
=
- - Now we are done... we can plug in our x and y values, and presto, our dots are arranged in a circle. But we don't see most of them...
+So we already know that angle and length are just another way of describing x and y. But we need some way of translating between the two
=
- - Our origin is in the top left corner. This means any dots with negative x or y values are off the screen.
=
- We can shift the dots so they are on the screen, or shift the screen so that it covers the dots. We will be shifting the screen
+<!-- slide -->
=
- Sample viewbox program to demonstrate
+Using a visual demonstration, if you draw a line from your object to the x axis, you have a triangle. Same if you draw a line to the y axis. You can figure out the point on the axis using sin and cos functions and multiplying the result by the length.
=
- Create a viewbox with correct perimeters (must be more than the radius of the circle plus the radius of a dot in each direction, and width and height are circumference)
+<!-- slide -->
=
- ```elm
- svg [ viewbox "-100 -100 200 200" ] []
- ```
+Let's use a chart to figure out the sin and cos of our angles
=
-Day 3
+*It's important to get a chart, otherwise we have to use calculators which work with radians*
=
-- Let the computer do the math
+<!-- slide -->
=
- - Introduce the problem: Can we avoid all these repetitive and manual steps?
+Now we are done... we can plug in our x and y values, and presto, our dots are arranged in a circle. But we don't see most of them...
=
- - What is a program made of? (in the REPL):
+<!-- slide -->
=
- - Values and names
+Our origin is in the top left corner. This means any dots with negative x or y values are off the screen.
=
- ```
- 5
- 10.4
- "Hello World"
- [1, 2, 3, 4, 5]
- ```
+We can shift the dots so they are on the screen, or shift the screen so that it covers the dots. We will be shifting the screen
=
- Numbers, strings, lists containing numbers and strings are all values. They are self-referential. They simply state what they are. There are other kinds of values that we will see later.
+<!-- slide -->
=
- You can give (or assign) a name to a value, as shown.
+Sample viewbox program to demonstrate
=
- ```
- family = 5
- price = 10.4
- morningGreeting = "Hello World"
- fingers = [1, 2, 3, 4, 5]
- ```
+<!-- slide -->
=
- Here, `family` is a name and `5` is the value assigned to `family`.
+Create a viewbox with correct perimeters (must be more than the radius of the circle plus the radius of a dot in each direction, and width and height are circumference)
=
- You can get the value back by calling its name.
+```elm
+svg [ viewbox "-100 -100 200 200" ] []
+```
=
- ```
- ---- Elm 0.19.0 ----------------------------------------------------------------
- Read <https://elm-lang.org/0.19.0/repl> to learn more: exit, help, imports, etc.
- --------------------------------------------------------------------------------
- > family = 5
- 5 : number
- > family
- 5 : number
- ```
+<!-- slide -->
=
- - Operators (+, -, ++)
+## Day 3
=
- ```
- > 2 + 5
- 7 : number
- ```
+Let the computer do the math
=
- ```
- > 4 - 6
- -2 : number
- ```
+<!-- slide -->
=
- ```
- > "Hello" ++ " world!"
- "Hello world!" : String
- ```
+Introduce the problem: Can we avoid all these repetitive and manual steps?
=
- ```
- > 1 :: [2, 3, 4, 5]
- [1,2,3,4,5] : List number
- ```
+<!-- slide -->
=
- Operators take two values and return a new value. We know that names refer to values, so we can use them in place of values:
+What is a program made of? (in the REPL):
=
- ```
- > family = 5
- 5 : number
- > family + 2
- 7 : number
- ```
+<!-- slide -->
=
- You can also give a name to the value returned by an operator:
+Values and names
=
- ```
- > family = 5
- 5 : number
- > familyAndPets = family + 2
- 7 : number
- > familyAndPets
- 7 : number
- ```
+```
+5
+10.4
+"Hello World"
+[1, 2, 3, 4, 5]
+```
=
- Note that different values have different types.
+* Numbers like `5`, `10.4`
+* strings like `"Hello world"`
+* and lists containing numbers or strings
=
- ```
- 5 : number
- 10.4 : Float
- "Hello World" : String
- [1,2,3,4,5] : List number
- ```
- Different operators work on different types. Adding a number and a string doesn't make sense. So if you try, Elm will complain (and give a helpful hint):
+are all values
=
- ```
- > "Hello " + 5
- -- TYPE MISMATCH ----------------------------------------------------------- elm
+<!-- slide -->
=
- I cannot do addition with String values like this one:
+They are self-referential. They simply state what they are. There are other kinds of values that we will see later.
=
- 5| "Hello " + 5
- ^^^^^^^^
- The (+) operator only works with Int and Float values.
+<!-- slide -->
=
- Hint: Switch to the (++) operator to append strings!
- ```
+You can give (or assign) a name to a value, as shown.
=
- (Int and Float are both types representing numbers)
+```
+family = 5
+price = 10.4
+morningGreeting = "Hello World"
+fingers = [1, 2, 3, 4, 5]
+```
=
+Here, `family` is a name and `5` is the value assigned to `family`.
=
- - Functions
+<!-- slide -->
=
- ```elm
- fun something = something ++ " is fun."
- ```
+You can get the value back by calling its name.
=
- Explain: a function is a thing that takes some values (called arguments) and return one value. So it's similar to operators. In fact operators are functions!
+```
+---- Elm 0.19.0 ----------------------------------------------------------------
+Read <https://elm-lang.org/0.19.0/repl> to learn more: exit, help, imports, etc.
+--------------------------------------------------------------------------------
+> family = 5
+5 : number
+> family
+5 : number
+```
=
- ```elm
- (-) 5 10
- 5 - 10
- ```
+<!-- slide -->
=
- You can think of a function as a machine. You put something in the machine, and it produces something in return. For example think about a machine that produces rubber ducks. You put a bucket of white plastic pellets and a bucket of red paint, and you get a bunch of red rubber ducks!
+Operators (`+`, `-`, `++`, `::`)
=
- What you get will depends on what you put. The color of the ducks depends on the paint you put. Quantity of ducks depends on how much plastic you put in.
+```
+> 2 + 5
+7 : number
+```
=
- ```elm
- makeMeSomeDucks color plastic =
- String.fromInt plastic ++ " " ++ color ++ " rubber ducks"
- ```
+```
+> 4 - 6
+-2 : number
+```
=
- Once you have a function, you can call it like this:
+```
+> "Hello" ++ " world!"
+"Hello world!" : String
+```
=
- ```elm
- > makeMeSomeDucks "blue" 12
- "12 blue rubber ducks" : String
- ```
+```
+> 1 :: [2, 3, 4, 5]
+[1,2,3,4,5] : List number
+```
=
- Note: functions help organize code into nice reusable chunks.
+<!-- slide -->
=
- How do you get functions? There are three ways.
+Operators take two values and return a new value. We know that names refer to values, so we can use them in place of values:
=
- 1. Some functions are always there for you, e.g. `(+)`, `(-)`
+```
+> family = 5
+5 : number
+> family + 2
+7 : number
+```
=
- 2. Some functions you can import using code like this:
+<!-- slide -->
=
- ```elm
- import Svg
- ```
+You can also give a name to the value returned by an operator:
=
- and then
+```
+> family = 5
+5 : number
+> familyAndPets = family + 2
+7 : number
+> familyAndPets
+7 : number
+```
=
- ```
- Svg.circle [ cx "10", cy "10", r "20" ] [ ]
- ```
+<!-- slide -->
=
- To call a function that was imported, you have to prefix it with the name of the module (in this example `Svg`).
+Note that different values have different types.
=
- 3. Finally, you will write some functions, just like we saw in the `fun` and `makeMeSomeDucks` examples.
+```
+5 : number
+10.4 : Float
+"Hello World" : String
+[1,2,3,4,5] : List number
+```
=
+<!-- slide -->
=
- Finally there is one special thing: the first line of the program is a module declaration. For now it's enough for us to know, that it has to be there and it has to match the name of the file.
+Different operators work on different types. Adding a number and a string doesn't make sense. So if you try, Elm will complain (and give a helpful hint):
=
- - Exercise: In our Main.elm, try to identify some values, names and function calls:
+```
+> "Hello " + 5
+-- TYPE MISMATCH ----------------------------------------------------------- elm
=
- Hint: `"darkred"` is a value, `main` is a name, `width` is a function.
+I cannot do addition with String values like this one:
=
- Hint: There is nothing else there now, but we will soon introduce our own functions.
+5| "Hello " + 5
+ ^^^^^^^^
+The (+) operator only works with Int and Float values.
=
- - Where can we use some functions?
+Hint: Switch to the (++) operator to append strings!
+```
=
- As we said before, functions are good when we have some repetitive operation that can be parametrized (like rubber ducks production).
+(Int and Float are both types representing numbers)
=
- Obviously calculating `x` and `y` coordinates is repetitive and can be parametrized (parameters are `radius` and `angle`).
+<!-- slide -->
=
- - Apply to our trigonometry calculations
+Functions
=
- - Compose and copy and paste the trigonometry functions to calculate cx and cy:
+```elm
+fun something = something ++ " is fun."
+```
=
- ```elm
- cx (String.fromFloat (cos (degrees 72) * 100))
- cy (String.fromFloat (sin (degrees 72) * 100))
- ```
+<!-- slide -->
=
- - Eliminate repetition:
+Explain: a function is a thing that takes some values (called arguments) and return one value. So it's similar to operators. In fact operators are functions!
=
- - define and reuse radius (100)
+```elm
+(-) 5 10
+5 - 10
+```
=
- Assign a value of `100` to a name `radius`:
+<!-- slide -->
=
- ```
- radius = 100
- ```
+You can think of a function as a machine. You put something in the machine, and it produces something in return. For example think about a machine that produces rubber ducks. You put a bucket of white plastic pellets and a bucket of red paint, and you get a bunch of red rubber ducks!
=
- and plug it to our functions:
+What you get will depends on what you put. The color of the ducks depends on the paint you put. Quantity of ducks depends on how much plastic you put in.
=
- ```elm
- cx (String.fromFloat (cos (degrees 72) * radius))
- cy (String.fromFloat (sin (degrees 72) * radius))
- ```
+<!-- slide -->
=
- As you see the names are good for repetitive things too.
+```elm
+makeMeSomeDucks color plastic =
+ String.fromInt plastic ++ " " ++ color ++ " rubber ducks"
+```
=
- - define and reuse `x : angle -> cx` and `y : angle -> cy`
+Once you have a function, you can call it like this:
=
+```elm
+> makeMeSomeDucks "blue" 12
+"12 blue rubber ducks" : String
+```
=
- ```elm
- x angle =
- String.fromFloat (cos (degrees angle) * radius)
- ```
+<!-- slide -->
=
- ```elm
- y angle =
- String.fromFloat (sin (degrees angle) * radius)
- ```
+Note: functions help organize code into nice reusable chunks.
=
- Then plug it into the code making circles:
+<!-- slide -->
=
- ```elm
- circle
- [ cx (x 72)
- , cy (y 72)
- , r "10"
- , fill "darkred"
- ]
- ```
+How do you get functions? There are three ways.
=
- - define and reuse `dot : angle -> color -> Svg`
+<!-- slide -->
=
+Some functions are always there for you
=
- ```elm
- dot angle color =
- circle
- [ cx (x angle)
- , cy (y angle)
- , r "10"
- , fill color
- ]
- ```
+`(+)`, `(-)`
=
- and plug it into our SVG picture:
+<!-- slide -->
=
+2. Some functions you can import using code like this:
=
- ```
- svg [ viewbox "-100 -100 200 200" ]
- [ dot 0 "darkred"
- , dot 72 "fuchsia"
- , dot 144 "saddlebrown"
- , dot 216 "deepskyblue"
- , dot 288 "gold"
- ]
- ```
+```elm
+import Svg
+```
=
- Want some more cool color inspiration. Check out https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#Color_keywords
+and then
=
- - Make it more general
+```
+Svg.circle [ cx "10", cy "10", r "20" ] [ ]
+```
=
- - Show List and list operations (map and indexedMap)
+To call a function that was imported, you have to prefix it with the name of the module (in this example `Svg`).
=
- There are a number of functions that operate on lists, for example `List.length` and `List.map`.
+<!-- slide -->
=
- Examples of a map: a shopping list, after you find each item and place it in your basket, you are 'mapping' from a list of needed items to a list of items in your basket. Now you have two lists, and they are related. One is your original shopping list, the other the list of items in the basket.
+Finally, you will write some functions, just like we saw in the `fun` and `makeMeSomeDucks` examples.
=
- Demonstrate:
+<!-- slide -->
=
- ```elm
- > things = ["Programming", "Swimming", "Dancing", "Saddle brown"]
- ["Programming","Swimming","Dancing","Saddle brown"]
- : List String
+Finally there is one special thing: the first line of the program is a module declaration. For now it's enough for us to know, that it has to be there and it has to match the name of the file.
=
+<!-- slide -->
=
- > List.length things
- 4 : Int
+Exercise: In our Main.elm, try to identify some values, names and function calls:
=
- > List.map fun things
- ["Programming is fun!","Swimming is fun!","Dancing is fun!","Saddle brown is fun!"]
- : List String
+Hint: `"darkred"` is a value, `main` is a name, `width` is a function.
=
- > things
- ["Programming","Swimming","Dancing","Saddle brown"]
- : List String
- ```
+Hint: There is nothing else there now, but we will soon introduce our own functions.
=
- Notice our original `things` list is unchanged. This is different from our rubber duck machine. The rubber duck turns the plastic and paint into rubber ducks. A function on the other hand 'creates' the value it gives you. You don't loose the original value given to it.
+<!-- slide -->
=
- - Make a `palette : List color`
+#### Where can we use some functions?
=
- ```elm
- palette =
- [ "darkred"
- , "fuchsia"
- , "saddlebrown"
- , "deepskyblue"
- , "gold"
- ]
- ```
+As we said before, functions are good when we have some repetitive operation that can be parametrized (like rubber ducks production).
=
- - Use `List.indexedMap dot palette` to generate the dots
+Obviously calculating `x` and `y` coordinates is repetitive and can be parametrized (parameters are `radius` and `angle`).
=
- Another function that operates on lists is `List.indexedMap`. Let's see it at work:
+<!-- slide -->
=
- ```elm
- dot index color =
- circle
- [ cx (x ((360 / (List.length palette)) * index))
- , cy (y ((360 / (List.length palette)) * index))
- , r "10"
- , fill color
- ]
+#### Apply to our trigonometry calculations
=
- List.indexedMap dot palette
- ```
-
- We can introduce a `let` block to make our code more readable and avoid repetition.
-
- ```elm
- dot index color =
- let
- angle =
- (360 / count) * index
-
- count =
- List.length palette
- in
- circle
- [ cx (x angle)
- , cy (y angle)
- , r "10"
- , fill color
- ]
- ```
+Compose and copy and paste the trigonometry functions to calculate cx and cy:
+
+```elm
+cx (String.fromFloat (cos (degrees 72) * 100))
+cy (String.fromFloat (sin (degrees 72) * 100))
+```
+
+<!-- slide -->
+
+#### Eliminate the repetition:
+
+Assign a value of `100` to a name `radius`:
+
+```
+radius = 100
+```
+
+and plug it to our functions:
+
+```elm
+cx (String.fromFloat (cos (degrees 72) * radius))
+cy (String.fromFloat (sin (degrees 72) * radius))
+```
+
+<!-- slide -->
+
+As you see the names are good for repetitive things too.
+
+Define and reuse
+
+- `x : angle -> cx`
+
+ ```elm
+ x angle =
+ String.fromFloat (cos (degrees angle) * radius)
+ ```
+
+- `y : angle -> cy`
+
+ ```elm
+ y angle =
+ String.fromFloat (sin (degrees angle) * radius)
+ ```
+
+<!-- slide -->
+
+Then plug it into the code making circles:
+
+```elm
+circle
+ [ cx (x 72)
+ , cy (y 72)
+ , r "10"
+ , fill "darkred"
+ ]
+```
+
+<!-- slide -->
+
+define and reuse `dot : angle -> color -> Svg`
+
+```elm
+dot angle color =
+ circle
+ [ cx (x angle)
+ , cy (y angle)
+ , r "10"
+ , fill color
+ ]
+```
+
+
+<!-- slide -->
+
+and plug it into our SVG picture:
+
+```
+svg [ viewbox "-100 -100 200 200" ]
+ [ dot 0 "darkred"
+ , dot 72 "fuchsia"
+ , dot 144 "saddlebrown"
+ , dot 216 "deepskyblue"
+ , dot 288 "gold"
+ ]
+```
+
+For more cool colors check out [Color keywords page at Mozilla Developer's Network](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#Color_keywords)
+
+<!-- slide -->
+
+- Make it more general
+
+ - Show List and list operations (map and indexedMap)
+
+ There are a number of functions that operate on lists, for example `List.length` and `List.map`.
+
+<!-- slide -->
+
+Examples of a map: a shopping list, after you find each item and place it in your basket, you are 'mapping' from a list of needed items to a list of items in your basket. Now you have two lists, and they are related. One is your original shopping list, the other the list of items in the basket.
+
+<!-- slide -->
+
+Demonstrate:
+
+```elm
+> things = ["Programming", "Swimming", "Dancing", "Saddle brown"]
+["Programming","Swimming","Dancing","Saddle brown"]
+ : List String
+
+
+> List.length things
+4 : Int
=
- Take a look at the [documentation for `List.indexedMap`](https://package.elm-lang.org/packages/elm/core/latest/List#indexedMap).
+> List.map fun things
+["Programming is fun!","Swimming is fun!","Dancing is fun!","Saddle brown is fun!"]
+ : List String
=
+> things
+["Programming","Swimming","Dancing","Saddle brown"]
+ : List String
+```
+
+<!-- slide -->
+
+Notice our original `things` list is unchanged. This is different from our rubber duck machine. The rubber duck turns the plastic and paint into rubber ducks. A function on the other hand 'creates' the value it gives you. You don't loose the original value given to it.
+
+<!-- slide -->
=
-Day 4
+Make a `palette : List color`
+
+```elm
+palette =
+ [ "darkred"
+ , "fuchsia"
+ , "saddlebrown"
+ , "deepskyblue"
+ , "gold"
+ ]
+```
+
+<!-- slide -->
+
+Use `List.indexedMap dot palette` to generate the dots
+
+<!-- slide -->
+
+Another function that operates on lists is `List.indexedMap`. Let's see it at work:
+
+```elm
+dot index color =
+ circle
+ [ cx (x ((360 / (List.length palette)) * index))
+ , cy (y ((360 / (List.length palette)) * index))
+ , r "10"
+ , fill color
+ ]
+
+List.indexedMap dot palette
+```
+
+<!-- slide -->
+
+We can introduce a `let` block to make our code more readable and avoid repetition.
+
+```elm
+dot index color =
+ let
+ angle =
+ (360 / count) * index
+
+ count =
+ List.length palette
+ in
+ circle
+ [ cx (x angle)
+ , cy (y angle)
+ , r "10"
+ , fill color
+ ]
+```
+
+<!-- slide -->
+
+Take a look at the [documentation for `List.indexedMap`](https://package.elm-lang.org/packages/elm/core/latest/List#indexedMap).
+
+<!-- slide -->
+
+## Day 4
+
+<!-- slide -->
=
=Introduce SVG groups
=
- - We'll need one group for the dots and one group for the lines
+We'll need one group for the dots and one group for the lines
=
- ```elm
- svg [ viewBox "-100 -100 200 200" ]
- [ Svg.g [] (List.indexedMap dot pallete)
- , Svg.g [] [ Svg.line [ x1 "123", y1 "112", x2 "41", y2 "11", stroke "black" ] [] ]
+```elm
+svg [ viewBox "-100 -100 200 200" ]
+ [ Svg.g [] (List.indexedMap dot pallete)
+ , Svg.g [] [ Svg.line [ x1 "123", y1 "112", x2 "41", y2 "11", stroke "black" ] [] ]
+ ]
+```
+
+<!-- slide -->
+
+Exercise: add a few lines with different colors
+
+Now we'll learn how to color a line with a gradient (going from one color to another)
+
+<!-- slide -->
+
+```
+svg [ width "600", height "600", viewBox "-300 -300 600 600" ]
+ [ Svg.g [] (List.indexedMap dot pallete)
+ , Svg.defs []
+ [ Svg.linearGradient [ Svg.Attributes.id "MyGradient", x1 "0", y1 "0", x2 "1", y2 "0" ]
+ [ Svg.stop [ Svg.Attributes.offset "0", Svg.Attributes.stopColor "saddlebrown" ] []
+ , Svg.stop [ Svg.Attributes.offset "1", Svg.Attributes.stopColor "pink" ] []
+ ]
+ ]
+ , Svg.g []
+ [ Svg.line [ x1 "0", y1 "0", x2 "100", y2 "100", stroke "url(#MyGradient)" ] []
+ , Svg.line [ x1 "0", y1 "0", x2 "-100", y2 "-100", stroke "url(#MyGradient)" ] []
= ]
- ```
+ ]
+```
+
+<!-- slide -->
=
- Exercise: add a few lines with different colors
+We see a problem here. The gradient always goes from saddlebrown to pink from top left to bottom right. If we want to use the same gradient to connect different points, we'll need to be creative.
=
- Now we'll learn how to color a line with a gradient (going from one color to another)
+<!-- slide -->
=
- ```
+```
+main =
= svg [ width "600", height "600", viewBox "-300 -300 600 600" ]
= [ Svg.g [] (List.indexedMap dot pallete)
= , Svg.defs []
- [ Svg.linearGradient [ Svg.Attributes.id "MyGradient", x1 "0", y1 "0", x2 "1", y2 "0" ]
+ [ Svg.linearGradient [ Svg.Attributes.id "MyGradient", x1 "0", y1 "0", x2 "1", y2 "0", gradientUnits "userSpaceOnUse" ]
= [ Svg.stop [ Svg.Attributes.offset "0", Svg.Attributes.stopColor "saddlebrown" ] []
= , Svg.stop [ Svg.Attributes.offset "1", Svg.Attributes.stopColor "pink" ] []
= ]
= ]
= , Svg.g []
- [ Svg.line [ x1 "0", y1 "0", x2 "100", y2 "100", stroke "url(#MyGradient)" ] []
- , Svg.line [ x1 "0", y1 "0", x2 "-100", y2 "-100", stroke "url(#MyGradient)" ] []
+ [ Svg.line [ x1 "0", y1 "0", x2 "1", y2 "0", transform "rotate(45),scale(100,1)", stroke "url(#MyGradient)" ] []
+ , Svg.line [ x1 "0", y1 "0", x2 "1", y2 "0", transform "rotate(180),scale(100,1)", stroke "url(#MyGradient)" ] []
+ , Svg.line [ x1 "0", y1 "0", x2 "1", y2 "0", transform "rotate(270),scale(100,1)", stroke "url(#MyGradient)" ] []
= ]
= ]
- ```
-
- We see a problem here. The gradient always goes from saddlebrown to pink from top left to bottom right. If we want to use the same gradient to connect different points, we'll need to be creative.
-
- ```
- main =
- svg [ width "600", height "600", viewBox "-300 -300 600 600" ]
- [ Svg.g [] (List.indexedMap dot pallete)
- , Svg.defs []
- [ Svg.linearGradient [ Svg.Attributes.id "MyGradient", x1 "0", y1 "0", x2 "1", y2 "0", gradientUnits "userSpaceOnUse" ]
- [ Svg.stop [ Svg.Attributes.offset "0", Svg.Attributes.stopColor "saddlebrown" ] []
- , Svg.stop [ Svg.Attributes.offset "1", Svg.Attributes.stopColor "pink" ] []
- ]
- ]
- , Svg.g []
- [ Svg.line [ x1 "0", y1 "0", x2 "1", y2 "0", transform "rotate(45),scale(100,1)", stroke "url(#MyGradient)" ] []
- , Svg.line [ x1 "0", y1 "0", x2 "1", y2 "0", transform "rotate(180),scale(100,1)", stroke "url(#MyGradient)" ] []
- , Svg.line [ x1 "0", y1 "0", x2 "1", y2 "0", transform "rotate(270),scale(100,1)", stroke "url(#MyGradient)" ] []
- ]
- ]
- ```
+```
+
+TODO: We need to fix the viewbox!
=
- We need to fix the viewbox!
+<!-- slide -->
=
- Here's the trick: we have the lines initially match the gradient. We're using the same x and y values for all the lines and the gradient. Then we transform the lines to position them where we'd like them. We're actually using our old friend polar coordinates here.
=
- If you're interested in what `gradientUnits "userSpaceOnUse"` does, come see me after the lesson.
+Here's the trick: we have the lines initially match the gradient. We're using the same x and y values for all the lines and the gradient. Then we transform the lines to position them where we'd like them. We're actually using our old friend polar coordinates here.
=
- ```elm
- gradient : Int -> String -> Svg msg
- gradient index color =
- Svg.linearGradient [ Svg.Attributes.id ("Gradient-" ++ String.fromInt index), x1 "0", y1 "0", x2 "1", y2 "0", gradientUnits "userSpaceOnUse" ]
- [ Svg.stop [ Svg.Attributes.offset "0", Svg.Attributes.stopColor "saddlebrown" ] []
- , Svg.stop [ Svg.Attributes.offset "1", Svg.Attributes.stopColor color ] []
+If you're interested in what `gradientUnits "userSpaceOnUse"` does, come see me after the lesson.
+
+<!-- slide -->
+
+```elm
+gradient : Int -> String -> Svg msg
+gradient index color =
+ Svg.linearGradient
+ [ id ("Gradient-" ++ String.fromInt index)
+ , x1 "0"
+ , y1 "0"
+ , x2 "1"
+ , y2 "0"
+ , gradientUnits "userSpaceOnUse"
+ ]
+ [ Svg.stop
+ [ Svg.Attributes.offset "0"
+ , Svg.Attributes.stopColor "saddlebrown"
+ ]
+ []
+ , Svg.stop
+ [ Svg.Attributes.offset "1"
+ , Svg.Attributes.stopColor color
= ]
+ []
+ ]
+```
+
+<!-- slide -->
+
+```
+line : Int -> String -> Svg msg
+line index color =
+ let
+ count =
+ List.length pallete
=
+ angle =
+ (360 / toFloat count) * toFloat index
=
- line : Int -> String -> Svg msg
- line index color =
- let
- angle =
- (360 / toFloat (List.length pallete)) * toFloat index
+ transformation =
+ "rotate("
+ ++ String.fromFloat angle
+ ++ "), scale("
+ ++ String.fromInt radius
+ ++ ", 1)"
=
- transformation =
- "rotate(" ++ String.fromFloat angle ++ "),scale(" ++ String.fromInt radius ++ ",1)"
+ url =
+ "url(#Gradient-" ++ String.fromInt index ++ ")"
+ in
+ Svg.line
+ [ x1 "0"
+ , y1 "0"
+ , x2 "1"
+ , y2 "0"
+ , transform transformation
+ , stroke url
+ ]
+ []
+```
=
- url =
- "url(#Gradient-" ++ String.fromInt index ++ ")"
- in
- Svg.line [ x1 "0", y1 "0", x2 "1", y2 "0", transform transformation, stroke url ] []
+<!-- slide -->
=
+```
+main =
+ svg [ width "600", height "600", viewBox "-300 -300 600 600" ]
+ [ Svg.g [] (List.indexedMap dot pallete)
+ , Svg.defs [] (List.indexedMap gradient pallete)
+ , Svg.g [] (List.indexedMap line pallete)
+ ]
+```
=
- main =
- svg [ width "600", height "600", viewBox "-300 -300 600 600" ]
- [ Svg.g [] (List.indexedMap dot pallete)
- , Svg.defs [] (List.indexedMap gradient pallete)
- , Svg.g [] (List.indexedMap line pallete)
- ]
- ```
+<!-- slide -->
=
= This may look like a lot, but our `gradient` and `line` functions are very similar to our `dot` function from earlier. In fact we see an essential principle of good programming design here. We spent a lot of time banging our heads over gradients. Now that we've done that work, we can hide the implementation in our gradient and line functions. Now, if we want to draw a new dot, line, and gradient going from the center to the dot, we only need to add a new item to our palette. Our functions do the rest for us. Our functions conceal our complexity. We can forget about the implementation, so long as we know how to operate them. They're like black boxes, we know what goes in and what comes out, but we don't have to know how they work on the inside.
=
+<!-- slide -->
=
-Day 5
+## Day 5
+
+Let's make a beautiful tree!
+
+<!-- slide -->
=
=Our project is looking pretty good at this point. We've created some interesting graphical elements, and learned some interesting ways to place them programatically. However, it doesn't take user input like many of the programs we use. We'll be doing some pretty interesting things with user input later in this workshop, but for now we'll start with a deceptively simple step: setting the background color.
=
=I say this step is deceptively simple, because we'll need to make some big changes to our program and introduce a number of new concepts.
=
+<!-- slide -->
+
=Show complete code.
=
+<!-- slide -->
+
=There's a lot going on here, and it's not obvious how it all works.
=
=Our `main` value has changed.
@@ -574,8 +768,15 @@ main =
= }
=```
=
-Previously, the value of `main` was an SVG element. Now it contains a call to `Browser.sandbox`. A sandbox is a basic interactive program. For a program to be interactive, it need more than just a SVG element. It also needs a `state`. The state of an application is basically the information used to determine how the application should be rendered. Think about the Atom program we are all running at the moment. We know we are all running the same program, however we are not all looking at exactly the same thing. The text in our neighbors editor might be slightly different than the text in our own. If they opened another file, a letter to a friend for example, the text would be very different. The text input is part of the state of the Atom application. Maybe your neighbor has scrolled to another point in the screen. The scroll point is also part of the state of the Atom application. The cursor might be on another line in their viewport. The cursor position is also part of the state of the Atom application. Atom uses all of this state information to render exactly what you are seeing on your computer screen. Anything that can change will be represented in a state variable. If you input or delete text, move the cursor, scroll up or down, or change any of the application settings, you are updating
-the state of the Atom application.
+<!-- slide -->
+
+Previously, the value of `main` was an SVG element. Now it contains a call to `Browser.sandbox` function.
+
+<!-- slide -->
+
+A sandbox is a basic interactive program. For a program to be interactive, it need more than just a SVG element.
+
+<!-- slide -->
=
=```
=type alias State =
@@ -588,8 +789,12 @@ type alias Color =
=
=Here we create something called a type alias. We've already discussed types. A type alias is basically a way to give an alternative name for a type. We give the alias `Color` to `String` and the alias `State` to `Color`. So in the end all three names point to the same type!
=
+<!-- slide -->
+
=Why are we doing this? All of these types are strings, and Elm will treat them all as strings. But it will make our code more readable to use these aliases. We are writing an application that allows the user to change the background color. That should explain why our `State` is a `Color`.
=
+<!-- slide -->
+
=Our `Browser.sandbox` takes something with the name `init`. This is our initial state. Let's take a look at the value of `init`.
=
=```
@@ -598,8 +803,12 @@ init =
= "white"
=```
=
+<!-- slide -->
+
=We see that the initial state is `"white"`. We'll see how our application uses this value in a moment.
=
+<!-- slide -->
+
=Looking back at our `sandbox`, we see it also takes a `view`. Let's take a look at our view function:
=
=```
@@ -617,14 +826,24 @@ view color =
= ]
=```
=
+<!-- slide -->
+
=You'll notice that this `view` function looks very similar to the `main` variable from earlier. Before, our application was only a view. Now the view is only one piece of our interactive application.
=
+<!-- slide -->
+
=One important way our `view` function differs from the `main` variable from ealier is that `view` is a function. We see it takes a variable of type `State` (a string), which we have named `color`. In an Elm sandbox project the `view` always takes a variable with the same type as the `init` variable. This is the current state of the application. `view` can use the state to render the application properly, just as Atom uses its state to render text for its user.
=
+<!-- slide -->
+
=We see that we've added a line to the svg attributes, `Svg.Attributes.style ("background: " ++ color)`. Here we use the state of the application to set the background color of the svg element.
=
+<!-- slide -->
+
=All we need now is some way to update the state (the background color) of the application.
=
+<!-- slide -->
+
=Taking a look at our `main` function, we see that it take a third and final function, `update`. Let's look at our update function:
=
=```
@@ -640,58 +859,147 @@ update msg state =
=
=```
=
+<!-- slide -->
+
=We see our `update` function takes two variables, of type `Msg` and `State`. If we think of update as a machine which takes an old state and spits out a new state, this state variable corresponds to the old state.
=
+<!-- slide -->
+
=The `msg` variable is something new. Looking above, we see that we have defined a `Msg` variable type. This is similar to our `State` and `Color` union types, in that we have defined a new type that Elm understands. However, unlike the `type alias` constructor, the `type` constructor does not simply give a new name for an existing type. Instead, here we define a completely unique type. We see values with type Msg must have the form `SetBackground Color`. `SetBackground "white"`, `SetBackground "blue"`, `SetBackground "saddlebrown"` are all valid values of the type `Msg`.
=
+<!-- slide -->
+
=What is important to understand is that our `msg` variable is used by `update` to determine how it should update the state. We see that update first checks that `msg` has the form `SetBackground color`, and then returns the value of `color`. This will become our new state. Every time update is called, the sandbox will re-render our view with the new state.
=
+<!-- slide -->
+
=So now we see how our application changes the background color of the svg element. However, we haven't seen when it will update the background color. Well, we want to change the background color by clicking a dot. So let's take a look at our `dot` function to see if we can find a hint there.
=
-- `Model`:
+<!-- slide -->
=
- the structure of the state of the program
+<dl>
+<dt>Model
+<dd>the structure of the state of the program
+</dl>
=
- TODO: Consistently use `Model` and `model` instead of less standard `State` to make it easier to our participants.
+> TODO: Consistently use `Model` and `model` instead of less standard `State` to make it easier to our participants.
=
-- `Msg`
+<!-- slide -->
=
+<dl>
+<dt>Msg
+<dd>
= What events can there be in the program. Currently have only one possible event - setting the background to a given color.
+</dl>
+
+<!-- slide -->
=
=- `update`
=
= the instructions of how to change the state when a particular message comes in.
=
+<!-- slide -->
+
=- `view`
=
= the instruction of what to display on the screen. It also contains instructions of what messages to send when certain events (like clicks on elements) happen. Every time the state changes the instructions will be applied.
=
+<!-- slide -->
+
=- `init`
=
= What is the initial state right after the program is started.
=
+<!-- slide -->
+
=- `main`
=
= glues it all together.
=
-Next steps:
+<!-- slide -->
=
=- How do we model the tree
=
- - introduces the model for later interactive program
+<!-- slide -->
+
+A tree is a composition of segments. A segment can be either a twig or a branch. Twig is a terminal segment of a tree. When the tree is very young it consists of a single twig. Later this twig will evolve into a branch and will grow it's own twigs, that in turn will evolve into branches. So a branch is a segment of a tree that ends with some other segments (twigs or branches).
+
+<!-- slide -->
+
+Note that a branch can split to several branches that each in turn can split into several branches and so on. This way a tree can be as complex as we want. Note that real trees are like that. It's difficult to say how many generations of branches it can have from the trunk to the smallest little twigs on top of the crown. This kind of structure is called recursive. It repeats the same pattern.
+
+<!-- slide -->
=
- - naturally introduces union types
+Each segment (a twig or a branch) has length, a direction (represented as an angle) and a color (so that it's beautiful despite having no leafs or flowers).
=
+Here is how we can represent it in code:
=
-- Make a tree grow
+```
+type alias Segment =
+ { length : Float
+ , direction : Float
+ , color : Color
+ }
+```
=
- - Make a function that given the age of the tree and rules returns a tree
+<!-- slide -->
=
- - Use time subscription to make the tree grow
+If you consider that a branch can split into more branches or twigs, and then that a trunk of a tree is just a first branch or a twig if the tree is very young, then you can see that each segment separated from the parent branch can be considered a tree.
=
=<!-- slide -->
=
-``
+So we can reuse the same structure to represent a tree or any of it's segments!
+
+Look:
+
+```
+type Tree
+ = Twig Segment
+ | Branch Segment (List Tree)
+```
+
+<!-- slide -->
+
+Think about it! A tree is a twig (when it's young) or a branch that splits into __a zero, one or many__ __twigs or branches__.
+
+ - zero, one or many: a list
+ - twig or branch: a tree
+
+So a Twig is just a Segment while a branch is a Segment with a list of Trees!
+
+<!-- slide -->
+
+Let's encode a simple tree by hand:
+
+```elm
+tree =
+ Branch { length = 10, direction = degrees -90, color = "brown" }
+ [ Branch { length = 6, direction = degrees -45, color = "lightbrown" }
+ [ Twig { length = 2, direction = degrees -30, color = "green" }
+ , Twig { length = 2, direction = degrees -60, color = "green" }
+ ]
+ , Branch { length = 6, direction = degrees -135, color = "lightbrown" }
+ [ Twig { length = 2, direction = degrees -120, color = "green" }
+ , Twig { length = 2, direction = degrees -150, color = "green" }
+ ]
+ ]
+```
+
+<!-- slide -->
+
+## Day 6
+
+Make the tree grow!
+
+<!-- slide -->
+
+Make a function that given the age of the tree and rules returns a tree
+
+<!-- slide -->
+
+Use time subscription to make the tree grow
+
+<!-- slide -->
=
=```
=dot : Int -> String -> Svg Msg
@@ -712,12 +1020,18 @@ dot index color =
=
=We see there is a new line here:
=
+<!-- slide -->
+
=```
=Svg.Events.onClick (SetBackground color)
=```
=
+<!-- slide -->
+
=`onClick` is an event. It basically tells our svg element to listen for someone to click on it. It will then handle the event by emitting the Msg `SetBackground color`. We know `color` is the color of the dot. We see then that when someone clicks on the dot, the Msg will be emitted, `update` will be called with this Msg and will update our state. Our sandbox will rerender the view with this new state. The user will see the svg element with a new background color.
=
+<!-- slide -->
+
=Notice that our `view` function, as well as all the functions that call functions responsible for rendering svg elements, including `gradient`, `line`, and `dot` have return values with type `Svg Msg` or `Html Msg`. This is because svg element can emit Msgs which will be handled by `update`.
=
=Complete the Tree program using only SVG transformations
No trigonometry anymore!
index 5e5f50b..2ec5008 100644
--- a/src/Tree.elm
+++ b/src/Tree.elm
@@ -5,6 +5,7 @@ import Browser.Events
=import Dict exposing (Dict)
=import Html exposing (Html)
=import Json.Decode exposing (Decoder)
+import List.Extra as List
=import Svg exposing (..)
=import Svg.Attributes exposing (..)
=
@@ -50,18 +51,15 @@ type alias Rules =
=
=init : Flags -> ( Model, Cmd Msg )
=init () =
- ( { time = 0
+ ( { time = 20000
= , rules =
= Dict.fromList
= [ ( "red"
- , [ Segment "green" 15 0.2
- , Segment "green" 175 0.1
- ]
+ , [ Segment "brown" -90 6 ]
= )
- , ( "green", [ Segment "blue" 15 0.3 ] )
- , ( "blue"
- , [ Segment "red" 15 0.7
- , Segment "purple" 45 0.1
+ , ( "brown"
+ , [ Segment "brown" 45 3
+ , Segment "brown" -45 3
= ]
= )
= ]
@@ -72,17 +70,58 @@ init () =
=
=view : Model -> Html Msg
=view model =
+ let
+ gradients : List (Svg Msg)
+ gradients =
+ model.rules
+ |> Dict.map
+ (\parentColor children ->
+ children
+ |> List.map (\child -> ( parentColor, child.color ))
+ )
+ |> Dict.values
+ |> List.concat
+ |> List.unique
+ |> List.map
+ (\(( start, end ) as colors) ->
+ linearGradient
+ [ id (gradientId colors)
+ , x1 "0"
+ , y1 "0"
+ , x2 "1"
+ , y2 "0"
+ , gradientUnits "userSpaceOnUse"
+ ]
+ [ stop
+ [ stopColor start, offset "0.2" ]
+ []
+ , stop
+ [ stopColor end, offset "0.8" ]
+ []
+ ]
+ )
+
+ tree =
+ "red"
+ |> svgTree model.rules (model.time / 5000) (model.time / 5000)
+ |> g
+ [ transform
+ ("scale("
+ ++ String.fromFloat (model.time / 1000)
+ ++ ")"
+ )
+ ]
+ in
= Html.div []
= [ Html.h1 [] [ Html.text <| String.fromFloat model.time ]
- , "red"
- |> svgTree model.rules (model.time / 5000) (model.time / 5000)
- |> g [ transform ("scale(" ++ String.fromFloat (model.time / 1000) ++ ")") ]
- |> List.singleton
- |> svg
- [ viewBox "-1000 -1000 2000 2000"
- , height "800px"
- , width "800px"
- ]
+ , svg
+ [ viewBox "-1000 -1000 2000 2000"
+ , height "800px"
+ , width "800px"
+ ]
+ [ defs [] gradients
+ , tree
+ ]
= ]
=
=
@@ -145,17 +184,41 @@ svgTree rules treeAge branchAge color =
= ++ String.fromFloat segment.angle
= ++ ")"
= ++ ", scale("
- ++ String.fromFloat ((branchAge - 1) / treeAge)
+ ++ String.fromFloat (branchAge / treeAge)
= ++ ")"
= ++ ", translate("
- ++ String.fromFloat (segment.length * branchAge)
+ ++ String.fromFloat segment.length
= ++ ", 0)"
= in
= g
= [ transform transformation ]
- (svgTree rules treeAge (branchAge - 1) segment.color)
+ (line
+ [ stroke ("url(#" ++ gradientId ( color, segment.color ) ++ ")")
+ , x1 <| String.fromFloat 0
+ , x2 <| String.fromFloat 1
+ , y1 <| String.fromFloat 0
+ , y2 <| String.fromFloat 0
+ , strokeWidth "1"
+ , transform
+ ("translate("
+ ++ String.fromFloat (0 - segment.length)
+ ++ ", 0)"
+ ++ ", "
+ ++ "scale("
+ ++ String.fromFloat segment.length
+ ++ ", 1)"
+ )
+ ]
+ []
+ :: svgTree rules treeAge (branchAge - 1) segment.color
+ )
= )
- |> (::) (circle [ cx "0", cy "0", fill color, r "1" ] [])
+ |> (::) (circle [ cx "0", cy "0", fill color, r "0.5" ] [])
=
= else
= []
+
+
+gradientId : ( Color, Color ) -> String
+gradientId ( start, end ) =
+ "connection-" ++ start ++ "-" ++ endDefine Transformation as union type and use applyTransformations function to create Svg.Attribute for transformations
index 2ec5008..711307f 100644
--- a/src/Tree.elm
+++ b/src/Tree.elm
@@ -51,7 +51,7 @@ type alias Rules =
=
=init : Flags -> ( Model, Cmd Msg )
=init () =
- ( { time = 20000
+ ( { time = 0
= , rules =
= Dict.fromList
= [ ( "red"
@@ -178,20 +178,13 @@ svgTree rules treeAge branchAge color =
= |> Maybe.withDefault []
= |> List.map
= (\segment ->
- let
- transformation =
- "rotate("
- ++ String.fromFloat segment.angle
- ++ ")"
- ++ ", scale("
- ++ String.fromFloat (branchAge / treeAge)
- ++ ")"
- ++ ", translate("
- ++ String.fromFloat segment.length
- ++ ", 0)"
- in
= g
- [ transform transformation ]
+ [ applyTransformations
+ [ Rotate segment.angle
+ , Scale (branchAge / treeAge) (branchAge / treeAge)
+ , Translate segment.length 0
+ ]
+ ]
= (line
= [ stroke ("url(#" ++ gradientId ( color, segment.color ) ++ ")")
= , x1 <| String.fromFloat 0
@@ -199,15 +192,10 @@ svgTree rules treeAge branchAge color =
= , y1 <| String.fromFloat 0
= , y2 <| String.fromFloat 0
= , strokeWidth "1"
- , transform
- ("translate("
- ++ String.fromFloat (0 - segment.length)
- ++ ", 0)"
- ++ ", "
- ++ "scale("
- ++ String.fromFloat segment.length
- ++ ", 1)"
- )
+ , applyTransformations
+ [ Translate (0 - segment.length) 0
+ , Scale segment.length 1
+ ]
= ]
= []
= :: svgTree rules treeAge (branchAge - 1) segment.color
@@ -219,6 +207,47 @@ svgTree rules treeAge branchAge color =
= []
=
=
+type Transformation
+ = Identity
+ | Scale Float Float
+ | Translate Float Float
+ | Rotate Float
+
+
+applyTransformations : List Transformation -> Svg.Attribute Msg
+applyTransformations transformations =
+ let
+ toString : Transformation -> String
+ toString transformation =
+ case transformation of
+ Identity ->
+ ""
+
+ Scale x y ->
+ "scale("
+ ++ String.fromFloat x
+ ++ ", "
+ ++ String.fromFloat y
+ ++ ")"
+
+ Translate x y ->
+ "translate("
+ ++ String.fromFloat x
+ ++ ", "
+ ++ String.fromFloat y
+ ++ ")"
+
+ Rotate angle ->
+ "rotate("
+ ++ String.fromFloat angle
+ ++ ")"
+ in
+ transformations
+ |> List.map toString
+ |> String.join " "
+ |> transform
+
+
=gradientId : ( Color, Color ) -> String
=gradientId ( start, end ) =
= "connection-" ++ start ++ "-" ++ endMake subtrees scale linearly
Code reformatting
index 711307f..9f15a49 100644
--- a/src/Tree.elm
+++ b/src/Tree.elm
@@ -103,7 +103,7 @@ view model =
=
= tree =
= "red"
- |> svgTree model.rules (model.time / 5000) (model.time / 5000)
+ |> svgTree model.rules (model.time / 5000)
= |> g
= [ transform
= ("scale("
@@ -170,41 +170,52 @@ subscriptions model =
= ]
=
=
-svgTree : Rules -> Float -> Float -> Color -> List (Svg Msg)
-svgTree rules treeAge branchAge color =
- if branchAge > 0 then
- rules
- |> Dict.get color
- |> Maybe.withDefault []
- |> List.map
- (\segment ->
- g
- [ applyTransformations
- [ Rotate segment.angle
- , Scale (branchAge / treeAge) (branchAge / treeAge)
- , Translate segment.length 0
- ]
- ]
- (line
- [ stroke ("url(#" ++ gradientId ( color, segment.color ) ++ ")")
- , x1 <| String.fromFloat 0
- , x2 <| String.fromFloat 1
- , y1 <| String.fromFloat 0
- , y2 <| String.fromFloat 0
- , strokeWidth "1"
- , applyTransformations
- [ Translate (0 - segment.length) 0
- , Scale segment.length 1
- ]
- ]
- []
- :: svgTree rules treeAge (branchAge - 1) segment.color
- )
- )
- |> (::) (circle [ cx "0", cy "0", fill color, r "0.5" ] [])
+svgTree : Rules -> Float -> Color -> List (Svg Msg)
+svgTree rules age color =
+ if age <= 0 then
+ []
=
= else
- []
+ let
+ subtrees =
+ rules
+ |> Dict.get color
+ |> Maybe.withDefault []
+ |> List.map subtree
+
+ subtree segment =
+ let
+ gradient =
+ "url(#" ++ gradientId ( color, segment.color ) ++ ")"
+
+ scale =
+ age / (age + 1)
+ in
+ g
+ [ applyTransformations
+ [ Rotate segment.angle
+ , Scale scale scale
+ , Translate segment.length 0
+ ]
+ ]
+ (line
+ [ stroke gradient
+ , x1 "0"
+ , x2 "1"
+ , y1 "0"
+ , y2 "0"
+ , strokeWidth "1"
+ , applyTransformations
+ [ Translate (0 - segment.length) 0
+ , Scale segment.length 1
+ ]
+ ]
+ []
+ :: svgTree rules (age - 1) segment.color
+ )
+ in
+ circle [ cx "0", cy "0", fill color, r "0.5" ] []
+ :: subtrees
=
=
=type TransformationRemove circles and use round linecap instead.
index 9f15a49..a7f36d8 100644
--- a/src/Tree.elm
+++ b/src/Tree.elm
@@ -51,15 +51,25 @@ type alias Rules =
=
=init : Flags -> ( Model, Cmd Msg )
=init () =
- ( { time = 0
+ ( { time = 50000
= , rules =
= Dict.fromList
- [ ( "red"
- , [ Segment "brown" -90 6 ]
+ [ ( "brown"
+ , [ Segment "green" -115 1
+ , Segment "green" -65 1
+ , Segment "saddlebrown" 90 2
+ ]
+ )
+ , ( "green"
+ , [ Segment "green" 20 3
+ , Segment "green" -20 3
+ , Segment "red" 90 1
+ , Segment "red" -90 1
+ ]
= )
- , ( "brown"
- , [ Segment "brown" 45 3
- , Segment "brown" -45 3
+ , ( "saddlebrown"
+ , [ Segment "saddlebrown" 20 3
+ , Segment "saddlebrown" -20 3
= ]
= )
= ]
@@ -93,16 +103,16 @@ view model =
= , gradientUnits "userSpaceOnUse"
= ]
= [ stop
- [ stopColor start, offset "0.2" ]
+ [ stopColor start, offset "0" ]
= []
= , stop
- [ stopColor end, offset "0.8" ]
+ [ stopColor end, offset "1" ]
= []
= ]
= )
=
= tree =
- "red"
+ "brown"
= |> svgTree model.rules (model.time / 5000)
= |> g
= [ transform
@@ -204,7 +214,8 @@ svgTree rules age color =
= , x2 "1"
= , y1 "0"
= , y2 "0"
- , strokeWidth "1"
+ , strokeWidth "0.2"
+ , strokeLinecap "round"
= , applyTransformations
= [ Translate (0 - segment.length) 0
= , Scale segment.length 1
@@ -214,8 +225,7 @@ svgTree rules age color =
= :: svgTree rules (age - 1) segment.color
= )
= in
- circle [ cx "0", cy "0", fill color, r "0.5" ] []
- :: subtrees
+ subtrees
=
=
=type Transformation
Commits: 3
Render svgTree in one pass directly from rules
In tree sample remove Tree type and merge grow and svgTree functions. Use SVG transformations for rotation, scaling and translation, offloading the difficult geometry transformation to the browser.
This way we don't have any union types except for Msg.
The downside is that the branches do not grow linearly. The size of the branch is:
treeAge! / (treeAge ^ (ceil treeAge))index 1939a19..5e5f50b 100644
--- a/src/Tree.elm
+++ b/src/Tree.elm
@@ -4,6 +4,7 @@ import Browser
=import Browser.Events
=import Dict exposing (Dict)
=import Html exposing (Html)
+import Json.Decode exposing (Decoder)
=import Svg exposing (..)
=import Svg.Attributes exposing (..)
=
@@ -29,11 +30,7 @@ type alias Model =
=
=type Msg
= = Progress Float
-
-
-type Tree
- = Tip Segment
- | Node Segment (List Tree)
+ | Regress Float
=
=
=type alias Color =
@@ -53,25 +50,21 @@ type alias Rules =
=
=init : Flags -> ( Model, Cmd Msg )
=init () =
- ( { time = 1
+ ( { time = 0
= , rules =
= Dict.fromList
= [ ( "red"
- , [ Segment "yellow" (degrees 15) 10
- , Segment "yellow" (degrees 175) 10
+ , [ Segment "green" 15 0.2
+ , Segment "green" 175 0.1
+ ]
+ )
+ , ( "green", [ Segment "blue" 15 0.3 ] )
+ , ( "blue"
+ , [ Segment "red" 15 0.7
+ , Segment "purple" 45 0.1
= ]
= )
- , ( "yellow", [ Segment "blue" (degrees 15) 10 ] )
- , ( "blue", [ Segment "red" (degrees 15) 10 ] )
= ]
-
- -- [ ( "red"
- -- , [ Segment "blue" (degrees 90) 10
- -- , Segment "yellow" (degrees -45) 4
- -- ]
- -- )
- -- , ( "yellow", [ Segment "red" (degrees 90) 6 ] )
- -- ]
= }
= , Cmd.none
= )
@@ -81,12 +74,15 @@ view : Model -> Html Msg
=view model =
= Html.div []
= [ Html.h1 [] [ Html.text <| String.fromFloat model.time ]
- , Segment "red" 0 0
- |> Tip
- |> grow model.rules (model.time / 1000)
- |> svgTree
+ , "red"
+ |> svgTree model.rules (model.time / 5000) (model.time / 5000)
+ |> g [ transform ("scale(" ++ String.fromFloat (model.time / 1000) ++ ")") ]
= |> List.singleton
- |> svg [ viewBox "-1000 -1000 2000 2000", height "800px", width "800px" ]
+ |> svg
+ [ viewBox "-1000 -1000 2000 2000"
+ , height "800px"
+ , width "800px"
+ ]
= ]
=
=
@@ -98,82 +94,68 @@ update msg model =
= , Cmd.none
= )
=
+ Regress delta ->
+ ( { model | time = model.time - delta }
+ , Cmd.none
+ )
+
=
=subscriptions : Model -> Sub Msg
=subscriptions model =
- Browser.Events.onAnimationFrameDelta Progress
-
-
-
--- Sub.none
-
-
-htmlTree : Tree -> Html Msg
-htmlTree tree =
- case tree of
- Tip segment ->
- Html.li [] [ Html.text <| "Tip: " ++ Debug.toString segment ]
-
- Node segment trees ->
- Html.li []
- [ Html.text <| "Tip: " ++ Debug.toString segment
- , Html.ul [] <| List.map htmlTree trees
- ]
+ let
+ handleKeyPress : Decoder Msg
+ handleKeyPress =
+ Json.Decode.field "key" Json.Decode.string
+ |> Json.Decode.andThen
+ (\key ->
+ case Debug.log "Key" key of
+ "," ->
+ Json.Decode.succeed (Regress 10)
+
+ "." ->
+ Json.Decode.succeed (Progress 10)
+
+ "<" ->
+ Json.Decode.succeed (Regress 1000)
+
+ ">" ->
+ Json.Decode.succeed (Progress 1000)
+
+ _ ->
+ Json.Decode.fail "Unknown key press"
+ )
+ in
+ Sub.batch
+ [ Browser.Events.onKeyPress handleKeyPress
+ , Browser.Events.onAnimationFrameDelta Progress
+ ]
=
=
-svgTree : Tree -> Html Msg
-svgTree tree =
- case tree of
- Tip segment ->
- circle [ cx "0", cy "0", fill segment.color, r "10" ] []
-
- Node segment trees ->
- let
- x =
- segment.length * cos segment.angle
-
- y =
- segment.length * sin segment.angle
-
- transformation =
- "translate("
- ++ String.fromFloat x
- ++ ", "
- ++ String.fromFloat y
- ++ ")"
- in
- g [ transform transformation ] <|
- circle [ cx "0", cy "0", fill segment.color, r "10" ] []
- :: List.map svgTree trees
-
-
-grow : Rules -> Float -> Tree -> Tree
-grow rules age branch =
- if age > 0 then
- case branch of
- Tip segment ->
- let
- tips =
- rules
- |> Dict.get segment.color
- |> Maybe.withDefault []
- |> List.map
- (\rule ->
- { rule
- | angle = rule.angle + segment.angle
- , length = rule.length * age
- }
- )
- |> List.map Tip
- in
- tips
- |> Node segment
- |> grow rules (age - 1)
-
- Node segment trees ->
- trees
- |> List.map (grow rules age)
- |> Node segment
+svgTree : Rules -> Float -> Float -> Color -> List (Svg Msg)
+svgTree rules treeAge branchAge color =
+ if branchAge > 0 then
+ rules
+ |> Dict.get color
+ |> Maybe.withDefault []
+ |> List.map
+ (\segment ->
+ let
+ transformation =
+ "rotate("
+ ++ String.fromFloat segment.angle
+ ++ ")"
+ ++ ", scale("
+ ++ String.fromFloat ((branchAge - 1) / treeAge)
+ ++ ")"
+ ++ ", translate("
+ ++ String.fromFloat (segment.length * branchAge)
+ ++ ", 0)"
+ in
+ g
+ [ transform transformation ]
+ (svgTree rules treeAge (branchAge - 1) segment.color)
+ )
+ |> (::) (circle [ cx "0", cy "0", fill color, r "1" ] [])
=
= else
- branch
+ []Add more elements to the ViewBox sample
Render the ViewBox indicator () in both pictures.
index 5b77dc1..551db09 100644
--- a/src/ViewBox.elm
+++ b/src/ViewBox.elm
@@ -118,9 +118,9 @@ ui model =
= , y (String.fromFloat model.top)
= , width (String.fromFloat model.width)
= , height (String.fromFloat model.height)
- , opacity "0.8"
+ , opacity "0.3"
= , stroke "white"
- , Svg.Attributes.style "fill: hsl(120, 100%, 85.1%)"
+ , fill "pink"
= ]
= []
= ]
@@ -128,7 +128,6 @@ ui model =
= , Element.el
= [ Element.height Element.fill
= , Element.width Element.fill
- , Background.color <| Element.rgb 0.7 1 0.7
= ]
= (Element.html <|
= svg
@@ -140,6 +139,16 @@ ui model =
= , Svg.Attributes.style "width: 100; height: 100%"
= ]
= [ g [] world
+ , rect
+ [ x (String.fromFloat model.left)
+ , y (String.fromFloat model.top)
+ , width (String.fromFloat model.width)
+ , height (String.fromFloat model.height)
+ , opacity "0.3"
+ , stroke "white"
+ , fill "pink"
+ ]
+ []
= ]
= )
= ]
@@ -237,26 +246,6 @@ ui model =
=world : List (Svg Msg)
=world =
= [ circle [ cx "400", cy "300", r "250", fill "purple" ] []
+ , circle [ cx "200", cy "350", r "50", fill "yellow" ] []
+ , circle [ cx "400", cy "600", r "20", fill "red" ] []
= ]
-
-
-
--- <|
--- Element.html <|
--- graph
--- [ circle
--- [ cx <| String.fromFloat model.x
--- , cy <| String.fromFloat model.y
--- , r "0.01"
--- , fill "magenta"
--- ]
--- []
--- , text_
--- [ x <| String.fromFloat (model.x + 0.03)
--- , y <| String.fromFloat model.y
--- , fontSize "0.05"
--- , dominantBaseline "central"
--- ]
--- [ text <| Debug.toString ( model.x, model.y ) ]
--- ]
--- , ]Set min value of the width and height in the ViewBox examle to 1
index 551db09..de064d5 100644
--- a/src/ViewBox.elm
+++ b/src/ViewBox.elm
@@ -212,7 +212,7 @@ ui model =
= , label =
= Input.labelBelow [ Element.centerX ] <|
= Element.text ("width: " ++ String.fromFloat model.width)
- , min = 0
+ , min = 1
= , max = 1000 - model.left
= , value = model.width
= , thumb = Input.defaultThumb
@@ -234,7 +234,7 @@ ui model =
= , label =
= Input.labelBelow [ Element.centerX ] <|
= Element.text ("height: " ++ String.fromFloat model.height)
- , min = 0
+ , min = 1
= , max = 1000 - model.top
= , value = model.height
= , thumb = Input.defaultThumb
Commits: 2
Implement the viewbox example
new file mode 100644
index 0000000..5b77dc1
--- /dev/null
+++ b/src/ViewBox.elm
@@ -0,0 +1,262 @@
+module ViewBox exposing
+ ( Flags
+ , Model
+ , Msg
+ , init
+ , main
+ , subscriptions
+ , ui
+ , update
+ , view
+ )
+
+import Browser
+import CartesianPlane exposing (graph)
+import Element
+import Element.Background as Background
+import Element.Border as Border
+import Element.Input as Input
+import Html exposing (Html)
+import Svg exposing (..)
+import Svg.Attributes exposing (..)
+
+
+main =
+ Browser.element
+ { init = init
+ , view = view
+ , update = update
+ , subscriptions = subscriptions
+ }
+
+
+type alias Flags =
+ ()
+
+
+type alias Model =
+ { left : Float
+ , top : Float
+ , width : Float
+ , height : Float
+ }
+
+
+type Msg
+ = Move Float Float
+ | Resize Float Float
+
+
+init : Flags -> ( Model, Cmd Msg )
+init () =
+ ( { left = 0
+ , top = 0
+ , width = 400
+ , height = 400
+ }
+ , Cmd.none
+ )
+
+
+view : Model -> Html.Html Msg
+view model =
+ let
+ wrapper element =
+ Element.el
+ [ Element.width (Element.maximum 1200 Element.fill)
+ , Element.height Element.fill
+ , Element.centerX
+ ]
+ element
+ in
+ ui model
+ |> wrapper
+ |> Element.layout
+ [ Element.height Element.fill
+ , Element.width Element.fill
+ ]
+
+
+update : Msg -> Model -> ( Model, Cmd Msg )
+update msg model =
+ case msg of
+ Move left top ->
+ ( { model | left = left, top = top }, Cmd.none )
+
+ Resize width height ->
+ ( { model | width = width, height = height }, Cmd.none )
+
+
+subscriptions : Model -> Sub Msg
+subscriptions model =
+ Sub.none
+
+
+ui model =
+ Element.column
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ , Element.spacing 30
+ , Element.padding 30
+ ]
+ [ Element.row
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ , Element.centerX
+ , Element.spacing 30
+ , Element.padding 30
+ ]
+ [ Element.el
+ [ Element.height Element.fill
+ , Element.width Element.fill
+ ]
+ (Element.html <|
+ svg [ viewBox "0 0 1000 1000" ]
+ [ g [] world
+ , rect
+ [ x (String.fromFloat model.left)
+ , y (String.fromFloat model.top)
+ , width (String.fromFloat model.width)
+ , height (String.fromFloat model.height)
+ , opacity "0.8"
+ , stroke "white"
+ , Svg.Attributes.style "fill: hsl(120, 100%, 85.1%)"
+ ]
+ []
+ ]
+ )
+ , Element.el
+ [ Element.height Element.fill
+ , Element.width Element.fill
+ , Background.color <| Element.rgb 0.7 1 0.7
+ ]
+ (Element.html <|
+ svg
+ [ [ model.left, model.top, model.width, model.height ]
+ |> List.map String.fromFloat
+ |> String.join " "
+ |> viewBox
+ , preserveAspectRatio "none"
+ , Svg.Attributes.style "width: 100; height: 100%"
+ ]
+ [ g [] world
+ ]
+ )
+ ]
+ , Input.slider
+ [ Element.behindContent
+ (Element.el
+ [ Element.width Element.fill
+ , Element.height (Element.px 2)
+ , Element.centerY
+ , Background.color <| Element.rgb 0.7 0.7 0.7
+ , Border.rounded 2
+ ]
+ Element.none
+ )
+ ]
+ { onChange = \left -> Move left model.top
+ , label =
+ Input.labelBelow [ Element.centerX ] <|
+ Element.text ("left: " ++ String.fromFloat model.left)
+ , min = 0
+ , max = 1000 - model.width
+ , value = model.left
+ , thumb = Input.defaultThumb
+ , step = Just 1
+ }
+ , Input.slider
+ [ Element.behindContent
+ (Element.el
+ [ Element.width Element.fill
+ , Element.height (Element.px 2)
+ , Element.centerY
+ , Background.color <| Element.rgb 0.7 0.7 0.7
+ , Border.rounded 2
+ ]
+ Element.none
+ )
+ ]
+ { onChange = \top -> Move model.left top
+ , label =
+ Input.labelBelow [ Element.centerX ] <|
+ Element.text ("top: " ++ String.fromFloat model.top)
+ , min = 0
+ , max = 1000 - model.width
+ , value = model.top
+ , thumb = Input.defaultThumb
+ , step = Just 1
+ }
+ , Input.slider
+ [ Element.behindContent
+ (Element.el
+ [ Element.width Element.fill
+ , Element.height (Element.px 2)
+ , Element.centerY
+ , Background.color <| Element.rgb 0.7 0.7 0.7
+ , Border.rounded 2
+ ]
+ Element.none
+ )
+ ]
+ { onChange = \width -> Resize width model.height
+ , label =
+ Input.labelBelow [ Element.centerX ] <|
+ Element.text ("width: " ++ String.fromFloat model.width)
+ , min = 0
+ , max = 1000 - model.left
+ , value = model.width
+ , thumb = Input.defaultThumb
+ , step = Just 1
+ }
+ , Input.slider
+ [ Element.behindContent
+ (Element.el
+ [ Element.width Element.fill
+ , Element.height (Element.px 2)
+ , Element.centerY
+ , Background.color <| Element.rgb 0.7 0.7 0.7
+ , Border.rounded 2
+ ]
+ Element.none
+ )
+ ]
+ { onChange = \height -> Resize model.width height
+ , label =
+ Input.labelBelow [ Element.centerX ] <|
+ Element.text ("height: " ++ String.fromFloat model.height)
+ , min = 0
+ , max = 1000 - model.top
+ , value = model.height
+ , thumb = Input.defaultThumb
+ , step = Just 1
+ }
+ ]
+
+
+world : List (Svg Msg)
+world =
+ [ circle [ cx "400", cy "300", r "250", fill "purple" ] []
+ ]
+
+
+
+-- <|
+-- Element.html <|
+-- graph
+-- [ circle
+-- [ cx <| String.fromFloat model.x
+-- , cy <| String.fromFloat model.y
+-- , r "0.01"
+-- , fill "magenta"
+-- ]
+-- []
+-- , text_
+-- [ x <| String.fromFloat (model.x + 0.03)
+-- , y <| String.fromFloat model.y
+-- , fontSize "0.05"
+-- , dominantBaseline "central"
+-- ]
+-- [ text <| Debug.toString ( model.x, model.y ) ]
+-- ]
+-- , ]Implement three simple examples: Simplest (just SVG), FillTheScreen and CenterdDot
Let the cartesian plane take the edge lenght argument.
index 0d82b4a..83986b5 100644
--- a/src/CartesianPlane.elm
+++ b/src/CartesianPlane.elm
@@ -5,33 +5,54 @@ import Svg exposing (..)
=import Svg.Attributes exposing (..)
=
=
-graph : List (Svg msg) -> Html msg
-graph shapes =
+graph : Float -> List (Svg msg) -> Html msg
+graph size shapes =
= let
+ top =
+ 0 - size / 2
+
+ left =
+ 0 - size / 2
+
+ bottom =
+ size / 2
+
+ right =
+ size / 2
+
+ hairline =
+ size / 1000
+
+ fontsize =
+ size / 200
+
= background =
= g []
= [ line
- [ x1 "-1"
+ [ x1 (String.fromFloat left)
= , y1 "0"
- , x2 "1"
+ , x2 (String.fromFloat right)
= , y2 "0"
= , stroke "black"
- , strokeWidth "0.001"
+ , strokeWidth (String.fromFloat hairline)
= ]
= []
= , line
= [ x1 "0"
= , x2 "0"
- , y1 "-1"
- , y2 "1"
+ , x2 (String.fromFloat top)
+ , y2 (String.fromFloat bottom)
= , stroke "black"
- , strokeWidth "0.001"
+ , strokeWidth (String.fromFloat hairline)
= ]
= []
= ]
= in
= svg
- [ viewBox "-1 -1 2 2"
+ [ [ left, top, size, size ]
+ |> List.map String.fromFloat
+ |> String.join " "
+ |> viewBox
= , preserveAspectRatio "xMidYMid meet"
= , Svg.Attributes.width "100%"
= , Svg.Attributes.height "100%"new file mode 100644
index 0000000..0382141
--- /dev/null
+++ b/src/CenteredDot.elm
@@ -0,0 +1,18 @@
+module Simplest exposing (main)
+
+import Element
+import Svg
+import Svg.Attributes
+
+
+main =
+ Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+ (Element.html
+ (Svg.svg
+ [ Svg.Attributes.viewBox "-1000 -1000 2000 2000" ]
+ [ Svg.circle [ Svg.Attributes.r "10" ] [] ]
+ )
+ )new file mode 100644
index 0000000..753928b
--- /dev/null
+++ b/src/FillTheScreen.elm
@@ -0,0 +1,24 @@
+module FillTheScreen exposing (main)
+
+import Element
+import Svg
+import Svg.Attributes
+
+
+main =
+ Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+ (Element.html
+ (Svg.svg
+ [ Svg.Attributes.style "background: pink; height: 100%; width: 100%" ]
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "30"
+ , Svg.Attributes.cy "30"
+ ]
+ []
+ ]
+ )
+ )new file mode 100644
index 0000000..8710e3c
--- /dev/null
+++ b/src/Simplest.elm
@@ -0,0 +1,15 @@
+module Simplest exposing (main)
+
+import Svg
+import Svg.Attributes
+
+
+main =
+ Svg.svg [ Svg.Attributes.style "background: pink" ]
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "30"
+ , Svg.Attributes.cy "30"
+ ]
+ []
+ ]
Commits: 2
Outline tree growing and a basic implementation of a tree program
index b4c52b5..b6f46a0 100644
--- a/index.md
+++ b/index.md
@@ -384,9 +384,9 @@ Day 3
=
= There are a number of functions that operate on lists, for example `List.length` and `List.map`.
=
- Examples of a map: a shopping list, after you find each item and place it in your basket, you are 'mapping' from a list of needed items to a list of items in your basket. Now you have two lists, and they are related. One is your original shopping list, the other the list of items in the basket.
+ Examples of a map: a shopping list, after you find each item and place it in your basket, you are 'mapping' from a list of needed items to a list of items in your basket. Now you have two lists, and they are related. One is your original shopping list, the other the list of items in the basket.
=
- Demonstrate:
+ Demonstrate:
=
= ```elm
= > things = ["Programming", "Swimming", "Dancing", "Saddle brown"]
@@ -406,12 +406,12 @@ Day 3
= : List String
= ```
=
- Notice our original `things` list is unchanged. This is different from our rubber duck machine. The rubber duck turns the plastic and paint into rubber ducks. A function on the other hand 'creates' the value it gives you. You don't loose the original value given to it.
+ Notice our original `things` list is unchanged. This is different from our rubber duck machine. The rubber duck turns the plastic and paint into rubber ducks. A function on the other hand 'creates' the value it gives you. You don't loose the original value given to it.
=
= - Make a `palette : List color`
=
= ```elm
- palette =
+ palette =
= [ "darkred"
= , "fuchsia"
= , "saddlebrown"
@@ -436,14 +436,14 @@ Day 3
= List.indexedMap dot palette
= ```
=
- We can introduce a `let` block to make our code more readable and avoid repetition.
+ We can introduce a `let` block to make our code more readable and avoid repetition.
=
= ```elm
= dot index color =
= let
- angle =
+ angle =
= (360 / count) * index
-
+
= count =
= List.length palette
= in
@@ -491,8 +491,8 @@ Introduce SVG groups
= ]
= ```
=
- We see a problem here. The gradient always goes from saddlebrown to pink from top left to bottom right. If we want to use the same gradient to connect different points, we'll need to be creative.
-
+ We see a problem here. The gradient always goes from saddlebrown to pink from top left to bottom right. If we want to use the same gradient to connect different points, we'll need to be creative.
+
= ```
= main =
= svg [ width "600", height "600", viewBox "-300 -300 600 600" ]
@@ -513,7 +513,7 @@ Introduce SVG groups
=
= We need to fix the viewbox!
=
- Here's the trick: we have the lines initially match the gradient. We're using the same x and y values for all the lines and the gradient. Then we transform the lines to position them where we'd like them. We're actually using our old friend polar coordinates here.
+ Here's the trick: we have the lines initially match the gradient. We're using the same x and y values for all the lines and the gradient. Then we transform the lines to position them where we'd like them. We're actually using our old friend polar coordinates here.
=
= If you're interested in what `gradientUnits "userSpaceOnUse"` does, come see me after the lesson.
=
@@ -524,23 +524,23 @@ Introduce SVG groups
= [ Svg.stop [ Svg.Attributes.offset "0", Svg.Attributes.stopColor "saddlebrown" ] []
= , Svg.stop [ Svg.Attributes.offset "1", Svg.Attributes.stopColor color ] []
= ]
-
-
+
+
= line : Int -> String -> Svg msg
= line index color =
= let
= angle =
= (360 / toFloat (List.length pallete)) * toFloat index
-
+
= transformation =
= "rotate(" ++ String.fromFloat angle ++ "),scale(" ++ String.fromInt radius ++ ",1)"
-
+
= url =
= "url(#Gradient-" ++ String.fromInt index ++ ")"
= in
= Svg.line [ x1 "0", y1 "0", x2 "1", y2 "0", transform transformation, stroke url ] []
-
-
+
+
= main =
= svg [ width "600", height "600", viewBox "-300 -300 600 600" ]
= [ Svg.g [] (List.indexedMap dot pallete)
@@ -549,23 +549,23 @@ Introduce SVG groups
= ]
= ```
=
- This may look like a lot, but our `gradient` and `line` functions are very similar to our `dot` function from earlier. In fact we see an essential principle of good programming design here. We spent a lot of time banging our heads over gradients. Now that we've done that work, we can hide the implementation in our gradient and line functions. Now, if we want to draw a new dot, line, and gradient going from the center to the dot, we only need to add a new item to our palette. Our functions do the rest for us. Our functions conceal our complexity. We can forget about the implementation, so long as we know how to operate them. They're like black boxes, we know what goes in and what comes out, but we don't have to know how they work on the inside.
+ This may look like a lot, but our `gradient` and `line` functions are very similar to our `dot` function from earlier. In fact we see an essential principle of good programming design here. We spent a lot of time banging our heads over gradients. Now that we've done that work, we can hide the implementation in our gradient and line functions. Now, if we want to draw a new dot, line, and gradient going from the center to the dot, we only need to add a new item to our palette. Our functions do the rest for us. Our functions conceal our complexity. We can forget about the implementation, so long as we know how to operate them. They're like black boxes, we know what goes in and what comes out, but we don't have to know how they work on the inside.
=
=
=Day 5
=
- Our project is looking pretty good at this point. We've created some interesting graphical elements, and learned some interesting ways to place them programatically. However, it doesn't take user input like many of the programs we use. We'll be doing some pretty interesting things with user input later in this workshop, but for now we'll start with a deceptively simple step: setting the background color.
+Our project is looking pretty good at this point. We've created some interesting graphical elements, and learned some interesting ways to place them programatically. However, it doesn't take user input like many of the programs we use. We'll be doing some pretty interesting things with user input later in this workshop, but for now we'll start with a deceptively simple step: setting the background color.
=
-I say this step is deceptively simple, because we'll need to make some big changes to our program and introduce a number of new concepts.
+I say this step is deceptively simple, because we'll need to make some big changes to our program and introduce a number of new concepts.
=
-Show complete code.
+Show complete code.
=
-There's a lot going on here, and it's not obvious how it all works.
+There's a lot going on here, and it's not obvious how it all works.
=
-Our `main` function has changed.
+Our `main` value has changed.
=
=```
-main : Program () Color Msg
+main : Program () State Msg
=main =
= Browser.sandbox
= { init = init
@@ -574,7 +574,8 @@ main =
= }
=```
=
-Previously, our `main` function contained an SVG element. Now it contains a call to `Browser.sandbox`. A sandbox is a basic interactive program. For a program to be interactive, it need more than just a SVG element. It also needs a `state`. The state of an application is basically the information used to determine how the application should be rendered. Think about the Atom program we are all running at the moment. We know we are all running the same program, however we are not all looking at exactly the same thing. The text in our neighbours editor might be slightly different than the text in our own. If they opened another file, a letter to a friend for example, the text would be very different. The text input is part of the state of the Atom application. Maybe your neighbour has scrolled to another point in the screen. The scroll point is also part of the state of the Atom application. The cursor might be on another line in their viewport. The cursor position is also part of the state of the Atom application. Atom uses all of this state information to render exactly what you are seeing on your computer screen. Anything that can change will be represented in a state variable. If you input or delete text, move the cursor, scroll up or down, or change any of the application settings, you are updating the state of the Atom application.
+Previously, the value of `main` was an SVG element. Now it contains a call to `Browser.sandbox`. A sandbox is a basic interactive program. For a program to be interactive, it need more than just a SVG element. It also needs a `state`. The state of an application is basically the information used to determine how the application should be rendered. Think about the Atom program we are all running at the moment. We know we are all running the same program, however we are not all looking at exactly the same thing. The text in our neighbors editor might be slightly different than the text in our own. If they opened another file, a letter to a friend for example, the text would be very different. The text input is part of the state of the Atom application. Maybe your neighbor has scrolled to another point in the screen. The scroll point is also part of the state of the Atom application. The cursor might be on another line in their viewport. The cursor position is also part of the state of the Atom application. Atom uses all of this state information to render exactly what you are seeing on your computer screen. Anything that can change will be represented in a state variable. If you input or delete text, move the cursor, scroll up or down, or change any of the application settings, you are updating
+the state of the Atom application.
=
=```
=type alias State =
@@ -585,9 +586,11 @@ type alias Color =
= String
=```
=
-Here we create something called a type alias. We've already discussed types. A type alias is basically a way to give an alternative name for a type. We give the alias `Color` to string and the alias `State` to `Color`. All of these types are strings, and Elm will treat them all as strings. But it will make our code more readable to use these aliases. We are writing an application that allows the user to change the background color. That should explain why our `State` is a `Color`.
+Here we create something called a type alias. We've already discussed types. A type alias is basically a way to give an alternative name for a type. We give the alias `Color` to `String` and the alias `State` to `Color`. So in the end all three names point to the same type!
+
+Why are we doing this? All of these types are strings, and Elm will treat them all as strings. But it will make our code more readable to use these aliases. We are writing an application that allows the user to change the background color. That should explain why our `State` is a `Color`.
=
-Our `Browser.sandbox` takes something with the name `init`. This is our initial state. Let's take a look at the value of `init`.
+Our `Browser.sandbox` takes something with the name `init`. This is our initial state. Let's take a look at the value of `init`.
=
=```
=init : State
@@ -595,9 +598,9 @@ init =
= "white"
=```
=
-We see that the initial state is `"white"`. We'll see how our application uses this value in a moment.
+We see that the initial state is `"white"`. We'll see how our application uses this value in a moment.
=
-Looking back at our `sandbox`, we see it also takes a `view`. Let's take a look at our view function:
+Looking back at our `sandbox`, we see it also takes a `view`. Let's take a look at our view function:
=
=```
=view : State -> Html Msg
@@ -616,14 +619,13 @@ view color =
=
=You'll notice that this `view` function looks very similar to the `main` variable from earlier. Before, our application was only a view. Now the view is only one piece of our interactive application.
=
-One important way our `view` function differs from the `main` variable from ealier is that `view` is a function. We see it takes a variable of type `State` (a string), which we have named `color`. In an Elm sandbox project the `view` always takes a variable with the same type as the `init` variable. This is the current state of the application. `view` can use the state to render the application properly, just as Atom uses its state to render text for its user.
-
-We see that we've added a line to the svg attributes, `Svg.Attributes.style ("background: " ++ color)`. Here we use the state of the application to set the background color of the svg element.
+One important way our `view` function differs from the `main` variable from ealier is that `view` is a function. We see it takes a variable of type `State` (a string), which we have named `color`. In an Elm sandbox project the `view` always takes a variable with the same type as the `init` variable. This is the current state of the application. `view` can use the state to render the application properly, just as Atom uses its state to render text for its user.
=
+We see that we've added a line to the svg attributes, `Svg.Attributes.style ("background: " ++ color)`. Here we use the state of the application to set the background color of the svg element.
=
=All we need now is some way to update the state (the background color) of the application.
=
-Taking a look at our `main` function, we see that it take a third and final function, `update`. Let's look at our update function:
+Taking a look at our `main` function, we see that it take a third and final function, `update`. Let's look at our update function:
=
=```
=type Msg
@@ -638,13 +640,58 @@ update msg state =
=
=```
=
-We see our `update` function takes two variables, of type `Msg` and `State`. If we think of update as a machine which takes an old state and spits out a new state, this state variable corresponds to the old state.
+We see our `update` function takes two variables, of type `Msg` and `State`. If we think of update as a machine which takes an old state and spits out a new state, this state variable corresponds to the old state.
+
+The `msg` variable is something new. Looking above, we see that we have defined a `Msg` variable type. This is similar to our `State` and `Color` union types, in that we have defined a new type that Elm understands. However, unlike the `type alias` constructor, the `type` constructor does not simply give a new name for an existing type. Instead, here we define a completely unique type. We see values with type Msg must have the form `SetBackground Color`. `SetBackground "white"`, `SetBackground "blue"`, `SetBackground "saddlebrown"` are all valid values of the type `Msg`.
+
+What is important to understand is that our `msg` variable is used by `update` to determine how it should update the state. We see that update first checks that `msg` has the form `SetBackground color`, and then returns the value of `color`. This will become our new state. Every time update is called, the sandbox will re-render our view with the new state.
+
+So now we see how our application changes the background color of the svg element. However, we haven't seen when it will update the background color. Well, we want to change the background color by clicking a dot. So let's take a look at our `dot` function to see if we can find a hint there.
+
+- `Model`:
+
+ the structure of the state of the program
+
+ TODO: Consistently use `Model` and `model` instead of less standard `State` to make it easier to our participants.
+
+- `Msg`
=
-The `msg` variable is something new. Looking above, we see that we have defined a `Msg` variable type. This is similar to our `State` and `Color` union types, in that we have defined a new type that Elm understands. However, unlike the `type alias` constructor, the `type` constructor does not simly give a new name for an existing type. Instead, here we define a completely unique type. We see values with type Msg must have the form `SetBackground Color`. `SetBackground "white"`, `SetBackground "blue"`, `SetBackground "saddlebrown"` are all valid values of the type `Msg`. This is a bit complex, but will become familiar once we've seen some examples in use.
+ What events can there be in the program. Currently have only one possible event - setting the background to a given color.
=
-What is important to understand is that our `msg` variable is used by `update` to determine how it should update the state. We see that update first checks that `msg` has the form `SetBackground color`, and then returns the value of `color`. This will become our new state. Every time update is called, the sandbox will rerender our view with the new state.
+- `update`
+
+ the instructions of how to change the state when a particular message comes in.
+
+- `view`
+
+ the instruction of what to display on the screen. It also contains instructions of what messages to send when certain events (like clicks on elements) happen. Every time the state changes the instructions will be applied.
+
+- `init`
+
+ What is the initial state right after the program is started.
+
+- `main`
+
+ glues it all together.
+
+Next steps:
+
+- How do we model the tree
+
+ - introduces the model for later interactive program
+
+ - naturally introduces union types
+
+
+- Make a tree grow
+
+ - Make a function that given the age of the tree and rules returns a tree
+
+ - Use time subscription to make the tree grow
+
+<!-- slide -->
=
-So now we see how our application changes the background color of the svg element. However, we haven't seen when it will update the background color. Well, we want to change the background color by clicking a dot. So let's take a look at our `dot` function to see if we can find a hint there.
+``
=
=```
=dot : Int -> String -> Svg Msg
@@ -663,15 +710,15 @@ dot index color =
= []
=```
=
-We see there is a new line here:
+We see there is a new line here:
=
=```
=Svg.Events.onClick (SetBackground color)
=```
=
-`onClick` is an event. It basically tells our svg element to listen for someone to click on it. It will then handle the event by emitting the Msg `SetBackground color`. We know `color` is the color of the dot. We see then that when someone clicks on the dot, the Msg will be emitted, `update` will be called with this Msg and will update our state. Our sandbox will rerender the view with this new state. The user will see the svg element with a new background color.
+`onClick` is an event. It basically tells our svg element to listen for someone to click on it. It will then handle the event by emitting the Msg `SetBackground color`. We know `color` is the color of the dot. We see then that when someone clicks on the dot, the Msg will be emitted, `update` will be called with this Msg and will update our state. Our sandbox will rerender the view with this new state. The user will see the svg element with a new background color.
=
-Notice that our `view` function, as well as all the functions that call functions responsible for rendering svg elements, including `gradient`, `line`, and `dot` have return values with type `Svg Msg` or `Html Msg`. This is because svg element can emit Msgs which will be handled by `update`.
+Notice that our `view` function, as well as all the functions that call functions responsible for rendering svg elements, including `gradient`, `line`, and `dot` have return values with type `Svg Msg` or `Html Msg`. This is because svg element can emit Msgs which will be handled by `update`.
=
=
=<!-- slide -->Basic implementation of a tree growing program
Accidentally not included in previous commit.
new file mode 100644
index 0000000..1939a19
--- /dev/null
+++ b/src/Tree.elm
@@ -0,0 +1,179 @@
+module Tree exposing (main)
+
+import Browser
+import Browser.Events
+import Dict exposing (Dict)
+import Html exposing (Html)
+import Svg exposing (..)
+import Svg.Attributes exposing (..)
+
+
+main =
+ Browser.element
+ { init = init
+ , view = view
+ , update = update
+ , subscriptions = subscriptions
+ }
+
+
+type alias Flags =
+ ()
+
+
+type alias Model =
+ { time : Float
+ , rules : Rules
+ }
+
+
+type Msg
+ = Progress Float
+
+
+type Tree
+ = Tip Segment
+ | Node Segment (List Tree)
+
+
+type alias Color =
+ String
+
+
+type alias Segment =
+ { color : Color
+ , angle : Float
+ , length : Float
+ }
+
+
+type alias Rules =
+ Dict Color (List Segment)
+
+
+init : Flags -> ( Model, Cmd Msg )
+init () =
+ ( { time = 1
+ , rules =
+ Dict.fromList
+ [ ( "red"
+ , [ Segment "yellow" (degrees 15) 10
+ , Segment "yellow" (degrees 175) 10
+ ]
+ )
+ , ( "yellow", [ Segment "blue" (degrees 15) 10 ] )
+ , ( "blue", [ Segment "red" (degrees 15) 10 ] )
+ ]
+
+ -- [ ( "red"
+ -- , [ Segment "blue" (degrees 90) 10
+ -- , Segment "yellow" (degrees -45) 4
+ -- ]
+ -- )
+ -- , ( "yellow", [ Segment "red" (degrees 90) 6 ] )
+ -- ]
+ }
+ , Cmd.none
+ )
+
+
+view : Model -> Html Msg
+view model =
+ Html.div []
+ [ Html.h1 [] [ Html.text <| String.fromFloat model.time ]
+ , Segment "red" 0 0
+ |> Tip
+ |> grow model.rules (model.time / 1000)
+ |> svgTree
+ |> List.singleton
+ |> svg [ viewBox "-1000 -1000 2000 2000", height "800px", width "800px" ]
+ ]
+
+
+update : Msg -> Model -> ( Model, Cmd Msg )
+update msg model =
+ case msg of
+ Progress delta ->
+ ( { model | time = model.time + delta }
+ , Cmd.none
+ )
+
+
+subscriptions : Model -> Sub Msg
+subscriptions model =
+ Browser.Events.onAnimationFrameDelta Progress
+
+
+
+-- Sub.none
+
+
+htmlTree : Tree -> Html Msg
+htmlTree tree =
+ case tree of
+ Tip segment ->
+ Html.li [] [ Html.text <| "Tip: " ++ Debug.toString segment ]
+
+ Node segment trees ->
+ Html.li []
+ [ Html.text <| "Tip: " ++ Debug.toString segment
+ , Html.ul [] <| List.map htmlTree trees
+ ]
+
+
+svgTree : Tree -> Html Msg
+svgTree tree =
+ case tree of
+ Tip segment ->
+ circle [ cx "0", cy "0", fill segment.color, r "10" ] []
+
+ Node segment trees ->
+ let
+ x =
+ segment.length * cos segment.angle
+
+ y =
+ segment.length * sin segment.angle
+
+ transformation =
+ "translate("
+ ++ String.fromFloat x
+ ++ ", "
+ ++ String.fromFloat y
+ ++ ")"
+ in
+ g [ transform transformation ] <|
+ circle [ cx "0", cy "0", fill segment.color, r "10" ] []
+ :: List.map svgTree trees
+
+
+grow : Rules -> Float -> Tree -> Tree
+grow rules age branch =
+ if age > 0 then
+ case branch of
+ Tip segment ->
+ let
+ tips =
+ rules
+ |> Dict.get segment.color
+ |> Maybe.withDefault []
+ |> List.map
+ (\rule ->
+ { rule
+ | angle = rule.angle + segment.angle
+ , length = rule.length * age
+ }
+ )
+ |> List.map Tip
+ in
+ tips
+ |> Node segment
+ |> grow rules (age - 1)
+
+ Node segment trees ->
+ trees
+ |> List.map (grow rules age)
+ |> Node segment
+
+ else
+ branch
Commits: 1
Outline changing the background color when a dot is clicked
index 2daa781..b4c52b5 100644
--- a/index.md
+++ b/index.md
@@ -551,7 +551,128 @@ Introduce SVG groups
=
= This may look like a lot, but our `gradient` and `line` functions are very similar to our `dot` function from earlier. In fact we see an essential principle of good programming design here. We spent a lot of time banging our heads over gradients. Now that we've done that work, we can hide the implementation in our gradient and line functions. Now, if we want to draw a new dot, line, and gradient going from the center to the dot, we only need to add a new item to our palette. Our functions do the rest for us. Our functions conceal our complexity. We can forget about the implementation, so long as we know how to operate them. They're like black boxes, we know what goes in and what comes out, but we don't have to know how they work on the inside.
=
-
+
+Day 5
+
+ Our project is looking pretty good at this point. We've created some interesting graphical elements, and learned some interesting ways to place them programatically. However, it doesn't take user input like many of the programs we use. We'll be doing some pretty interesting things with user input later in this workshop, but for now we'll start with a deceptively simple step: setting the background color.
+
+I say this step is deceptively simple, because we'll need to make some big changes to our program and introduce a number of new concepts.
+
+Show complete code.
+
+There's a lot going on here, and it's not obvious how it all works.
+
+Our `main` function has changed.
+
+```
+main : Program () Color Msg
+main =
+ Browser.sandbox
+ { init = init
+ , view = view
+ , update = update
+ }
+```
+
+Previously, our `main` function contained an SVG element. Now it contains a call to `Browser.sandbox`. A sandbox is a basic interactive program. For a program to be interactive, it need more than just a SVG element. It also needs a `state`. The state of an application is basically the information used to determine how the application should be rendered. Think about the Atom program we are all running at the moment. We know we are all running the same program, however we are not all looking at exactly the same thing. The text in our neighbours editor might be slightly different than the text in our own. If they opened another file, a letter to a friend for example, the text would be very different. The text input is part of the state of the Atom application. Maybe your neighbour has scrolled to another point in the screen. The scroll point is also part of the state of the Atom application. The cursor might be on another line in their viewport. The cursor position is also part of the state of the Atom application. Atom uses all of this state information to render exactly what you are seeing on your computer screen. Anything that can change will be represented in a state variable. If you input or delete text, move the cursor, scroll up or down, or change any of the application settings, you are updating the state of the Atom application.
+
+```
+type alias State =
+ Color
+
+
+type alias Color =
+ String
+```
+
+Here we create something called a type alias. We've already discussed types. A type alias is basically a way to give an alternative name for a type. We give the alias `Color` to string and the alias `State` to `Color`. All of these types are strings, and Elm will treat them all as strings. But it will make our code more readable to use these aliases. We are writing an application that allows the user to change the background color. That should explain why our `State` is a `Color`.
+
+Our `Browser.sandbox` takes something with the name `init`. This is our initial state. Let's take a look at the value of `init`.
+
+```
+init : State
+init =
+ "white"
+```
+
+We see that the initial state is `"white"`. We'll see how our application uses this value in a moment.
+
+Looking back at our `sandbox`, we see it also takes a `view`. Let's take a look at our view function:
+
+```
+view : State -> Html Msg
+view color =
+ svg
+ [ width "600"
+ , height "600"
+ , viewBox "-300 -300 600 600"
+ , Svg.Attributes.style ("background: " ++ color)
+ ]
+ [ Svg.g [] (List.indexedMap dot pallete)
+ , Svg.defs [] (List.indexedMap gradient pallete)
+ , Svg.g [] (List.indexedMap line pallete)
+ ]
+```
+
+You'll notice that this `view` function looks very similar to the `main` variable from earlier. Before, our application was only a view. Now the view is only one piece of our interactive application.
+
+One important way our `view` function differs from the `main` variable from ealier is that `view` is a function. We see it takes a variable of type `State` (a string), which we have named `color`. In an Elm sandbox project the `view` always takes a variable with the same type as the `init` variable. This is the current state of the application. `view` can use the state to render the application properly, just as Atom uses its state to render text for its user.
+
+We see that we've added a line to the svg attributes, `Svg.Attributes.style ("background: " ++ color)`. Here we use the state of the application to set the background color of the svg element.
+
+
+All we need now is some way to update the state (the background color) of the application.
+
+Taking a look at our `main` function, we see that it take a third and final function, `update`. Let's look at our update function:
+
+```
+type Msg
+ = SetBackground Color
+
+
+update : Msg -> State -> State
+update msg state =
+ case msg of
+ SetBackground color ->
+ color
+
+```
+
+We see our `update` function takes two variables, of type `Msg` and `State`. If we think of update as a machine which takes an old state and spits out a new state, this state variable corresponds to the old state.
+
+The `msg` variable is something new. Looking above, we see that we have defined a `Msg` variable type. This is similar to our `State` and `Color` union types, in that we have defined a new type that Elm understands. However, unlike the `type alias` constructor, the `type` constructor does not simly give a new name for an existing type. Instead, here we define a completely unique type. We see values with type Msg must have the form `SetBackground Color`. `SetBackground "white"`, `SetBackground "blue"`, `SetBackground "saddlebrown"` are all valid values of the type `Msg`. This is a bit complex, but will become familiar once we've seen some examples in use.
+
+What is important to understand is that our `msg` variable is used by `update` to determine how it should update the state. We see that update first checks that `msg` has the form `SetBackground color`, and then returns the value of `color`. This will become our new state. Every time update is called, the sandbox will rerender our view with the new state.
+
+So now we see how our application changes the background color of the svg element. However, we haven't seen when it will update the background color. Well, we want to change the background color by clicking a dot. So let's take a look at our `dot` function to see if we can find a hint there.
+
+```
+dot : Int -> String -> Svg Msg
+dot index color =
+ let
+ angle =
+ (360 / toFloat (List.length pallete)) * toFloat index
+ in
+ circle
+ [ cx (x angle)
+ , cy (y angle)
+ , r "40"
+ , fill color
+ , Svg.Events.onClick (SetBackground color)
+ ]
+ []
+```
+
+We see there is a new line here:
+
+```
+Svg.Events.onClick (SetBackground color)
+```
+
+`onClick` is an event. It basically tells our svg element to listen for someone to click on it. It will then handle the event by emitting the Msg `SetBackground color`. We know `color` is the color of the dot. We see then that when someone clicks on the dot, the Msg will be emitted, `update` will be called with this Msg and will update our state. Our sandbox will rerender the view with this new state. The user will see the svg element with a new background color.
+
+Notice that our `view` function, as well as all the functions that call functions responsible for rendering svg elements, including `gradient`, `line`, and `dot` have return values with type `Svg Msg` or `Html Msg`. This is because svg element can emit Msgs which will be handled by `update`.
+
=
=<!-- slide -->
=
Commits: 1
Outline lines and gradients
index 936c34e..2daa781 100644
--- a/index.md
+++ b/index.md
@@ -20,7 +20,7 @@ presentation:
=
= - Atom
=
-
+Day 1
=
=- Setup the project
=
@@ -80,6 +80,8 @@ presentation:
= - Exercise: make 3 more dots (5 total)
=
=
+Day 2
+
=- Place the dots in a circle
=
= - Introduce a problem: We want to place the dots in a circle: show an example in the slides
@@ -116,6 +118,11 @@ presentation:
=
= Create a viewbox with correct perimeters (must be more than the radius of the circle plus the radius of a dot in each direction, and width and height are circumference)
=
+ ```elm
+ svg [ viewbox "-100 -100 200 200" ] []
+ ```
+
+Day 3
=
=- Let the computer do the math
=
@@ -349,8 +356,8 @@ presentation:
= ```elm
= dot angle color =
= circle
- [ cx (x 72)
- , cy (y 72)
+ [ cx (x angle)
+ , cy (y angle)
= , r "10"
= , fill color
= ]
@@ -362,10 +369,10 @@ presentation:
= ```
= svg [ viewbox "-100 -100 200 200" ]
= [ dot 0 "darkred"
- , dot 288 "gold"
= , dot 72 "fuchsia"
= , dot 144 "saddlebrown"
= , dot 216 "deepskyblue"
+ , dot 288 "gold"
= ]
= ```
=
@@ -375,11 +382,176 @@ presentation:
=
= - Show List and list operations (map and indexedMap)
=
+ There are a number of functions that operate on lists, for example `List.length` and `List.map`.
+
+ Examples of a map: a shopping list, after you find each item and place it in your basket, you are 'mapping' from a list of needed items to a list of items in your basket. Now you have two lists, and they are related. One is your original shopping list, the other the list of items in the basket.
+
+ Demonstrate:
+
+ ```elm
+ > things = ["Programming", "Swimming", "Dancing", "Saddle brown"]
+ ["Programming","Swimming","Dancing","Saddle brown"]
+ : List String
+
+
+ > List.length things
+ 4 : Int
+
+ > List.map fun things
+ ["Programming is fun!","Swimming is fun!","Dancing is fun!","Saddle brown is fun!"]
+ : List String
+
+ > things
+ ["Programming","Swimming","Dancing","Saddle brown"]
+ : List String
+ ```
+
+ Notice our original `things` list is unchanged. This is different from our rubber duck machine. The rubber duck turns the plastic and paint into rubber ducks. A function on the other hand 'creates' the value it gives you. You don't loose the original value given to it.
+
= - Make a `palette : List color`
=
+ ```elm
+ palette =
+ [ "darkred"
+ , "fuchsia"
+ , "saddlebrown"
+ , "deepskyblue"
+ , "gold"
+ ]
+ ```
+
= - Use `List.indexedMap dot palette` to generate the dots
=
+ Another function that operates on lists is `List.indexedMap`. Let's see it at work:
=
+ ```elm
+ dot index color =
+ circle
+ [ cx (x ((360 / (List.length palette)) * index))
+ , cy (y ((360 / (List.length palette)) * index))
+ , r "10"
+ , fill color
+ ]
+
+ List.indexedMap dot palette
+ ```
+
+ We can introduce a `let` block to make our code more readable and avoid repetition.
+
+ ```elm
+ dot index color =
+ let
+ angle =
+ (360 / count) * index
+
+ count =
+ List.length palette
+ in
+ circle
+ [ cx (x angle)
+ , cy (y angle)
+ , r "10"
+ , fill color
+ ]
+ ```
+
+ Take a look at the [documentation for `List.indexedMap`](https://package.elm-lang.org/packages/elm/core/latest/List#indexedMap).
+
+
+Day 4
+
+Introduce SVG groups
+
+ - We'll need one group for the dots and one group for the lines
+
+ ```elm
+ svg [ viewBox "-100 -100 200 200" ]
+ [ Svg.g [] (List.indexedMap dot pallete)
+ , Svg.g [] [ Svg.line [ x1 "123", y1 "112", x2 "41", y2 "11", stroke "black" ] [] ]
+ ]
+ ```
+
+ Exercise: add a few lines with different colors
+
+ Now we'll learn how to color a line with a gradient (going from one color to another)
+
+ ```
+ svg [ width "600", height "600", viewBox "-300 -300 600 600" ]
+ [ Svg.g [] (List.indexedMap dot pallete)
+ , Svg.defs []
+ [ Svg.linearGradient [ Svg.Attributes.id "MyGradient", x1 "0", y1 "0", x2 "1", y2 "0" ]
+ [ Svg.stop [ Svg.Attributes.offset "0", Svg.Attributes.stopColor "saddlebrown" ] []
+ , Svg.stop [ Svg.Attributes.offset "1", Svg.Attributes.stopColor "pink" ] []
+ ]
+ ]
+ , Svg.g []
+ [ Svg.line [ x1 "0", y1 "0", x2 "100", y2 "100", stroke "url(#MyGradient)" ] []
+ , Svg.line [ x1 "0", y1 "0", x2 "-100", y2 "-100", stroke "url(#MyGradient)" ] []
+ ]
+ ]
+ ```
+
+ We see a problem here. The gradient always goes from saddlebrown to pink from top left to bottom right. If we want to use the same gradient to connect different points, we'll need to be creative.
+
+ ```
+ main =
+ svg [ width "600", height "600", viewBox "-300 -300 600 600" ]
+ [ Svg.g [] (List.indexedMap dot pallete)
+ , Svg.defs []
+ [ Svg.linearGradient [ Svg.Attributes.id "MyGradient", x1 "0", y1 "0", x2 "1", y2 "0", gradientUnits "userSpaceOnUse" ]
+ [ Svg.stop [ Svg.Attributes.offset "0", Svg.Attributes.stopColor "saddlebrown" ] []
+ , Svg.stop [ Svg.Attributes.offset "1", Svg.Attributes.stopColor "pink" ] []
+ ]
+ ]
+ , Svg.g []
+ [ Svg.line [ x1 "0", y1 "0", x2 "1", y2 "0", transform "rotate(45),scale(100,1)", stroke "url(#MyGradient)" ] []
+ , Svg.line [ x1 "0", y1 "0", x2 "1", y2 "0", transform "rotate(180),scale(100,1)", stroke "url(#MyGradient)" ] []
+ , Svg.line [ x1 "0", y1 "0", x2 "1", y2 "0", transform "rotate(270),scale(100,1)", stroke "url(#MyGradient)" ] []
+ ]
+ ]
+ ```
+
+ We need to fix the viewbox!
+
+ Here's the trick: we have the lines initially match the gradient. We're using the same x and y values for all the lines and the gradient. Then we transform the lines to position them where we'd like them. We're actually using our old friend polar coordinates here.
+
+ If you're interested in what `gradientUnits "userSpaceOnUse"` does, come see me after the lesson.
+
+ ```elm
+ gradient : Int -> String -> Svg msg
+ gradient index color =
+ Svg.linearGradient [ Svg.Attributes.id ("Gradient-" ++ String.fromInt index), x1 "0", y1 "0", x2 "1", y2 "0", gradientUnits "userSpaceOnUse" ]
+ [ Svg.stop [ Svg.Attributes.offset "0", Svg.Attributes.stopColor "saddlebrown" ] []
+ , Svg.stop [ Svg.Attributes.offset "1", Svg.Attributes.stopColor color ] []
+ ]
+
+
+ line : Int -> String -> Svg msg
+ line index color =
+ let
+ angle =
+ (360 / toFloat (List.length pallete)) * toFloat index
+
+ transformation =
+ "rotate(" ++ String.fromFloat angle ++ "),scale(" ++ String.fromInt radius ++ ",1)"
+
+ url =
+ "url(#Gradient-" ++ String.fromInt index ++ ")"
+ in
+ Svg.line [ x1 "0", y1 "0", x2 "1", y2 "0", transform transformation, stroke url ] []
+
+
+ main =
+ svg [ width "600", height "600", viewBox "-300 -300 600 600" ]
+ [ Svg.g [] (List.indexedMap dot pallete)
+ , Svg.defs [] (List.indexedMap gradient pallete)
+ , Svg.g [] (List.indexedMap line pallete)
+ ]
+ ```
+
+ This may look like a lot, but our `gradient` and `line` functions are very similar to our `dot` function from earlier. In fact we see an essential principle of good programming design here. We spent a lot of time banging our heads over gradients. Now that we've done that work, we can hide the implementation in our gradient and line functions. Now, if we want to draw a new dot, line, and gradient going from the center to the dot, we only need to add a new item to our palette. Our functions do the rest for us. Our functions conceal our complexity. We can forget about the implementation, so long as we know how to operate them. They're like black boxes, we know what goes in and what comes out, but we don't have to know how they work on the inside.
+
+
=
=<!-- slide -->
=
Commits: 3
Put slides back in MD file for easier editing
Also make Git ignore Emacs temporary files.
Co-Authored-By: Sam Phillips samuel.rodney.phillips@gmail.com
index 0c19f1d..1cf77ef 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
=/elm-stuff
=/**/.DS_Store
=*.swp
+.#*index a2ea019..5704253 100644
--- a/index.md
+++ b/index.md
@@ -6,7 +6,45 @@ presentation:
=
=<!-- slide -->
=
-# FP-Art!
+## Conspect
+
+- Setup the development environment
+
+ - Elm
+
+ - Node.js
+
+ why?
+
+ - Test REPL (2 + 2)
+
+ - Atom
+
+
+
+- Setup the project
+
+ Do we want source control?
+
+ - Create directory
+
+ - `elm init`
+
+ - Open a file in the editor
+
+ - Type hello world program
+
+ - Elm Reactor
+
+ - Web browser
+
+
+
+
+
+<!-- slide -->
+
+# Software Garden
=## A functional programming workshop
=### for non-programmers
=
@@ -29,14 +67,34 @@ presentation:
=We will need:
=
=- a text editor (I use [Atom][])
-- and [Elm programming language][]
+- and the [Elm programming language][]
=
=[Atom]: https://atom.io/
=[Elm programming language]: https://elm-lang.org/
=
=<!-- slide -->
=
-We will use the terminal a little bit.
+### Elm Installation
+
+<!-- slide -->
+
+To install the Elm programming language, go to it's website:
+
+http://elm-lang.org/
+
+and follow the installation instructions.
+
+<!-- slide -->
+
+Some of the tools we use with Elm require Node.js.
+
+Go to the [Node.js website][Node.js] and install the current version (the green button on the right).
+
+[Node.js]: https://nodejs.org/en/
+
+<!-- slide -->
+
+We will need to use the terminal a little bit.
=
=# :fa-terminal:
=
@@ -44,13 +102,17 @@ Don't be scared. It's easy :)
=
=<!-- slide data-background-image="images/mac-launchpad-terminal.png" data-background-size="cover" data-background-position="top center"-->
=
-> <p style="color: white">In Launchpad find a program called <code>terminal</code> and start it.</p>
+<p style="color: white">
+ In Launchpad find a program called <code>terminal</code> and start it.
+</p>
=
=<!-- slide -->
=
=You should see a window like this
=
-<img class="plain" alt="Mac Terminal app window" src="images/mac-terminal-window.png" />
+<!-- slide -->
+
+<img alt="Mac Terminal app window" src="../assets/mac-terminal-window.png" class="plain"/>
=
=<!-- slide -->
=
@@ -185,7 +247,6 @@ apm install language-elm
=
=<!-- slide -->
=
-
=# First program!
=
=<!-- slide -->
@@ -236,7 +297,7 @@ atom src/Main.elm
=
=<small>This command should open a new Atom window with empty text file.</small>
=
-<!-- slide data-transition=zoom -->
+<!-- slide -->
=
=### `main.elm`
=
@@ -308,7 +369,21 @@ elm reactor
=
=<small>you can press up arrow :fa-arrow-up: on the keyboard to get to previous commands</small>
=
+
+<!-- slide -->
+
+Move sliders to change the `x` and `y` coordinates of the dot. It's like moving a thing on a table by telling how far left, right, up or down should it go.
+
+`<CartesianCoordinates program>`
+
=<!-- slide -->
=
-<iframe id="cartesian" data-src="./CartesianCoordinates.html" class="stretch">
-</iframe>
+Move sliders to change the `angle` and `length` properties of the line connecting the dot and origin. The x and y coordinates will be calculated like this:
+
+```
+x = cos(angle) * length
+y = sin(angle) * length
+```
+
+
+`<PolarCoordinates program>`Work on outline for slides
index 5704253..334b686 100644
--- a/index.md
+++ b/index.md
@@ -39,6 +39,236 @@ presentation:
= - Web browser
=
=
+- Let's make a dot!
+
+ - Introduce a problem: We want to have a dot on the screen
+
+ - Show complete code
+
+ - Explain:
+
+ The dot has a position (x + y coordinates) and size (radius)
+
+ Coordinate planes: we can use CartesianCoordinates
+
+ - Introduce SVG
+
+
+- Give a color to the dot
+
+ - Explain SVG attributes (svg elements take a list of attributes)
+
+ - Everyone can pick a color
+
+
+- Multiple dots
+
+ - Introduce a problem: We want to have two dots on the screen
+
+ - Explain a list, and show there is already a list there (with one item)
+
+ Do you see any other lists?
+
+ - Element takes a list of children
+
+ - Mention, how can we have a list with one or zero elements? Shopping list with one one item. Empty spoon drawer (list of spoons)
+
+ - Give your new dot a color and different coordinates
+
+ - Show the code for 2 dots
+
+ - Exercise: make 3 more dots (5 total)
+
+
+- Place the dots in a circle
+
+ - Introduce a problem: We want to place the dots in a circle: show an example in the slides
+
+ - How we will get there: we will change the cartesian coordinates of the dots, nothing else! But figuring out the right coordinates is a bit tricky
+
+ - How can we figure out the correct cartesian coordinates?
+
+ - Story about the flowerpot and the table: two ways to measure relative position, one is distance from a x and y axis (cartesian coordinates), the other is angle and distance relative to origin (polar coordinates)
+
+ - What does this have to do with a circle? Do you know the expression 'I did a 180'. Where would you be looking if you did two 180s (a "360"). We see that circles and angles are closely related!
+
+ - Exercise with compass and 5 objects, place objects evenly along compass. How many degrees apart are they? (observe if we multiply the result by 5, we're back to 360)
+
+ - We have one part of it (angle), we now need the distance. Notice they are all the same distance from the origin (the center dot) of the compass. Definition of circle: points that are an equal distance from a center. Actually, the distance doesn't matter, so long as they all have the same distance. You can have a big circle or a small circle, they're both circles
+
+ - Ok great, we're done! Now, does anyone know how to give an angle and distance to svg? Oh... no? We don't either... you can't. You can only give x and y values relative to the origin
+
+ - So we already know that angle and length are just another way of describing x and y. But we need some way of translating between the two
+
+ Using a visual demonstration, if you draw a line from your object to the x axis, you have a triangle. Same if you draw a line to the y axis. You can figure out the point on the axis using sin and cos functions and multiplying the result by the length.
+
+ Let's use a chart to figure out the sin and cos of our angles
+
+ *It's important to get a chart, otherwise we have to use calculators which work with radians*
+
+ - Now we are done... we can plug in our x and y values, and presto, our dots are arranged in a circle. But we don't see most of them...
+
+ - Our origin is in the top left corner. This means any dots with negative x or y values are off the screen.
+
+ We can shift the dots so they are on the screen, or shift the screen so that it covers the dots. We will be shifting the screen
+
+ Sample viewbox program to demonstrate
+
+ Create a viewbox with correct perimeters (must be more than the radius of the circle plus the radius of a dot in each direction, and width and height are circumference)
+
+
+- Let the computer do the math
+
+ - Introduce the problem: Can we avoid all these repetitive and manual steps?
+
+ - What is a program made of? (in the REPL):
+
+ - Values and names
+
+ ```
+ 5
+ 10.4
+ "Hello World"
+ [1, 2, 3, 4, 5]
+ ```
+
+ Numbers, strings, lists containing numbers and strings are all values. They are self-referential. The simply state what they are. There are other kinds of values that we will see later.
+
+
+ You can give (or assign) a name to a value, as shown.
+
+ ```
+ family = 5
+ price = 10.4
+ morningGreeting = "Hello World"
+ fingers = [1, 2, 3, 4, 5]
+ ```
+
+ Here, `family` is a name and `5` is the value assigned to `family`.
+
+ You can get the value back by calling its name.
+
+ ```
+ ---- Elm 0.19.0 ----------------------------------------------------------------
+ Read <https://elm-lang.org/0.19.0/repl> to learn more: exit, help, imports, etc.
+ --------------------------------------------------------------------------------
+ > family = 5
+ 5 : number
+ > family
+ 5 : number
+ ```
+
+ - Operators (+, -, ++)
+
+ ```
+ > 2 + 5
+ 7 : number
+ ```
+
+ ```
+ > 4 - 6
+ -2 : number
+ ```
+
+ ```
+ > "Hello" ++ " world!"
+ "Hello world!" : String
+ ```
+
+ ```
+ > 1 :: [2, 3, 4, 5]
+ [1,2,3,4,5] : List number
+ ```
+
+ Operators take two values and return a new value. We know that names refer to values, so we can use them in place of values:
+
+ ```
+ > family = 5
+ 5 : number
+ > family + 2
+ 7 : number
+ ```
+
+ You can also give a name to the value returned by an operator:
+
+ ```
+ > family = 5
+ 5 : number
+ > familyAndPets = family + 2
+ 7 : number
+ > familyAndPets
+ 7 : number
+ ```
+
+ Note that different values have different types.
+
+ ```
+ 5 : number
+ 10.4 : Float
+ "Hello World" : String
+ [1,2,3,4,5] : List number
+ ```
+ Different operators work on different types. Adding a number and a string doesn't make sense. So if you try, Elm will complain (and give a helpful hint):
+
+ ```
+ > "Hello " + 5
+ -- TYPE MISMATCH ----------------------------------------------------------- elm
+
+ I cannot do addition with String values like this one:
+
+ 5| "Hello " + 5
+ ^^^^^^^^
+ The (+) operator only works with Int and Float values.
+
+ Hint: Switch to the (++) operator to append strings!
+ ```
+
+ (Int and Float are both types representing numbers)
+
+
+ - Functions
+
+ ```elm
+ fun something = something ++ " is fun."
+ ```
+
+ Mention that operators are really functions:
+
+ ```elm
+ (-) 5 10
+ 5 - 10
+ ```
+
+ - Exercise: In our Main.elm, try to identify some values, names and functions:
+
+ Hint: `"darkred"` is a value, `main` is a name, `width` is a function.
+
+ - Where can we use some functions?
+
+ - Apply to our trigonometry calculations
+
+ - Compose and copy and paste the trigonometry functions to calculate cx and cy:
+
+ ```elm
+ (String.fromFloat (sin (degrees 72) * 100))
+ (String.fromFloat (cos (degrees 72) * 100))
+ ```
+
+ - Eliminate repetition:
+
+ - define and reuse radius (100)
+
+ - define and reuse `x : angle -> cx` and `y : angle -> cy`
+
+ - define and reuse `dot : angle -> color -> Svg`
+
+ - Make it more general
+
+ - Show List and list operations (map and indexedMap)
+
+ - Make a `palette : List color`
+
+ - Use `List.indexedMap dot palette` to generate the dots
=
=
=Add more to the conspect
Co-Authored-By: Sam Phillips samuel.rodney.phillips@gmail.com
index 334b686..936c34e 100644
--- a/index.md
+++ b/index.md
@@ -132,8 +132,7 @@ presentation:
= [1, 2, 3, 4, 5]
= ```
=
- Numbers, strings, lists containing numbers and strings are all values. They are self-referential. The simply state what they are. There are other kinds of values that we will see later.
-
+ Numbers, strings, lists containing numbers and strings are all values. They are self-referential. They simply state what they are. There are other kinds of values that we will see later.
=
= You can give (or assign) a name to a value, as shown.
=
@@ -232,36 +231,146 @@ presentation:
= fun something = something ++ " is fun."
= ```
=
- Mention that operators are really functions:
+ Explain: a function is a thing that takes some values (called arguments) and return one value. So it's similar to operators. In fact operators are functions!
=
= ```elm
= (-) 5 10
= 5 - 10
= ```
=
- - Exercise: In our Main.elm, try to identify some values, names and functions:
+ You can think of a function as a machine. You put something in the machine, and it produces something in return. For example think about a machine that produces rubber ducks. You put a bucket of white plastic pellets and a bucket of red paint, and you get a bunch of red rubber ducks!
+
+ What you get will depends on what you put. The color of the ducks depends on the paint you put. Quantity of ducks depends on how much plastic you put in.
+
+ ```elm
+ makeMeSomeDucks color plastic =
+ String.fromInt plastic ++ " " ++ color ++ " rubber ducks"
+ ```
+
+ Once you have a function, you can call it like this:
+
+ ```elm
+ > makeMeSomeDucks "blue" 12
+ "12 blue rubber ducks" : String
+ ```
+
+ Note: functions help organize code into nice reusable chunks.
+
+ How do you get functions? There are three ways.
+
+ 1. Some functions are always there for you, e.g. `(+)`, `(-)`
+
+ 2. Some functions you can import using code like this:
+
+ ```elm
+ import Svg
+ ```
+
+ and then
+
+ ```
+ Svg.circle [ cx "10", cy "10", r "20" ] [ ]
+ ```
+
+ To call a function that was imported, you have to prefix it with the name of the module (in this example `Svg`).
+
+ 3. Finally, you will write some functions, just like we saw in the `fun` and `makeMeSomeDucks` examples.
+
+
+ Finally there is one special thing: the first line of the program is a module declaration. For now it's enough for us to know, that it has to be there and it has to match the name of the file.
+
+ - Exercise: In our Main.elm, try to identify some values, names and function calls:
=
= Hint: `"darkred"` is a value, `main` is a name, `width` is a function.
=
+ Hint: There is nothing else there now, but we will soon introduce our own functions.
+
= - Where can we use some functions?
=
+ As we said before, functions are good when we have some repetitive operation that can be parametrized (like rubber ducks production).
+
+ Obviously calculating `x` and `y` coordinates is repetitive and can be parametrized (parameters are `radius` and `angle`).
+
= - Apply to our trigonometry calculations
=
= - Compose and copy and paste the trigonometry functions to calculate cx and cy:
=
= ```elm
- (String.fromFloat (sin (degrees 72) * 100))
- (String.fromFloat (cos (degrees 72) * 100))
+ cx (String.fromFloat (cos (degrees 72) * 100))
+ cy (String.fromFloat (sin (degrees 72) * 100))
= ```
=
= - Eliminate repetition:
=
= - define and reuse radius (100)
=
+ Assign a value of `100` to a name `radius`:
+
+ ```
+ radius = 100
+ ```
+
+ and plug it to our functions:
+
+ ```elm
+ cx (String.fromFloat (cos (degrees 72) * radius))
+ cy (String.fromFloat (sin (degrees 72) * radius))
+ ```
+
+ As you see the names are good for repetitive things too.
+
= - define and reuse `x : angle -> cx` and `y : angle -> cy`
=
+
+ ```elm
+ x angle =
+ String.fromFloat (cos (degrees angle) * radius)
+ ```
+
+ ```elm
+ y angle =
+ String.fromFloat (sin (degrees angle) * radius)
+ ```
+
+ Then plug it into the code making circles:
+
+ ```elm
+ circle
+ [ cx (x 72)
+ , cy (y 72)
+ , r "10"
+ , fill "darkred"
+ ]
+ ```
+
= - define and reuse `dot : angle -> color -> Svg`
=
+
+ ```elm
+ dot angle color =
+ circle
+ [ cx (x 72)
+ , cy (y 72)
+ , r "10"
+ , fill color
+ ]
+ ```
+
+ and plug it into our SVG picture:
+
+
+ ```
+ svg [ viewbox "-100 -100 200 200" ]
+ [ dot 0 "darkred"
+ , dot 288 "gold"
+ , dot 72 "fuchsia"
+ , dot 144 "saddlebrown"
+ , dot 216 "deepskyblue"
+ ]
+ ```
+
+ Want some more cool color inspiration. Check out https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#Color_keywords
+
= - Make it more general
=
= - Show List and list operations (map and indexedMap)
Commits: 6
Embed the cartesian coordinates sample in the slide
Making the SVG element fit the slide was difficult. In the end we are relying on setting the width of it's parent to absolute value in pixels.
Co-Authored-By: Sam Phillips samuel.rodney.phillips@gmail.com
index 609bebd..8d5eb8f 100644
--- a/src/CartesianCoordinates.elm
+++ b/src/CartesianCoordinates.elm
@@ -1,4 +1,14 @@
-module CartesianCoordinates exposing (main)
+module CartesianCoordinates exposing
+ ( Flags
+ , Model
+ , Msg
+ , init
+ , main
+ , subscriptions
+ , ui
+ , update
+ , view
+ )
=
=import Browser
=import CartesianPlane exposing (graph)
@@ -44,80 +54,17 @@ init () =
=
=view : Model -> Html.Html Msg
=view model =
- Element.layout
- [ Element.height Element.fill -- (Element.px 600)
- , Element.width Element.fill
- ]
- <|
- Element.column
- [ Element.height Element.fill
- , Element.width <| Element.px 600
- , Element.centerX
- , Element.spacing 30
- , Element.padding 30
- ]
- [ Element.el [ Element.height <| Element.px 400 ] <|
- Element.html <|
- graph
- [ circle
- [ cx <| String.fromFloat model.x
- , cy <| String.fromFloat model.y
- , r "0.01"
- , fill "magenta"
- ]
- []
- , text_
- [ x <| String.fromFloat (model.x + 0.03)
- , y <| String.fromFloat model.y
- , fontSize "0.05"
- , dominantBaseline "central"
- ]
- [ text <| Debug.toString ( model.x, model.y ) ]
- ]
- , Input.slider
- [ Element.behindContent
- (Element.el
- [ Element.width Element.fill
- , Element.height (Element.px 2)
- , Element.centerY
- , Background.color <| Element.rgb 0.7 0.7 0.7
- , Border.rounded 2
- ]
- Element.none
- )
- ]
- { onChange = SetX
- , label =
- Input.labelBelow [ Element.centerX ] <|
- Element.text ("x value: " ++ String.fromFloat model.x)
- , min = -1
- , max = 1
- , value = model.x
- , thumb = Input.defaultThumb
- , step = Just 0.01
- }
- , Input.slider
- [ Element.behindContent
- (Element.el
- [ Element.width Element.fill
- , Element.height (Element.px 2)
- , Element.centerY
- , Background.color <| Element.rgb 0.7 0.7 0.7
- , Border.rounded 2
- ]
- Element.none
- )
- ]
- { onChange = SetY
- , label =
- Input.labelBelow [ Element.centerX ] <|
- Element.text ("y value: " ++ String.fromFloat model.y)
- , min = -1
- , max = 1
- , value = model.y
- , thumb = Input.defaultThumb
- , step = Just 0.01
- }
+ let
+ wrapper element =
+ Element.el
+ [ Element.width (Element.maximum 600 Element.fill), Element.centerX ]
+ element
+ in
+ ui model
+ |> wrapper
+ |> Element.layout
+ [ Element.height Element.fill -- (Element.px 600)
+ , Element.width Element.fill
= ]
=
=
@@ -134,3 +81,79 @@ update msg model =
=subscriptions : Model -> Sub Msg
=subscriptions model =
= Sub.none
+
+
+ui model =
+ Element.column
+ [ Element.width Element.fill
+ , Element.centerX
+ , Element.spacing 30
+ , Element.padding 30
+ ]
+ [ Element.el
+ [ Element.height Element.fill
+ , Element.width Element.fill
+ ]
+ <|
+ Element.html <|
+ graph
+ [ circle
+ [ cx <| String.fromFloat model.x
+ , cy <| String.fromFloat model.y
+ , r "0.01"
+ , fill "magenta"
+ ]
+ []
+ , text_
+ [ x <| String.fromFloat (model.x + 0.03)
+ , y <| String.fromFloat model.y
+ , fontSize "0.05"
+ , dominantBaseline "central"
+ ]
+ [ text <| Debug.toString ( model.x, model.y ) ]
+ ]
+ , Input.slider
+ [ Element.behindContent
+ (Element.el
+ [ Element.width Element.fill
+ , Element.height (Element.px 2)
+ , Element.centerY
+ , Background.color <| Element.rgb 0.7 0.7 0.7
+ , Border.rounded 2
+ ]
+ Element.none
+ )
+ ]
+ { onChange = SetX
+ , label =
+ Input.labelBelow [ Element.centerX ] <|
+ Element.text ("x value: " ++ String.fromFloat model.x)
+ , min = -1
+ , max = 1
+ , value = model.x
+ , thumb = Input.defaultThumb
+ , step = Just 0.01
+ }
+ , Input.slider
+ [ Element.behindContent
+ (Element.el
+ [ Element.width Element.fill
+ , Element.height (Element.px 2)
+ , Element.centerY
+ , Background.color <| Element.rgb 0.7 0.7 0.7
+ , Border.rounded 2
+ ]
+ Element.none
+ )
+ ]
+ { onChange = SetY
+ , label =
+ Input.labelBelow [ Element.centerX ] <|
+ Element.text ("y value: " ++ String.fromFloat model.y)
+ , min = -1
+ , max = 1
+ , value = model.y
+ , thumb = Input.defaultThumb
+ , step = Just 0.01
+ }
+ ]index 394005d..0d82b4a 100644
--- a/src/CartesianPlane.elm
+++ b/src/CartesianPlane.elm
@@ -33,6 +33,8 @@ graph shapes =
= svg
= [ viewBox "-1 -1 2 2"
= , preserveAspectRatio "xMidYMid meet"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.height "100%"
= , Svg.Attributes.style "width: 100%, height: 100%"
= ]
= (background :: shapes)index 0e51aaa..da86dd4 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -2,11 +2,13 @@ module Main exposing (main)
=
=import Browser exposing (Document)
=import Browser.Events
+import CartesianCoordinates
=import Dict
=import Element
=import Element.Font as Font
-import Html exposing (Html)
-import Html.Attributes as Html
+import Element.Keyed
+import Html
+import Html.Attributes
=import Json.Decode as Decode exposing (Decoder)
=import Presentation exposing (Slide, markdown)
=
@@ -26,18 +28,29 @@ type alias Flags =
=
=type alias Model =
= { currentSlide : Int
+
+ -- Nested programs
+ , cartesianCoordinates : CartesianCoordinates.Model
= }
=
=
=type Msg
= = Next
= | Previous
+ -- Nested programs
+ | CartesianCoordinatesMsg CartesianCoordinates.Msg
=
=
=init : Flags -> ( Model, Cmd Msg )
=init flags =
- ( { currentSlide = 0 }
- , Cmd.none
+ let
+ ( cartesianCoordinatesModel, cartesianCoordinatesCmd ) =
+ CartesianCoordinates.init ()
+ in
+ ( { currentSlide = 0
+ , cartesianCoordinates = cartesianCoordinatesModel
+ }
+ , Cmd.batch [ Cmd.map CartesianCoordinatesMsg cartesianCoordinatesCmd ]
= )
=
=
@@ -51,10 +64,19 @@ view model =
= ]
= <|
= Element.column
- [ Element.centerX, Element.centerY, Font.center ]
- [ slides
+ [ Element.centerX
+ , Font.center
+ , Element.height Element.fill
+ , Element.width (Element.maximum 800 Element.fill)
+ ]
+ [ slides model
= |> Dict.get model.currentSlide
- |> Maybe.map (Element.column [ Element.height Element.fill ])
+ |> Maybe.map
+ (Element.column
+ [ Element.width Element.fill
+ , Element.centerY
+ ]
+ )
= |> Maybe.withDefault (Element.text "404: Slide not found")
= ]
= ]
@@ -67,7 +89,7 @@ update msg model =
= Next ->
= ( { model
= | currentSlide =
- min (Dict.size slides - 1) (model.currentSlide + 1)
+ min (Dict.size (slides model) - 1) (model.currentSlide + 1)
= }
= , Cmd.none
= )
@@ -80,6 +102,16 @@ update msg model =
= , Cmd.none
= )
=
+ -- Nested programs
+ CartesianCoordinatesMsg msg_ ->
+ let
+ ( model_, cmd_ ) =
+ CartesianCoordinates.update msg_ model.cartesianCoordinates
+ in
+ ( { model | cartesianCoordinates = model_ }
+ , Cmd.map CartesianCoordinatesMsg cmd_
+ )
+
=
=subscriptions model =
= let
@@ -95,6 +127,12 @@ subscriptions model =
= "ArrowRight" ->
= Decode.succeed Next
=
+ "a" ->
+ Decode.succeed Previous
+
+ "d" ->
+ Decode.succeed Next
+
= _ ->
= Decode.fail "Unsupported key"
= )
@@ -102,7 +140,7 @@ subscriptions model =
= Browser.Events.onKeyPress handleKeyPress
=
=
-slides =
+slides model =
= Dict.fromList <|
= List.indexedMap Tuple.pair <|
= [ markdown """
@@ -111,6 +149,23 @@ slides =
= ### for non-programmers
= """
= ]
+ :: [ markdown """
+ Move sliders to change the `x` and `y` coordinates of the dot. It's like moving a thing on a table by telling how far left, right, up or down should it go.
+ """
+ , model.cartesianCoordinates
+ |> CartesianCoordinates.ui
+ |> Element.map CartesianCoordinatesMsg
+ |> Element.el
+ [ Element.centerX
+ , Element.width (Element.maximum 600 Element.fill)
+ ]
+ ]
+ -- :: [ markdown """
+ -- # FP-Art!
+ -- ## A functional programming workshop
+ -- ### for non-programmers
+ -- """
+ -- ]
= :: [ markdown """
= ## Setup
= """
@@ -157,7 +212,16 @@ slides =
= :: [ markdown """
= You should see a window like this
= """
- , Element.el [ Element.height Element.fill, Element.width Element.fill ] <| Element.html <| Html.img [ Html.class "plain", Html.alt "Mac Terminal app window", Html.src "../assets/mac-terminal-window.png" ] []
+ , Html.img
+ [ Html.Attributes.alt "Mac Terminal app window"
+ , Html.Attributes.src "../assets/mac-terminal-window.png"
+ ]
+ []
+ |> Element.html
+ |> Element.el
+ [ Element.height Element.fill
+ , Element.width Element.fill
+ ]
= , markdown """
= Here is some more markdown.
= """Indent the slides content
Co-Authored-By: Sam Phillips samuel.rodney.phillips@gmail.com
index da86dd4..d00c059 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -147,7 +147,7 @@ slides model =
= # FP-Art!
= ## A functional programming workshop
= ### for non-programmers
- """
+ """
= ]
= :: [ markdown """
= Move sliders to change the `x` and `y` coordinates of the dot. It's like moving a thing on a table by telling how far left, right, up or down should it go.
@@ -167,51 +167,50 @@ slides model =
= -- """
= -- ]
= :: [ markdown """
- ## Setup
- """
+ ## Setup
+ """
= , markdown """
- # Stuff
- """
+ # Stuff
+ """
= ]
= :: [ markdown """
- > This setup instructions are based on an assumption that you are using a Mac.
- >
- > If you are using Linux or BSD, then you probably know how to install stuff.
- >
- > If you are using anything else, then... well, good luck.
- >
- > The rest of the instructions should work with any platform.
-
- """ ]
+ > This setup instructions are based on an assumption that you are using a Mac.
+ >
+ > If you are using Linux or BSD, then you probably know how to install stuff.
+ >
+ > If you are using anything else, then... well, good luck.
+ >
+ > The rest of the instructions should work with any platform.
+
+ """ ]
= :: [ markdown """
=
- We will need:
+ We will need:
=
- - a text editor (I use [Atom][])
- - and [Elm programming language][]
+ - a text editor (I use [Atom][])
+ - and [Elm programming language][]
=
- [Atom]: https://atom.io/
- [Elm programming language]: https://elm-lang.org/
+ [Atom]: https://atom.io/
+ [Elm programming language]: https://elm-lang.org/
=
- """
+ """
= ]
= :: [ markdown """
+ We will use the terminal a little bit.
=
- We will use the terminal a little bit.
+ # :fa-terminal:
=
- # :fa-terminal:
+ Don't be scared. It's easy :)
=
- Don't be scared. It's easy :)
+ <!-- slide data-background-image="images/mac-launchpad-terminal.png" data-background-size="cover" data-background-position="top center"-->
=
- <!-- slide data-background-image="images/mac-launchpad-terminal.png" data-background-size="cover" data-background-position="top center"-->
+ > <p style="color: white">In Launchpad find a program called <code>terminal</code> and start it.</p>
=
- > <p style="color: white">In Launchpad find a program called <code>terminal</code> and start it.</p>
-
- """
+ """
= ]
= :: [ markdown """
- You should see a window like this
- """
+ You should see a window like this
+ """
= , Html.img
= [ Html.Attributes.alt "Mac Terminal app window"
= , Html.Attributes.src "../assets/mac-terminal-window.png"
@@ -223,312 +222,312 @@ slides model =
= , Element.width Element.fill
= ]
= , markdown """
- Here is some more markdown.
- """
+ Here is some more markdown.
+ """
= ]
= :: [ markdown """
=
-Now we are going to install few things.
+ Now we are going to install few things.
=
-- Homebrew (to install other things)
-- Elm programming language
-- Atom editor
+ - Homebrew (to install other things)
+ - Elm programming language
+ - Atom editor
=
-"""
+ """
= ]
= :: [ markdown """
=
-### Install Homebrew
+ ### Install Homebrew
=
-Follow instructions on the [Homebrew website](https://brew.sh/) by typing the following in the terminal (you probably want to copy and paste it):
+ Follow instructions on the [Homebrew website](https://brew.sh/) by typing the following in the terminal (you probably want to copy and paste it):
=
-```sh
-/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
-```
+ ```sh
+ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
+ ```
=
-<small>
-Make sure that the command is exactly like the one above, including the `/` character at the beginning, the quotes and parentheses.
+ <small>
+ Make sure that the command is exactly like the one above, including the `/` character at the beginning, the quotes and parentheses.
=
-It will ask you to confirm several actions and ask for your password. It may take few minutes to finish, so get your coffee
-</small>
+ It will ask you to confirm several actions and ask for your password. It may take few minutes to finish, so get your coffee
+ </small>
=
-:fa-coffee:
+ :fa-coffee:
=
-Once it's done you should see a command prompt like that:
+ Once it's done you should see a command prompt like that:
=
-```
-~ your-name$
-```
+ ```
+ ~ your-name$
+ ```
=
=
-"""
+ """
= ]
= :: [ markdown """
=
-### Install the Elm programming language
+ ### Install the Elm programming language
=
-<small>Once we have Homebrew installed, we can use it to install the language.</small>
+ <small>Once we have Homebrew installed, we can use it to install the language.</small>
=
-Type the following in the terminal.
+ Type the following in the terminal.
=
-```sh
-brew install node elm
-```
+ ```sh
+ brew install node elm
+ ```
=
-and check if it works by typing:
+ and check if it works by typing:
=
-```
-elm repl
-```
+ ```
+ elm repl
+ ```
=
-"""
+ """
= ]
= :: [ markdown """
=
-Note that the command line changes. You should see something like that
+ Note that the command line changes. You should see something like that
=
-```
----- Elm 0.19.0 ----------------------------------------------------------------
-Read <https://elm-lang.org/0.19.0/repl> to learn more: exit, help, imports, etc.
---------------------------------------------------------------------------------
->
-```
+ ```
+ ---- Elm 0.19.0 ----------------------------------------------------------------
+ Read <https://elm-lang.org/0.19.0/repl> to learn more: exit, help, imports, etc.
+ --------------------------------------------------------------------------------
+ >
+ ```
=
-It's the Elm REPL
+ It's the Elm REPL
=
-<small>Read - Evaluate - Print Loop</small>
+ <small>Read - Evaluate - Print Loop</small>
=
-*[REPL]: Read - Evaluate - Print Loop.
+ *[REPL]: Read - Evaluate - Print Loop.
=
-"""
+ """
= ]
= :: [ markdown """
=
-Inside the REPL type
+ Inside the REPL type
=
-```
-2 + 2
-```
+ ```
+ 2 + 2
+ ```
=
-And expect to see
+ And expect to see
=
-```
----- Elm 0.19.0 ----------------------------------------------------------------
-Read <https://elm-lang.org/0.19.0/repl> to learn more: exit, help, imports, etc.
---------------------------------------------------------------------------------
-> 2 + 2
-4 : number
->
-```
+ ```
+ ---- Elm 0.19.0 ----------------------------------------------------------------
+ Read <https://elm-lang.org/0.19.0/repl> to learn more: exit, help, imports, etc.
+ --------------------------------------------------------------------------------
+ > 2 + 2
+ 4 : number
+ >
+ ```
=
-Easy, huh?
+ Easy, huh?
=
-"""
+ """
= ]
= :: [ markdown """
=
-We will learn more about REPL later. For now type `:exit` to close it.
+ We will learn more about REPL later. For now type `:exit` to close it.
=
-The command line should look like before again.
+ The command line should look like before again.
=
-"""
+ """
= ]
= :: [ markdown """
=
-### Install Atom text editor
+ ### Install Atom text editor
=
-Computer programs are represented as text, so the text editor is the most fundamental tool of a programmer. There is a lot of good text editors, but to get you started, we will use [Atom] here.
+ Computer programs are represented as text, so the text editor is the most fundamental tool of a programmer. There is a lot of good text editors, but to get you started, we will use [Atom] here.
=
-"""
+ """
= ]
= :: [ markdown """
=
-Type following in the terminal:
+ Type following in the terminal:
=
-```
-brew cask install atom
-```
+ ```
+ brew cask install atom
+ ```
=
-And start it with:
+ And start it with:
=
-```sh
-atom
-```
+ ```sh
+ atom
+ ```
=
-"""
+ """
= ]
= :: [ markdown """
=
-One last thing we need is Elm support for Atom editor. You can install it by typing in the terminal:
+ One last thing we need is Elm support for Atom editor. You can install it by typing in the terminal:
=
-```
-apm install language-elm
-```
+ ```
+ apm install language-elm
+ ```
=
-<small>APM is the Atom Package Manager, but don't pay much attention to it. We won't be using it again.</small>
+ <small>APM is the Atom Package Manager, but don't pay much attention to it. We won't be using it again.</small>
=
-"""
+ """
= ]
= :: [ markdown """
=
-**We are all set!**
+ **We are all set!**
=
-:smile:
+ :smile:
=
-"""
+ """
= ]
= :: [ markdown """
=
=
-# First program!
+ # First program!
=
-"""
+ """
= ]
= :: [ markdown """
=
-As mentioned before, programs are represented as text (called *the source code*).
+ As mentioned before, programs are represented as text (called *the source code*).
=
-The source code is stored in files
+ The source code is stored in files
=
-:fa-file:
+ :fa-file:
=
-and files are organized in directories
+ and files are organized in directories
=
-:fa-folder:
+ :fa-folder:
=
-"""
+ """
= ]
= :: [ markdown """
=
-So the first step is to create a directory for our new program. Let's call it `fpart`.
+ So the first step is to create a directory for our new program. Let's call it `fpart`.
=
-In the terminal type
+ In the terminal type
=
-```sh
-mkdir fpart/
-```
+ ```sh
+ mkdir fpart/
+ ```
=
-and then
+ and then
=
-```
-cd fpart/
-```
+ ```
+ cd fpart/
+ ```
=
-<small>This creates a new directory and makes it the current one. Again, don't worry about the details.</small>
+ <small>This creates a new directory and makes it the current one. Again, don't worry about the details.</small>
=
-"""
+ """
= ]
= :: [ markdown """
=
-To easily create a new program, we can type
+ To easily create a new program, we can type
=
-```
-elm init
-```
+ ```
+ elm init
+ ```
=
-"""
+ """
= ]
= :: [ markdown """
=
-Then to create a file with source code, type
+ Then to create a file with source code, type
=
-```
-atom src/Main.elm
-```
+ ```
+ atom src/Main.elm
+ ```
=
-<small>This command should open a new Atom window with empty text file.</small>
+ <small>This command should open a new Atom window with empty text file.</small>
=
-"""
+ """
= ]
= :: [ markdown """
=
-### `main.elm`
+ ### `main.elm`
=
-```elm
-module Main exposing (main)
+ ```elm
+ module Main exposing (main)
=
-import Html
+ import Html
=
=
-main =
- Html.text "Hello, Tree!"
-```
+ main =
+ Html.text "Hello, Tree!"
+ ```
=
-<small>Type the above in the editor and save the file</small>
+ <small>Type the above in the editor and save the file</small>
=
-"""
+ """
= ]
= :: [ markdown """
=
-To see the program running type following in the terminal
+ To see the program running type following in the terminal
=
-```sh
-elm reactor
-```
+ ```sh
+ elm reactor
+ ```
=
-<small>This command starts the Elm reactor, which will let you run your program in the web browser. Note that it won't give you the command line back - it will run as long as you don't stop it.</small>
+ <small>This command starts the Elm reactor, which will let you run your program in the web browser. Note that it won't give you the command line back - it will run as long as you don't stop it.</small>
=
-"""
+ """
= ]
= :: [ markdown """
=
-# Voila!
+ # Voila!
=
-<small>Open following address in the web browser</small>
+ <small>Open following address in the web browser</small>
=
-http://localhost:8000/src/Main.elm
+ http://localhost:8000/src/Main.elm
=
-<small>Note that the same address was printed by Elm reactor</small>
+ <small>Note that the same address was printed by Elm reactor</small>
=
-"""
+ """
= ]
= :: [ markdown """
=
-# Let's make a dot!
-# :fa-circle:
+ # Let's make a dot!
+ # :fa-circle:
=
-"""
+ """
= ]
= :: [ markdown """
=
-We are going to use a technology called SVG
+ We are going to use a technology called SVG
=
-<small>Scalable Vector Graphics</small>
+ <small>Scalable Vector Graphics</small>
=
=
-*[SVG]: Scalable Vector Graphics
+ *[SVG]: Scalable Vector Graphics
=
-"""
+ """
= ]
= :: [ markdown """
=
-Let's install an Elm package to help us work with SVG.
+ Let's install an Elm package to help us work with SVG.
=
-Stop the Reactor running in terminal by pressing
+ Stop the Reactor running in terminal by pressing
=
-`CTRL-C`
+ `CTRL-C`
=
-and type the following
+ and type the following
=
-```
-elm install elm/svg
-```
+ ```
+ elm install elm/svg
+ ```
=
-Then start the reactor again
+ Then start the reactor again
=
-```
-elm reactor
-```
+ ```
+ elm reactor
+ ```
=
-<small>you can press up arrow :fa-arrow-up: on the keyboard to get to previous commands</small>
+ <small>you can press up arrow :fa-arrow-up: on the keyboard to get to previous commands</small>
=
-"""
+ """
= ]
= :: [ markdown """
=
-<iframe id="cartesian" data-src="./CartesianCoordinates.html" class="stretch">
-</iframe>
+ <iframe id="cartesian" data-src="./CartesianCoordinates.html" class="stretch">
+ </iframe>
=
-"""
+ """
= ]
= :: []In PolarCoordinates, use CartesianPlane.graph to draw cartesian plane
Remove background colors from PolarCoordinates.view
Co-authored-by: Tadeusz Łazurski tadeusz@lazurski.pl
index fead82e..65f6718 100644
--- a/src/PolarCoordinates.elm
+++ b/src/PolarCoordinates.elm
@@ -1,6 +1,7 @@
=module PolarCoordinates exposing (main)
=
=import Browser
+import CartesianPlane
=import Element
=import Element.Background as Background
=import Element.Border as Border
@@ -50,43 +51,19 @@ view model =
= Element.layout
= [ Element.height Element.fill -- (Element.px 600)
= , Element.width Element.fill
- , Background.color <| Element.rgb 0.6 0 0
= ]
= <|
= Element.column
= [ Element.height Element.fill
= , Element.width <| Element.px 600
- , Background.color <| Element.rgb 0 0.6 0
= , Element.centerX
= , Element.spacing 30
= , Element.padding 30
= ]
= [ Element.el [] <|
= Element.html <|
- svg
- [ viewBox "-1 -1 2 2"
- , preserveAspectRatio "xMidYMid meet"
- , Svg.Attributes.style "width: 100%, height: 100%"
- ]
- [ line
- [ x1 "-1"
- , y1 "0"
- , x2 "1"
- , y2 "0"
- , stroke "black"
- , strokeWidth "0.001"
- ]
- []
- , line
- [ x1 "0"
- , x2 "0"
- , y1 "-1"
- , y2 "1"
- , stroke "black"
- , strokeWidth "0.001"
- ]
- []
- , circle
+ CartesianPlane.graph
+ [ circle
= [ cx <| String.fromFloat point.x
= , cy <| String.fromFloat point.y
= , r "0.01"Merge remote-tracking branch 'origin/elm' into elm
Embed PolarCoordinates sample in slides
Co-authored-by: Tadeusz Łazurski tadeusz@lazurski.pl
index 8d5eb8f..6be4d7b 100644
--- a/src/CartesianCoordinates.elm
+++ b/src/CartesianCoordinates.elm
@@ -63,7 +63,7 @@ view model =
= ui model
= |> wrapper
= |> Element.layout
- [ Element.height Element.fill -- (Element.px 600)
+ [ Element.height Element.fill
= , Element.width Element.fill
= ]
=index d00c059..f40b7a2 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -10,6 +10,7 @@ import Element.Keyed
=import Html
=import Html.Attributes
=import Json.Decode as Decode exposing (Decoder)
+import PolarCoordinates
=import Presentation exposing (Slide, markdown)
=
=
@@ -31,6 +32,7 @@ type alias Model =
=
= -- Nested programs
= , cartesianCoordinates : CartesianCoordinates.Model
+ , polarCoordinates : PolarCoordinates.Model
= }
=
=
@@ -39,6 +41,7 @@ type Msg
= | Previous
= -- Nested programs
= | CartesianCoordinatesMsg CartesianCoordinates.Msg
+ | PolarCoordinatesMsg PolarCoordinates.Msg
=
=
=init : Flags -> ( Model, Cmd Msg )
@@ -46,11 +49,18 @@ init flags =
= let
= ( cartesianCoordinatesModel, cartesianCoordinatesCmd ) =
= CartesianCoordinates.init ()
+
+ ( polarCoordinatesModel, polarCoordinatesCmd ) =
+ PolarCoordinates.init ()
= in
= ( { currentSlide = 0
= , cartesianCoordinates = cartesianCoordinatesModel
+ , polarCoordinates = polarCoordinatesModel
= }
- , Cmd.batch [ Cmd.map CartesianCoordinatesMsg cartesianCoordinatesCmd ]
+ , Cmd.batch
+ [ Cmd.map CartesianCoordinatesMsg cartesianCoordinatesCmd
+ , Cmd.map PolarCoordinatesMsg polarCoordinatesCmd
+ ]
= )
=
=
@@ -112,6 +122,15 @@ update msg model =
= , Cmd.map CartesianCoordinatesMsg cmd_
= )
=
+ PolarCoordinatesMsg msg_ ->
+ let
+ ( model_, cmd_ ) =
+ PolarCoordinates.update msg_ model.polarCoordinates
+ in
+ ( { model | polarCoordinates = model_ }
+ , Cmd.map PolarCoordinatesMsg cmd_
+ )
+
=
=subscriptions model =
= let
@@ -150,8 +169,8 @@ slides model =
= """
= ]
= :: [ markdown """
- Move sliders to change the `x` and `y` coordinates of the dot. It's like moving a thing on a table by telling how far left, right, up or down should it go.
- """
+ Move sliders to change the `x` and `y` coordinates of the dot. It's like moving a thing on a table by telling how far left, right, up or down should it go.
+ """
= , model.cartesianCoordinates
= |> CartesianCoordinates.ui
= |> Element.map CartesianCoordinatesMsg
@@ -160,17 +179,25 @@ slides model =
= , Element.width (Element.maximum 600 Element.fill)
= ]
= ]
- -- :: [ markdown """
- -- # FP-Art!
- -- ## A functional programming workshop
- -- ### for non-programmers
- -- """
- -- ]
= :: [ markdown """
- ## Setup
+ Move sliders to change the `angle` and `length` properties of the line connecting the dot and origin. The x and y coordinates will be calculated like this:
+
+ ```
+ x = cos(angle) * length
+
+ y = sin(angle) * length
+ ```
= """
- , markdown """
- # Stuff
+ , model.polarCoordinates
+ |> PolarCoordinates.ui
+ |> Element.map PolarCoordinatesMsg
+ |> Element.el
+ [ Element.centerX
+ , Element.width (Element.maximum 600 Element.fill)
+ ]
+ ]
+ :: [ markdown """
+ ## Setup
= """
= ]
= :: [ markdown """index 65f6718..7a0d79c 100644
--- a/src/PolarCoordinates.elm
+++ b/src/PolarCoordinates.elm
@@ -1,8 +1,18 @@
-module PolarCoordinates exposing (main)
+module PolarCoordinates exposing
+ ( Flags
+ , Model
+ , Msg
+ , init
+ , main
+ , subscriptions
+ , ui
+ , update
+ , view
+ )
=
=import Browser
=import CartesianPlane
-import Element
+import Element exposing (Element)
=import Element.Background as Background
=import Element.Border as Border
=import Element.Input as Input
@@ -44,95 +54,109 @@ init () =
=
=view : Model -> Html.Html Msg
=view model =
+ let
+ wrapper element =
+ Element.el
+ [ Element.width (Element.maximum 600 Element.fill), Element.centerX ]
+ element
+ in
+ ui model
+ |> wrapper
+ |> Element.layout
+ [ Element.height Element.fill
+ , Element.width Element.fill
+ ]
+
+
+ui : Model -> Element Msg
+ui model =
= let
= point =
= cartesian model
= in
- Element.layout
- [ Element.height Element.fill -- (Element.px 600)
- , Element.width Element.fill
+ Element.column
+ [ Element.width Element.fill
+ , Element.centerX
+ , Element.spacing 30
+ , Element.padding 30
= ]
- <|
- Element.column
+ [ Element.el
= [ Element.height Element.fill
- , Element.width <| Element.px 600
- , Element.centerX
- , Element.spacing 30
- , Element.padding 30
+ , Element.width Element.fill
= ]
- [ Element.el [] <|
- Element.html <|
- CartesianPlane.graph
- [ circle
- [ cx <| String.fromFloat point.x
- , cy <| String.fromFloat point.y
- , r "0.01"
- , fill "magenta"
- ]
- []
- , line
- [ x1 "0"
- , x2 <| String.fromFloat point.x
- , y1 "0"
- , y2 <| String.fromFloat point.y
- , stroke "magenta"
- , strokeWidth "0.001"
- ]
- []
- , text_
- [ x <| String.fromFloat (point.x + 0.02)
- , y <| String.fromFloat (point.y + 0.01)
- , Svg.Attributes.fontSize "0.03pt"
- ]
- [ text <|
- Debug.toString ( point.x, point.y )
- ]
+ <|
+ Element.html <|
+ CartesianPlane.graph
+ [ circle
+ [ cx <| String.fromFloat point.x
+ , cy <| String.fromFloat point.y
+ , r "0.01"
+ , fill "magenta"
+ ]
+ []
+ , line
+ [ x1 "0"
+ , x2 <| String.fromFloat point.x
+ , y1 "0"
+ , y2 <| String.fromFloat point.y
+ , stroke "magenta"
+ , strokeWidth "0.001"
= ]
- , Input.slider
- [ Element.behindContent
- (Element.el
- [ Element.width Element.fill
- , Element.height (Element.px 2)
- , Element.centerY
- , Background.color <| Element.rgb 0.7 0.7 0.7
- , Border.rounded 2
+ []
+ , text_
+ [ x <| String.fromFloat (point.x + 0.02)
+ , y <| String.fromFloat (point.y + 0.01)
+ , Svg.Attributes.fontSize "0.03pt"
= ]
- Element.none
- )
- ]
- { onChange = SetAngle
- , label =
- Input.labelBelow [ Element.centerX ] <|
- Element.text ("angle value: " ++ String.fromFloat model.angle)
- , min = 0
- , max = 360
- , value = model.angle
- , thumb = Input.defaultThumb
- , step = Just 1
- }
- , Input.slider
- [ Element.behindContent
- (Element.el
- [ Element.width Element.fill
- , Element.height (Element.px 2)
- , Element.centerY
- , Background.color <| Element.rgb 0.7 0.7 0.7
- , Border.rounded 2
+ [ text <|
+ Debug.toString ( point.x, point.y )
= ]
- Element.none
- )
- ]
- { onChange = SetRadius
- , label =
- Input.labelBelow [ Element.centerX ] <|
- Element.text ("radius value: " ++ String.fromFloat model.radius)
- , min = 0
- , max = 1
- , value = model.radius
- , thumb = Input.defaultThumb
- , step = Just 0.01
- }
+ ]
+ , Input.slider
+ [ Element.behindContent
+ (Element.el
+ [ Element.width Element.fill
+ , Element.height (Element.px 2)
+ , Element.centerY
+ , Background.color <| Element.rgb 0.7 0.7 0.7
+ , Border.rounded 2
+ ]
+ Element.none
+ )
= ]
+ { onChange = SetAngle
+ , label =
+ Input.labelBelow [ Element.centerX ] <|
+ Element.text ("angle value: " ++ String.fromFloat model.angle)
+ , min = 0
+ , max = 360
+ , value = model.angle
+ , thumb = Input.defaultThumb
+ , step = Just 1
+ }
+ , Input.slider
+ [ Element.behindContent
+ (Element.el
+ [ Element.width Element.fill
+ , Element.height (Element.px 2)
+ , Element.centerY
+ , Background.color <| Element.rgb 0.7 0.7 0.7
+ , Border.rounded 2
+ ]
+ Element.none
+ )
+ ]
+ { onChange = SetRadius
+ , label =
+ Input.labelBelow [ Element.centerX ] <|
+ Element.text ("radius value: " ++ String.fromFloat model.radius)
+ , min = 0
+ , max = 1
+ , value = model.radius
+ , thumb = Input.defaultThumb
+ , step = Just 0.01
+ }
+ ]
=
=
=update : Msg -> Model -> ( Model, Cmd Msg )Create content for slides.
Co-authored-by: Tadeusz Łazurski tadeusz@lazurski.pl
index f40b7a2..f18bc73 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -163,39 +163,11 @@ slides model =
= Dict.fromList <|
= List.indexedMap Tuple.pair <|
= [ markdown """
- # FP-Art!
+ # Software Garden
= ## A functional programming workshop
= ### for non-programmers
= """
= ]
- :: [ markdown """
- Move sliders to change the `x` and `y` coordinates of the dot. It's like moving a thing on a table by telling how far left, right, up or down should it go.
- """
- , model.cartesianCoordinates
- |> CartesianCoordinates.ui
- |> Element.map CartesianCoordinatesMsg
- |> Element.el
- [ Element.centerX
- , Element.width (Element.maximum 600 Element.fill)
- ]
- ]
- :: [ markdown """
- Move sliders to change the `angle` and `length` properties of the line connecting the dot and origin. The x and y coordinates will be calculated like this:
-
- ```
- x = cos(angle) * length
-
- y = sin(angle) * length
- ```
- """
- , model.polarCoordinates
- |> PolarCoordinates.ui
- |> Element.map PolarCoordinatesMsg
- |> Element.el
- [ Element.centerX
- , Element.width (Element.maximum 600 Element.fill)
- ]
- ]
= :: [ markdown """
= ## Setup
= """
@@ -215,7 +187,7 @@ slides model =
= We will need:
=
= - a text editor (I use [Atom][])
- - and [Elm programming language][]
+ - and the [Elm programming language][]
=
= [Atom]: https://atom.io/
= [Elm programming language]: https://elm-lang.org/
@@ -223,7 +195,25 @@ slides model =
= """
= ]
= :: [ markdown """
- We will use the terminal a little bit.
+ ### Elm Installation
+ """
+ ]
+ :: [ markdown """
+ To install the Elm programming language, go to the [website][Elm] and follow the installation instructions.
+
+ [Elm]: http://elm-lang.org/
+ """
+ ]
+ :: [ markdown """
+ Some of the tools we use with Elm require Node.js.
+
+ Go to the [Node.js website][Node.js] and install the current version (the green button on the right).
+
+ [Node.js]: https://nodejs.org/en/
+ """
+ ]
+ :: [ markdown """
+ We will need to use the terminal a little bit.
=
= # :fa-terminal:
=
@@ -557,4 +547,32 @@ slides model =
=
= """
= ]
+ :: [ markdown """
+ Move sliders to change the `x` and `y` coordinates of the dot. It's like moving a thing on a table by telling how far left, right, up or down should it go.
+ """
+ , model.cartesianCoordinates
+ |> CartesianCoordinates.ui
+ |> Element.map CartesianCoordinatesMsg
+ |> Element.el
+ [ Element.centerX
+ , Element.width (Element.maximum 600 Element.fill)
+ ]
+ ]
+ :: [ markdown """
+ Move sliders to change the `angle` and `length` properties of the line connecting the dot and origin. The x and y coordinates will be calculated like this:
+
+ ```
+ x = cos(angle) * length
+
+ y = sin(angle) * length
+ ```
+ """
+ , model.polarCoordinates
+ |> PolarCoordinates.ui
+ |> Element.map PolarCoordinatesMsg
+ |> Element.el
+ [ Element.centerX
+ , Element.width (Element.maximum 600 Element.fill)
+ ]
+ ]
= :: []
Commits: 4
Gitignore .swp files
index 9114974..0c19f1d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
=/elm-stuff
=/**/.DS_Store
+*.swpBegin reconstructing the 'fpart' presentation as an Elm program
We can render markdown and html elements
Next todo: scaling images to fit on slide
index 988c3a5..67b87e4 100644
--- a/elm.json
+++ b/elm.json
@@ -10,6 +10,8 @@
= "elm/core": "1.0.0",
= "elm/html": "1.0.0",
= "elm/svg": "1.0.1",
+ "elm-community/list-extra": "8.1.0",
+ "elm-explorations/markdown": "1.0.0",
= "ianmackenzie/elm-geometry": "1.2.1",
= "ianmackenzie/elm-geometry-svg": "1.0.2",
= "mdgriffith/elm-ui": "1.1.0"new file mode 100644
index 0000000..88cb6e2
--- /dev/null
+++ b/src/Main.elm
@@ -0,0 +1,400 @@
+module Main exposing (main)
+
+import Dict
+import Element
+import Html exposing (Html)
+import Html.Attributes as Html
+import Presentation exposing (Slide, markdown)
+
+
+currentSlide =
+ 5
+
+
+main =
+ let
+ slide =
+ slides
+ |> Dict.get currentSlide
+
+ notFound =
+ Element.text "404: Slide not found"
+ in
+ Element.layout [ Element.width Element.fill, Element.height Element.fill ] <|
+ Element.row
+ [ Element.centerX, Element.centerY ]
+ [ slide
+ |> Maybe.map (Element.column [])
+ |> Maybe.withDefault notFound
+ ]
+
+
+slides =
+ Dict.fromList <|
+ List.indexedMap Tuple.pair <|
+ [ markdown """
+ # FP-Art!
+ ## A functional programming workshop
+ ### for non-programmers
+ """
+ , markdown """
+ # Stuff
+ """
+ ]
+ :: [ markdown """
+ ## Setup
+ """
+ , markdown """
+ # Stuff
+ """
+ ]
+ :: [ markdown """
+ > This setup instructions are based on an assumption that you are using a Mac.
+ >
+ > If you are using Linux or BSD, then you probably know how to install stuff.
+ >
+ > If you are using anything else, then... well, good luck.
+ >
+ > The rest of the instructions should work with any platform.
+
+ """ ]
+ :: [ markdown """
+
+ We will need:
+
+ - a text editor (I use [Atom][])
+ - and [Elm programming language][]
+
+ [Atom]: https://atom.io/
+ [Elm programming language]: https://elm-lang.org/
+
+ """
+ ]
+ :: [ markdown """
+
+ We will use the terminal a little bit.
+
+ # :fa-terminal:
+
+ Don't be scared. It's easy :)
+
+ <!-- slide data-background-image="images/mac-launchpad-terminal.png" data-background-size="cover" data-background-position="top center"-->
+
+ > <p style="color: white">In Launchpad find a program called <code>terminal</code> and start it.</p>
+
+ """
+ ]
+ :: [ markdown """
+ You should see a window like this
+ """
+ , Element.el [ Element.height Element.fill, Element.width Element.fill ] <| Element.html <| Html.img [ Html.class "plain", Html.alt "Mac Terminal app window", Html.src "../assets/mac-terminal-window.png" ] []
+ , markdown """
+ Here is some more markdown.
+ """
+ ]
+ :: [ markdown """
+
+Now we are going to install few things.
+
+- Homebrew (to install other things)
+- Elm programming language
+- Atom editor
+
+"""
+ ]
+ :: [ markdown """
+
+### Install Homebrew
+
+Follow instructions on the [Homebrew website](https://brew.sh/) by typing the following in the terminal (you probably want to copy and paste it):
+
+```sh
+/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
+```
+
+<small>
+Make sure that the command is exactly like the one above, including the `/` character at the beginning, the quotes and parentheses.
+
+It will ask you to confirm several actions and ask for your password. It may take few minutes to finish, so get your coffee
+</small>
+
+:fa-coffee:
+
+Once it's done you should see a command prompt like that:
+
+```
+~ your-name$
+```
+
+
+"""
+ ]
+ :: [ markdown """
+
+### Install the Elm programming language
+
+<small>Once we have Homebrew installed, we can use it to install the language.</small>
+
+Type the following in the terminal.
+
+```sh
+brew install node elm
+```
+
+and check if it works by typing:
+
+```
+elm repl
+```
+
+"""
+ ]
+ :: [ markdown """
+
+Note that the command line changes. You should see something like that
+
+```
+---- Elm 0.19.0 ----------------------------------------------------------------
+Read <https://elm-lang.org/0.19.0/repl> to learn more: exit, help, imports, etc.
+--------------------------------------------------------------------------------
+>
+```
+
+It's the Elm REPL
+
+<small>Read - Evaluate - Print Loop</small>
+
+*[REPL]: Read - Evaluate - Print Loop.
+
+"""
+ ]
+ :: [ markdown """
+
+Inside the REPL type
+
+```
+2 + 2
+```
+
+And expect to see
+
+```
+---- Elm 0.19.0 ----------------------------------------------------------------
+Read <https://elm-lang.org/0.19.0/repl> to learn more: exit, help, imports, etc.
+--------------------------------------------------------------------------------
+> 2 + 2
+4 : number
+>
+```
+
+Easy, huh?
+
+"""
+ ]
+ :: [ markdown """
+
+We will learn more about REPL later. For now type `:exit` to close it.
+
+The command line should look like before again.
+
+"""
+ ]
+ :: [ markdown """
+
+### Install Atom text editor
+
+Computer programs are represented as text, so the text editor is the most fundamental tool of a programmer. There is a lot of good text editors, but to get you started, we will use [Atom] here.
+
+"""
+ ]
+ :: [ markdown """
+
+Type following in the terminal:
+
+```
+brew cask install atom
+```
+
+And start it with:
+
+```sh
+atom
+```
+
+"""
+ ]
+ :: [ markdown """
+
+One last thing we need is Elm support for Atom editor. You can install it by typing in the terminal:
+
+```
+apm install language-elm
+```
+
+<small>APM is the Atom Package Manager, but don't pay much attention to it. We won't be using it again.</small>
+
+"""
+ ]
+ :: [ markdown """
+
+**We are all set!**
+
+:smile:
+
+"""
+ ]
+ :: [ markdown """
+
+
+# First program!
+
+"""
+ ]
+ :: [ markdown """
+
+As mentioned before, programs are represented as text (called *the source code*).
+
+The source code is stored in files
+
+:fa-file:
+
+and files are organized in directories
+
+:fa-folder:
+
+"""
+ ]
+ :: [ markdown """
+
+So the first step is to create a directory for our new program. Let's call it `fpart`.
+
+In the terminal type
+
+```sh
+mkdir fpart/
+```
+
+and then
+
+```
+cd fpart/
+```
+
+<small>This creates a new directory and makes it the current one. Again, don't worry about the details.</small>
+
+"""
+ ]
+ :: [ markdown """
+
+To easily create a new program, we can type
+
+```
+elm init
+```
+
+"""
+ ]
+ :: [ markdown """
+
+Then to create a file with source code, type
+
+```
+atom src/Main.elm
+```
+
+<small>This command should open a new Atom window with empty text file.</small>
+
+"""
+ ]
+ :: [ markdown """
+
+### `main.elm`
+
+```elm
+module Main exposing (main)
+
+import Html
+
+
+main =
+ Html.text "Hello, Tree!"
+```
+
+<small>Type the above in the editor and save the file</small>
+
+"""
+ ]
+ :: [ markdown """
+
+To see the program running type following in the terminal
+
+```sh
+elm reactor
+```
+
+<small>This command starts the Elm reactor, which will let you run your program in the web browser. Note that it won't give you the command line back - it will run as long as you don't stop it.</small>
+
+"""
+ ]
+ :: [ markdown """
+
+# Voila!
+
+<small>Open following address in the web browser</small>
+
+http://localhost:8000/src/Main.elm
+
+<small>Note that the same address was printed by Elm reactor</small>
+
+"""
+ ]
+ :: [ markdown """
+
+# Let's make a dot!
+# :fa-circle:
+
+"""
+ ]
+ :: [ markdown """
+
+We are going to use a technology called SVG
+
+<small>Scalable Vector Graphics</small>
+
+
+*[SVG]: Scalable Vector Graphics
+
+"""
+ ]
+ :: [ markdown """
+
+Let's install an Elm package to help us work with SVG.
+
+Stop the Reactor running in terminal by pressing
+
+`CTRL-C`
+
+and type the following
+
+```
+elm install elm/svg
+```
+
+Then start the reactor again
+
+```
+elm reactor
+```
+
+<small>you can press up arrow :fa-arrow-up: on the keyboard to get to previous commands</small>
+
+"""
+ ]
+ :: [ markdown """
+
+<iframe id="cartesian" data-src="./CartesianCoordinates.html" class="stretch">
+</iframe>
+
+"""
+ ]
+ :: []new file mode 100644
index 0000000..c017bb5
--- /dev/null
+++ b/src/Presentation.elm
@@ -0,0 +1,41 @@
+module Presentation exposing (Slide, markdown)
+
+import Element exposing (Element)
+import List.Extra as List
+import Markdown
+
+
+type alias Slide msg =
+ List (Element msg)
+
+
+markdown : String -> Element msg
+markdown string =
+ let
+ dedentedString =
+ string
+ |> String.lines
+ |> List.map dedent
+ |> String.join "\n"
+
+ dedent line =
+ line
+ |> String.toList
+ |> List.indexedMap Tuple.pair
+ |> List.dropWhile (\( index, character ) -> index < indentation && character == ' ')
+ |> List.map Tuple.second
+ |> String.fromList
+
+ indentation =
+ string
+ |> String.lines
+ |> List.filter (\line -> String.trim line /= "")
+ |> List.head
+ |> Maybe.withDefault ""
+ |> String.toList
+ |> List.findIndex ((/=) ' ')
+ |> Maybe.withDefault 0
+ in
+ Markdown.toHtml [] dedentedString
+ |> Element.html
+ |> Element.el [ Element.centerX ]Evolve program to Browser.sandobx, center text on slides
index 88cb6e2..50a8c46 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -1,45 +1,71 @@
=module Main exposing (main)
=
+import Browser
=import Dict
=import Element
+import Element.Font as Font
=import Html exposing (Html)
=import Html.Attributes as Html
=import Presentation exposing (Slide, markdown)
=
=
-currentSlide =
- 5
+main =
+ Browser.sandbox
+ { init = init
+ , view = view
+ , update = update
+ }
=
=
-main =
- let
- slide =
- slides
- |> Dict.get currentSlide
-
- notFound =
- Element.text "404: Slide not found"
- in
- Element.layout [ Element.width Element.fill, Element.height Element.fill ] <|
+type alias Model =
+ { currentSlide : Int
+ }
+
+
+type Msg
+ = Next
+ | Previous
+
+
+init : Model
+init =
+ { currentSlide = 0 }
+
+
+view : Model -> Html Msg
+view model =
+ Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+ <|
= Element.row
- [ Element.centerX, Element.centerY ]
- [ slide
+ [ Element.centerX, Element.centerY, Font.center ]
+ [ slides
+ |> Dict.get model.currentSlide
= |> Maybe.map (Element.column [])
- |> Maybe.withDefault notFound
+ |> Maybe.withDefault (Element.text "404: Slide not found")
= ]
=
=
+update : Msg -> Model -> Model
+update msg model =
+ case msg of
+ Next ->
+ { model | currentSlide = model.currentSlide + 1 }
+
+ Previous ->
+ { model | currentSlide = model.currentSlide - 1 }
+
+
=slides =
= Dict.fromList <|
= List.indexedMap Tuple.pair <|
= [ markdown """
- # FP-Art!
- ## A functional programming workshop
- ### for non-programmers
- """
- , markdown """
- # Stuff
- """
+ # FP-Art!
+ ## A functional programming workshop
+ ### for non-programmers
+ """
= ]
= :: [ markdown """
= ## SetupImplement keyboard navigation
index 67b87e4..d17943a 100644
--- a/elm.json
+++ b/elm.json
@@ -9,6 +9,7 @@
= "elm/browser": "1.0.0",
= "elm/core": "1.0.0",
= "elm/html": "1.0.0",
+ "elm/json": "1.0.0",
= "elm/svg": "1.0.1",
= "elm-community/list-extra": "8.1.0",
= "elm-explorations/markdown": "1.0.0",
@@ -17,7 +18,6 @@
= "mdgriffith/elm-ui": "1.1.0"
= },
= "indirect": {
- "elm/json": "1.0.0",
= "elm/time": "1.0.0",
= "elm/url": "1.0.0",
= "elm/virtual-dom": "1.0.2",index 50a8c46..0e51aaa 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -1,22 +1,29 @@
=module Main exposing (main)
=
-import Browser
+import Browser exposing (Document)
+import Browser.Events
=import Dict
=import Element
=import Element.Font as Font
=import Html exposing (Html)
=import Html.Attributes as Html
+import Json.Decode as Decode exposing (Decoder)
=import Presentation exposing (Slide, markdown)
=
=
=main =
- Browser.sandbox
+ Browser.document
= { init = init
= , view = view
= , update = update
+ , subscriptions = subscriptions
= }
=
=
+type alias Flags =
+ ()
+
+
=type alias Model =
= { currentSlide : Int
= }
@@ -27,35 +34,72 @@ type Msg
= | Previous
=
=
-init : Model
-init =
- { currentSlide = 0 }
+init : Flags -> ( Model, Cmd Msg )
+init flags =
+ ( { currentSlide = 0 }
+ , Cmd.none
+ )
=
=
-view : Model -> Html Msg
+view : Model -> Document Msg
=view model =
- Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
- ]
- <|
- Element.row
- [ Element.centerX, Element.centerY, Font.center ]
- [ slides
- |> Dict.get model.currentSlide
- |> Maybe.map (Element.column [])
- |> Maybe.withDefault (Element.text "404: Slide not found")
+ { title = "FP-Art!"
+ , body =
+ [ Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
= ]
+ <|
+ Element.column
+ [ Element.centerX, Element.centerY, Font.center ]
+ [ slides
+ |> Dict.get model.currentSlide
+ |> Maybe.map (Element.column [ Element.height Element.fill ])
+ |> Maybe.withDefault (Element.text "404: Slide not found")
+ ]
+ ]
+ }
=
=
-update : Msg -> Model -> Model
+update : Msg -> Model -> ( Model, Cmd Msg )
=update msg model =
= case msg of
= Next ->
- { model | currentSlide = model.currentSlide + 1 }
+ ( { model
+ | currentSlide =
+ min (Dict.size slides - 1) (model.currentSlide + 1)
+ }
+ , Cmd.none
+ )
=
= Previous ->
- { model | currentSlide = model.currentSlide - 1 }
+ ( { model
+ | currentSlide =
+ max 0 (model.currentSlide - 1)
+ }
+ , Cmd.none
+ )
+
+
+subscriptions model =
+ let
+ handleKeyPress : Decoder Msg
+ handleKeyPress =
+ Decode.field "key" Decode.string
+ |> Decode.andThen
+ (\key ->
+ case Debug.log "Key" key of
+ "ArrowLeft" ->
+ Decode.succeed Previous
+
+ "ArrowRight" ->
+ Decode.succeed Next
+
+ _ ->
+ Decode.fail "Unsupported key"
+ )
+ in
+ Browser.Events.onKeyPress handleKeyPress
=
=
=slides =
Commits: 1
Extract workshop slides and samples from fpart-wiki
new file mode 100644
index 0000000..9114974
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+/elm-stuff
+/**/.DS_Storenew file mode 100644
index 0000000..a115152
Binary files /dev/null and b/assets/Elm MVU architecture.jpg differnew file mode 100644
index 0000000..a975a29
Binary files /dev/null and b/assets/mac-launchpad-terminal.png differnew file mode 100644
index 0000000..5bc5df1
Binary files /dev/null and b/assets/mac-terminal-window.png differnew file mode 100644
index 0000000..988c3a5
--- /dev/null
+++ b/elm.json
@@ -0,0 +1,31 @@
+{
+ "type": "application",
+ "source-directories": [
+ "src"
+ ],
+ "elm-version": "0.19.0",
+ "dependencies": {
+ "direct": {
+ "elm/browser": "1.0.0",
+ "elm/core": "1.0.0",
+ "elm/html": "1.0.0",
+ "elm/svg": "1.0.1",
+ "ianmackenzie/elm-geometry": "1.2.1",
+ "ianmackenzie/elm-geometry-svg": "1.0.2",
+ "mdgriffith/elm-ui": "1.1.0"
+ },
+ "indirect": {
+ "elm/json": "1.0.0",
+ "elm/time": "1.0.0",
+ "elm/url": "1.0.0",
+ "elm/virtual-dom": "1.0.2",
+ "ianmackenzie/elm-float-extra": "1.0.1",
+ "ianmackenzie/elm-interval": "1.0.1",
+ "ianmackenzie/elm-triangular-mesh": "1.0.2"
+ }
+ },
+ "test-dependencies": {
+ "direct": {},
+ "indirect": {}
+ }
+}
\ No newline at end of filenew file mode 100644
index 0000000..a2ea019
--- /dev/null
+++ b/index.md
@@ -0,0 +1,314 @@
+---
+presentation:
+ enableSpeakerNotes: true
+ theme: solarized.css
+---
+
+<!-- slide -->
+
+# FP-Art!
+## A functional programming workshop
+### for non-programmers
+
+<!-- slide -->
+
+## Setup
+
+<!-- slide -->
+
+> This setup instructions are based on an assumption that you are using a Mac.
+>
+> If you are using Linux or BSD, then you probably know how to install stuff.
+>
+> If you are using anything else, then... well, good luck.
+>
+> The rest of the instructions should work with any platform.
+
+<!-- slide -->
+
+We will need:
+
+- a text editor (I use [Atom][])
+- and [Elm programming language][]
+
+[Atom]: https://atom.io/
+[Elm programming language]: https://elm-lang.org/
+
+<!-- slide -->
+
+We will use the terminal a little bit.
+
+# :fa-terminal:
+
+Don't be scared. It's easy :)
+
+<!-- slide data-background-image="images/mac-launchpad-terminal.png" data-background-size="cover" data-background-position="top center"-->
+
+> <p style="color: white">In Launchpad find a program called <code>terminal</code> and start it.</p>
+
+<!-- slide -->
+
+You should see a window like this
+
+<img class="plain" alt="Mac Terminal app window" src="images/mac-terminal-window.png" />
+
+<!-- slide -->
+
+Now we are going to install few things.
+
+- Homebrew (to install other things)
+- Elm programming language
+- Atom editor
+
+<!-- slide -->
+
+### Install Homebrew
+
+Follow instructions on the [Homebrew website](https://brew.sh/) by typing the following in the terminal (you probably want to copy and paste it):
+
+```sh
+/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
+```
+
+<small>
+Make sure that the command is exactly like the one above, including the `/` character at the beginning, the quotes and parentheses.
+
+It will ask you to confirm several actions and ask for your password. It may take few minutes to finish, so get your coffee
+</small>
+
+:fa-coffee:
+
+Once it's done you should see a command prompt like that:
+
+```
+~ your-name$
+```
+
+
+<!-- slide -->
+
+### Install the Elm programming language
+
+<small>Once we have Homebrew installed, we can use it to install the language.</small>
+
+Type the following in the terminal.
+
+```sh
+brew install node elm
+```
+
+and check if it works by typing:
+
+```
+elm repl
+```
+
+<!-- slide -->
+
+Note that the command line changes. You should see something like that
+
+```
+---- Elm 0.19.0 ----------------------------------------------------------------
+Read <https://elm-lang.org/0.19.0/repl> to learn more: exit, help, imports, etc.
+--------------------------------------------------------------------------------
+>
+```
+
+It's the Elm REPL
+
+<small>Read - Evaluate - Print Loop</small>
+
+*[REPL]: Read - Evaluate - Print Loop.
+
+<!-- slide -->
+
+Inside the REPL type
+
+```
+2 + 2
+```
+
+And expect to see
+
+```
+---- Elm 0.19.0 ----------------------------------------------------------------
+Read <https://elm-lang.org/0.19.0/repl> to learn more: exit, help, imports, etc.
+--------------------------------------------------------------------------------
+> 2 + 2
+4 : number
+>
+```
+
+Easy, huh?
+
+<!-- slide -->
+
+We will learn more about REPL later. For now type `:exit` to close it.
+
+The command line should look like before again.
+
+<!-- slide -->
+
+### Install Atom text editor
+
+Computer programs are represented as text, so the text editor is the most fundamental tool of a programmer. There is a lot of good text editors, but to get you started, we will use [Atom] here.
+
+<!-- slide -->
+
+Type following in the terminal:
+
+```
+brew cask install atom
+```
+
+And start it with:
+
+```sh
+atom
+```
+
+<!-- slide -->
+
+One last thing we need is Elm support for Atom editor. You can install it by typing in the terminal:
+
+```
+apm install language-elm
+```
+
+<small>APM is the Atom Package Manager, but don't pay much attention to it. We won't be using it again.</small>
+
+<!-- slide -->
+
+**We are all set!**
+
+:smile:
+
+<!-- slide -->
+
+
+# First program!
+
+<!-- slide -->
+
+As mentioned before, programs are represented as text (called *the source code*).
+
+The source code is stored in files
+
+:fa-file:
+
+and files are organized in directories
+
+:fa-folder:
+
+<!-- slide -->
+
+So the first step is to create a directory for our new program. Let's call it `fpart`.
+
+In the terminal type
+
+```sh
+mkdir fpart/
+```
+
+and then
+
+```
+cd fpart/
+```
+
+<small>This creates a new directory and makes it the current one. Again, don't worry about the details.</small>
+
+<!-- slide -->
+
+To easily create a new program, we can type
+
+```
+elm init
+```
+
+<!-- slide -->
+
+Then to create a file with source code, type
+
+```
+atom src/Main.elm
+```
+
+<small>This command should open a new Atom window with empty text file.</small>
+
+<!-- slide data-transition=zoom -->
+
+### `main.elm`
+
+```elm
+module Main exposing (main)
+
+import Html
+
+
+main =
+ Html.text "Hello, Tree!"
+```
+
+<small>Type the above in the editor and save the file</small>
+
+<!-- slide -->
+
+To see the program running type following in the terminal
+
+```sh
+elm reactor
+```
+
+<small>This command starts the Elm reactor, which will let you run your program in the web browser. Note that it won't give you the command line back - it will run as long as you don't stop it.</small>
+
+<!-- slide -->
+
+# Voila!
+
+<small>Open following address in the web browser</small>
+
+http://localhost:8000/src/Main.elm
+
+<small>Note that the same address was printed by Elm reactor</small>
+
+<!-- slide -->
+
+# Let's make a dot!
+# :fa-circle:
+
+<!-- slide -->
+
+We are going to use a technology called SVG
+
+<small>Scalable Vector Graphics</small>
+
+
+*[SVG]: Scalable Vector Graphics
+
+<!-- slide -->
+
+Let's install an Elm package to help us work with SVG.
+
+Stop the Reactor running in terminal by pressing
+
+`CTRL-C`
+
+and type the following
+
+```
+elm install elm/svg
+```
+
+Then start the reactor again
+
+```
+elm reactor
+```
+
+<small>you can press up arrow :fa-arrow-up: on the keyboard to get to previous commands</small>
+
+<!-- slide -->
+
+<iframe id="cartesian" data-src="./CartesianCoordinates.html" class="stretch">
+</iframe>new file mode 100644
index 0000000..609bebd
--- /dev/null
+++ b/src/CartesianCoordinates.elm
@@ -0,0 +1,136 @@
+module CartesianCoordinates exposing (main)
+
+import Browser
+import CartesianPlane exposing (graph)
+import Element
+import Element.Background as Background
+import Element.Border as Border
+import Element.Input as Input
+import Html exposing (Html)
+import Svg exposing (..)
+import Svg.Attributes exposing (..)
+
+
+main =
+ Browser.element
+ { init = init
+ , view = view
+ , update = update
+ , subscriptions = subscriptions
+ }
+
+
+type alias Flags =
+ ()
+
+
+type alias Model =
+ { x : Float
+ , y : Float
+ }
+
+
+type Msg
+ = SetX Float
+ | SetY Float
+
+
+init : Flags -> ( Model, Cmd Msg )
+init () =
+ ( { x = 0, y = 0 }
+ , Cmd.none
+ )
+
+
+view : Model -> Html.Html Msg
+view model =
+ Element.layout
+ [ Element.height Element.fill -- (Element.px 600)
+ , Element.width Element.fill
+ ]
+ <|
+ Element.column
+ [ Element.height Element.fill
+ , Element.width <| Element.px 600
+ , Element.centerX
+ , Element.spacing 30
+ , Element.padding 30
+ ]
+ [ Element.el [ Element.height <| Element.px 400 ] <|
+ Element.html <|
+ graph
+ [ circle
+ [ cx <| String.fromFloat model.x
+ , cy <| String.fromFloat model.y
+ , r "0.01"
+ , fill "magenta"
+ ]
+ []
+ , text_
+ [ x <| String.fromFloat (model.x + 0.03)
+ , y <| String.fromFloat model.y
+ , fontSize "0.05"
+ , dominantBaseline "central"
+ ]
+ [ text <| Debug.toString ( model.x, model.y ) ]
+ ]
+ , Input.slider
+ [ Element.behindContent
+ (Element.el
+ [ Element.width Element.fill
+ , Element.height (Element.px 2)
+ , Element.centerY
+ , Background.color <| Element.rgb 0.7 0.7 0.7
+ , Border.rounded 2
+ ]
+ Element.none
+ )
+ ]
+ { onChange = SetX
+ , label =
+ Input.labelBelow [ Element.centerX ] <|
+ Element.text ("x value: " ++ String.fromFloat model.x)
+ , min = -1
+ , max = 1
+ , value = model.x
+ , thumb = Input.defaultThumb
+ , step = Just 0.01
+ }
+ , Input.slider
+ [ Element.behindContent
+ (Element.el
+ [ Element.width Element.fill
+ , Element.height (Element.px 2)
+ , Element.centerY
+ , Background.color <| Element.rgb 0.7 0.7 0.7
+ , Border.rounded 2
+ ]
+ Element.none
+ )
+ ]
+ { onChange = SetY
+ , label =
+ Input.labelBelow [ Element.centerX ] <|
+ Element.text ("y value: " ++ String.fromFloat model.y)
+ , min = -1
+ , max = 1
+ , value = model.y
+ , thumb = Input.defaultThumb
+ , step = Just 0.01
+ }
+ ]
+
+
+update : Msg -> Model -> ( Model, Cmd Msg )
+update msg model =
+ case msg of
+ SetX x ->
+ ( { model | x = x }, Cmd.none )
+
+ SetY y ->
+ ( { model | y = y }, Cmd.none )
+
+
+subscriptions : Model -> Sub Msg
+subscriptions model =
+ Sub.nonenew file mode 100644
index 0000000..394005d
--- /dev/null
+++ b/src/CartesianPlane.elm
@@ -0,0 +1,38 @@
+module CartesianPlane exposing (graph)
+
+import Html exposing (Html)
+import Svg exposing (..)
+import Svg.Attributes exposing (..)
+
+
+graph : List (Svg msg) -> Html msg
+graph shapes =
+ let
+ background =
+ g []
+ [ line
+ [ x1 "-1"
+ , y1 "0"
+ , x2 "1"
+ , y2 "0"
+ , stroke "black"
+ , strokeWidth "0.001"
+ ]
+ []
+ , line
+ [ x1 "0"
+ , x2 "0"
+ , y1 "-1"
+ , y2 "1"
+ , stroke "black"
+ , strokeWidth "0.001"
+ ]
+ []
+ ]
+ in
+ svg
+ [ viewBox "-1 -1 2 2"
+ , preserveAspectRatio "xMidYMid meet"
+ , Svg.Attributes.style "width: 100%, height: 100%"
+ ]
+ (background :: shapes)new file mode 100644
index 0000000..fead82e
--- /dev/null
+++ b/src/PolarCoordinates.elm
@@ -0,0 +1,180 @@
+module PolarCoordinates exposing (main)
+
+import Browser
+import Element
+import Element.Background as Background
+import Element.Border as Border
+import Element.Input as Input
+import Html exposing (Html)
+import Svg exposing (..)
+import Svg.Attributes exposing (..)
+
+
+main =
+ Browser.element
+ { init = init
+ , view = view
+ , update = update
+ , subscriptions = subscriptions
+ }
+
+
+type alias Flags =
+ ()
+
+
+type alias Model =
+ { angle : Float
+ , radius : Float
+ }
+
+
+type Msg
+ = SetAngle Float
+ | SetRadius Float
+
+
+init : Flags -> ( Model, Cmd Msg )
+init () =
+ ( { angle = 0, radius = 0.5 }
+ , Cmd.none
+ )
+
+
+view : Model -> Html.Html Msg
+view model =
+ let
+ point =
+ cartesian model
+ in
+ Element.layout
+ [ Element.height Element.fill -- (Element.px 600)
+ , Element.width Element.fill
+ , Background.color <| Element.rgb 0.6 0 0
+ ]
+ <|
+ Element.column
+ [ Element.height Element.fill
+ , Element.width <| Element.px 600
+ , Background.color <| Element.rgb 0 0.6 0
+ , Element.centerX
+ , Element.spacing 30
+ , Element.padding 30
+ ]
+ [ Element.el [] <|
+ Element.html <|
+ svg
+ [ viewBox "-1 -1 2 2"
+ , preserveAspectRatio "xMidYMid meet"
+ , Svg.Attributes.style "width: 100%, height: 100%"
+ ]
+ [ line
+ [ x1 "-1"
+ , y1 "0"
+ , x2 "1"
+ , y2 "0"
+ , stroke "black"
+ , strokeWidth "0.001"
+ ]
+ []
+ , line
+ [ x1 "0"
+ , x2 "0"
+ , y1 "-1"
+ , y2 "1"
+ , stroke "black"
+ , strokeWidth "0.001"
+ ]
+ []
+ , circle
+ [ cx <| String.fromFloat point.x
+ , cy <| String.fromFloat point.y
+ , r "0.01"
+ , fill "magenta"
+ ]
+ []
+ , line
+ [ x1 "0"
+ , x2 <| String.fromFloat point.x
+ , y1 "0"
+ , y2 <| String.fromFloat point.y
+ , stroke "magenta"
+ , strokeWidth "0.001"
+ ]
+ []
+ , text_
+ [ x <| String.fromFloat (point.x + 0.02)
+ , y <| String.fromFloat (point.y + 0.01)
+ , Svg.Attributes.fontSize "0.03pt"
+ ]
+ [ text <|
+ Debug.toString ( point.x, point.y )
+ ]
+ ]
+ , Input.slider
+ [ Element.behindContent
+ (Element.el
+ [ Element.width Element.fill
+ , Element.height (Element.px 2)
+ , Element.centerY
+ , Background.color <| Element.rgb 0.7 0.7 0.7
+ , Border.rounded 2
+ ]
+ Element.none
+ )
+ ]
+ { onChange = SetAngle
+ , label =
+ Input.labelBelow [ Element.centerX ] <|
+ Element.text ("angle value: " ++ String.fromFloat model.angle)
+ , min = 0
+ , max = 360
+ , value = model.angle
+ , thumb = Input.defaultThumb
+ , step = Just 1
+ }
+ , Input.slider
+ [ Element.behindContent
+ (Element.el
+ [ Element.width Element.fill
+ , Element.height (Element.px 2)
+ , Element.centerY
+ , Background.color <| Element.rgb 0.7 0.7 0.7
+ , Border.rounded 2
+ ]
+ Element.none
+ )
+ ]
+ { onChange = SetRadius
+ , label =
+ Input.labelBelow [ Element.centerX ] <|
+ Element.text ("radius value: " ++ String.fromFloat model.radius)
+ , min = 0
+ , max = 1
+ , value = model.radius
+ , thumb = Input.defaultThumb
+ , step = Just 0.01
+ }
+ ]
+
+
+update : Msg -> Model -> ( Model, Cmd Msg )
+update msg model =
+ case msg of
+ SetAngle angle ->
+ ( { model | angle = angle }, Cmd.none )
+
+ SetRadius radius ->
+ ( { model | radius = radius }, Cmd.none )
+
+
+subscriptions : Model -> Sub Msg
+subscriptions model =
+ Sub.none
+
+
+cartesian : { angle : Float, radius : Float } -> { x : Float, y : Float }
+cartesian model =
+ { x = model.radius * cos (degrees model.angle)
+ , y = model.radius * sin (degrees model.angle)
+ }