Begin styling efforts
Start styling the Chord Drill Sergeant for mobile devices because that is that device on which I will primarily use CDS. I'm also deleting the debugger related code. I would like to support a debugger, but I'm not currently using this one, so I am going to remove it to keep things slender. - Introduce TailwindCSS, which also introduced elm-live, index.html, index.css - Add mobile-first styling for the preferences modal - Remove unused code
This commit is contained in:
parent
a64601cc05
commit
1d427c4921
8 changed files with 264 additions and 126 deletions
|
@ -1 +1,3 @@
|
||||||
/elm-stuff
|
/elm-stuff
|
||||||
|
/elm.js
|
||||||
|
/output.css
|
|
@ -53,5 +53,5 @@ in which you can develop:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ nix-shell
|
$ nix-shell
|
||||||
$ elm reactor
|
$ elm-live -- src/Main.elm --output=elm.js
|
||||||
```
|
```
|
||||||
|
|
3
website/sandbox/chord-drill-sergeant/index.css
Normal file
3
website/sandbox/chord-drill-sergeant/index.css
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
15
website/sandbox/chord-drill-sergeant/index.html
Normal file
15
website/sandbox/chord-drill-sergeant/index.html
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<title>Chord Drill Sergeant</title>
|
||||||
|
<link rel="stylesheet" href="./output.css" />
|
||||||
|
<script src="./elm.js"></script>
|
||||||
|
</head>
|
||||||
|
<body class="font-serif">
|
||||||
|
<div id="mount"></div>
|
||||||
|
<script>
|
||||||
|
Elm.Main.init({node: document.getElementById("mount")});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -4,5 +4,6 @@ in pkgs.mkShell {
|
||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
elmPackages.elm
|
elmPackages.elm
|
||||||
elmPackages.elm-format
|
elmPackages.elm-format
|
||||||
|
elmPackages.elm-live
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
module Main exposing (main)
|
module Main exposing (main)
|
||||||
|
|
||||||
import Browser
|
import Browser
|
||||||
import ChordInspector
|
|
||||||
import Html exposing (..)
|
import Html exposing (..)
|
||||||
import Html.Attributes exposing (..)
|
import Html.Attributes exposing (..)
|
||||||
import Html.Events exposing (..)
|
import Html.Events exposing (..)
|
||||||
import NoteInspector
|
|
||||||
import Piano
|
import Piano
|
||||||
import Random
|
import Random
|
||||||
import Random.List
|
import Random.List
|
||||||
import Tempo
|
import Tempo
|
||||||
import Theory
|
import Theory
|
||||||
import Time exposing (..)
|
import Time exposing (..)
|
||||||
|
import UI
|
||||||
|
|
||||||
|
|
||||||
type alias Model =
|
type alias Model =
|
||||||
|
@ -26,13 +25,15 @@ type alias Model =
|
||||||
, firstNote : Theory.Note
|
, firstNote : Theory.Note
|
||||||
, lastNote : Theory.Note
|
, lastNote : Theory.Note
|
||||||
, practiceMode : PracticeMode
|
, practiceMode : PracticeMode
|
||||||
, debug :
|
, view : View
|
||||||
{ enable : Bool
|
|
||||||
, inspectChord : Bool
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type View
|
||||||
|
= Preferences
|
||||||
|
| Practice
|
||||||
|
|
||||||
|
|
||||||
{-| Control the type of practice you'd like.
|
{-| Control the type of practice you'd like.
|
||||||
-}
|
-}
|
||||||
type PracticeMode
|
type PracticeMode
|
||||||
|
@ -48,7 +49,6 @@ type Msg
|
||||||
| IncreaseTempo
|
| IncreaseTempo
|
||||||
| DecreaseTempo
|
| DecreaseTempo
|
||||||
| SetTempo String
|
| SetTempo String
|
||||||
| ToggleInspectChord
|
|
||||||
| ToggleInversion Theory.ChordInversion
|
| ToggleInversion Theory.ChordInversion
|
||||||
| ToggleChordType Theory.ChordType
|
| ToggleChordType Theory.ChordType
|
||||||
| TogglePitchClass Theory.PitchClass
|
| TogglePitchClass Theory.PitchClass
|
||||||
|
@ -84,7 +84,7 @@ init : Model
|
||||||
init =
|
init =
|
||||||
let
|
let
|
||||||
( firstNote, lastNote ) =
|
( firstNote, lastNote ) =
|
||||||
( Theory.C3, Theory.C5 )
|
( Theory.A1, Theory.C8 )
|
||||||
|
|
||||||
inversions =
|
inversions =
|
||||||
Theory.allInversions
|
Theory.allInversions
|
||||||
|
@ -96,7 +96,7 @@ init =
|
||||||
Theory.allPitchClasses
|
Theory.allPitchClasses
|
||||||
|
|
||||||
keys =
|
keys =
|
||||||
Theory.allKeys
|
[]
|
||||||
|
|
||||||
practiceMode =
|
practiceMode =
|
||||||
KeyMode
|
KeyMode
|
||||||
|
@ -121,13 +121,10 @@ init =
|
||||||
, whitelistedKeys = keys
|
, whitelistedKeys = keys
|
||||||
, selectedChord = Nothing
|
, selectedChord = Nothing
|
||||||
, isPaused = True
|
, isPaused = True
|
||||||
, tempo = 60
|
, tempo = 30
|
||||||
, firstNote = firstNote
|
, firstNote = firstNote
|
||||||
, lastNote = lastNote
|
, lastNote = lastNote
|
||||||
, debug =
|
, view = Preferences
|
||||||
{ enable = False
|
|
||||||
, inspectChord = True
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -212,16 +209,6 @@ update msg model =
|
||||||
, Cmd.none
|
, Cmd.none
|
||||||
)
|
)
|
||||||
|
|
||||||
ToggleInspectChord ->
|
|
||||||
( { model
|
|
||||||
| debug =
|
|
||||||
{ inspectChord = not model.debug.inspectChord
|
|
||||||
, enable = model.debug.enable
|
|
||||||
}
|
|
||||||
}
|
|
||||||
, Cmd.none
|
|
||||||
)
|
|
||||||
|
|
||||||
ToggleChordType chordType ->
|
ToggleChordType chordType ->
|
||||||
let
|
let
|
||||||
chordTypes =
|
chordTypes =
|
||||||
|
@ -331,33 +318,6 @@ playPause { isPaused } =
|
||||||
button [ onClick Pause ] [ text "Pause" ]
|
button [ onClick Pause ] [ text "Pause" ]
|
||||||
|
|
||||||
|
|
||||||
debugger : Html Msg
|
|
||||||
debugger =
|
|
||||||
fieldset []
|
|
||||||
[ label [] [ text "Inspect Chord" ]
|
|
||||||
, input [ type_ "checkbox", onClick ToggleInspectChord, checked init.debug.inspectChord ] []
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
pitchClassCheckboxes : List Theory.PitchClass -> Html Msg
|
|
||||||
pitchClassCheckboxes pitchClasses =
|
|
||||||
ul []
|
|
||||||
(Theory.allPitchClasses
|
|
||||||
|> List.map
|
|
||||||
(\pitchClass ->
|
|
||||||
li []
|
|
||||||
[ label [] [ text (Theory.viewPitchClass pitchClass) ]
|
|
||||||
, input
|
|
||||||
[ type_ "checkbox"
|
|
||||||
, onClick (TogglePitchClass pitchClass)
|
|
||||||
, checked (List.member pitchClass pitchClasses)
|
|
||||||
]
|
|
||||||
[]
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
chordTypeCheckboxes : List Theory.ChordType -> Html Msg
|
chordTypeCheckboxes : List Theory.ChordType -> Html Msg
|
||||||
chordTypeCheckboxes chordTypes =
|
chordTypeCheckboxes chordTypes =
|
||||||
ul []
|
ul []
|
||||||
|
@ -396,45 +356,71 @@ inversionCheckboxes inversions =
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
keyCheckboxes : List Theory.Key -> Html Msg
|
selectKey :
|
||||||
keyCheckboxes keys =
|
Model
|
||||||
|
->
|
||||||
|
{ pitchClass : Theory.PitchClass
|
||||||
|
, majorKey : Theory.Key
|
||||||
|
, minorKey : Theory.Key
|
||||||
|
, bluesKey : Theory.Key
|
||||||
|
}
|
||||||
|
-> Html Msg
|
||||||
|
selectKey model { pitchClass, majorKey, minorKey, bluesKey } =
|
||||||
|
let
|
||||||
|
active key =
|
||||||
|
List.member key model.whitelistedKeys
|
||||||
|
in
|
||||||
|
div [ class "flex pt-0" ]
|
||||||
|
[ p [ class "text-gray-500 text-center text-5xl flex-1 py-10" ] [ text (Theory.viewPitchClass pitchClass) ]
|
||||||
|
, UI.textToggleButton
|
||||||
|
{ label = "major"
|
||||||
|
, handleClick = ToggleKey majorKey
|
||||||
|
, classes = [ "flex-1" ]
|
||||||
|
, toggled = active majorKey
|
||||||
|
}
|
||||||
|
, UI.textToggleButton
|
||||||
|
{ label = "minor"
|
||||||
|
, handleClick = ToggleKey minorKey
|
||||||
|
, classes = [ "flex-1" ]
|
||||||
|
, toggled = active minorKey
|
||||||
|
}
|
||||||
|
, UI.textToggleButton
|
||||||
|
{ label = "blues"
|
||||||
|
, handleClick = ToggleKey bluesKey
|
||||||
|
, classes = [ "flex-1" ]
|
||||||
|
, toggled = active bluesKey
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
keyCheckboxes : Model -> Html Msg
|
||||||
|
keyCheckboxes model =
|
||||||
div []
|
div []
|
||||||
[ h2 [] [ text "Choose Key" ]
|
[ h2 [ class "text-center py-10 text-5xl" ] [ text "Select Keys" ]
|
||||||
, button [ onClick SelectAllKeys ] [ text "Select all" ]
|
|
||||||
, button [ onClick DeselectAllKeys ] [ text "Deselect all" ]
|
|
||||||
, ul []
|
, ul []
|
||||||
(Theory.allKeys
|
(Theory.allPitchClasses
|
||||||
|> List.map
|
|> List.map
|
||||||
(\key ->
|
(\pitchClass ->
|
||||||
li []
|
selectKey model
|
||||||
[ label [] [ text (Theory.viewKey key) ]
|
{ pitchClass = pitchClass
|
||||||
, input
|
, majorKey = { pitchClass = pitchClass, mode = Theory.MajorMode }
|
||||||
[ type_ "checkbox"
|
, minorKey = { pitchClass = pitchClass, mode = Theory.MinorMode }
|
||||||
, onClick (ToggleKey key)
|
, bluesKey = { pitchClass = pitchClass, mode = Theory.BluesMode }
|
||||||
, checked (List.member key keys)
|
}
|
||||||
]
|
|
||||||
[]
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
displayChord :
|
displayChord :
|
||||||
{ debug : Bool
|
{ chord : Theory.Chord
|
||||||
, chord : Theory.Chord
|
|
||||||
, firstNote : Theory.Note
|
, firstNote : Theory.Note
|
||||||
, lastNote : Theory.Note
|
, lastNote : Theory.Note
|
||||||
}
|
}
|
||||||
-> Html Msg
|
-> Html Msg
|
||||||
displayChord { debug, chord, firstNote, lastNote } =
|
displayChord { chord, firstNote, lastNote } =
|
||||||
div []
|
div []
|
||||||
[ if debug then
|
[ p [] [ text (Theory.viewChord chord) ]
|
||||||
ChordInspector.render chord
|
|
||||||
|
|
||||||
else
|
|
||||||
span [] []
|
|
||||||
, p [] [ text (Theory.viewChord chord) ]
|
|
||||||
, case Theory.notesForChord chord of
|
, case Theory.notesForChord chord of
|
||||||
Just x ->
|
Just x ->
|
||||||
Piano.render
|
Piano.render
|
||||||
|
@ -448,57 +434,65 @@ displayChord { debug, chord, firstNote, lastNote } =
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
view : Model -> Html Msg
|
practiceModeButtons : Model -> Html Msg
|
||||||
view model =
|
practiceModeButtons model =
|
||||||
div []
|
div [ class "text-center" ]
|
||||||
|
[ h2 [ class "py-10 text-5xl" ] [ text "Practice Mode" ]
|
||||||
|
, div [ class "flex pb-6" ]
|
||||||
|
[ UI.simpleButton
|
||||||
|
{ label = "Key"
|
||||||
|
, classes = [ "flex-1", "rounded-r-none" ]
|
||||||
|
, handleClick = SetPracticeMode KeyMode
|
||||||
|
, color =
|
||||||
|
if model.practiceMode == KeyMode then
|
||||||
|
UI.Primary
|
||||||
|
|
||||||
|
else
|
||||||
|
UI.Secondary
|
||||||
|
}
|
||||||
|
, UI.simpleButton
|
||||||
|
{ label = "Fine Tune"
|
||||||
|
, handleClick = SetPracticeMode FineTuneMode
|
||||||
|
, classes = [ "flex-1", "rounded-l-none" ]
|
||||||
|
, color =
|
||||||
|
if model.practiceMode == FineTuneMode then
|
||||||
|
UI.Primary
|
||||||
|
|
||||||
|
else
|
||||||
|
UI.Secondary
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
preferences : Model -> Html Msg
|
||||||
|
preferences model =
|
||||||
|
div [ class "pt-10 pb-20 px-10" ]
|
||||||
[ Tempo.render
|
[ Tempo.render
|
||||||
{ tempo = model.tempo
|
{ tempo = model.tempo
|
||||||
, handleIncrease = IncreaseTempo
|
|
||||||
, handleDecrease = DecreaseTempo
|
|
||||||
, handleInput = SetTempo
|
, handleInput = SetTempo
|
||||||
}
|
}
|
||||||
, div []
|
, practiceModeButtons model
|
||||||
[ h2 [] [ text "Practice Mode" ]
|
|
||||||
, input
|
|
||||||
[ type_ "radio"
|
|
||||||
, id "key-mode"
|
|
||||||
, name "key-mode"
|
|
||||||
, checked (model.practiceMode == KeyMode)
|
|
||||||
, onClick (SetPracticeMode KeyMode)
|
|
||||||
]
|
|
||||||
[]
|
|
||||||
, label [ for "key-mode" ] [ text "Key Mode" ]
|
|
||||||
, input
|
|
||||||
[ type_ "radio"
|
|
||||||
, id "fine-tune-mode"
|
|
||||||
, name "fine-tune-mode"
|
|
||||||
, checked (model.practiceMode == FineTuneMode)
|
|
||||||
, onClick (SetPracticeMode FineTuneMode)
|
|
||||||
]
|
|
||||||
[]
|
|
||||||
, label [ for "fine-tune-mode" ] [ text "Fine-tuning Mode" ]
|
|
||||||
]
|
|
||||||
, case model.practiceMode of
|
, case model.practiceMode of
|
||||||
KeyMode ->
|
KeyMode ->
|
||||||
keyCheckboxes model.whitelistedKeys
|
keyCheckboxes model
|
||||||
|
|
||||||
FineTuneMode ->
|
FineTuneMode ->
|
||||||
div []
|
div []
|
||||||
[ pitchClassCheckboxes model.whitelistedPitchClasses
|
[ inversionCheckboxes model.whitelistedInversions
|
||||||
, inversionCheckboxes model.whitelistedInversions
|
|
||||||
, chordTypeCheckboxes model.whitelistedChordTypes
|
, chordTypeCheckboxes model.whitelistedChordTypes
|
||||||
]
|
]
|
||||||
, playPause model
|
]
|
||||||
, if model.debug.enable then
|
|
||||||
debugger
|
|
||||||
|
|
||||||
else
|
|
||||||
span [] []
|
practice : Model -> Html Msg
|
||||||
|
practice model =
|
||||||
|
div []
|
||||||
|
[ playPause model
|
||||||
, case model.selectedChord of
|
, case model.selectedChord of
|
||||||
Just chord ->
|
Just chord ->
|
||||||
displayChord
|
displayChord
|
||||||
{ debug = model.debug.inspectChord
|
{ chord = chord
|
||||||
, chord = chord
|
|
||||||
, firstNote = model.firstNote
|
, firstNote = model.firstNote
|
||||||
, lastNote = model.lastNote
|
, lastNote = model.lastNote
|
||||||
}
|
}
|
||||||
|
@ -508,6 +502,16 @@ view model =
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
view : Model -> Html Msg
|
||||||
|
view model =
|
||||||
|
case model.view of
|
||||||
|
Preferences ->
|
||||||
|
preferences model
|
||||||
|
|
||||||
|
Practice ->
|
||||||
|
practice model
|
||||||
|
|
||||||
|
|
||||||
{-| For now, I'm just dumping things onto the page to sketch ideas.
|
{-| For now, I'm just dumping things onto the page to sketch ideas.
|
||||||
-}
|
-}
|
||||||
main =
|
main =
|
||||||
|
|
|
@ -3,25 +3,22 @@ module Tempo exposing (render)
|
||||||
import Html exposing (..)
|
import Html exposing (..)
|
||||||
import Html.Attributes exposing (..)
|
import Html.Attributes exposing (..)
|
||||||
import Html.Events exposing (..)
|
import Html.Events exposing (..)
|
||||||
|
import UI
|
||||||
|
|
||||||
|
|
||||||
type alias Props msg =
|
type alias Props msg =
|
||||||
{ tempo : Int
|
{ tempo : Int
|
||||||
, handleIncrease : msg
|
|
||||||
, handleDecrease : msg
|
|
||||||
, handleInput : String -> msg
|
, handleInput : String -> msg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
render : Props msg -> Html msg
|
render : Props msg -> Html msg
|
||||||
render { tempo, handleIncrease, handleDecrease, handleInput } =
|
render { tempo, handleInput } =
|
||||||
div []
|
div [ class "text-center" ]
|
||||||
[ p [] [ text (String.fromInt tempo ++ " BPM") ]
|
[ p [ class "text-5xl py-10" ] [ text (String.fromInt tempo ++ " BPM") ]
|
||||||
, button [ onClick handleDecrease ] [ text "Slower" ]
|
, UI.textField
|
||||||
, input
|
{ placeholderText = "Set tempo..."
|
||||||
[ onInput handleInput
|
, handleInput = handleInput
|
||||||
, placeholder "Set tempo..."
|
, classes = []
|
||||||
]
|
}
|
||||||
[]
|
|
||||||
, button [ onClick handleIncrease ] [ text "Faster" ]
|
|
||||||
]
|
]
|
||||||
|
|
116
website/sandbox/chord-drill-sergeant/src/UI.elm
Normal file
116
website/sandbox/chord-drill-sergeant/src/UI.elm
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
module UI exposing (..)
|
||||||
|
|
||||||
|
import Html exposing (..)
|
||||||
|
import Html.Attributes exposing (..)
|
||||||
|
import Html.Events exposing (..)
|
||||||
|
|
||||||
|
|
||||||
|
type Color
|
||||||
|
= Primary
|
||||||
|
| Secondary
|
||||||
|
|
||||||
|
|
||||||
|
bgForColor : Color -> String
|
||||||
|
bgForColor color =
|
||||||
|
case color of
|
||||||
|
Primary ->
|
||||||
|
"bg-gray-600"
|
||||||
|
|
||||||
|
Secondary ->
|
||||||
|
"bg-gray-300"
|
||||||
|
|
||||||
|
|
||||||
|
textForColor : Color -> String
|
||||||
|
textForColor color =
|
||||||
|
case color of
|
||||||
|
Primary ->
|
||||||
|
"text-white"
|
||||||
|
|
||||||
|
Secondary ->
|
||||||
|
"text-black"
|
||||||
|
|
||||||
|
|
||||||
|
tw : List String -> String
|
||||||
|
tw styles =
|
||||||
|
String.join " " styles
|
||||||
|
|
||||||
|
|
||||||
|
simpleButton :
|
||||||
|
{ label : String
|
||||||
|
, handleClick : msg
|
||||||
|
, color : Color
|
||||||
|
, classes : List String
|
||||||
|
}
|
||||||
|
-> Html msg
|
||||||
|
simpleButton { label, handleClick, color, classes } =
|
||||||
|
let
|
||||||
|
buttonClasses =
|
||||||
|
[ bgForColor color
|
||||||
|
, textForColor color
|
||||||
|
, "py-10"
|
||||||
|
, "px-20"
|
||||||
|
, "text-5xl"
|
||||||
|
, "rounded-lg"
|
||||||
|
]
|
||||||
|
in
|
||||||
|
button
|
||||||
|
[ class (tw <| List.concat [ buttonClasses, classes ])
|
||||||
|
, onClick handleClick
|
||||||
|
]
|
||||||
|
[ text label ]
|
||||||
|
|
||||||
|
|
||||||
|
textToggleButton :
|
||||||
|
{ label : String
|
||||||
|
, handleClick : msg
|
||||||
|
, classes : List String
|
||||||
|
, toggled : Bool
|
||||||
|
}
|
||||||
|
-> Html msg
|
||||||
|
textToggleButton { label, toggled, handleClick, classes } =
|
||||||
|
let
|
||||||
|
( textColor, textTreatment ) =
|
||||||
|
if toggled then
|
||||||
|
( "text-red-600", "underline" )
|
||||||
|
|
||||||
|
else
|
||||||
|
( "text-black", "no-underline" )
|
||||||
|
|
||||||
|
buttonClasses =
|
||||||
|
[ textColor
|
||||||
|
, textTreatment
|
||||||
|
, "py-10"
|
||||||
|
, "px-10"
|
||||||
|
, "text-5xl"
|
||||||
|
]
|
||||||
|
in
|
||||||
|
button
|
||||||
|
[ class (tw <| List.concat [ buttonClasses, classes ])
|
||||||
|
, onClick handleClick
|
||||||
|
]
|
||||||
|
[ text label ]
|
||||||
|
|
||||||
|
|
||||||
|
textField :
|
||||||
|
{ placeholderText : String
|
||||||
|
, handleInput : String -> msg
|
||||||
|
, classes : List String
|
||||||
|
}
|
||||||
|
-> Html msg
|
||||||
|
textField { placeholderText, handleInput, classes } =
|
||||||
|
let
|
||||||
|
inputClasses =
|
||||||
|
[ "text-5xl"
|
||||||
|
, "w-full"
|
||||||
|
, "py-10"
|
||||||
|
, "px-16"
|
||||||
|
, "border"
|
||||||
|
, "rounded-lg"
|
||||||
|
]
|
||||||
|
in
|
||||||
|
input
|
||||||
|
[ class (tw <| List.concat [ inputClasses, classes ])
|
||||||
|
, onInput handleInput
|
||||||
|
, placeholder placeholderText
|
||||||
|
]
|
||||||
|
[]
|
Loading…
Reference in a new issue