├── .editorconfig ├── .github ├── linters │ └── .ecrc └── workflows │ ├── ci-lib.yml │ └── ci-super-linter.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── docs ├── docs.ipkg └── src │ └── Examples │ ├── Balls.md │ ├── CSS.md │ ├── CSS │ ├── Balls.idr │ ├── Colors.idr │ ├── Core.idr │ ├── Fractals.idr │ ├── MathGame.idr │ ├── Performance.idr │ ├── Requests.idr │ └── Reset.idr │ ├── Fractals.idr │ ├── Fractals │ └── Dragon.idr │ ├── Main.md │ ├── MathGame.md │ ├── Performance.md │ ├── Requests.md │ ├── Reset.md │ ├── Selector.md │ └── Util.idr ├── dom-mvc.ipkg ├── extra ├── dom-mvc-extra.ipkg └── src │ ├── Text │ └── HTML │ │ ├── Class.idr │ │ ├── Confirm.idr │ │ ├── DomID.idr │ │ ├── Extra.idr │ │ ├── File.idr │ │ └── Validation.idr │ └── Web │ └── MVC │ ├── Controller.idr │ └── Controller │ ├── Confirm.idr │ ├── File.idr │ ├── Form.idr │ ├── List.idr │ └── Validation.idr ├── js └── README.md ├── mvc.html ├── pack.toml ├── pics ├── pic1.jpg ├── pic10.jpg ├── pic11.jpg ├── pic2.jpg ├── pic3.jpg ├── pic4.jpg ├── pic5.jpg ├── pic6.jpg ├── pic7.jpg ├── pic8.jpg └── pic9.jpg └── src ├── Text ├── CSS.idr ├── CSS │ ├── Angle.idr │ ├── Color.idr │ ├── Cursor.idr │ ├── Declaration.idr │ ├── Dir.idr │ ├── Flexbox.idr │ ├── Gradient.idr │ ├── Grid.idr │ ├── Length.idr │ ├── ListStyleType.idr │ ├── Percentage.idr │ ├── Property.idr │ ├── Rule.idr │ └── Selector.idr ├── HTML.idr └── HTML │ ├── Attribute.idr │ ├── Event.idr │ ├── Node.idr │ ├── Ref.idr │ ├── Select.idr │ └── Tag.idr └── Web ├── MVC.idr └── MVC ├── Animate.idr ├── Canvas.idr ├── Canvas ├── Angle.idr ├── Scene.idr ├── Shape.idr ├── Style.idr └── Transformation.idr ├── Cmd.idr ├── Event.idr ├── Http.idr ├── Util.idr ├── View.idr └── Widget.idr /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | # Defaults for every file 5 | [*] 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | charset = utf-8 10 | 11 | # Idris source files 12 | [*.{idr,ipkg,tex,yaff,lidr}] 13 | indent_style = space 14 | indent_size = 2 15 | 16 | # Various configuration files 17 | [{*.yml,.ecrc}] 18 | indent_style = space 19 | indent_size = 2 20 | 21 | [*.py] 22 | indent_style = space 23 | indent_size = 4 24 | 25 | [*.{c,h}] 26 | indent_style = space 27 | indent_size = 4 28 | 29 | [*.{md,rst}] 30 | indent_style = space 31 | indent_size = 2 32 | 33 | [*.sh] 34 | indent_style = space 35 | indent_size = 4 36 | shell_variant = posix 37 | switch_case_indent = true 38 | 39 | [*.bat] 40 | indent_style = space 41 | indent_size = 4 42 | 43 | [{Makefile,*.mk}] 44 | indent_style = tab 45 | 46 | [*.nix] 47 | indent_style = space 48 | indent_size = 2 49 | 50 | [expected] 51 | trim_trailing_whitespace = false 52 | -------------------------------------------------------------------------------- /.github/linters/.ecrc: -------------------------------------------------------------------------------- 1 | { 2 | "Disable": { 3 | "IndentSize": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.github/workflows/ci-lib.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Build 3 | 4 | on: 5 | push: 6 | branches: 7 | - '**' 8 | tags: 9 | - '**' 10 | pull_request: 11 | branches: 12 | - main 13 | schedule: 14 | - cron: '0 1 * * *' 15 | 16 | defaults: 17 | run: 18 | shell: bash 19 | 20 | jobs: 21 | build: 22 | name: Build ${{ github.repository }} with Idris2 latest 23 | runs-on: ubuntu-latest 24 | env: 25 | PACK_DIR: /root/.pack 26 | strategy: 27 | fail-fast: false 28 | container: ghcr.io/stefan-hoeck/idris2-pack:latest 29 | steps: 30 | - name: Checkout 31 | uses: actions/checkout@v2 32 | - name: Build library 33 | run: pack install dom-mvc 34 | - name: Check docs 35 | run: pack typecheck dom-mvc-docs 36 | - name: Check extra 37 | run: pack typecheck dom-mvc-extra 38 | -------------------------------------------------------------------------------- /.github/workflows/ci-super-linter.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Lint 3 | 4 | on: 5 | push: 6 | branches: 7 | - '*' 8 | tags: 9 | - '*' 10 | pull_request: 11 | branches: 12 | - main 13 | - master 14 | 15 | jobs: 16 | build: 17 | name: Lint Code Base 18 | runs-on: ubuntu-latest 19 | steps: 20 | 21 | - name: Checkout 22 | uses: actions/checkout@v2 23 | with: 24 | # Full git history is needed to get a proper list of changed files within `super-linter` 25 | fetch-depth: 0 26 | 27 | - name: Lint Code Base 28 | uses: github/super-linter/slim@v4 29 | env: 30 | VALIDATE_ALL_CODEBASE: false 31 | DEFAULT_BRANCH: main 32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 33 | IGNORE_GENERATED_FILES: true 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | generated/ 3 | *.*~ 4 | js/mvc.js 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2023, Stefan Höck 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | export IDRIS2 ?= idris2 2 | 3 | .PHONY: page 4 | page: 5 | pack build docs/docs.ipkg 6 | cp docs/build/exec/mvc.js js/mvc.js 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # idris2-dom-mvc: Single Page Web Applications in Idris 2 | 3 | This is an experimental library about putting a nice API on top 4 | of [idris2-dom](https://github.com/stefan-hoeck/idris2-dom) 5 | for writing interactive single page web applications. 6 | Unlike the [idris2-rhone-js](https://github.com/stefan-hoeck/idris2-rhone-js) 7 | library, which takes a functional reactive programming approach 8 | to GUI programming, the concept of this library is much simpler: 9 | Events fired from user interaction update the current application 10 | state via pure functions, and the UI is updated according to 11 | the current event and new application state. This is a similar 12 | approach to what the [Elm programming language](https://elm-lang.org/) 13 | does. However, we take a more fine-grained approach to updating the DOM 14 | and therefore don't need an extra step via a virtual DOM, which 15 | can be beneficial for performance. 16 | 17 | This is still very much work in progress, but I transferred the 18 | whole rhone-js tutorial to this library and the resulting code is 19 | a lot simpler compared to the one from rhone-js. 20 | Here's the link to the [tutorial](docs/src/Examples/Main.md). 21 | 22 | ## Dependencies 23 | 24 | This project makes use of several other Idris2 projects: 25 | 26 | * [idris2-elab-util](https://github.com/stefan-hoeck/idris2-elab-util) 27 | * [idris2-dom](https://github.com/stefan-hoeck/idris2-dom) 28 | * [idris2-refined](https://github.com/stefan-hoeck/idris2-refined) 29 | * [idris2-tailrec](https://github.com/stefan-hoeck/idris2-tailrec) 30 | * [idris2-json-simple](https://github.com/stefan-hoeck/idris2-json) 31 | 32 | It is strongly suggested to use 33 | a package manager like [pack](https://github.com/stefan-hoeck/idris2-pack) 34 | to install and maintain the required dependencies and build the project. 35 | 36 | ## Building the Example Page 37 | 38 | If you have installed pack as suggested above, 39 | you can build the example page with `make page` and have a look at 40 | it by loading `mvc.html` into your browser. 41 | 42 | ## Package `dom-mvc-extra` 43 | 44 | This additional library contains an opinionated set of utilities I find 45 | generally useful in my own projects. It takes a slightly different 46 | approach towards the "model, view, update" concept, by introducing 47 | stateful computations (from `Control.Monad.State`) paired with optics 48 | (from `idris2-monocle`) for operating on smaller parts of a large 49 | application state. 50 | 51 | In addition, it introduces several new data types and interactive DOM 52 | elements that I typically like to have around. 53 | -------------------------------------------------------------------------------- /docs/docs.ipkg: -------------------------------------------------------------------------------- 1 | package dom-mvc-docs 2 | version = 0.0.1 3 | authors = "Stefan Höck" 4 | depends = dom-mvc 5 | , contrib 6 | , finite 7 | , monocle 8 | , rio 9 | 10 | opts = "--codegen javascript" 11 | 12 | main = Examples.Main 13 | executable = "mvc.js" 14 | 15 | modules = Examples.CSS 16 | , Examples.CSS.Colors 17 | , Examples.CSS.Core 18 | , Examples.CSS.Fractals 19 | , Examples.CSS.MathGame 20 | , Examples.CSS.Performance 21 | , Examples.CSS.Reset 22 | 23 | , Examples.Fractals 24 | , Examples.Fractals.Dragon 25 | 26 | , Examples.Balls 27 | , Examples.MathGame 28 | , Examples.Performance 29 | , Examples.Reset 30 | , Examples.Selector 31 | , Examples.Util 32 | 33 | sourcedir = "src" 34 | -------------------------------------------------------------------------------- /docs/src/Examples/Balls.md: -------------------------------------------------------------------------------- 1 | # Running Animations: Bouncing Balls 2 | 3 | In this tutorial we are going to have a look at 4 | running a (non-interactive) animation. We simulate 5 | the frictionless movement of a group of balls under 6 | the influence of gravitation in a two-dimensional 7 | room. 8 | 9 | The user interface will be very simple: Just a 10 | validated text input for defining the number of 11 | balls to animate and a button to (re)start the 12 | animation. The main focus of the tutorial will 13 | be the animation itself. 14 | 15 | ```idris 16 | module Examples.Balls 17 | 18 | import Data.Either 19 | import Data.Nat 20 | import Data.Refined.Integer 21 | import Data.Vect 22 | 23 | import Derive.Prelude 24 | import Derive.Refined 25 | 26 | import Examples.CSS.Colors 27 | import Examples.CSS.Balls 28 | import Examples.Util 29 | 30 | import Text.CSS.Color 31 | import Web.MVC 32 | import Web.MVC.Animate 33 | import Web.MVC.Canvas 34 | 35 | %default total 36 | %language ElabReflection 37 | ``` 38 | 39 | ## Model 40 | 41 | We first define a couple of physical entities: 42 | 43 | ```idris 44 | -- 2D Vector 45 | V2 : Type 46 | V2 = Vect 2 Double 47 | 48 | -- Velocity of a point in 2D space 49 | Velocity : Type 50 | Velocity = V2 51 | 52 | -- Acceleration of a point in 2D space 53 | Acceleration : Type 54 | Acceleration = V2 55 | 56 | -- constant acceleration vector 57 | acc : Acceleration 58 | acc = [0,-9.81] 59 | 60 | -- height and width of the room in m 61 | w : Double 62 | w = 10 63 | 64 | -- start height of all balls 65 | h0 : Double 66 | h0 = 9 67 | 68 | -- ball radius in m 69 | r : Double 70 | r = 0.1 71 | 72 | -- start velocity in m/s 73 | v0 : Double 74 | v0 = 4 75 | 76 | -- vector addition 77 | (+) : V2 -> V2 -> V2 78 | [u,v] + [x,y] = [u+x, v+y] 79 | 80 | -- multiplication with a scalar 81 | (*) : Double -> V2 -> V2 82 | m * [x,y] = [m * x, m * y] 83 | ``` 84 | 85 | We need a data type to hold the current state of a 86 | ball in motion: Its color, position and velocity: 87 | 88 | ```idris 89 | record Ball where 90 | constructor MkBall 91 | col : Color 92 | pos : V2 93 | vel : Velocity 94 | ``` 95 | 96 | Next, we define the event type and application state. 97 | We use again a refined primitive to make sure user input 98 | has been properly validated: 99 | 100 | ```idris 101 | MinBalls, MaxBalls : Integer 102 | MinBalls = 1 103 | MaxBalls = 5000 104 | 105 | record NumBalls where 106 | constructor B 107 | value : Integer 108 | {auto 0 prf : FromTo MinBalls MaxBalls value} 109 | 110 | %runElab derive "NumBalls" [Show,Eq,Ord,RefinedInteger] 111 | 112 | public export 113 | data BallsEv : Type where 114 | BallsInit : BallsEv 115 | GotCleanup : IO () -> BallsEv 116 | Run : BallsEv 117 | NumIn : Either String NumBalls -> BallsEv 118 | Next : DTime -> BallsEv 119 | 120 | public export 121 | record BallsST where 122 | constructor BS 123 | balls : List Ball 124 | count : Nat 125 | dtime : DTime 126 | numBalls : Maybe NumBalls 127 | cleanup : IO () 128 | 129 | fpsCount : Nat 130 | fpsCount = 15 131 | 132 | export 133 | init : BallsST 134 | init = BS [] fpsCount 0 Nothing (pure ()) 135 | 136 | read : String -> Either String NumBalls 137 | read = 138 | let err := "expected integer between \{show MinBalls} and \{show MaxBalls}" 139 | in maybeToEither err . refineNumBalls . cast 140 | ``` 141 | 142 | A couple of things require some explanation: 143 | 144 | First: We want to display the performance of our animation and display 145 | the number of frames per second. For this, we accumulate the time 146 | taken to animate 15 frames (`fpsCount`) and reduce a counter 147 | (`count`) on every frame. 148 | 149 | Second: We want to make sure the animation is stopped once the user 150 | selects another example application. Field `cleanup` is used for this. 151 | It is set to a dummy initially, but once the controller starts the 152 | animation, it is replace with a proper cleanup hook. 153 | This is then invoked in the cleanup routine of the main selector application 154 | when applications are switched. 155 | 156 | Third: We are going to react on an event that is not fired due 157 | to user interaction in this example app. Event `Next dt` will be 158 | fired from the animation we start. It is registered in main 159 | controller at the end of this source file. 160 | 161 | ## View 162 | 163 | We draw our set of balls in a canvas, so we need 164 | some instructions for doing so. A ball will sometimes 165 | move beyond its physical boundaries, in which case the 166 | controller (see below) will adjust its direction 167 | of movement and it will move back into the room. 168 | To get the illusion of reflecting the ball at the 169 | correct location, we hide the ball as long as it is 170 | outside the room (this happens only for very short 171 | moments due to the limited time resolution of 172 | our animation): 173 | 174 | ```idris 175 | inBounds : Ball -> Bool 176 | inBounds (MkBall _ [x,y] _) = y >= 0 && x >= 0 && x <= w 177 | 178 | ballToScene : Ball -> Scene 179 | ballToScene b@(MkBall _ [x,y] _) = 180 | S1 [Fill $ if inBounds b then b.col else transparent] Id $ 181 | circle x (w - y) r Fill 182 | ``` 183 | 184 | The utilities for describing and rendering a canvas scene 185 | can be found at `Web.MVC.Canvas` and its submodules. 186 | 187 | We also draw some primitive walls and a floor to visualize 188 | the room: 189 | 190 | ```idris 191 | -- room wall thickness in meters 192 | wallThickness : Double 193 | wallThickness = 0.20 194 | 195 | -- walls and floor of the room. 196 | walls : Shape 197 | walls = 198 | let hwt := wallThickness / 2 199 | in polyLine [(-hwt, 0), (-hwt, w+hwt), (w+hwt,w+hwt), (w+hwt,0)] 200 | ``` 201 | 202 | We can now describe a scene of balls plus the room 203 | at a given point in time: 204 | 205 | ```idris 206 | ballsToScene : List Ball -> Scene 207 | ballsToScene bs = 208 | SM [] (Transform 50 0 0 50 10 10) $ 209 | [ SM [] Id $ map ballToScene bs 210 | , S1 [Stroke base80, LineWidth wallThickness] Id walls 211 | ] 212 | ``` 213 | 214 | Of course, we also need to set up the HTML objects of 215 | our application: 216 | 217 | ```idris 218 | -- canvas width and height 219 | wcanvas : Bits32 220 | wcanvas = 520 221 | 222 | content : Node BallsEv 223 | content = 224 | div [ class ballsContent ] 225 | [ lbl "Number of balls:" lblCount 226 | , input 227 | [ Id txtCount 228 | , onInput (NumIn . read) 229 | , onEnterDown Run 230 | , class widget 231 | , placeholder "Range: [\{show MinBalls}, \{show MaxBalls}]" 232 | ] [] 233 | , button [Id btnRun, onClick Run, disabled True, classes [widget,btn]] ["Run"] 234 | , div [Id log] [] 235 | , canvas [Id out, width wcanvas, height wcanvas] [] 236 | ] 237 | ``` 238 | 239 | ## Controller 240 | 241 | The main focus of the controller will be to properly 242 | animate the bouncing balls. 243 | 244 | For calculating the next position and velocity vector 245 | of a ball, we use simple Newtonian physics and some 246 | help from the `VectorSpace` interface. We 247 | also need some form of collision detection to make 248 | sure our balls don't leave the room: 249 | 250 | ```idris 251 | -- Collision detection: We verify that the given ball 252 | -- is still in the room. If this is not the case, we simulate 253 | -- a bouncing off the walls by inverting the x-velocity (if the 254 | -- ball hit a wall) or the y-velocity (if the ball hit the ground) 255 | checkBounds : Ball -> Ball 256 | checkBounds b@(MkBall c [px,py] [vx,vy]) = 257 | if (py <= r && vy < 0) then (MkBall c [px,py] [vx,-vy]) 258 | else if (px <= r && vx < 0) then (MkBall c [px,py] [-vx,vy]) 259 | else if (px >= (w - r) && vx > 0) then (MkBall c [px,py] [-vx,vy]) 260 | else b 261 | 262 | -- moves a ball after a given time delta 263 | -- by adjusting its position and velocity 264 | nextBall : DTime -> Ball -> Ball 265 | nextBall delta (MkBall c p v) = 266 | let dt := cast delta / the Double 1000 -- time in seconds 267 | v2 := v + (dt * acc) 268 | p2 := p + (dt / 2 * (v + v2)) 269 | in checkBounds (MkBall c p2 v2) 270 | ``` 271 | 272 | We also need a way to create an initial set of 273 | balls based on user input. We evenly distribute 274 | them at a height of nine meters, giving them 275 | slightly different colors and starting velocities: 276 | 277 | ```idris 278 | initialBalls : NumBalls -> List Ball 279 | initialBalls (B n) = go (cast n) Nil 280 | 281 | where 282 | col : Bits8 -> Color 283 | col 0 = comp100 284 | col 1 = comp80 285 | col 2 = comp60 286 | col 3 = comp40 287 | col _ = comp20 288 | 289 | ball : Nat -> Ball 290 | ball k = 291 | let factor := cast {to = Double} k / (cast n - 1.0) 292 | phi := pi * factor 293 | x0 := 1.0 + factor * 8 294 | in MkBall (col $ cast k `mod` 5) [x0,9] (v0 * [sin phi, cos phi]) 295 | 296 | go : (k : Nat) -> List Ball -> List Ball 297 | go 0 bs = bs 298 | go (S k) bs = go k $ ball k :: bs 299 | ``` 300 | 301 | Adjusting the state involves some fiddling with the FPS counter. 302 | The rest is pretty straight forward: 303 | 304 | ```idris 305 | export 306 | update : BallsEv -> BallsST -> BallsST 307 | update BallsInit s = init 308 | update (GotCleanup cu) s = {cleanup := cu} s 309 | update Run s = {balls := maybe s.balls initialBalls s.numBalls} s 310 | update (NumIn x) s = {numBalls := eitherToMaybe x} s 311 | update (Next m) s = case s.count of 312 | 0 => { balls $= map (nextBall m), dtime := 0, count := fpsCount } s 313 | S k => { balls $= map (nextBall m), dtime $= (+m), count := k } s 314 | ``` 315 | 316 | Almost all events will be fired from the animation, so its safe 317 | to render the scene on every event: 318 | 319 | ```idris 320 | showFPS : Bits32 -> String 321 | showFPS 0 = "" 322 | showFPS n = 323 | let val := 1000 * cast fpsCount `div` n 324 | in "FPS: \{show val}" 325 | ``` 326 | 327 | In addition, we redraw the whole application in case of the `Init` 328 | event, and we update the text field's validation message upon 329 | user input: 330 | 331 | ```idris 332 | export 333 | display : BallsEv -> BallsST -> Cmd BallsEv 334 | display BallsInit _ = 335 | child exampleDiv content <+> animateWithCleanup GotCleanup Next 336 | display Run _ = noAction 337 | display (GotCleanup _) _ = noAction 338 | display (NumIn x) _ = validate txtCount x <+> disabledE btnRun x 339 | display (Next m) s = 340 | batch 341 | [ render out (ballsToScene s.balls) 342 | , cmdIf (s.count == 0) (text log $ showFPS s.dtime) 343 | ] 344 | ``` 345 | 346 | The main controller must make sure the animation is started 347 | by registering an event handler upon initialization. 348 | Function `Web.MVC.Animate.animate` will respond with a 349 | cleanup hook, which we put in the `cleanup` field of the 350 | application state. 351 | 352 | 354 | -------------------------------------------------------------------------------- /docs/src/Examples/CSS.md: -------------------------------------------------------------------------------- 1 | # CSS 2 | 3 | The plan of *rhone-js* is to eventually come 4 | 'batteries included' and this means having a way 5 | to programmatically declare (and change) the appearance 6 | of a web page. The `Text.CSS` submodules therefore come 7 | with a (so far incomplete) set of data types for 8 | declaring CSS rules in a type-safe manner. 9 | 10 | ```idris 11 | module Examples.CSS 12 | 13 | import Data.String 14 | import Examples.CSS.Balls 15 | import Examples.CSS.Core 16 | import Examples.CSS.Fractals 17 | import Examples.CSS.MathGame 18 | import Examples.CSS.Performance 19 | import Examples.CSS.Reset 20 | import Examples.CSS.Requests 21 | import Text.CSS 22 | 23 | %default total 24 | ``` 25 | ## IDs and Classes 26 | 27 | The `rhone.html` document at the project root defines two 28 | entry points for our single-page web page: A `style` element 29 | in the header, where our CSS rules go, and the body element, 30 | where the content of our web page goes. We typically refer 31 | to HTML elements via `ElemRef` values 32 | (defined in `Rhone.JS.ElemRef`), which come with 33 | an ID and a tag to allow us to safely request the properly 34 | typed element from the DOM. 35 | 36 | ## CSS Rules 37 | 38 | Note: I'm by no means an expert, so 39 | the CSS rules below might quite well seem horrible 40 | to purists; suggestions of improvements are welcome. 41 | 42 | Here are the core rules for laying out the web page (the details can 43 | be found in the corresponding submodules). 44 | 45 | ```idris 46 | export 47 | rules : List (Rule 1) 48 | rules = 49 | coreCSS 50 | ++ Balls.css 51 | ++ Fractals.css 52 | ++ MathGame.css 53 | ++ Performance.css 54 | ++ Reset.css 55 | ++ Requests.css 56 | ``` 57 | 58 | 60 | -------------------------------------------------------------------------------- /docs/src/Examples/CSS/Balls.idr: -------------------------------------------------------------------------------- 1 | module Examples.CSS.Balls 2 | 3 | import Data.Vect 4 | import Examples.CSS.Colors 5 | import Text.CSS 6 | import Text.HTML 7 | import public Examples.CSS.Core 8 | 9 | -------------------------------------------------------------------------------- 10 | -- IDs 11 | -------------------------------------------------------------------------------- 12 | 13 | export 14 | out : Ref Canvas 15 | out = Id "balls_out" 16 | 17 | export 18 | btnRun : Ref Tag.Button 19 | btnRun = Id "balls_run" 20 | 21 | export 22 | txtCount : Ref Tag.Input 23 | txtCount = Id "balls_numballs" 24 | 25 | export 26 | log : Ref Div 27 | log = Id "balls_log" 28 | 29 | -------------------------------------------------------------------------------- 30 | -- Rules 31 | -------------------------------------------------------------------------------- 32 | 33 | export 34 | ballsContent : String 35 | ballsContent = "balls_content" 36 | 37 | export 38 | lblCount : String 39 | lblCount = "balls_lblcount" 40 | 41 | data Tag = LNum | INum | BRun | LFPS | Anim | Dot 42 | 43 | AreaTag Tag where 44 | showTag LNum = "LNum" 45 | showTag INum = "INum" 46 | showTag BRun = "BRun" 47 | showTag LFPS = "LFPS" 48 | showTag Anim = "Anim" 49 | showTag Dot = "." 50 | 51 | export 52 | css : List (Rule 1) 53 | css = 54 | [ Media "min-width: 300px" 55 | [ class ballsContent 56 | [ display $ Area 57 | (replicate 4 MinContent) 58 | [MaxContent, MaxContent] 59 | [ [LNum, INum] 60 | , [Dot, BRun] 61 | , [LFPS, LFPS] 62 | , [Anim, Anim] 63 | ] 64 | 65 | , columnGap $ px 10 66 | , rowGap $ px 10 67 | , padding $ VH (px 20) (px 10) 68 | ] 69 | ] 70 | 71 | , Media "min-width: 800px" 72 | [ class ballsContent 73 | [ display $ Area 74 | (replicate 4 MinContent) 75 | [MaxContent, MaxContent, fr 1] 76 | [ [LNum, INum, Anim] 77 | , [Dot, BRun, Anim] 78 | , [LFPS, LFPS, Anim] 79 | , [Dot, Dot, Anim] 80 | ] 81 | 82 | , columnGap $ px 10 83 | , rowGap $ px 10 84 | , padding $ VH (px 20) (px 10) 85 | ] 86 | ] 87 | 88 | , class lblCount [ gridArea LNum ] 89 | 90 | , ref txtCount 91 | [ gridArea INum 92 | , textAlign End 93 | ] 94 | 95 | , ref btnRun [ gridArea BRun ] 96 | 97 | , ref log [ gridArea LFPS ] 98 | 99 | , ref out 100 | [ justifySelf Center 101 | , gridArea Anim 102 | , maxWidth $ px 500 103 | , width $ px 500 104 | ] 105 | ] 106 | -------------------------------------------------------------------------------- /docs/src/Examples/CSS/Colors.idr: -------------------------------------------------------------------------------- 1 | module Examples.CSS.Colors 2 | 3 | import Data.Maybe 4 | import Text.CSS 5 | 6 | export 7 | lightest_grey : Color 8 | lightest_grey = hsl 0 0 90 9 | 10 | export 11 | lighter_grey : Color 12 | lighter_grey = hsl 0 0 70 13 | 14 | export 15 | light_grey : Color 16 | light_grey = hsl 0 0 50 17 | 18 | export 19 | dark_grey : Color 20 | dark_grey = hsl 0 0 30 21 | 22 | export 23 | darker_grey : Color 24 | darker_grey = hsl 0 0 10 25 | 26 | export 27 | base100 : Color 28 | base100 = rgb 230 115 0 29 | 30 | export 31 | base80 : Color 32 | base80 = rgb 230 138 46 33 | 34 | export 35 | base60 : Color 36 | base60 = rgb 230 161 92 37 | 38 | export 39 | base40 : Color 40 | base40 = rgb 230 184 138 41 | 42 | export 43 | base20 : Color 44 | base20 = rgb 230 207 184 45 | 46 | export 47 | comp100 : Color 48 | comp100 = rgb 0 115 230 49 | 50 | export 51 | comp80 : Color 52 | comp80 = rgb 46 138 230 53 | 54 | export 55 | comp60 : Color 56 | comp60 = rgb 92 161 230 57 | 58 | export 59 | comp40 : Color 60 | comp40 = rgb 138 184 230 61 | 62 | export 63 | comp20 : Color 64 | comp20 = rgb 184 207 230 65 | -------------------------------------------------------------------------------- /docs/src/Examples/CSS/Core.idr: -------------------------------------------------------------------------------- 1 | module Examples.CSS.Core 2 | 3 | import Examples.CSS.Colors 4 | import Text.CSS 5 | import Text.HTML 6 | 7 | -------------------------------------------------------------------------------- 8 | -- IDs 9 | -------------------------------------------------------------------------------- 10 | 11 | ||| ID of the `
` element. The page content will 12 | ||| be placed here. 13 | export 14 | contentDiv : Ref Tag.Body 15 | contentDiv = Id "content" 16 | 17 | ||| The page consists of a static heading with a title an 18 | ||| (eventually) a short description of the project. 19 | ||| This is followed by a selection box, where visitors can 20 | ||| choose an example application. 21 | ||| 22 | ||| The example application will be dynamicall generated and 23 | ||| placed in to a `