Cleanups. Adding Speaker detail.

This commit is contained in:
Vidir Valberg Gudmundsson 2017-08-02 22:20:38 +02:00
parent a447ca476f
commit d4d7fad439
13 changed files with 177 additions and 44 deletions

View file

@ -44,6 +44,10 @@ speakerDecoder : Decoder Speaker
speakerDecoder = speakerDecoder =
decode Speaker decode Speaker
|> required "name" string |> required "name" string
|> required "slug" string
|> required "biography" string
|> optional "large_picture_url" (nullable string) Nothing
|> optional "small_picture_url" (nullable string) Nothing
eventDecoder : Decoder Event eventDecoder : Decoder Event
@ -52,7 +56,7 @@ eventDecoder =
|> required "title" string |> required "title" string
|> required "slug" string |> required "slug" string
|> required "abstract" string |> required "abstract" string
|> required "speakers" (list speakerDecoder) |> required "speaker_slugs" (list string)
|> required "video_recording" bool |> required "video_recording" bool
|> optional "video_url" string "" |> optional "video_url" string ""
|> required "event_type" string |> required "event_type" string
@ -115,3 +119,4 @@ initDataDecoder =
|> required "event_instances" (list eventInstanceDecoder) |> required "event_instances" (list eventInstanceDecoder)
|> required "event_locations" (list eventLocationDecoder) |> required "event_locations" (list eventLocationDecoder)
|> required "event_types" (list eventTypeDecoder) |> required "event_types" (list eventTypeDecoder)
|> required "speakers" (list speakerDecoder)

View file

@ -37,7 +37,7 @@ init flags location =
Filter [] [] [] Filter [] [] []
model = model =
Model [] [] [] [] [] flags emptyFilter location currentRoute False Model [] [] [] [] [] [] flags emptyFilter location currentRoute False
in in
model ! [ sendInitMessage flags.camp_slug flags.websocket_server ] model ! [ sendInitMessage flags.camp_slug flags.websocket_server ]

View file

@ -2,7 +2,7 @@ module Messages exposing (Msg(..))
-- Local modules -- Local modules
import Models exposing (Day, EventType, EventLocation, EventInstance) import Models exposing (Day, EventType, EventLocation, EventInstance, VideoRecordingFilter)
-- External modules -- External modules
@ -15,6 +15,6 @@ type Msg
| WebSocketPayload String | WebSocketPayload String
| ToggleEventTypeFilter EventType | ToggleEventTypeFilter EventType
| ToggleEventLocationFilter EventLocation | ToggleEventLocationFilter EventLocation
| ToggleVideoRecordingFilter { name : String, slug : String, filter : EventInstance -> Bool } | ToggleVideoRecordingFilter VideoRecordingFilter
| OnLocationChange Location | OnLocationChange Location
| BackInHistory | BackInHistory

View file

@ -10,11 +10,36 @@ import Date exposing (Date, now)
import Navigation exposing (Location) import Navigation exposing (Location)
type alias EventSlug =
String
type alias EventInstanceSlug =
String
type alias SpeakerSlug =
String
type alias DaySlug =
String
type alias FilterQuery =
String
-- Route is defined here rather than in Routing.elm due to it being used in Model. If it were in Routing.elm we would have a circular dependency.
type Route type Route
= OverviewRoute = OverviewRoute
| OverviewFilteredRoute String | OverviewFilteredRoute FilterQuery
| DayRoute String | DayRoute DaySlug
| EventRoute EventSlug | EventRoute EventSlug
| SpeakerRoute SpeakerSlug
| NotFoundRoute | NotFoundRoute
@ -24,6 +49,7 @@ type alias Model =
, eventInstances : List EventInstance , eventInstances : List EventInstance
, eventLocations : List EventLocation , eventLocations : List EventLocation
, eventTypes : List EventType , eventTypes : List EventType
, speakers : List Speaker
, flags : Flags , flags : Flags
, filter : Filter , filter : Filter
, location : Location , location : Location
@ -35,10 +61,14 @@ type alias Model =
type alias Filter = type alias Filter =
{ eventTypes : List EventType { eventTypes : List EventType
, eventLocations : List EventLocation , eventLocations : List EventLocation
, videoRecording : List { name : String, slug : String, filter : EventInstance -> Bool } , videoRecording : List VideoRecordingFilter
} }
type alias VideoRecordingFilter =
{ name : String, slug : String, filter : EventInstance -> Bool }
type alias Day = type alias Day =
{ day_name : String { day_name : String
, date : Date , date : Date
@ -48,17 +78,13 @@ type alias Day =
type alias Speaker = type alias Speaker =
{ name : String { name : String
, slug : SpeakerSlug
, biography : String
, largePictureUrl : Maybe String
, smallPictureUrl : Maybe String
} }
type alias EventSlug =
String
type alias EventInstanceSlug =
String
type alias EventInstance = type alias EventInstance =
{ title : String { title : String
, slug : EventInstanceSlug , slug : EventInstanceSlug
@ -83,7 +109,7 @@ type alias Event =
{ title : String { title : String
, slug : EventSlug , slug : EventSlug
, abstract : String , abstract : String
, speakers : List Speaker , speakerSlugs : List SpeakerSlug
, videoRecording : Bool , videoRecording : Bool
, videoUrl : String , videoUrl : String
, eventType : String , eventType : String

View file

@ -14,16 +14,16 @@ import UrlParser exposing (Parser, (</>), oneOf, map, top, s, string, parseHash)
{-- {--
URLs to support: URLs to support:
- # - #/
This show the overview of the schedule This show the overview of the schedule
- #?type={types},location={locations},video={no,yes,link} - #/?type={types},location={locations},video={not-to-be-recorded,to-be-recorded,has-recording}
This is the overview, just with filters enable This is the overview, just with filters enable
- #day/{year}-{month}-{day} - #/day/{year}-{month}-{day}
Show a particular day Show a particular day
- #event/{slug} - #/event/{slug}
Show a particular event Show a particular event
--} --}
@ -36,6 +36,7 @@ matchers =
, map OverviewFilteredRoute (top </> string) , map OverviewFilteredRoute (top </> string)
, map DayRoute (s "day" </> string) , map DayRoute (s "day" </> string)
, map EventRoute (s "event" </> string) , map EventRoute (s "event" </> string)
, map SpeakerRoute (s "speaker" </> string)
] ]
@ -43,3 +44,29 @@ parseLocation : Location -> Route
parseLocation location = parseLocation location =
parseHash matchers location parseHash matchers location
|> Maybe.withDefault NotFoundRoute |> Maybe.withDefault NotFoundRoute
routeToString : Route -> String
routeToString route =
let
parts =
case route of
OverviewRoute ->
[]
OverviewFilteredRoute query ->
[ query ]
DayRoute iso ->
[ "day", iso ]
EventRoute slug ->
[ "event", slug ]
SpeakerRoute slug ->
[ "speaker", slug ]
NotFoundRoute ->
[]
in
"#/" ++ String.join "/" parts

View file

@ -7,6 +7,7 @@ import Messages exposing (Msg(..))
import Views.DayPicker exposing (dayPicker) import Views.DayPicker exposing (dayPicker)
import Views.DayView exposing (dayView) import Views.DayView exposing (dayView)
import Views.EventDetail exposing (eventDetailView) import Views.EventDetail exposing (eventDetailView)
import Views.SpeakerDetail exposing (speakerDetailView)
import Views.ScheduleOverview exposing (scheduleOverviewView) import Views.ScheduleOverview exposing (scheduleOverviewView)
@ -48,6 +49,9 @@ view model =
EventRoute eventSlug -> EventRoute eventSlug ->
eventDetailView eventSlug model eventDetailView eventSlug model
SpeakerRoute speakerSlug ->
speakerDetailView speakerSlug model
NotFoundRoute -> NotFoundRoute ->
div [] [ text "Not found!" ] div [] [ text "Not found!" ]
] ]

View file

@ -4,6 +4,7 @@ module Views.DayPicker exposing (..)
import Models exposing (..) import Models exposing (..)
import Messages exposing (Msg(..)) import Messages exposing (Msg(..))
import Routing exposing (routeToString)
-- Core modules -- Core modules
@ -51,7 +52,7 @@ dayPicker model =
, ( "btn-default", not isAllDaysActive ) , ( "btn-default", not isAllDaysActive )
, ( "btn-primary", isAllDaysActive ) , ( "btn-primary", isAllDaysActive )
] ]
, href ("#") , href <| routeToString OverviewRoute
] ]
[ text "All Days" [ text "All Days"
] ]
@ -78,7 +79,7 @@ dayButton day activeDate =
, ( "btn-default", not isActive ) , ( "btn-default", not isActive )
, ( "btn-primary", isActive ) , ( "btn-primary", isActive )
] ]
, href ("#day/" ++ (Date.Extra.toFormattedString "y-MM-dd" day.date)) , href <| routeToString <| DayRoute <| Date.Extra.toFormattedString "y-MM-dd" day.date
] ]
[ text day.day_name [ text day.day_name
] ]

View file

@ -3,7 +3,8 @@ module Views.DayView exposing (dayView)
-- Local modules -- Local modules
import Messages exposing (Msg(..)) import Messages exposing (Msg(..))
import Models exposing (Model, Day, EventInstance, EventLocation) import Models exposing (Model, Day, EventInstance, EventLocation, Route(EventRoute))
import Routing exposing (routeToString)
-- Core modules -- Core modules
@ -236,7 +237,7 @@ eventInstanceBlock offset numberInGroup ( eventInstance, lefts ) =
, ( "background-color", eventInstance.backgroundColor ) , ( "background-color", eventInstance.backgroundColor )
, ( "color", eventInstance.forgroundColor ) , ( "color", eventInstance.forgroundColor )
] ]
, href ("#event/" ++ eventInstance.eventSlug) , href <| routeToString <| EventRoute eventInstance.eventSlug
] ]
[ p [] [ text ((Date.Extra.toFormattedString "HH:mm" eventInstance.from) ++ " " ++ eventInstance.title) ] [ p [] [ text ((Date.Extra.toFormattedString "HH:mm" eventInstance.from) ++ " " ++ eventInstance.title) ]
] ]

View file

@ -4,6 +4,7 @@ module Views.EventDetail exposing (eventDetailView)
import Messages exposing (Msg(..)) import Messages exposing (Msg(..))
import Models exposing (..) import Models exposing (..)
import Routing exposing (routeToString)
-- Core modules -- Core modules
@ -27,15 +28,12 @@ eventDetailView eventSlug model =
model.events model.events
|> List.filter (\e -> e.slug == eventSlug) |> List.filter (\e -> e.slug == eventSlug)
|> List.head |> List.head
eventInstances =
List.filter (\instance -> instance.eventSlug == eventSlug) model.eventInstances
in in
case event of case event of
Just event -> Just event ->
div [ class "row" ] div [ class "row" ]
[ eventDetailContent event [ eventDetailContent event
, eventDetailSidebar event eventInstances , eventDetailSidebar event model
] ]
Nothing -> Nothing ->
@ -53,12 +51,52 @@ eventDetailContent event =
, text " Back" , text " Back"
] ]
, h3 [] [ text event.title ] , h3 [] [ text event.title ]
, p [] [ Markdown.toHtml [] event.abstract ] , div [] [ Markdown.toHtml [] event.abstract ]
] ]
eventDetailSidebar : Event -> List EventInstance -> Html Msg getSpeakersFromSlugs : List Speaker -> List SpeakerSlug -> List Speaker -> List Speaker
eventDetailSidebar event eventInstances = getSpeakersFromSlugs speakers slugs collectedSpeakers =
case speakers of
[] ->
collectedSpeakers
speaker :: rest ->
let
foundSlug =
slugs
|> List.filter (\slug -> slug == speaker.slug)
|> List.head
foundSpeaker =
case foundSlug of
Just slug ->
[ speaker ]
Nothing ->
[]
newSlugs =
case foundSlug of
Just slug ->
List.filter (\x -> x /= slug) slugs
Nothing ->
slugs
newCollectedSpeakers =
collectedSpeakers ++ foundSpeaker
in
case slugs of
[] ->
collectedSpeakers
_ ->
getSpeakersFromSlugs rest newSlugs newCollectedSpeakers
eventDetailSidebar : Event -> Model -> Html Msg
eventDetailSidebar event model =
let let
videoRecordingLink = videoRecordingLink =
case event.videoUrl of case event.videoUrl of
@ -71,6 +109,12 @@ eventDetailSidebar event eventInstances =
, text " Watch recording here!" , text " Watch recording here!"
] ]
] ]
eventInstances =
List.filter (\instance -> instance.eventSlug == event.slug) model.eventInstances
speakers =
getSpeakersFromSlugs model.speakers event.speakerSlugs []
in in
div div
[ classList [ classList
@ -80,7 +124,7 @@ eventDetailSidebar event eventInstances =
] ]
] ]
(videoRecordingLink (videoRecordingLink
++ [ speakerSidebar event.speakers ++ [ speakerSidebar speakers
, eventMetaDataSidebar event , eventMetaDataSidebar event
, eventInstancesSidebar eventInstances , eventInstancesSidebar eventInstances
] ]
@ -121,7 +165,7 @@ speakerSidebar speakers =
speakerDetail : Speaker -> Html Msg speakerDetail : Speaker -> Html Msg
speakerDetail speaker = speakerDetail speaker =
li [] li []
[ text speaker.name [ a [ href <| routeToString <| SpeakerRoute speaker.slug ] [ text speaker.name ]
] ]

View file

@ -3,7 +3,8 @@ module Views.FilterView exposing (filterSidebar, applyFilters, parseFilterFromQu
-- Local modules -- Local modules
import Messages exposing (Msg(..)) import Messages exposing (Msg(..))
import Models exposing (Model, EventInstance, Filter, Day) import Models exposing (Model, EventInstance, Filter, Day, FilterQuery, Route(OverviewFilteredRoute), VideoRecordingFilter)
import Routing exposing (routeToString)
-- Core modules -- Core modules
@ -14,7 +15,7 @@ import Regex
-- External modules -- External modules
import Html exposing (Html, text, div, ul, li, span, i, h4) import Html exposing (Html, text, div, ul, li, span, i, h4)
import Html.Attributes exposing (class, classList, href) import Html.Attributes exposing (class, classList)
import Html.Events exposing (onClick) import Html.Events exposing (onClick)
import Date.Extra exposing (Interval(..), equalBy) import Date.Extra exposing (Interval(..), equalBy)
@ -95,7 +96,7 @@ hasRecordingFilter eventInstance =
eventInstance.videoUrl /= "" eventInstance.videoUrl /= ""
videoRecordingFilters : List { name : String, slug : String, filter : EventInstance -> Bool } videoRecordingFilters : List VideoRecordingFilter
videoRecordingFilters = videoRecordingFilters =
[ { name = "Will not be recorded", slug = "not-to-be-recorded", filter = notRecordedFilter } [ { name = "Will not be recorded", slug = "not-to-be-recorded", filter = notRecordedFilter }
, { name = "Will recorded", slug = "to-be-recorded", filter = recordedFilter } , { name = "Will recorded", slug = "to-be-recorded", filter = recordedFilter }
@ -177,7 +178,7 @@ getFilter filterType modelItems query =
List.filterMap (\x -> findFilter modelItems x) filterSlugs List.filterMap (\x -> findFilter modelItems x) filterSlugs
parseFilterFromQuery : String -> Model -> Filter parseFilterFromQuery : FilterQuery -> Model -> Filter
parseFilterFromQuery query model = parseFilterFromQuery query model =
let let
types = types =
@ -195,7 +196,7 @@ parseFilterFromQuery query model =
} }
filterToQuery : Filter -> String filterToQuery : Filter -> FilterQuery
filterToQuery filter = filterToQuery filter =
let let
typePart = typePart =
@ -225,4 +226,4 @@ filterToQuery filter =
result = result =
String.join "&" (List.filter (\x -> x /= "") [ typePart, locationPart, videoPart ]) String.join "&" (List.filter (\x -> x /= "") [ typePart, locationPart, videoPart ])
in in
"#" ++ result routeToString <| OverviewFilteredRoute result

View file

@ -3,8 +3,9 @@ module Views.ScheduleOverview exposing (scheduleOverviewView)
-- Local modules -- Local modules
import Messages exposing (Msg(..)) import Messages exposing (Msg(..))
import Models exposing (Model, Day, EventInstance, Filter) import Models exposing (Model, Day, EventInstance, Filter, Route(EventRoute))
import Views.FilterView exposing (filterSidebar, applyFilters, parseFilterFromQuery) import Views.FilterView exposing (filterSidebar, applyFilters, parseFilterFromQuery)
import Routing exposing (routeToString)
-- External modules -- External modules
@ -50,7 +51,7 @@ dayEventInstanceView eventInstance =
[ ( "event", True ) [ ( "event", True )
, ( "event-in-overview", True ) , ( "event-in-overview", True )
] ]
, href ("#event/" ++ eventInstance.eventSlug) , href <| routeToString <| EventRoute eventInstance.eventSlug
, style , style
[ ( "background-color", eventInstance.backgroundColor ) [ ( "background-color", eventInstance.backgroundColor )
, ( "color", eventInstance.forgroundColor ) , ( "color", eventInstance.forgroundColor )

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 Event, EventInstance, Favorite, EventLocation, EventType from .models import Event, EventInstance, Favorite, EventLocation, EventType, Speaker
class ScheduleConsumer(JsonWebsocketConsumer): class ScheduleConsumer(JsonWebsocketConsumer):
@ -41,12 +41,16 @@ class ScheduleConsumer(JsonWebsocketConsumer):
event_types_query_set = EventType.objects.filter() event_types_query_set = EventType.objects.filter()
event_types = list([x.serialize() for x in event_types_query_set]) event_types = list([x.serialize() for x in event_types_query_set])
speakers_query_set = Speaker.objects.filter(camp=camp)
speakers = list([x.serialize() for x in speakers_query_set])
data = { data = {
"action": "init", "action": "init",
"events": events, "events": events,
"event_instances": event_instances, "event_instances": event_instances,
"event_locations": event_locations, "event_locations": event_locations,
"event_types": event_types, "event_types": event_types,
"speakers": speakers,
"days": days, "days": days,
} }
except Camp.DoesNotExist: except Camp.DoesNotExist:

View file

@ -14,6 +14,8 @@ from django.dispatch import receiver
from django.utils.text import slugify from django.utils.text import slugify
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import reverse_lazy from django.core.urlresolvers import reverse_lazy
from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse_lazy, reverse
from django.core.files.storage import FileSystemStorage from django.core.files.storage import FileSystemStorage
from django.urls import reverse from django.urls import reverse
from django.apps import apps from django.apps import apps
@ -433,8 +435,8 @@ class Event(CampRelatedModel):
'title': self.title, 'title': self.title,
'slug': self.slug, 'slug': self.slug,
'abstract': self.abstract, 'abstract': self.abstract,
'speakers': [ 'speaker_slugs': [
speaker.serialize() speaker.slug
for speaker in self.speakers.all() for speaker in self.speakers.all()
], ],
'video_recording': self.video_recording, 'video_recording': self.video_recording,
@ -613,10 +615,27 @@ 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 get_picture_url(self, size):
return reverse('speaker_picture', kwargs={'camp_slug': self.camp.slug, 'slug': self.slug, 'picture': size})
def get_small_picture_url(self):
return self.get_picture_url('thumbnail')
def get_large_picture_url(self):
return self.get_picture_url('large')
def serialize(self): def serialize(self):
data = { data = {
'name': self.name, 'name': self.name,
'slug': self.slug,
'biography': self.biography,
} }
if self.picture_small and self.picture_large:
data['large_picture_url'] = self.get_large_picture_url()
data['small_picture_url'] = self.get_small_picture_url()
return data return data