Homework-4/src/ClassSchedule/View.elm

270 lines
9.1 KiB
Elm
Raw Normal View History

module ClassSchedule.View exposing (..)
import ClassSchedule.Model exposing (..)
2021-05-16 17:02:57 -07:00
import Html exposing (Html, Attribute, div, text, table, td, th, tr, span, input, h1)
2021-05-16 21:00:04 -07:00
import Html.Attributes exposing (class, classList, type_, style)
2021-05-16 17:02:57 -07:00
import Html.Events exposing (onClick)
import Tuple exposing (..)
import Dict exposing (..)
2021-05-16 17:02:57 -07:00
import FeatherIcons
iconButton : String -> FeatherIcons.Icon -> String -> List (Attribute Msg) -> Html Msg
iconButton c i s attrs = span ([ class "button", class ("color-" ++ c) ] ++ attrs)
[ FeatherIcons.toHtml [] i, text s ]
viewDept : Department -> String
viewDept d =
case d of
ComputerScience -> "CS"
Mathematics -> "MTH"
Art -> "ART"
Biology -> "BIO"
viewCrn : Crn -> String
viewCrn (d, i) = viewDept d ++ " " ++ (String.pad 3 '0' <| String.fromInt i)
viewTimeNumber : Int -> String
viewTimeNumber i = String.pad 2 '0' <| String.fromInt i
viewDayHalf : DayHalf -> String
viewDayHalf dh =
case dh of
AM -> "AM"
PM -> "PM"
viewTime : Time -> String
viewTime (m, h, dh) = viewTimeNumber m ++ ":" ++ viewTimeNumber h ++ viewDayHalf dh
viewTimeRange : (Time, Time) -> String
2021-05-16 21:00:04 -07:00
viewTimeRange (t1, t2) = viewTime t1 ++ "-" ++ viewTime t2
viewDayCode : DayOfWeek -> String
viewDayCode dw =
case dw of
Monday -> "M"
Tuesday -> "T"
Wednesday -> "W"
Thursday -> "R"
Friday -> "F"
Saturday -> "S"
Sunday -> "U"
unique : List eq -> List eq
unique l =
case l of
[] -> []
(x::xs) -> x :: unique (List.filter ((/=) x) xs)
extractTimes : Course -> List (Time, Time)
extractTimes c =
2021-05-16 17:02:57 -07:00
let extractTime (d, t1, t2) = (t1, t2)
in unique <| List.map extractTime (c.times)
extractClasses : Course -> (Time, Time) -> List DayOfWeek
extractClasses c (t1, t2) =
let matches (d, tt1, tt2) = if tt1 == t1 && tt2 == t2 then Just d else Nothing
in List.filterMap matches (c.times)
extractTimeCodes : Course -> List String
extractTimeCodes c =
let
fromTime tr =
let dayCodes = String.concat <| List.map viewDayCode <| extractClasses c tr
in dayCodes ++ " " ++ viewTimeRange tr
in List.map fromTime <| extractTimes c
2021-05-16 17:02:57 -07:00
viewClass : Maybe Int -> Int -> (CourseStatus, Course) -> Html Msg
viewClass sel i (cs, c) =
let
isSelected = sel == Just i
addedIndicator =
case cs of
Added -> [ FeatherIcons.check |> FeatherIcons.toHtml [] ]
NotAdded -> []
in
tr [ class "hoverable", onClick (SelectCourse i), classList [("selected", isSelected)] ]
[ td [] addedIndicator
, td [] [ text <| viewCrn (c.crn) ]
, td [] [ text <| c.name ]
, td [] [ text <| String.join ", " <| c.instructors ]
, td [] [ text <| String.join ", " <| extractTimeCodes c ]
, td [] <|
case (isSelected, cs) of
(True, NotAdded) -> [ iconButton "green" (FeatherIcons.check) "Add" [ onClick (AddCourse i)] ]
(True, Added) -> [ iconButton "red" (FeatherIcons.x) "Remove" [onClick (RemoveCourse i)] ]
_ -> []
]
2021-05-16 17:02:57 -07:00
viewClassTable : Maybe Int -> List (CourseStatus, Course) -> Html Msg
viewClassTable sel =
let
header = tr []
2021-05-16 17:02:57 -07:00
[ th [] [ ]
, th [] [ text "Crn." ]
, th [] [ text "Course Name" ]
, th [] [ text "Instructors" ]
, th [] [ text "Times" ]
2021-05-16 17:02:57 -07:00
, th [] []
]
in
2021-05-16 17:02:57 -07:00
div [ class "table-wrapper" ] << List.singleton << table [ class "class-table" ] << (::) header << List.indexedMap (viewClass sel)
viewToolbar : Model -> Html Msg
viewToolbar m = div []
[ span [] [ text "Search: " , input [ type_ "text" ] [] ]
]
viewClassList : Model -> Html Msg
viewClassList m = div []
[ viewToolbar m
, case get (m.term) (m.terms) of
2021-05-16 17:02:57 -07:00
Just cs -> viewClassTable (m.selected) cs
Nothing -> text "Please select a term!"
]
2021-05-16 21:00:04 -07:00
hour24 : Time -> Int
hour24 (h, _, th) = if th == AM then h else h + 12
from24 : Int -> Time
from24 i = (modBy 12 i, 0, if i >= 12 then PM else AM)
timeSortable : Time -> Int
timeSortable (h, m, th) = hour24 (h,m,th) * 60 + m
rangeSortable : (Time, Time) -> (Int, Int)
rangeSortable (t1, t2) = (timeSortable t1, -(timeSortable t2))
toHourCount : Time -> Time -> Float
toHourCount t1 t2 = toFloat (timeSortable t2 - timeSortable t1) / 60
maxTime : Time -> Time -> Time
maxTime t1 t2 = if timeSortable t1 > timeSortable t2 then t1 else t2
index : List a -> List (Int, a)
index =
let
indexWith n l =
case l of
[] -> []
(x::xs) -> (n,x) :: indexWith (n+1) xs
in indexWith 0
type alias SelectionReason = (CourseStatus, Bool)
findDay : Maybe Int -> DayOfWeek -> List (CourseStatus, Course) -> List (Course, SelectionReason, (Time, Time))
findDay ms dw l =
let
find (cs, b, c) = List.filter (\(ndw, _, _) -> ndw == dw) c.times
|> List.map (\(_, t1, t2) -> (c, (cs, b), (t1, t2)))
active (i, (cs, c)) =
if cs == Added || ms == Just i
then Just (cs, ms == Just i, c)
else Nothing
in
index l
|> List.filterMap active
|> List.concatMap find
findGroups : List (Course, SelectionReason, (Time, Time)) -> List (List (Course, SelectionReason, (Time, Time)))
findGroups cs =
let
findGroupsRec acc mt rcs =
case rcs of
[] ->
case acc of
[] -> []
_ -> [List.reverse acc]
((c, sr, (t1, t2))::trcs) ->
if timeSortable mt > timeSortable t1
then findGroupsRec ((c,sr,(t1,t2))::acc) (maxTime t2 mt) trcs
else List.reverse acc :: findGroupsRec [(c, sr, (t1, t2))] (maxTime t2 mt) trcs
in
findGroupsRec [] (0, 0, AM) <| List.sortBy (\(_, _, r) -> rangeSortable r) cs
type alias CellInfo =
{ width : Float
, height : Float
, verticalOffset : Float
, index : Int
, reason : SelectionReason
}
const : a -> b -> a
const x _ = x
mergeDicts : (a -> a -> a) -> Dict comparable a -> Dict comparable a -> Dict comparable a
mergeDicts f d1 d2 = Dict.merge Dict.insert (\k a b -> Dict.insert k (f a b)) Dict.insert d1 d2 Dict.empty
groupDims : List (Course, SelectionReason, (Time, Time)) -> Dict Int (List (Course, CellInfo))
groupDims l =
let
width = 100.0 / toFloat (List.length l)
courseInfo i (c, cr, (t1, t2)) = Dict.singleton (hour24 t1 - 8)
<| List.singleton
<| pair c
<|
{ width = width
, height = 100 * toHourCount t1 t2
, verticalOffset = let (_, m, _) = t1 in 100 * toFloat m / 60
, index = i
, reason = cr
}
in
List.foldl (mergeDicts (++)) Dict.empty <| List.indexedMap courseInfo l
days : List DayOfWeek
days = [Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday]
buildCellDict : Maybe Int -> List (CourseStatus, Course) -> Dict Int (Dict Int (List (Course, CellInfo)))
buildCellDict ms l =
let
singleDay i dw = findDay ms dw l
|> findGroups
|> List.map groupDims
|> List.foldl (mergeDicts (++)) Dict.empty
|> Dict.singleton i
in
List.foldl Dict.union Dict.empty <| List.indexedMap singleDay days
viewCourseBlock : CellInfo -> String -> Html Msg
viewCourseBlock ci s =
let
ps f = String.fromFloat f ++ "%"
nw = if (ci.width) == 100.0 then 100.0 else (ci.width)*0.95
(ad, sel) = ci.reason
in div
[ class "course-block"
, style "width" (ps nw)
, style "height" (ps (ci.height))
, style "left" (ps (toFloat ci.index * ci.width))
, style "top" (ps (ci.verticalOffset))
, classList [("selected", sel), ("color-green", ad == Added)]
]
[ text s ]
viewTableDayHeader : DayOfWeek -> Html Msg
viewTableDayHeader dw = span [ class "table-day-header" ] [ text <| viewDayCode dw ]
viewClassSchedule : Model -> Html Msg
2021-05-16 21:00:04 -07:00
viewClassSchedule m =
let
header = span [] [] :: List.map viewTableDayHeader days
cellDict = buildCellDict m.selected (Maybe.withDefault [] <| Dict.get m.term m.terms)
times = List.range 0 10
time h = List.map (\w -> Maybe.withDefault [] <| Maybe.andThen (Dict.get h) <| Dict.get w cellDict) (List.range 0 6)
container xs = div [ class "column-container" ] <|
List.map (\(c, ci) -> viewCourseBlock ci (viewCrn c.crn)) xs
in
Debug.log (Debug.toString cellDict) <| div [ class "table-wrapper", style "width" "100%", style "padding" "1rem" ]
[ div [ class "week-grid" ] <|
header ++ (List.concatMap (\i -> span [ style "padding-right" "1rem" ] [ text <| viewTime <| from24 (i+8)] :: List.map container (time i)) times)
]
viewModel : Model -> Html Msg
viewModel m = div [ class "main" ]
[ h1 [] [ text "Oregon State University Course Schedule" ]
, div [ class "split-pane" ]
[ div [ class "split-elem" ] [ viewClassList m ]
, div [ class "split-elem" ] [ viewClassSchedule m ]
]
]