Move the habit-screens project into //website
I'd like to eventually deploy this to wpcarro.dev. Coming soon!
This commit is contained in:
parent
3feb8ceb9a
commit
9e2fbfde8e
17 changed files with 149 additions and 18 deletions
|
|
@ -1,12 +0,0 @@
|
|||
# Habit Screens
|
||||
|
||||
Problem: I would like to increase the rate at which I complete my daily, weekly,
|
||||
monthly, yearly habits.
|
||||
|
||||
Solution: Habit Screens are mounted in strategic locations throughout my
|
||||
apartment. Each Habit Screen displays the habits that I should complete that
|
||||
day, and I can tap each item to mark it as complete. I will encounter the Habit
|
||||
Screens in my bedroom, kitchen, and bathroom, so I will have adequate "cues" to
|
||||
focus my attention. By marking each item as complete and tracking the results
|
||||
over time, I will have more incentive to maintain my consistency
|
||||
(i.e. "reward").
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
source_up
|
||||
use_nix
|
||||
3
scratch/habit-screens/client/.gitignore
vendored
3
scratch/habit-screens/client/.gitignore
vendored
|
|
@ -1,3 +0,0 @@
|
|||
/elm-stuff
|
||||
/Main.min.js
|
||||
/output.css
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
# Elm
|
||||
|
||||
Elm has one of the best developer experiences that I'm aware of. The error
|
||||
messages are helpful and the entire experience is optimized to improve the ease
|
||||
of writing web applications.
|
||||
|
||||
## Developing
|
||||
|
||||
If you're interested in contributing, the following will create an environment
|
||||
in which you can develop:
|
||||
|
||||
```shell
|
||||
$ nix-shell
|
||||
$ npx tailwindcss build index.css -o output.css
|
||||
$ elm-live -- src/Main.elm --output=Main.min.js
|
||||
```
|
||||
|
||||
You can now view your web client at `http://localhost:8000`!
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
{
|
||||
"type": "application",
|
||||
"source-directories": [
|
||||
"src"
|
||||
],
|
||||
"elm-version": "0.19.1",
|
||||
"dependencies": {
|
||||
"direct": {
|
||||
"elm/browser": "1.0.2",
|
||||
"elm/core": "1.0.5",
|
||||
"elm/html": "1.0.0",
|
||||
"elm/random": "1.0.0",
|
||||
"elm/svg": "1.0.1",
|
||||
"elm/time": "1.0.0",
|
||||
"elm-community/list-extra": "8.2.3",
|
||||
"elm-community/maybe-extra": "5.2.0",
|
||||
"elm-community/random-extra": "3.1.0",
|
||||
"justinmimbs/date": "3.2.1"
|
||||
},
|
||||
"indirect": {
|
||||
"elm/json": "1.1.3",
|
||||
"elm/parser": "1.1.0",
|
||||
"elm/url": "1.0.0",
|
||||
"elm/virtual-dom": "1.0.2",
|
||||
"owanturist/elm-union-find": "1.0.0"
|
||||
}
|
||||
},
|
||||
"test-dependencies": {
|
||||
"direct": {},
|
||||
"indirect": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Elm SPA</title>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Chilanka">
|
||||
<link rel="stylesheet" href="./output.css">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Chilanka';
|
||||
}
|
||||
</style>
|
||||
<script src="./Main.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="mount"></div>
|
||||
<script>
|
||||
Elm.Main.init({node: document.getElementById("mount")});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
let
|
||||
briefcase = import <briefcase> {};
|
||||
pkgs = briefcase.third_party.pkgs;
|
||||
in pkgs.mkShell {
|
||||
buildInputs = with pkgs.elmPackages; [
|
||||
elm
|
||||
elm-format
|
||||
elm-live
|
||||
];
|
||||
}
|
||||
|
|
@ -1,465 +0,0 @@
|
|||
module Habits exposing (render)
|
||||
|
||||
import Browser
|
||||
import Date exposing (Date)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Html.Events exposing (..)
|
||||
import Set exposing (Set)
|
||||
import State exposing (HabitType(..))
|
||||
import Time exposing (Weekday(..))
|
||||
import UI
|
||||
import Utils exposing (Strategy(..))
|
||||
|
||||
|
||||
morning : List State.Habit
|
||||
morning =
|
||||
List.map
|
||||
(\( duration, x ) ->
|
||||
{ label = x
|
||||
, habitType = State.Morning
|
||||
, minutesDuration = duration
|
||||
}
|
||||
)
|
||||
[ ( 1, "Make bed" )
|
||||
, ( 2, "Brush teeth" )
|
||||
, ( 10, "Shower" )
|
||||
, ( 1, "Do push-ups" )
|
||||
, ( 10, "Meditate" )
|
||||
]
|
||||
|
||||
|
||||
evening : List State.Habit
|
||||
evening =
|
||||
List.map
|
||||
(\( duration, x ) ->
|
||||
{ label = x
|
||||
, habitType = State.Evening
|
||||
, minutesDuration = duration
|
||||
}
|
||||
)
|
||||
[ ( 30, "Read" )
|
||||
, ( 1, "Record in habit Journal" )
|
||||
]
|
||||
|
||||
|
||||
monday : List ( Int, String )
|
||||
monday =
|
||||
[ ( 90, "Bikram Yoga @ 17:00" )
|
||||
]
|
||||
|
||||
|
||||
tuesday : List ( Int, String )
|
||||
tuesday =
|
||||
[ ( 90, "Bikram Yoga @ 18:00" )
|
||||
]
|
||||
|
||||
|
||||
wednesday : List ( Int, String )
|
||||
wednesday =
|
||||
[ ( 5, "Shave" )
|
||||
, ( 90, "Bikram Yoga @ 17:00" )
|
||||
]
|
||||
|
||||
|
||||
thursday : List ( Int, String )
|
||||
thursday =
|
||||
[]
|
||||
|
||||
|
||||
friday : List ( Int, String )
|
||||
friday =
|
||||
[ ( 60, "Bikram Yoga @ 17:00" )
|
||||
, ( 3, "Take-out trash" )
|
||||
, ( 60, "Shop for groceries" )
|
||||
]
|
||||
|
||||
|
||||
saturday : List ( Int, String )
|
||||
saturday =
|
||||
[ ( 60, "Warm Yin Yoga @ 15:00" )
|
||||
]
|
||||
|
||||
|
||||
sunday : List ( Int, String )
|
||||
sunday =
|
||||
[ ( 1, "Shampoo" )
|
||||
, ( 5, "Shave" )
|
||||
, ( 1, "Trim nails" )
|
||||
, ( 1, "Combine trash cans" )
|
||||
, ( 10, "Mop tile and wood floors" )
|
||||
, ( 10, "Laundry" )
|
||||
, ( 5, "Vacuum bedroom" )
|
||||
, ( 5, "Dust surfaces" )
|
||||
, ( 5, "Clean mirrors" )
|
||||
, ( 5, "Clean desk" )
|
||||
]
|
||||
|
||||
|
||||
payday : List State.Habit
|
||||
payday =
|
||||
List.map
|
||||
(\( duration, x ) ->
|
||||
{ label = x
|
||||
, habitType = State.Payday
|
||||
, minutesDuration = duration
|
||||
}
|
||||
)
|
||||
[ ( 1, "Ensure \"Emergency\" fund has a balance of 1000 GBP" )
|
||||
, ( 1, "Open \"finances_2020\" Google Sheet" )
|
||||
, ( 1, "Settle up with Mimi on TransferWise" )
|
||||
, ( 1, "Adjust GBP:USD exchange rate" )
|
||||
, ( 1, "Adjust \"Stocks (after tax)\" to reflect amount Google sent" )
|
||||
, ( 1, "Add remaining cash to \"Carryover (cash)\"" )
|
||||
, ( 1, "Adjust \"Paycheck\" to reflect amount Google sent" )
|
||||
, ( 5, "In the \"International Xfer\" table, send \"Xfer amount\" from Monzo to USAA" )
|
||||
, ( 10, "Go to an ATM and extract the amount in \"ATM withdrawal\"" )
|
||||
, ( 0, "Await the TransferWise transaction to complete and pay MyFedLoan in USD" )
|
||||
]
|
||||
|
||||
|
||||
firstOfTheMonth : List State.Habit
|
||||
firstOfTheMonth =
|
||||
List.map
|
||||
(\( duration, x ) ->
|
||||
{ label = x
|
||||
, habitType = State.FirstOfTheMonth
|
||||
, minutesDuration = duration
|
||||
}
|
||||
)
|
||||
[ ( 10, "Create habit template in journal" )
|
||||
, ( 30, "Assess previous month's performance" )
|
||||
, ( 5, "Register for Bikram Yoga classes" )
|
||||
]
|
||||
|
||||
|
||||
firstOfTheYear : List State.Habit
|
||||
firstOfTheYear =
|
||||
List.map
|
||||
(\( duration, x ) ->
|
||||
{ label = x
|
||||
, habitType = State.FirstOfTheYear
|
||||
, minutesDuration = duration
|
||||
}
|
||||
)
|
||||
[ ( 60, "Write a post mortem for the previous year" )
|
||||
]
|
||||
|
||||
|
||||
habitTypes :
|
||||
{ includeMorning : Bool
|
||||
, includeEvening : Bool
|
||||
, date : Date
|
||||
}
|
||||
-> List State.HabitType
|
||||
habitTypes { includeMorning, includeEvening, date } =
|
||||
let
|
||||
habitTypePredicates : List ( State.HabitType, Date -> Bool )
|
||||
habitTypePredicates =
|
||||
[ ( Morning, \_ -> includeMorning )
|
||||
, ( DayOfWeek, \_ -> True )
|
||||
, ( Payday, \x -> Date.day x == 25 )
|
||||
, ( FirstOfTheMonth, \x -> Date.day x == 1 )
|
||||
, ( FirstOfTheYear, \x -> Date.day x == 1 && Date.monthNumber x == 1 )
|
||||
, ( Evening, \_ -> includeEvening )
|
||||
]
|
||||
in
|
||||
habitTypePredicates
|
||||
|> List.filter (\( _, predicate ) -> predicate date)
|
||||
|> List.map (\( habitType, _ ) -> habitType)
|
||||
|
||||
|
||||
habitsFor : State.HabitType -> Weekday -> List State.Habit
|
||||
habitsFor habitType weekday =
|
||||
case habitType of
|
||||
Morning ->
|
||||
morning
|
||||
|
||||
Evening ->
|
||||
evening
|
||||
|
||||
DayOfWeek ->
|
||||
let
|
||||
toHabit : List ( Int, String ) -> List State.Habit
|
||||
toHabit =
|
||||
List.map
|
||||
(\( duration, x ) ->
|
||||
{ label = x
|
||||
, habitType = State.DayOfWeek
|
||||
, minutesDuration = duration
|
||||
}
|
||||
)
|
||||
in
|
||||
case weekday of
|
||||
Mon ->
|
||||
toHabit monday
|
||||
|
||||
Tue ->
|
||||
toHabit tuesday
|
||||
|
||||
Wed ->
|
||||
toHabit wednesday
|
||||
|
||||
Thu ->
|
||||
toHabit thursday
|
||||
|
||||
Fri ->
|
||||
toHabit friday
|
||||
|
||||
Sat ->
|
||||
toHabit saturday
|
||||
|
||||
Sun ->
|
||||
toHabit sunday
|
||||
|
||||
Payday ->
|
||||
payday
|
||||
|
||||
FirstOfTheMonth ->
|
||||
firstOfTheMonth
|
||||
|
||||
FirstOfTheYear ->
|
||||
firstOfTheYear
|
||||
|
||||
|
||||
weekdayLabelFor : Weekday -> State.WeekdayLabel
|
||||
weekdayLabelFor weekday =
|
||||
case weekday of
|
||||
Mon ->
|
||||
"Monday"
|
||||
|
||||
Tue ->
|
||||
"Tuesday"
|
||||
|
||||
Wed ->
|
||||
"Wednesday"
|
||||
|
||||
Thu ->
|
||||
"Thursday"
|
||||
|
||||
Fri ->
|
||||
"Friday"
|
||||
|
||||
Sat ->
|
||||
"Saturday"
|
||||
|
||||
Sun ->
|
||||
"Sunday"
|
||||
|
||||
|
||||
timeRemaining : State.WeekdayLabel -> State.CompletedHabits -> List State.Habit -> Int
|
||||
timeRemaining weekdayLabel completed habits =
|
||||
habits
|
||||
|> List.indexedMap
|
||||
(\i { label, minutesDuration } ->
|
||||
if Set.member ( weekdayLabel, label ) completed then
|
||||
0
|
||||
|
||||
else
|
||||
minutesDuration
|
||||
)
|
||||
|> List.sum
|
||||
|
||||
|
||||
render : State.Model -> Html State.Msg
|
||||
render { today, visibleDayOfWeek, completed, includeMorning, includeEvening } =
|
||||
case ( today, visibleDayOfWeek ) of
|
||||
( Just todaysDate, Just visibleWeekday ) ->
|
||||
let
|
||||
todaysWeekday : Weekday
|
||||
todaysWeekday =
|
||||
Date.weekday todaysDate
|
||||
|
||||
habits : List State.Habit
|
||||
habits =
|
||||
habitTypes
|
||||
{ includeMorning = includeMorning
|
||||
, includeEvening = includeEvening
|
||||
, date = todaysDate
|
||||
}
|
||||
|> List.map (\habitType -> habitsFor habitType todaysWeekday)
|
||||
|> List.concat
|
||||
in
|
||||
div
|
||||
[ Utils.class
|
||||
[ Always "max-w-xl mx-auto py-6 px-6"
|
||||
, When (todaysWeekday /= visibleWeekday) "pt-20"
|
||||
]
|
||||
]
|
||||
[ header []
|
||||
[ if todaysWeekday /= visibleWeekday then
|
||||
div [ class "text-center w-full bg-blue-600 text-white fixed top-0 left-0 px-3 py-4" ]
|
||||
[ p [ class "py-2 inline pr-5" ]
|
||||
[ text "As you are not viewing today's habits, the UI is in read-only mode" ]
|
||||
, UI.button
|
||||
[ class "bg-blue-200 px-4 py-2 rounded text-blue-600 text-xs font-bold"
|
||||
, onClick State.ViewToday
|
||||
]
|
||||
[ text "View Today's Habits" ]
|
||||
]
|
||||
|
||||
else
|
||||
text ""
|
||||
, div [ class "flex center" ]
|
||||
[ UI.button
|
||||
[ class "w-1/4 text-gray-500"
|
||||
, onClick State.ViewPrevious
|
||||
]
|
||||
[ text "‹ previous" ]
|
||||
, h1 [ class "font-bold text-blue-500 text-3xl text-center w-full" ]
|
||||
[ text (weekdayLabelFor visibleWeekday) ]
|
||||
, UI.button
|
||||
[ class "w-1/4 text-gray-500"
|
||||
, onClick State.ViewNext
|
||||
]
|
||||
[ text "next ›" ]
|
||||
]
|
||||
]
|
||||
, if todaysWeekday == visibleWeekday then
|
||||
p [ class "text-center pt-1 pb-4" ]
|
||||
[ let
|
||||
t : Int
|
||||
t =
|
||||
timeRemaining (weekdayLabelFor todaysWeekday) completed habits
|
||||
in
|
||||
if t == 0 then
|
||||
text "Nothing to do!"
|
||||
|
||||
else
|
||||
text
|
||||
((habits
|
||||
|> timeRemaining (weekdayLabelFor todaysWeekday) completed
|
||||
|> String.fromInt
|
||||
)
|
||||
++ " minutes remaining"
|
||||
)
|
||||
]
|
||||
|
||||
else
|
||||
text ""
|
||||
, if todaysWeekday == visibleWeekday then
|
||||
div []
|
||||
[ UI.button
|
||||
[ onClick
|
||||
(if Set.size completed == 0 then
|
||||
State.DoNothing
|
||||
|
||||
else
|
||||
State.ClearAll
|
||||
)
|
||||
, Utils.class
|
||||
[ Always "ml-10 px-3"
|
||||
, If (Set.size completed == 0)
|
||||
"text-gray-500 cursor-not-allowed"
|
||||
"text-red-500 underline cursor-pointer"
|
||||
]
|
||||
]
|
||||
[ let
|
||||
numCompleted : Int
|
||||
numCompleted =
|
||||
habits
|
||||
|> List.indexedMap (\i { label } -> ( i, label ))
|
||||
|> List.filter
|
||||
(\( i, label ) ->
|
||||
Set.member
|
||||
( weekdayLabelFor todaysWeekday, label )
|
||||
completed
|
||||
)
|
||||
|> List.length
|
||||
in
|
||||
if numCompleted == 0 then
|
||||
text "Clear"
|
||||
|
||||
else
|
||||
text ("Clear (" ++ String.fromInt numCompleted ++ ")")
|
||||
]
|
||||
, UI.button
|
||||
[ onClick State.ToggleMorning
|
||||
, Utils.class
|
||||
[ Always "px-3 underline"
|
||||
, If includeMorning
|
||||
"text-gray-600"
|
||||
"text-blue-600"
|
||||
]
|
||||
]
|
||||
[ text
|
||||
(if includeMorning then
|
||||
"Hide Morning"
|
||||
|
||||
else
|
||||
"Show Morning"
|
||||
)
|
||||
]
|
||||
, UI.button
|
||||
[ Utils.class
|
||||
[ Always "px-3 underline"
|
||||
, If includeEvening
|
||||
"text-gray-600"
|
||||
"text-blue-600"
|
||||
]
|
||||
, onClick State.ToggleEvening
|
||||
]
|
||||
[ text
|
||||
(if includeEvening then
|
||||
"Hide Evening"
|
||||
|
||||
else
|
||||
"Show Evening"
|
||||
)
|
||||
]
|
||||
]
|
||||
|
||||
else
|
||||
text ""
|
||||
, ul [ class "pb-10" ]
|
||||
(habits
|
||||
|> List.indexedMap
|
||||
(\i { label, minutesDuration } ->
|
||||
let
|
||||
isCompleted : Bool
|
||||
isCompleted =
|
||||
Set.member ( weekdayLabelFor todaysWeekday, label ) completed
|
||||
in
|
||||
li [ class "text-xl list-disc ml-6" ]
|
||||
[ if todaysWeekday == visibleWeekday then
|
||||
UI.button
|
||||
[ class "py-5 px-3"
|
||||
, onClick
|
||||
(State.ToggleHabit
|
||||
(weekdayLabelFor todaysWeekday)
|
||||
label
|
||||
)
|
||||
]
|
||||
[ span
|
||||
[ Utils.class
|
||||
[ Always "text-white pt-1 px-2 rounded"
|
||||
, If isCompleted "bg-gray-400" "bg-blue-500"
|
||||
]
|
||||
]
|
||||
[ text (String.fromInt minutesDuration ++ " mins") ]
|
||||
, p
|
||||
[ Utils.class
|
||||
[ Always "inline pl-3"
|
||||
, When isCompleted "line-through text-gray-400"
|
||||
]
|
||||
]
|
||||
[ text label ]
|
||||
]
|
||||
|
||||
else
|
||||
UI.button
|
||||
[ class "py-5 px-3 cursor-not-allowed"
|
||||
, onClick State.DoNothing
|
||||
]
|
||||
[ text label ]
|
||||
]
|
||||
)
|
||||
)
|
||||
, footer [ class "bg-white text-sm text-center text-gray-500 fixed bottom-0 left-0 w-full py-4" ]
|
||||
[ p [] [ text "This app is brought to you by William Carroll." ]
|
||||
, p [] [ text "Client: Elm; Server: n/a" ]
|
||||
]
|
||||
]
|
||||
|
||||
( _, _ ) ->
|
||||
p [] [ text "Unable to display habits because we do not know what day of the week it is." ]
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
module Main exposing (main)
|
||||
|
||||
import Browser
|
||||
import Habits
|
||||
import Html exposing (..)
|
||||
import State
|
||||
import Time
|
||||
|
||||
|
||||
subscriptions : State.Model -> Sub State.Msg
|
||||
subscriptions model =
|
||||
-- once per minute
|
||||
Time.every (1000 * 60) (\_ -> State.MaybeAdjustWeekday)
|
||||
|
||||
|
||||
view : State.Model -> Html State.Msg
|
||||
view model =
|
||||
case model.view of
|
||||
State.Habits ->
|
||||
Habits.render model
|
||||
|
||||
|
||||
main =
|
||||
Browser.element
|
||||
{ init = \() -> State.init
|
||||
, subscriptions = subscriptions
|
||||
, update = State.update
|
||||
, view = view
|
||||
}
|
||||
|
|
@ -1,195 +0,0 @@
|
|||
module State exposing (..)
|
||||
|
||||
import Date exposing (Date)
|
||||
import Set exposing (Set)
|
||||
import Task
|
||||
import Time exposing (Weekday(..))
|
||||
|
||||
|
||||
type alias WeekdayLabel =
|
||||
String
|
||||
|
||||
|
||||
type alias HabitLabel =
|
||||
String
|
||||
|
||||
|
||||
type Msg
|
||||
= DoNothing
|
||||
| SetView View
|
||||
| ReceiveDate Date
|
||||
| ToggleHabit WeekdayLabel HabitLabel
|
||||
| MaybeAdjustWeekday
|
||||
| ViewToday
|
||||
| ViewPrevious
|
||||
| ViewNext
|
||||
| ClearAll
|
||||
| ToggleMorning
|
||||
| ToggleEvening
|
||||
|
||||
|
||||
type View
|
||||
= Habits
|
||||
|
||||
|
||||
type HabitType
|
||||
= Morning
|
||||
| Evening
|
||||
| DayOfWeek
|
||||
| Payday
|
||||
| FirstOfTheMonth
|
||||
| FirstOfTheYear
|
||||
|
||||
|
||||
type alias Habit =
|
||||
{ label : HabitLabel
|
||||
, habitType : HabitType
|
||||
, minutesDuration : Int
|
||||
}
|
||||
|
||||
|
||||
type alias CompletedHabits =
|
||||
Set ( WeekdayLabel, HabitLabel )
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ isLoading : Bool
|
||||
, view : View
|
||||
, today : Maybe Date
|
||||
, completed : CompletedHabits
|
||||
, visibleDayOfWeek : Maybe Weekday
|
||||
, includeMorning : Bool
|
||||
, includeEvening : Bool
|
||||
}
|
||||
|
||||
|
||||
previousDay : Weekday -> Weekday
|
||||
previousDay weekday =
|
||||
case weekday of
|
||||
Mon ->
|
||||
Sun
|
||||
|
||||
Tue ->
|
||||
Mon
|
||||
|
||||
Wed ->
|
||||
Tue
|
||||
|
||||
Thu ->
|
||||
Wed
|
||||
|
||||
Fri ->
|
||||
Thu
|
||||
|
||||
Sat ->
|
||||
Fri
|
||||
|
||||
Sun ->
|
||||
Sat
|
||||
|
||||
|
||||
nextDay : Weekday -> Weekday
|
||||
nextDay weekday =
|
||||
case weekday of
|
||||
Mon ->
|
||||
Tue
|
||||
|
||||
Tue ->
|
||||
Wed
|
||||
|
||||
Wed ->
|
||||
Thu
|
||||
|
||||
Thu ->
|
||||
Fri
|
||||
|
||||
Fri ->
|
||||
Sat
|
||||
|
||||
Sat ->
|
||||
Sun
|
||||
|
||||
Sun ->
|
||||
Mon
|
||||
|
||||
|
||||
{-| The initial state for the application.
|
||||
-}
|
||||
init : ( Model, Cmd Msg )
|
||||
init =
|
||||
( { isLoading = False
|
||||
, view = Habits
|
||||
, today = Nothing
|
||||
, completed = Set.empty
|
||||
, visibleDayOfWeek = Nothing
|
||||
, includeMorning = False
|
||||
, includeEvening = False
|
||||
}
|
||||
, Date.today |> Task.perform ReceiveDate
|
||||
)
|
||||
|
||||
|
||||
{-| Now that we have state, we need a function to change the state.
|
||||
-}
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update msg ({ today, visibleDayOfWeek, completed } as model) =
|
||||
case msg of
|
||||
DoNothing ->
|
||||
( model, Cmd.none )
|
||||
|
||||
SetView x ->
|
||||
( { model
|
||||
| view = x
|
||||
, isLoading = True
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
ReceiveDate x ->
|
||||
( { model
|
||||
| today = Just x
|
||||
, visibleDayOfWeek = Just (Date.weekday x)
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
ToggleHabit weekdayLabel habitLabel ->
|
||||
( { model
|
||||
| completed =
|
||||
if Set.member ( weekdayLabel, habitLabel ) completed then
|
||||
Set.remove ( weekdayLabel, habitLabel ) completed
|
||||
|
||||
else
|
||||
Set.insert ( weekdayLabel, habitLabel ) completed
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
MaybeAdjustWeekday ->
|
||||
( model, Date.today |> Task.perform ReceiveDate )
|
||||
|
||||
ViewToday ->
|
||||
( { model | visibleDayOfWeek = today |> Maybe.map Date.weekday }, Cmd.none )
|
||||
|
||||
ViewPrevious ->
|
||||
( { model
|
||||
| visibleDayOfWeek = visibleDayOfWeek |> Maybe.map previousDay
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
ViewNext ->
|
||||
( { model
|
||||
| visibleDayOfWeek = visibleDayOfWeek |> Maybe.map nextDay
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
ClearAll ->
|
||||
( { model | completed = Set.empty }, Cmd.none )
|
||||
|
||||
ToggleMorning ->
|
||||
( { model | includeMorning = not model.includeMorning }, Cmd.none )
|
||||
|
||||
ToggleEvening ->
|
||||
( { model | includeEvening = not model.includeEvening }, Cmd.none )
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
module UI exposing (..)
|
||||
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
|
||||
|
||||
button : List (Attribute msg) -> List (Html msg) -> Html msg
|
||||
button attrs children =
|
||||
Html.button ([ class "focus:outline-none" ] ++ attrs) children
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
module Utils exposing (..)
|
||||
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Maybe.Extra
|
||||
|
||||
|
||||
type Strategy
|
||||
= Always String
|
||||
| When Bool String
|
||||
| If Bool String String
|
||||
|
||||
|
||||
class : List Strategy -> Attribute msg
|
||||
class classes =
|
||||
classes
|
||||
|> List.map
|
||||
(\strategy ->
|
||||
case strategy of
|
||||
Always x ->
|
||||
Just x
|
||||
|
||||
When True x ->
|
||||
Just x
|
||||
|
||||
When False _ ->
|
||||
Nothing
|
||||
|
||||
If True x _ ->
|
||||
Just x
|
||||
|
||||
If False _ x ->
|
||||
Just x
|
||||
)
|
||||
|> Maybe.Extra.values
|
||||
|> String.join " "
|
||||
|> Html.Attributes.class
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
# Habit Screens
|
||||
|
||||
## MVP
|
||||
|
||||
One Android tablet mounted on my bedroom wall displaying habits for that day. I
|
||||
can toggle the done/todo states on each item by tapping it. There is no
|
||||
server. All of the habits are defined in the client-side codebase. The
|
||||
application is available online at wpcarro.dev.
|
||||
|
||||
## Ideal
|
||||
|
||||
Three Android tablets: one mounted in my bedroom, another in my bathroom, and a
|
||||
third in my kitchen. Each tablet has a view of the current state of the
|
||||
application and updates in soft real-time.
|
||||
|
||||
I track the rates at which I complete each habit and compile all of the metrics
|
||||
into a dashboard. When I move a habit from Saturday to Sunday or from Wednesday
|
||||
to Monday, it doesn't break the tracking.
|
||||
|
||||
When I complete a habit, it quickly renders some consistency information like
|
||||
"completing rate since Monday" and "length of current streak".
|
||||
|
||||
I don't consider this application that sensitive, but for security purposes I
|
||||
would like this application to be accessible within a private network. This is
|
||||
something I don't know too much about setting up, but I don't want anyone to be
|
||||
able to visit www.BillAndHisHabits.com and change the states of my habits and
|
||||
affect the tracking data. Nor do I want anyone to be able to make HTTP requests
|
||||
to my server to alter the state of the application without my permission.
|
||||
|
||||
## Client
|
||||
|
||||
Language: Elm
|
||||
|
||||
### Updates across devices
|
||||
|
||||
Instead of setting up sockets on my server and subscribing to them from the
|
||||
client, I think each device should poll the server once every second (or fewer)
|
||||
to maintain UI consistency.
|
||||
|
||||
## Server
|
||||
|
||||
Language: Haskell
|
||||
Database: SQLite
|
||||
Loading…
Add table
Add a link
Reference in a new issue