Organized the code in a more sane manner. Also some websocket work.

This commit is contained in:
Vidir Valberg Gudmundsson 2017-07-17 11:25:57 +02:00
parent a5ef793dcf
commit e8c0ab1941
14 changed files with 751 additions and 548 deletions

View file

@ -1,523 +0,0 @@
module Main exposing (..)
import Html exposing (Html, Attribute, div, input, text, li, ul, a, h4, label, i, span, hr, small, p)
import Html.Attributes exposing (class, classList, id, type_, for, style, href)
import Html.Events exposing (onClick)
import WebSocket exposing (listen)
import Json.Decode exposing (int, string, float, list, bool, Decoder)
import Json.Encode
import Json.Decode.Pipeline exposing (decode, required, optional, hardcoded)
import Markdown
import Navigation exposing (Location)
import UrlParser exposing ((</>))
main : Program Flags Model Msg
main =
Navigation.programWithFlags
OnLocationChange
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}
scheduleServer : String
scheduleServer =
"ws://localhost:8000/schedule/"
-- ROUTING
type Route
= OverviewRoute
| EventInstanceRoute EventInstanceSlug
| NotFoundRoute
matchers : UrlParser.Parser (Route -> a) a
matchers =
UrlParser.oneOf
[ UrlParser.map OverviewRoute UrlParser.top
, UrlParser.map EventInstanceRoute (UrlParser.s "event" </> UrlParser.string)
]
parseLocation : Location -> Route
parseLocation location =
case UrlParser.parseHash matchers location of
Just route ->
route
Nothing ->
NotFoundRoute
-- MODEL
type alias Model =
{ days : List Day
, eventInstances : List EventInstance
, eventLocations : List EventLocation
, eventTypes : List EventType
, flags : Flags
, activeDay : Day
, filter : Filter
, route : Route
}
type alias Filter =
{ eventTypes : List EventType
, eventLocations : List EventLocation
}
type alias Day =
{ day_name : String
, iso : String
, repr : String
}
type alias Speaker =
{ name : String
, url : String
}
type alias EventInstanceSlug =
String
type alias EventInstance =
{ title : String
, id : Int
, url : String
, abstract : String
, eventSlug : EventInstanceSlug
, eventType : String
, backgroundColor : String
, forgroundColor : String
, from : String
, to : String
, timeslots : Float
, location : String
, locationIcon : String
, speakers : List Speaker
, videoRecording : Bool
, videoUrl : String
}
emptyEventInstance : EventInstance
emptyEventInstance =
{ title = "This should not happen!"
, id = 0
, url = ""
, abstract = ""
, eventSlug = ""
, eventType = ""
, backgroundColor = ""
, forgroundColor = ""
, from = ""
, to = ""
, timeslots = 0.0
, location = ""
, locationIcon = ""
, speakers = []
, videoRecording = False
, videoUrl = ""
}
type alias EventLocation =
{ name : String
, slug : String
, icon : String
}
type alias EventType =
{ name : String
, slug : String
, color : String
, lightText : Bool
}
type alias Flags =
{ schedule_timeslot_length_minutes : Int
, schedule_midnight_offset_hours : Int
, ics_button_href : String
, camp_slug : String
}
allDaysDay : Day
allDaysDay =
Day "All Days" "" ""
init : Flags -> Location -> ( Model, Cmd Msg )
init flags location =
( Model [] [] [] [] flags allDaysDay (Filter [] []) (parseLocation location), sendInitMessage flags.camp_slug )
sendInitMessage : String -> Cmd Msg
sendInitMessage camp_slug =
WebSocket.send scheduleServer
(Json.Encode.encode 0
(Json.Encode.object
[ ( "action", Json.Encode.string "init" )
, ( "camp_slug", Json.Encode.string camp_slug )
]
)
)
-- UPDATE
type Msg
= NoOp
| WebSocketPayload String
| MakeActiveday Day
| ToggleEventTypeFilter EventType
| ToggleEventLocationFilter EventLocation
| OnLocationChange Location
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
NoOp ->
( model, Cmd.none )
WebSocketPayload str ->
let
newModel =
case Json.Decode.decodeString initDataDecoder str of
Ok m ->
m model.flags allDaysDay (Filter [] []) model.route
Err error ->
model
in
newModel ! []
MakeActiveday day ->
{ model | activeDay = day } ! []
ToggleEventTypeFilter eventType ->
let
eventTypesFilter =
if List.member eventType model.filter.eventTypes then
List.filter (\x -> x /= eventType) model.filter.eventTypes
else
eventType :: model.filter.eventTypes
currentFilter =
model.filter
newFilter =
{ currentFilter | eventTypes = eventTypesFilter }
in
{ model | filter = newFilter } ! []
ToggleEventLocationFilter eventLocation ->
let
eventLocationsFilter =
if List.member eventLocation model.filter.eventLocations then
List.filter (\x -> x /= eventLocation) model.filter.eventLocations
else
eventLocation :: model.filter.eventLocations
currentFilter =
model.filter
newFilter =
{ currentFilter | eventLocations = eventLocationsFilter }
in
{ model | filter = newFilter } ! []
OnLocationChange location ->
let
newRoute =
parseLocation location
in
{ model | route = newRoute } ! []
-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions model =
WebSocket.listen scheduleServer WebSocketPayload
-- DECODERS
dayDecoder : Decoder Day
dayDecoder =
decode Day
|> required "day_name" string
|> required "iso" string
|> required "repr" string
speakerDecoder : Decoder Speaker
speakerDecoder =
decode Speaker
|> required "name" string
|> required "url" string
eventInstanceDecoder : Decoder EventInstance
eventInstanceDecoder =
decode EventInstance
|> required "title" string
|> required "id" int
|> required "url" string
|> required "abstract" string
|> required "event_slug" string
|> required "event_type" string
|> required "bg-color" string
|> required "fg-color" string
|> required "from" string
|> required "to" string
|> required "timeslots" float
|> required "location" string
|> required "location_icon" string
|> required "speakers" (list speakerDecoder)
|> required "video_recording" bool
|> optional "video_url" string ""
eventLocationDecoder : Decoder EventLocation
eventLocationDecoder =
decode EventLocation
|> required "name" string
|> required "slug" string
|> required "icon" string
eventTypeDecoder : Decoder EventType
eventTypeDecoder =
decode EventType
|> required "name" string
|> required "slug" string
|> required "color" string
|> required "light_text" bool
initDataDecoder : Decoder (Flags -> Day -> Filter -> Route -> Model)
initDataDecoder =
decode Model
|> required "days" (list dayDecoder)
|> required "event_instances" (list eventInstanceDecoder)
|> required "event_locations" (list eventLocationDecoder)
|> required "event_types" (list eventTypeDecoder)
-- VIEW
dayButton : Day -> Day -> Html Msg
dayButton day activeDay =
a
[ classList
[ ( "btn", True )
, ( "btn-default", day /= activeDay )
, ( "btn-primary", day == activeDay )
]
, onClick (MakeActiveday day)
]
[ text day.day_name
]
view : Model -> Html Msg
view model =
div []
[ div [ class "row" ]
[ div [ id "schedule-days", class "btn-group" ]
(List.map (\day -> dayButton day model.activeDay) (allDaysDay :: model.days))
]
, hr [] []
, case model.route of
OverviewRoute ->
scheduleOverviewView model
EventInstanceRoute eventInstanceId ->
eventInstanceDetailView eventInstanceId model.eventInstances
NotFoundRoute ->
div [] [ text "Not found!" ]
]
eventInstanceDetailView : EventInstanceSlug -> List EventInstance -> Html Msg
eventInstanceDetailView eventInstanceId eventInstances =
let
eventInstance =
case List.head (List.filter (\e -> e.eventSlug == eventInstanceId) eventInstances) of
Just eventInstance ->
eventInstance
Nothing ->
emptyEventInstance
in
div [ class "row" ]
[ div [ class "col-sm-9" ]
[ a [ href "#" ]
[ text "Back"
]
, h4 [] [ text eventInstance.title ]
, p [] [ Markdown.toHtml [] eventInstance.abstract ]
]
, div
[ classList
[ ( "col-sm-3", True )
, ( "schedule-sidebar", True )
]
]
[ h4 [] [ text "Speakers" ]
]
]
scheduleOverviewView : Model -> Html Msg
scheduleOverviewView model =
div [ class "row" ]
[ div
[ classList
[ ( "col-sm-3", True )
, ( "col-sm-push-9", True )
, ( "schedule-sidebar", True )
, ( "schedule-filter", True )
]
]
[ h4 [] [ text "Filter" ]
, div [ class "form-group" ]
[ filterView "Type" model.eventTypes model.filter.eventTypes ToggleEventTypeFilter
, filterView "Location" model.eventLocations model.filter.eventLocations ToggleEventLocationFilter
]
]
, div
[ classList
[ ( "col-sm-9", True )
, ( "col-sm-pull-3", True )
]
]
(List.map (\day -> dayRowView day model) model.days)
]
dayRowView : Day -> Model -> Html Msg
dayRowView day model =
let
types =
List.map (\eventType -> eventType.slug)
(if List.isEmpty model.filter.eventTypes then
model.eventTypes
else
model.filter.eventTypes
)
locations =
List.map (\eventLocation -> eventLocation.slug)
(if List.isEmpty model.filter.eventLocations then
model.eventLocations
else
model.filter.eventLocations
)
filteredEventInstances =
List.filter
(\eventInstance ->
((String.slice 0 10 eventInstance.from) == day.iso)
&& List.member eventInstance.location locations
&& List.member eventInstance.eventType types
)
model.eventInstances
in
div []
[ h4 []
[ text day.repr ]
, div [ class "schedule-day-row" ]
(List.map dayEventInstanceView filteredEventInstances)
]
dayEventInstanceView : EventInstance -> Html Msg
dayEventInstanceView eventInstance =
a
[ class "event"
, href ("#event/" ++ eventInstance.eventSlug)
, style
[ ( "background-color", eventInstance.backgroundColor )
, ( "color", eventInstance.forgroundColor )
]
]
[ small []
[ text ((String.slice 11 16 eventInstance.from) ++ " - " ++ (String.slice 11 16 eventInstance.to)) ]
, i [ classList [ ( "fa", True ), ( "fa-" ++ eventInstance.locationIcon, True ), ( "pull-right", True ) ] ] []
, p
[]
[ text eventInstance.title ]
]
filterView :
String
-> List { a | name : String }
-> List { a | name : String }
-> ({ a | name : String } -> Msg)
-> Html Msg
filterView name possibleFilters currentFilters action =
div []
[ text (name ++ ":")
, ul [] (List.map (\filter -> filterChoiceView filter currentFilters action) possibleFilters)
]
filterChoiceView :
{ a | name : String }
-> List { a | name : String }
-> ({ a | name : String } -> Msg)
-> Html Msg
filterChoiceView filter currentFilters action =
let
active =
List.member filter currentFilters
notActive =
not active
in
li []
[ div
[ classList
[ ( "btn", True )
, ( "btn-default", True )
, ( "filter-choice-active", active )
]
, onClick (action filter)
]
[ span []
[ i [ classList [ ( "fa", True ), ( "fa-minus", active ), ( "fa-plus", notActive ) ] ] []
, text (" " ++ filter.name)
]
]
]

View file

@ -1,5 +1,5 @@
all: all:
elm-make Main.elm --warn --output ../src/program/static/js/elm_based_schedule.js elm-make src/Main.elm --warn --output ../src/program/static/js/elm_based_schedule.js
debug: debug:
elm-make Main.elm --debug --warn --output ../src/program/static/js/elm_based_schedule.js elm-make src/Main.elm --debug --warn --output ../src/program/static/js/elm_based_schedule.js

View file

@ -4,7 +4,7 @@
"repository": "https://github.com/user/project.git", "repository": "https://github.com/user/project.git",
"license": "BSD3", "license": "BSD3",
"source-directories": [ "source-directories": [
"." "src/"
], ],
"exposed-modules": [], "exposed-modules": [],
"dependencies": { "dependencies": {

95
schedule/src/Decoders.elm Normal file
View file

@ -0,0 +1,95 @@
module Decoders exposing (..)
-- Local modules
import Models exposing (Day, Speaker, Event, EventInstance, EventLocation, EventType, Model, Flags, Filter, Route(..))
-- Core modules
import Json.Decode exposing (int, string, float, list, bool, dict, Decoder)
import Json.Decode.Pipeline exposing (decode, required, optional, hardcoded)
-- DECODERS
type alias WebSocketAction =
{ action : String
}
webSocketActionDecoder : Decoder WebSocketAction
webSocketActionDecoder =
decode WebSocketAction
|> required "action" string
dayDecoder : Decoder Day
dayDecoder =
decode Day
|> required "day_name" string
|> required "iso" string
|> required "repr" string
speakerDecoder : Decoder Speaker
speakerDecoder =
decode Speaker
|> required "name" string
eventDecoder : Decoder Event
eventDecoder =
decode Event
|> required "title" string
|> required "slug" string
|> required "abstract" string
|> required "speakers" (list speakerDecoder)
eventInstanceDecoder : Decoder EventInstance
eventInstanceDecoder =
decode EventInstance
|> required "title" string
|> required "slug" string
|> required "id" int
|> required "url" string
|> required "event_slug" string
|> required "event_type" string
|> required "bg-color" string
|> required "fg-color" string
|> required "from" string
|> required "to" string
|> required "timeslots" float
|> required "location" string
|> required "location_icon" string
|> required "video_recording" bool
|> optional "video_url" string ""
eventLocationDecoder : Decoder EventLocation
eventLocationDecoder =
decode EventLocation
|> required "name" string
|> required "slug" string
|> required "icon" string
eventTypeDecoder : Decoder EventType
eventTypeDecoder =
decode EventType
|> required "name" string
|> required "slug" string
|> required "color" string
|> required "light_text" bool
initDataDecoder : Decoder (Flags -> Day -> Filter -> Route -> Model)
initDataDecoder =
decode Model
|> required "days" (list dayDecoder)
|> required "event_instances" (list eventInstanceDecoder)
|> required "event_locations" (list eventLocationDecoder)
|> required "event_types" (list eventTypeDecoder)
|> hardcoded []

41
schedule/src/Main.elm Normal file
View file

@ -0,0 +1,41 @@
module Main exposing (..)
-- Local modules
import Models exposing (..)
import Routing exposing (parseLocation)
import Update exposing (update)
import Messages exposing (Msg(..))
import WebSocketCalls exposing (scheduleServer, sendInitMessage)
import Views exposing (view)
-- External modules
import WebSocket exposing (listen)
import Navigation exposing (Location)
main : Program Flags Model Msg
main =
Navigation.programWithFlags
OnLocationChange
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}
init : Flags -> Location -> ( Model, Cmd Msg )
init flags location =
( Model [] [] [] [] [] flags allDaysDay (Filter [] []) (parseLocation location), sendInitMessage flags.camp_slug )
-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions model =
WebSocket.listen scheduleServer WebSocketPayload

19
schedule/src/Messages.elm Normal file
View file

@ -0,0 +1,19 @@
module Messages exposing (Msg(..))
-- Local modules
import Models exposing (Day, EventType, EventLocation)
-- External modules
import Navigation exposing (Location)
type Msg
= NoOp
| WebSocketPayload String
| MakeActiveday Day
| ToggleEventTypeFilter EventType
| ToggleEventLocationFilter EventLocation
| OnLocationChange Location

126
schedule/src/Models.elm Normal file
View file

@ -0,0 +1,126 @@
module Models exposing (..)
type Route
= OverviewRoute
| DayRoute DayIso
| EventInstanceRoute EventInstanceSlug
| NotFoundRoute
type alias Model =
{ days : List Day
, eventInstances : List EventInstance
, eventLocations : List EventLocation
, eventTypes : List EventType
, events : List Event
, flags : Flags
, activeDay : Day
, filter : Filter
, route : Route
}
type alias Filter =
{ eventTypes : List EventType
, eventLocations : List EventLocation
}
type alias DayIso =
String
type alias Day =
{ day_name : String
, iso : DayIso
, repr : String
}
type alias Speaker =
{ name : String
}
type alias EventSlug =
String
type alias EventInstanceSlug =
String
type alias EventInstance =
{ title : String
, slug : EventInstanceSlug
, id : Int
, url : String
, eventSlug : EventSlug
, eventType : String
, backgroundColor : String
, forgroundColor : String
, from : String
, to : String
, timeslots : Float
, location : String
, locationIcon : String
, videoRecording : Bool
, videoUrl : String
}
type alias Event =
{ title : String
, slug : EventSlug
, abstract : String
, speakers : List Speaker
}
emptyEventInstance : EventInstance
emptyEventInstance =
{ title = "This should not happen!"
, slug = "this-should-not-happen"
, id = 0
, url = ""
, eventSlug = ""
, eventType = ""
, backgroundColor = ""
, forgroundColor = ""
, from = ""
, to = ""
, timeslots = 0.0
, location = ""
, locationIcon = ""
, videoRecording = False
, videoUrl = ""
}
type alias EventLocation =
{ name : String
, slug : String
, icon : String
}
type alias EventType =
{ name : String
, slug : String
, color : String
, lightText : Bool
}
type alias Flags =
{ schedule_timeslot_length_minutes : Int
, schedule_midnight_offset_hours : Int
, ics_button_href : String
, camp_slug : String
}
allDaysDay : Day
allDaysDay =
Day "All Days" "" ""

30
schedule/src/Routing.elm Normal file
View file

@ -0,0 +1,30 @@
module Routing exposing (..)
-- Local modules
import Models exposing (DayIso, EventInstanceSlug, Route(..))
-- External modules
import Navigation exposing (Location)
import UrlParser exposing ((</>))
matchers : UrlParser.Parser (Route -> a) a
matchers =
UrlParser.oneOf
[ UrlParser.map OverviewRoute UrlParser.top
, UrlParser.map DayRoute (UrlParser.s "day" </> UrlParser.string)
, UrlParser.map EventInstanceRoute (UrlParser.s "event" </> UrlParser.string)
]
parseLocation : Location -> Route
parseLocation location =
case UrlParser.parseHash matchers location of
Just route ->
route
Nothing ->
NotFoundRoute

110
schedule/src/Update.elm Normal file
View file

@ -0,0 +1,110 @@
module Update exposing (update)
-- Local modules
import Models exposing (Model, Route(EventInstanceRoute), emptyEventInstance, allDaysDay, Filter)
import Messages exposing (Msg(..))
import Decoders exposing (webSocketActionDecoder, initDataDecoder, eventDecoder)
import Routing exposing (parseLocation)
import WebSocketCalls exposing (sendGetEventContent)
-- Core modules
import Json.Decode
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
NoOp ->
( model, Cmd.none )
WebSocketPayload str ->
let
newModel =
case Json.Decode.decodeString webSocketActionDecoder str of
Ok webSocketAction ->
case webSocketAction.action of
"init" ->
case Json.Decode.decodeString initDataDecoder str of
Ok m ->
m model.flags allDaysDay (Filter [] []) model.route
Err error ->
model
"get_event_content" ->
case Json.Decode.decodeString eventDecoder str of
Ok event ->
{ model | events = event :: model.events }
Err error ->
model
_ ->
model
Err error ->
model
in
newModel ! []
MakeActiveday day ->
{ model | activeDay = day } ! []
ToggleEventTypeFilter eventType ->
let
eventTypesFilter =
if List.member eventType model.filter.eventTypes then
List.filter (\x -> x /= eventType) model.filter.eventTypes
else
eventType :: model.filter.eventTypes
currentFilter =
model.filter
newFilter =
{ currentFilter | eventTypes = eventTypesFilter }
in
{ model | filter = newFilter } ! []
ToggleEventLocationFilter eventLocation ->
let
eventLocationsFilter =
if List.member eventLocation model.filter.eventLocations then
List.filter (\x -> x /= eventLocation) model.filter.eventLocations
else
eventLocation :: model.filter.eventLocations
currentFilter =
model.filter
newFilter =
{ currentFilter | eventLocations = eventLocationsFilter }
in
{ model | filter = newFilter } ! []
OnLocationChange location ->
let
newRoute =
parseLocation (Debug.log "location" location)
onLoadCmd =
case newRoute of
EventInstanceRoute eventInstanceSlug ->
let
eventInstance =
case List.head (List.filter (\x -> x.slug == eventInstanceSlug) model.eventInstances) of
Just eventInstance ->
eventInstance
Nothing ->
emptyEventInstance
in
sendGetEventContent model.flags.camp_slug eventInstance.eventSlug
_ ->
Cmd.none
in
{ model | route = newRoute } ! [ onLoadCmd ]

241
schedule/src/Views.elm Normal file
View file

@ -0,0 +1,241 @@
module Views exposing (..)
-- Local modules
import Models exposing (..)
import Messages exposing (Msg(..))
-- Core modules
import Html exposing (Html, Attribute, div, input, text, li, ul, a, h4, label, i, span, hr, small, p)
import Html.Attributes exposing (class, classList, id, type_, for, style, href)
import Html.Events exposing (onClick)
-- External modules
import Markdown
dayButton : Day -> Day -> Html Msg
dayButton day activeDay =
a
[ classList
[ ( "btn", True )
, ( "btn-default", day /= activeDay )
, ( "btn-primary", day == activeDay )
]
, onClick (MakeActiveday day)
, href
("#"
++ case day.iso of
"" ->
""
iso ->
"day/" ++ iso
)
]
[ text day.day_name
]
view : Model -> Html Msg
view model =
div []
[ div [ class "row" ]
[ div [ id "schedule-days", class "btn-group" ]
(List.map (\day -> dayButton day model.activeDay) (allDaysDay :: model.days))
]
, hr [] []
, case model.route of
OverviewRoute ->
scheduleOverviewView model
DayRoute dayIso ->
dayView dayIso model
EventInstanceRoute eventInstanceSlug ->
eventInstanceDetailView eventInstanceSlug model
NotFoundRoute ->
div [] [ text "Not found!" ]
]
dayView : DayIso -> Model -> Html Msg
dayView dayIso model =
div []
[ filterSideBar model
]
eventInstanceDetailView : EventInstanceSlug -> Model -> Html Msg
eventInstanceDetailView eventInstanceSlug model =
let
eventInstance =
case List.head (List.filter (\e -> e.slug == eventInstanceSlug) model.eventInstances) of
Just eventInstance ->
eventInstance
Nothing ->
emptyEventInstance
event =
case List.head (List.filter (\e -> e.slug == eventInstance.eventSlug) model.events) of
Just event ->
event
Nothing ->
{ title = "", slug = "", abstract = "", speakers = [] }
in
div [ class "row" ]
[ div [ class "col-sm-9" ]
[ a [ href "#" ]
[ text "Back"
]
, h4 [] [ text eventInstance.title ]
, p [] [ Markdown.toHtml [] event.abstract ]
, hr [] []
, h4 [] [ text "TODO: Show all instances here!" ]
]
, div
[ classList
[ ( "col-sm-3", True )
, ( "schedule-sidebar", True )
]
]
[ h4 [] [ text "Speakers" ]
]
]
filterSideBar : Model -> Html Msg
filterSideBar model =
div
[ classList
[ ( "col-sm-3", True )
, ( "col-sm-push-9", True )
, ( "schedule-sidebar", True )
, ( "schedule-filter", True )
]
]
[ h4 [] [ text "Filter" ]
, div [ class "form-group" ]
[ filterView "Type" model.eventTypes model.filter.eventTypes ToggleEventTypeFilter
, filterView "Location" model.eventLocations model.filter.eventLocations ToggleEventLocationFilter
]
]
scheduleOverviewView : Model -> Html Msg
scheduleOverviewView model =
div [ class "row" ]
[ filterSideBar model
, div
[ classList
[ ( "col-sm-9", True )
, ( "col-sm-pull-3", True )
]
]
(List.map (\day -> dayRowView day model) model.days)
]
dayRowView : Day -> Model -> Html Msg
dayRowView day model =
let
types =
List.map (\eventType -> eventType.slug)
(if List.isEmpty model.filter.eventTypes then
model.eventTypes
else
model.filter.eventTypes
)
locations =
List.map (\eventLocation -> eventLocation.slug)
(if List.isEmpty model.filter.eventLocations then
model.eventLocations
else
model.filter.eventLocations
)
filteredEventInstances =
List.filter
(\eventInstance ->
((String.slice 0 10 eventInstance.from) == day.iso)
&& List.member eventInstance.location locations
&& List.member eventInstance.eventType types
)
model.eventInstances
in
div []
[ h4 []
[ text day.repr ]
, div [ class "schedule-day-row" ]
(List.map dayEventInstanceView filteredEventInstances)
]
dayEventInstanceView : EventInstance -> Html Msg
dayEventInstanceView eventInstance =
a
[ class "event"
, href ("#event/" ++ eventInstance.slug)
, style
[ ( "background-color", eventInstance.backgroundColor )
, ( "color", eventInstance.forgroundColor )
]
]
[ small []
[ text ((String.slice 11 16 eventInstance.from) ++ " - " ++ (String.slice 11 16 eventInstance.to)) ]
, i [ classList [ ( "fa", True ), ( "fa-" ++ eventInstance.locationIcon, True ), ( "pull-right", True ) ] ] []
, p
[]
[ text eventInstance.title ]
]
filterView :
String
-> List { a | name : String }
-> List { a | name : String }
-> ({ a | name : String } -> Msg)
-> Html Msg
filterView name possibleFilters currentFilters action =
div []
[ text (name ++ ":")
, ul [] (List.map (\filter -> filterChoiceView filter currentFilters action) possibleFilters)
]
filterChoiceView :
{ a | name : String }
-> List { a | name : String }
-> ({ a | name : String } -> Msg)
-> Html Msg
filterChoiceView filter currentFilters action =
let
active =
List.member filter currentFilters
notActive =
not active
in
li []
[ div
[ classList
[ ( "btn", True )
, ( "btn-default", True )
, ( "filter-choice-active", active )
]
, onClick (action filter)
]
[ span []
[ i [ classList [ ( "fa", True ), ( "fa-minus", active ), ( "fa-plus", notActive ) ] ] []
, text (" " ++ filter.name)
]
]
]

View file

@ -0,0 +1,42 @@
module WebSocketCalls exposing (scheduleServer, sendInitMessage, sendGetEventContent)
-- Internal modules
import Models exposing (EventSlug)
import Messages exposing (Msg)
-- External modules
import WebSocket
import Json.Encode
scheduleServer : String
scheduleServer =
"ws://localhost:8000/schedule/"
sendInitMessage : String -> Cmd Msg
sendInitMessage camp_slug =
WebSocket.send scheduleServer
(Json.Encode.encode 0
(Json.Encode.object
[ ( "action", Json.Encode.string "init" )
, ( "camp_slug", Json.Encode.string camp_slug )
]
)
)
sendGetEventContent : String -> EventSlug -> Cmd Msg
sendGetEventContent campSlug eventSlug =
WebSocket.send scheduleServer
(Json.Encode.encode 0
(Json.Encode.object
[ ( "action", Json.Encode.string "get_event_content" )
, ( "event_slug", Json.Encode.string eventSlug )
, ( "camp_slug", Json.Encode.string campSlug )
]
)
)

View file

@ -1,7 +1,7 @@
from channels.generic.websockets import JsonWebsocketConsumer from channels.generic.websockets import JsonWebsocketConsumer
from camps.models import Camp from camps.models import Camp
from .models import EventInstance, Favorite, EventLocation, EventType from .models import Event, EventInstance, Favorite, EventLocation, EventType
class ScheduleConsumer(JsonWebsocketConsumer): class ScheduleConsumer(JsonWebsocketConsumer):
@ -21,32 +21,45 @@ class ScheduleConsumer(JsonWebsocketConsumer):
camp = Camp.objects.get(slug=camp_slug) camp = Camp.objects.get(slug=camp_slug)
days = list(map( days = list(map(
lambda day: lambda day:
{ 'repr': day.lower.strftime('%A %Y-%m-%d') {
, 'iso': day.lower.strftime('%Y-%m-%d') 'repr': day.lower.strftime('%A %Y-%m-%d'),
, 'day_name': day.lower.strftime('%A') 'iso': day.lower.strftime('%Y-%m-%d'),
'day_name': day.lower.strftime('%A'),
}, },
camp.get_days('camp') camp.get_days('camp')
)) ))
event_instances_query_set = EventInstance.objects.filter(event__camp=camp) event_instances_query_set = EventInstance.objects.filter(event__camp=camp)
event_instances = list([x.to_json(user=message.user) for x in event_instances_query_set]) event_instances = list([x.serialize(user=message.user) for x in event_instances_query_set])
event_locations_query_set = EventLocation.objects.filter(camp=camp) event_locations_query_set = EventLocation.objects.filter(camp=camp)
event_locations = list([x.to_json() for x in event_locations_query_set]) event_locations = list([x.serialize() for x in event_locations_query_set])
event_types_query_set = EventType.objects.filter() event_types_query_set = EventType.objects.filter()
event_types = list([x.to_json() for x in event_types_query_set]) event_types = list([x.serialize() for x in event_types_query_set])
data = { data = {
"action": "init",
"event_locations": event_locations, "event_locations": event_locations,
"event_types": event_types, "event_types": event_types,
"accept": True, "accept": True,
"event_instances": event_instances, "event_instances": event_instances,
"days": days, "days": days,
"action": "init"
} }
except Camp.DoesNotExist: except Camp.DoesNotExist:
pass pass
if action == 'get_event_content':
camp_slug = content.get('camp_slug')
event_slug = content.get('event_slug')
print(camp_slug)
print(event_slug)
event = Event.objects.get(
slug=event_slug,
camp__slug=camp_slug
)
data = event.serialize()
data['action'] = "get_event_content"
if action == 'favorite': if action == 'favorite':
event_instance_id = content.get('event_instance_id') event_instance_id = content.get('event_instance_id')
event_instance = EventInstance.objects.get(id=event_instance_id) event_instance = EventInstance.objects.get(id=event_instance_id)

View file

@ -311,7 +311,7 @@ class EventLocation(CampRelatedModel):
class Meta: class Meta:
unique_together = (('camp', 'slug'), ('camp', 'name')) unique_together = (('camp', 'slug'), ('camp', 'name'))
def to_json(self): def serialize(self):
return { return {
"name": self.name, "name": self.name,
"slug": self.slug, "slug": self.slug,
@ -357,7 +357,7 @@ class EventType(CreatedUpdatedModel):
def __str__(self): def __str__(self):
return self.name return self.name
def to_json(self): def serialize(self):
return { return {
"name": self.name, "name": self.name,
"slug": self.slug, "slug": self.slug,
@ -428,6 +428,18 @@ class Event(CampRelatedModel):
def get_absolute_url(self): def get_absolute_url(self):
return reverse_lazy('event_detail', kwargs={'camp_slug': self.camp.slug, 'slug': self.slug}) return reverse_lazy('event_detail', kwargs={'camp_slug': self.camp.slug, 'slug': self.slug})
def serialize(self):
data = {
'title': self.title,
'slug': self.slug,
'abstract': self.abstract,
'speakers': [
speaker.serialize()
for speaker in self.speakers.all()
],
}
return data
class EventInstance(CampRelatedModel): class EventInstance(CampRelatedModel):
""" An instance of an event """ """ An instance of an event """
@ -490,24 +502,15 @@ class EventInstance(CampRelatedModel):
ievent['location'] = icalendar.vText(self.location.name) ievent['location'] = icalendar.vText(self.location.name)
return ievent return ievent
def to_json(self, user=None): def serialize(self, user=None):
parser = CommonMark.Parser()
renderer = CommonMark.HtmlRenderer()
ast = parser.parse(self.event.abstract)
abstract = renderer.render(ast)
data = { data = {
'title': self.event.title, 'title': self.event.title,
'slug': self.event.slug + '-' + str(self.id),
'event_slug': self.event.slug, 'event_slug': self.event.slug,
'abstract': abstract,
'from': self.when.lower.astimezone().isoformat(), 'from': self.when.lower.astimezone().isoformat(),
'to': self.when.upper.astimezone().isoformat(), 'to': self.when.upper.astimezone().isoformat(),
'url': str(self.event.get_absolute_url()), 'url': str(self.event.get_absolute_url()),
'id': self.id, 'id': self.id,
'speakers': [
{'name': speaker.name, 'url': str(speaker.get_absolute_url())}
for speaker in self.event.speakers.all()
],
'bg-color': self.event.event_type.color, 'bg-color': self.event.event_type.color,
'fg-color': '#fff' if self.event.event_type.light_text else '#000', 'fg-color': '#fff' if self.event.event_type.light_text else '#000',
'event_type': self.event.event_type.slug, 'event_type': self.event.event_type.slug,
@ -604,6 +607,12 @@ class Speaker(CampRelatedModel):
def get_absolute_url(self): def get_absolute_url(self):
return reverse_lazy('speaker_detail', kwargs={'camp_slug': self.camp.slug, 'slug': self.slug}) return reverse_lazy('speaker_detail', kwargs={'camp_slug': self.camp.slug, 'slug': self.slug})
def serialize(self):
data = {
'name': self.name,
}
return data
class Favorite(models.Model): class Favorite(models.Model):
user = models.ForeignKey('auth.User', related_name='favorites') user = models.ForeignKey('auth.User', related_name='favorites')

View file

@ -11,7 +11,7 @@
<script> <script>
var container = document.getElementById('schedule-container'); var container = document.getElementById('schedule-container');
Elm.Main.embed( var elm_app = Elm.Main.embed(
container, container,
{ 'schedule_timeslot_length_minutes': Number('{{ schedule_timeslot_length_minutes }}') { 'schedule_timeslot_length_minutes': Number('{{ schedule_timeslot_length_minutes }}')
, 'schedule_midnight_offset_hours': Number('{{ schedule_midnight_offset_hours }}') , 'schedule_midnight_offset_hours': Number('{{ schedule_midnight_offset_hours }}')